diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 501f6a74..00000000 --- a/.gitignore +++ /dev/null @@ -1,16 +0,0 @@ -# SVN -*.svn - -# ECLIPSE -*.settings -*.project -*.pydevproject - -# COMPILED PYTHON -*.pyc - -# debug -qad_debug.* - -Thumbs.db -*.psproj \ No newline at end of file diff --git a/.project b/.project new file mode 100644 index 00000000..d2a6e666 --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + QAD + + + + + + org.python.pydev.PyDevBuilder + + + + + + org.python.pydev.pythonNature + + diff --git a/.pydevproject b/.pydevproject new file mode 100644 index 00000000..f1d2317b --- /dev/null +++ b/.pydevproject @@ -0,0 +1,8 @@ + + + + Default + + python interpreter + + diff --git a/Appunti per QAD.docx b/Appunti per QAD.docx deleted file mode 100644 index ab97923d..00000000 Binary files a/Appunti per QAD.docx and /dev/null differ diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..818433ec --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/Makefile b/Makefile index 8ee6592c..8202a42f 100644 --- a/Makefile +++ b/Makefile @@ -1,32 +1,32 @@ - -###### EDIT ##################### - - -#UI files to compile -#UI_FILES = qad.ui qad_dsettings.ui qad_dimstyle.ui -#Qt resource files to compile -RESOURCES = qad.qrc qad_dsettings.qrc - - -################################# -# DO NOT EDIT FOLLOWING - -#COMPILED_UI = $(UI_FILES:%.ui=%_ui.py) -COMPILED_RESOURCES = $(RESOURCES:%.qrc=%_rc.py) - -all : resources -#all : resources ui - -resources : $(COMPILED_RESOURCES) - -#ui : $(COMPILED_UI) - -#%_ui.py : %.ui -# pyuic4 $< -o $@ - -%_rc.py : %.qrc - pyrcc4 $< -o $@ - -clean : - #$(RM) $(COMPILED_UI) $(COMPILED_RESOURCES) $(COMPILED_UI:.py=.pyc) $(COMPILED_RESOURCES:.py=.pyc) - $(RM) $(COMPILED_RESOURCES) $(COMPILED_RESOURCES:.py=.pyc) + +###### EDIT ##################### + + +#UI files to compile +#UI_FILES = qad.ui qad_dsettings.ui qad_dimstyle.ui +#Qt resource files to compile +RESOURCES = qad.qrc qad_dsettings.qrc + + +################################# +# DO NOT EDIT FOLLOWING + +#COMPILED_UI = $(UI_FILES:%.ui=%_ui.py) +COMPILED_RESOURCES = $(RESOURCES:%.qrc=%_rc.py) + +all : resources +#all : resources ui + +resources : $(COMPILED_RESOURCES) + +#ui : $(COMPILED_UI) + +#%_ui.py : %.ui +# pyuic4 $< -o $@ + +%_rc.py : %.qrc + pyrcc4 $< -o $@ + +clean : + #$(RM) $(COMPILED_UI) $(COMPILED_RESOURCES) $(COMPILED_UI:.py=.pyc) $(COMPILED_RESOURCES:.py=.pyc) + $(RM) $(COMPILED_RESOURCES) $(COMPILED_RESOURCES:.py=.pyc) diff --git a/README.md b/README.md index 2d034de6..c7fdbdca 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,28 @@ #QAD# + + +---------- +Quantum Aided Design - Cad Tools plugin for QGIS 3. ----------- +###Developers### -Quantum Aided Design - Cad Tools plugin for QGIS 2. +gam17 -###Developers### ###User Interface Designers:## + +gam17 + +em-rezende + ###Documentation:## + +gam17 + +###Testers:## + +Aitor Gil (jaitor1) + +Gabriel De Luca + +Tony Shepherd diff --git a/__init__.py b/__init__.py index 34c7d135..ec1d5b3b 100644 --- a/__init__.py +++ b/__init__.py @@ -1,39 +1,29 @@ -""" -/*************************************************************************** - QAD - A QGIS plugin - Selezione di layer attraverso gli oggetti grafici - ------------------- - begin : 2012-01-12 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ - This script initializes the plugin, making it known to QGIS. -""" - - -def name(): - return "Quantum Aided Design" -def description(): - return "Comandi di editazione grafica in stile CAD" -def version(): - return "Version 2.0" -def icon(): - return ":/plugins/qad/icons/qad.png" -def qgisMinimumVersion(): - return "2.0" -def classFactory(iface): - - # load Qad class from file qad - from qad import Qad - return Qad(iface) +""" +/*************************************************************************** + QAD + A QGIS plugin + Selezione di layer attraverso gli oggetti grafici + ------------------- + begin : 2019-09-16 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + This script initializes the plugin, making it known to QGIS. +""" + + +def classFactory(iface): + + # load Qad class from file qad + from .qad import Qad + return Qad(iface) diff --git a/auth.ini b/auth.ini new file mode 100644 index 00000000..4090b91c --- /dev/null +++ b/auth.ini @@ -0,0 +1 @@ +N=1 >1=O10/1@01 = \ No newline at end of file diff --git a/changelog b/changelog new file mode 100644 index 00000000..605ea05c --- /dev/null +++ b/changelog @@ -0,0 +1,160 @@ +version 3.0.8 +--------------------------- +* 2024-10-07 Fixed bug on m2p option (reported by Fabrice DEMIEL) + + +version 3.0.7 +--------------------------- + +* 2024-10-07 Fixed bug on stretching last segment of a polygon +* 2024-09-19 New set of icons for menus and buttons (proposed and edited by em-rezende) +* 2024-08-28 Fixed bug on label size calculation (reported by em-rezende) +* 2024-08-28 Fixed bug on Fedora linux version (reported by Jesper Reenberg) +* 2024-08-28 Fixed bug on intersection snap mode +* 2024-08-28 Fixed bug on stretching and saving dimension + + +version 3.0.6 +--------------------------- + +* 2024-05-27 Fixed calculation to find intersections between 2 ellipses and between a circle and an ellipse +* 2024-05-16 Fixed bug on draw a line from tangent to tangent +* 2024-05-16 Fixed bug on dimension (reported by em-rezende) +* 2024-05-16 Full support for curved type layer (compoundCurve, multiCurve, curvePolygon, multiSurface) +* 2024-02-14 Selecting a point you can choose the "Midpoint between 2 points" option +* 2024-01-19 Fixed bug on selection options (reported by Justin Ralston) +* 2023-10-17 Added Polar Tracking options: additional angles. (inspired by Jonathan GUILLERM) +* 2023-09-29 The form to fill the data does not appear, now it does. (proposed by franksalas1965) +* 2023-09-29 qad offset command now use the native offset function provided by qgis api +* 2023-04-07 pedit now can join multilinestring if composed by only one part. +* 2022-07-27 Fixed bug on intersections between circle and ellipse +* 2022-07-27 Changed algorithm to auto-detect arc and circle +* 2022-07-27 Fixed bug on delete last vertex of a polyline +* 2022-06-20 Fixed bug on OFFSET-tool creates wrong calculations with PLINE-vectors (reported by Atol2022) +* 2022-03-28 Fixed bug on problems using vector layers having a "scale based" visibility (reported by TheLooka) +* 2022-05-29 Fixed bug on Trimming a circle into an arc (reported by Jopie01) +* 2022-05-29 Fixed bug on Error when trying to trim a line to a circle (reported by Jopie01) +* 2022-05-29 Fixed bug on MPOLYGON-tool crashes when trying to use trace-function (reported by Atol2022) + + +version 3.0.5 +--------------------------- + +* 2021-08-30 Fixed bug on pedit -> join option + + +version 3.0.4 +--------------------------- + +* 2020-09-10 Fixed bug when rotating, scaling or mirroring symbol entities (reported by Camilo Corredor) +* 2020-09-10 Now QAD honors QGIS's snap settings (ALL LAYERS, ACTIVE LAYER, ADVANCED) (by Olivier Dalang) + + +version 3.0.3 +--------------------------- + +* 2020-08-12 Fixed bug when changing drafting tool config (e.g. colors, etc.) (reported by sdnxgr) +* 2020-08-12 Fixed bug on selected object which gets deselected when the mouse moves on any vertex of that object (reported by reancris) +* 2020-06-29 Fixed bug on and command (reported by Gabriel De Luca) + + +version 3.0.2 +--------------------------- + +* 2020-05-15 Fixed bug on command (reported by Gabriel De Luca) + + +version 3.0.1 +--------------------------- + +* 2020-03-05 Fixed bug on command + + +version 2.14.5: +--------------------------- + +* 2018-10-11 Fixed bug on offset command when custom coordinate reference system is used (reported by Dave Burkholder) + +* 2018-10-11 Added command + +* 2018-10-11 Added right click control in options dialog + +* 2018-10-11 Added , system variables + + +version 2.14.4: +--------------------------- + +* 2018-05-11 Added French localization (by dobriseb) + +* 2018-05-11 Added Dynamic Input + +* 2018-05-11 Added , , , , , , , system variables + +* 2017-03-01 Fixed bug "If you want to draw a circle with a given diameter it draws instead a circle with the radius specified as diameter" (reported by Mazur93) + +* 2017-03-01 F2 key to toggle the text command windows visibility + +* 2017-02-08 Added command (inspired by Simon Haufe) + +* 2017-02-08 Added property for DIM commands (inspired by Simon Haufe) + +If you want to draw a circle with a given diameter it draws instead a circle with the radius specified as diameter + + +version 2.14.2: +--------------------------- + +* 2017-07-24 Fixed bug on offset algorithm (reported by peter-shr) + +* 2017-05-08 Fixed bug on duplicating dimension layers (reported by Benni5K) + +* 2017-04-28 Fixed error on traceback when moving circle in a linestring layer (reported by Benni5K) + + +version 2.14.1: +--------------------------- + +* 2017-03-13 Added contextual menu on right click event for command options + +* 2017-03-13 Fixed bug on new feature creation when id is a db sequence result (reported by Benni5K) + + +version 2.8.14: +--------------------------- + +* 2016-12-12 Fixed bug on command (Spanish localization only, reported by dortegat) + +* 2016-12-12 Fixed bug on "OSNAP is enabled only if polar snapping is also enabled" (reported by sdnxgr) + +* 2016-12-12 Fixed bug on "user defined coordinate reference system. Like USER:100000" (reported by AndreyMai) + +* 2016-12-12 Fixed bug on "The zoom (scroll wheel) isn't working when using QAD" (reported by patricev) + + +version 2.8.13: +--------------------------- + +* 2016-10-14 Added "hold Ctrl to switch direction" option on and commands + +* 2016-10-12 Added spanish localization + +* 2016-09-12 Added command + +* 2016-06-09 Distinction between project variables and global variables (inspired by sdnxgr) + +* 2016-09-09 Added command + +* 2016-06-30 Added command + +* 2016-06-30 Removed system variable (used as dim style property) + +* 2016-06-16 Added system variable + +* 2016-06-13 Fixed bug on dimension saving + +* 2016-05-05 Added , , , commands + +* 2016-05-05 Added and system variables + +* 2016-05-05 Added and in MAPMPEDIT command \ No newline at end of file diff --git a/qad_arc_cmd.py b/cmd/qad_arc_cmd.py similarity index 81% rename from qad_arc_cmd.py rename to cmd/qad_arc_cmd.py index 3f489e12..db5f4ada 100644 --- a/qad_arc_cmd.py +++ b/cmd/qad_arc_cmd.py @@ -1,1057 +1,1114 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - comando ARC per disegnare un arco - - ------------------- - begin : 2013-05-22 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -import math - - -from qad_getpoint import * -from qad_arc_maptool import * -from qad_generic_cmd import QadCommandClass -from qad_msg import QadMsg -from qad_textwindow import * -import qad_utils -import qad_layer -import qad_grip - - -# Classe che gestisce il comando ARC -class QadARCCommandClass(QadCommandClass): - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadARCCommandClass(self.plugIn) - - def getName(self): - return QadMsg.translate("Command_list", "ARC") - - def getEnglishName(self): - return "ARC" - - def connectQAction(self, action): - QObject.connect(action, SIGNAL("triggered()"), self.plugIn.runARCCommand) - - def getIcon(self): - return QIcon(":/plugins/qad/icons/arc.png") - - def getNote(self): - # impostare le note esplicative del comando - return QadMsg.translate("Command_ARC", "Draws an arc by many methods.") - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - self.vertices = [] - - def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): - if (self.plugIn is not None): - if self.PointMapTool is None: - self.PointMapTool = Qad_arc_maptool(self.plugIn) - return self.PointMapTool - else: - return None - - def run(self, msgMapTool = False, msg = None): - self.isValidPreviousInput = True # per gestire il comando anche in macro - - if self.plugIn.canvas.mapRenderer().destinationCrs().geographicFlag(): - self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) - return True # fine comando - - currLayer, errMsg = qad_layer.getCurrLayerEditable(self.plugIn.canvas, QGis.Line) - if currLayer is None: - self.showErr(errMsg) - return True # fine comando - - #========================================================================= - # RICHIESTA PRIMO PUNTO o CENTRO - if self.step == 0: # inizio del comando - # imposto il map tool - self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_START_PT) - keyWords = QadMsg.translate("Command_ARC", "Center") - - prompt = QadMsg.translate("Command_ARC", "Specify the start point of the arc or [{0}]:").format(keyWords) - - englishKeyWords = "Center" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o enter o una parola chiave - # msg, inputType, default, keyWords, nessun controllo di modo - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - None, \ - keyWords, QadInputModeEnum.NONE) - self.step = 1 - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA PRIMO PUNTO o CENTRO - elif self.step == 1: # dopo aver atteso un punto o enter o una parola chiave si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if value is None: - if self.plugIn.lastPoint is not None: - value = self.plugIn.lastPoint - else: - return True # fine comando - - if type(value) == QgsPoint: # se é stato inserito il punto iniziale dell'arco - self.startPt = value - self.plugIn.setLastPoint(value) - - # imposto il map tool - self.getPointMapTool().arcStartPt = self.startPt - self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_PT_KNOWN_ASK_FOR_SECOND_PT) - - keyWords = QadMsg.translate("Command_ARC", "Center") + "/" + \ - QadMsg.translate("Command_ARC", "End") - - prompt = QadMsg.translate("Command_ARC", "Specify second point of the arc or [{0}]:").format(keyWords) - - englishKeyWords = "Center" + "/" + "End" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o una parola chiave - # msg, inputType, default, keyWords - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - None, \ - keyWords, QadInputModeEnum.NONE) - - self.step = 2 - return False - else: # si vuole inserire il centro dell'arco - # imposto il map tool - self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_CENTER_PT) - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_ARC", "Specify the center of the arc: ")) - - self.step = 13 - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA SECONDO PUNTO o CENTRO o FINE - elif self.step == 2: # dopo aver atteso un punto o una parola chiave si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("Command_ARC", "Center") or value == "Center": - # imposto il map tool - self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_PT_KNOWN_ASK_FOR_CENTER_PT) - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_ARC", "Specify the center of the arc: ")) - self.step = 4 - elif value == QadMsg.translate("Command_ARC", "End") or value == "End": - # imposto il map tool - self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_PT_KNOWN_ASK_FOR_END_PT) - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_ARC", "Specify the final point of the arc: ")) - self.step = 8 - elif type(value) == QgsPoint: # se é stato inserito il secondo punto dell'arco - self.secondPt = value - # imposto il map tool - self.getPointMapTool().arcSecondPt = self.secondPt - self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_SECOND_PT_KNOWN_ASK_FOR_END_PT) - - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_ARC", "Specify the final point of the arc: ")) - self.step = 3 - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA PUNTO FINALE DELL'ARCO (da step = 2) - elif self.step == 3: # dopo aver atteso un punto si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - self.endPt = value - - arc = QadArc() - if arc.fromStartSecondEndPts(self.startPt, self.secondPt, self.endPt) == True: - self.plugIn.setLastPoint(arc.getEndPt()) - points = arc.asPolyline() - if points is not None: - # se i punti sono così vicini da essere considerati uguali - if qad_utils.ptNear(self.startPt, arc.getStartPt()): - self.plugIn.setLastSegmentAng(arc.getTanDirectionOnEndPt()) - else: - self.plugIn.setLastSegmentAng(arc.getTanDirectionOnStartPt() + math.pi) - - qad_layer.addLineToLayer(self.plugIn, currLayer, points) - return True # fine comando - - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_ARC", "Specify the final point of the arc: ")) - self.isValidPreviousInput = False # per gestire il comando anche in macro - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA CENTRO DELL'ARCO (da step = 2) - elif self.step == 4: # dopo aver atteso un punto si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - self.centerPt = value - self.plugIn.setLastPoint(value) - - # imposto il map tool - self.getPointMapTool().arcCenterPt = self.centerPt - self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_CENTER_PT_KNOWN_ASK_FOR_END_PT) - - keyWords = QadMsg.translate("Command_ARC", "Angle") + "/" + \ - QadMsg.translate("Command_ARC", "chord Length") - - prompt = QadMsg.translate("Command_ARC", "Specify the final point of the arc or [{0}]: ").format(keyWords) - - englishKeyWords = "Angle" + "/" + "chord Length" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o una parola chiave - # msg, inputType, default, keyWords, valori nulli non ammessi - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - None, \ - keyWords, QadInputModeEnum.NOT_NULL) - - self.step = 5 - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA "Specificare punto finale dell'arco o [Angolo/Lunghezza corda]: " (da step = 4) - elif self.step == 5: # dopo aver atteso un punto o una parola chiave si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("Command_ARC", "Angle") or value == "Angle": - # imposto il map tool - self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_CENTER_PT_KNOWN_ASK_FOR_ANGLE) - # si appresta ad attendere un punto o un numero reale - # msg, inputType, default, keyWords, valori nulli non ammessi - self.waitFor(QadMsg.translate("Command_ARC", "Specify the included angle: "), \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.ANGLE, \ - None, "", \ - QadInputModeEnum.NOT_NULL | QadInputModeEnum.NOT_ZERO) - self.step = 6 - return False - elif value == QadMsg.translate("Command_ARC", "chord Length") or value == "chord Length": - # imposto il map tool - self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_CENTER_PT_KNOWN_ASK_FOR_CHORD) - # si appresta ad attendere un punto o un numero reale - # msg, inputType, default, keyWords, valori positivi - self.waitFor(QadMsg.translate("Command_ARC", "Specify the chord length: "), \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT, \ - None, "", \ - QadInputModeEnum.NOT_NULL | QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) - self.step = 7 - return False - elif type(value) == QgsPoint: # se é stato inserito il punto finale dell'arco - self.endPt = value - - arc = QadArc() - if arc.fromStartCenterEndPts(self.startPt, self.centerPt, self.endPt) == True: - self.plugIn.setLastPoint(arc.getEndPt()) - points = arc.asPolyline() - if points is not None: - # se i punti sono così vicini da essere considerati uguali - if qad_utils.ptNear(self.startPt, arc.getStartPt()): - self.plugIn.setLastSegmentAng(arc.getTanDirectionOnEndPt()) - else: - self.plugIn.setLastSegmentAng(arc.getTanDirectionOnStartPt() + math.pi) - - qad_layer.addLineToLayer(self.plugIn, currLayer, points) - return True # fine comando - - keyWords = QadMsg.translate("Command_ARC", "Angle") + "/" + \ - QadMsg.translate("Command_ARC", "chord Length") - prompt = QadMsg.translate("Command_ARC", "Specify the final point of the arc or [{0}]: ").format(keyWords) - - englishKeyWords = "Angle" + "/" + "chord Length" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o una parola chiave - # msg, inputType, default, keyWords, valori nulli non ammessi - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - None, \ - keyWords, QadInputModeEnum.NOT_NULL) - self.isValidPreviousInput = False # per gestire il comando anche in macro - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA "Specificare angolo inscritto: " (da step = 5) - elif self.step == 6: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == QgsPoint: - self.angle = qad_utils.getAngleBy2Pts(self.centerPt, value) - else: - self.angle = value - - arc = QadArc() - if arc.fromStartCenterPtsAngle(self.startPt, self.centerPt, self.angle) == True: - self.plugIn.setLastPoint(arc.getEndPt()) - points = arc.asPolyline() - if points is not None: - # se i punti sono così vicini da essere considerati uguali - if qad_utils.ptNear(self.startPt, arc.getStartPt()): - self.plugIn.setLastSegmentAng(arc.getTanDirectionOnEndPt()) - else: - self.plugIn.setLastSegmentAng(arc.getTanDirectionOnStartPt() + math.pi) - - qad_layer.addLineToLayer(self.plugIn, currLayer, points) - return True # fine comando - - # si appresta ad attendere un punto o un numero reale - # msg, inputType, default, keyWords, valori nulli non ammessi - self.waitFor(QadMsg.translate("Command_ARC", "Specify the included angle: "), \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.ANGLE, \ - None, "", \ - QadInputModeEnum.NOT_NULL | QadInputModeEnum.NOT_ZERO) - self.isValidPreviousInput = False # per gestire il comando anche in macro - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA "Specificare lunghezza della corda: " (da step = 5) - elif self.step == 7: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == QgsPoint: - self.chord = qad_utils.getDistance(self.startPt, value) - else: - self.chord = value - - arc = QadArc() - if arc.fromStartCenterPtsChord(self.startPt, self.centerPt, self.chord) == True: - self.plugIn.setLastPoint(arc.getEndPt()) - points = arc.asPolyline() - if points is not None: - # se i punti sono così vicini da essere considerati uguali - if qad_utils.ptNear(self.startPt, arc.getStartPt()): - self.plugIn.setLastSegmentAng(arc.getTanDirectionOnEndPt()) - else: - self.plugIn.setLastSegmentAng(arc.getTanDirectionOnStartPt() + math.pi) - - qad_layer.addLineToLayer(self.plugIn, currLayer, points) - return True # fine comando - - # si appresta ad attendere un punto o un numero reale - # msg, inputType, default, keyWords, valori positivi ammessi - self.waitFor(QadMsg.translate("Command_ARC", "Specify the chord length: "), \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT, \ - None, "", \ - QadInputModeEnum.NOT_NULL | QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) - self.isValidPreviousInput = False # per gestire il comando anche in macro - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA "Specificare punto finale dell'arco: " (da step = 1) - elif self.step == 8: # dopo aver atteso un punto si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - self.endPt = value - self.plugIn.setLastPoint(self.endPt) - - # imposto il map tool - self.getPointMapTool().arcEndPt = self.endPt - self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_END_PT_KNOWN_ASK_FOR_CENTER) - - keyWords = QadMsg.translate("Command_ARC", "Angle") + "/" + \ - QadMsg.translate("Command_ARC", "Direction") + "/" + \ - QadMsg.translate("Command_ARC", "Radius") - - prompt = QadMsg.translate("Command_ARC", "Specify the center point of the arc or [{0}]: ").format(keyWords) - - englishKeyWords = "Angle" + "/" + "Direction" + "/" + "Radius" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o una parola chiave - # msg, inputType, default, keyWords, valori nulli non ammessi - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - None, \ - keyWords, QadInputModeEnum.NOT_NULL) - - self.step = 9 - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA "Specificare centro dell'arco o [Angolo/Direzione/Raggio]: " (da step = 8) - elif self.step == 9: # dopo aver atteso un punto o una parola chiave si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("Command_ARC", "Angle") or value == "Angle": - # imposto il map tool - self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_END_PT_KNOWN_ASK_FOR_ANGLE) - # si appresta ad attendere un punto o un numero reale - # msg, inputType, default, keyWords, isNullable - self.waitFor(QadMsg.translate("Command_ARC", "Specify the included angle: "), \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.ANGLE, \ - None, "", QadInputModeEnum.NOT_NULL | QadInputModeEnum.NOT_ZERO) - self.step = 10 - return False - elif value == QadMsg.translate("Command_ARC", "Direction") or value == "Direction": - # imposto il map tool - self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_END_PT_KNOWN_ASK_FOR_TAN) - # si appresta ad attendere un punto o un numero reale - # msg, inputType, default, keyWords, isNullable - self.waitFor(QadMsg.translate("Command_ARC", "Specify the tangent direction for the start point of the arc: "), \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.ANGLE, \ - None, "", QadInputModeEnum.NOT_NULL) - self.step = 11 - return False - elif value == QadMsg.translate("Command_ARC", "Radius") or value == "Radius": - # imposto il map tool - self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_END_PT_KNOWN_ASK_FOR_RADIUS) - # si appresta ad attendere un punto o un numero reale - # msg, inputType, default, keyWords, isNullable - self.waitFor(QadMsg.translate("Command_ARC", "Specify the radius of the arc: "), \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT, \ - None, "", \ - QadInputModeEnum.NOT_NULL | QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) - self.step = 12 - return False - elif type(value) == QgsPoint: # se é stato inserito il centro dell'arco - self.centerPt = value - - arc = QadArc() - if arc.fromStartCenterEndPts(self.startPt, self.centerPt, self.endPt) == True: - self.plugIn.setLastPoint(arc.getEndPt()) - points = arc.asPolyline() - if points is not None: - # se i punti sono così vicini da essere considerati uguali - if qad_utils.ptNear(self.startPt, arc.getStartPt()): - self.plugIn.setLastSegmentAng(arc.getTanDirectionOnEndPt()) - else: - self.plugIn.setLastSegmentAng(arc.getTanDirectionOnStartPt() + math.pi) - - qad_layer.addLineToLayer(self.plugIn, currLayer, points) - return True # fine comando - - keyWords = QadMsg.translate("Command_ARC", "Angle") + "/" + \ - QadMsg.translate("Command_ARC", "Direction") + "/" + \ - QadMsg.translate("Command_ARC", "Radius") - - prompt = QadMsg.translate("Command_ARC", "Specify the center point of the arc or [{0}]: ").format(keyWords) - - englishKeyWords = "Angle" + "/" + "Direction" + "/" + "Radius" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o una parola chiave - # msg, inputType, default, keyWords, isNullable - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - None, \ - keyWords, QadInputModeEnum.NOT_NULL) - self.isValidPreviousInput = False # per gestire il comando anche in macro - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA "Specificare angolo inscritto: " (da step = 9) - elif self.step == 10: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == QgsPoint: - self.angle = qad_utils.getAngleBy2Pts(self.startPt, value) - else: - self.angle = value - - arc = QadArc() - if arc.fromStartEndPtsAngle(self.startPt, self.endPt, self.angle) == True: - self.plugIn.setLastPoint(arc.getEndPt()) - points = arc.asPolyline() - if points is not None: - # se i punti sono così vicini da essere considerati uguali - if qad_utils.ptNear(self.startPt, arc.getStartPt()): - self.plugIn.setLastSegmentAng(arc.getTanDirectionOnEndPt()) - else: - self.plugIn.setLastSegmentAng(arc.getTanDirectionOnStartPt() + math.pi) - - qad_layer.addLineToLayer(self.plugIn, currLayer, points) - return True # fine comando - - # si appresta ad attendere un punto o un numero reale - # msg, inputType, default, keyWords, valori non nulli - self.waitFor(QadMsg.translate("Command_ARC", "Specify the included angle: "), \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.ANGLE, \ - None, "", \ - QadInputModeEnum.NOT_NULL | QadInputModeEnum.NOT_ZERO) - self.isValidPreviousInput = False # per gestire il comando anche in macro - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA "Specificare direzione tangente per il punto iniziale dell'arco: " (da step = 9) - elif self.step == 11: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == QgsPoint: - self.angleTan = qad_utils.getAngleBy2Pts(self.startPt, value) - else: - self.angleTan = value - - arc = QadArc() - if arc.fromStartEndPtsTan(self.startPt, self.endPt, self.angleTan) == True: - self.plugIn.setLastPoint(arc.getEndPt()) - points = arc.asPolyline() - if points is not None: - # se i punti sono così vicini da essere considerati uguali - if qad_utils.ptNear(self.startPt, arc.getStartPt()): - self.plugIn.setLastSegmentAng(arc.getTanDirectionOnEndPt()) - else: - self.plugIn.setLastSegmentAng(arc.getTanDirectionOnStartPt() + math.pi) - - qad_layer.addLineToLayer(self.plugIn, currLayer, points) - return True # fine comando - - # si appresta ad attendere un punto o un numero reale - # msg, inputType, default, keyWords, isNullable - self.waitFor(QadMsg.translate("Command_ARC", "Specify the tangent direction for the start point of the arc: "), \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.ANGLE, \ - None, "", QadInputModeEnum.NOT_NULL) - self.isValidPreviousInput = False # per gestire il comando anche in macro - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA "Specificare raggio dell'arco: " (da step = 9) - elif self.step == 12: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == QgsPoint: - self.radius = qad_utils.getDistance(self.endPt, value) - else: - self.radius = value - - self.plugIn.setLastRadius(self.radius) - - arc = QadArc() - if arc.fromStartEndPtsRadius(self.startPt, self.endPt, self.radius) == True: - self.plugIn.setLastPoint(arc.getEndPt()) - points = arc.asPolyline() - if points is not None: - # se i punti sono così vicini da essere considerati uguali - if qad_utils.ptNear(self.startPt, arc.getStartPt()): - self.plugIn.setLastSegmentAng(arc.getTanDirectionOnEndPt()) - else: - self.plugIn.setLastSegmentAng(arc.getTanDirectionOnStartPt() + math.pi) - - qad_layer.addLineToLayer(self.plugIn, currLayer, points) - return True # fine comando - - # si appresta ad attendere un punto o un numero reale - # msg, inputType, default, keyWords, valori positivi - self.waitFor(QadMsg.translate("Command_ARC", "Specify the radius of the arc: "), \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT, \ - None, "", \ - QadInputModeEnum.NOT_NULL | QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) - self.isValidPreviousInput = False # per gestire il comando anche in macro - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA CENTRO DELL'ARCO (da step = 1) - elif self.step == 13: # dopo aver atteso un punto si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - self.centerPt = value - self.plugIn.setLastPoint(value) - - # imposto il map tool - self.getPointMapTool().arcCenterPt = self.centerPt - self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.CENTER_PT_KNOWN_ASK_FOR_START_PT) - - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_ARC", "Specify the start point of the arc: ")) - self.step = 14 - - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA PUNTO INIZIALE DELL'ARCO (da step = 13) - elif self.step == 14: # dopo aver atteso un punto si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - self.startPt = value - self.plugIn.setLastPoint(value) - - # imposto il map tool - self.getPointMapTool().arcStartPt = self.startPt - self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_CENTER_PT_KNOWN_ASK_FOR_END_PT) - - keyWords = QadMsg.translate("Command_ARC", "Angle") + "/" + \ - QadMsg.translate("Command_ARC", "chord Length") - - prompt = QadMsg.translate("Command_ARC", "Specify the final point of the arc or [{0}]: ").format(keyWords) - - englishKeyWords = "Angle" + "/" + "chord Length" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o una parola chiave - # msg, inputType, default, keyWords, isNullable - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - None, \ - keyWords, QadInputModeEnum.NOT_NULL) - - self.step = 5 - return False - - -#============================================================================ -# Classe che gestisce il comando per cambiare il raggio di un arco per i grip -#============================================================================ -class QadGRIPCHANGEARCRADIUSCommandClass(QadCommandClass): - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadGRIPCHANGEARCRADIUSCommandClass(self.plugIn) - - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - self.entity = None - self.skipToNextGripCommand = False - self.copyEntities = False - self.basePt = QgsPoint() - self.nOperationsToUndo = 0 - - - def __del__(self): - QadCommandClass.__del__(self) - - - def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): - if (self.plugIn is not None): - if self.PointMapTool is None: - self.PointMapTool = Qad_gripChangeArcRadius_maptool(self.plugIn) - return self.PointMapTool - else: - return None - - - #============================================================================ - # setSelectedEntityGripPoints - #============================================================================ - def setSelectedEntityGripPoints(self, entitySetGripPoints): - # lista delle entityGripPoint con dei grip point selezionati - # setta la prima entità con un grip selezionato - self.entity = None - for entityGripPoints in entitySetGripPoints.entityGripPoints: - for gripPoint in entityGripPoints.gripPoints: - # grip point selezionato - if gripPoint.getStatus() == qad_grip.QadGripStatusEnum.SELECTED: - # verifico se l'entità appartiene ad uno stile di quotatura - if entityGripPoints.entity.isDimensionComponent(): - return False - if entityGripPoints.entity.getEntityType() != QadEntityGeomTypeEnum.ARC: - return False - - self.entity = entityGripPoints.entity - arc = entityGripPoints.entity.getQadGeom() # arco in map coordinate - self.basePt.set(arc.center.x(), arc.center.y()) - return True - return False - - - #============================================================================ - # changeRadius - #============================================================================ - def changeRadius(self, radius): - # radius = nuovo raggio dell'arco - if radius <= 0: - return False - arc = self.entity.getQadGeom() - arc.radius = radius - points = arc.asPolyline() - if points is None: - return False - - g = QgsGeometry.fromPolyline(points) - f = self.entity.getFeature() - f.setGeometry(g) - - self.plugIn.beginEditCommand("Feature stretched", [self.entity.layer]) - - if self.copyEntities == False: - # plugIn, layer, feature, refresh, check_validity - if qad_layer.updateFeatureToLayer(self.plugIn, self.entity.layer, f, False, False) == False: - self.plugIn.destroyEditCommand() - return False - else: - # plugIn, layer, features, coordTransform, refresh, check_validity - if qad_layer.addFeatureToLayer(self.plugIn, self.entity.layer, f, None, False, False) == False: - self.plugIn.destroyEditCommand() - return False - - self.plugIn.setLastRadius(radius) - self.plugIn.endEditCommand() - self.nOperationsToUndo = self.nOperationsToUndo + 1 - - return True - - - #============================================================================ - # waitForRadius - #============================================================================ - def waitForRadius(self): - keyWords = QadMsg.translate("Command_GRIP", "Base point") + "/" + \ - QadMsg.translate("Command_GRIP", "Copy") + "/" + \ - QadMsg.translate("Command_GRIP", "Undo") + "/" + \ - QadMsg.translate("Command_GRIP", "eXit") - prompt = QadMsg.translate("Command_GRIPCHANGEARCRADIUS", "Specify the radius or [{0}]: ").format(keyWords) - - englishKeyWords = "Base point" + "/" + "Copy" + "/" + "Undo" + "/" "eXit" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o enter o una parola chiave - # msg, inputType, default, keyWords, valori positivi - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT | QadInputTypeEnum.KEYWORDS, \ - None, \ - keyWords, QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) - self.step = 1 - # imposto il map tool - self.getPointMapTool().setEntity(self.entity) # setta basePt nel centro dell'arco - self.getPointMapTool().basePt = self.basePt - self.getPointMapTool().setMode(Qad_gripChangeArcRadius_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_RADIUS_PT) - - - #============================================================================ - # waitForBasePt - #============================================================================ - def waitForBasePt(self): - self.step = 2 - # imposto il map tool - self.getPointMapTool().setMode(Qad_gripChangeArcRadius_maptool_ModeEnum.ASK_FOR_BASE_PT) - - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_GRIP", "Specify base point: ")) - - - #============================================================================ - # run - #============================================================================ - def run(self, msgMapTool = False, msg = None): - if self.plugIn.canvas.mapRenderer().destinationCrs().geographicFlag(): - self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) - return True # fine comando - - #========================================================================= - # RICHIESTA SELEZIONE OGGETTI - if self.step == 0: # inizio del comando - if self.entity is None: # non ci sono oggetti da stirare - return True - self.showMsg(QadMsg.translate("Command_GRIPCHANGEARCRADIUS", "\n** RADIUS **\n")) - # si appresta ad attendere raggio - self.waitForRadius() - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DEL RAGGIO DI RACCORDO (da step = 1) - elif self.step == 1: - ctrlKey = False - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - if self.copyEntities == False: - self.skipToNextGripCommand = True - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - ctrlKey = self.getPointMapTool().ctrlKey - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("Command_GRIP", "Base point") or value == "Base point": - # si appresta ad attendere il punto base - self.waitForBasePt() - elif value == QadMsg.translate("Command_GRIP", "Copy") or value == "Copy": - # Copia entità lasciando inalterate le originali - self.copyEntities = True - # si appresta ad attendere raggio - self.waitForRadius() - elif value == QadMsg.translate("Command_GRIP", "Undo") or value == "Undo": - if self.nOperationsToUndo > 0: - self.nOperationsToUndo = self.nOperationsToUndo - 1 - self.plugIn.undoEditCommand() - else: - self.showMsg(QadMsg.translate("QAD", "\nThe command has been canceled.")) - # si appresta ad attendere raggio - self.waitForRadius() - elif value == QadMsg.translate("Command_GRIP", "eXit") or value == "eXit": - return True # fine comando - elif type(value) == QgsPoint or type(value) == float: # se é stato inserito il raggio - if type(value) == QgsPoint: # se é stato inserito il raggio con un punto - if value == self.basePt: - self.showMsg(QadMsg.translate("QAD", "\nThe value must be positive and not zero.")) - # si appresta ad attendere raggio - self.waitForRadius() - return False - - radius = qad_utils.getDistance(self.basePt, value) - else: - radius = value - - if ctrlKey: - self.copyEntities = True - - self.changeRadius(radius) - - if self.copyEntities == False: - return True - # si appresta ad attendere raggio - self.waitForRadius() - else: - if self.copyEntities == False: - self.skipToNextGripCommand = True - return True # fine comando - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA PUNTO BASE (da step = 1) - elif self.step == 2: # dopo aver atteso un punto - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - pass # opzione di default - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == QgsPoint: # se é stato inserito il punto base - self.basePt.set(value.x(), value.y()) - # imposto il map tool - self.getPointMapTool().basePt = self.basePt - - # si appresta ad attendere raggio - self.waitForRadius() - - return False +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin OK + + comando ARC per disegnare un arco + + ------------------- + begin : 2013-05-22 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.PyQt.QtGui import QIcon +from qgis.core import QgsWkbTypes, QgsGeometry, QgsPointXY +import math + + +from ..qad_arc import QadArc +from ..qad_getpoint import QadGetPointDrawModeEnum +from .qad_arc_maptool import Qad_arc_maptool, Qad_arc_maptool_ModeEnum, \ + Qad_gripChangeArcRadius_maptool, Qad_gripChangeArcRadius_maptool_ModeEnum +from .qad_generic_cmd import QadCommandClass +from ..qad_msg import QadMsg +from ..qad_textwindow import QadInputTypeEnum, QadInputModeEnum +from .. import qad_utils +from .. import qad_layer +from ..qad_grip import QadGripStatusEnum +from ..qad_dim import QadDimStyles + + +# Classe che gestisce il comando ARC +class QadARCCommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadARCCommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "ARC") + + def getEnglishName(self): + return "ARC" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runARCCommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/arc.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_ARC", "Draws an arc by many methods.") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + self.vertices = [] + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if (self.plugIn is not None): + if self.PointMapTool is None: + self.PointMapTool = Qad_arc_maptool(self.plugIn) + return self.PointMapTool + else: + return None + + def run(self, msgMapTool = False, msg = None): + self.isValidPreviousInput = True # per gestire il comando anche in macro + + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + currLayer, errMsg = qad_layer.getCurrLayerEditable(self.plugIn.canvas, QgsWkbTypes.LineGeometry) + if currLayer is None: + self.showErr(errMsg) + return True # fine comando + + # ========================================================================= + # RICHIESTA PRIMO PUNTO o CENTRO + if self.step == 0: # inizio del comando + # imposto il map tool + self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_START_PT) + keyWords = QadMsg.translate("Command_ARC", "Center") + + prompt = QadMsg.translate("Command_ARC", "Specify the start point of the arc or [{0}]:").format(keyWords) + + englishKeyWords = "Center" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo di modo + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, QadInputModeEnum.NONE) + self.step = 1 + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA PRIMO PUNTO o CENTRO + elif self.step == 1: # dopo aver atteso un punto o enter o una parola chiave si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if value is None: + if self.plugIn.lastPoint is not None: + value = self.plugIn.lastPoint + else: + return True # fine comando + + if type(value) == QgsPointXY: # se é stato inserito il punto iniziale dell'arco + self.startPt = value + self.plugIn.setLastPoint(value) + + # imposto il map tool + self.getPointMapTool().arcStartPt = self.startPt + self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_PT_KNOWN_ASK_FOR_SECOND_PT) + + keyWords = QadMsg.translate("Command_ARC", "Center") + "/" + \ + QadMsg.translate("Command_ARC", "End") + + prompt = QadMsg.translate("Command_ARC", "Specify second point of the arc or [{0}]:").format(keyWords) + + englishKeyWords = "Center" + "/" + "End" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o una parola chiave + # msg, inputType, default, keyWords + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, QadInputModeEnum.NONE) + + self.step = 2 + return False + else: # si vuole inserire il centro dell'arco + # imposto il map tool + self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_CENTER_PT) + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_ARC", "Specify the center of the arc: ")) + + self.step = 13 + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA SECONDO PUNTO o CENTRO o FINE + elif self.step == 2: # dopo aver atteso un punto o una parola chiave si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("Command_ARC", "Center") or value == "Center": + # imposto il map tool + self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_PT_KNOWN_ASK_FOR_CENTER_PT) + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_ARC", "Specify the center of the arc: ")) + self.step = 4 + elif value == QadMsg.translate("Command_ARC", "End") or value == "End": + # imposto il map tool + self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_PT_KNOWN_ASK_FOR_END_PT) + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_ARC", "Specify the final point of the arc: ")) + self.step = 8 + elif type(value) == QgsPointXY: # se é stato inserito il secondo punto dell'arco + self.secondPt = value + # imposto il map tool + self.getPointMapTool().arcSecondPt = self.secondPt + self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_SECOND_PT_KNOWN_ASK_FOR_END_PT) + + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_ARC", "Specify the final point of the arc: ")) + self.step = 3 + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA PUNTO FINALE DELL'ARCO (da step = 2) + elif self.step == 3: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + self.endPt = value + + arc = QadArc() + if arc.fromStartSecondEndPts(self.startPt, self.secondPt, self.endPt) == True: + self.plugIn.setLastPoint(arc.getEndPt()) + geom = arc.asGeom(currLayer.wkbType()) + if geom is not None: + # se i punti sono così vicini da essere considerati uguali + if qad_utils.ptNear(self.startPt, arc.getStartPt()): + self.plugIn.setLastSegmentAng(arc.getTanDirectionOnEndPt()) + else: + self.plugIn.setLastSegmentAng(arc.getTanDirectionOnStartPt() + math.pi) + + self.getPointMapTool().setPolarAngOffset(self.plugIn.lastSegmentAng) + + qad_layer.addGeomToLayer(self.plugIn, currLayer, self.mapToLayerCoordinates(currLayer, geom)) + return True # fine comando + + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_ARC", "Specify the final point of the arc: ")) + self.isValidPreviousInput = False # per gestire il comando anche in macro + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA CENTRO DELL'ARCO (da step = 2) + elif self.step == 4: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + self.centerPt = value + self.plugIn.setLastPoint(value) + + # imposto il map tool + self.getPointMapTool().arcCenterPt = self.centerPt + self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_CENTER_PT_KNOWN_ASK_FOR_END_PT) + + keyWords = QadMsg.translate("Command_ARC", "Angle") + "/" + \ + QadMsg.translate("Command_ARC", "chord Length") + + prompt = QadMsg.translate("Command_ARC", "Specify the final point of the arc (hold Ctrl to switch direction) or [{0}]: ").format(keyWords) + + englishKeyWords = "Angle" + "/" + "chord Length" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o una parola chiave + # msg, inputType, default, keyWords, valori nulli non ammessi + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, QadInputModeEnum.NOT_NULL) + + self.step = 5 + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA "Specificare punto finale dell'arco o [Angolo/Lunghezza corda]: " (da step = 4) + elif self.step == 5: # dopo aver atteso un punto o una parola chiave si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + ctrlPressed = self.getPointMapTool().ctrlKey + else: # il punto arriva come parametro della funzione + value = msg + ctrlPressed = False + + if type(value) == unicode: + if value == QadMsg.translate("Command_ARC", "Angle") or value == "Angle": + # imposto il map tool + self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_CENTER_PT_KNOWN_ASK_FOR_ANGLE) + # si appresta ad attendere un punto o un numero reale + # msg, inputType, default, keyWords, valori nulli non ammessi + self.waitFor(QadMsg.translate("Command_ARC", "Specify the included angle (hold Ctrl to switch direction): "), \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.ANGLE, \ + None, "", \ + QadInputModeEnum.NOT_NULL | QadInputModeEnum.NOT_ZERO) + self.step = 6 + return False + elif value == QadMsg.translate("Command_ARC", "chord Length") or value == "chord Length": + # imposto il map tool + self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_CENTER_PT_KNOWN_ASK_FOR_CHORD) + # si appresta ad attendere un punto o un numero reale + # msg, inputType, default, keyWords, valori positivi + self.waitFor(QadMsg.translate("Command_ARC", "Specify the chord length (hold Ctrl to switch direction): "), \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT, \ + None, "", \ + QadInputModeEnum.NOT_NULL | QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) + self.step = 7 + return False + elif type(value) == QgsPointXY: # se é stato inserito il punto finale dell'arco + self.endPt = value + + arc = QadArc() + if arc.fromStartCenterEndPts(self.startPt, self.centerPt, self.endPt) == True: + if ctrlPressed: # inverto angolo iniziale-finale + arc.inverseAngles() + + self.plugIn.setLastPoint(arc.getEndPt()) + + geom = arc.asGeom(currLayer.wkbType()) + if geom is not None: + # se i punti sono così vicini da essere considerati uguali + if qad_utils.ptNear(self.startPt, arc.getStartPt()): + self.plugIn.setLastSegmentAng(arc.getTanDirectionOnEndPt()) + else: + self.plugIn.setLastSegmentAng(arc.getTanDirectionOnStartPt() + math.pi) + + self.getPointMapTool().setPolarAngOffset(self.plugIn.lastSegmentAng) + + qad_layer.addGeomToLayer(self.plugIn, currLayer, self.mapToLayerCoordinates(currLayer, geom)) + return True # fine comando + + keyWords = QadMsg.translate("Command_ARC", "Angle") + "/" + \ + QadMsg.translate("Command_ARC", "chord Length") + prompt = QadMsg.translate("Command_ARC", "Specify the final point of the arc (hold Ctrl to switch direction) or [{0}]: ").format(keyWords) + + englishKeyWords = "Angle" + "/" + "chord Length" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o una parola chiave + # msg, inputType, default, keyWords, valori nulli non ammessi + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, QadInputModeEnum.NOT_NULL) + self.isValidPreviousInput = False # per gestire il comando anche in macro + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA "Specificare angolo inscritto: " (da step = 5) + elif self.step == 6: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + ctrlPressed = self.getPointMapTool().ctrlKey + else: # il punto arriva come parametro della funzione + value = msg + ctrlPressed = False + + if type(value) == QgsPointXY: + self.angle = qad_utils.getAngleBy2Pts(self.centerPt, value) + else: + self.angle = value + + arc = QadArc() + if arc.fromStartCenterPtsAngle(self.startPt, self.centerPt, self.angle) == True: + if ctrlPressed: # inverto angolo iniziale-finale + arc.inverseAngles() + + self.plugIn.setLastPoint(arc.getEndPt()) + geom = arc.asGeom(currLayer.wkbType()) + if geom is not None: + # se i punti sono così vicini da essere considerati uguali + if qad_utils.ptNear(self.startPt, arc.getStartPt()): + self.plugIn.setLastSegmentAng(arc.getTanDirectionOnEndPt()) + else: + self.plugIn.setLastSegmentAng(arc.getTanDirectionOnStartPt() + math.pi) + + self.getPointMapTool().setPolarAngOffset(self.plugIn.lastSegmentAng) + + qad_layer.addGeomToLayer(self.plugIn, currLayer, self.mapToLayerCoordinates(currLayer, geom)) + return True # fine comando + + # si appresta ad attendere un punto o un numero reale + # msg, inputType, default, keyWords, valori nulli non ammessi + self.waitFor(QadMsg.translate("Command_ARC", "Specify the included angle (hold Ctrl to switch direction): "), \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.ANGLE, \ + None, "", \ + QadInputModeEnum.NOT_NULL | QadInputModeEnum.NOT_ZERO) + self.isValidPreviousInput = False # per gestire il comando anche in macro + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA "Specificare lunghezza della corda: " (da step = 5) + elif self.step == 7: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + ctrlPressed = self.getPointMapTool().ctrlKey + else: # il punto arriva come parametro della funzione + value = msg + ctrlPressed = False + + if type(value) == QgsPointXY: + self.chord = qad_utils.getDistance(self.startPt, value) + else: + self.chord = value + + arc = QadArc() + if arc.fromStartCenterPtsChord(self.startPt, self.centerPt, self.chord) == True: + if ctrlPressed: # inverto angolo iniziale-finale + arc.inverseAngles() + + self.plugIn.setLastPoint(arc.getEndPt()) + geom = arc.asGeom(currLayer.wkbType()) + if geom is not None: + # se i punti sono così vicini da essere considerati uguali + if qad_utils.ptNear(self.startPt, arc.getStartPt()): + self.plugIn.setLastSegmentAng(arc.getTanDirectionOnEndPt()) + else: + self.plugIn.setLastSegmentAng(arc.getTanDirectionOnStartPt() + math.pi) + + self.getPointMapTool().setPolarAngOffset(self.plugIn.lastSegmentAng) + + qad_layer.addGeomToLayer(self.plugIn, currLayer, self.mapToLayerCoordinates(currLayer, geom)) + return True # fine comando + + # si appresta ad attendere un punto o un numero reale + # msg, inputType, default, keyWords, valori positivi ammessi + self.waitFor(QadMsg.translate("Command_ARC", "Specify the chord length (hold Ctrl to switch direction): "), \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT, \ + None, "", \ + QadInputModeEnum.NOT_NULL | QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) + self.isValidPreviousInput = False # per gestire il comando anche in macro + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA "Specificare punto finale dell'arco: " (da step = 1) + elif self.step == 8: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + self.endPt = value + self.plugIn.setLastPoint(self.endPt) + + # imposto il map tool + self.getPointMapTool().arcEndPt = self.endPt + self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_END_PT_KNOWN_ASK_FOR_CENTER) + + keyWords = QadMsg.translate("Command_ARC", "Angle") + "/" + \ + QadMsg.translate("Command_ARC", "Direction") + "/" + \ + QadMsg.translate("Command_ARC", "Radius") + + prompt = QadMsg.translate("Command_ARC", "Specify the center point of the arc (hold Ctrl to switch direction) or [{0}]: ").format(keyWords) + + englishKeyWords = "Angle" + "/" + "Direction" + "/" + "Radius" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o una parola chiave + # msg, inputType, default, keyWords, valori nulli non ammessi + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, QadInputModeEnum.NOT_NULL) + + self.step = 9 + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA "Specificare centro dell'arco o [Angolo/Direzione/Raggio]: " (da step = 8) + elif self.step == 9: # dopo aver atteso un punto o una parola chiave si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + ctrlPressed = self.getPointMapTool().ctrlKey + else: # il punto arriva come parametro della funzione + value = msg + ctrlPressed = False + + if type(value) == unicode: + if value == QadMsg.translate("Command_ARC", "Angle") or value == "Angle": + # imposto il map tool + self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_END_PT_KNOWN_ASK_FOR_ANGLE) + # si appresta ad attendere un punto o un numero reale + # msg, inputType, default, keyWords, isNullable + self.waitFor(QadMsg.translate("Command_ARC", "Specify the included angle (hold Ctrl to switch direction): "), \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.ANGLE, \ + None, "", QadInputModeEnum.NOT_NULL | QadInputModeEnum.NOT_ZERO) + self.step = 10 + return False + elif value == QadMsg.translate("Command_ARC", "Direction") or value == "Direction": + # imposto il map tool + self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_END_PT_KNOWN_ASK_FOR_TAN) + # si appresta ad attendere un punto o un numero reale + # msg, inputType, default, keyWords, isNullable + self.waitFor(QadMsg.translate("Command_ARC", "Specify the tangent direction for the start point of the arc (hold Ctrl to switch direction): "), \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.ANGLE, \ + None, "", QadInputModeEnum.NOT_NULL) + self.step = 11 + return False + elif value == QadMsg.translate("Command_ARC", "Radius") or value == "Radius": + # imposto il map tool + self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_END_PT_KNOWN_ASK_FOR_RADIUS) + # si appresta ad attendere un punto o un numero reale + # msg, inputType, default, keyWords, isNullable + self.waitFor(QadMsg.translate("Command_ARC", "Specify the radius of the arc (hold Ctrl to switch direction): "), \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT, \ + None, "", \ + QadInputModeEnum.NOT_NULL | QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) + self.step = 12 + return False + elif type(value) == QgsPointXY: # se é stato inserito il centro dell'arco + self.centerPt = value + + arc = QadArc() + if arc.fromStartCenterEndPts(self.startPt, self.centerPt, self.endPt) == True: + if ctrlPressed: # inverto angolo iniziale-finale + arc.inverseAngles() + + self.plugIn.setLastPoint(arc.getEndPt()) + geom = arc.asGeom(currLayer.wkbType()) + if geom is not None: + # se i punti sono così vicini da essere considerati uguali + if qad_utils.ptNear(self.startPt, arc.getStartPt()): + self.plugIn.setLastSegmentAng(arc.getTanDirectionOnEndPt()) + else: + self.plugIn.setLastSegmentAng(arc.getTanDirectionOnStartPt() + math.pi) + + self.getPointMapTool().setPolarAngOffset(self.plugIn.lastSegmentAng) + + qad_layer.addGeomToLayer(self.plugIn, currLayer, self.mapToLayerCoordinates(currLayer, geom)) + return True # fine comando + + keyWords = QadMsg.translate("Command_ARC", "Angle") + "/" + \ + QadMsg.translate("Command_ARC", "Direction") + "/" + \ + QadMsg.translate("Command_ARC", "Radius") + + prompt = QadMsg.translate("Command_ARC", "Specify the center point of the arc (hold Ctrl to switch direction) or [{0}]: ").format(keyWords) + + englishKeyWords = "Angle" + "/" + "Direction" + "/" + "Radius" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o una parola chiave + # msg, inputType, default, keyWords, isNullable + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, QadInputModeEnum.NOT_NULL) + self.isValidPreviousInput = False # per gestire il comando anche in macro + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA "Specificare angolo inscritto: " (da step = 9) + elif self.step == 10: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + ctrlPressed = self.getPointMapTool().ctrlKey + else: # il punto arriva come parametro della funzione + value = msg + ctrlPressed = False + + if type(value) == QgsPointXY: + self.angle = qad_utils.getAngleBy2Pts(self.startPt, value) + else: + self.angle = value + + arc = QadArc() + if arc.fromStartEndPtsAngle(self.startPt, self.endPt, self.angle) == True: + if ctrlPressed: # inverto angolo iniziale-finale + arc.inverseAngles() + + self.plugIn.setLastPoint(arc.getEndPt()) + geom = arc.asGeom(currLayer.wkbType()) + if geom is not None: + # se i punti sono così vicini da essere considerati uguali + if qad_utils.ptNear(self.startPt, arc.getStartPt()): + self.plugIn.setLastSegmentAng(arc.getTanDirectionOnEndPt()) + else: + self.plugIn.setLastSegmentAng(arc.getTanDirectionOnStartPt() + math.pi) + + self.getPointMapTool().setPolarAngOffset(self.plugIn.lastSegmentAng) + + qad_layer.addGeomToLayer(self.plugIn, currLayer, self.mapToLayerCoordinates(currLayer, geom)) + return True # fine comando + + # si appresta ad attendere un punto o un numero reale + # msg, inputType, default, keyWords, valori non nulli + self.waitFor(QadMsg.translate("Command_ARC", "Specify the included angle (hold Ctrl to switch direction): "), \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.ANGLE, \ + None, "", \ + QadInputModeEnum.NOT_NULL | QadInputModeEnum.NOT_ZERO) + self.isValidPreviousInput = False # per gestire il comando anche in macro + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA "Specificare direzione tangente per il punto iniziale dell'arco: " (da step = 9) + elif self.step == 11: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + ctrlPressed = self.getPointMapTool().ctrlKey + else: # il punto arriva come parametro della funzione + value = msg + ctrlPressed = False + + if type(value) == QgsPointXY: + self.angleTan = qad_utils.getAngleBy2Pts(self.startPt, value) + else: + self.angleTan = value + + arc = QadArc() + if arc.fromStartEndPtsTan(self.startPt, self.endPt, self.angleTan) == True: + if ctrlPressed: # inverto angolo iniziale-finale + arc.inverseAngles() + + self.plugIn.setLastPoint(arc.getEndPt()) + geom = arc.asGeom(currLayer.wkbType()) + if geom is not None: + # se i punti sono così vicini da essere considerati uguali + if qad_utils.ptNear(self.startPt, arc.getStartPt()): + self.plugIn.setLastSegmentAng(arc.getTanDirectionOnEndPt()) + else: + self.plugIn.setLastSegmentAng(arc.getTanDirectionOnStartPt() + math.pi) + + self.getPointMapTool().setPolarAngOffset(self.plugIn.lastSegmentAng) + + qad_layer.addGeomToLayer(self.plugIn, currLayer, self.mapToLayerCoordinates(currLayer, geom)) + return True # fine comando + + # si appresta ad attendere un punto o un numero reale + # msg, inputType, default, keyWords, isNullable + self.waitFor(QadMsg.translate("Command_ARC", "Specify the tangent direction for the start point of the arc (hold Ctrl to switch direction): "), \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.ANGLE, \ + None, "", QadInputModeEnum.NOT_NULL) + self.isValidPreviousInput = False # per gestire il comando anche in macro + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA "Specificare raggio dell'arco: " (da step = 9) + elif self.step == 12: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + ctrlPressed = self.getPointMapTool().ctrlKey + else: # il punto arriva come parametro della funzione + value = msg + ctrlPressed = False + + if type(value) == QgsPointXY: + self.radius = qad_utils.getDistance(self.endPt, value) + else: + self.radius = value + + self.plugIn.setLastRadius(self.radius) + + arc = QadArc() + if arc.fromStartEndPtsRadius(self.startPt, self.endPt, self.radius) == True: + if ctrlPressed: # inverto angolo iniziale-finale + arc.inverseAngles() + + self.plugIn.setLastPoint(arc.getEndPt()) + geom = arc.asGeom(currLayer.wkbType()) + if geom is not None: + # se i punti sono così vicini da essere considerati uguali + if qad_utils.ptNear(self.startPt, arc.getStartPt()): + self.plugIn.setLastSegmentAng(arc.getTanDirectionOnEndPt()) + else: + self.plugIn.setLastSegmentAng(arc.getTanDirectionOnStartPt() + math.pi) + + self.getPointMapTool().setPolarAngOffset(self.plugIn.lastSegmentAng) + + qad_layer.addGeomToLayer(self.plugIn, currLayer, self.mapToLayerCoordinates(currLayer, geom)) + return True # fine comando + + # si appresta ad attendere un punto o un numero reale + # msg, inputType, default, keyWords, valori positivi + self.waitFor(QadMsg.translate("Command_ARC", "Specify the radius of the arc (hold Ctrl to switch direction): "), \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT, \ + None, "", \ + QadInputModeEnum.NOT_NULL | QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) + self.isValidPreviousInput = False # per gestire il comando anche in macro + return False + + + # ======================================================================== + # RISPOSTA ALLA RICHIESTA CENTRO DELL'ARCO (da step = 1) + elif self.step == 13: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + self.centerPt = value + self.plugIn.setLastPoint(value) + + # imposto il map tool + self.getPointMapTool().arcCenterPt = self.centerPt + self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.CENTER_PT_KNOWN_ASK_FOR_START_PT) + + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_ARC", "Specify the start point of the arc: ")) + self.step = 14 + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA PUNTO INIZIALE DELL'ARCO (da step = 13) + elif self.step == 14: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + self.startPt = value + self.plugIn.setLastPoint(value) + + # imposto il map tool + self.getPointMapTool().arcStartPt = self.startPt + self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_CENTER_PT_KNOWN_ASK_FOR_END_PT) + + keyWords = QadMsg.translate("Command_ARC", "Angle") + "/" + \ + QadMsg.translate("Command_ARC", "chord Length") + + prompt = QadMsg.translate("Command_ARC", "Specify the final point of the arc (hold Ctrl to switch direction) or [{0}]: ").format(keyWords) + + englishKeyWords = "Angle" + "/" + "chord Length" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o una parola chiave + # msg, inputType, default, keyWords, isNullable + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, QadInputModeEnum.NOT_NULL) + + self.step = 5 + return False + + +# ============================================================================ +# Classe che gestisce il comando per cambiare il raggio di un arco per i grip +# ============================================================================ +class QadGRIPCHANGEARCRADIUSCommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadGRIPCHANGEARCRADIUSCommandClass(self.plugIn) + + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + self.entity = None + self.skipToNextGripCommand = False + self.copyEntities = False + self.basePt = QgsPointXY() + self.nOperationsToUndo = 0 + + + def __del__(self): + QadCommandClass.__del__(self) + + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if (self.plugIn is not None): + if self.PointMapTool is None: + self.PointMapTool = Qad_gripChangeArcRadius_maptool(self.plugIn) + return self.PointMapTool + else: + return None + + + # ============================================================================ + # setSelectedEntityGripPoints + # ============================================================================ + def setSelectedEntityGripPoints(self, entitySetGripPoints): + # lista delle entityGripPoint con dei grip point selezionati + # setta la prima entità con un grip selezionato + self.entity = None + for entityGripPoints in entitySetGripPoints.entityGripPoints: + for gripPoint in entityGripPoints.gripPoints: + # grip point selezionato + if gripPoint.getStatus() == QadGripStatusEnum.SELECTED: + # verifico se l'entità appartiene ad uno stile di quotatura + if QadDimStyles.isDimEntity(entityGripPoints.entity): + return False + if entityGripPoints.entity.getQadGeom().whatIs() != "ARC": + return False + + self.entity = entityGripPoints.entity + arc = entityGripPoints.entity.getQadGeom() # arco in map coordinate + self.basePt.set(arc.center.x(), arc.center.y()) + return True + return False + + + # ============================================================================ + # changeRadius + # ============================================================================ + def changeRadius(self, radius): + # radius = nuovo raggio dell'arco + if radius <= 0: + return False + arc = self.entity.getQadGeom() + arc.radius = radius + points = arc.asPolyline() + if points is None: + return False + + g = QgsGeometry.fromPolylineXY(points) + f = self.entity.getFeature() + f.setGeometry(g) + + self.plugIn.beginEditCommand("Feature stretched", [self.entity.layer]) + + if self.copyEntities == False: + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, self.entity.layer, f, False, False) == False: + self.plugIn.destroyEditCommand() + return False + else: + # plugIn, layer, features, coordTransform, refresh, check_validity + if qad_layer.addFeatureToLayer(self.plugIn, self.entity.layer, f, None, False, False) == False: + self.plugIn.destroyEditCommand() + return False + + self.plugIn.setLastRadius(radius) + self.plugIn.endEditCommand() + self.nOperationsToUndo = self.nOperationsToUndo + 1 + + return True + + + # ============================================================================ + # waitForRadius + # ============================================================================ + def waitForRadius(self): + keyWords = QadMsg.translate("Command_GRIP", "Base point") + "/" + \ + QadMsg.translate("Command_GRIP", "Copy") + "/" + \ + QadMsg.translate("Command_GRIP", "Undo") + "/" + \ + QadMsg.translate("Command_GRIP", "eXit") + prompt = QadMsg.translate("Command_GRIPCHANGEARCRADIUS", "Specify the radius or [{0}]: ").format(keyWords) + + englishKeyWords = "Base point" + "/" + "Copy" + "/" + "Undo" + "/" "eXit" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, valori positivi + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT | QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) + self.step = 1 + # imposto il map tool + self.getPointMapTool().setEntity(self.entity) # setta basePt nel centro dell'arco + self.getPointMapTool().basePt = self.basePt + self.getPointMapTool().setMode(Qad_gripChangeArcRadius_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_RADIUS_PT) + + + # ============================================================================ + # waitForBasePt + # ============================================================================ + def waitForBasePt(self): + self.step = 2 + # imposto il map tool + self.getPointMapTool().setMode(Qad_gripChangeArcRadius_maptool_ModeEnum.ASK_FOR_BASE_PT) + + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_GRIP", "Specify base point: ")) + + + # ============================================================================ + # run + # ============================================================================ + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + # ========================================================================= + # RICHIESTA SELEZIONE OGGETTI + if self.step == 0: # inizio del comando + if self.entity is None: # non ci sono oggetti da stirare + return True + self.showMsg(QadMsg.translate("Command_GRIPCHANGEARCRADIUS", "\n** RADIUS **\n")) + # si appresta ad attendere raggio + self.waitForRadius() + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL RAGGIO DI RACCORDO (da step = 1) + elif self.step == 1: + ctrlKey = False + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + if self.copyEntities == False: + self.skipToNextGripCommand = True + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + ctrlKey = self.getPointMapTool().ctrlKey + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("Command_GRIP", "Base point") or value == "Base point": + # si appresta ad attendere il punto base + self.waitForBasePt() + elif value == QadMsg.translate("Command_GRIP", "Copy") or value == "Copy": + # Copia entità lasciando inalterate le originali + self.copyEntities = True + # si appresta ad attendere raggio + self.waitForRadius() + elif value == QadMsg.translate("Command_GRIP", "Undo") or value == "Undo": + if self.nOperationsToUndo > 0: + self.nOperationsToUndo = self.nOperationsToUndo - 1 + self.plugIn.undoEditCommand() + else: + self.showMsg(QadMsg.translate("QAD", "\nThe command has been canceled.")) + # si appresta ad attendere raggio + self.waitForRadius() + elif value == QadMsg.translate("Command_GRIP", "eXit") or value == "eXit": + return True # fine comando + elif type(value) == QgsPointXY or type(value) == float: # se é stato inserito il raggio + if type(value) == QgsPointXY: # se é stato inserito il raggio con un punto + if value == self.basePt: + self.showMsg(QadMsg.translate("QAD", "\nThe value must be positive and not zero.")) + # si appresta ad attendere raggio + self.waitForRadius() + return False + + radius = qad_utils.getDistance(self.basePt, value) + else: + radius = value + + if ctrlKey: + self.copyEntities = True + + self.changeRadius(radius) + + if self.copyEntities == False: + return True + # si appresta ad attendere raggio + self.waitForRadius() + else: + if self.copyEntities == False: + self.skipToNextGripCommand = True + return True # fine comando + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA PUNTO BASE (da step = 1) + elif self.step == 2: # dopo aver atteso un punto + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + pass # opzione di default + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == QgsPointXY: # se é stato inserito il punto base + self.basePt.set(value.x(), value.y()) + # imposto il map tool + self.getPointMapTool().basePt = self.basePt + + # si appresta ad attendere raggio + self.waitForRadius() + + return False diff --git a/cmd/qad_arc_maptool.py b/cmd/qad_arc_maptool.py new file mode 100644 index 00000000..82c7c653 --- /dev/null +++ b/cmd/qad_arc_maptool.py @@ -0,0 +1,466 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + classe per gestire il map tool di richiesta di un punto in ambito del comando arco + + ------------------- + begin : 2013-05-22 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from qgis.core import QgsCoordinateTransform, QgsGeometry, QgsProject, QgsWkbTypes + + +from .. import qad_utils +from ..qad_getpoint import QadGetPoint, QadGetPointDrawModeEnum +from ..qad_line import QadLine +from ..qad_polyline import QadPolyline +from ..qad_arc import QadArc +from ..qad_rubberband import QadRubberBand +from ..qad_highlight import QadHighlight +from ..qad_entity import QadEntity + + +# =============================================================================== +# Qad_arc_maptool_ModeEnum class. +# =============================================================================== +class Qad_arc_maptool_ModeEnum(): + # noto niente si richiede il primo punto + NONE_KNOWN_ASK_FOR_START_PT = 1 + # noto il punto iniziale dell'arco si richiede il secondo punto + START_PT_KNOWN_ASK_FOR_SECOND_PT = 2 + # noti il punto iniziale e il secondo punto dell'arco si richiede il punto finale + START_SECOND_PT_KNOWN_ASK_FOR_END_PT = 3 + # noto il punto iniziale dell'arco si richiede il centro + START_PT_KNOWN_ASK_FOR_CENTER_PT = 4 + # noti il punto iniziale e il centro dell'arco si richiede il punto finale + START_CENTER_PT_KNOWN_ASK_FOR_END_PT = 5 + # noti il punto iniziale e il centro dell'arco si richiede l'angolo inscritto + START_CENTER_PT_KNOWN_ASK_FOR_ANGLE = 6 + # noti il punto iniziale e il centro dell'arco si richiede la lunghezza della corda + START_CENTER_PT_KNOWN_ASK_FOR_CHORD = 7 + # noto il punto iniziale dell'arco si richiede il punto finale + START_PT_KNOWN_ASK_FOR_END_PT = 8 + # noti il punto iniziale e finale dell'arco si richiede il centro + START_END_PT_KNOWN_ASK_FOR_CENTER = 9 + # noti il punto iniziale e finale dell'arco si richiede l'angolo inscritto + START_END_PT_KNOWN_ASK_FOR_ANGLE = 10 + # noti il punto iniziale e finale dell'arco si richiede la direzione della tangente al punto iniziale + START_END_PT_KNOWN_ASK_FOR_TAN = 11 + # noti il punto iniziale e finale dell'arco si richiede il raggio + START_END_PT_KNOWN_ASK_FOR_RADIUS = 12 + # noto niente si richiede il centro + NONE_KNOWN_ASK_FOR_CENTER_PT = 13 + # noto il centro dell'arco si richiede il punto iniziale + CENTER_PT_KNOWN_ASK_FOR_START_PT = 14 + # noti il punto iniziale e la tangente al punto iniziale si richiede il punto finale + START_PT_TAN_KNOWN_ASK_FOR_END_PT = 15 + # noto il punto iniziale dell'arco si richiede l'angolo inscritto + START_PT_KNOWN_ASK_FOR_ANGLE = 16 + # noti il punto iniziale e l'angolo inscritto dell'arco si richiede il punto finale + START_PT_ANGLE_KNOWN_ASK_FOR_END_PT = 17 + # noti il punto iniziale e l'angolo inscritto dell'arco si richiede il centro + START_PT_ANGLE_KNOWN_ASK_FOR_CENTER_PT = 18 + # noti il punto iniziale e l'angolo inscritto dell'arco si richiede il raggio + START_PT_ANGLE_KNOWN_ASK_FOR_RADIUS = 19 + # noti il punto iniziale e l'angolo inscritto dell'arco si richiede il secondo punto per misurare il raggio + START_PT_ANGLE_KNOWN_ASK_FOR_SECONDPTRADIUS = 20 + # noti il punto iniziale, l'angolo inscritto e il raggio dell'arco si richiede la direzione della corda + START_PT_ANGLE_RADIUS_KNOWN_ASK_FOR_CHORDDIRECTION = 21 + # noti il punto iniziale e il raggio dell'arco si richiede il punto finale + START_PT_RADIUS_KNOWN_ASK_FOR_END_PT = 22 + + +# =============================================================================== +# Qad_arc_maptool class +# =============================================================================== +class Qad_arc_maptool(QadGetPoint): + + def __init__(self, plugIn, asToolForMPolygon = False): + QadGetPoint.__init__(self, plugIn) + self.arcStartPt = None + self.arcSecondPt = None + self.arcEndPt = None + self.arcCenterPt = None + self.arcTanOnStartPt = None + self.arcAngle = None + self.arcStartPtForRadius = None + self.arcRadius = None + self.__rubberBand = QadRubberBand(self.canvas) + + self.asToolForMPolygon = asToolForMPolygon # se True significa che è usato per disegnare un poligono + if self.asToolForMPolygon: + self.__polygonRubberBand = QadRubberBand(self.plugIn.canvas, True) + self.endVertex = None # punta al vertice iniziale e finale del poligono di QadPLINECommandClass + else: + self.__polygonRubberBand = None + + self.layer = None + + + def hidePointMapToolMarkers(self): + QadGetPoint.hidePointMapToolMarkers(self) + self.__rubberBand.hide() + if self.__polygonRubberBand is not None: self.__polygonRubberBand.hide() + + def showPointMapToolMarkers(self): + QadGetPoint.showPointMapToolMarkers(self) + self.__rubberBand.show() + if self.__polygonRubberBand is not None: self.__polygonRubberBand.show() + + def clear(self): + QadGetPoint.clear(self) + self.__rubberBand.reset() + if self.__polygonRubberBand is not None: self.__polygonRubberBand.reset() + self.mode = None + + + # ============================================================================ + # removeItems + # ============================================================================ + def removeItems(self): + QadGetPoint.removeItems(self) + # prima lo stacco dal canvas altrimenti non si rimuove perchè usato da canvas + if self.__rubberBand is not None: + del self.__rubberBand + self.__rubberBand = None + + if self.__polygonRubberBand is not None: + del self.__polygonRubberBand + self.__polygonRubberBand = None + + + def canvasMoveEvent(self, event): + QadGetPoint.canvasMoveEvent(self, event) + + self.__rubberBand.reset() + if self.__polygonRubberBand is not None: self.__polygonRubberBand.reset() + + result = False + arc = QadArc() + + # noti il primo e il secondo punto dell'arco si richiede il terzo punto + if self.mode == Qad_arc_maptool_ModeEnum.START_SECOND_PT_KNOWN_ASK_FOR_END_PT: + result = arc.fromStartSecondEndPts(self.arcStartPt, self.arcSecondPt, self.tmpPoint) + # noti il primo punto e il centro dell'arco si richiede il punto finale + elif self.mode == Qad_arc_maptool_ModeEnum.START_CENTER_PT_KNOWN_ASK_FOR_END_PT: + result = arc.fromStartCenterEndPts(self.arcStartPt, self.arcCenterPt, self.tmpPoint) + if result == True and self.tmpCtrlKey: # inverto angolo iniziale-finale + arc.inverseAngles() + # noti il primo punto e il centro dell'arco si richiede l'angolo inscritto + elif self.mode == Qad_arc_maptool_ModeEnum.START_CENTER_PT_KNOWN_ASK_FOR_ANGLE: + angle = qad_utils.getAngleBy2Pts(self.arcCenterPt, self.tmpPoint) + result = arc.fromStartCenterPtsAngle(self.arcStartPt, self.arcCenterPt, angle) + if result == True and self.tmpCtrlKey: # inverto angolo iniziale-finale + arc.inverseAngles() + # noti il primo punto e il centro dell'arco si richiede la lunghezza della corda + elif self.mode == Qad_arc_maptool_ModeEnum.START_CENTER_PT_KNOWN_ASK_FOR_CHORD: + chord = qad_utils.getDistance(self.arcStartPt, self.tmpPoint) + result = arc.fromStartCenterPtsChord(self.arcStartPt, self.arcCenterPt, chord) + if result == True and self.tmpCtrlKey: # inverto angolo iniziale-finale + arc.inverseAngles() + # noti il punto iniziale e finale dell'arco si richiede il centro + elif self.mode == Qad_arc_maptool_ModeEnum.START_END_PT_KNOWN_ASK_FOR_CENTER: + result = arc.fromStartCenterEndPts(self.arcStartPt, self.tmpPoint, self.arcEndPt) + if result == True and self.tmpCtrlKey: # inverto angolo iniziale-finale + arc.inverseAngles() + # noti il punto iniziale e finale dell'arco si richiede l'angolo inscritto + elif self.mode == Qad_arc_maptool_ModeEnum.START_END_PT_KNOWN_ASK_FOR_ANGLE: + angle = qad_utils.getAngleBy2Pts(self.arcStartPt, self.tmpPoint) + result = arc.fromStartEndPtsAngle(self.arcStartPt, self.arcEndPt, angle) + if result == True and self.tmpCtrlKey: # inverto angolo iniziale-finale + arc.inverseAngles() + # noti il punto iniziale e finale dell'arco si richiede la direzione della tangente + elif self.mode == Qad_arc_maptool_ModeEnum.START_END_PT_KNOWN_ASK_FOR_TAN: + tan = qad_utils.getAngleBy2Pts(self.arcStartPt, self.tmpPoint) + result = arc.fromStartEndPtsTan(self.arcStartPt, self.arcEndPt, tan) + if result == True and self.tmpCtrlKey: # inverto angolo iniziale-finale + arc.inverseAngles() + # noti il punto iniziale e finale dell'arco si richiede il raggio + elif self.mode == Qad_arc_maptool_ModeEnum.START_END_PT_KNOWN_ASK_FOR_RADIUS: + radius = qad_utils.getDistance(self.arcEndPt, self.tmpPoint) + result = arc.fromStartEndPtsRadius(self.arcStartPt, self.arcEndPt, radius) + if result == True and self.tmpCtrlKey: # inverto angolo iniziale-finale + arc.inverseAngles() + # noti il punto iniziale e la tangente al punto iniziale si richiede il punto finale + elif self.mode == Qad_arc_maptool_ModeEnum.START_PT_TAN_KNOWN_ASK_FOR_END_PT: + result = arc.fromStartEndPtsTan(self.arcStartPt, self.tmpPoint, self.arcTanOnStartPt) + if result == True and self.tmpCtrlKey: # inverto angolo iniziale-finale + arc.inverseAngles() + # noti il punto iniziale e l'angolo inscritto dell'arco si richiede il punto finale + elif self.mode == Qad_arc_maptool_ModeEnum.START_PT_ANGLE_KNOWN_ASK_FOR_END_PT: + result = arc.fromStartEndPtsAngle(self.arcStartPt, self.tmpPoint, self.arcAngle) + if result == True and self.tmpCtrlKey: # inverto angolo iniziale-finale + arc.inverseAngles() + # noti il punto iniziale e l'angolo inscritto dell'arco si richiede il centro + elif self.mode == Qad_arc_maptool_ModeEnum.START_PT_ANGLE_KNOWN_ASK_FOR_CENTER_PT: + result = arc.fromStartCenterPtsAngle(self.arcStartPt, self.tmpPoint, self.arcAngle) + if result == True and self.tmpCtrlKey: # inverto angolo iniziale-finale + arc.inverseAngles() + # noti il punto iniziale, l'angolo inscritto e il raggio dell'arco si richiede la direzione della corda + elif self.mode == Qad_arc_maptool_ModeEnum.START_PT_ANGLE_RADIUS_KNOWN_ASK_FOR_CHORDDIRECTION: + chordDirection = qad_utils.getAngleBy2Pts(self.arcStartPt, self.tmpPoint) + result = arc.fromStartPtAngleRadiusChordDirection(self.arcStartPt, self.arcAngle, \ + self.arcRadius, chordDirection) + if result == True and self.tmpCtrlKey: # inverto angolo iniziale-finale + arc.inverseAngles() + # noti il punto iniziale e il raggio dell'arco si richiede il punto finale + elif self.mode == Qad_arc_maptool_ModeEnum.START_PT_RADIUS_KNOWN_ASK_FOR_END_PT: + result = arc.fromStartEndPtsRadius(self.arcStartPt, self.tmpPoint, self.arcRadius) + if result == True and self.tmpCtrlKey: # inverto angolo iniziale-finale + arc.inverseAngles() + + if result == True: + if self.__polygonRubberBand is None: # significa che NON è usato per disegnare un poligono + if self.layer is not None: + g = arc.asGeom(self.layer.wkbType()) + else: + g = arc.asGeom(QgsWkbTypes.CompoundCurve) # è un arco virtuale che non verrà salvato da questo comando + + if g is not None: self.__rubberBand.setGeometry(g) + else: # significa che è usato per disegnare un poligono + pline = QadPolyline() + pline.append(arc) + + if self.endVertex is not None: + line = QadLine() + line.set(arc.getEndPt(), self.endVertex) + pline.append(line) + line = QadLine() + line.set(self.endVertex, arc.getStartPt()) + pline.append(line) + else: + line = QadLine() + line.set(arc.getEndPt(), arc.getStartPt()) + pline.append(line) + + if self.layer is not None: + g = pline.asGeom(self.layer.wkbType()) + else: + g = pline.asGeom(QgsWkbTypes.CurvePolygon) # è un arco virtuale che non verrà salvato da questo comando + + self.__polygonRubberBand.setGeometry(g) + +# points = arc.asPolyline() +# +# if points is not None: +# self.__rubberBand.setLine(points) +# if self.__polygonRubberBand is not None: # significa che è usato per disegnare un poligono +# if self.endVertex is not None: +# points.insert(0, self.endVertex) +# self.__polygonRubberBand.setPolygon(points) + + + def activate(self): + QadGetPoint.activate(self) + self.__rubberBand.show() + if self.__polygonRubberBand is not None: self.__polygonRubberBand.show() + + def deactivate(self): + try: # necessario perché se si chiude QGIS parte questo evento nonostante non ci sia più l'oggetto maptool ! + QadGetPoint.deactivate(self) + self.__rubberBand.hide() + if self.__polygonRubberBand is not None: self.__polygonRubberBand.hide() + except: + pass + + def setMode(self, mode): + self.mode = mode + # noto niente si richiede il primo punto + if self.mode == Qad_arc_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_START_PT: + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + # noto il primo punto dell'arco si richiede il secondo punto + elif self.mode == Qad_arc_maptool_ModeEnum.START_PT_KNOWN_ASK_FOR_SECOND_PT: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.arcStartPt) + # noti il primo e il secondo punto dell'arco si richiede il terzo punto + elif self.mode == Qad_arc_maptool_ModeEnum.START_SECOND_PT_KNOWN_ASK_FOR_END_PT: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.arcSecondPt) + # noto il primo punto dell'arco si richiede il centro + elif self.mode == Qad_arc_maptool_ModeEnum.START_PT_KNOWN_ASK_FOR_CENTER_PT: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.arcStartPt) + # noti il primo punto e il centro dell'arco si richiede il punto finale + elif self.mode == Qad_arc_maptool_ModeEnum.START_CENTER_PT_KNOWN_ASK_FOR_END_PT: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.arcCenterPt) + # noti il primo punto e il centro dell'arco si richiede l'angolo inscritto + elif self.mode == Qad_arc_maptool_ModeEnum.START_CENTER_PT_KNOWN_ASK_FOR_ANGLE: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.arcCenterPt) + # noti il primo punto e il centro dell'arco si richiede la lunghezza della corda + elif self.mode == Qad_arc_maptool_ModeEnum.START_CENTER_PT_KNOWN_ASK_FOR_CHORD: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.arcStartPt) + # noto il punto iniziale dell'arco si richiede il punto finale + elif self.mode == Qad_arc_maptool_ModeEnum.START_PT_KNOWN_ASK_FOR_END_PT: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.arcStartPt) + # noti il punto iniziale e finale dell'arco si richiede il centro + elif self.mode == Qad_arc_maptool_ModeEnum.START_END_PT_KNOWN_ASK_FOR_CENTER: + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + # noti il punto iniziale e finale dell'arco si richiede l'angolo inscritto + elif self.mode == Qad_arc_maptool_ModeEnum.START_END_PT_KNOWN_ASK_FOR_ANGLE: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.arcStartPt) + # noti il punto iniziale e finale dell'arco si richiede la direzione della tangente + elif self.mode == Qad_arc_maptool_ModeEnum.START_END_PT_KNOWN_ASK_FOR_TAN: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.arcStartPt) + # noti il punto iniziale e finale dell'arco si richiede il raggio + elif self.mode == Qad_arc_maptool_ModeEnum.START_END_PT_KNOWN_ASK_FOR_RADIUS: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.arcEndPt) + # noto niente si richiede il centro + elif self.mode == Qad_arc_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_CENTER_PT: + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + # noto il centro dell'arco si richiede il punto iniziale + elif self.mode == Qad_arc_maptool_ModeEnum.CENTER_PT_KNOWN_ASK_FOR_START_PT: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.arcCenterPt) + # noti il punto iniziale e la tangente al punto iniziale si richiede il punto finale + elif self.mode == Qad_arc_maptool_ModeEnum.START_PT_TAN_KNOWN_ASK_FOR_END_PT: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.arcStartPt) + # noto il punto iniziale dell'arco si richiede l'angolo inscritto + elif self.mode == Qad_arc_maptool_ModeEnum.START_PT_KNOWN_ASK_FOR_ANGLE: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.arcStartPt) + # noti il punto iniziale e l'angolo inscritto dell'arco si richiede il punto finale + elif self.mode == Qad_arc_maptool_ModeEnum.START_PT_ANGLE_KNOWN_ASK_FOR_END_PT: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.arcStartPt) + # noti il punto iniziale e l'angolo inscritto dell'arco si richiede il centro + elif self.mode == Qad_arc_maptool_ModeEnum.START_PT_ANGLE_KNOWN_ASK_FOR_CENTER_PT: + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + # noti il punto iniziale e l'angolo inscritto dell'arco si richiede il raggio + elif self.mode == Qad_arc_maptool_ModeEnum.START_PT_ANGLE_KNOWN_ASK_FOR_RADIUS: + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + # noti il punto iniziale e l'angolo inscritto dell'arco si richiede il secondo punto per misurare il raggio + elif self.mode == Qad_arc_maptool_ModeEnum.START_PT_ANGLE_KNOWN_ASK_FOR_SECONDPTRADIUS: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.arcStartPtForRadius) + # noti il punto iniziale, l'angolo inscritto e il raggio dell'arco si richiede la direzione della corda + elif self.mode == Qad_arc_maptool_ModeEnum.START_PT_ANGLE_RADIUS_KNOWN_ASK_FOR_CHORDDIRECTION: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.arcStartPt) + # noti il punto iniziale e il raggio dell'arco si richiede il punto finale + elif self.mode == Qad_arc_maptool_ModeEnum.START_PT_RADIUS_KNOWN_ASK_FOR_END_PT: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.arcStartPt) + + + +# =============================================================================== +# Qad_scale_maptool_ModeEnum class. +# =============================================================================== +class Qad_gripChangeArcRadius_maptool_ModeEnum(): + # si richiede il punto base + ASK_FOR_BASE_PT = 1 + # noto il punto base si richiede il secondo punto per il raggio + BASE_PT_KNOWN_ASK_FOR_RADIUS_PT = 2 + + +# =============================================================================== +# Qad_gripChangeArcRadius_maptool class +# =============================================================================== +class Qad_gripChangeArcRadius_maptool(QadGetPoint): + + def __init__(self, plugIn): + QadGetPoint.__init__(self, plugIn) + + self.basePt = None + self.entity = None + self.arc = None + self.coordTransform = None + self.__highlight = QadHighlight(self.canvas) + + def hidePointMapToolMarkers(self): + QadGetPoint.hidePointMapToolMarkers(self) + self.__highlight.hide() + + def showPointMapToolMarkers(self): + QadGetPoint.showPointMapToolMarkers(self) + self.__highlight.show() + + def clear(self): + QadGetPoint.clear(self) + self.__highlight.reset() + self.mode = None + + def setEntity(self, entity): + self.entity = QadEntity(entity) + self.arc = self.entity.getQadGeom() # arco in map coordinate + self.basePt = self.arc.center + self.coordTransform = QgsCoordinateTransform(self.canvas.mapSettings().destinationCrs(), \ + entity.layer.crs(), \ + QgsProject.instance()) + + + # ============================================================================ + # stretch + # ============================================================================ + def changeRadius(self, radius): + self.__highlight.reset() + # radius = nuovo raggio dell'arco + # tolerance2ApproxCurve = tolleranza per ricreare le curve + self.arc.radius = radius + points = self.arc.asPolyline() + if points is None: + return False + + g = QgsGeometry.fromPolylineXY(points) + # trasformo la geometria nel crs del layer + g.transform(self.coordTransform) + self.__highlight.addGeometry(g, self.entity.layer) + + + def canvasMoveEvent(self, event): + QadGetPoint.canvasMoveEvent(self, event) + + # noto il punto base si richiede il secondo punto per il raggio + if self.mode == Qad_gripChangeArcRadius_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_RADIUS_PT: + radius = qad_utils.getDistance(self.basePt, self.tmpPoint) + self.changeRadius(radius) + + + def activate(self): + QadGetPoint.activate(self) + self.__highlight.show() + + def deactivate(self): + try: # necessario perché se si chiude QGIS parte questo evento nonostante non ci sia più l'oggetto maptool ! + QadGetPoint.deactivate(self) + self.__highlight.hide() + except: + pass + + + def setMode(self, mode): + self.mode = mode + # noto niente si richiede il punto base + if self.mode == Qad_gripChangeArcRadius_maptool_ModeEnum.ASK_FOR_BASE_PT: + self.clear() + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + self.__highlight.reset() + # noto il punto base si richiede il secondo punto per il raggio + elif self.mode == Qad_gripChangeArcRadius_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_RADIUS_PT: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.basePt) + \ No newline at end of file diff --git a/cmd/qad_array_cmd.py b/cmd/qad_array_cmd.py new file mode 100644 index 00000000..ff14396f --- /dev/null +++ b/cmd/qad_array_cmd.py @@ -0,0 +1,2117 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin ok + + comando ARRAY per copiare serie di oggetti + + ------------------- + begin : 2016-05-03 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.PyQt.QtGui import QIcon +from qgis.core import QgsPointXY + + +import math + + +from .. import qad_array_fun +from .. import qad_layer +from .. import qad_utils +from .qad_array_maptool import Qad_array_maptool, Qad_array_maptool_ModeEnum +from .qad_generic_cmd import QadCommandClass +from ..qad_msg import QadMsg +from ..qad_getpoint import QadGetPointDrawModeEnum +from ..qad_polyline import QadPolyline +from .qad_getdist_cmd import QadGetDistClass +from .qad_getangle_cmd import QadGetAngleClass +from .qad_entsel_cmd import QadEntSelClass +from ..qad_textwindow import QadInputTypeEnum, QadInputModeEnum +from .qad_ssget_cmd import QadSSGetClass +from ..qad_entity import QadCacheEntitySet, QadCacheEntitySetIterator, QadEntityTypeEnum +from ..qad_variables import QadVariables, QadDELOBJEnum +from ..qad_dim import QadDimStyles, appendDimEntityIfNotExisting +from ..qad_geom_relations import getQadGeomClosestPart +from ..qad_multi_geom import getQadGeomAt + + +# =============================================================================== +# QadARRAYCommandClassSeriesTypeEnum class. +# =============================================================================== +class QadARRAYCommandClassSeriesTypeEnum(): + RECTANGLE = 1 # serie rettangolare + PATH = 2 # serie lungo una traiettoria + POLAR = 3 # serie polare + + +# =============================================================================== +# QadARRAYCommandClassPathMethodTypeEnum class. +# =============================================================================== +class QadARRAYCommandClassPathMethodTypeEnum(): + DIVIDE = 1 # metodo dividi + MEASURE = 2 # metodo misura + + +# =============================================================================== +# QadARRAYCommandClassStepEnum class. +# =============================================================================== +class QadARRAYCommandClassStepEnum(): + ASK_FOR_SELSET = 0 # richiede il gruppo di selezione ogggetti (deve essere = 0 perchè è l'inizio del comando) + ASK_FOR_ARRAYTYPE = 1 # richiede il tipo di serie + ASK_FOR_ROW_N = 2 # richiede il numero di righe (per rettangolo, traiettoria, polare) + ASK_FOR_ROW_SPACE_OR_TOT = 3 # richiede la distanza tra le righe o il totale (per rettangolo, traiettoria, polare) + ASK_FOR_ROW_SPACE_TOT = 4 # richiede il totale della spaziatura delle righe (per rettangolo, traiettoria) + ASK_FOR_ROW_SPACE_2PT = 5 # richiede il secondo punto per misurare la distanza tra le righe + ASK_FOR_BASE_PT = 6 # richiede il punto base (per rettangolo, traiettoria, polare) + ASK_FOR_MAIN_OPTIONS = 7 # richiede di selezionare un'opzione (per rettangolo, traiettoria, polare) + ASK_FOR_ITEM_N = 8 # richiede il numero di elementi lungo la traiettoria (per traiettoria, polare) + ASK_FOR_ITEM_ROTATION = 9 # richiede se gli elementi devono essere allineati (per traiettoria, polare) + ASK_FOR_DEL_ORIG_OBJS = 10 # richiede se gli elementi originali devono essere cancellati (per rettangolo, traiettoria, polare) + ASK_FOR_BASE_PT_BEFORE_MAIN_OPTIONS = 29 # richiede il punto base prima delle opzioni (per polare) + # RETTANGOLO + ASK_FOR_ANGLE = 11 # richiede l'angolo di rotazione dell'asse delle righe + ASK_FOR_COLUMN_COUNT = 12 # richiede il numero di colonne dall'opzione COUNT + ASK_FOR_COLUMN_N = 13 # richiede il numero di colonne dall'opzione COLUMN + ASK_FOR_COLUMN_SPACE_OR_CELL = 14 # richiede la distanza tra le colonne o l'unità di cella + ASK_FOR_COLUMN_SPACE_2PT = 15 # richiede il secondo punto per misurare la distanza tra le colonne + ASK_FOR_ROW_COUNT = 16 # richiede il numero di righe dall'opzione COUNT + ASK_FOR_ROW_SPACE = 17 # richiede la distanza tra le righe + ASK_FOR_1PT_CELL = 18 # richiede il primo angolo della cella + ASK_FOR_2PT_CELL = 19 # richiede il secondo angolo della cella + ASK_FOR_COLUMN_SPACE_OR_TOT = 20 # richiede la distanza tra le colonne o il totale + ASK_FOR_COLUMN_SPACE_TOT = 21 # richiede il totale della spaziatura delle colonne + # TRAIETTORIA + ASK_FOR_PATH_OBJ = 22 # richiede la selezione dell'oggetto traiettoria + ASK_FOR_PATH_METHOD = 23 # richiede il metodo + ASK_FOR_TAN_DIRECTION = 24 # richiede la selezione della direzione della tangente + ASK_FOR_ITEM_SPACE = 25 # richiede la distanza tra gli elementi + # POLARE + ASK_FOR_CENTER_PT = 26 # richiede la selezione del punto centrale della serie + ASK_FOR_ANGLE_BETWEEN_ITEMS = 27 # richiede la selezione dell'angolo tra gli elementi + ASK_FOR_FULL_ANGLE = 28 # richiede la selezione dell'angolo da riempire + + + +# Classe che gestisce il comando ARRAY +class QadARRAYCommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadARRAYCommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "ARRAY") + + def getEnglishName(self): + return "ARRAY" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runARRAYCommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/arrayRect.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_ARRAY", "Creates copies of objects in a regularly spaced rectangular, polar, or path array.") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + self.SSGetClass = QadSSGetClass(plugIn) + self.SSGetClass.onlyEditableLayers = True + self.entSelClass = None + self.cacheEntitySet = QadCacheEntitySet() + self.defaultValue = None + + self.basePt = QgsPointXY() + self.arrayType = self.plugIn.lastArrayType_array + self.distanceBetweenRows = None + self.distanceBetweenCols = None + self.itemsRotation = self.plugIn.lastItemsRotation_array + self.delObj = QadVariables.get(QadMsg.translate("Environment variables", "DELOBJ")) + self.delOrigSelSet = False + if self.delObj == QadDELOBJEnum.DELETE_ALL: # Delete all defining geometry + self.delOrigSelSet = True + + # serie rettangolare + self.rectangleAngle = self.plugIn.lastRectangleAngle_array + self.rectangleCols = self.plugIn.lastRectangleCols_array + self.rectangleRows = self.plugIn.lastRectangleRows_array + self.firstPt = QgsPointXY() # primo punto per misurare la distanza tra righe + + # serie traiettoria + self.pathTangentDirection = self.plugIn.lastPathTangentDirection_array + self.pathRows = self.plugIn.lastPathRows_array + self.pathItemsNumber = 1 + self.pathPolyline = QadPolyline() + self.pathMethod = QadARRAYCommandClassPathMethodTypeEnum.MEASURE + self.distanceFromStartPt = 0.0 # uso interno quando si imposta il metodo dividi + + # serie polare + self.centerPt = QgsPointXY() + self.polarItemsNumber = self.plugIn.lastPolarItemsNumber_array + self.polarAngleBetween = self.plugIn.lastPolarAngleBetween_array + self.polarRows = self.plugIn.lastPolarRows_array + + self.GetDistClass = None + self.GetAngleClass = None + + self.featureCache = [] # lista di (layer, feature) + + def __del__(self): + QadCommandClass.__del__(self) + if self.SSGetClass is not None: + del self.SSGetClass + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if self.step == QadARRAYCommandClassStepEnum.ASK_FOR_SELSET: # quando si é in fase di selezione entità + return self.SSGetClass.getPointMapTool() + # quando si é in fase di richiesta rotazione + elif self.step == QadARRAYCommandClassStepEnum.ASK_FOR_ANGLE or \ + self.step == QadARRAYCommandClassStepEnum.ASK_FOR_TAN_DIRECTION or \ + self.step == QadARRAYCommandClassStepEnum.ASK_FOR_ANGLE_BETWEEN_ITEMS or \ + self.step == QadARRAYCommandClassStepEnum.ASK_FOR_FULL_ANGLE: + return self.GetAngleClass.getPointMapTool() + # quando si é in fase di richiesta distanza + elif self.step == QadARRAYCommandClassStepEnum.ASK_FOR_COLUMN_SPACE_2PT or \ + self.step == QadARRAYCommandClassStepEnum.ASK_FOR_ROW_SPACE_TOT or \ + self.step == QadARRAYCommandClassStepEnum.ASK_FOR_COLUMN_SPACE_TOT or \ + self.step == QadARRAYCommandClassStepEnum.ASK_FOR_ITEM_SPACE or \ + self.step == QadARRAYCommandClassStepEnum.ASK_FOR_ROW_SPACE_2PT: + return self.GetDistClass.getPointMapTool() + else: + if (self.plugIn is not None): + if self.PointMapTool is None: + self.PointMapTool = Qad_array_maptool(self.plugIn) + return self.PointMapTool + else: + return None + + + def getCurrentContextualMenu(self): + if self.step == QadARRAYCommandClassStepEnum.ASK_FOR_SELSET: # quando si é in fase di selezione entità + return None # return self.SSGetClass.getCurrentContextualMenu() + # quando si é in fase di richiesta rotazione + elif self.step == QadARRAYCommandClassStepEnum.ASK_FOR_ANGLE or \ + self.step == QadARRAYCommandClassStepEnum.ASK_FOR_TAN_DIRECTION or \ + self.step == QadARRAYCommandClassStepEnum.ASK_FOR_ANGLE_BETWEEN_ITEMS or \ + self.step == QadARRAYCommandClassStepEnum.ASK_FOR_FULL_ANGLE: + return self.GetAngleClass.getCurrentContextualMenu() + # quando si é in fase di richiesta distanza + elif self.step == QadARRAYCommandClassStepEnum.ASK_FOR_COLUMN_SPACE_2PT or \ + self.step == QadARRAYCommandClassStepEnum.ASK_FOR_ROW_SPACE_TOT or \ + self.step == QadARRAYCommandClassStepEnum.ASK_FOR_COLUMN_SPACE_TOT or \ + self.step == QadARRAYCommandClassStepEnum.ASK_FOR_ITEM_SPACE or \ + self.step == QadARRAYCommandClassStepEnum.ASK_FOR_ROW_SPACE_2PT: + return self.GetDistClass.getCurrentContextualMenu() + else: + return self.contextualMenu + + + # ============================================================================ + # updatePointMapToolParams + # ============================================================================ + def updatePointMapToolParams(self): + self.step = -1 * self.step # trucchetto per prendere il map tool base + self.getPointMapTool().refreshSnapType() # aggiorno lo snapType che può essere variato da altri maptool + + self.getPointMapTool().cacheEntitySet = self.cacheEntitySet + self.getPointMapTool().basePt = self.basePt + self.getPointMapTool().arrayType = self.arrayType + self.getPointMapTool().distanceBetweenRows = self.distanceBetweenRows + self.getPointMapTool().distanceBetweenCols = self.distanceBetweenCols + self.getPointMapTool().itemsRotation = self.itemsRotation + + if self.arrayType == QadARRAYCommandClassSeriesTypeEnum.RECTANGLE: # serie rettangolare + self.getPointMapTool().rectangleAngle = self.rectangleAngle + self.getPointMapTool().rectangleCols = self.rectangleCols + self.getPointMapTool().rectangleRows = self.rectangleRows + self.getPointMapTool().firstPt = self.firstPt + self.getPointMapTool().doRectangleArray() + elif self.arrayType == QadARRAYCommandClassSeriesTypeEnum.PATH: # serie traiettoria + self.getPointMapTool().pathTangentDirection = self.pathTangentDirection + self.getPointMapTool().pathRows = self.pathRows + self.getPointMapTool().pathItemsNumber = self.pathItemsNumber + self.getPointMapTool().pathPolyline = self.pathPolyline + self.getPointMapTool().distanceFromStartPt = self.distanceFromStartPt + self.getPointMapTool().doPathArray() + elif self.arrayType == QadARRAYCommandClassSeriesTypeEnum.POLAR: # serie polare + self.getPointMapTool().centerPt = self.centerPt + self.getPointMapTool().polarItemsNumber = self.polarItemsNumber + self.getPointMapTool().polarAngleBetween = self.polarAngleBetween + self.getPointMapTool().polarRows = self.polarRows + self.getPointMapTool().doPolarArray() + + self.step = -1 * self.step # trucchetto per prendere il map tool base + + + # ============================================================================ + # setEntitySet + # ============================================================================ + def setEntitySet(self, ss): + self.cacheEntitySet.clear() + self.cacheEntitySet.appendEntitySet(ss) + rect = self.cacheEntitySet.getBoundingBox() + self.distanceBetweenRows = rect.height() + (rect.height() / 2) if rect.height() != 0 else 1 + self.distanceBetweenCols = rect.width() + (rect.width() / 2) if rect.width() != 0 else 1 + center = rect.center() + self.basePt.setX(center.x()) + self.basePt.setY(center.y()) + + + # ============================================================================ + # doRectangleArray + # ============================================================================ + def doRectangleArray(self): + self.plugIn.beginEditCommand("Feature copied", self.cacheEntitySet.getLayerList()) + + dimElaboratedList = [] # lista delle quotature già elaborate + entityIterator = QadCacheEntitySetIterator(self.cacheEntitySet) + for entity in entityIterator: + qadGeom = entity.getQadGeom() # così inizializzo le info qad + # verifico se l'entità appartiene ad uno stile di quotatura + dimEntity = QadDimStyles.getDimEntity(entity) + if dimEntity is not None: + if appendDimEntityIfNotExisting(dimElaboratedList, dimEntity) == False: # quota già elaborata + continue + entity = dimEntity + + if qad_array_fun.arrayRectangleEntity(self.plugIn, entity, self.basePt, self.rectangleRows, self.rectangleCols, \ + self.distanceBetweenRows, self.distanceBetweenCols, self.rectangleAngle, self.itemsRotation, + True, None) == False: + self.plugIn.destroyEditCommand() + return + + if self.delOrigSelSet: # se devo rimuovere gli oggetti originali + entityIterator = QadCacheEntitySetIterator(self.cacheEntitySet) + for entity in entityIterator: + if qad_layer.deleteFeatureToLayer(self.plugIn, entity.layer, entity.featureId, False) == False: + self.plugIn.destroyEditCommand() + return + + self.plugIn.endEditCommand() + + + # ============================================================================ + # doPathArray + # ============================================================================ + def doPathArray(self): + self.plugIn.beginEditCommand("Feature copied", self.cacheEntitySet.getLayerList()) + + dimElaboratedList = [] # lista delle quotature già elaborate + entityIterator = QadCacheEntitySetIterator(self.cacheEntitySet) + for entity in entityIterator: + qadGeom = entity.getQadGeom() # così inizializzo le info qad + # verifico se l'entità appartiene ad uno stile di quotatura + dimEntity = QadDimStyles.getDimEntity(entity) + if dimEntity is not None: + if appendDimEntityIfNotExisting(dimElaboratedList, dimEntity) == False: # quota già elaborata + continue + entity = dimEntity + + if qad_array_fun.arrayPathEntity(self.plugIn, entity, self.basePt, self.pathRows, self.pathItemsNumber, \ + self.distanceBetweenRows, self.distanceBetweenCols, self.pathTangentDirection, self.itemsRotation, \ + self.pathPolyline, self.distanceFromStartPt, True, None) == False: + self.plugIn.destroyEditCommand() + return + + if self.delOrigSelSet: # se devo rimuovere gli oggetti originali + entityIterator = QadCacheEntitySetIterator(self.cacheEntitySet) + for entity in entityIterator: + if qad_layer.deleteFeatureToLayer(self.plugIn, entity.layer, entity.featureId, False) == False: + self.plugIn.destroyEditCommand() + return + + self.plugIn.endEditCommand() + + + # ============================================================================ + # doPolarArray + # ============================================================================ + def doPolarArray(self): + self.plugIn.beginEditCommand("Feature copied", self.cacheEntitySet.getLayerList()) + + dimElaboratedList = [] # lista delle quotature già elaborate + entityIterator = QadCacheEntitySetIterator(self.cacheEntitySet) + for entity in entityIterator: + qadGeom = entity.getQadGeom() # così inizializzo le info qad + # verifico se l'entità appartiene ad uno stile di quotatura + dimEntity = QadDimStyles.getDimEntity(entity) + if dimEntity is not None: + if appendDimEntityIfNotExisting(dimElaboratedList, dimEntity) == False: # quota già elaborata + continue + entity = dimEntity + + if qad_array_fun.arrayPolarEntity(self.plugIn, entity, self.basePt, self.centerPt, self.polarItemsNumber, \ + self.polarAngleBetween, self.polarRows, self.distanceBetweenRows, self.itemsRotation, \ + True, None) == False: + self.plugIn.destroyEditCommand() + return + + if self.delOrigSelSet: # se devo rimuovere gli oggetti originali + entityIterator = QadCacheEntitySetIterator(self.cacheEntitySet) + for entity in entityIterator: + if qad_layer.deleteFeatureToLayer(self.plugIn, entity.layer, entity.featureId, False) == False: + self.plugIn.destroyEditCommand() + return + + self.plugIn.endEditCommand() + + + # ============================================================================ + # setPathPolyline + # ============================================================================ + def setPathPolyline(self, entity, point): + """ + Setta self.pathPolyline che definisce la traiettoria + """ + qadGeom = entity.getQadGeom() + # la funzione ritorna una lista con + # ( + # + # + # + # se geometria chiusa è tipo polyline la lista contiene anche + # + # <"a sinistra di" se il punto é alla sinista della parte (< 0 -> sinistra, > 0 -> destra) + dummy = getQadGeomClosestPart(qadGeom, point) + # ritorna la sotto-geometria + subGeom = getQadGeomAt(qadGeom, dummy[2], dummy[3]) + self.pathPolyline = subGeom.copy() + return True + + + # ============================================================================ + # setDistancesByPathItemNumberOnDivide + # ============================================================================ + def setDistancesByPathItemNumberOnDivide(self): + # imposta le distanza dall'inizio della traccia e la distanza tra gli elementi + # quando gli elementi devono essere distribuiti uniformemente + self.distanceBetweenCols = self.pathPolyline.length() / (self.pathItemsNumber + 1) + self.distanceFromStartPt = self.distanceBetweenCols + + + # ============================================================================ + # setItemNumberByDistanceBetweenColsOnMeasure + # ============================================================================ + def setItemNumberByDistanceBetweenColsOnMeasure(self): + # imposta le distanza dall'inizio della traccia e il numero di elementi + # quando gli elementi non devono essere distribuiti uniformemente ma a partire dall'inizio della traccia + self.pathItemsNumber = int(self.pathPolyline.length() / self.distanceBetweenCols) + 1 + self.distanceFromStartPt = 0.0 + + + # ============================================================================ + # waitForMainOptions + # ============================================================================ + def waitForMainOptions(self): + if self.arrayType == QadARRAYCommandClassSeriesTypeEnum.RECTANGLE: + self.waitForRectangleArrayOptions() + elif self.arrayType == QadARRAYCommandClassSeriesTypeEnum.PATH: + self.waitForPathArrayOptions() + elif self.arrayType == QadARRAYCommandClassSeriesTypeEnum.POLAR: + self.waitForPolarArrayOptions() + + self.updatePointMapToolParams() + + + # ============================================================================ + # waitForArrayType + # ============================================================================ + def waitForArrayType(self): + self.step = QadARRAYCommandClassStepEnum.ASK_FOR_ARRAYTYPE + # imposto il map tool + self.getPointMapTool().setMode(Qad_array_maptool_ModeEnum.NONE) + + keyWords = QadMsg.translate("Command_ARRAY", "Rectangular") + "/" + \ + QadMsg.translate("Command_ARRAY", "PAth") + "/" + \ + QadMsg.translate("Command_ARRAY", "POlar") + englishKeyWords = "Rectangular" + "/" + "PAth" + "/" + "POlar" + + if self.arrayType == QadARRAYCommandClassSeriesTypeEnum.RECTANGLE: + self.defaultValue = QadMsg.translate("Command_ARRAY", "Rectangular") + elif self.arrayType == QadARRAYCommandClassSeriesTypeEnum.PATH: + self.defaultValue = QadMsg.translate("Command_ARRAY", "PAth") + elif self.arrayType == QadARRAYCommandClassSeriesTypeEnum.POLAR: + self.defaultValue = QadMsg.translate("Command_ARRAY", "POlar") + + prompt = QadMsg.translate("Command_ARRAY", "Enter array type [{0}] <{1}>: ").format(keyWords, self.defaultValue) + + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.KEYWORDS, \ + self.defaultValue, \ + keyWords, QadInputModeEnum.NONE) + + + # ============================================================================ + # waitForBasePt + # ============================================================================ + def waitForBasePt(self, nextStep = QadARRAYCommandClassStepEnum.ASK_FOR_BASE_PT): + self.step = nextStep + # imposto il map tool + self.getPointMapTool().setMode(Qad_array_maptool_ModeEnum.ASK_FOR_BASE_PT) + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_ARRAY", "Specify base point: ")) + + + # ============================================================================ + # waitForItemsNumber + # ============================================================================ + def waitForItemsNumber(self): + self.step = QadARRAYCommandClassStepEnum.ASK_FOR_ITEM_N + # imposto il map tool + self.getPointMapTool().setMode(Qad_array_maptool_ModeEnum.NONE) + + if self.arrayType == QadARRAYCommandClassSeriesTypeEnum.PATH: + self.defaultValue = self.pathItemsNumber + if self.pathMethod == QadARRAYCommandClassPathMethodTypeEnum.MEASURE: + keyWords = QadMsg.translate("Command_ARRAY", "Fill entire path") + englishKeyWords = "Fill entire path" + # si appresta ad attendere un numero intero + prompt = QadMsg.translate("Command_ARRAY", "Number of Items to Array or [{0}] <{1}>: ").format(keyWords, str(self.defaultValue)) + keyWords += "_" + englishKeyWords + inputType = QadInputTypeEnum.INT | QadInputTypeEnum.KEYWORDS + else: + keyWords = "" + prompt = QadMsg.translate("Command_ARRAY", "Number of Items to Array <{0}>: ").format(str(self.defaultValue)) + inputType = QadInputTypeEnum.INT + elif self.arrayType == QadARRAYCommandClassSeriesTypeEnum.POLAR: + self.defaultValue = self.polarItemsNumber + # si appresta ad attendere un numero intero + keyWords = "" + prompt = QadMsg.translate("Command_ARRAY", "Number of Items to Array <{0}>: ").format(str(self.defaultValue)) + inputType = QadInputTypeEnum.INT + + # msg, inputType, default, keyWords, valori positivi + self.waitFor(prompt, \ + inputType, \ + self.defaultValue, \ + keyWords, \ + QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) + + + # ============================================================================ + # waitForRows + # ============================================================================ + def waitForRows(self, nextStep): + self.step = nextStep + # imposto il map tool + self.getPointMapTool().setMode(Qad_array_maptool_ModeEnum.NONE) + + # si appresta ad attendere un numero intero + msg = QadMsg.translate("Command_ARRAY", "Specify number of rows <{0}>: ") + if self.arrayType == QadARRAYCommandClassSeriesTypeEnum.RECTANGLE: + self.defaultValue = self.rectangleRows + elif self.arrayType == QadARRAYCommandClassSeriesTypeEnum.PATH: + self.defaultValue = self.pathRows + elif self.arrayType == QadARRAYCommandClassSeriesTypeEnum.POLAR: + self.defaultValue = self.polarRows + prompt = msg.format(str(self.defaultValue)) + # msg, inputType, default, keyWords, valori positivi + self.waitFor(prompt, \ + QadInputTypeEnum.INT, \ + self.defaultValue, \ + "", \ + QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) + + + # ============================================================================ + # waitForDistanceBetweenRows + # ============================================================================ + def waitForDistanceBetweenRows(self, totalOption, nextStep): + self.step = nextStep + self.getPointMapTool().setMode(Qad_array_maptool_ModeEnum.ASK_FOR_ROW_SPACE_FIRST_PT) + + self.defaultValue = self.distanceBetweenRows + if self.arrayType == QadARRAYCommandClassSeriesTypeEnum.RECTANGLE: + inputMode = QadInputModeEnum.NOT_ZERO + elif self.arrayType == QadARRAYCommandClassSeriesTypeEnum.PATH: + inputMode = QadInputModeEnum.NOT_ZERO + elif self.arrayType == QadARRAYCommandClassSeriesTypeEnum.POLAR: + inputMode = QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE + + if totalOption: + keyWords = QadMsg.translate("Command_ARRAY", "Total") + englishKeyWords = "Total" + prompt = QadMsg.translate("Command_ARRAY", "Specify distance between rows or [{0}] <{1}>: ").format(keyWords, str(self.defaultValue)) + keyWords += "_" + englishKeyWords + inputType = QadInputTypeEnum.FLOAT | QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS + else: + prompt = QadMsg.translate("Command_ARRAY", "Specify distance between rows <{0}>: ").format(str(self.defaultValue)) + keyWords = "" + inputType = QadInputTypeEnum.FLOAT | QadInputTypeEnum.POINT2D + + # si appresta ad attendere un punto, un numero reale o enter o una parola chiave + # msg, inputType, default, keyWords, inputMode + self.waitFor(prompt, \ + inputType, \ + self.defaultValue, \ + keyWords, \ + inputMode) + + + # ========================================================================= + # waitForDistanceBetweenRows2Pt + # ========================================================================= + def waitForDistanceBetweenRows2Pt(self, startPt): + self.step = QadARRAYCommandClassStepEnum.ASK_FOR_ROW_SPACE_2PT + + if self.GetDistClass is not None: + del self.GetDistClass + self.GetDistClass = QadGetDistClass(self.plugIn) + + self.GetDistClass.dist = self.distanceBetweenRows + self.GetDistClass.inputMode = QadInputModeEnum.NOT_ZERO + self.GetDistClass.startPt = startPt + self.GetDistClass.run() + + + # ============================================================================ + # waitForTotalDistanceRows + # ============================================================================ + def waitForTotalDistanceRows(self): + self.step = QadARRAYCommandClassStepEnum.ASK_FOR_ROW_SPACE_TOT + + if self.arrayType == QadARRAYCommandClassSeriesTypeEnum.RECTANGLE: + default = self.rectangleRows * self.distanceBetweenRows + inputMode = QadInputModeEnum.NOT_ZERO + elif self.arrayType == QadARRAYCommandClassSeriesTypeEnum.PATH: + default = self.pathRows * self.distanceBetweenRows + inputMode = QadInputModeEnum.NOT_ZERO + elif self.arrayType == QadARRAYCommandClassSeriesTypeEnum.POLAR: + default = self.polarRows * self.distanceBetweenRows + inputMode = QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE + + if self.GetDistClass is not None: + del self.GetDistClass + self.GetDistClass = QadGetDistClass(self.plugIn) + prompt = QadMsg.translate("Command_ARRAY", "Specifies the total distance between the start and end row <{0}>: ") + self.GetDistClass.msg = prompt.format(str(default)) + self.GetDistClass.dist = default + self.GetDistClass.inputMode = inputMode + self.GetDistClass.run() + + + # ============================================================================ + # waitForDelOrigObjs + # ============================================================================ + def waitForDelOrigObjs(self): + self.step = QadARRAYCommandClassStepEnum.ASK_FOR_DEL_ORIG_OBJS + # imposto il map tool + self.getPointMapTool().setMode(Qad_array_maptool_ModeEnum.NONE) + + keyWords = QadMsg.translate("QAD", "Yes") + "/" + QadMsg.translate("QAD", "No") + self.defaultValue = QadMsg.translate("QAD", "Yes") + prompt = QadMsg.translate("Command_ARRAY", "Delete source objects of the array ? [{0}] <{1}>: ").format(keyWords, self.defaultValue) + + englishKeyWords = "Yes" + "/" + "No" + keyWords += "_" + englishKeyWords + + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.KEYWORDS, \ + self.defaultValue, \ + keyWords, QadInputModeEnum.NONE) + + + # ============================================================================ + # waitForItemsRotation + # ============================================================================ + def waitForItemsRotation(self): + self.step = QadARRAYCommandClassStepEnum.ASK_FOR_ITEM_ROTATION + # imposto il map tool + self.getPointMapTool().setMode(Qad_array_maptool_ModeEnum.NONE) + + keyWords = QadMsg.translate("QAD", "Yes") + "/" + QadMsg.translate("QAD", "No") + if self.itemsRotation: + self.defaultValue = QadMsg.translate("QAD", "Yes") + else: + self.defaultValue = QadMsg.translate("QAD", "No") + + if self.arrayType == QadARRAYCommandClassSeriesTypeEnum.RECTANGLE: + prompt = QadMsg.translate("Command_ARRAY", "Rotate objects as they are arrayed ? [{0}] <{1}>: ").format(keyWords, self.defaultValue) + elif self.arrayType == QadARRAYCommandClassSeriesTypeEnum.PATH: + prompt = QadMsg.translate("Command_ARRAY", "Align arrayed items to the path ? [{0}] <{1}>: ").format(keyWords, self.defaultValue) + elif self.arrayType == QadARRAYCommandClassSeriesTypeEnum.POLAR: + prompt = QadMsg.translate("Command_ARRAY", "Rotate objects as they are arrayed ? [{0}] <{1}>: ").format(keyWords, self.defaultValue) + + englishKeyWords = "Yes" + "/" + "No" + keyWords += "_" + englishKeyWords + + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.KEYWORDS, \ + self.defaultValue, \ + keyWords, QadInputModeEnum.NONE) + + + # ============================================================================ + # SERIE RETTANGOLARE - INIZIO + # ============================================================================ + + + # ============================================================================ + # waitForRectangleArrayOptions + # ============================================================================ + def waitForRectangleArrayOptions(self): + self.step = QadARRAYCommandClassStepEnum.ASK_FOR_MAIN_OPTIONS + # imposto il map tool + self.getPointMapTool().setMode(Qad_array_maptool_ModeEnum.NONE) + + keyWords = QadMsg.translate("Command_ARRAY", "Base point") + "/" + \ + QadMsg.translate("Command_ARRAY", "Angle") + "/" + \ + QadMsg.translate("Command_ARRAY", "COUnt") + "/" + \ + QadMsg.translate("Command_ARRAY", "Spacing") + "/" + \ + QadMsg.translate("Command_ARRAY", "Columns") + "/" + \ + QadMsg.translate("Command_ARRAY", "Rows") + "/" + \ + QadMsg.translate("Command_ARRAY", "rotate Items") + "/" + \ + QadMsg.translate("Command_ARRAY", "eXit") + englishKeyWords = "Base point" + "/" + "Angle" + "/" + "COUnt" + "/" + "Spacing" + "/" + \ + "Columns" + "/" + "Rows" + "/" + "rotate Items" + "/" + "eXit" + + self.defaultValue = QadMsg.translate("Command_ARRAY", "eXit") + prompt = QadMsg.translate("Command_ARRAY", "Select an option to edit array [{0}] <{1}>: ").format(keyWords, self.defaultValue) + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.KEYWORDS, \ + self.defaultValue, \ + keyWords, QadInputModeEnum.NONE) + + + # ============================================================================ + # waitForRectangleAngle + # ============================================================================ + def waitForRectangleAngle(self): + self.step = QadARRAYCommandClassStepEnum.ASK_FOR_ANGLE + if self.GetAngleClass is not None: + del self.GetAngleClass + # si appresta ad attendere l'angolo di rotazione + self.GetAngleClass = QadGetAngleClass(self.plugIn) + prompt = QadMsg.translate("Command_ARRAY", "Specify the angle of rotation for the row axis <{0}>: ") + self.GetAngleClass.msg = prompt.format(str(qad_utils.toDegrees(self.rectangleAngle))) + self.GetAngleClass.angle = self.rectangleAngle + self.GetAngleClass.run() + return False + + + # ============================================================================ + # waitForRectangleColumns + # ============================================================================ + def waitForRectangleColumns(self, nextStep): + self.step = nextStep + # imposto il map tool + self.getPointMapTool().setMode(Qad_array_maptool_ModeEnum.NONE) + + # optionFrom può essere ASK_FOR_COLUMN_COUNT o ASK_FOR_COLUMN_N + self.defaultValue = self.rectangleCols + # si appresta ad attendere un numero intero + msg = QadMsg.translate("Command_ARRAY", "Specify number of columns <{0}>: ") + # msg, inputType, default, keyWords, valori positivi + self.waitFor(msg.format(str(self.defaultValue)), \ + QadInputTypeEnum.INT, \ + self.defaultValue, \ + "", \ + QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) + + + # ============================================================================ + # waitForRectangleColumnsSpacing + # ============================================================================ + def waitForRectangleColumnsSpacing(self): + self.step = QadARRAYCommandClassStepEnum.ASK_FOR_COLUMN_SPACE_OR_CELL + # imposto il map tool + self.getPointMapTool().setMode(Qad_array_maptool_ModeEnum.ASK_FOR_COLUMN_SPACE_FIRST_PT) + + self.defaultValue = self.distanceBetweenCols + keyWords = QadMsg.translate("Command_ARRAY", "Unit cell") + englishKeyWords = "Unit cell" + prompt = QadMsg.translate("Command_ARRAY", "Specify distance between columns or [{0}] <{1}>: ") + prompt = prompt.format(keyWords, str(self.defaultValue)) + keyWords += "_" + englishKeyWords + + # si appresta ad attendere un punto, un numero reale o enter o una parola chiave + # msg, inputType, default, keyWords, valori positivi + self.waitFor(prompt, \ + QadInputTypeEnum.FLOAT | QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + self.defaultValue, \ + keyWords, \ + QadInputModeEnum.NOT_ZERO) + + + # ============================================================================ + # waitForRectangleColumnsSpacing2Pt + # ============================================================================ + def waitForRectangleColumnsSpacing2Pt(self, startPt): + self.step = QadARRAYCommandClassStepEnum.ASK_FOR_COLUMN_SPACE_2PT + + if self.GetDistClass is not None: + del self.GetDistClass + self.GetDistClass = QadGetDistClass(self.plugIn) + self.GetDistClass.dist = self.distanceBetweenCols + self.GetDistClass.inputMode = QadInputModeEnum.NOT_ZERO + self.GetDistClass.startPt = startPt + self.GetDistClass.run() + + + # ============================================================================ + # waitForRectangleTotalDistanceCols + # ============================================================================ + def waitForRectangleTotalDistanceCols(self): + self.step = QadARRAYCommandClassStepEnum.ASK_FOR_COLUMN_SPACE_TOT + + if self.GetDistClass is not None: + del self.GetDistClass + self.GetDistClass = QadGetDistClass(self.plugIn) + prompt = QadMsg.translate("Command_ARRAY", "Specifies the total distance between the start and end columns <{0}>: ") + default = self.rectangleCols * self.distanceBetweenCols + self.GetDistClass.msg = prompt.format(str(default)) + self.GetDistClass.dist = default + self.GetDistClass.inputMode = QadInputModeEnum.NOT_ZERO + self.GetDistClass.run() + + + # ============================================================================ + # waitForRectangleDistanceBetweenCols + # ============================================================================ + def waitForRectangleDistanceBetweenCols(self, totalOption): + self.step = QadARRAYCommandClassStepEnum.ASK_FOR_COLUMN_SPACE_OR_TOT + # imposto il map tool + self.getPointMapTool().setMode(Qad_array_maptool_ModeEnum.ASK_FOR_COLUMN_SPACE_FIRST_PT) + + self.defaultValue = self.distanceBetweenCols + + if totalOption: + keyWords = QadMsg.translate("Command_ARRAY", "Total") + englishKeyWords = "Total" + prompt = QadMsg.translate("Command_ARRAY", "Specify distance between columns or [{0}] <{1}>: ").format(keyWords, str(self.defaultValue)) + keyWords += "_" + englishKeyWords + inputType = QadInputTypeEnum.FLOAT | QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS + else: + prompt = QadMsg.translate("Command_ARRAY", "Specify distance between columns <{0}>: ").format(str(self.defaultValue)) + keyWords = "" + inputType = QadInputTypeEnum.FLOAT | QadInputTypeEnum.POINT2D + + # si appresta ad attendere un punto, un numero reale o enter o una parola chiave + # msg, inputType, default, keyWords, valori positivi + self.waitFor(prompt, \ + inputType, \ + self.defaultValue, \ + keyWords, \ + QadInputModeEnum.NOT_ZERO) + + + # ============================================================================ + # waitForRectangleFirstCellCorner + # ============================================================================ + def waitForRectangleFirstCellCorner(self): + self.step = QadARRAYCommandClassStepEnum.ASK_FOR_1PT_CELL + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_ARRAY", "Specify first cell corner: ")) + + + # ============================================================================ + # waitForRectangleSecondCellCorner + # ============================================================================ + def waitForRectangleSecondCellCorner(self): + self.step = QadARRAYCommandClassStepEnum.ASK_FOR_2PT_CELL + # imposto il map tool + self.getPointMapTool().firstPt = self.firstPt + self.getPointMapTool().setMode(Qad_array_maptool_ModeEnum.ASK_FOR_2PT_CELL) + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_ARRAY", "Specify second cell corner: ")) + + + # ============================================================================ + # SERIE RETTANGOLARE - FINE + # SERIE TRAIETTORIA - INIZIO + # ============================================================================ + + + # ============================================================================ + # waitForPathObject + # ============================================================================ + def waitForPathObject(self, msgMapTool, msg): + if self.entSelClass is not None: + del self.entSelClass + + self.step = QadARRAYCommandClassStepEnum.ASK_FOR_PATH_OBJ + + self.entSelClass = QadEntSelClass(self.plugIn) + self.entSelClass.msg = QadMsg.translate("Command_ARRAY", "Select the object to use for the path of the array: ") + # scarto la selezione di punti e quote + self.entSelClass.checkPointLayer = False + self.entSelClass.checkLineLayer = True + self.entSelClass.checkPolygonLayer = True + self.entSelClass.checkDimLayers = False + + self.entSelClass.run(msgMapTool, msg) + + + # ============================================================================ + # waitForPathArrayOptions + # ============================================================================ + def waitForPathArrayOptions(self): + self.step = QadARRAYCommandClassStepEnum.ASK_FOR_MAIN_OPTIONS + # imposto il map tool + self.getPointMapTool().setMode(Qad_array_maptool_ModeEnum.NONE) + + keyWords = QadMsg.translate("Command_ARRAY", "Method") + "/" + \ + QadMsg.translate("Command_ARRAY", "Base point") + "/" + \ + QadMsg.translate("Command_ARRAY", "Tangent direction") + "/" + \ + QadMsg.translate("Command_ARRAY", "Items") + "/" + \ + QadMsg.translate("Command_ARRAY", "Rows") + "/" + \ + QadMsg.translate("Command_ARRAY", "Align items") + "/" + \ + QadMsg.translate("Command_ARRAY", "eXit") + englishKeyWords = "Method" + "/" + "Base point" + "/" + "Tangent direction" + "/" + "Items" + "/" + \ + "Rows" + "/" + "Align items" + "/" + "eXit" + + self.defaultValue = QadMsg.translate("Command_ARRAY", "eXit") + prompt = QadMsg.translate("Command_ARRAY", "Select an option to edit array [{0}] <{1}>: ").format(keyWords, self.defaultValue) + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.KEYWORDS, \ + self.defaultValue, \ + keyWords, QadInputModeEnum.NONE) + + + # ============================================================================ + # waitForPathMethod + # ============================================================================ + def waitForPathMethod(self): + self.step = QadARRAYCommandClassStepEnum.ASK_FOR_PATH_METHOD + + keyWords = QadMsg.translate("Command_ARRAY", "Divide") + "/" + QadMsg.translate("Command_ARRAY", "Measure") + if self.pathMethod == QadARRAYCommandClassPathMethodTypeEnum.DIVIDE: + self.defaultValue = QadMsg.translate("Command_ARRAY", "Divide") + elif self.pathMethod == QadARRAYCommandClassPathMethodTypeEnum.MEASURE: + self.defaultValue = QadMsg.translate("Command_ARRAY", "Measure") + prompt = QadMsg.translate("Command_ARRAY", "Specify path method [{0}] <{1}>: ").format(keyWords, self.defaultValue) + + englishKeyWords = "Divide" + "/" + "Measure" + keyWords += "_" + englishKeyWords + + # si appresta ad attendere enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.KEYWORDS, \ + self.defaultValue, \ + keyWords, QadInputModeEnum.NONE) + + + # ============================================================================ + # waitForPathTangentDirection + # ============================================================================ + def waitForPathTangentDirection(self): + self.step = QadARRAYCommandClassStepEnum.ASK_FOR_TAN_DIRECTION + + if self.GetAngleClass is not None: + del self.GetAngleClass + # si appresta ad attendere l'angolo di rotazione + self.GetAngleClass = QadGetAngleClass(self.plugIn) + prompt = QadMsg.translate("Command_ARRAY", "Specify the first point for array tangent direction: ") + self.GetAngleClass.msg = prompt.format(str(qad_utils.toDegrees(self.pathTangentDirection))) + self.GetAngleClass.angle = self.pathTangentDirection + self.GetAngleClass.run() + return False + + + # ============================================================================ + # waitForPathDistanceBetweenItems + # ============================================================================ + def waitForPathDistanceBetweenItems(self): + self.step = QadARRAYCommandClassStepEnum.ASK_FOR_ITEM_SPACE + + if self.GetDistClass is not None: + del self.GetDistClass + self.GetDistClass = QadGetDistClass(self.plugIn) + prompt = QadMsg.translate("Command_ARRAY", "Specify distance between items along path <{0}>: ") + self.GetDistClass.msg = prompt.format(str(self.distanceBetweenCols)) + self.GetDistClass.dist = self.distanceBetweenCols + self.GetDistClass.inputMode = QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE + self.GetDistClass.run() + + + # ============================================================================ + # SERIE TRAIETTORIA - FINE + # SERIE POLARE - INIZIO + # ============================================================================ + + + # ============================================================================ + # waitForPolarCenterPt + # ============================================================================ + def waitForPolarCenterPt(self): + self.step = QadARRAYCommandClassStepEnum.ASK_FOR_CENTER_PT + + keyWords = QadMsg.translate("Command_ARRAY", "Base point") + englishKeyWords = "Base point" + prompt = QadMsg.translate("Command_ARRAY", "Specify center point of array or [{0}]: ").format(keyWords) + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, QadInputModeEnum.NONE) + + + # ============================================================================ + # waitForPolarArrayOptions + # ============================================================================ + def waitForPolarArrayOptions(self): + self.step = QadARRAYCommandClassStepEnum.ASK_FOR_MAIN_OPTIONS + # imposto il map tool + self.getPointMapTool().setMode(Qad_array_maptool_ModeEnum.NONE) + + keyWords = QadMsg.translate("Command_ARRAY", "Base point") + "/" + \ + QadMsg.translate("Command_ARRAY", "Items") + "/" + \ + QadMsg.translate("Command_ARRAY", "Angle between") + "/" + \ + QadMsg.translate("Command_ARRAY", "Fill angle") + "/" + \ + QadMsg.translate("Command_ARRAY", "ROWs") + "/" + \ + QadMsg.translate("Command_ARRAY", "Rotate items") + "/" + \ + QadMsg.translate("Command_ARRAY", "eXit") + englishKeyWords = "Base point" + "/" + "Items" + "/" + "Angle between" + "/" + "Angle between" + "/" + \ + "Fill angle" + "/" + "ROWs" + "/" + "Rotate items" + "/" + "eXit" + + self.defaultValue = QadMsg.translate("Command_ARRAY", "eXit") + prompt = QadMsg.translate("Command_ARRAY", "Select an option to edit array [{0}] <{1}>: ").format(keyWords, self.defaultValue) + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.KEYWORDS, \ + self.defaultValue, \ + keyWords, QadInputModeEnum.NONE) + + + # ============================================================================ + # waitForPolarAngleBetween + # ============================================================================ + def waitForPolarAngleBetween(self): + self.step = QadARRAYCommandClassStepEnum.ASK_FOR_ANGLE_BETWEEN_ITEMS + + if self.GetAngleClass is not None: + del self.GetAngleClass + # si appresta ad attendere l'angolo di rotazione + self.GetAngleClass = QadGetAngleClass(self.plugIn) + prompt = QadMsg.translate("Command_ARRAY", "Specify the angle between items <{0}>: ") + self.GetAngleClass.msg = prompt.format(str(qad_utils.toDegrees(self.polarAngleBetween))) + self.GetAngleClass.angle = self.polarAngleBetween + self.GetAngleClass.run() + return False + + + # ============================================================================ + # waitForPolarAngleBetween + # ============================================================================ + def waitForPolarFillAngle(self): + self.step = QadARRAYCommandClassStepEnum.ASK_FOR_FULL_ANGLE + + if self.GetAngleClass is not None: + del self.GetAngleClass + # si appresta ad attendere l'angolo di rotazione + self.GetAngleClass = QadGetAngleClass(self.plugIn) + default = self.polarItemsNumber * self.polarAngleBetween + prompt = QadMsg.translate("Command_ARRAY", "Specify angle to fill (+ = CCW, - = CW) <{0}>: ") + self.GetAngleClass.msg = prompt.format(str(qad_utils.toDegrees(default))) + self.GetAngleClass.angle = default + self.GetAngleClass.run() + return False + + + # ============================================================================ + # SERIE POLARE - FINE + # ============================================================================ + + + # ============================================================================ + # run + # ============================================================================ + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + # ========================================================================= + # RICHIESTA SELEZIONE OGGETTI + if self.step == QadARRAYCommandClassStepEnum.ASK_FOR_SELSET: # inizio del comando + if self.cacheEntitySet.isEmpty() == False: # se era già stato impostato da codice tramite "self.setEntitySet" + self.waitForArrayType() + return False + + if self.SSGetClass.run(msgMapTool, msg) == True: + if self.SSGetClass.entitySet.count() == 0: + return True # fine comando + self.setEntitySet(self.SSGetClass.entitySet) + + del self.SSGetClass + self.SSGetClass = None + + self.waitForArrayType() + self.step = -1 * self.step # trucchetto per prendere il map tool base + self.getPointMapTool().refreshSnapType() # aggiorno lo snapType che può essere variato dal maptool di selezione entità + self.step = -1 * self.step # trucchetto per prendere il map tool base + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL TIPO DI SERIE (da step = ASK_FOR_SELSET) + elif self.step == QadARRAYCommandClassStepEnum.ASK_FOR_ARRAYTYPE: # dopo aver atteso una parola chiave si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + value = self.defaultValue + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: # la parola chiave arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("Command_ARRAY", "Rectangular") or value == "Rectangular": + self.arrayType = QadARRAYCommandClassSeriesTypeEnum.RECTANGLE + self.plugIn.setLastArrayType_array(self.arrayType) + self.waitForMainOptions() + elif value == QadMsg.translate("Command_ARRAY", "PAth") or value == "PAth": + self.arrayType = QadARRAYCommandClassSeriesTypeEnum.PATH + self.plugIn.setLastArrayType_array(self.arrayType) + self.waitForPathObject(msgMapTool, msg) + elif value == QadMsg.translate("Command_ARRAY", "POlar") or value == "POlar": + self.arrayType = QadARRAYCommandClassSeriesTypeEnum.POLAR + self.plugIn.setLastArrayType_array(self.arrayType) + self.waitForPolarCenterPt() + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DI UN OPZIONE DAL MENU PRINCIPALE (da step = ASK_FOR_ARRAYTYPE da tutte le opzioni) + elif self.step == QadARRAYCommandClassStepEnum.ASK_FOR_MAIN_OPTIONS: # dopo aver atteso un punto o una parola chiave si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + value = self.defaultValue + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: # il punto arriva come parametro della funzione + value = msg + + if value is None: + value = QadMsg.translate("Command_ARRAY", "eXit") + + if type(value) == unicode: + if self.arrayType == QadARRAYCommandClassSeriesTypeEnum.RECTANGLE: + if value == QadMsg.translate("Command_ARRAY", "Base point") or value == "Base point": + self.waitForBasePt() + elif value == QadMsg.translate("Command_ARRAY", "Angle") or value == "Angle": + self.waitForRectangleAngle() + elif value == QadMsg.translate("Command_ARRAY", "COUnt") or value == "COUnt": + self.waitForRectangleColumns(QadARRAYCommandClassStepEnum.ASK_FOR_COLUMN_COUNT) + elif value == QadMsg.translate("Command_ARRAY", "Spacing") or value == "Spacing": + self.waitForRectangleColumnsSpacing() + elif value == QadMsg.translate("Command_ARRAY", "Columns") or value == "Columns": + self.waitForRectangleColumns(QadARRAYCommandClassStepEnum.ASK_FOR_COLUMN_N) + elif value == QadMsg.translate("Command_ARRAY", "Rows") or value == "Rows": + self.waitForRows(QadARRAYCommandClassStepEnum.ASK_FOR_ROW_N) + elif value == QadMsg.translate("Command_ARRAY", "rotate Items") or value == "rotate Items": + self.waitForItemsRotation() + elif value == QadMsg.translate("Command_ARRAY", "eXit") or value == "eXit": + if self.delObj == QadDELOBJEnum.ASK_FOR_DELETE_ALL: + self.waitForDelOrigObjs() + else: + self.doRectangleArray() + return True # fine comando + + elif self.arrayType == QadARRAYCommandClassSeriesTypeEnum.PATH: + if value == QadMsg.translate("Command_ARRAY", "Method") or value == "Method": + self.waitForPathMethod() + elif value == QadMsg.translate("Command_ARRAY", "Base point") or value == "Base point": + self.waitForBasePt() + elif value == QadMsg.translate("Command_ARRAY", "Tangent direction") or value == "Tangent direction": + self.waitForPathTangentDirection() + elif value == QadMsg.translate("Command_ARRAY", "Items") or value == "Items": + if self.pathMethod == QadARRAYCommandClassPathMethodTypeEnum.MEASURE: + self.waitForPathDistanceBetweenItems() + elif self.pathMethod == QadARRAYCommandClassPathMethodTypeEnum.DIVIDE: + self.waitForItemsNumber() + elif value == QadMsg.translate("Command_ARRAY", "Rows") or value == "Rows": + self.waitForRows(QadARRAYCommandClassStepEnum.ASK_FOR_ROW_N) + elif value == QadMsg.translate("Command_ARRAY", "Align items") or value == "Align items": + self.waitForItemsRotation() + elif value == QadMsg.translate("Command_ARRAY", "eXit") or value == "eXit": + if self.delObj == QadDELOBJEnum.ASK_FOR_DELETE_ALL: + self.waitForDelOrigObjs() + else: + self.doPathArray() + return True # fine comando + + elif self.arrayType == QadARRAYCommandClassSeriesTypeEnum.POLAR: + if value == QadMsg.translate("Command_ARRAY", "Base point") or value == "Base point": + self.waitForBasePt() + elif value == QadMsg.translate("Command_ARRAY", "Items") or value == "Items": + self.waitForItemsNumber() + elif value == QadMsg.translate("Command_ARRAY", "Angle between") or value == "Angle between": + self.waitForPolarAngleBetween() + elif value == QadMsg.translate("Command_ARRAY", "Fill angle") or value == "Fill angle": + self.waitForPolarFillAngle() + elif value == QadMsg.translate("Command_ARRAY", "ROWs") or value == "ROWs": + self.waitForRows(QadARRAYCommandClassStepEnum.ASK_FOR_ROW_N) + elif value == QadMsg.translate("Command_ARRAY", "Rotate items") or value == "Rotate items": + self.waitForItemsRotation() + elif value == QadMsg.translate("Command_ARRAY", "eXit") or value == "eXit": + if self.delObj == QadDELOBJEnum.ASK_FOR_DELETE_ALL: + self.waitForDelOrigObjs() + else: + self.doPolarArray() + return True # fine comando + elif type(value) == QgsPointXY: # se é stato indicato un punto + pass + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL PUNTO BASE (da step = ASK_FOR_MAIN_OPTIONS) + elif self.step == QadARRAYCommandClassStepEnum.ASK_FOR_BASE_PT: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + self.basePt.set(value.x(), value.y()) + + self.waitForMainOptions() + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DI CANCELLAZIONE DEGLI OGGETTI ORIGINALI (da step = ASK_FOR_MAIN_OPTIONS) + elif self.step == QadARRAYCommandClassStepEnum.ASK_FOR_DEL_ORIG_OBJS: # dopo aver atteso una parola chiave si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + value = self.defaultValue + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: + # la parola chiave arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("QAD", "Yes") or value == "Yes": + self.delOrigSelSet = True + else: + self.delOrigSelSet = False + + if self.arrayType == QadARRAYCommandClassSeriesTypeEnum.RECTANGLE: + self.doRectangleArray() + elif self.arrayType == QadARRAYCommandClassSeriesTypeEnum.PATH: + self.doPathArray() + elif self.arrayType == QadARRAYCommandClassSeriesTypeEnum.POLAR: + self.doPolarArray() + + return True + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DELL'ANGOLO DELLA SERIE (da step = ASK_FOR_MAIN_OPTIONS) + # ========================================================================= + elif self.step == QadARRAYCommandClassStepEnum.ASK_FOR_ANGLE: + if self.GetAngleClass.run(msgMapTool, msg) == True: + if self.GetAngleClass.angle is not None: + self.rectangleAngle = self.GetAngleClass.angle + self.plugIn.setLastRectangleAngle_array(self.rectangleAngle) + self.plugIn.setLastRot(self.rectangleAngle) + self.waitForMainOptions() + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL NUMERO DELLE COLONNE DELLA SERIE RETTANGOLO OPZIONE COUNT (da step = ASK_FOR_MAIN_OPTIONS) + # ========================================================================= + elif self.step == QadARRAYCommandClassStepEnum.ASK_FOR_COLUMN_COUNT: # dopo aver atteso un numero intero si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + value = self.defaultValue + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: + # il numero di colonne arriva come parametro della funzione + value = msg + + maxArray = QadVariables.get(QadMsg.translate("Environment variables", "MAXARRAY")) + if value * self.rectangleRows > maxArray: + errMsg = QadMsg.translate("Command_ARRAY", "\nThe array size can't be greater than {0} elements. See MAXARRAY system variable.") + self.showErr(errMsg.format(str(maxArray))) + else: + self.rectangleCols = value + self.plugIn.setLastRectangleCols_array(self.rectangleCols) + self.updatePointMapToolParams() + self.waitForRows(QadARRAYCommandClassStepEnum.ASK_FOR_ROW_COUNT) + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL NUMERO DI RIGHE DELLA SERIE RETTANGOLO OPZIONE COUNT (da step = ASK_FOR_COLUMN_N) + # ========================================================================= + elif self.step == QadARRAYCommandClassStepEnum.ASK_FOR_ROW_COUNT: # dopo aver atteso un numero intero si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + value = self.defaultValue + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: + # il numero di righe arriva come parametro della funzione + value = msg + + maxArray = QadVariables.get(QadMsg.translate("Environment variables", "MAXARRAY")) + if value * self.rectangleCols > maxArray: + errMsg = QadMsg.translate("Command_ARRAY", "\nThe array size can't be greater than {0} elements. See MAXARRAY system variable.") + self.showErr(errMsg.format(str(maxArray))) + else: + self.rectangleRows = value + self.plugIn.setLastRectangleRows_array(self.rectangleRows) + self.waitForMainOptions() + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DELLA DISTANZA TRA COLONNE (da step = ASK_FOR_MAIN_OPTIONS) + elif self.step == QadARRAYCommandClassStepEnum.ASK_FOR_COLUMN_SPACE_OR_CELL: # dopo aver atteso un punto, un numero o una parola chiave si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + value = self.defaultValue + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == QgsPointXY: # se é stato inserito il primo punto per misurare la distanza tra colonne + self.waitForRectangleColumnsSpacing2Pt(value) + elif type(value) == float: # se é stato inserita la distanza + self.distanceBetweenCols = value + self.updatePointMapToolParams() + self.waitForDistanceBetweenRows(False, QadARRAYCommandClassStepEnum.ASK_FOR_ROW_SPACE) # senza opzione di "totale" + elif type(value) == unicode: + if value == QadMsg.translate("Command_ARRAY", "Unit cell") or value == "Unit cell": + self.waitForRectangleFirstCellCorner() + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL SECONDO PT PER MISURARE LA DISTANZA TRA COLONNE (da step = ASK_FOR_COLUMN_SPACE_OR_CELL) + elif self.step == QadARRAYCommandClassStepEnum.ASK_FOR_COLUMN_SPACE_2PT: # dopo aver atteso un punto si riavvia il comando + if self.GetDistClass.run(msgMapTool, msg) == True: + if self.GetDistClass.dist is not None: + self.distanceBetweenCols = self.GetDistClass.dist + + del self.GetDistClass + self.GetDistClass = None + + self.updatePointMapToolParams() + self.waitForDistanceBetweenRows(False, QadARRAYCommandClassStepEnum.ASK_FOR_ROW_SPACE) # senza opzione di "totale" + return False # fine comando + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DELLA DISTANZA TRA RIGHE (da step = ASK_FOR_COLUMN_SPACE_OR_CELL) + elif self.step == QadARRAYCommandClassStepEnum.ASK_FOR_ROW_SPACE: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + value = self.defaultValue + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == QgsPointXY: # se é stato inserito il primo punto per misurare la distanza tra righe + self.waitForDistanceBetweenRows2Pt(value) + elif type(value) == float: # se é stato inserita la distanza + self.distanceBetweenRows = value + self.waitForMainOptions() + return False # fine comando + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL SECONDO PT PER MISURARE LA DISTANZA TRA RIGHE (da step = ASK_FOR_ROW_SPACE) + elif self.step == QadARRAYCommandClassStepEnum.ASK_FOR_ROW_SPACE_2PT: # dopo aver atteso un punto si riavvia il comando + if self.GetDistClass.run(msgMapTool, msg) == True: + if self.GetDistClass.dist is not None: + self.distanceBetweenRows = self.GetDistClass.dist + + del self.GetDistClass + self.GetDistClass = None + self.waitForMainOptions() + return False # fine comando + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL PRIMO ANGOLO DELLA CELLA (da step = ASK_FOR_COLUMN_SPACE_OR_CELL) + elif self.step == QadARRAYCommandClassStepEnum.ASK_FOR_1PT_CELL: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == QgsPointXY: # se é stato inserito il primo punto per misurare la distanza tra righe + self.firstPt.set(value.x(), value.y()) + self.waitForRectangleSecondCellCorner() + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL SECONDO ANGOLO DELLA CELLA (da step = ASK_FOR_1PT_CELL) + elif self.step == QadARRAYCommandClassStepEnum.ASK_FOR_2PT_CELL: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == QgsPointXY: # se é stato inserito il primo punto per misurare la distanza tra righe + if (value.y() - self.firstPt.y()) == 0 or (value.x() - self.firstPt.x()) == 0: + self.showErr(QadMsg.translate("Command_ARRAY", "\nCell size must be greater than 0.")) + else: + self.distanceBetweenRows = value.y() - self.firstPt.y() + self.distanceBetweenCols = value.x() - self.firstPt.x() + self.waitForMainOptions() + else: + self.waitForMainOptions() + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL NUMERO DELLE COLONNE DELLA SERIE RETTANGOLO OPZIONE COLUMN (da step = ASK_FOR_MAIN_OPTIONS) + # ========================================================================= + elif self.step == QadARRAYCommandClassStepEnum.ASK_FOR_COLUMN_N: # dopo aver atteso un numero intero si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + value = self.defaultValue + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: + # il numero di righe arriva come parametro della funzione + value = msg + + # il numero delle colonnne arriva come parametro della funzione + maxArray = QadVariables.get(QadMsg.translate("Environment variables", "MAXARRAY")) + if value * self.rectangleRows > maxArray: + errMsg = QadMsg.translate("Command_ARRAY", "\nThe array size can't be greater than {0} elements. See MAXARRAY system variable.") + self.showErr(errMsg.format(str(maxArray))) + else: + self.rectangleCols = value + self.plugIn.setLastRectangleCols_array(self.rectangleCols) + self.updatePointMapToolParams() + self.waitForRectangleDistanceBetweenCols(True) # con opzione "TOTAL" + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DELLA DISTANZA TRA COLONNE (da step = ASK_FOR_COLUMN_N) + elif self.step == QadARRAYCommandClassStepEnum.ASK_FOR_COLUMN_SPACE_OR_TOT: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + value = self.defaultValue + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == QgsPointXY: # se é stato inserito il primo punto per misurare la distanza tra righe + self.waitForRectangleColumnsSpacing2Pt(value) + elif type(value) == float: # se é stato inserita la distanza + self.distanceBetweenCols = value + self.waitForMainOptions() + elif type(value) == unicode: + if value == QadMsg.translate("Command_ARRAY", "Total") or value == "Total": + self.waitForRectangleTotalDistanceCols() + return False # fine comando + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL SECONDO PT PER MISURARE LA DISTANZA TOTALE TRA COLONNE (da step = ASK_FOR_COLUMN_SPACE_OR_TOT) + elif self.step == QadARRAYCommandClassStepEnum.ASK_FOR_COLUMN_SPACE_TOT: # dopo aver atteso un punto si riavvia il comando + if self.GetDistClass.run(msgMapTool, msg) == True: + if self.GetDistClass.dist is not None: + if self.rectangleCols > 1: + self.distanceBetweenCols = self.GetDistClass.dist / (self.rectangleCols - 1) + + del self.GetDistClass + self.GetDistClass = None + self.waitForMainOptions() + return False # fine comando + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL NUMERO DELLE RIGHE OPZIONE ROW (da step = ASK_FOR_MAIN_OPTIONS) + # ========================================================================= + elif self.step == QadARRAYCommandClassStepEnum.ASK_FOR_ROW_N: # dopo aver atteso un numero intero si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + value = self.defaultValue + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: + # il numero di righe arriva come parametro della funzione + value = msg + + maxArray = QadVariables.get(QadMsg.translate("Environment variables", "MAXARRAY")) + # il numero di righe arriva come parametro della funzione + if self.arrayType == QadARRAYCommandClassSeriesTypeEnum.RECTANGLE: + if value * self.rectangleCols > maxArray: + errMsg = QadMsg.translate("Command_ARRAY", "\nThe array size can't be greater than {0} elements. See MAXARRAY system variable.") + self.showErr(errMsg.format(str(maxArray))) + return False + else: + self.rectangleRows = value + self.plugIn.setLastRectangleRows_array(self.rectangleRows) + elif self.arrayType == QadARRAYCommandClassSeriesTypeEnum.PATH: + if value * self.pathItemsNumber > maxArray: + errMsg = QadMsg.translate("Command_ARRAY", "\nThe array size can't be greater than {0} elements. See MAXARRAY system variable.") + self.showErr(errMsg.format(str(maxArray))) + return False + else: + self.pathRows = value + self.plugIn.setLastPathRows_array(self.pathRows) + elif self.arrayType == QadARRAYCommandClassSeriesTypeEnum.POLAR: + if value * self.polarItemsNumber > maxArray: + errMsg = QadMsg.translate("Command_ARRAY", "\nThe array size can't be greater than {0} elements. See MAXARRAY system variable.") + self.showErr(errMsg.format(str(maxArray))) + return False + else: + self.polarRows = value + self.plugIn.setLastPolarRows_array(self.polarRows) + + self.updatePointMapToolParams() + self.waitForDistanceBetweenRows(True, QadARRAYCommandClassStepEnum.ASK_FOR_ROW_SPACE_OR_TOT) # con opzione "TOTAL" + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DELLA DISTANZA TRA RIGHE (da step = ASK_FOR_ROW_N) + elif self.step == QadARRAYCommandClassStepEnum.ASK_FOR_ROW_SPACE_OR_TOT: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + value = self.defaultValue + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == QgsPointXY: # se é stato inserito il primo punto per misurare la distanza tra righe + self.waitForDistanceBetweenRows2Pt(value) + elif type(value) == float: # se é stato inserita la distanza + self.distanceBetweenRows = value + self.waitForMainOptions() + elif type(value) == unicode: + if value == QadMsg.translate("Command_ARRAY", "Total") or value == "Total": + self.waitForTotalDistanceRows() + return False # fine comando + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL SECONDO PT PER MISURARE LA DISTANZA TOTALE TRA RIGHE (da step = ASK_FOR_ROW_SPACE) + elif self.step == QadARRAYCommandClassStepEnum.ASK_FOR_ROW_SPACE_TOT: # dopo aver atteso un punto si riavvia il comando + if self.GetDistClass.run(msgMapTool, msg) == True: + if self.GetDistClass.dist is not None: + if self.arrayType == QadARRAYCommandClassSeriesTypeEnum.RECTANGLE: + if self.rectangleRows > 1: + self.distanceBetweenRows = self.GetDistClass.dist / (self.rectangleRows - 1) + elif self.arrayType == QadARRAYCommandClassSeriesTypeEnum.PATH: + if self.pathRows > 1: + self.distanceBetweenRows = self.GetDistClass.dist / (self.pathRows - 1) + elif self.arrayType == QadARRAYCommandClassSeriesTypeEnum.POLAR: + if self.polarRows > 1: + self.distanceBetweenRows = self.GetDistClass.dist / (self.polarRows - 1) + + del self.GetDistClass + self.GetDistClass = None + self.waitForMainOptions() + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL NUMERO DI ELEMENTI DELLA SERIE (da step = ASK_FOR_MAIN_OPTIONS) + elif self.step == QadARRAYCommandClassStepEnum.ASK_FOR_ITEM_N: + if msgMapTool == True: # il punto arriva da una selezione grafica + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + value = self.defaultValue + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: + # il numero di elementi arriva come parametro della funzione + value = msg + + maxArray = QadVariables.get(QadMsg.translate("Environment variables", "MAXARRAY")) + + if self.arrayType == QadARRAYCommandClassSeriesTypeEnum.PATH: + if self.pathMethod == QadARRAYCommandClassPathMethodTypeEnum.DIVIDE: + if type(value) == int or type(value) == long: # se é stato inserito il numero di elementi + if value * self.pathRows > maxArray: + errMsg = QadMsg.translate("Command_ARRAY", "\nThe array size can't be greater than {0} elements. See MAXARRAY system variable.") + self.showErr(errMsg.format(str(maxArray))) + else: + self.pathItemsNumber = value + self.setDistancesByPathItemNumberOnDivide() + self.waitForMainOptions() + elif self.pathMethod == QadARRAYCommandClassPathMethodTypeEnum.MEASURE: + if type(value) == int or type(value) == long: # se é stato inserito il numero di elementi + if value * self.pathRows > maxArray: + errMsg = QadMsg.translate("Command_ARRAY", "\nThe array size can't be greater than {0} elements. See MAXARRAY system variable.") + self.showErr(errMsg.format(str(maxArray))) + elif (value - 1) * self.distanceBetweenCols > self.pathPolyline.length(): + errMsg = QadMsg.translate("Command_ARRAY", "\nMaximun number of items = {0}.") + self.showErr(errMsg.format(str(int(self.pathPolyline.length() / self.distanceBetweenCols) + 1))) + else: + self.pathItemsNumber = value + self.distanceFromStartPt = 0.0 + self.waitForMainOptions() + elif type(value) == unicode: + if value == QadMsg.translate("Command_ARRAY", "Fill entire path") or value == "Fill entire path": + self.setItemNumberByDistanceBetweenColsOnMeasure() + self.waitForMainOptions() + elif self.arrayType == QadARRAYCommandClassSeriesTypeEnum.POLAR: + if msg * self.polarRows > maxArray: + errMsg = QadMsg.translate("Command_ARRAY", "\nThe array size can't be greater than {0} elements. See MAXARRAY system variable.") + self.showErr(errMsg.format(str(maxArray))) + else: + fillAngle = self.polarItemsNumber * self.polarAngleBetween + self.polarItemsNumber = value + self.polarAngleBetween = 2 * math.pi / value + self.plugIn.setLastPolarItemsNumber_array(self.polarItemsNumber) + self.plugIn.setLastPolarAngleBetween_array(self.polarAngleBetween) + self.waitForMainOptions() + + return False # fine comando + + + # ============================================================================ + # SERIE TRAIETTORIA - INIZIO + # ============================================================================ + + + # ========================================================================= + # RISPOSTA ALLA SELEZIONE DI UN'ENTITA' DA USARE COME PERCORSO DELLA SERIE (da step = ASK_FOR_ARRAYTYPE) + elif self.step == QadARRAYCommandClassStepEnum.ASK_FOR_PATH_OBJ: + if self.entSelClass.run(msgMapTool, msg) == True: + if self.entSelClass.entity.isInitialized(): + if self.setPathPolyline(self.entSelClass.entity, self.entSelClass.point) == True: + self.setItemNumberByDistanceBetweenColsOnMeasure() + self.waitForMainOptions() + else: + if self.entSelClass.canceledByUsr == True: # fine comando + return True + self.showMsg(QadMsg.translate("QAD", "No geometries in this position.")) + self.waitForPathObject(msgMapTool, msg) + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL METODO (da step = ASK_FOR_MAIN_OPTIONS) + elif self.step == QadARRAYCommandClassStepEnum.ASK_FOR_PATH_METHOD: # dopo aver atteso una parola chiave si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + value = self.defaultValue + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: + # la parola chiave arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("Command_ARRAY", "Divide") or value == "Divide": + self.pathMethod = QadARRAYCommandClassPathMethodTypeEnum.DIVIDE + self.setDistancesByPathItemNumberOnDivide() + elif value == QadMsg.translate("Command_ARRAY", "Measure") or value == "Measure": + self.pathMethod = QadARRAYCommandClassPathMethodTypeEnum.MEASURE + self.setItemNumberByDistanceBetweenColsOnMeasure() + self.waitForMainOptions() + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DELLA DIREZIONE DELLA TANGENTE (da step = ASK_FOR_MAIN_OPTIONS) + # ========================================================================= + elif self.step == QadARRAYCommandClassStepEnum.ASK_FOR_TAN_DIRECTION: + if self.GetAngleClass.run(msgMapTool, msg) == True: + if self.GetAngleClass.angle is not None: + self.pathTangentDirection = self.GetAngleClass.angle + self.plugIn.setLastPathTangentDirection_array(self.pathTangentDirection) + self.waitForMainOptions() + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL SECONDO PT PER MISURARE LA DISTANZA TRA ELEMENTI (da step = ASK_FOR_MAIN_OPTIONS) + elif self.step == QadARRAYCommandClassStepEnum.ASK_FOR_ITEM_SPACE: + if self.GetDistClass.run(msgMapTool, msg) == True: + if self.GetDistClass.dist is not None: + l = self.pathPolyline.length() + if self.GetDistClass.dist > l: + errMsg = QadMsg.translate("Command_ARRAY", "\nThe distance between items can't be greater than {0}.") + self.showErr(errMsg.format(str(l))) + else: + self.distanceBetweenCols = self.GetDistClass.dist + + del self.GetDistClass + self.GetDistClass = None + self.updatePointMapToolParams() + self.waitForItemsNumber() + return False # fine comando + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DELL'ALLINEAMENTO DEGLI ELEMENTI (da step = ASK_FOR_MAIN_OPTIONS) + elif self.step == QadARRAYCommandClassStepEnum.ASK_FOR_ITEM_ROTATION: # dopo aver atteso una parola chiave si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + value = self.defaultValue + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: + # la parola chiave arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("QAD", "Yes") or value == "Yes": + self.itemsRotation = True + elif value == QadMsg.translate("QAD", "No") or value == "No": + self.itemsRotation = False + self.plugIn.setLastItemsRotation_array(self.itemsRotation) + self.waitForMainOptions() + + return False + + + # ============================================================================ + # SERIE TRAIETTORIA - FINE + # SERIE POLARE - INIZIO + # ============================================================================ + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL CENTRO DELLA SERIE ((da step = ASK_FOR_ARRAYTYPE)) + elif self.step == QadARRAYCommandClassStepEnum.ASK_FOR_CENTER_PT: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == QgsPointXY: # se é stato inserito il punto centrale della serie + self.centerPt.set(value.x(), value.y()) + self.waitForMainOptions() + elif type(value) == unicode: + if value == QadMsg.translate("Command_ARRAY", "Base point") or value == "Base point": + self.updatePointMapToolParams() + self.waitForBasePt(QadARRAYCommandClassStepEnum.ASK_FOR_BASE_PT_BEFORE_MAIN_OPTIONS) + return False # fine comando + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL PUNTO BASE (da step = ASK_FOR_CENTER_PT) + elif self.step == QadARRAYCommandClassStepEnum.ASK_FOR_BASE_PT_BEFORE_MAIN_OPTIONS: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + self.basePt.set(value.x(), value.y()) + self.updatePointMapToolParams() + self.waitForPolarCenterPt() + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DELL'ANGOLO TRA GLI ELEMENTI (da step = ASK_FOR_MAIN_OPTIONS) + # ========================================================================= + elif self.step == QadARRAYCommandClassStepEnum.ASK_FOR_ANGLE_BETWEEN_ITEMS: + if self.GetAngleClass.run(msgMapTool, msg) == True: + if self.GetAngleClass.angle is not None: + if self.GetAngleClass.angle * self.polarItemsNumber > math.pi * 2: + errMsg = QadMsg.translate("Command_ARRAY", "\nThe angle between can't be greater than {0}.") + maxAngleBetween = math.pi * 2 / self.polarItemsNumber + self.showErr(errMsg.format(str(qad_utils.toDegrees(maxAngleBetween)))) + else: + self.polarAngleBetween = self.GetAngleClass.angle + self.plugIn.setLastPolarAngleBetween_array(self.polarAngleBetween) + + self.waitForMainOptions() + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DELL'ANGOLO TRA GLI ELEMENTI (da step = ASK_FOR_MAIN_OPTIONS) + # ========================================================================= + elif self.step == QadARRAYCommandClassStepEnum.ASK_FOR_FULL_ANGLE: + if self.GetAngleClass.run(msgMapTool, msg) == True: + if self.GetAngleClass.angle is not None: + self.polarAngleBetween = self.GetAngleClass.angle / self.polarItemsNumber + self.plugIn.setLastPolarAngleBetween_array(self.polarAngleBetween) + self.waitForMainOptions() + + return False + + +############################################################################### +# Classe che gestisce il comando ARRAYRECT +class QadARRAYRECTCommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadARRAYRECTCommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "ARRAYRECT") + + def getEnglishName(self): + return "ARRAYRECT" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runARRAYRECTCommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/arrayRect.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_ARRAY", "Distributes object copies into any combination of rows and columns.") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + self.SSGetClass = QadSSGetClass(plugIn) + self.SSGetClass.onlyEditableLayers = True + self.arrayCmd = QadARRAYCommandClass(plugIn) + + def __del__(self): + QadCommandClass.__del__(self) + if self.SSGetClass is not None: + del self.SSGetClass + del self.arrayCmd + + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if self.step == QadARRAYCommandClassStepEnum.ASK_FOR_SELSET: # quando si é in fase di selezione entità + return self.SSGetClass.getPointMapTool() + else: + return self.arrayCmd.getPointMapTool() + + + def getCurrentContextualMenu(self): + if self.step == QadARRAYCommandClassStepEnum.ASK_FOR_SELSET: # quando si é in fase di selezione entità + return None # return self.SSGetClass.getCurrentContextualMenu() + else: + return self.arrayCmd.getCurrentContextualMenu() + + + # ============================================================================ + # run + # ============================================================================ + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + # ========================================================================= + # RICHIESTA SELEZIONE OGGETTI + if self.step == QadARRAYCommandClassStepEnum.ASK_FOR_SELSET: # inizio del comando + if self.SSGetClass.run(msgMapTool, msg) == True: + if self.SSGetClass.entitySet.count() == 0: + return True # fine comando + self.arrayCmd.setEntitySet(self.SSGetClass.entitySet) + + del self.SSGetClass + self.SSGetClass = None + + self.step = -1 + self.arrayCmd.step = QadARRAYCommandClassStepEnum.ASK_FOR_ARRAYTYPE + + return self.arrayCmd.run(False, QadMsg.translate("Command_ARRAY", "Rectangular")) + + return False + + else: + return self.arrayCmd.run(msgMapTool, msg) + + +############################################################################### +# Classe che gestisce il comando ARRAYPATH +class QadARRAYPATHCommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadARRAYPATHCommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "ARRAYPATH") + + def getEnglishName(self): + return "ARRAYPATH" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runARRAYPATHCommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/arrayPath.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_ARRAY", "Evenly distributes object copies along a path or a portion of a path.") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + self.SSGetClass = QadSSGetClass(plugIn) + self.SSGetClass.onlyEditableLayers = True + self.arrayCmd = QadARRAYCommandClass(plugIn) + + def __del__(self): + QadCommandClass.__del__(self) + if self.SSGetClass is not None: + del self.SSGetClass + del self.arrayCmd + + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if self.step == QadARRAYCommandClassStepEnum.ASK_FOR_SELSET: # quando si é in fase di selezione entità + return self.SSGetClass.getPointMapTool() + else: + return self.arrayCmd.getPointMapTool() + + + def getCurrentContextualMenu(self): + if self.step == QadARRAYCommandClassStepEnum.ASK_FOR_SELSET: # quando si é in fase di selezione entità + return None # return self.SSGetClass.getCurrentContextualMenu() + else: + return self.arrayCmd.getCurrentContextualMenu() + + + # ============================================================================ + # run + # ============================================================================ + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + # ========================================================================= + # RICHIESTA SELEZIONE OGGETTI + if self.step == QadARRAYCommandClassStepEnum.ASK_FOR_SELSET: # inizio del comando + if self.SSGetClass.run(msgMapTool, msg) == True: + if self.SSGetClass.entitySet.count() == 0: + return True # fine comando + self.arrayCmd.setEntitySet(self.SSGetClass.entitySet) + + del self.SSGetClass + self.SSGetClass = None + + self.step = -1 + self.arrayCmd.step = QadARRAYCommandClassStepEnum.ASK_FOR_ARRAYTYPE + + return self.arrayCmd.run(False, QadMsg.translate("Command_ARRAY", "PAth")) + + return False + + else: + return self.arrayCmd.run(msgMapTool, msg) + + +############################################################################### +# Classe che gestisce il comando ARRAYPOLAR +class QadARRAYPOLARCommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadARRAYPOLARCommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "ARRAYPOLAR") + + def getEnglishName(self): + return "ARRAYPOLAR" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runARRAYPOLARCommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/arrayPolar.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_ARRAY", "Evenly distributes object copies in a circular pattern around a center point.") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + self.SSGetClass = QadSSGetClass(plugIn) + self.SSGetClass.onlyEditableLayers = True + self.arrayCmd = QadARRAYCommandClass(plugIn) + + def __del__(self): + QadCommandClass.__del__(self) + if self.SSGetClass is not None: + del self.SSGetClass + del self.arrayCmd + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if self.step == QadARRAYCommandClassStepEnum.ASK_FOR_SELSET: # quando si é in fase di selezione entità + return self.SSGetClass.getPointMapTool() + else: + return self.arrayCmd.getPointMapTool() + + + def getCurrentContextualMenu(self): + if self.step == QadARRAYCommandClassStepEnum.ASK_FOR_SELSET: # quando si é in fase di selezione entità + return None # return self.SSGetClass.getCurrentContextualMenu() + else: + return self.arrayCmd.getCurrentContextualMenu() + + + # ============================================================================ + # run + # ============================================================================ + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + # ========================================================================= + # RICHIESTA SELEZIONE OGGETTI + if self.step == QadARRAYCommandClassStepEnum.ASK_FOR_SELSET: # inizio del comando + if self.SSGetClass.run(msgMapTool, msg) == True: + if self.SSGetClass.entitySet.count() == 0: + return True # fine comando + self.arrayCmd.setEntitySet(self.SSGetClass.entitySet) + + del self.SSGetClass + self.SSGetClass = None + + self.step = -1 + self.arrayCmd.step = QadARRAYCommandClassStepEnum.ASK_FOR_ARRAYTYPE + + return self.arrayCmd.run(False, QadMsg.translate("Command_ARRAY", "POlar")) + + return False + + else: + return self.arrayCmd.run(msgMapTool, msg) diff --git a/cmd/qad_array_maptool.py b/cmd/qad_array_maptool.py new file mode 100644 index 00000000..7fc2e920 --- /dev/null +++ b/cmd/qad_array_maptool.py @@ -0,0 +1,222 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin ok + + classe per gestire il map tool in ambito del comando array + + ------------------- + begin : 2016-05-31 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from .. import qad_utils +from ..qad_getpoint import QadGetPoint, QadGetPointSelectionModeEnum, QadGetPointDrawModeEnum +from ..qad_highlight import QadHighlight +from ..qad_dim import QadDimStyles, appendDimEntityIfNotExisting +from ..qad_entity import QadCacheEntitySetIterator, QadEntityTypeEnum +from .. import qad_array_fun + + +# =============================================================================== +# Qad_array_maptool_ModeEnum class. +# =============================================================================== +class Qad_array_maptool_ModeEnum(): + # non si richiede niente + NONE = 0 + # si richiede il punto base + ASK_FOR_BASE_PT = 1 + # si richiede il primo punto per la distanza tra colonne + ASK_FOR_COLUMN_SPACE_FIRST_PT = 2 + # si richiede il primo punto per la dimensione della cella + ASK_FOR_1PT_CELL = 3 + # si richiede il psecondo punto per la dimensione della cella + ASK_FOR_2PT_CELL = 4 + # si richiede il primo punto per la distanza tra righe + ASK_FOR_ROW_SPACE_FIRST_PT = 5 + + +# =============================================================================== +# Qad_array_maptool class +# =============================================================================== +class Qad_array_maptool(QadGetPoint): + + def __init__(self, plugIn): + QadGetPoint.__init__(self, plugIn) + + self.cacheEntitySet = None + self.basePt = None + self.arrayType = None + self.distanceBetweenRows = None + self.distanceBetweenCols = None + self.itemsRotation = None + + # serie rettangolare + self.rectangleAngle = None + self.rectangleCols = None + self.rectangleRows = None + self.firstPt = None + + # serie traiettoria + self.pathTangentDirection = None + self.pathRows = None + self.pathItemsNumber = None + self.pathPolyline = None + + # serie polare + self.centerPt = None + self.polarItemsNumber = None + self.polarAngleBetween = None + self.polarRows = None + + self.__highlight = QadHighlight(self.canvas) + + def hidePointMapToolMarkers(self): + QadGetPoint.hidePointMapToolMarkers(self) + self.__highlight.hide() + + def showPointMapToolMarkers(self): + QadGetPoint.showPointMapToolMarkers(self) + self.__highlight.show() + + def clear(self): + QadGetPoint.clear(self) + self.__highlight.reset() + self.mode = None + + + # ============================================================================ + # doRectangleArray + # ============================================================================ + def doRectangleArray(self): + self.__highlight.reset() + + dimElaboratedList = [] # lista delle quotature già elaborate + entityIterator = QadCacheEntitySetIterator(self.cacheEntitySet) + for entity in entityIterator: + qadGeom = entity.getQadGeom().copy() # così inizializzo le info qad + # verifico se l'entità appartiene ad uno stile di quotatura + dimEntity = QadDimStyles.getDimEntity(entity) + if dimEntity is not None: + if appendDimEntityIfNotExisting(dimElaboratedList, dimEntity) == False: # quota già elaborata + continue + entity = dimEntity + + if qad_array_fun.arrayRectangleEntity(self.plugIn, entity, self.basePt, self.rectangleRows, self.rectangleCols, \ + self.distanceBetweenRows, self.distanceBetweenCols, self.rectangleAngle, self.itemsRotation, + False, self.__highlight) == False: + return + + + # ============================================================================ + # doPathArray + # ============================================================================ + def doPathArray(self): + self.__highlight.reset() + + dimElaboratedList = [] # lista delle quotature già elaborate + entityIterator = QadCacheEntitySetIterator(self.cacheEntitySet) + for entity in entityIterator: + qadGeom = entity.getQadGeom().copy() # così inizializzo le info qad + # verifico se l'entità appartiene ad uno stile di quotatura + dimEntity = QadDimStyles.getDimEntity(entity) + if dimEntity is not None: + if appendDimEntityIfNotExisting(dimElaboratedList, dimEntity) == False: # quota già elaborata + continue + entity = dimEntity + + if qad_array_fun.arrayPathEntity(self.plugIn, entity, self.basePt, self.pathRows, self.pathItemsNumber, \ + self.distanceBetweenRows, self.distanceBetweenCols, self.pathTangentDirection, self.itemsRotation, \ + self.pathPolyline, self.distanceFromStartPt, \ + False, self.__highlight) == False: + return + + + # ============================================================================ + # doPolarArray + # ============================================================================ + def doPolarArray(self): + self.__highlight.reset() + + dimElaboratedList = [] # lista delle quotature già elaborate + entityIterator = QadCacheEntitySetIterator(self.cacheEntitySet) + for entity in entityIterator: + qadGeom = entity.getQadGeom().copy() # così inizializzo le info qad + # verifico se l'entità appartiene ad uno stile di quotatura + dimEntity = QadDimStyles.getDimEntity(entity) + if dimEntity is not None: + if appendDimEntityIfNotExisting(dimElaboratedList, dimEntity) == False: # quota già elaborata + continue + entity = dimEntity + + if qad_array_fun.arrayPolarEntity(self.plugIn, entity, self.basePt, self.centerPt, self.polarItemsNumber, \ + self.polarAngleBetween, self.polarRows, self.distanceBetweenRows, self.itemsRotation, \ + False, self.__highlight) == False: + return + + + def canvasMoveEvent(self, event): + QadGetPoint.canvasMoveEvent(self, event) + +# # noto il punto base si richiede il secondo punto +# if self.mode == Qad_array_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_COPY_PT: +# self.setCopiedGeometries(self.tmpPoint) + + + def activate(self): + QadGetPoint.activate(self) + self.__highlight.show() + + def deactivate(self): + try: # necessario perché se si chiude QGIS parte questo evento nonostante non ci sia più l'oggetto maptool ! + QadGetPoint.deactivate(self) + self.__highlight.hide() + except: + pass + + def setMode(self, mode): + self.mode = mode + # non si richiede niente + if self.mode == Qad_array_maptool_ModeEnum.NONE: + self.setSelectionMode(QadGetPointSelectionModeEnum.NONE) + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + # si richiede il punto base + elif self.mode == Qad_array_maptool_ModeEnum.ASK_FOR_BASE_PT: + self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + # si richiede il primo punto per la distanza tra colonne + elif self.mode == Qad_array_maptool_ModeEnum.ASK_FOR_COLUMN_SPACE_FIRST_PT: + self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + # si richiede il primo punto per la dimensione della cella + elif self.mode == Qad_array_maptool_ModeEnum.ASK_FOR_1PT_CELL: + self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + # si richiede il psecondo punto per la dimensione della cella + elif self.mode == Qad_array_maptool_ModeEnum.ASK_FOR_2PT_CELL: + self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_RECTANGLE) + self.setStartPoint(self.firstPt) + # si richiede il primo punto per la distanza tra righe + elif self.mode == Qad_array_maptool_ModeEnum.ASK_FOR_ROW_SPACE_FIRST_PT: + self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + + + # si richiede il secondo punto per la distanza tra colonne +# elif self.mode == Qad_array_maptool_ModeEnum.ASK_FOR_COLUMN_SPACE_SECOND_PT: +# self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) +# self.setStartPoint(self.firstPt) diff --git a/cmd/qad_break_cmd.py b/cmd/qad_break_cmd.py new file mode 100644 index 00000000..ec206959 --- /dev/null +++ b/cmd/qad_break_cmd.py @@ -0,0 +1,305 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + comando SPEZZA per tagliare un oggetto + + ------------------- + begin : 2014-01-09 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from qgis.PyQt.QtGui import QIcon +from qgis.core import QgsWkbTypes, QgsFeature, QgsPointXY + + +from .qad_generic_cmd import QadCommandClass +from ..qad_getpoint import QadGetPointDrawModeEnum +from ..qad_textwindow import QadInputTypeEnum, QadInputModeEnum +from .qad_entsel_cmd import QadEntSelClass +from ..qad_msg import QadMsg +from .. import qad_layer +from .. import qad_utils +from ..qad_break_fun import breakQadGeometry +from ..qad_multi_geom import fromQadGeomToQgsGeom, setQadGeomAt + + +# Classe che gestisce il comando BREAK +class QadBREAKCommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadBREAKCommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "BREAK") + + def getEnglishName(self): + return "BREAK" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runBREAKCommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/break.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_BREAK", "Breaks an object.") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + self.entSelClass = None + self.firstPt = None + self.secondPt = None + + def __del__(self): + QadCommandClass.__del__(self) + if self.entSelClass is not None: + self.entSelClass.entity.deselectOnLayer() + del self.entSelClass + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if self.step == 1: # quando si é in fase di selezione entità + return self.entSelClass.getPointMapTool(drawMode) + else: + return QadCommandClass.getPointMapTool(self, drawMode) + + + def getCurrentContextualMenu(self): + if self.step == 1: # quando si é in fase di selezione entità + return self.entSelClass.getCurrentContextualMenu() + else: + return self.contextualMenu + + + def waitForEntsel(self, msgMapTool, msg): + if self.entSelClass is not None: + del self.entSelClass + self.step = 1 + self.entSelClass = QadEntSelClass(self.plugIn) + self.entSelClass.msg = QadMsg.translate("Command_BREAK", "Select the object to break: ") + # scarto la selezione di punti e poligoni + self.entSelClass.checkPointLayer = False + self.entSelClass.checkLineLayer = True + self.entSelClass.checkPolygonLayer = False + self.entSelClass.checkDimLayers = False + self.entSelClass.onlyEditableLayers = True + + self.entSelClass.run(msgMapTool, msg) + + + # ============================================================================ + # breakFeatures + # ============================================================================ + def breakFeatures(self): + f = self.entSelClass.entity.getFeature() + if f is None: + return + qadGeom = self.entSelClass.entity.getQadGeom() + result = breakQadGeometry(qadGeom, self.firstPt, self.secondPt) + if result is None: return + + layer = self.entSelClass.entity.layer + LineTempLayer = None + self.plugIn.beginEditCommand("Feature broken", layer) + + line1 = result[0] + line2 = result[1] + atGeom = result[2] + atSubGeom = result[3] + if layer.geometryType() == QgsWkbTypes.LineGeometry: + if line1 is not None: + updGeom = setQadGeomAt(qadGeom, line1, atGeom, atSubGeom) + if updGeom is None: + self.plugIn.destroyEditCommand() + return + brokenFeature1 = QgsFeature(f) + # trasformo la geometria nel crs del layer + brokenFeature1.setGeometry(fromQadGeomToQgsGeom(updGeom, layer)) + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, layer, brokenFeature1, False, False) == False: + self.plugIn.destroyEditCommand() + return + if line2 is not None: + brokenFeature2 = QgsFeature(f) + # trasformo la geometria nel crs del layer + brokenFeature2.setGeometry(fromQadGeomToQgsGeom(line2, layer)) + # plugIn, layer, feature, coordTransform, refresh, check_validity + if qad_layer.addFeatureToLayer(self.plugIn, layer, brokenFeature2, None, False, False, False) == False: + self.plugIn.destroyEditCommand() + return + else: + # aggiungo le linee nei layer temporanei di QAD + if LineTempLayer is None: + LineTempLayer = qad_layer.createQADTempLayer(self.plugIn, QgsWkbTypes.LineGeometry) + self.plugIn.addLayerToLastEditCommand("Feature broken", LineTempLayer) + + lineGeoms = [] + if line1 is not None: + lineGeoms.append(fromQadGeomToQgsGeom(line1, layer)) + if line2 is not None: + lineGeoms.append(fromQadGeomToQgsGeom(line2, layer)) + + # trasformo la geometria in quella dei layer temporanei + # plugIn, pointGeoms, lineGeoms, polygonGeoms, coord, refresh + if qad_layer.addGeometriesToQADTempLayers(self.plugIn, None, lineGeoms, None, None, False) == False: + self.plugIn.destroyEditCommand() + return + + updGeom = delQadGeomAt(qadGeom, atGeom, atSubGeom) + + if updGeom == False: # da cancellare + # plugIn, layer, feature id, refresh + if qad_layer.deleteFeatureToLayer(self.plugIn, layer, f.id(), False) == False: + self.plugIn.destroyEditCommand() + return + else: + brokenFeature1 = QgsFeature(f) + # trasformo la geometria nel crs del layer + brokenFeature1.setGeometry(fromQadGeomToQgsGeom(updGeom, layer)) + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, layer, brokenFeature1, False, False) == False: + self.plugIn.destroyEditCommand() + return + + self.plugIn.endEditCommand() + + + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + if self.step == 0: + self.waitForEntsel(msgMapTool, msg) + return False # continua + + # ========================================================================= + # RISPOSTA ALLA SELEZIONE DI UN'ENTITA' (da step = 0) + elif self.step == 1: + if self.entSelClass.run(msgMapTool, msg) == True: + if self.entSelClass.entity.isInitialized(): + layer = self.entSelClass.entity.layer + self.firstPt = self.entSelClass.point + self.plugIn.setLastPoint(self.firstPt) + + keyWords = QadMsg.translate("Command_BREAK", "First point") + prompt = QadMsg.translate("Command_BREAK", "Specify second break point or [{0}]: ").format(keyWords) + + self.step = 2 + self.getPointMapTool().refreshSnapType() # aggiorno lo snapType che può essere variato dal maptool di selezione entità + + englishKeyWords = "First point" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, QadInputModeEnum.NONE) + return False + else: + if self.entSelClass.canceledByUsr == True: # fine comando + return True + self.showMsg(QadMsg.translate("QAD", "No geometries in this position.")) + self.waitForEntsel(msgMapTool, msg) + return False # continua + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL SECONDO PUNTO DI INTERRUZIONE (da step = 1) + elif self.step == 2: # dopo aver atteso un punto o una parola chiave si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + pass # opzione di default "secondo punto" + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if value is None or type(value) == unicode: + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_BREAK", "Specify first break point: ")) + self.step = 3 + elif type(value) == QgsPointXY: # se é stato inserito il secondo punto + self.secondPt = value + self.plugIn.setLastPoint(self.secondPt) + self.breakFeatures() + return True + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL PRIMO PUNTO DI INTERRUZIONE (da step = 2) + elif self.step == 3: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + self.firstPt = value + self.plugIn.setLastPoint(self.firstPt) + + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_BREAK", "Specify second break point: ")) + self.step = 4 + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL SECONDO PUNTO DI INTERRUZIONE (da step = 3) + elif self.step == 4: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + self.secondPt = value + self.plugIn.setLastPoint(self.secondPt) + self.breakFeatures() + + return True diff --git a/qad_circle_cmd.py b/cmd/qad_circle_cmd.py similarity index 75% rename from qad_circle_cmd.py rename to cmd/qad_circle_cmd.py index dbc07a03..910bf896 100644 --- a/qad_circle_cmd.py +++ b/cmd/qad_circle_cmd.py @@ -1,840 +1,849 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - comando CIRCLE per disegnare un cerchio - - ------------------- - begin : 2013-05-22 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -from qad_getpoint import * -from qad_circle_maptool import * -from qad_generic_cmd import QadCommandClass -from qad_msg import QadMsg -from qad_textwindow import * -from qad_entity import * -import qad_utils -import qad_layer - - -# Classe che gestisce il comando CIRCLE -class QadCIRCLECommandClass(QadCommandClass): - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadCIRCLECommandClass(self.plugIn) - - def getName(self): - return QadMsg.translate("Command_list", "CIRCLE") - - def getEnglishName(self): - return "CIRCLE" - - def connectQAction(self, action): - QObject.connect(action, SIGNAL("triggered()"), self.plugIn.runCIRCLECommand) - - def getIcon(self): - return QIcon(":/plugins/qad/icons/circle.png") - - def getNote(self): - # impostare le note esplicative del comando - return QadMsg.translate("Command_CIRCLE", "Draws a circlerc by many methods.") - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - # se questo flag = True il comando serve all'interno di un altro comando per disegnare un cerchio - # che non verrà salvato su un layer - self.virtualCmd = False - self.rubberBandBorderColor = None - self.rubberBandFillColor = None - self.centerPt = None - self.radius = None - self.area = 100 - - def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): - if (self.plugIn is not None): - if self.PointMapTool is None: - self.PointMapTool = Qad_circle_maptool(self.plugIn) - self.PointMapTool.setRubberBandColor(self.rubberBandBorderColor, self.rubberBandFillColor) - return self.PointMapTool - else: - return None - - def setRubberBandColor(self, rubberBandBorderColor, rubberBandFillColor): - self.rubberBandBorderColor = rubberBandBorderColor - self.rubberBandFillColor = rubberBandFillColor - if self.PointMapTool is not None: - self.PointMapTool.setRubberBandColor(self.rubberBandBorderColor, self.rubberBandFillColor) - - def run(self, msgMapTool = False, msg = None): - self.isValidPreviousInput = True # per gestire il comando anche in macro - - if self.plugIn.canvas.mapRenderer().destinationCrs().geographicFlag(): - self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) - return True # fine comando - - currLayer = None - if self.virtualCmd == False: # se si vuole veramente salvare il cerchio in un layer - # il layer corrente deve essere editabile e di tipo linea o poligono - currLayer, errMsg = qad_layer.getCurrLayerEditable(self.plugIn.canvas, [QGis.Line, QGis.Polygon]) - if currLayer is None: - self.showErr(errMsg) - return True # fine comando - self.getPointMapTool().geomType = QGis.Line if currLayer.geometryType() == QGis.Line else QGis.Polygon - - #========================================================================= - # RICHIESTA PRIMO PUNTO o CENTRO - if self.step == 0: # inizio del comando - # imposto il map tool - self.getPointMapTool().setMode(Qad_circle_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_CENTER_PT) - keyWords = QadMsg.translate("Command_CIRCLE", "3Points") + "/" + \ - QadMsg.translate("Command_CIRCLE", "2POints") + "/" + \ - QadMsg.translate("Command_CIRCLE", "Ttr (tangent tangent radius)") - prompt = QadMsg.translate("Command_CIRCLE", "Specify the center point of the circle or [{0}]: ").format(keyWords) - - englishKeyWords = "3Points" + "/" + "2POints" + "/" + "Ttr (tangent tangent radius)" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o enter o una parola chiave - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - None, \ - keyWords, QadInputModeEnum.NONE) - - self.step = 1 - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA CENTRO - elif self.step == 1: # dopo aver atteso un punto o enter o una parola chiave si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi é stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if value is None: - if self.plugIn.lastPoint is not None: - value = self.plugIn.lastPoint - else: - return True # fine comando - - if type(value) == unicode: - if value == QadMsg.translate("Command_CIRCLE", "3Points") or value == "3Points": - # imposto il map tool - self.getPointMapTool().setMode(Qad_circle_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_FIRST_PT) - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_CIRCLE", "Specify first point on the circle: ")) - self.step = 4 - elif value == QadMsg.translate("Command_CIRCLE", "2POints") or value == "2POints": - # imposto il map tool - self.getPointMapTool().setMode(Qad_circle_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_FIRST_DIAM_PT) - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_CIRCLE", "Specify first end of the circle diameter: ")) - self.step = 7 - elif value == QadMsg.translate("Command_CIRCLE", "Ttr (tangent tangent radius)") or \ - value == "Ttr (tangent tangent radius)": - # imposto il map tool - self.getPointMapTool().setMode(Qad_circle_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_FIRST_TAN) - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_CIRCLE", "Specify first tangent element of the circle: ")) - self.step = 9 - elif type(value) == QgsPoint: # se é stato inserito il centro del cerchio - self.centerPt = value - self.plugIn.setLastPoint(value) - - # imposto il map tool - self.getPointMapTool().centerPt = self.centerPt - self.getPointMapTool().setMode(Qad_circle_maptool_ModeEnum.CENTER_PT_KNOWN_ASK_FOR_RADIUS) - - keyWords = QadMsg.translate("Command_CIRCLE", "Diameter") + "/" + \ - QadMsg.translate("Command_CIRCLE", "Area") - prompt = QadMsg.translate("Command_CIRCLE", "Specify the circle radius or [{0}]: ").format(keyWords) - - englishKeyWords = "Diameter" + "/" + "Area" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o una parola chiave - # msg, inputType, default, keyWords, valori positivi - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT | QadInputTypeEnum.KEYWORDS, \ - None, \ - keyWords, \ - QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) - - self.step = 2 - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA RAGGIO O DIAMETRO O AREA - elif self.step == 2: # dopo aver atteso un punto o una parola chiave si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("Command_CIRCLE", "Diameter") or value == "Diameter": - # imposto il map tool - self.getPointMapTool().setMode(Qad_circle_maptool_ModeEnum.CENTER_PT_KNOWN_ASK_FOR_DIAM) - # si appresta ad attendere un punto o un numero reale - # msg, inputType, default, keyWords, valori positivi - self.waitFor(QadMsg.translate("Command_CIRCLE", "Specify the circle diameter: "), \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT, \ - None, \ - "", \ - QadInputModeEnum.NOT_NULL | QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) - self.step = 3 - elif value == QadMsg.translate("Command_CIRCLE", "Area") or value == "Area": - msg = QadMsg.translate("Command_CIRCLE", "Enter circle area in current unit <{0}>: ") - # si appresta ad attendere un numero reale - # msg, inputType, default, keyWords, valori positivi - self.waitFor(msg.format(str(self.area)), QadInputTypeEnum.FLOAT, \ - self.area, "", \ - QadInputModeEnum.NOT_NULL | QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) - self.getPointMapTool().setMode(Qad_circle_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_CENTER_PT) - self.step = 13 - elif type(value) == QgsPoint or type(value) == float: # se é stato inserito il raggio del cerchio - if type(value) == QgsPoint: # se é stato inserito il raggio del cerchio con un punto - self.radius = qad_utils.getDistance(self.centerPt, value) - else: - self.radius = value - - self.plugIn.setLastRadius(self.radius) - - circle = QadCircle() - circle.set(self.centerPt, self.radius) - points = circle.asPolyline() - if points is not None: - if self.virtualCmd == False: # se si vuole veramente salvare il cerchio in un layer - if currLayer.geometryType() == QGis.Line: - qad_layer.addLineToLayer(self.plugIn, currLayer, points) - else: - qad_layer.addPolygonToLayer(self.plugIn, currLayer, points) - return True # fine comando - - keyWords = QadMsg.translate("Command_CIRCLE", "Diameter") + "/" + \ - QadMsg.translate("Command_CIRCLE", "Area") - prompt = QadMsg.translate("Command_CIRCLE", "Specify the circle radius or [{0}]: ").format(keyWords) - - englishKeyWords = "Diameter" + "/" + "Area" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o una parola chiave - # msg, inputType, default, keyWords, valori positivi - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - None, \ - keyWords, QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) - self.isValidPreviousInput = False # per gestire il comando anche in macro - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DIAMETRO DEL CERCHIO (da step = 2) - elif self.step == 3: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == QgsPoint: # se é stato inserito un punto - self.radius = qad_utils.getDistance(self.centerPt, value) / 2 - elif type(value) == float: # se é stato inserito un numero reale - self.radius = value - - self.plugIn.setLastRadius(self.radius) - - circle = QadCircle() - circle.set(self.centerPt, self.radius) - points = circle.asPolyline() - if points is not None: - if self.virtualCmd == False: # se si vuole veramente salvare il cerchio in un layer - if currLayer.geometryType() == QGis.Line: - qad_layer.addLineToLayer(self.plugIn, currLayer, points) - else: - qad_layer.addPolygonToLayer(self.plugIn, currLayer, points) - return True # fine comando - - # si appresta ad attendere un punto o un numero reale - # msg, inputType, default, keyWords, valori positivi - self.waitFor(QadMsg.translate("Command_CIRCLE", "Specify the circle diameter: "), \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT, \ - None, \ - "", \ - QadInputModeEnum.NOT_NULL | QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) - self.isValidPreviousInput = False # per gestire il comando anche in macro - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DEL PRIMO PUNTO DEL CERCHIO (da step = 1) - elif self.step == 4: # dopo aver atteso un punto si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - snapTypeOnSel = self.getPointMapTool().snapTypeOnSelection - value = self.getPointMapTool().point - entity = self.getPointMapTool().entity - else: # il punto arriva come parametro della funzione - value = msg - snapTypeOnSel = QadSnapTypeEnum.NONE - - # se é stato selezionato un punto con la modalità TAN_DEF é un punto differito - if snapTypeOnSel == QadSnapTypeEnum.TAN_DEF and entity.isInitialized(): - self.firstPt = None - self.firstPtTan = value - self.firstGeomTan = QgsGeometry(entity.getGeometry()) # duplico la geometria - coordTransform = QgsCoordinateTransform(entity.layer.crs(), self.plugIn.canvas.mapRenderer().destinationCrs()) # trasformo la geometria - self.firstGeomTan.transform(coordTransform) - # imposto il map tool - self.getPointMapTool().setMode(Qad_circle_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT) - else: # altrimenti é un punto esplicito - self.firstPt = value - self.plugIn.setLastPoint(value) - # imposto il map tool - self.getPointMapTool().firstPt = self.firstPt - self.getPointMapTool().setMode(Qad_circle_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT) - - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_CIRCLE", "Specify second point on the circle: ")) - - self.step = 5 - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DEL SECONDO PUNTO DEL CERCHIO (da step = 4) - elif self.step == 5: # dopo aver atteso un punto si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - snapTypeOnSel = self.getPointMapTool().snapTypeOnSelection - value = self.getPointMapTool().point - entity = self.getPointMapTool().entity - else: # il punto arriva come parametro della funzione - value = msg - snapTypeOnSel = QadSnapTypeEnum.NONE - - # se é stato selezionato un punto con la modalità TAN_DEF é un punto differito - if snapTypeOnSel == QadSnapTypeEnum.TAN_DEF and entity.isInitialized(): - self.secondPt = None - self.secondPtTan = value - self.secondGeomTan = QgsGeometry(entity.getGeometry()) # duplico la geometria - coordTransform = QgsCoordinateTransform(entity.layer.crs(), self.plugIn.canvas.mapRenderer().destinationCrs()) # trasformo la geometria - self.secondGeomTan.transform(coordTransform) - # imposto il map tool - self.getPointMapTool().setMode(Qad_circle_maptool_ModeEnum.FIRST_SECOND_PT_KNOWN_ASK_FOR_THIRD_PT) - else: # altrimenti é un punto esplicito - self.secondPt = value - self.plugIn.setLastPoint(value) - # imposto il map tool - self.getPointMapTool().secondPt = self.secondPt - self.getPointMapTool().setMode(Qad_circle_maptool_ModeEnum.FIRST_SECOND_PT_KNOWN_ASK_FOR_THIRD_PT) - - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_CIRCLE", "Specify the third point on the circle: ")) - - self.step = 6 - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DEL TERZO PUNTO DEL CERCHIO (da step = 5) - elif self.step == 6: # dopo aver atteso un punto si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - snapTypeOnSel = self.getPointMapTool().snapTypeOnSelection - value = self.getPointMapTool().point - entity = self.getPointMapTool().entity - else: # il punto arriva come parametro della funzione - value = msg - snapTypeOnSel = QadSnapTypeEnum.NONE - - # se é stato selezionato un punto con la modalità TAN_DEF é un punto differito - if snapTypeOnSel == QadSnapTypeEnum.TAN_DEF and entity.isInitialized(): - self.thirdPt = None - self.thirdPtTan = value - self.thirdGeomTan = QgsGeometry(entity.getGeometry()) # duplico la geometria - coordTransform = QgsCoordinateTransform(entity.layer.crs(), self.plugIn.canvas.mapRenderer().destinationCrs()) # trasformo la geometria - self.thirdGeomTan.transform(coordTransform) - else: # altrimenti é un punto esplicito - self.thirdPt = value - self.plugIn.setLastPoint(value) - - circle = QadCircle() - points = None - if self.firstPt is None: # se il primo punto é definito con un punto differito - if self.secondPt is None: # se il secondo punto é definito con un punto differito - if self.thirdPt is None: # se il terzo punto é definito con un punto differito - if circle.from3TanPts(self.firstGeomTan, self.firstPtTan, \ - self.secondGeomTan, self.secondPtTan, \ - self.thirdGeomTan, self.thirdPtTan) == True: - points = circle.asPolyline() - else: # se il terzo punto é definito con un punto esplicito - if circle.from1IntPt2TanPts(self.thirdPt, self.firstGeomTan, self.firstPtTan, - self.secondGeomTan, self.secondPtTan) == True: - points = circle.asPolyline() - else: # se il secondo punto é definito con un punto esplicito - if self.thirdPt is None: # se il terzo punto é definito con un punto differito - if circle.from1IntPt2TanPts(self.secondPt, self.firstGeomTan, self.firstPtTan, - self.thirdGeomTan, self.thirdPtTan) == True: - points = circle.asPolyline() - else: # se il terzo punto é definito con un punto esplicito - if circle.from2IntPts1TanPt(self.secondPt, self.thirdPt, \ - self.firstGeomTan, self.firstPtTan) == True: - points = circle.asPolyline() - else: # se il primo punto é definito con un punto esplicito - if self.secondPt is None: # se il secondo punto é definito con un punto differito - if self.thirdPt is None: # se il terzo punto é definito con un punto differito - if circle.from1IntPt2TanPts(self.firstPt, self.secondGeomTan, self.secondPtTan, - self.thirdGeomTan, self.thirdPtTan) == True: - points = circle.asPolyline() - else: # se il terzo punto é definito con un punto esplicito - if circle.from2IntPts1TanPt(self.firstPt, self.thirdPt, \ - self.secondGeomTan, self.secondPtTan) == True: - points = circle.asPolyline() - else: # se il secondo punto é definito con un punto esplicito - if self.thirdPt is None: # se il terzo punto é definito con un punto differito - if circle.from2IntPts1TanPt(self.firstPt, self.secondPt, \ - self.thirdGeomTan, self.thirdPtTan) == True: - points = circle.asPolyline() - else: # se il terzo punto é definito con un punto esplicito - if circle.from3Pts(self.firstPt, self.secondPt, value) == True: - points = circle.asPolyline() - - if points is not None: - self.centerPt = circle.center - self.radius = circle.radius - if self.virtualCmd == False: # se si vuole veramente salvare il cerchio in un layer - if currLayer.geometryType() == QGis.Line: - qad_layer.addLineToLayer(self.plugIn, currLayer, points) - else: - qad_layer.addPolygonToLayer(self.plugIn, currLayer, points) - return True # fine comando - - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_CIRCLE", "Specify the third point on the circle: ")) - self.isValidPreviousInput = False # per gestire il comando anche in macro - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DELLA PRIMA ESTREMITA' DIAM DEL CERCHIO (da step = 1) - elif self.step == 7: # dopo aver atteso un punto si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - snapTypeOnSel = self.getPointMapTool().snapTypeOnSelection - value = self.getPointMapTool().point - entity = self.getPointMapTool().entity - else: # il punto arriva come parametro della funzione - value = msg - snapTypeOnSel = QadSnapTypeEnum.NONE - - # se é stato selezionato un punto con la modalità TAN_DEF é un punto differito - if snapTypeOnSel == QadSnapTypeEnum.TAN_DEF and entity.isInitialized(): - self.firstDiamPt = None - self.firstDiamPtTan = value - self.firstDiamGeomTan = QgsGeometry(entity.getGeometry()) # duplico la geometria - coordTransform = QgsCoordinateTransform(entity.layer.crs(), self.plugIn.canvas.mapRenderer().destinationCrs()) # trasformo la geometria - self.firstDiamGeomTan.transform(coordTransform) - # imposto il map tool - self.getPointMapTool().setMode(Qad_circle_maptool_ModeEnum.FIRST_DIAM_PT_KNOWN_ASK_FOR_SECOND_DIAM_PT) - else: # altrimenti é un punto esplicito - self.firstDiamPt = value - # imposto il map tool - self.getPointMapTool().firstDiamPt = self.firstDiamPt - self.getPointMapTool().setMode(Qad_circle_maptool_ModeEnum.FIRST_DIAM_PT_KNOWN_ASK_FOR_SECOND_DIAM_PT) - - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_CIRCLE", "Specify second end of the circle diameter: ")) - - self.step = 8 - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DELLA SECONDA ESTREMITA' DIAM DEL CERCHIO (da step = 7) - elif self.step == 8: # dopo aver atteso un punto si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - snapTypeOnSel = self.getPointMapTool().snapTypeOnSelection - value = self.getPointMapTool().point - entity = self.getPointMapTool().entity - else: # il punto arriva come parametro della funzione - value = msg - snapTypeOnSel = QadSnapTypeEnum.NONE - - # se é stato selezionato un punto con la modalità TAN_DEF é un punto differito - if snapTypeOnSel == QadSnapTypeEnum.TAN_DEF and entity.isInitialized(): - self.secondDiamPt = None - self.secondDiamPtTan = value - self.secondDiamGeomTan = QgsGeometry(entity.getGeometry()) # duplico la geometria - coordTransform = QgsCoordinateTransform(entity.layer.crs(), self.plugIn.canvas.mapRenderer().destinationCrs()) # trasformo la geometria - self.secondDiamGeomTan.transform(coordTransform) - else: # altrimenti é un punto esplicito - self.secondDiamPt = value - - circle = QadCircle() - points = None - if self.firstDiamPt is None: # se il diametro é definito con il primo punto differito - if self.secondDiamPt is None: # il diametro é definito con il secondo punto differito - if circle.fromDiamEnds2TanPts(self.firstDiamGeomTan, self.firstDiamPtTan, \ - self.secondDiamGeomTan, self.secondDiamPtTan) == True: - points = circle.asPolyline() - else: # se il diametro è definito con il secondo punto esplicito - if circle.fromDiamEndsPtTanPt(self.secondDiamPt, self.firstDiamGeomTan, self.firstDiamPtTan) == True: - points = circle.asPolyline() - else: # se il diametro è definito con il primo punto esplicito - if self.secondDiamPt is None: # il diametro è definito con il secondo punto differito - if circle.fromDiamEndsPtTanPt(self.firstDiamPt, self.secondDiamGeomTan, self.secondDiamPtTan) == True: - points = circle.asPolyline() - else: # se il diametro è definito con il secondo punto esplicito - if circle.fromDiamEnds(self.firstDiamPt, self.secondDiamPt) == True: - points = circle.asPolyline() - - if points is not None: - self.centerPt = circle.center - self.radius = circle.radius - if self.virtualCmd == False: # se si vuole veramente salvare il cerchio in un layer - if currLayer.geometryType() == QGis.Line: - qad_layer.addLineToLayer(self.plugIn, currLayer, points) - else: - qad_layer.addPolygonToLayer(self.plugIn, currLayer, points) - return True # fine comand - - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_CIRCLE", "Specify second end of the circle diameter: ")) - self.isValidPreviousInput = False # per gestire il comando anche in macro - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA PRIMA TANGENTE (da step = 1) - elif self.step == 9: # dopo aver atteso un punto si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - entity = self.getPointMapTool().entity - else: # il punto arriva come parametro della funzione - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_CIRCLE", "Specify first tangent element of the circle: ")) - self.isValidPreviousInput = False # per gestire il comando anche in macro - return False - - if not entity.isInitialized(): # se non è stata selezionata una entità - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_CIRCLE", "Specify first tangent element of the circle: ")) - self.isValidPreviousInput = False # per gestire il comando anche in macro - return False - - wkbType = entity.getGeometry().wkbType() - if wkbType == QGis.WKBPoint or wkbType == QGis.WKBMultiPoint: - self.showErr(QadMsg.translate("Command_CIRCLE", "\nSelect a circle, an arc or a line.")) - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_CIRCLE", "Specify first tangent element of the circle: ")) - self.isValidPreviousInput = False # per gestire il comando anche in macro - return False - - self.tanGeom1 = QgsGeometry(entity.getGeometry()) - coordTransform = QgsCoordinateTransform(entity.layer.crs(), self.plugIn.canvas.mapRenderer().destinationCrs()) # trasformo la geometria - self.tanGeom1.transform(coordTransform) - self.tanPt1 = self.getPointMapTool().point - - # imposto il map tool - self.getPointMapTool().tanGeom1 = self.tanGeom1 - self.getPointMapTool().tanPt1 = self.tanPt1 - self.getPointMapTool().setMode(Qad_circle_maptool_ModeEnum.FIRST_TAN_KNOWN_ASK_FOR_SECOND_TAN) - - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_CIRCLE", "Specify second tangent element of the circle: ")) - self.step = 10 - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA SECONDA TANGENTE (da step = 9) - elif self.step == 10: # dopo aver atteso un punto si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - entity = self.getPointMapTool().entity - else: # il punto arriva come parametro della funzione - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_CIRCLE", "Specify second tangent element of the circle: ")) - self.isValidPreviousInput = False # per gestire il comando anche in macro - return False - - if not entity.isInitialized(): # se non è stata selezionata una entità - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_CIRCLE", "Specify second tangent element of the circle: ")) - self.isValidPreviousInput = False # per gestire il comando anche in macro - return False - - wkbType = entity.getGeometry().wkbType() - if wkbType == QGis.WKBPoint or wkbType == QGis.WKBMultiPoint: - self.showErr(QadMsg.translate("Command_CIRCLE", "\nSelect a circle, an arc or a line.")) - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_CIRCLE", "Specify second tangent element of the circle: ")) - self.isValidPreviousInput = False # per gestire il comando anche in macro - return False - - self.tanGeom2 = QgsGeometry(entity.getGeometry()) - coordTransform = QgsCoordinateTransform(entity.layer.crs(), self.plugIn.canvas.mapRenderer().destinationCrs()) # trasformo la geometria - self.tanGeom2.transform(coordTransform) - self.tanPt2 = self.getPointMapTool().point - - # imposto il map tool - self.getPointMapTool().tanGeom2 = self.tanGeom2 - self.getPointMapTool().tanPt2 = self.tanPt2 - self.getPointMapTool().setMode(Qad_circle_maptool_ModeEnum.FIRST_SECOND_TAN_KNOWN_ASK_FOR_RADIUS) - - # si appresta ad attendere un punto o un numero reale - # msg, inputType, default, keyWords, valori positivi - msg = QadMsg.translate("Command_CIRCLE", "Specify the circle radius <{0}>: ") - self.waitFor(msg.format(str(self.plugIn.lastRadius)), \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT, \ - self.plugIn.lastRadius, "", \ - QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) - self.step = 11 - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA RAGGIO (da step = 10) - elif self.step == 11: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == QgsPoint: - self.startPtForRadius = value - - # imposto il map tool - self.getPointMapTool().startPtForRadius = self.startPtForRadius - self.getPointMapTool().setMode(Qad_circle_maptool_ModeEnum.FIRST_SECOND_TAN_FIRSTPTRADIUS_KNOWN_ASK_FOR_SECONDPTRADIUS) - - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_CIRCLE", "Specify second point: ")) - self.step = 12 - return False - else: - self.plugIn.setLastRadius(value) - - circle = QadCircle() - if circle.from2TanPtsRadius(self.tanGeom1, self.tanPt1, \ - self.tanGeom2, self.tanPt2, value) == True: - points = circle.asPolyline() - if points is not None: - self.centerPt = circle.center - self.radius = circle.radius - if self.virtualCmd == False: # se si vuole veramente salvare il cerchio in un layer - if currLayer.geometryType() == QGis.Line: - qad_layer.addLineToLayer(self.plugIn, currLayer, points) - else: - qad_layer.addPolygonToLayer(self.plugIn, currLayer, points) - else: - self.showMsg(QadMsg.translate("Command_CIRCLE", "\nThe circle doesn't exist.")) - else: - self.showMsg(QadMsg.translate("Command_CIRCLE", "\nThe circle doesn't exist.")) - - return True # fine comando - - #========================================================================= - # RISPOSTA ALLA RICHIESTA SECONDO PUNTO DEL RAGGIO (da step = 11) - elif self.step == 12: # dopo aver atteso un punto si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - self.radius = qad_utils.getDistance(self.startPtForRadius, value) - self.plugIn.setLastRadius(self.radius) - - circle = QadCircle() - if circle.from2TanPtsRadius(self.tanGeom1, self.tanPt1, \ - self.tanGeom2, self.tanPt2, self.radius) == True: - points = circle.asPolyline() - if points is not None: - self.centerPt = circle.center - self.radius = circle.radius - if self.virtualCmd == False: # se si vuole veramente salvare il cerchio in un layer - if currLayer.geometryType() == QGis.Line: - qad_layer.addLineToLayer(self.plugIn, currLayer, points) - else: - qad_layer.addPolygonToLayer(self.plugIn, currLayer, points) - else: - self.showMsg(QadMsg.translate("Command_CIRCLE", "\nThe circle doesn't exist.")) - else: - self.showMsg(QadMsg.translate("Command_CIRCLE", "\nThe circle doesn't exist.")) - return True # fine comando - - #========================================================================= - # RISPOSTA ALLA RICHIESTA AREA DEL CERCHIO (da step = 2) - elif self.step == 13: # dopo aver atteso un numero si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton != True: # se NON usato il tasto destro del mouse - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == float: # è stata inserita l'area - self.area = value - - circle = QadCircle() - circle.fromCenterArea(self.centerPt, self.area) - self.radius = circle.radius - self.plugIn.setLastRadius(self.radius) - points = circle.asPolyline() - if points is not None: - if self.virtualCmd == False: # se si vuole veramente salvare il cerchio in un layer - if currLayer.geometryType() == QGis.Line: - qad_layer.addLineToLayer(self.plugIn, currLayer, points) - else: - qad_layer.addPolygonToLayer(self.plugIn, currLayer, points) - return True # fine comando - - self.isValidPreviousInput = False # per gestire il comando anche in macro - return False +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + comando CIRCLE per disegnare un cerchio + + ------------------- + begin : 2013-05-22 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.core import QgsWkbTypes, QgsPointXY, QgsGeometry +from qgis.PyQt.QtGui import QIcon + + +from .. import qad_layer +from .. import qad_utils +from .qad_circle_maptool import Qad_circle_maptool, Qad_circle_maptool_ModeEnum +from .qad_generic_cmd import QadCommandClass +from ..qad_msg import QadMsg +from ..qad_textwindow import QadInputModeEnum, QadInputTypeEnum +from ..qad_geom_relations import * +from ..qad_multi_geom import * +from ..qad_circle_fun import * +from ..qad_getpoint import QadGetPointDrawModeEnum +from ..qad_snapper import QadSnapTypeEnum + + +# Classe che gestisce il comando CIRCLE +class QadCIRCLECommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadCIRCLECommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "CIRCLE") + + def getEnglishName(self): + return "CIRCLE" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runCIRCLECommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/circle.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_CIRCLE", "Draws a circle by many methods.") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + # se questo flag = True il comando serve all'interno di un altro comando per disegnare un cerchio + # che non verrà salvato su un layer + self.virtualCmd = False + self.rubberBandBorderColor = None + self.rubberBandFillColor = None + self.centerPt = None + self.radius = None + self.area = 100 + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if (self.plugIn is not None): + if self.PointMapTool is None: + self.PointMapTool = Qad_circle_maptool(self.plugIn) + self.PointMapTool.setRubberBandColor(self.rubberBandBorderColor, self.rubberBandFillColor) + return self.PointMapTool + else: + return None + + def setRubberBandColor(self, rubberBandBorderColor, rubberBandFillColor): + self.rubberBandBorderColor = rubberBandBorderColor + self.rubberBandFillColor = rubberBandFillColor + if self.PointMapTool is not None: + self.PointMapTool.setRubberBandColor(self.rubberBandBorderColor, self.rubberBandFillColor) + + def run(self, msgMapTool = False, msg = None): + self.isValidPreviousInput = True # per gestire il comando anche in macro + + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + currLayer = None + if self.virtualCmd == False: # se si vuole veramente salvare il cerchio in un layer + # il layer corrente deve essere editabile e di tipo linea o poligono + currLayer, errMsg = qad_layer.getCurrLayerEditable(self.plugIn.canvas, [QgsWkbTypes.LineGeometry, QgsWkbTypes.PolygonGeometry]) + if currLayer is None: + self.showErr(errMsg) + return True # fine comando + self.getPointMapTool().layer = currLayer + + # ========================================================================= + # RICHIESTA PRIMO PUNTO o CENTRO + if self.step == 0: # inizio del comando + # imposto il map tool + self.getPointMapTool().setMode(Qad_circle_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_CENTER_PT) + keyWords = QadMsg.translate("Command_CIRCLE", "3Points") + "/" + \ + QadMsg.translate("Command_CIRCLE", "2POints") + "/" + \ + QadMsg.translate("Command_CIRCLE", "Ttr (tangent tangent radius)") + prompt = QadMsg.translate("Command_CIRCLE", "Specify the center point of the circle or [{0}]: ").format(keyWords) + + englishKeyWords = "3Points" + "/" + "2POints" + "/" + "Ttr (tangent tangent radius)" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, QadInputModeEnum.NONE) + + self.step = 1 + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA CENTRO + elif self.step == 1: # dopo aver atteso un punto o enter o una parola chiave si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi é stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if value is None: + if self.plugIn.lastPoint is not None: + value = self.plugIn.lastPoint + else: + return True # fine comando + + if type(value) == unicode: + if value == QadMsg.translate("Command_CIRCLE", "3Points") or value == "3Points": + # imposto il map tool + self.getPointMapTool().setMode(Qad_circle_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_FIRST_PT) + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_CIRCLE", "Specify first point on the circle: ")) + self.step = 4 + elif value == QadMsg.translate("Command_CIRCLE", "2POints") or value == "2POints": + # imposto il map tool + self.getPointMapTool().setMode(Qad_circle_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_FIRST_DIAM_PT) + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_CIRCLE", "Specify first end of the circle diameter: ")) + self.step = 7 + elif value == QadMsg.translate("Command_CIRCLE", "Ttr (tangent tangent radius)") or \ + value == "Ttr (tangent tangent radius)": + # imposto il map tool + self.getPointMapTool().setMode(Qad_circle_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_FIRST_TAN) + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_CIRCLE", "Specify first tangent element of the circle: ")) + self.step = 9 + elif type(value) == QgsPointXY: # se é stato inserito il centro del cerchio + self.centerPt = value + self.plugIn.setLastPoint(value) + + # imposto il map tool + self.getPointMapTool().centerPt = self.centerPt + self.getPointMapTool().setMode(Qad_circle_maptool_ModeEnum.CENTER_PT_KNOWN_ASK_FOR_RADIUS) + + keyWords = QadMsg.translate("Command_CIRCLE", "Diameter") + "/" + \ + QadMsg.translate("Command_CIRCLE", "Area") + prompt = QadMsg.translate("Command_CIRCLE", "Specify the circle radius or [{0}]: ").format(keyWords) + + englishKeyWords = "Diameter" + "/" + "Area" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o una parola chiave + # msg, inputType, default, keyWords, valori positivi + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT | QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, \ + QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) + + self.step = 2 + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA RAGGIO O DIAMETRO O AREA + elif self.step == 2: # dopo aver atteso un punto o una parola chiave si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("Command_CIRCLE", "Diameter") or value == "Diameter": + # imposto il map tool + self.getPointMapTool().setMode(Qad_circle_maptool_ModeEnum.CENTER_PT_KNOWN_ASK_FOR_DIAM) + # si appresta ad attendere un punto o un numero reale + # msg, inputType, default, keyWords, valori positivi + self.waitFor(QadMsg.translate("Command_CIRCLE", "Specify the circle diameter: "), \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT, \ + None, \ + "", \ + QadInputModeEnum.NOT_NULL | QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) + self.step = 3 + elif value == QadMsg.translate("Command_CIRCLE", "Area") or value == "Area": + msg = QadMsg.translate("Command_CIRCLE", "Enter circle area in current unit <{0}>: ") + # si appresta ad attendere un numero reale + # msg, inputType, default, keyWords, valori positivi + self.waitFor(msg.format(str(self.area)), QadInputTypeEnum.FLOAT, \ + self.area, "", \ + QadInputModeEnum.NOT_NULL | QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) + self.getPointMapTool().setMode(Qad_circle_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_CENTER_PT) + self.step = 13 + elif type(value) == QgsPointXY or type(value) == float: # se é stato inserito il raggio del cerchio + if type(value) == QgsPointXY: # se é stato inserito il raggio del cerchio con un punto + self.radius = qad_utils.getDistance(self.centerPt, value) + else: + self.radius = value + + self.plugIn.setLastRadius(self.radius) + + circle = QadCircle() + if circle.set(self.centerPt, self.radius) is not None: + geom = circle.asGeom(currLayer.wkbType()) + if geom is not None: + if self.virtualCmd == False: # se si vuole veramente salvare il cerchio in un layer + qad_layer.addGeomToLayer(self.plugIn, currLayer, self.mapToLayerCoordinates(currLayer, geom)) + return True # fine comando + + keyWords = QadMsg.translate("Command_CIRCLE", "Diameter") + "/" + \ + QadMsg.translate("Command_CIRCLE", "Area") + prompt = QadMsg.translate("Command_CIRCLE", "Specify the circle radius or [{0}]: ").format(keyWords) + + englishKeyWords = "Diameter" + "/" + "Area" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o una parola chiave + # msg, inputType, default, keyWords, valori positivi + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) + self.isValidPreviousInput = False # per gestire il comando anche in macro + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DIAMETRO DEL CERCHIO (da step = 2) + elif self.step == 3: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == QgsPointXY: # se é stato inserito un punto + self.radius = qad_utils.getDistance(self.centerPt, value) / 2 + elif type(value) == float: # se é stato inserito un numero reale + self.radius = value / 2 + + self.plugIn.setLastRadius(self.radius) + + circle = QadCircle() + if circle.set(self.centerPt, self.radius) is not None: + geom = circle.asGeom(currLayer.wkbType()) + if geom is not None: + if self.virtualCmd == False: # se si vuole veramente salvare il cerchio in un layer + qad_layer.addGeomToLayer(self.plugIn, currLayer, self.mapToLayerCoordinates(currLayer, geom)) + return True # fine comando + + # si appresta ad attendere un punto o un numero reale + # msg, inputType, default, keyWords, valori positivi + self.waitFor(QadMsg.translate("Command_CIRCLE", "Specify the circle diameter: "), \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT, \ + None, \ + "", \ + QadInputModeEnum.NOT_NULL | QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) + self.isValidPreviousInput = False # per gestire il comando anche in macro + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL PRIMO PUNTO DEL CERCHIO (da step = 1) + elif self.step == 4: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + snapTypeOnSel = self.getPointMapTool().snapTypeOnSelection + value = self.getPointMapTool().point + entity = self.getPointMapTool().entity + else: # il punto arriva come parametro della funzione + value = msg + snapTypeOnSel = QadSnapTypeEnum.NONE + + # se é stato selezionato un punto con la modalità TAN_DEF é un punto differito + if snapTypeOnSel == QadSnapTypeEnum.TAN_DEF and entity.isInitialized(): + self.firstPt = None + self.firstPtTan = value + + # la funzione ritorna una lista con + # ( + # + # + # + # se geometria chiusa è tipo polyline la lista contiene anche + # + # <"a sinistra di" se il punto é alla sinista della parte (< 0 -> sinistra, > 0 -> destra) + # ) + # parte più vicina al punto self.firstPtTan + result = getQadGeomClosestPart(entity.getQadGeom(), self.firstPtTan) + # duplico la geometria della parte + self.firstGeomTan = getQadGeomPartAt(entity.getQadGeom(), result[2], result[3], result[4]).copy() + + # imposto il map tool + self.getPointMapTool().setMode(Qad_circle_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT) + else: # altrimenti é un punto esplicito + self.firstPt = value + self.plugIn.setLastPoint(value) + # imposto il map tool + self.getPointMapTool().firstPt = self.firstPt + self.getPointMapTool().setMode(Qad_circle_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT) + + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_CIRCLE", "Specify second point on the circle: ")) + + self.step = 5 + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL SECONDO PUNTO DEL CERCHIO (da step = 4) + elif self.step == 5: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + snapTypeOnSel = self.getPointMapTool().snapTypeOnSelection + value = self.getPointMapTool().point + entity = self.getPointMapTool().entity + else: # il punto arriva come parametro della funzione + value = msg + snapTypeOnSel = QadSnapTypeEnum.NONE + + # se é stato selezionato un punto con la modalità TAN_DEF é un punto differito + if snapTypeOnSel == QadSnapTypeEnum.TAN_DEF and entity.isInitialized(): + self.secondPt = None + self.secondPtTan = value + + # la funzione ritorna una lista con + # ( + # + # + # + # se geometria chiusa è tipo polyline la lista contiene anche + # + # <"a sinistra di" se il punto é alla sinista della parte (< 0 -> sinistra, > 0 -> destra) + # ) + # parte più vicina al punto self.secondPtTan + result = getQadGeomClosestPart(entity.getQadGeom(), self.secondPtTan) + # duplico la geometria della parte + self.secondGeomTan = getQadGeomPartAt(entity.getQadGeom(), result[2], result[3], result[4]).copy() + # imposto il map tool + self.getPointMapTool().setMode(Qad_circle_maptool_ModeEnum.FIRST_SECOND_PT_KNOWN_ASK_FOR_THIRD_PT) + else: # altrimenti é un punto esplicito + self.secondPt = value + self.plugIn.setLastPoint(value) + # imposto il map tool + self.getPointMapTool().secondPt = self.secondPt + self.getPointMapTool().setMode(Qad_circle_maptool_ModeEnum.FIRST_SECOND_PT_KNOWN_ASK_FOR_THIRD_PT) + + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_CIRCLE", "Specify the third point on the circle: ")) + + self.step = 6 + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL TERZO PUNTO DEL CERCHIO (da step = 5) + elif self.step == 6: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + snapTypeOnSel = self.getPointMapTool().snapTypeOnSelection + value = self.getPointMapTool().point + entity = self.getPointMapTool().entity + else: # il punto arriva come parametro della funzione + value = msg + snapTypeOnSel = QadSnapTypeEnum.NONE + + # se é stato selezionato un punto con la modalità TAN_DEF é un punto differito + if snapTypeOnSel == QadSnapTypeEnum.TAN_DEF and entity.isInitialized(): + self.thirdPt = None + self.thirdPtTan = value + + # la funzione ritorna una lista con + # ( + # + # + # + # se geometria chiusa è tipo polyline la lista contiene anche + # + # <"a sinistra di" se il punto é alla sinista della parte (< 0 -> sinistra, > 0 -> destra) + # ) + # parte più vicina al punto self.secondPtTan + result = getQadGeomClosestPart(entity.getQadGeom(), self.thirdPtTan) + # duplico la geometria della parte + self.thirdGeomTan = getQadGeomPartAt(entity.getQadGeom(), result[2], result[3], result[4]).copy() + else: # altrimenti é un punto esplicito + self.thirdPt = value + self.plugIn.setLastPoint(value) + + circle = None + if self.firstPt is None: # se il primo punto é definito con un punto differito + if self.secondPt is None: # se il secondo punto é definito con un punto differito + if self.thirdPt is None: # se il terzo punto é definito con un punto differito + circle = circleFrom3TanPts(self.firstGeomTan, self.firstPtTan, \ + self.secondGeomTan, self.secondPtTan, \ + self.thirdGeomTan, self.thirdPtTan) + else: # se il terzo punto é definito con un punto esplicito + circle = circleFrom1IntPt2TanPts(self.thirdPt, self.firstGeomTan, self.firstPtTan, + self.secondGeomTan, self.secondPtTan) + else: # se il secondo punto é definito con un punto esplicito + if self.thirdPt is None: # se il terzo punto é definito con un punto differito + circle = circleFrom1IntPt2TanPts(self.secondPt, self.firstGeomTan, self.firstPtTan, + self.thirdGeomTan, self.thirdPtTan) + else: # se il terzo punto é definito con un punto esplicito + circle = circleFrom2IntPts1TanPt(self.secondPt, self.thirdPt, \ + self.firstGeomTan, self.firstPtTan) + else: # se il primo punto é definito con un punto esplicito + if self.secondPt is None: # se il secondo punto é definito con un punto differito + if self.thirdPt is None: # se il terzo punto é definito con un punto differito + circe = circleFrom1IntPt2TanPts(self.firstPt, self.secondGeomTan, self.secondPtTan, + self.thirdGeomTan, self.thirdPtTan) + else: # se il terzo punto é definito con un punto esplicito + circe = circleFrom2IntPts1TanPt(self.firstPt, self.thirdPt, \ + self.secondGeomTan, self.secondPtTan) + else: # se il secondo punto é definito con un punto esplicito + if self.thirdPt is None: # se il terzo punto é definito con un punto differito + circle = circleFrom2IntPts1TanPt(self.firstPt, self.secondPt, \ + self.thirdGeomTan, self.thirdPtTan) + else: # se il terzo punto é definito con un punto esplicito + circle = circleFrom3Pts(self.firstPt, self.secondPt, value) + + if circle is not None: + self.centerPt = circle.center + self.radius = circle.radius + + geom = circle.asGeom(currLayer.wkbType()) + if geom is not None: + if self.virtualCmd == False: # se si vuole veramente salvare il cerchio in un layer + qad_layer.addGeomToLayer(self.plugIn, currLayer, self.mapToLayerCoordinates(currLayer, geom)) + return True # fine comando + + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_CIRCLE", "Specify the third point on the circle: ")) + self.isValidPreviousInput = False # per gestire il comando anche in macro + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DELLA PRIMA ESTREMITA' DIAM DEL CERCHIO (da step = 1) + elif self.step == 7: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + snapTypeOnSel = self.getPointMapTool().snapTypeOnSelection + value = self.getPointMapTool().point + entity = self.getPointMapTool().entity + else: # il punto arriva come parametro della funzione + value = msg + snapTypeOnSel = QadSnapTypeEnum.NONE + + # se é stato selezionato un punto con la modalità TAN_DEF é un punto differito + if snapTypeOnSel == QadSnapTypeEnum.TAN_DEF and entity.isInitialized(): + self.firstDiamPt = None + self.firstDiamPtTan = value + result = getQadGeomClosestPart(entity.getQadGeom(), self.firstDiamPtTan) + # duplico la geometria della parte + self.firstDiamGeomTan = getQadGeomPartAt(entity.getQadGeom(), result[2], result[3], result[4]).copy() + # imposto il map tool + self.getPointMapTool().setMode(Qad_circle_maptool_ModeEnum.FIRST_DIAM_PT_KNOWN_ASK_FOR_SECOND_DIAM_PT) + else: # altrimenti é un punto esplicito + self.firstDiamPt = value + # imposto il map tool + self.getPointMapTool().firstDiamPt = self.firstDiamPt + self.getPointMapTool().setMode(Qad_circle_maptool_ModeEnum.FIRST_DIAM_PT_KNOWN_ASK_FOR_SECOND_DIAM_PT) + + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_CIRCLE", "Specify second end of the circle diameter: ")) + + self.step = 8 + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DELLA SECONDA ESTREMITA' DIAM DEL CERCHIO (da step = 7) + elif self.step == 8: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + snapTypeOnSel = self.getPointMapTool().snapTypeOnSelection + value = self.getPointMapTool().point + entity = self.getPointMapTool().entity + else: # il punto arriva come parametro della funzione + value = msg + snapTypeOnSel = QadSnapTypeEnum.NONE + + # se é stato selezionato un punto con la modalità TAN_DEF é un punto differito + if snapTypeOnSel == QadSnapTypeEnum.TAN_DEF and entity.isInitialized(): + self.secondDiamPt = None + self.secondDiamPtTan = value + result = getQadGeomClosestPart(entity.getQadGeom(), self.secondDiamPtTan) + # duplico la geometria della parte + self.secondDiamGeomTan = getQadGeomPartAt(entity.getQadGeom(), result[2], result[3], result[4]).copy() + else: # altrimenti é un punto esplicito + self.secondDiamPt = value + + circle = None + if self.firstDiamPt is None: # se il diametro é definito con il primo punto differito + if self.secondDiamPt is None: # il diametro é definito con il secondo punto differito + circle = circleFromDiamEnds2TanPts(self.firstDiamGeomTan, self.firstDiamPtTan, \ + self.secondDiamGeomTan, self.secondDiamPtTan) + else: # se il diametro è definito con il secondo punto esplicito + circle = circleFromDiamEndsPtTanPt(self.secondDiamPt, self.firstDiamGeomTan, self.firstDiamPtTan) + else: # se il diametro è definito con il primo punto esplicito + if self.secondDiamPt is None: # il diametro è definito con il secondo punto differito + circle = circleFromDiamEndsPtTanPt(self.firstDiamPt, self.secondDiamGeomTan, self.secondDiamPtTan) + else: # se il diametro è definito con il secondo punto esplicito + circle = QadCircle().fromDiamEnds(self.firstDiamPt, self.secondDiamPt) + + if circle is not None: + self.centerPt = circle.center + self.radius = circle.radius + + geom = circle.asGeom(currLayer.wkbType()) + if geom is not None: + if self.virtualCmd == False: # se si vuole veramente salvare il cerchio in un layer + qad_layer.addGeomToLayer(self.plugIn, currLayer, self.mapToLayerCoordinates(currLayer, geom)) + return True # fine comando + + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_CIRCLE", "Specify second end of the circle diameter: ")) + self.isValidPreviousInput = False # per gestire il comando anche in macro + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA PRIMA TANGENTE (da step = 1) + elif self.step == 9: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + entity = self.getPointMapTool().entity + else: # il punto arriva come parametro della funzione + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_CIRCLE", "Specify first tangent element of the circle: ")) + self.isValidPreviousInput = False # per gestire il comando anche in macro + return False + + if not entity.isInitialized(): # se non è stata selezionata una entità + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_CIRCLE", "Specify first tangent element of the circle: ")) + self.isValidPreviousInput = False # per gestire il comando anche in macro + return False + + result = getQadGeomClosestPart(entity.getQadGeom(), self.getPointMapTool().point) + g = getQadGeomPartAt(entity.getQadGeom(), result[2], result[3], result[4]) + gType = g.whatIs() + if gType != "LINE" and gType != "ARC" and gType != "CIRCLE": + self.showErr(QadMsg.translate("Command_CIRCLE", "\nSelect a circle, an arc or a line.")) + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_CIRCLE", "Specify first tangent element of the circle: ")) + self.isValidPreviousInput = False # per gestire il comando anche in macro + return False + + self.tanPt1 = self.getPointMapTool().point + # duplico la geometria della parte + self.tanGeom1 = g.copy() + + # imposto il map tool + self.getPointMapTool().tanGeom1 = self.tanGeom1 + self.getPointMapTool().tanPt1 = self.tanPt1 + self.getPointMapTool().setMode(Qad_circle_maptool_ModeEnum.FIRST_TAN_KNOWN_ASK_FOR_SECOND_TAN) + + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_CIRCLE", "Specify second tangent element of the circle: ")) + self.step = 10 + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA SECONDA TANGENTE (da step = 9) + elif self.step == 10: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + entity = self.getPointMapTool().entity + else: # il punto arriva come parametro della funzione + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_CIRCLE", "Specify second tangent element of the circle: ")) + self.isValidPreviousInput = False # per gestire il comando anche in macro + return False + + if not entity.isInitialized(): # se non è stata selezionata una entità + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_CIRCLE", "Specify second tangent element of the circle: ")) + self.isValidPreviousInput = False # per gestire il comando anche in macro + return False + + result = getQadGeomClosestPart(entity.getQadGeom(), self.getPointMapTool().point) + g = getQadGeomPartAt(entity.getQadGeom(), result[2], result[3], result[4]) + gType = g.whatIs() + if gType != "LINE" and gType != "ARC" and gType != "CIRCLE": + self.showErr(QadMsg.translate("Command_CIRCLE", "\nSelect a circle, an arc or a line.")) + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_CIRCLE", "Specify second tangent element of the circle: ")) + self.isValidPreviousInput = False # per gestire il comando anche in macro + return False + + self.tanPt2 = self.getPointMapTool().point + # duplico la geometria della parte + self.tanGeom2 = g.copy() + + # imposto il map tool + self.getPointMapTool().tanGeom2 = self.tanGeom2 + self.getPointMapTool().tanPt2 = self.tanPt2 + self.getPointMapTool().setMode(Qad_circle_maptool_ModeEnum.FIRST_SECOND_TAN_KNOWN_ASK_FOR_RADIUS) + + # si appresta ad attendere un punto o un numero reale + # msg, inputType, default, keyWords, valori positivi + msg = QadMsg.translate("Command_CIRCLE", "Specify the circle radius <{0}>: ") + self.waitFor(msg.format(str(self.plugIn.lastRadius)), \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT, \ + self.plugIn.lastRadius, "", \ + QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) + self.step = 11 + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA RAGGIO (da step = 10) + elif self.step == 11: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == QgsPointXY: + self.startPtForRadius = value + + # imposto il map tool + self.getPointMapTool().startPtForRadius = self.startPtForRadius + self.getPointMapTool().setMode(Qad_circle_maptool_ModeEnum.FIRST_SECOND_TAN_FIRSTPTRADIUS_KNOWN_ASK_FOR_SECONDPTRADIUS) + + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_CIRCLE", "Specify second point: ")) + self.step = 12 + return False + else: + self.plugIn.setLastRadius(value) + + circle = circleFrom2TanPtsRadius(self.tanGeom1, self.tanPt1, \ + self.tanGeom2, self.tanPt2, value) + if circle is not None: + geom = circle.asGeom(currLayer.wkbType()) + if geom is not None: + self.centerPt = circle.center + self.radius = circle.radius + if self.virtualCmd == False: # se si vuole veramente salvare il cerchio in un layer + qad_layer.addGeomToLayer(self.plugIn, currLayer, self.mapToLayerCoordinates(currLayer, geom)) + else: + self.showMsg(QadMsg.translate("Command_CIRCLE", "\nThe circle doesn't exist.")) + else: + self.showMsg(QadMsg.translate("Command_CIRCLE", "\nThe circle doesn't exist.")) + + return True # fine comando + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA SECONDO PUNTO DEL RAGGIO (da step = 11) + elif self.step == 12: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + self.radius = qad_utils.getDistance(self.startPtForRadius, value) + self.plugIn.setLastRadius(self.radius) + + circle = circleFrom2TanPtsRadius(self.tanGeom1, self.tanPt1, \ + self.tanGeom2, self.tanPt2, self.radius) + if circle is not None: + geom = circle.asGeom(currLayer.wkbType()) + if geom is not None: + self.centerPt = circle.center + self.radius = circle.radius + if self.virtualCmd == False: # se si vuole veramente salvare il cerchio in un layer + qad_layer.addGeomToLayer(self.plugIn, currLayer, self.mapToLayerCoordinates(currLayer, geom)) + else: + self.showMsg(QadMsg.translate("Command_CIRCLE", "\nThe circle doesn't exist.")) + else: + self.showMsg(QadMsg.translate("Command_CIRCLE", "\nThe circle doesn't exist.")) + return True # fine comando + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA AREA DEL CERCHIO (da step = 2) + elif self.step == 13: # dopo aver atteso un numero si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton != True: # se NON usato il tasto destro del mouse + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == float: # è stata inserita l'area + self.area = value + + circle = QadCircle().fromCenterArea(self.centerPt, self.area) + if circle is not None: + self.radius = circle.radius + self.plugIn.setLastRadius(self.radius) + geom = circle.asGeom(currLayer.wkbType()) + if geom is not None: + if self.virtualCmd == False: # se si vuole veramente salvare il cerchio in un layer + qad_layer.addGeomToLayer(self.plugIn, currLayer, self.mapToLayerCoordinates(currLayer, geom)) + return True # fine comando + + self.isValidPreviousInput = False # per gestire il comando anche in macro + return False diff --git a/qad_circle_maptool.py b/cmd/qad_circle_maptool.py similarity index 84% rename from qad_circle_maptool.py rename to cmd/qad_circle_maptool.py index 25b0cbe4..42d48f38 100644 --- a/qad_circle_maptool.py +++ b/cmd/qad_circle_maptool.py @@ -1,214 +1,208 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - classe per gestire il map tool di richiesta di un punto in ambito del comando cerchio - - ------------------- - begin : 2013-05-22 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -from qgis.gui import * -import math - - -import qad_utils -from qad_snapper import * -from qad_snappointsdisplaymanager import * -from qad_variables import * -from qad_getpoint import * -from qad_circle import * -from qad_rubberband import QadRubberBand - - -#=============================================================================== -# Qad_circle_maptool_ModeEnum class. -#=============================================================================== -class Qad_circle_maptool_ModeEnum(): - # noto niente si richiede il centro - NONE_KNOWN_ASK_FOR_CENTER_PT = 1 - # noto il centro del cerchio si richiede il raggio - CENTER_PT_KNOWN_ASK_FOR_RADIUS = 2 - # noto il centro del cerchio si richiede il diametro - CENTER_PT_KNOWN_ASK_FOR_DIAM = 3 - # noto niente si richiede il primo punto - NONE_KNOWN_ASK_FOR_FIRST_PT = 4 - # noto il primo punto si richiede il secondo punto - FIRST_PT_KNOWN_ASK_FOR_SECOND_PT = 5 - # noto il primo e il secondo punto si richiede il terzo punto - FIRST_SECOND_PT_KNOWN_ASK_FOR_THIRD_PT = 6 - # noto niente si richiede il primo punto di estremità diam - NONE_KNOWN_ASK_FOR_FIRST_DIAM_PT = 7 - # noto il primo punto di estremità diam si richiede il secondo punto di estremità diam - FIRST_DIAM_PT_KNOWN_ASK_FOR_SECOND_DIAM_PT = 8 - # noto niente si richiede l'entita del primo punto di tangenza - NONE_KNOWN_ASK_FOR_FIRST_TAN = 9 - # nota l'entita del primo punto di tangenza si richiede quella del secondo punto di tangenza - FIRST_TAN_KNOWN_ASK_FOR_SECOND_TAN = 10 - # note la prima e la seconda entita dei punti di tangenza si richiede il raggio - FIRST_SECOND_TAN_KNOWN_ASK_FOR_RADIUS = 11 - # noto note la prima, la seconda entita dei punti di tangenza e il primo punto per misurare il raggio - # si richiede il secondo punto per misurare il raggio - FIRST_SECOND_TAN_FIRSTPTRADIUS_KNOWN_ASK_FOR_SECONDPTRADIUS = 12 - -#=============================================================================== -# Qad_circle_maptool class -#=============================================================================== -class Qad_circle_maptool(QadGetPoint): - - def __init__(self, plugIn): - QadGetPoint.__init__(self, plugIn) - - self.centerPt = None - self.radius = None - self.firstPt = None - self.secondPt = None - self.firstDiamPt = None - self.tan1 = None - self.tan2 = None - self.startPtForRadius = None - - self.__rubberBand = QadRubberBand(self.canvas, False) - self.geomType = QGis.Polygon - - def setRubberBandColor(self, rubberBandBorderColor, rubberBandFillColor): - if rubberBandBorderColor is not None: - self.__rubberBand.setBorderColor(rubberBandBorderColor) - if rubberBandFillColor is not None: - self.__rubberBand.setFillColor(rubberBandFillColor) - - def hidePointMapToolMarkers(self): - QadGetPoint.hidePointMapToolMarkers(self) - self.__rubberBand.hide() - - def showPointMapToolMarkers(self): - QadGetPoint.showPointMapToolMarkers(self) - self.__rubberBand.show() - - def clear(self): - QadGetPoint.clear(self) - self.__rubberBand.reset() - self.mode = None - - - def canvasMoveEvent(self, event): - QadGetPoint.canvasMoveEvent(self, event) - - self.__rubberBand.reset() - - result = False - circle = QadCircle() - - # noto il centro del cerchio si richiede il raggio - if self.mode == Qad_circle_maptool_ModeEnum.CENTER_PT_KNOWN_ASK_FOR_RADIUS: - radius = qad_utils.getDistance(self.centerPt, self.tmpPoint) - circle.set(self.centerPt, radius) - result = True - # noto il centro del cerchio si richiede il diametro - elif self.mode == Qad_circle_maptool_ModeEnum.CENTER_PT_KNOWN_ASK_FOR_DIAM: - diam = qad_utils.getDistance(self.centerPt, self.tmpPoint) - result = circle.set(self.centerPt, diam / 2) - result = True - # noto il primo e il secondo punto si richiede il terzo punto - elif self.mode == Qad_circle_maptool_ModeEnum.FIRST_SECOND_PT_KNOWN_ASK_FOR_THIRD_PT: - if (self.firstPt is not None) and (self.secondPt is not None): - result = circle.from3Pts(self.firstPt, self.secondPt, self.tmpPoint) - # noto il primo punto di estremità diam si richiede il secondo punto di estremità diam - elif self.mode == Qad_circle_maptool_ModeEnum.FIRST_DIAM_PT_KNOWN_ASK_FOR_SECOND_DIAM_PT: - if self.firstDiamPt is not None: - result = circle.fromDiamEnds(self.firstDiamPt, self.tmpPoint) - # noto note la prima, la seconda entita dei punti di tangenza e il primo punto per misurare il raggio - # si richiede il secondo punto per misurare il raggio - elif self.mode == Qad_circle_maptool_ModeEnum.FIRST_SECOND_TAN_FIRSTPTRADIUS_KNOWN_ASK_FOR_SECONDPTRADIUS: - radius = qad_utils.getDistance(self.startPtForRadius, self.tmpPoint) - result = circle.from2TanPtsRadius(self.tanGeom1, self.tanPt1, \ - self.tanGeom2, self.tanPt2, radius) - - if result == True: - points = circle.asPolyline() - - if points is not None: - if self.geomType == QGis.Polygon: - self.__rubberBand.setPolygon(points) - else: - self.__rubberBand.setLine(points) - - - def activate(self): - QadGetPoint.activate(self) - self.__rubberBand.show() - - - def deactivate(self): - try: # necessario perché se si chiude QGIS parte questo evento nonostante non ci sia più l'oggetto maptool ! - QadGetPoint.deactivate(self) - self.__rubberBand.hide() - except: - pass - - def setMode(self, mode): - self.mode = mode - # noto niente si richiede il centro - if self.mode == Qad_circle_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_CENTER_PT: - self.setDrawMode(QadGetPointDrawModeEnum.NONE) - # noto il centro del cerchio si richiede il raggio - elif self.mode == Qad_circle_maptool_ModeEnum.CENTER_PT_KNOWN_ASK_FOR_RADIUS: - self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.setStartPoint(self.centerPt) - # noto il centro del cerchio si richiede il diametro - elif self.mode == Qad_circle_maptool_ModeEnum.CENTER_PT_KNOWN_ASK_FOR_DIAM: - self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.setStartPoint(self.centerPt) - # noto niente si richiede il primo punto - elif self.mode == Qad_circle_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_FIRST_PT: - self.setDrawMode(QadGetPointDrawModeEnum.NONE) - # noto il primo punto si richiede il secondo punto - elif self.mode == Qad_circle_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT: - self.setDrawMode(QadGetPointDrawModeEnum.NONE) - # noto il primo e il secondo punto si richiede il terzo punto - elif self.mode == Qad_circle_maptool_ModeEnum.FIRST_SECOND_PT_KNOWN_ASK_FOR_THIRD_PT: - self.setDrawMode(QadGetPointDrawModeEnum.NONE) - # noto niente si richiede il primo punto di estremità diam - elif self.mode == Qad_circle_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_FIRST_DIAM_PT: - self.setDrawMode(QadGetPointDrawModeEnum.NONE) - # noto il primo punto di estremità diam si richiede il secondo punto di estremità diam - elif self.mode == Qad_circle_maptool_ModeEnum.FIRST_DIAM_PT_KNOWN_ASK_FOR_SECOND_DIAM_PT: - self.setDrawMode(QadGetPointDrawModeEnum.NONE) - # noto niente si richiede l'entita del primo punto di tangenza - elif self.mode == Qad_circle_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_FIRST_TAN: - self.setDrawMode(QadGetPointDrawModeEnum.NONE) - self.setSelectionMode(QadGetPointSelectionModeEnum.ENTITY_SELECTION) - self.forceSnapTypeOnce(QadSnapTypeEnum.TAN_DEF) - # nota l'entita del primo punto di tangenza si richiede quella del secondo punto di tangenza - elif self.mode == Qad_circle_maptool_ModeEnum.FIRST_TAN_KNOWN_ASK_FOR_SECOND_TAN: - self.setDrawMode(QadGetPointDrawModeEnum.NONE) - self.setSelectionMode(QadGetPointSelectionModeEnum.ENTITY_SELECTION) - self.forceSnapTypeOnce(QadSnapTypeEnum.TAN_DEF) - # note la prima e la seconda entita dei punti di tangenza si richiede il raggio - elif self.mode == Qad_circle_maptool_ModeEnum.FIRST_SECOND_TAN_KNOWN_ASK_FOR_RADIUS: - self.setDrawMode(QadGetPointDrawModeEnum.NONE) - # siccome il puntatore era stato variato in ENTITY_SELECTION dalla selez precedente - self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) - # noto note la prima, la seconda entita dei punti di tangenza e il primo punto per misurare il raggio - # si richiede il secondo punto per misurare il raggio - elif self.mode == Qad_circle_maptool_ModeEnum.FIRST_SECOND_TAN_FIRSTPTRADIUS_KNOWN_ASK_FOR_SECONDPTRADIUS: - self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.setStartPoint(self.startPtForRadius) +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + classe per gestire il map tool di richiesta di un punto in ambito del comando cerchio + + ------------------- + begin : 2013-05-22 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from qgis.core import QgsWkbTypes + + +from .. import qad_utils +from ..qad_getpoint import QadGetPoint, QadGetPointDrawModeEnum, QadGetPointSelectionModeEnum +from ..qad_circle import QadCircle +from ..qad_circle_fun import * +from ..qad_rubberband import QadRubberBand +from ..qad_snapper import QadSnapTypeEnum + + +# =============================================================================== +# Qad_circle_maptool_ModeEnum class. +# =============================================================================== +class Qad_circle_maptool_ModeEnum(): + # noto niente si richiede il centro + NONE_KNOWN_ASK_FOR_CENTER_PT = 1 + # noto il centro del cerchio si richiede il raggio + CENTER_PT_KNOWN_ASK_FOR_RADIUS = 2 + # noto il centro del cerchio si richiede il diametro + CENTER_PT_KNOWN_ASK_FOR_DIAM = 3 + # noto niente si richiede il primo punto + NONE_KNOWN_ASK_FOR_FIRST_PT = 4 + # noto il primo punto si richiede il secondo punto + FIRST_PT_KNOWN_ASK_FOR_SECOND_PT = 5 + # noto il primo e il secondo punto si richiede il terzo punto + FIRST_SECOND_PT_KNOWN_ASK_FOR_THIRD_PT = 6 + # noto niente si richiede il primo punto di estremità diam + NONE_KNOWN_ASK_FOR_FIRST_DIAM_PT = 7 + # noto il primo punto di estremità diam si richiede il secondo punto di estremità diam + FIRST_DIAM_PT_KNOWN_ASK_FOR_SECOND_DIAM_PT = 8 + # noto niente si richiede l'entita del primo punto di tangenza + NONE_KNOWN_ASK_FOR_FIRST_TAN = 9 + # nota l'entita del primo punto di tangenza si richiede quella del secondo punto di tangenza + FIRST_TAN_KNOWN_ASK_FOR_SECOND_TAN = 10 + # note la prima e la seconda entita dei punti di tangenza si richiede il raggio + FIRST_SECOND_TAN_KNOWN_ASK_FOR_RADIUS = 11 + # noto note la prima, la seconda entita dei punti di tangenza e il primo punto per misurare il raggio + # si richiede il secondo punto per misurare il raggio + FIRST_SECOND_TAN_FIRSTPTRADIUS_KNOWN_ASK_FOR_SECONDPTRADIUS = 12 + +# =============================================================================== +# Qad_circle_maptool class +# =============================================================================== +class Qad_circle_maptool(QadGetPoint): + + def __init__(self, plugIn): + QadGetPoint.__init__(self, plugIn) + + self.centerPt = None + self.radius = None + self.firstPt = None + self.secondPt = None + self.firstDiamPt = None + self.tan1 = None + self.tan2 = None + self.startPtForRadius = None + + self.__rubberBand = QadRubberBand(self.canvas, False) + self.layer = None + self.mode = None + + + def setRubberBandColor(self, rubberBandBorderColor, rubberBandFillColor): + if rubberBandBorderColor is not None: + self.__rubberBand.setBorderColor(rubberBandBorderColor) + if rubberBandFillColor is not None: + self.__rubberBand.setFillColor(rubberBandFillColor) + + def hidePointMapToolMarkers(self): + QadGetPoint.hidePointMapToolMarkers(self) + self.__rubberBand.hide() + + def showPointMapToolMarkers(self): + QadGetPoint.showPointMapToolMarkers(self) + self.__rubberBand.show() + + def clear(self): + QadGetPoint.clear(self) + self.__rubberBand.reset() + self.mode = None + + + def canvasMoveEvent(self, event): + QadGetPoint.canvasMoveEvent(self, event) + + self.__rubberBand.reset() + + circle = None + + # noto il centro del cerchio si richiede il raggio + if self.mode == Qad_circle_maptool_ModeEnum.CENTER_PT_KNOWN_ASK_FOR_RADIUS: + radius = qad_utils.getDistance(self.centerPt, self.tmpPoint) + circle = QadCircle().set(self.centerPt, radius) + # noto il centro del cerchio si richiede il diametro + elif self.mode == Qad_circle_maptool_ModeEnum.CENTER_PT_KNOWN_ASK_FOR_DIAM: + diam = qad_utils.getDistance(self.centerPt, self.tmpPoint) + circle = QadCircle().set(self.centerPt, diam / 2) + # noto il primo e il secondo punto si richiede il terzo punto + elif self.mode == Qad_circle_maptool_ModeEnum.FIRST_SECOND_PT_KNOWN_ASK_FOR_THIRD_PT: + if (self.firstPt is not None) and (self.secondPt is not None): + circle = circleFrom3Pts(self.firstPt, self.secondPt, self.tmpPoint) + # noto il primo punto di estremità diam si richiede il secondo punto di estremità diam + elif self.mode == Qad_circle_maptool_ModeEnum.FIRST_DIAM_PT_KNOWN_ASK_FOR_SECOND_DIAM_PT: + if self.firstDiamPt is not None: + circle = QadCircle().fromDiamEnds(self.firstDiamPt, self.tmpPoint) + # noto note la prima, la seconda entita dei punti di tangenza e il primo punto per misurare il raggio + # si richiede il secondo punto per misurare il raggio + elif self.mode == Qad_circle_maptool_ModeEnum.FIRST_SECOND_TAN_FIRSTPTRADIUS_KNOWN_ASK_FOR_SECONDPTRADIUS: + radius = qad_utils.getDistance(self.startPtForRadius, self.tmpPoint) + circle = circleFrom2TanPtsRadius(self.tanGeom1, self.tanPt1, \ + self.tanGeom2, self.tanPt2, radius) + + if circle is not None: + if self.layer is not None: + g = circle.asGeom(self.layer.wkbType()) + else: + g = circle.asGeom(QgsWkbTypes.CompoundCurve) # è un cerchio virtuale che non verrà salvato da questo comando + + if g is not None: + self.__rubberBand.setGeometry(g) + + + def activate(self): + QadGetPoint.activate(self) + self.__rubberBand.show() + + + def deactivate(self): + try: # necessario perché se si chiude QGIS parte questo evento nonostante non ci sia più l'oggetto maptool ! + QadGetPoint.deactivate(self) + self.__rubberBand.hide() + except: + pass + + def setMode(self, mode): + self.mode = mode + # noto niente si richiede il centro + if self.mode == Qad_circle_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_CENTER_PT: + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + # noto il centro del cerchio si richiede il raggio + elif self.mode == Qad_circle_maptool_ModeEnum.CENTER_PT_KNOWN_ASK_FOR_RADIUS: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.centerPt) + # noto il centro del cerchio si richiede il diametro + elif self.mode == Qad_circle_maptool_ModeEnum.CENTER_PT_KNOWN_ASK_FOR_DIAM: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.centerPt) + # noto niente si richiede il primo punto + elif self.mode == Qad_circle_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_FIRST_PT: + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + # noto il primo punto si richiede il secondo punto + elif self.mode == Qad_circle_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT: + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + # noto il primo e il secondo punto si richiede il terzo punto + elif self.mode == Qad_circle_maptool_ModeEnum.FIRST_SECOND_PT_KNOWN_ASK_FOR_THIRD_PT: + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + # noto niente si richiede il primo punto di estremità diam + elif self.mode == Qad_circle_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_FIRST_DIAM_PT: + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + # noto il primo punto di estremità diam si richiede il secondo punto di estremità diam + elif self.mode == Qad_circle_maptool_ModeEnum.FIRST_DIAM_PT_KNOWN_ASK_FOR_SECOND_DIAM_PT: + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + # noto niente si richiede l'entita del primo punto di tangenza + elif self.mode == Qad_circle_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_FIRST_TAN: + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + self.setSelectionMode(QadGetPointSelectionModeEnum.ENTITY_SELECTION) + self.forceSnapTypeOnce(QadSnapTypeEnum.TAN_DEF) + # nota l'entita del primo punto di tangenza si richiede quella del secondo punto di tangenza + elif self.mode == Qad_circle_maptool_ModeEnum.FIRST_TAN_KNOWN_ASK_FOR_SECOND_TAN: + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + self.setSelectionMode(QadGetPointSelectionModeEnum.ENTITY_SELECTION) + self.forceSnapTypeOnce(QadSnapTypeEnum.TAN_DEF) + # note la prima e la seconda entita dei punti di tangenza si richiede il raggio + elif self.mode == Qad_circle_maptool_ModeEnum.FIRST_SECOND_TAN_KNOWN_ASK_FOR_RADIUS: + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + # siccome il puntatore era stato variato in ENTITY_SELECTION dalla selez precedente + self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) + # noto note la prima, la seconda entita dei punti di tangenza e il primo punto per misurare il raggio + # si richiede il secondo punto per misurare il raggio + elif self.mode == Qad_circle_maptool_ModeEnum.FIRST_SECOND_TAN_FIRSTPTRADIUS_KNOWN_ASK_FOR_SECONDPTRADIUS: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.startPtForRadius) diff --git a/qad_copy_cmd.py b/cmd/qad_copy_cmd.py similarity index 76% rename from qad_copy_cmd.py rename to cmd/qad_copy_cmd.py index 73d9d67e..f3d9becb 100644 --- a/qad_copy_cmd.py +++ b/cmd/qad_copy_cmd.py @@ -1,552 +1,555 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - comando COPY per copiare oggetti - - ------------------- - begin : 2013-10-02 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -from qad_copy_maptool import * -from qad_generic_cmd import QadCommandClass -from qad_msg import QadMsg -from qad_getpoint import * -from qad_textwindow import * -from qad_ssget_cmd import QadSSGetClass -from qad_entity import * -from qad_variables import * -import qad_utils -import qad_layer -from qad_dim import * - - -# Classe che gestisce il comando COPY -class QadCOPYCommandClass(QadCommandClass): - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadCOPYCommandClass(self.plugIn) - - def getName(self): - return QadMsg.translate("Command_list", "COPY") - - def getEnglishName(self): - return "COPY" - - def connectQAction(self, action): - QObject.connect(action, SIGNAL("triggered()"), self.plugIn.runCOPYCommand) - - def getIcon(self): - return QIcon(":/plugins/qad/icons/copyEnt.png") - - def getNote(self): - # impostare le note esplicative del comando - return QadMsg.translate("Command_COPY", "Copies selected objects a specified distance in a specified direction.") - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - self.SSGetClass = QadSSGetClass(plugIn) - self.SSGetClass.onlyEditableLayers = True - self.entitySet = QadEntitySet() - self.basePt = QgsPoint() - self.series = False - self.seriesLen = 2 - self.adjust = False - self.copyMode = QadVariables.get(QadMsg.translate("Environment variables", "COPYMODE")) - - self.featureCache = [] # lista di (layer, feature) - self.nOperationsToUndo = 0 - - def __del__(self): - QadCommandClass.__del__(self) - del self.SSGetClass - - def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): - if self.step == 0: # quando si é in fase di selezione entità - return self.SSGetClass.getPointMapTool() - else: - if (self.plugIn is not None): - if self.PointMapTool is None: - self.PointMapTool = Qad_copy_maptool(self.plugIn) - return self.PointMapTool - else: - return None - - - #============================================================================ - # move - #============================================================================ - def move(self, f, offSetX, offSetY, layerEntitySet, entitySet, dimEntity): - if dimEntity is None: - # sposto la feature e la rimuovo da entitySet (é la prima) - f.setGeometry(qad_utils.moveQgsGeometry(f.geometry(), offSetX, offSetY)) - # plugIn, layer, feature, coordTransform, refresh, check_validity - if qad_layer.addFeatureToLayer(self.plugIn, layerEntitySet.layer, f, None, False, False) == False: - return False - else: - # sposto la quota - dimEntity.move(offSetX, offSetY) - if dimEntity.addToLayers(self.plugIn) == False: - return False - - return True - - - #============================================================================ - # copyGeoms - #============================================================================ - def copyGeoms(self, newPt): - # copio entitySet - entitySet = QadEntitySet(self.entitySet) - - self.plugIn.beginEditCommand("Feature copied", entitySet.getLayerList()) - - for layerEntitySet in entitySet.layerEntitySetList: - layer = layerEntitySet.layer - - transformedBasePt = self.mapToLayerCoordinates(layer, self.basePt) - transformedNewPt = self.mapToLayerCoordinates(layer, newPt) - offSetX = transformedNewPt.x() - transformedBasePt.x() - offSetY = transformedNewPt.y() - transformedBasePt.y() - - while len(layerEntitySet.featureIds) > 0: - featureId = layerEntitySet.featureIds[0] - f = layerEntitySet.getFeature(featureId) - if f is None: - del layerEntitySet.featureIds[0] - continue - - # verifico se l'entità appartiene ad uno stile di quotatura - dimEntity = QadDimStyles.getDimEntity(layerEntitySet.layer, f.id()) - - if self.series and self.seriesLen > 0: # devo fare una serie - if self.adjust == True: - offSetX = offSetX / (self.seriesLen - 1) - offSetY = offSetY / (self.seriesLen - 1) - - deltaX = offSetX - deltaY = offSetY - - for i in xrange(1, self.seriesLen, 1): - if self.move(f, deltaX, deltaY, layerEntitySet, entitySet, dimEntity) == False: - self.plugIn.destroyEditCommand() - return - deltaX = deltaX + offSetX - deltaY = deltaY + offSetY - else: - if self.move(f, offSetX, offSetY, layerEntitySet, entitySet, dimEntity) == False: - self.plugIn.destroyEditCommand() - return - - # la rimuovo da entitySet - if dimEntity is None: - del layerEntitySet.featureIds[0] - else: - dimEntitySet = dimEntity.getEntitySet() - entitySet.subtract(dimEntitySet) - - self.plugIn.endEditCommand() - self.nOperationsToUndo = self.nOperationsToUndo + 1 - - - #============================================================================ - # waitForBasePt - #============================================================================ - def waitForBasePt(self): - # imposto il map tool - self.getPointMapTool().setMode(Qad_copy_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_BASE_PT) - - if self.copyMode == 0: # Imposta il comando COPIA in modo che venga ripetuto automaticamente - keyWords = QadMsg.translate("Command_COPY", "Displacement") + "/" + \ - QadMsg.translate("Command_COPY", "mOde") - englishKeyWords = "Displacement" + "/" + "mOde" - - else: - # l'opzione Multiple viene tradotta in italiano in "MUltiplo" nel contesto "waitForBasePt" - # l'opzione Multiple viene tradotta in italiano in "Multipla" nel contesto "waitForMode" - # e "Multipla" nel caso di modalità di copia - keyWords = QadMsg.translate("Command_COPY", "Displacement") + "/" + \ - QadMsg.translate("Command_COPY", "mOde") + "/" + \ - QadMsg.translate("Command_COPY", "Multiple", "waitForBasePt") - englishKeyWords = "Displacement" + "/" + "mOde" + "/" + "Multiple" - - default = QadMsg.translate("Command_COPY", "Displacement") - prompt = QadMsg.translate("Command_COPY", "Specify base point or [{0}] <{1}>: ").format(keyWords, default) - - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o enter o una parola chiave - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - default, \ - keyWords, QadInputModeEnum.NONE) - self.step = 2 - - #============================================================================ - # waitForSeries - #============================================================================ - def waitForSeries(self): - # si appresta ad attendere un numero intero - msg = QadMsg.translate("Command_COPY", "Number of Items to Array <{0}>: ") - # msg, inputType, default, keyWords, valori positivi - self.waitFor(msg.format(str(self.seriesLen)), \ - QadInputTypeEnum.INT, \ - self.seriesLen, \ - "", \ - QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) - self.step = 6 - - #============================================================================ - # waitForSecondPt - #============================================================================ - def waitForSecondPt(self): - self.series = False - self.adjust = False - self.getPointMapTool().seriesLen = 0 - self.getPointMapTool().setMode(Qad_copy_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_COPY_PT) - - if len(self.featureCache) > 0: - keyWords = QadMsg.translate("Command_COPY", "Array") + "/" + \ - QadMsg.translate("Command_COPY", "Exit") + "/" + \ - QadMsg.translate("Command_COPY", "Undo") - default = QadMsg.translate("Command_COPY", "Exit") - prompt = QadMsg.translate("Command_COPY", "Specify second point or [{0}] <{1}>: ").format(keyWords, default) - - englishKeyWords = "Array" + "/" + "Exit" + "/" + "Undo" + "/" + "Exit" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o enter o una parola chiave - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - default, \ - keyWords, QadInputModeEnum.NONE) - else: - keyWords = QadMsg.translate("Command_COPY", "Array") - prompt = QadMsg.translate("Command_COPY", "Specify second point or [{0}] : ").format(keyWords) - - englishKeyWords = "Array" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o enter o una parola chiave - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - None, \ - keyWords, QadInputModeEnum.NONE) - - self.step = 3 - - #============================================================================ - # waitForSecondPtBySeries - #============================================================================ - def waitForSecondPtBySeries(self): - if self.adjust == False: - keyWords = QadMsg.translate("Command_COPY", "Fit") - englishKeyWords = "Fit" - else: - keyWords = QadMsg.translate("Command_COPY", "Array") - englishKeyWords = "Array" - prompt = QadMsg.translate("Command_COPY", "Specify second point or [{0}]: ").format(keyWords) - - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o enter o una parola chiave - # msg, inputType, default, keyWords, valore nullo non permesso - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - "", \ - keyWords, QadInputModeEnum.NOT_NULL) - self.step = 7 - - #============================================================================ - # run - #============================================================================ - def run(self, msgMapTool = False, msg = None): - if self.plugIn.canvas.mapRenderer().destinationCrs().geographicFlag(): - self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) - return True # fine comando - - #========================================================================= - # RICHIESTA SELEZIONE OGGETTI - if self.step == 0: # inizio del comando - if self.SSGetClass.run(msgMapTool, msg) == True: - # selezione terminata - self.step = 1 - return self.run(msgMapTool, msg) - - #========================================================================= - # COPIA OGGETTI - elif self.step == 1: - self.entitySet.set(self.SSGetClass.entitySet) - - if self.entitySet.count() == 0: - return True # fine comando - - CurrSettingsMsg = QadMsg.translate("QAD", "\nCurrent settings: ") - if self.copyMode == 0: # 0 = multipla - CurrSettingsMsg = CurrSettingsMsg + QadMsg.translate("Command_COPY", "Copy mode = Multiple") - else: # 1 = singola - CurrSettingsMsg = CurrSettingsMsg + QadMsg.translate("Command_COPY", "Copy mode = Single") - self.showMsg(CurrSettingsMsg) - - self.getPointMapTool().entitySet.set(self.entitySet) - self.waitForBasePt() - self.getPointMapTool().refreshSnapType() # riagggiorno lo snapType che può essere variato dal maptool di selezione entità - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA PUNTO BASE (da step = 1) - elif self.step == 2: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - pass # opzione di default "spostamento" - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if value is None: - value = QadMsg.translate("Command_COPY", "Displacement") - - if type(value) == unicode: - if value == QadMsg.translate("Command_COPY", "Displacement") or value == "Displacement": - self.basePt.set(0, 0) - self.getPointMapTool().basePt = self.basePt - self.getPointMapTool().setMode(Qad_copy_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_COPY_PT) - # si appresta ad attendere un punto - msg = QadMsg.translate("Command_COPY", "Specify the displacement from the origin point 0,0 <{0}, {1}>: ") - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(msg.format(str(self.plugIn.lastOffsetPt.x()), str(self.plugIn.lastOffsetPt.y())), \ - QadInputTypeEnum.POINT2D, \ - self.plugIn.lastOffsetPt, \ - "", QadInputModeEnum.NONE) - self.step = 4 - elif value == QadMsg.translate("Command_COPY", "mOde") or value == "mOde": - # l'opzione Multiple viene tradotta in italiano in "Multipla" nel contesto "waitForMode" - keyWords = QadMsg.translate("Command_COPY", "Single") + "/" + \ - QadMsg.translate("Command_COPY", "Multiple", "waitForMode") - englishKeyWords = "Single" + "/" + "Multiple" - - if self.copyMode == 0: # Imposta il comando COPIA in modo che venga ripetuto automaticamente - # l'opzione Multiple viene tradotta in italiano in "Multipla" nel contesto "waitForMode" - default = QadMsg.translate("Command_COPY", "Multiple", "waitForMode") - else: - default = QadMsg.translate("Command_COPY", "Single") - prompt = QadMsg.translate("Command_COPY", "Enter a copy mode option [{0}] <{1}>: ").format(keyWords, default) - - keyWords += "_" + englishKeyWords - # si appresta ad attendere enter o una parola chiave - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(prompt, \ - QadInputTypeEnum.KEYWORDS, \ - default, \ - keyWords, QadInputModeEnum.NONE) - self.step = 5 - # l'opzione Multiple viene tradotta in italiano in "MUltiplo" nel contesto "waitForBasePt" - elif value == QadMsg.translate("Command_COPY", "Multiple", "waitForBasePt") or value == "Multiple": - self.copyMode = 0 # Imposta il comando COPIA in modo che venga ripetuto automaticamente - self.waitForBasePt() - elif type(value) == QgsPoint: # se é stato inserito il punto base - self.basePt.set(value.x(), value.y()) - - # imposto il map tool - self.getPointMapTool().basePt = self.basePt - self.waitForSecondPt() - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA SECONDO PUNTO PER COPIA (da step = 2) - elif self.step == 3: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - if len(self.featureCache) > 0: - value = QadMsg.translate("Command_COPY", "Exit") - else: - value = None - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - else: - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if value is None: - if len(self.featureCache) > 0: - value = QadMsg.translate("Command_COPY", "Exit") - else: - # utilizzare il primo punto come spostamento - value = QgsPoint(self.basePt) - self.basePt.set(0, 0) - self.copyGeoms(value) - return True # fine comando - - if type(value) == unicode: - if value == QadMsg.translate("Command_COPY", "Array") or value == "Array": - self.waitForSeries() - elif value == QadMsg.translate("Command_COPY", "Exit") or value == "Exit": - return True # fine comando - elif value == QadMsg.translate("Command_COPY", "Undo") or value == "Undo": - if self.nOperationsToUndo > 0: - self.nOperationsToUndo = self.nOperationsToUndo - 1 - self.plugIn.undoEditCommand() - else: - self.showMsg(QadMsg.translate("QAD", "\nThe command has been canceled.")) - self.waitForSecondPt() - elif type(value) == QgsPoint: # se é stato inserito lo spostamento con un punto - self.copyGeoms(value) - if self.copyMode == 1: # "Singola" - return True # fine comando - self.waitForSecondPt() - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DEL PUNTO DI SPOSTAMENTO (da step = 2) - elif self.step == 4: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - self.plugIn.setLastOffsetPt(value) - self.copyGeoms(value) - return True # fine comando - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DELLA MODALITA' (SINGOLA / MULTIPLA) (da step = 2) - elif self.step == 5: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # la parola chiave arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("Command_COPY", "Single") or value == "Single": - self.copyMode = 1 - QadVariables.set(QadMsg.translate("Environment variables", "COPYMODE"), 1) - QadVariables.save() - # l'opzione Multiple viene tradotta in italiano in "Multipla" nel contesto "waitForMode" - elif value == QadMsg.translate("Command_COPY", "Multiple", "waitForMode") or value == "Multiple": - self.copyMode = 0 - QadVariables.set(QadMsg.translate("Environment variables", "COPYMODE"), 0) - QadVariables.save() - - self.waitForBasePt() - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DELLA SERIE (da step = 3) - elif self.step == 6: # dopo aver atteso un numero intero si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - value = self.seriesLen - else: - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if value < 2: - self.showMsg(QadMsg.translate("Command_COPY", "\nThe value must be between 2 and 32767.")) - self.waitForSeries() - else: - self.series = True - self.seriesLen = value - self.getPointMapTool().seriesLen = self.seriesLen - - self.waitForSecondPtBySeries() - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA SECONDO PUNTO PER COPIA DA SERIE (da step = 6) - elif self.step == 7: # dopo aver atteso un punto o una parola chiave - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("Command_COPY", "Array") or value == "Array": - self.adjust = False - self.getPointMapTool().adjust = self.adjust - self.waitForSecondPtBySeries() - elif value == QadMsg.translate("Command_COPY", "Fit") or value == "Fit": - self.adjust = True - self.getPointMapTool().adjust = self.adjust - self.waitForSecondPtBySeries() - elif type(value) == QgsPoint: # se é stato inserito lo spostamento con un punto - self.copyGeoms(value) - if self.copyMode == 1: # "Singola" - return True # fine comando - self.waitForSecondPt() - +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + comando COPY per copiare oggetti + + ------------------- + begin : 2013-10-02 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.PyQt.QtGui import QIcon +from qgis.core import QgsPointXY + + +from .qad_copy_maptool import Qad_copy_maptool, Qad_copy_maptool_ModeEnum +from .qad_generic_cmd import QadCommandClass +from ..qad_msg import QadMsg +from ..qad_getpoint import QadGetPointDrawModeEnum +from ..qad_textwindow import QadInputTypeEnum, QadInputModeEnum +from .qad_ssget_cmd import QadSSGetClass +from ..qad_entity import QadCacheEntitySet, QadCacheEntitySetIterator, QadEntityTypeEnum +from ..qad_variables import QadVariables +from .. import qad_utils +from .. import qad_layer +from ..qad_dim import QadDimEntity, QadDimStyles, appendDimEntityIfNotExisting +from ..qad_multi_geom import fromQadGeomToQgsGeom + + +# Classe che gestisce il comando COPY +class QadCOPYCommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadCOPYCommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "COPY") + + def getEnglishName(self): + return "COPY" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runCOPYCommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/copyEnt.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_COPY", "Copies selected objects a specified distance in a specified direction.") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + self.SSGetClass = QadSSGetClass(plugIn) + self.SSGetClass.onlyEditableLayers = True + self.cacheEntitySet = QadCacheEntitySet() + self.basePt = QgsPointXY() + self.series = False + self.seriesLen = 2 + self.adjust = False + self.copyMode = QadVariables.get(QadMsg.translate("Environment variables", "COPYMODE")) + + self.nOperationsToUndo = 0 + + def __del__(self): + QadCommandClass.__del__(self) + del self.SSGetClass + + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if self.step == 0: # quando si é in fase di selezione entità + return self.SSGetClass.getPointMapTool() + else: + if (self.plugIn is not None): + if self.PointMapTool is None: + self.PointMapTool = Qad_copy_maptool(self.plugIn) + return self.PointMapTool + else: + return None + + + def getCurrentContextualMenu(self): + if self.step == 0: # quando si é in fase di selezione entità + return None # return self.SSGetClass.getCurrentContextualMenu() + else: + return self.contextualMenu + + + # ============================================================================ + # move + # ============================================================================ + def move(self, entity, offsetX, offsetY, openForm = True): + # verifico se l'entità appartiene ad uno stile di quotatura + if entity.whatIs() == "ENTITY": + # sposto la geometria dell'entità + qadGeom = entity.getQadGeom().copy() # la copio + qadGeom.move(offsetX, offsetY) + f = entity.getFeature() + f.setGeometry(fromQadGeomToQgsGeom(qadGeom, entity.layer)) + # plugIn, layer, feature, coordTransform, refresh, check_validity + if qad_layer.addFeatureToLayer(self.plugIn, entity.layer, f, None, False, False, openForm) == False: + return False + elif entity.whatIs() == "DIMENTITY": + newDimEntity = QadDimEntity(entity) # la copio + # sposto la quota + newDimEntity.move(offsetX, offsetY) + if newDimEntity.addToLayers(self.plugIn) == False: + return False + + return True + + + # ============================================================================ + # copyGeoms + # ============================================================================ + def copyGeoms(self, newPt): + offsetX = newPt.x() - self.basePt.x() + offsetY = newPt.y() - self.basePt.y() + + self.plugIn.beginEditCommand("Feature copied", self.cacheEntitySet.getLayerList()) + + dimElaboratedList = [] # lista delle quotature già elaborate + entityIterator = QadCacheEntitySetIterator(self.cacheEntitySet) + openForm = True if self.cacheEntitySet.count() == 1 and self.seriesLen <= 2 else False + + for entity in entityIterator: + qadGeom = entity.getQadGeom() # così inizializzo le info qad + # verifico se l'entità appartiene ad uno stile di quotatura + dimEntity = QadDimStyles.getDimEntity(entity) + if dimEntity is not None: + if appendDimEntityIfNotExisting(dimElaboratedList, dimEntity) == False: # quota già elaborata + continue + entity = dimEntity + + if self.seriesLen > 0: # devo fare una serie + if self.adjust == True: + offsetXToApply = offsetX / (self.seriesLen - 1) + offsetYToApply = offsetY / (self.seriesLen - 1) + else: + offsetXToApply = offsetX + offsetYToApply = offsetY + + deltaX = offsetXToApply + deltaY = offsetYToApply + + for i in range(1, self.seriesLen, 1): + if self.move(entity, deltaX, deltaY, openForm) == False: + self.plugIn.destroyEditCommand() + return + deltaX = deltaX + offsetXToApply + deltaY = deltaY + offsetYToApply + + else: + if self.move(entity, offsetX, offsetY, openForm) == False: + self.plugIn.destroyEditCommand() + return + + self.plugIn.endEditCommand() + self.nOperationsToUndo = self.nOperationsToUndo + 1 + + + # ============================================================================ + # waitForBasePt + # ============================================================================ + def waitForBasePt(self): + # imposto il map tool + self.getPointMapTool().setMode(Qad_copy_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_BASE_PT) + + if self.copyMode == 0: # Imposta il comando COPIA in modo che venga ripetuto automaticamente + keyWords = QadMsg.translate("Command_COPY", "Displacement") + "/" + \ + QadMsg.translate("Command_COPY", "mOde") + englishKeyWords = "Displacement" + "/" + "mOde" + + else: + # l'opzione Multiple viene tradotta in italiano in "MUltiplo" nel contesto "waitForBasePt" + # l'opzione Multiple viene tradotta in italiano in "Multipla" nel contesto "waitForMode" + # e "Multipla" nel caso di modalità di copia + keyWords = QadMsg.translate("Command_COPY", "Displacement") + "/" + \ + QadMsg.translate("Command_COPY", "mOde") + "/" + \ + QadMsg.translate("Command_COPY", "Multiple", "waitForBasePt") + englishKeyWords = "Displacement" + "/" + "mOde" + "/" + "Multiple" + + default = QadMsg.translate("Command_COPY", "Displacement") + prompt = QadMsg.translate("Command_COPY", "Specify base point or [{0}] <{1}>: ").format(keyWords, default) + + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + default, \ + keyWords, QadInputModeEnum.NONE) + self.step = 2 + + # ============================================================================ + # waitForSeries + # ============================================================================ + def waitForSeries(self): + # si appresta ad attendere un numero intero + msg = QadMsg.translate("Command_COPY", "Number of Items to Array <{0}>: ") + # msg, inputType, default, keyWords, valori positivi + self.waitFor(msg.format(str(self.seriesLen)), \ + QadInputTypeEnum.INT, \ + self.seriesLen, \ + "", \ + QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) + self.step = 6 + + # ============================================================================ + # waitForSecondPt + # ============================================================================ + def waitForSecondPt(self): + self.series = False + self.adjust = False + self.getPointMapTool().seriesLen = 0 + self.getPointMapTool().setMode(Qad_copy_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_COPY_PT) + + if self.nOperationsToUndo > 0: + keyWords = QadMsg.translate("Command_COPY", "Array") + "/" + \ + QadMsg.translate("Command_COPY", "Exit") + "/" + \ + QadMsg.translate("Command_COPY", "Undo") + default = QadMsg.translate("Command_COPY", "Exit") + prompt = QadMsg.translate("Command_COPY", "Specify second point or [{0}] <{1}>: ").format(keyWords, default) + + englishKeyWords = "Array" + "/" + "Exit" + "/" + "Undo" + "/" + "Exit" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + default, \ + keyWords, QadInputModeEnum.NONE) + else: + keyWords = QadMsg.translate("Command_COPY", "Array") + prompt = QadMsg.translate("Command_COPY", "Specify second point or [{0}] : ").format(keyWords) + + englishKeyWords = "Array" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, QadInputModeEnum.NONE) + + self.step = 3 + + # ============================================================================ + # waitForSecondPtBySeries + # ============================================================================ + def waitForSecondPtBySeries(self): + if self.adjust == False: + keyWords = QadMsg.translate("Command_COPY", "Fit") + englishKeyWords = "Fit" + else: + keyWords = QadMsg.translate("Command_COPY", "Array") + englishKeyWords = "Array" + prompt = QadMsg.translate("Command_COPY", "Specify second point or [{0}]: ").format(keyWords) + + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, valore nullo non permesso + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + "", \ + keyWords, QadInputModeEnum.NOT_NULL) + self.step = 7 + + # ============================================================================ + # run + # ============================================================================ + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + # ========================================================================= + # RICHIESTA SELEZIONE OGGETTI + if self.step == 0: # inizio del comando + if self.SSGetClass.run(msgMapTool, msg) == True: + # selezione terminata + self.step = 1 + return self.run(msgMapTool, msg) + + # ========================================================================= + # COPIA OGGETTI + elif self.step == 1: + if self.SSGetClass.entitySet.count() == 0: + return True # fine comando + self.cacheEntitySet.appendEntitySet(self.SSGetClass.entitySet) + + CurrSettingsMsg = QadMsg.translate("QAD", "\nCurrent settings: ") + if self.copyMode == 0: # 0 = multipla + CurrSettingsMsg = CurrSettingsMsg + QadMsg.translate("Command_COPY", "Copy mode = Multiple") + else: # 1 = singola + CurrSettingsMsg = CurrSettingsMsg + QadMsg.translate("Command_COPY", "Copy mode = Single") + self.showMsg(CurrSettingsMsg) + + self.getPointMapTool().cacheEntitySet = self.cacheEntitySet + self.waitForBasePt() + self.getPointMapTool().refreshSnapType() # riagggiorno lo snapType che può essere variato dal maptool di selezione entità + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA PUNTO BASE (da step = 1) + elif self.step == 2: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + pass # opzione di default "spostamento" + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if value is None: + value = QadMsg.translate("Command_COPY", "Displacement") + + if type(value) == unicode: + if value == QadMsg.translate("Command_COPY", "Displacement") or value == "Displacement": + self.basePt.set(0, 0) + self.getPointMapTool().basePt = self.basePt + self.getPointMapTool().setMode(Qad_copy_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_COPY_PT) + # si appresta ad attendere un punto + msg = QadMsg.translate("Command_COPY", "Specify the displacement from the origin point 0,0 <{0}, {1}>: ") + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(msg.format(str(self.plugIn.lastOffsetPt.x()), str(self.plugIn.lastOffsetPt.y())), \ + QadInputTypeEnum.POINT2D, \ + self.plugIn.lastOffsetPt, \ + "", QadInputModeEnum.NONE) + self.step = 4 + elif value == QadMsg.translate("Command_COPY", "mOde") or value == "mOde": + # l'opzione Multiple viene tradotta in italiano in "Multipla" nel contesto "waitForMode" + keyWords = QadMsg.translate("Command_COPY", "Single") + "/" + \ + QadMsg.translate("Command_COPY", "Multiple", "waitForMode") + englishKeyWords = "Single" + "/" + "Multiple" + + if self.copyMode == 0: # Imposta il comando COPIA in modo che venga ripetuto automaticamente + # l'opzione Multiple viene tradotta in italiano in "Multipla" nel contesto "waitForMode" + default = QadMsg.translate("Command_COPY", "Multiple", "waitForMode") + else: + default = QadMsg.translate("Command_COPY", "Single") + prompt = QadMsg.translate("Command_COPY", "Enter a copy mode option [{0}] <{1}>: ").format(keyWords, default) + + keyWords += "_" + englishKeyWords + # si appresta ad attendere enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.KEYWORDS, \ + default, \ + keyWords, QadInputModeEnum.NONE) + self.step = 5 + # l'opzione Multiple viene tradotta in italiano in "MUltiplo" nel contesto "waitForBasePt" + elif value == QadMsg.translate("Command_COPY", "Multiple", "waitForBasePt") or value == "Multiple": + self.copyMode = 0 # Imposta il comando COPIA in modo che venga ripetuto automaticamente + self.waitForBasePt() + elif type(value) == QgsPointXY: # se é stato inserito il punto base + self.basePt.set(value.x(), value.y()) + + # imposto il map tool + self.getPointMapTool().basePt = self.basePt + self.waitForSecondPt() + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA SECONDO PUNTO PER COPIA (da step = 2) + elif self.step == 3: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + if self.nOperationsToUndo > 0: + value = QadMsg.translate("Command_COPY", "Exit") + else: + value = None + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if value is None: + if self.nOperationsToUndo > 0: + value = QadMsg.translate("Command_COPY", "Exit") + else: + # utilizzare il primo punto come spostamento + value = QgsPointXY(self.basePt) + self.basePt.set(0, 0) + self.copyGeoms(value) + return True # fine comando + + if type(value) == unicode: + if value == QadMsg.translate("Command_COPY", "Array") or value == "Array": + self.waitForSeries() + elif value == QadMsg.translate("Command_COPY", "Exit") or value == "Exit": + return True # fine comando + elif value == QadMsg.translate("Command_COPY", "Undo") or value == "Undo": + if self.nOperationsToUndo > 0: + self.nOperationsToUndo = self.nOperationsToUndo - 1 + self.plugIn.undoEditCommand() + else: + self.showMsg(QadMsg.translate("QAD", "\nThe command has been canceled.")) + self.waitForSecondPt() + elif type(value) == QgsPointXY: # se é stato inserito lo spostamento con un punto + self.copyGeoms(value) + if self.copyMode == 1: # "Singola" + return True # fine comando + self.waitForSecondPt() + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL PUNTO DI SPOSTAMENTO (da step = 2) + elif self.step == 4: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + self.plugIn.setLastOffsetPt(value) + self.copyGeoms(value) + return True # fine comando + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DELLA MODALITA' (SINGOLA / MULTIPLA) (da step = 2) + elif self.step == 5: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # la parola chiave arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("Command_COPY", "Single") or value == "Single": + self.copyMode = 1 + QadVariables.set(QadMsg.translate("Environment variables", "COPYMODE"), 1) + QadVariables.save() + # l'opzione Multiple viene tradotta in italiano in "Multipla" nel contesto "waitForMode" + elif value == QadMsg.translate("Command_COPY", "Multiple", "waitForMode") or value == "Multiple": + self.copyMode = 0 + QadVariables.set(QadMsg.translate("Environment variables", "COPYMODE"), 0) + QadVariables.save() + + self.waitForBasePt() + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DELLA SERIE (da step = 3) + elif self.step == 6: # dopo aver atteso un numero intero si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + value = self.seriesLen + else: + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if value < 2: + self.showMsg(QadMsg.translate("Command_COPY", "\nThe value must be between 2 and 32767.")) + self.waitForSeries() + else: + self.series = True + self.seriesLen = value + self.getPointMapTool().seriesLen = self.seriesLen + + self.waitForSecondPtBySeries() + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA SECONDO PUNTO PER COPIA DA SERIE (da step = 6) + elif self.step == 7: # dopo aver atteso un punto o una parola chiave + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("Command_COPY", "Array") or value == "Array": + self.adjust = False + self.getPointMapTool().adjust = self.adjust + self.waitForSecondPtBySeries() + elif value == QadMsg.translate("Command_COPY", "Fit") or value == "Fit": + self.adjust = True + self.getPointMapTool().adjust = self.adjust + self.waitForSecondPtBySeries() + elif type(value) == QgsPointXY: # se é stato inserito lo spostamento con un punto + self.copyGeoms(value) + if self.copyMode == 1: # "Singola" + return True # fine comando + self.waitForSecondPt() + return False \ No newline at end of file diff --git a/cmd/qad_copy_maptool.py b/cmd/qad_copy_maptool.py new file mode 100644 index 00000000..dabd3a41 --- /dev/null +++ b/cmd/qad_copy_maptool.py @@ -0,0 +1,155 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + classe per gestire il map tool in ambito del comando copy + + ------------------- + begin : 2013-10-02 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from .. import qad_utils +from ..qad_getpoint import QadGetPoint, QadGetPointDrawModeEnum +from ..qad_highlight import QadHighlight +from ..qad_dim import QadDimEntity, QadDimStyles, appendDimEntityIfNotExisting +from ..qad_entity import QadCacheEntitySetIterator, QadEntityTypeEnum +from ..qad_multi_geom import fromQadGeomToQgsGeom + + +# =============================================================================== +# Qad_copy_maptool_ModeEnum class. +# =============================================================================== +class Qad_copy_maptool_ModeEnum(): + # noto niente si richiede il punto base + NONE_KNOWN_ASK_FOR_BASE_PT = 1 + # noto il punto base si richiede il secondo punto per la copia + BASE_PT_KNOWN_ASK_FOR_COPY_PT = 2 + + +# =============================================================================== +# Qad_copy_maptool class +# =============================================================================== +class Qad_copy_maptool(QadGetPoint): + + def __init__(self, plugIn): + QadGetPoint.__init__(self, plugIn) + + self.basePt = None + self.cacheEntitySet = None + self.seriesLen = 0 + self.adjust = False + self.__highlight = QadHighlight(self.canvas) + + def hidePointMapToolMarkers(self): + QadGetPoint.hidePointMapToolMarkers(self) + self.__highlight.hide() + + def showPointMapToolMarkers(self): + QadGetPoint.showPointMapToolMarkers(self) + self.__highlight.show() + + def clear(self): + QadGetPoint.clear(self) + self.__highlight.reset() + self.mode = None + + + # ============================================================================ + # move + # ============================================================================ + def move(self, entity, offsetX, offsetY): + # verifico se l'entità appartiene ad uno stile di quotatura + if entity.whatIs() == "ENTITY": + # sposto la geometria dell'entità + qadGeom = entity.getQadGeom().copy() # la copio + qadGeom.move(offsetX, offsetY) + self.__highlight.addGeometry(fromQadGeomToQgsGeom(qadGeom, entity.layer), entity.layer) + elif entity.whatIs() == "DIMENTITY": + newDimEntity = QadDimEntity(entity) # la copio + # sposto la quota + newDimEntity.move(offsetX, offsetY) + self.__highlight.addGeometry(newDimEntity.textualFeature.geometry(), newDimEntity.getTextualLayer()) + self.__highlight.addGeometries(newDimEntity.getLinearGeometryCollection(), newDimEntity.getLinearLayer()) + self.__highlight.addGeometries(newDimEntity.getSymbolGeometryCollection(), newDimEntity.getSymbolLayer()) + + + def setCopiedGeometries(self, newPt): + self.__highlight.reset() + + offsetX = newPt.x() - self.basePt.x() + offsetY = newPt.y() - self.basePt.y() + + dimElaboratedList = [] # lista delle quotature già elaborate + entityIterator = QadCacheEntitySetIterator(self.cacheEntitySet) + for entity in entityIterator: + qadGeom = entity.getQadGeom() # così inizializzo le info qad + # verifico se l'entità appartiene ad uno stile di quotatura + dimEntity = QadDimStyles.getDimEntity(entity) + if dimEntity is not None: + if appendDimEntityIfNotExisting(dimElaboratedList, dimEntity) == False: # quota già elaborata + continue + entity = dimEntity + + if self.seriesLen > 0: # devo fare una serie + if self.adjust == True: + offsetXToApply = offsetX / (self.seriesLen - 1) + offsetYToApply = offsetY / (self.seriesLen - 1) + else: + offsetXToApply = offsetX + offsetYToApply = offsetY + + deltaX = offsetXToApply + deltaY = offsetYToApply + + for i in range(1, self.seriesLen, 1): + self.move(entity, deltaX, deltaY) + deltaX = deltaX + offsetXToApply + deltaY = deltaY + offsetYToApply + + else: + self.move(entity, offsetX, offsetY) + + + def canvasMoveEvent(self, event): + QadGetPoint.canvasMoveEvent(self, event) + + # noto il punto base si richiede il secondo punto + if self.mode == Qad_copy_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_COPY_PT: + self.setCopiedGeometries(self.tmpPoint) + + + def activate(self): + QadGetPoint.activate(self) + self.__highlight.show() + + def deactivate(self): + try: # necessario perché se si chiude QGIS parte questo evento nonostante non ci sia più l'oggetto maptool ! + QadGetPoint.deactivate(self) + self.__highlight.hide() + except: + pass + + def setMode(self, mode): + self.mode = mode + # noto niente si richiede il punto base + if self.mode == Qad_copy_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_BASE_PT: + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + # noto il punto base si richiede il secondo punto per l'angolo di rotazione + elif self.mode == Qad_copy_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_COPY_PT: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.basePt) diff --git a/cmd/qad_dim_cmd.py b/cmd/qad_dim_cmd.py new file mode 100644 index 00000000..c9da6850 --- /dev/null +++ b/cmd/qad_dim_cmd.py @@ -0,0 +1,1511 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + comandi per generare le quotature + + ------------------- + begin : 2014-02-19 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.PyQt.QtGui import QIcon +from qgis.core import QgsPointXY +import math + + +from ..qad_snapper import QadSnapTypeEnum +from ..qad_arc import QadArc +from ..qad_dim import QadDimStyleAlignmentEnum, QadDimStyles, QadDimStyle, \ + QadDimTypeEnum, QadDimStyleTxtRotModeEnum +from .qad_dim_maptool import Qad_dim_maptool, Qad_dim_maptool_ModeEnum +from .qad_generic_cmd import QadCommandClass +from ..qad_msg import QadMsg +from ..qad_getpoint import QadGetPointDrawModeEnum +from ..qad_textwindow import QadInputTypeEnum, QadInputModeEnum +from .qad_entsel_cmd import QadEntSelClass +from .qad_getangle_cmd import QadGetAngleClass +from ..qad_variables import QadVariables +from .. import qad_utils +from ..qad_multi_geom import getQadGeomAt, getQadGeomPartAt +from ..qad_geom_relations import getQadGeomClosestPart, QadPerpendicularity, QadIntersections + +# ============================================================================ +# FUNZIONI GENERICHE - INIZIO +# ============================================================================ + + +# ============================================================================ +# FUNZIONI GENERICHE - FINE +# ============================================================================ + + +# Classe che gestisce il comando DIMLINEAR +class QadDIMLINEARCommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadDIMLINEARCommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "DIMLINEAR") + + def getEnglishName(self): + return "DIMLINEAR" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runDIMLINEARCommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/dimLinear.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_DIM", "Creates an horizontal or vertical linear dimension.") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + self.entSelClass = None + self.GetAngleClass = None + + self.dimPt1 = QgsPointXY() # primo punto di quotatura esplicito + self.dimPt2 = QgsPointXY() # secondo punto di quotatura esplicito + self.dimCircle = None # oggetto cerchio da quotare + + self.measure = None # misura della quota (se None viene calcolato) + self.preferredAlignment = QadDimStyleAlignmentEnum.HORIZONTAL # allineamento della linea di quota + # leggo lo stile di quotatura corrente + dimStyleName = QadVariables.get(QadMsg.translate("Environment variables", "DIMSTYLE")) + self.forcedDimLineAlignment = None # allineamento della linea di quota forzato + self.forcedDimLineRot = 0.0 # rotazione della linea di quota forzato + + _dimStyle = QadDimStyles.findDimStyle(dimStyleName) + if _dimStyle is not None: + self.dimStyle = QadDimStyle(_dimStyle) # ne faccio una copia perché può venire modificato dal comando + self.dimStyle.dimType = QadDimTypeEnum.LINEAR + else: + self.dimStyle = None + + + def __del__(self): + QadCommandClass.__del__(self) + if self.entSelClass is not None: + self.entSelClass.entity.deselectOnLayer() + del self.entSelClass + if self.GetAngleClass is not None: + del self.GetAngleClass + + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if self.step == 2: # quando si é in fase di selezione entità + return self.entSelClass.getPointMapTool(drawMode) + # quando si é in fase di richiesta rotazione + elif self.step == 6 or self.step == 7: + return self.GetAngleClass.getPointMapTool() + else: + if (self.plugIn is not None): + if self.PointMapTool is None: + self.PointMapTool = Qad_dim_maptool(self.plugIn) + return self.PointMapTool + else: + return None + + + def getCurrentContextualMenu(self): + if self.step == 2: # quando si é in fase di selezione entità + return self.entSelClass.getCurrentContextualMenu() + # quando si é in fase di richiesta rotazione + elif self.step == 6 or self.step == 7: + return self.GetAngleClass.getCurrentContextualMenu() + else: + return self.contextualMenu + + + # ============================================================================ + # addDimToLayers + # ============================================================================ + def addDimToLayers(self, linePosPt): + return self.dimStyle.addLinearDimToLayers(self.plugIn, self.dimPt1, self.dimPt2, \ + linePosPt, self.measure, self.preferredAlignment, \ + self.forcedDimLineRot) + + + # ============================================================================ + # waitForFirstPt + # ============================================================================ + def waitForFirstPt(self): + self.step = 1 + # imposto il map tool + self.getPointMapTool().setMode(Qad_dim_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_FIRST_PT) + + msg = QadMsg.translate("Command_DIM", "Specify first extension line origin or : ") + + # si appresta ad attendere un punto o enter + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(msg, \ + QadInputTypeEnum.POINT2D, \ + None, \ + "", QadInputModeEnum.NONE) + + + # ============================================================================ + # waitForSecondPt + # ============================================================================ + def waitForSecondPt(self): + self.step = 3 + # imposto il map tool + self.getPointMapTool().dimPt1 = self.dimPt1 + self.getPointMapTool().setMode(Qad_dim_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT) + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_DIM", "Specify second extension line origin: ")) + + + # ============================================================================ + # waitForEntsel + # ============================================================================ + def waitForEntsel(self, msgMapTool, msg): + if self.entSelClass is not None: + del self.entSelClass + self.step = 2 + self.entSelClass = QadEntSelClass(self.plugIn) + self.entSelClass.msg = QadMsg.translate("Command_DIM", "Select the object to dimension: ") + # scarto la selezione di punti + self.entSelClass.checkPointLayer = False + self.entSelClass.checkLineLayer = True + self.entSelClass.checkPolygonLayer = True + self.entSelClass.getPointMapTool().setSnapType(QadSnapTypeEnum.DISABLE) + self.entSelClass.run(msgMapTool, msg) + + + # ============================================================================ + # waitForDimensionLinePos + # ============================================================================ + def waitForDimensionLinePos(self): + self.step = 4 + # imposto il map tool + self.getPointMapTool().dimPt2 = self.dimPt2 + if self.getPointMapTool().dimPt1 is None: # in caso di selezione oggetto dimPt1 non era stato inizializzato + self.getPointMapTool().dimPt1 = self.dimPt1 + self.getPointMapTool().dimCircle = self.dimCircle + self.getPointMapTool().dimStyle = self.dimStyle + self.getPointMapTool().setMode(Qad_dim_maptool_ModeEnum.FIRST_SECOND_PT_KNOWN_ASK_FOR_ALIGNED_DIM_LINE_POS) + + # si appresta ad attendere un punto o una parola chiave + keyWords = QadMsg.translate("Command_DIM", "Text") + "/" + \ + QadMsg.translate("Command_DIM", "Angle") + prompt = QadMsg.translate("Command_DIM", "Specify dimension line location or [{0}]: ").format(keyWords) + englishKeyWords = "Text" + "/" + "Angle" + keyWords += "_" + englishKeyWords + + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, \ + QadInputModeEnum.NONE) + + + # ============================================================================ + # run + # ============================================================================ + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + if self.dimStyle is None: + self.showMsg(QadMsg.translate("QAD", "\nDimension style not valid.\nVerify the value of DIMSTYLE variable.\n")) + return True # fine comando + + errMsg = self.dimStyle.getInValidErrMsg() + if errMsg is not None: + self.showMsg(errMsg) + return True # fine comando + + errMsg = self.dimStyle.getNotGraphEditableErrMsg() + if errMsg is not None: + self.showMsg(errMsg) + return True # fine comando + + + # ========================================================================= + # RICHIESTA SELEZIONE ORIGINE PRIMA LINEA DI ESTENSIONE + if self.step == 0: # inizio del comando + self.waitForFirstPt() + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA ORIGINE PRIMA LINEA DI ESTENSIONE (da step = 0) + elif self.step == 1: + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + value = None # opzione di default None + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if value is None: + self.waitForEntsel(msgMapTool, msg) + else: + self.dimPt1.set(value.x(), value.y()) + self.waitForSecondPt() + + return False + + # ========================================================================= + # RISPOSTA ALLA SELEZIONE DI UN'ENTITA' (da step = 1) + elif self.step == 2: + if self.entSelClass.run(msgMapTool, msg) == True: + if self.entSelClass.entity.isInitialized(): + g = self.entSelClass.entity.getQadGeom() + res = getQadGeomClosestPart(g, self.entSelClass.point) + g = getQadGeomPartAt(g, res[2], res[3], res[4]) + objType = g.whatIs() + + if objType == "LINE" or objType == "ARC" or objType == "ELLIPSE_ARC": + self.dimPt1 = g.getStartPt() + self.dimPt2 = g.getEndPt() + elif objType == "CIRCLE": # se é cerchio + self.dimCircle = g.copy() + l = QadLine().set(self.dimCircle.center, self.entSelClass.point) + intPts = QadIntersections.infinityLineWithCircle(l, self.dimCircle) + if len(intPts) == 2: + self.dimPt1 = intPts[0] + self.dimPt2 = intPts[1] + + self.waitForDimensionLinePos() + return False + else: + if self.entSelClass.canceledByUsr == True: # fine comando + return True + self.showMsg(QadMsg.translate("QAD", "No geometries in this position.")) + self.waitForEntsel(msgMapTool, msg) + return False # continua + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA ORIGINE SECONDA LINEA DI ESTENSIONE (da step = 1) + elif self.step == 3: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if value is None: + return True + + if type(value) == QgsPointXY: # se é stato inserito il secondo punto + self.dimPt2.set(value.x(), value.y()) + self.waitForDimensionLinePos() + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DELLA POSIZIONE DELLA LINEA DI QUOTA (da step = 2 e 3) + elif self.step == 4: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("Command_DIM", "Text") or value == "Text": + prompt = QadMsg.translate("Command_DIM", "Enter dimension text <{0}>: ") + dist = qad_utils.getDistance(self.dimPt1, self.dimPt2) + self.waitForString(prompt.format(str(dist)), dist) + self.getPointMapTool().setMode(Qad_dim_maptool_ModeEnum.ASK_FOR_TEXT) + self.step = 5 + elif value == QadMsg.translate("Command_DIM", "Angle") or value == "Angle": + # si appresta ad attendere l'angolo di rotazione del testo + if self.GetAngleClass is not None: + del self.GetAngleClass + self.GetAngleClass = QadGetAngleClass(self.plugIn) + prompt = QadMsg.translate("Command_DIM", "Specify angle of dimension text <{0}>: ") + self.GetAngleClass.msg = prompt.format(str(qad_utils.toDegrees(self.dimStyle.textForcedRot))) + self.GetAngleClass.angle = self.dimStyle.textForcedRot + self.step = 6 + self.GetAngleClass.run(msgMapTool, msg) + elif type(value) == QgsPointXY: # se é stato inserito il punto di posizionamento linea quota + self.dimPt1 = self.getPointMapTool().dimPt1 + self.dimPt2 = self.getPointMapTool().dimPt2 + self.addDimToLayers(value) + return True # fine comando + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL TESTO (da step = 4) + elif self.step == 5: # dopo aver atteso una stringa si riavvia il comando + if type(msg) == unicode: + text = msg.strip() + if len(text) > 0: + self.measure = text + self.getPointMapTool().measure = self.measure + self.waitForDimensionLinePos() + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA ROTAZIONE DEL TESTO DI QUOTA (da step = 4) + elif self.step == 6: + if self.GetAngleClass.run(msgMapTool, msg) == True: + if self.GetAngleClass.angle is not None: + self.dimStyle.textRotMode = QadDimStyleTxtRotModeEnum.FORCED_ROTATION + self.dimStyle.textForcedRot = self.GetAngleClass.angle + self.waitForDimensionLinePos() + + return False + + +# QadDIMARCCommandClassStepEnum class. +# =============================================================================== +class QadDIMARCCommandClassStepEnum(): + START = 0 # deve essere = 0 perchè è l'inizio del comando + ASK_FOR_ENTSEL = 1 # richiede la selezione di un'entità + ASK_FOR_MAIN_OPTIONS = 2 # richiede di selezionare un'opzione + ASK_FOR_TEXT_VALUE = 3 # richiede il valore del testo della quota + ASK_FOR_TEXT_ROT = 4 # richiede la rotazione del testo della quota + ASK_FOR_1PT_ARC = 5 # richiede il primo punto dell'arco + ASK_FOR_2PT_ARC = 6 # richiede il secondo punto dell'arco + + +# Classe che gestisce il comando DIMARC +class QadDIMARCCommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadDIMARCCommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "DIMARC") + + def getEnglishName(self): + return "DIMARC" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runDIMARCCommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/dimArc.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_DIM", "Creates an arc length dimension.") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + self.entSelClass = None + self.GetAngleClass = None + self.dimPt1 = QgsPointXY() + self.dimPt2 = QgsPointXY() + self.dimArc = None # oggetto arco da quotare + self.dimPartialArc = QadArc() + self.leader = False # opzione disponibile solo per archi > 90 gradi + + self.measure = None # misura della quota (se None viene calcolato) + # leggo lo stile di quotatura corrente + dimStyleName = QadVariables.get(QadMsg.translate("Environment variables", "DIMSTYLE")) + self.dimStyle = QadDimStyles.findDimStyle(dimStyleName) + if self.dimStyle is not None: + self.dimStyle.dimType = QadDimTypeEnum.ARC_LENTGH + + + def __del__(self): + QadCommandClass.__del__(self) + if self.entSelClass is not None: + self.entSelClass.entity.deselectOnLayer() + del self.entSelClass + if self.GetAngleClass is not None: + del self.GetAngleClass + + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if self.step == QadDIMARCCommandClassStepEnum.ASK_FOR_ENTSEL: # quando si é in fase di selezione entità + return self.entSelClass.getPointMapTool(drawMode) + # quando si é in fase di richiesta rotazione + elif self.step == QadDIMARCCommandClassStepEnum.ASK_FOR_TEXT_ROT: + return self.GetAngleClass.getPointMapTool() + else: + if (self.plugIn is not None): + if self.PointMapTool is None: + self.PointMapTool = Qad_dim_maptool(self.plugIn) + return self.PointMapTool + else: + return None + + + def getCurrentContextualMenu(self): + if self.step == QadDIMARCCommandClassStepEnum.ASK_FOR_ENTSEL: # quando si é in fase di selezione entità + return self.entSelClass.getCurrentContextualMenu() + # quando si é in fase di richiesta rotazione + elif self.step == QadDIMARCCommandClassStepEnum.ASK_FOR_TEXT_ROT: + return self.GetAngleClass.getCurrentContextualMenu() + else: + return self.contextualMenu + + + # ============================================================================ + # setArc + # ============================================================================ + def setArc(self, entity, point): + """ + Setta self.dimArc che definisce l'arco da quotare + """ + qadGeom = entity.getQadGeom() + """ + la funzione ritorna una lista con + ( + + + + + <"a sinistra di" se il punto é alla sinista della parte con i seguenti valori: + - < 0 = sinistra (per linea, arco o arco di ellisse) o interno (per cerchi, ellissi) + - > 0 = destra (per linea, arco o arco di ellisse) o esterno (per cerchi, ellissi) + ) + """ + dummy = getQadGeomClosestPart(qadGeom, point) + # ritorna la sotto-geometria + subGeom = getQadGeomAt(qadGeom, dummy[2], dummy[3]) + + gType = subGeom.whatIs() + if gType == "POLYLINE": + subGeom = subGeom.getLinearObjectAt(dummy[4]) + gType = subGeom.whatIs() + + if gType == "ARC": + self.dimArc = subGeom + self.dimPartialArc.set(self.dimArc.center, self.dimArc.radius, self.dimArc.startAngle, self.dimArc.endAngle) + return True + else: + return False + + + # ============================================================================ + # getPartialPtOnArc + # ============================================================================ + def getPartialPtOnArc(self, pt): + """ + calcola il punto sull'arco più vicino a pt che è un punto scelto dall'utente + """ + + perpPts = QadPerpendicularity.fromPointToArc(pt, self.dimArc) + if len(perpPts) == 0: # cerco il punto più vicino a pt1 tra quello iniziale e finale + startPt = self.dimArc.getStartPt() + endPt = self.dimArc.getEndPt() + if qad_utils.getDistance(startPt, pt) <= qad_utils.getDistance(endPt, pt): + return startPt + else: + return endPt + elif len(perpPts) == 1: + return perpPts[0] + elif len(perpPts) == 2: # cerco il punto più vicino a pt1 + if qad_utils.getDistance(perpPts[0], pt) <= qad_utils.getDistance(perpPts[1], pt): + return perpPts[0] + else: + return perpPts[1] + + return None + + + # ============================================================================ + # setPartialArc + # ============================================================================ + def setPartialArc(self): + """ + Calcola l'arco parziale di dimArc che ha i punti finali in dimPt1 e dimPt2 + """ + self.dimPartialArc.setEndAngleByPt(self.dimPt1) + l1 = self.dimPartialArc.length() + self.dimPartialArc.setEndAngleByPt(self.dimPt2) + l2 = self.dimPartialArc.length() + + if l1 > l2: # se dimPt1 è più lontano di dimPt2 dal punto iniziale dell'arco + self.dimPartialArc.setEndAngleByPt(self.dimPt1) + self.dimPartialArc.setStartAngleByPt(self.dimPt2) + else: # se dimPt1 è più vicino di dimPt2 dal punto iniziale dell'arco + self.dimPartialArc.setStartAngleByPt(self.dimPt1) + + + # ============================================================================ + # addDimToLayers + # ============================================================================ + def addDimToLayers(self, linePosPt): + return self.dimStyle.addArcDimToLayers(self.plugIn, self.dimPartialArc, \ + linePosPt, self.measure, self.leader) + + + # ============================================================================ + # waitForEntsel + # ============================================================================ + def waitForEntsel(self, msgMapTool, msg): + if self.entSelClass is not None: + del self.entSelClass + self.step = QadDIMARCCommandClassStepEnum.ASK_FOR_ENTSEL + self.entSelClass = QadEntSelClass(self.plugIn) + self.entSelClass.msg = QadMsg.translate("Command_DIM", "Select arc or polyline arc segment: ") + # scarto la selezione di punti e delle quote + self.entSelClass.checkPointLayer = False + self.entSelClass.checkLineLayer = True + self.entSelClass.checkPolygonLayer = True + self.entSelClass.checkDimLayers = False + self.entSelClass.getPointMapTool().setSnapType(QadSnapTypeEnum.DISABLE) + self.entSelClass.run(msgMapTool, msg) + + + # ============================================================================ + # waitForDimensionLinePos + # ============================================================================ + def waitForDimensionLinePos(self): + self.step = QadDIMARCCommandClassStepEnum.ASK_FOR_MAIN_OPTIONS + # imposto il map tool + self.getPointMapTool().dimArc = self.dimPartialArc + self.getPointMapTool().dimStyle = self.dimStyle + self.getPointMapTool().leader = self.leader + self.getPointMapTool().setMode(Qad_dim_maptool_ModeEnum.FIRST_SECOND_PT_KNOWN_ASK_FOR_ARC_DIM_LINE_POS) + + # si appresta ad attendere un punto o una parola chiave + keyWords = QadMsg.translate("Command_DIM", "Text") + "/" + \ + QadMsg.translate("Command_DIM", "Angle") + "/" + \ + QadMsg.translate("Command_DIM", "Partial") + englishKeyWords = "Text" + "/" + "Angle" + "/" + "Partial" + + # se l'angolo dell'arco è > 90 gradi si usa anche l'opzione direttrice + if self.dimArc.totalAngle() > math.pi / 2: + if self.leader == False: + keyWords = keyWords + "/" + QadMsg.translate("Command_DIM", "Leader") + englishKeyWords = englishKeyWords + "/" + "Leader" + else: + keyWords = keyWords + "/" + QadMsg.translate("Command_DIM", "No leader") + englishKeyWords = englishKeyWords + "/" + "No leader" + + prompt = QadMsg.translate("Command_DIM", "Specify dimension location or [{0}]: ").format(keyWords) + keyWords += "_" + englishKeyWords + + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, \ + QadInputModeEnum.NONE) + + + # ============================================================================ + # waitForFirstPt + # ============================================================================ + def waitForFirstPt(self): + self.step = QadDIMARCCommandClassStepEnum.ASK_FOR_1PT_ARC + # imposto il map tool + self.getPointMapTool().setMode(Qad_dim_maptool_ModeEnum.ASK_FOR_PARTIAL_ARC_PT_FOR_DIM_ARC) + + msg = QadMsg.translate("Command_DIM", "Specify first point on the arc: ") + + # si appresta ad attendere un punto + self.waitForPoint(msg) + + + # ============================================================================ + # waitForSecondPt + # ============================================================================ + def waitForSecondPt(self): + self.step = QadDIMARCCommandClassStepEnum.ASK_FOR_2PT_ARC + # imposto il map tool + self.getPointMapTool().setMode(Qad_dim_maptool_ModeEnum.ASK_FOR_PARTIAL_ARC_PT_FOR_DIM_ARC) + + msg = QadMsg.translate("Command_DIM", "Specify second point on the arc: ") + + # si appresta ad attendere un punto + self.waitForPoint(msg) + + + # ============================================================================ + # run + # ============================================================================ + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + if self.dimStyle is None: + self.showMsg(QadMsg.translate("QAD", "\nDimension style not valid.\nVerify the value of DIMSTYLE variable.\n")) + return True # fine comando + + errMsg = self.dimStyle.getInValidErrMsg() + if errMsg is not None: + self.showMsg(errMsg) + return True # fine comando + + errMsg = self.dimStyle.getNotGraphEditableErrMsg() + if errMsg is not None: + self.showMsg(errMsg) + return True # fine comando + + if self.step == QadDIMARCCommandClassStepEnum.START: + self.waitForEntsel(msgMapTool, msg) + return False # continua + + # ========================================================================= + # RISPOSTA ALLA SELEZIONE DI UN'ENTITA' + if self.step == QadDIMARCCommandClassStepEnum.ASK_FOR_ENTSEL: + if self.entSelClass.run(msgMapTool, msg) == True: + if self.entSelClass.entity.isInitialized(): + if self.setArc(self.entSelClass.entity, self.entSelClass.point) == True: + self.waitForDimensionLinePos() + else: + self.showMsg(QadMsg.translate("Command_DIM", "Select an arc or polyline arc segment.")) + else: + if self.entSelClass.canceledByUsr == True: # fine comando + return True + self.showMsg(QadMsg.translate("QAD", "No geometries in this position.")) + self.waitForEntsel(msgMapTool, msg) + return False # continua + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DELLA POSIZIONE DELLA LINEA DI QUOTA + elif self.step == QadDIMARCCommandClassStepEnum.ASK_FOR_MAIN_OPTIONS: # dopo aver atteso un punto o una opzione si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("Command_DIM", "Text") or value == "Text": + prompt = QadMsg.translate("Command_DIM", "Enter dimension text <{0}>: ") + dist = self.dimPartialArc.length() + self.waitForString(prompt.format(str(dist)), dist) + self.getPointMapTool().setMode(Qad_dim_maptool_ModeEnum.ASK_FOR_TEXT) + self.step = QadDIMARCCommandClassStepEnum.ASK_FOR_TEXT_VALUE + elif value == QadMsg.translate("Command_DIM", "Angle") or value == "Angle": + # si appresta ad attendere l'angolo di rotazione del testo + if self.GetAngleClass is not None: + del self.GetAngleClass + self.GetAngleClass = QadGetAngleClass(self.plugIn) + prompt = QadMsg.translate("Command_DIM", "Specify angle of dimension text <{0}>: ") + self.GetAngleClass.msg = prompt.format(str(qad_utils.toDegrees(self.dimStyle.textForcedRot))) + self.GetAngleClass.angle = self.dimStyle.textForcedRot + self.step = QadDIMARCCommandClassStepEnum.ASK_FOR_TEXT_ROT + self.GetAngleClass.run(msgMapTool, msg) + elif value == QadMsg.translate("Command_DIM", "Partial") or value == "Partial": + self.waitForFirstPt() + elif value == QadMsg.translate("Command_DIM", "Leader") or value == "Leader": + self.leader = True + self.waitForDimensionLinePos() + elif value == QadMsg.translate("Command_DIM", "No leader") or value == "No leader": + self.leader = False + self.waitForDimensionLinePos() + + elif type(value) == QgsPointXY: # se é stato inserito il punto di posizionamento linea quota + self.addDimToLayers(value) + return True # fine comando + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL TESTO (da step = ASK_FOR_MAIN_OPTIONS) + elif self.step == QadDIMARCCommandClassStepEnum.ASK_FOR_TEXT_VALUE: # dopo aver atteso una stringa si riavvia il comando + if type(msg) == unicode: + text = msg.strip() + if len(text) > 0: + self.measure = text + self.getPointMapTool().measure = self.measure + self.waitForDimensionLinePos() + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA ROTAZIONE DEL TESTO DI QUOTA (da step = ASK_FOR_MAIN_OPTIONS) + elif self.step == QadDIMARCCommandClassStepEnum.ASK_FOR_TEXT_ROT: + if self.GetAngleClass.run(msgMapTool, msg) == True: + if self.GetAngleClass.angle is not None: + self.dimStyle.textRotMode = QadDimStyleTxtRotModeEnum.FORCED_ROTATION + self.dimStyle.textForcedRot = self.GetAngleClass.angle + self.waitForDimensionLinePos() + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL PRIMO PUNTO SULL'ARCO (da step = ASK_FOR_MAIN_OPTIONS) + elif self.step == QadDIMARCCommandClassStepEnum.ASK_FOR_1PT_ARC: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == QgsPointXY: # se é stato inserito il secondo punto + ptOnArc = self.getPartialPtOnArc(value) + if ptOnArc is not None: + self.dimPt1.set(ptOnArc.x(), ptOnArc.y()) + + self.waitForSecondPt() + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL PRIMO PUNTO SULL'ARCO (da step = ASK_FOR_1PT_ARC) + elif self.step == QadDIMARCCommandClassStepEnum.ASK_FOR_2PT_ARC: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == QgsPointXY: # se é stato inserito il secondo punto + ptOnArc = self.getPartialPtOnArc(value) + if ptOnArc is not None: + self.dimPt2.set(ptOnArc.x(), ptOnArc.y()) + + self.setPartialArc() + self.waitForDimensionLinePos() + + return False + + +# =============================================================================== +# QadDIMRADIUSCommandClassStepEnum class. +# =============================================================================== +class QadDIMRADIUSCommandClassStepEnum(): + START = 0 # deve essere = 0 perchè è l'inizio del comando + ASK_FOR_ENTSEL = 1 # richiede la selezione di un'entità + ASK_FOR_MAIN_OPTIONS = 2 # richiede di selezionare un'opzione + ASK_FOR_TEXT_VALUE = 3 # richiede il valore del testo della quota + ASK_FOR_TEXT_ROT = 4 # richiede la rotazione del testo della quota + + +# Classe che gestisce il comando DIMRADIUS +class QadDIMRADIUSCommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadDIMRADIUSCommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "DIMRADIUS") + + def getEnglishName(self): + return "DIMRADIUS" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runDIMRADIUSCommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/dimRadius.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_DIM", "Creates a radius dimension for a circle or an arc.") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + self.entSelClass = None + self.GetAngleClass = None + self.dimObj = None # oggetto arco o circle da quotare + + self.measure = None # misura della quota (se None viene calcolato) + # leggo lo stile di quotatura corrente + dimStyleName = QadVariables.get(QadMsg.translate("Environment variables", "DIMSTYLE")) + self.dimStyle = QadDimStyles.findDimStyle(dimStyleName) + if self.dimStyle is not None: + self.dimStyle.dimType = QadDimTypeEnum.ARC_LENTGH + + + def __del__(self): + QadCommandClass.__del__(self) + if self.entSelClass is not None: + self.entSelClass.entity.deselectOnLayer() + del self.entSelClass + if self.GetAngleClass is not None: + del self.GetAngleClass + + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if self.step == QadDIMRADIUSCommandClassStepEnum.ASK_FOR_ENTSEL: # quando si é in fase di selezione entità + return self.entSelClass.getPointMapTool(drawMode) + # quando si é in fase di richiesta rotazione + elif self.step == QadDIMRADIUSCommandClassStepEnum.ASK_FOR_TEXT_ROT: + return self.GetAngleClass.getPointMapTool() + else: + if (self.plugIn is not None): + if self.PointMapTool is None: + self.PointMapTool = Qad_dim_maptool(self.plugIn) + return self.PointMapTool + else: + return None + + + def getCurrentContextualMenu(self): + if self.step == QadDIMRADIUSCommandClassStepEnum.ASK_FOR_ENTSEL: # quando si é in fase di selezione entità + return self.entSelClass.getCurrentContextualMenu() + # quando si é in fase di richiesta rotazione + elif self.step == QadDIMRADIUSCommandClassStepEnum.ASK_FOR_TEXT_ROT: + return self.GetAngleClass.getCurrentContextualMenu() + else: + return self.contextualMenu + + + # ============================================================================ + # setArcOrCircle + # ============================================================================ + def setArcOrCircle(self, entity, point): + """ + Setta self.dimObj che definisce l'arco o il cerchio da quotare + """ + qadGeom = entity.getQadGeom() + """ + la funzione ritorna una lista con + ( + + + + + <"a sinistra di" se il punto é alla sinista della parte con i seguenti valori: + - < 0 = sinistra (per linea, arco o arco di ellisse) o interno (per cerchi, ellissi) + - > 0 = destra (per linea, arco o arco di ellisse) o esterno (per cerchi, ellissi) + ) + """ + dummy = getQadGeomClosestPart(qadGeom, point) + # ritorna la sotto-geometria + subGeom = getQadGeomAt(qadGeom, dummy[2], dummy[3]) + gType = subGeom.whatIs() + + if gType == "ARC" or gType == "CIRCLE": + self.dimObj = subGeom + return True + else: + return False + + + # ============================================================================ + # addDimToLayers + # ============================================================================ + def addDimToLayers(self, linePosPt): + return self.dimStyle.addRadiusDimToLayers(self.plugIn, self.dimObj, \ + linePosPt, self.measure) + + + # ============================================================================ + # waitForEntsel + # ============================================================================ + def waitForEntsel(self, msgMapTool, msg): + if self.entSelClass is not None: + del self.entSelClass + self.step = QadDIMRADIUSCommandClassStepEnum.ASK_FOR_ENTSEL + self.entSelClass = QadEntSelClass(self.plugIn) + self.entSelClass.msg = QadMsg.translate("Command_DIM", "Select arc or circle: ") + # scarto la selezione di punti e delle quote + self.entSelClass.checkPointLayer = False + self.entSelClass.checkLineLayer = True + self.entSelClass.checkPolygonLayer = True + self.entSelClass.checkDimLayers = False + self.entSelClass.getPointMapTool().setSnapType(QadSnapTypeEnum.DISABLE) + self.entSelClass.run(msgMapTool, msg) + + + # ============================================================================ + # waitForDimensionLinePos + # ============================================================================ + def waitForDimensionLinePos(self): + self.step = QadDIMRADIUSCommandClassStepEnum.ASK_FOR_MAIN_OPTIONS + # imposto il map tool + if self.dimObj.whatIs() == "ARC": + self.getPointMapTool().dimArc = self.dimObj + else: + self.getPointMapTool().dimCircle = self.dimObj + + self.getPointMapTool().dimStyle = self.dimStyle + self.getPointMapTool().setMode(Qad_dim_maptool_ModeEnum.OBJ_KNOWN_ASK_FOR_RADIUS_DIM_LINE_POS) + + # si appresta ad attendere un punto o una parola chiave + # si appresta ad attendere un punto o una parola chiave + keyWords = QadMsg.translate("Command_DIM", "Text") + "/" + \ + QadMsg.translate("Command_DIM", "Angle") + englishKeyWords = "Text" + "/" + "Angle" + + prompt = QadMsg.translate("Command_DIM", "Specify dimension location or [{0}]: ").format(keyWords) + keyWords += "_" + englishKeyWords + + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, \ + QadInputModeEnum.NONE) + + + # ============================================================================ + # run + # ============================================================================ + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + if self.dimStyle is None: + self.showMsg(QadMsg.translate("QAD", "\nDimension style not valid.\nVerify the value of DIMSTYLE variable.\n")) + return True # fine comando + + errMsg = self.dimStyle.getInValidErrMsg() + if errMsg is not None: + self.showMsg(errMsg) + return True # fine comando + + errMsg = self.dimStyle.getNotGraphEditableErrMsg() + if errMsg is not None: + self.showMsg(errMsg) + return True # fine comando + + if self.step == QadDIMRADIUSCommandClassStepEnum.START: + self.waitForEntsel(msgMapTool, msg) + return False # continua + + # ========================================================================= + # RISPOSTA ALLA SELEZIONE DI UN'ENTITA' + if self.step == QadDIMRADIUSCommandClassStepEnum.ASK_FOR_ENTSEL: + if self.entSelClass.run(msgMapTool, msg) == True: + if self.entSelClass.entity.isInitialized(): + if self.setArcOrCircle(self.entSelClass.entity, self.entSelClass.point) == True: + self.waitForDimensionLinePos() + else: + self.showMsg(QadMsg.translate("Command_DIM", "Select an arc or polyline arc segment.")) + else: + if self.entSelClass.canceledByUsr == True: # fine comando + return True + self.showMsg(QadMsg.translate("QAD", "No geometries in this position.")) + self.waitForEntsel(msgMapTool, msg) + return False # continua + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DELLA POSIZIONE DELLA LINEA DI QUOTA + elif self.step == QadDIMRADIUSCommandClassStepEnum.ASK_FOR_MAIN_OPTIONS: # dopo aver atteso un punto o una opzione si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("Command_DIM", "Text") or value == "Text": + prompt = QadMsg.translate("Command_DIM", "Enter dimension text <{0}>: ") + dist = self.dimObj.radius + self.waitForString(prompt.format(str(dist)), dist) + self.getPointMapTool().setMode(Qad_dim_maptool_ModeEnum.ASK_FOR_TEXT) + self.step = QadDIMRADIUSCommandClassStepEnum.ASK_FOR_TEXT_VALUE + elif value == QadMsg.translate("Command_DIM", "Angle") or value == "Angle": + # si appresta ad attendere l'angolo di rotazione del testo + if self.GetAngleClass is not None: + del self.GetAngleClass + self.GetAngleClass = QadGetAngleClass(self.plugIn) + prompt = QadMsg.translate("Command_DIM", "Specify angle of dimension text <{0}>: ") + self.GetAngleClass.msg = prompt.format(str(qad_utils.toDegrees(self.dimStyle.textForcedRot))) + self.GetAngleClass.angle = self.dimStyle.textForcedRot + self.step = QadDIMRADIUSCommandClassStepEnum.ASK_FOR_TEXT_ROT + self.GetAngleClass.run(msgMapTool, msg) + + elif type(value) == QgsPointXY: # se é stato inserito il punto di posizionamento linea quota + self.addDimToLayers(value) + return True # fine comando + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL TESTO (da step = ASK_FOR_MAIN_OPTIONS) + elif self.step == QadDIMRADIUSCommandClassStepEnum.ASK_FOR_TEXT_VALUE: # dopo aver atteso una stringa si riavvia il comando + if type(msg) == unicode: + text = msg.strip() + if len(text) > 0: + self.measure = text + self.getPointMapTool().measure = self.measure + self.waitForDimensionLinePos() + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA ROTAZIONE DEL TESTO DI QUOTA (da step = ASK_FOR_MAIN_OPTIONS) + elif self.step == QadDIMRADIUSCommandClassStepEnum.ASK_FOR_TEXT_ROT: + if self.GetAngleClass.run(msgMapTool, msg) == True: + if self.GetAngleClass.angle is not None: + self.dimStyle.textRotMode = QadDimStyleTxtRotModeEnum.FORCED_ROTATION + self.dimStyle.textForcedRot = self.GetAngleClass.angle + self.waitForDimensionLinePos() + + return False + + diff --git a/cmd/qad_dim_maptool.py b/cmd/qad_dim_maptool.py new file mode 100644 index 00000000..26b234c8 --- /dev/null +++ b/cmd/qad_dim_maptool.py @@ -0,0 +1,268 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + classe per gestire il map tool in ambito dei comandi di quotatura + + ------------------- + begin : 2013-05-22 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +import math + + +from .. import qad_utils +from ..qad_getpoint import QadGetPoint, QadGetPointDrawModeEnum +from ..qad_dim import QadDimStyleAlignmentEnum +from ..qad_rubberband import QadRubberBand + + +# =============================================================================== +# Qad_dim_maptool_ModeEnum class. +# =============================================================================== +class Qad_dim_maptool_ModeEnum(): + # noto niente si richiede il primo punto di quotatura + NONE_KNOWN_ASK_FOR_FIRST_PT = 1 + # noto il primo punto si richiede il secondo punto di quotatura + FIRST_PT_KNOWN_ASK_FOR_SECOND_PT = 2 + # noto i punti di quotatura si richiede la posizione della linea di quota lineare + FIRST_SECOND_PT_KNOWN_ASK_FOR_LINEAR_DIM_LINE_POS = 3 + # si richiede il testo di quota + ASK_FOR_TEXT = 4 + # noto i punti di quotatura si richiede la posizione della linea di quota allineata + FIRST_SECOND_PT_KNOWN_ASK_FOR_ALIGNED_DIM_LINE_POS = 5 + # si richiede un punto sull'arco per la quota arco + ASK_FOR_PARTIAL_ARC_PT_FOR_DIM_ARC = 6 + # noto i punti di quotatura si richiede la posizione della linea di quota arco + FIRST_SECOND_PT_KNOWN_ASK_FOR_ARC_DIM_LINE_POS = 7 + # noto l'oggetto da quotare (arco o cerchio) si richiede la posizione della linea di quota raggio + OBJ_KNOWN_ASK_FOR_RADIUS_DIM_LINE_POS = 8 + + +# =============================================================================== +# Qad_dim_maptool class +# =============================================================================== +class Qad_dim_maptool(QadGetPoint): + + def __init__(self, plugIn): + QadGetPoint.__init__(self, plugIn) + + dimStyle = None + self.dimPt1 = None + self.dimPt2 = None + self.dimCircle = None + + self.dimArc = None # per quotatura arco + + self.forcedTextRot = None # rotazione del testo di quota + self.measure = None # misura della quota (se None viene calcolato) + self.preferredAlignment = QadDimStyleAlignmentEnum.HORIZONTAL # allineamento della linea di quota + self.forcedDimLineAlignment = None # allineamento della linea di quota forzato + self.forcedDimLineRot = 0.0 # rotazione della linea di quota forzato + self.leader = False # per disegnare la linea direttrice nella quotatura arco + + self.__rubberBand = QadRubberBand(self.canvas) + + + def hidePointMapToolMarkers(self): + QadGetPoint.hidePointMapToolMarkers(self) + self.__rubberBand.hide() + + def showPointMapToolMarkers(self): + QadGetPoint.showPointMapToolMarkers(self) + self.__rubberBand.show() + + def clear(self): + QadGetPoint.clear(self) + self.__rubberBand.reset() + self.mode = None + + + def setDimLineAlignment(self, LinePosPt, horizLine1, horizLine2, verticalLine1, verticalLine2): + # < 0 se a sinistra della linea + sxOfHorizLine1 = True if qad_utils.leftOfLine(LinePosPt, horizLine1[0], horizLine1[1]) < 0 else False + sxOfHorizLine2 = True if qad_utils.leftOfLine(LinePosPt, horizLine2[0], horizLine2[1]) < 0 else False + + sxOfVerticalLine1 = True if qad_utils.leftOfLine(LinePosPt, verticalLine1[0], verticalLine1[1]) < 0 else False + sxOfVerticalLine2 = True if qad_utils.leftOfLine(LinePosPt, verticalLine2[0], verticalLine2[1]) < 0 else False + + # se LinePosPt é tra le linee di limite orizzontale e non é tra le linee di limite verticale + if sxOfHorizLine1 != sxOfHorizLine2 and sxOfVerticalLine1 == sxOfVerticalLine2: + self.preferredAlignment = QadDimStyleAlignmentEnum.HORIZONTAL + # se LinePosPt non é tra le linee di limite orizzontale ed é tra le linee di limite verticale + elif sxOfHorizLine1 == sxOfHorizLine2 and sxOfVerticalLine1 != sxOfVerticalLine2: + self.preferredAlignment = QadDimStyleAlignmentEnum.VERTICAL + + return + + + # ============================================================================ + # setLinearDimPtsAndDimLineAlignmentOnCircle + # ============================================================================ + def setLinearDimPtsAndDimLineAlignmentOnCircle(self, LinePosPt, circle): + pt1 = qad_utils.getPolarPointByPtAngle(circle.center, self.forcedDimLineRot, circle.radius) + pt2 = qad_utils.getPolarPointByPtAngle(pt1, self.forcedDimLineRot + math.pi / 2, circle.radius) + horizLine1 = [pt1, pt2] + + pt1 = qad_utils.getPolarPointByPtAngle(circle.center, self.forcedDimLineRot, -1 * circle.radius) + pt2 = qad_utils.getPolarPointByPtAngle(pt1, self.forcedDimLineRot + math.pi / 2, circle.radius) + horizLine2 = [pt1, pt2] + + pt1 = qad_utils.getPolarPointByPtAngle(circle.center, self.forcedDimLineRot + math.pi / 2, circle.radius) + pt2 = qad_utils.getPolarPointByPtAngle(pt1, self.forcedDimLineRot, circle.radius) + verticalLine1 = [pt1, pt2] + + pt1 = qad_utils.getPolarPointByPtAngle(circle.center, self.forcedDimLineRot + math.pi / 2, -1 * circle.radius) + pt2 = qad_utils.getPolarPointByPtAngle(pt1, self.forcedDimLineRot, circle.radius) + verticalLine2 = [pt1, pt2] + + # se non é stato impostato un allineamento forzato, lo calcolo in automatico + if self.forcedDimLineAlignment is None: + self.setDimLineAlignment(LinePosPt, horizLine1, horizLine2, verticalLine1, verticalLine2) + else: + self.preferredAlignment = self.forcedDimLineAlignment + + if self.preferredAlignment == QadDimStyleAlignmentEnum.HORIZONTAL: + self.dimPt1 = horizLine1[0] + self.dimPt2 = horizLine2[0] + else: + self.dimPt1 = verticalLine1[0] + self.dimPt2 = verticalLine2[0] + + + # ============================================================================ + # setLinearDimLineAlignmentOnDimPts + # ============================================================================ + def setLinearDimLineAlignmentOnDimPts(self, LinePosPt): + # se non é stato impostato un allineamento forzato, lo calcolo in automatico + if self.forcedDimLineAlignment is None: + pt2 = qad_utils.getPolarPointByPtAngle(self.dimPt1, self.forcedDimLineRot + math.pi / 2, 1) + horizLine1 = [self.dimPt1, pt2] + + pt2 = qad_utils.getPolarPointByPtAngle(self.dimPt2, self.forcedDimLineRot + math.pi / 2, 1) + horizLine2 = [self.dimPt2, pt2] + + pt2 = qad_utils.getPolarPointByPtAngle(self.dimPt1, self.forcedDimLineRot, 1) + verticalLine1 = [self.dimPt1, pt2] + + pt2 = qad_utils.getPolarPointByPtAngle(self.dimPt2, self.forcedDimLineRot, 1) + verticalLine2 = [self.dimPt2, pt2] + + self.setDimLineAlignment(LinePosPt, horizLine1, horizLine2, verticalLine1, verticalLine2) + else: + self.preferredAlignment = self.forcedDimLineAlignment + + + # ============================================================================ + # canvasMoveEvent + # ============================================================================ + def canvasMoveEvent(self, event): + QadGetPoint.canvasMoveEvent(self, event) + + self.__rubberBand.reset() + + dimEntity = None + + # noti i punti di quotatura si richiede la posizione della linea di quota lineare + if self.mode == Qad_dim_maptool_ModeEnum.FIRST_SECOND_PT_KNOWN_ASK_FOR_LINEAR_DIM_LINE_POS: + if self.dimCircle is not None: + self.setLinearDimPtsAndDimLineAlignmentOnCircle(self.tmpPoint, self.dimCircle) + else: + self.setLinearDimLineAlignmentOnDimPts(self.tmpPoint) + + dimEntity, textOffsetRect = self.dimStyle.getLinearDimFeatures(self.canvas, \ + self.dimPt1, \ + self.dimPt2, \ + self.tmpPoint, \ + self.measure, \ + self.preferredAlignment, \ + self.forcedDimLineRot) + # noti i punti di quotatura si richiede la posizione della linea di quota allineata + elif self.mode == Qad_dim_maptool_ModeEnum.FIRST_SECOND_PT_KNOWN_ASK_FOR_ALIGNED_DIM_LINE_POS: + dimEntity, textOffsetRect = self.dimStyle.getAlignedDimFeatures(self.canvas, \ + self.dimPt1, \ + self.dimPt2, \ + self.tmpPoint, \ + self.measure) + # noti i punti di quotatura si richiede la posizione della linea di quota arco + elif self.mode == Qad_dim_maptool_ModeEnum.FIRST_SECOND_PT_KNOWN_ASK_FOR_ARC_DIM_LINE_POS: + dimEntity, textOffsetRect = self.dimStyle.getArcDimFeatures(self.canvas, \ + self.dimArc, \ + self.tmpPoint, \ + self.measure, + self.leader) + # noto l'oggetto da quotare (arco o cerchio) si richiede la posizione della linea di quota raggio + elif self.mode == Qad_dim_maptool_ModeEnum.OBJ_KNOWN_ASK_FOR_RADIUS_DIM_LINE_POS: + dimObj = self.dimCircle if (self.dimCircle is not None) else self.dimArc + dimEntity, textOffsetRect = self.dimStyle.getRadiusDimFeatures(self.canvas, \ + dimObj, \ + self.tmpPoint, \ + self.measure) + + if dimEntity is not None: + # testo di quota + self.__rubberBand.addGeometry(dimEntity.textualFeature.geometry(), self.dimStyle.getTextualLayer()) # geom e layer + self.__rubberBand.addGeometry(textOffsetRect, self.dimStyle.getTextualLayer()) # geom e layer + for g in dimEntity.getLinearGeometryCollection(): + self.__rubberBand.addGeometry(g, self.dimStyle.getLinearLayer()) # geom e layer + for g in dimEntity.getSymbolGeometryCollection(): + self.__rubberBand.addGeometry(g, self.dimStyle.getSymbolLayer()) # geom e layer + + + def activate(self): + QadGetPoint.activate(self) + if self.__rubberBand is not None: + self.__rubberBand.show() + + def deactivate(self): + try: # necessario perché se si chiude QGIS parte questo evento nonostante non ci sia più l'oggetto maptool ! + QadGetPoint.deactivate(self) + if self.__rubberBand is not None: + self.__rubberBand.hide() + except: + pass + + def setMode(self, mode): + self.mode = mode + # noto niente si richiede il primo punto di quotatura + if self.mode == Qad_dim_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_FIRST_PT: + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + # noto il primo punto si richiede il secondo punto di quotatura + elif self.mode == Qad_dim_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.dimPt1) + # noto i punti di quotatura si richiede la posizione della linea di quota + elif self.mode == Qad_dim_maptool_ModeEnum.FIRST_SECOND_PT_KNOWN_ASK_FOR_LINEAR_DIM_LINE_POS: + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + self.setStartPoint(None) + # si richiede il testo di quota + elif self.mode == Qad_dim_maptool_ModeEnum.ASK_FOR_TEXT: + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + # noti i punti di quotatura si richiede la posizione della linea di quota allineata + elif self.mode == Qad_dim_maptool_ModeEnum.FIRST_SECOND_PT_KNOWN_ASK_FOR_ALIGNED_DIM_LINE_POS: + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + self.setStartPoint(None) + # si richiede un punto sull'arco per la quota arco + elif self.mode == Qad_dim_maptool_ModeEnum.ASK_FOR_PARTIAL_ARC_PT_FOR_DIM_ARC: + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + # noto i punti di quotatura si richiede la posizione della linea di quota + elif self.mode == Qad_dim_maptool_ModeEnum.FIRST_SECOND_PT_KNOWN_ASK_FOR_ARC_DIM_LINE_POS: + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + # noto l'oggetto da quotare (arco o cerchio) si richiede la posizione della linea di quota raggio + elif self.mode == Qad_dim_maptool_ModeEnum.OBJ_KNOWN_ASK_FOR_RADIUS_DIM_LINE_POS: + self.setDrawMode(QadGetPointDrawModeEnum.NONE) diff --git a/qad_dimstyle_cmd.py b/cmd/qad_dimstyle_cmd.py similarity index 84% rename from qad_dimstyle_cmd.py rename to cmd/qad_dimstyle_cmd.py index 2a6eed79..b9a39b55 100644 --- a/qad_dimstyle_cmd.py +++ b/cmd/qad_dimstyle_cmd.py @@ -1,72 +1,70 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - comando DSETTINGS per impostazione stili di quotatura - - ------------------- - begin : 2015-05-19 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -from qgis.core import QgsApplication - - -from qad_dimstyle_dlg import QadDIMSTYLEDialog - - -from qad_generic_cmd import QadCommandClass -from qad_msg import QadMsg - - -# Classe che gestisce il comando DIMSTYLE -class QadDIMSTYLECommandClass(QadCommandClass): - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadDIMSTYLECommandClass(self.plugIn) - - def getName(self): - return QadMsg.translate("Command_list", "DIMSTYLE") - - def getEnglishName(self): - return "DIMSTYLE" - - def connectQAction(self, action): - QObject.connect(action, SIGNAL("triggered()"), self.plugIn.runDIMSTYLECommand) - - def getIcon(self): - return QIcon(":/plugins/qad/icons/dimStyle.png") - - def getNote(self): - # impostare le note esplicative del comando - return QadMsg.translate("Command_DIMSTYLE", "Creates new styles, sets the current style, modifies styles, sets overrides on the current style, and compares styles.") - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - - def run(self, msgMapTool = False, msg = None): - if self.plugIn.canvas.mapRenderer().destinationCrs().geographicFlag(): - self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) - return True # fine comando - Form = QadDIMSTYLEDialog(self.plugIn) - Form.exec_() +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + comando DSETTINGS per impostazione stili di quotatura + + ------------------- + begin : 2015-05-19 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.PyQt.QtGui import * +from qgis.core import * + + +from ..qad_dimstyle_dlg import QadDIMSTYLEDialog + + +from .qad_generic_cmd import QadCommandClass +from ..qad_msg import QadMsg + + +# Classe che gestisce il comando DIMSTYLE +class QadDIMSTYLECommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadDIMSTYLECommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "DIMSTYLE") + + def getEnglishName(self): + return "DIMSTYLE" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runDIMSTYLECommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/dimStyle.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_DIMSTYLE", "Creates new styles, sets the current style, modifies styles, sets overrides on the current style, and compares styles.") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + Form = QadDIMSTYLEDialog(self.plugIn) + Form.exec_() return True \ No newline at end of file diff --git a/cmd/qad_divide_cmd.py b/cmd/qad_divide_cmd.py new file mode 100644 index 00000000..2a208653 --- /dev/null +++ b/cmd/qad_divide_cmd.py @@ -0,0 +1,290 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin ok + + comando DIVIDE per creare oggetti puntuali a distanza uguale lungo il perimetro o la lunghezza di un oggetto + + ------------------- + begin : 2016-09-09 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.core import QgsGeometry, QgsFeature, QgsWkbTypes, QgsVectorLayerUtils +from qgis.PyQt.QtGui import QIcon + + +from .qad_generic_cmd import QadCommandClass +from ..qad_msg import QadMsg +from .qad_entsel_cmd import QadEntSelClass +from ..qad_textwindow import QadInputTypeEnum, QadInputModeEnum +from .. import qad_utils +from .. import qad_layer +from ..qad_dim import QadDimStyles +from ..qad_multi_geom import getQadGeomAt +from ..qad_geom_relations import getQadGeomClosestPart + + +# =============================================================================== +# QadDIVIDECommandClassStepEnum class. +# =============================================================================== +class QadDIVIDECommandClassStepEnum(): + ASK_FOR_ENT = 1 # richiede la selezione di un oggetto (0 è l'inizio del comando) + ASK_FOR_ALIGNMENT = 2 # richiede l'allineamento + ASK_SEGMENT_NUMBER = 3 # richiede il numero di segmenti + + +# Classe che gestisce il comando DIVIDE +class QadDIVIDECommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadDIVIDECommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "DIVIDE") + + def getEnglishName(self): + return "DIVIDE" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runDIVIDECommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/divide.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_DIVIDE", "Creates evenly spaced punctual objects along the length or perimeter of an object.") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + self.entSelClass = None + self.objectAlignment = True + self.nSegments = 1 + + def __del__(self): + QadCommandClass.__del__(self) + if self.entSelClass is not None: + self.entSelClass.entity.deselectOnLayer() + del self.entSelClass + + + # ============================================================================ + # waitForEntsel + # ============================================================================ + def waitForEntsel(self, msgMapTool, msg): + if self.entSelClass is not None: + del self.entSelClass + self.step = QadDIVIDECommandClassStepEnum.ASK_FOR_ENT + self.entSelClass = QadEntSelClass(self.plugIn) + self.entSelClass.msg = QadMsg.translate("Command_DIVIDE", "Select object to divide: ") + # scarto la selezione di punti + self.entSelClass.checkPointLayer = False + self.entSelClass.checkLineLayer = True + self.entSelClass.checkPolygonLayer = True + self.entSelClass.checkDimLayers = False + self.entSelClass.onlyEditableLayers = False + + self.entSelClass.run(msgMapTool, msg) + + + # ============================================================================ + # waitForAlignmentObjs + # ============================================================================ + def waitForAlignmentObjs(self): + self.step = QadDIVIDECommandClassStepEnum.ASK_FOR_ALIGNMENT + + keyWords = QadMsg.translate("QAD", "Yes") + "/" + QadMsg.translate("QAD", "No") + self.defaultValue = QadMsg.translate("QAD", "Yes") + prompt = QadMsg.translate("Command_DIVIDE", "Align with object ? [{0}] <{1}>: ").format(keyWords, self.defaultValue) + + englishKeyWords = "Yes" + "/" + "No" + keyWords += "_" + englishKeyWords + + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.KEYWORDS, \ + self.defaultValue, \ + keyWords, QadInputModeEnum.NONE) + + + # ============================================================================ + # waitForSegmentNumber + # ============================================================================ + def waitForSegmentNumber(self): + self.step = QadDIVIDECommandClassStepEnum.ASK_SEGMENT_NUMBER + + # si appresta ad attendere un numero intero + msg = QadMsg.translate("Command_DIVIDE", "Enter the number of segments: ") + # msg, inputType, default, keyWords, valori positivi + self.waitFor(msg, \ + QadInputTypeEnum.INT, \ + None, \ + "", \ + QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) + + + # ============================================================================ + # addFeature + # ============================================================================ + def addFeature(self, layer, insPt, rot, openForm = True): + transformedPoint = self.mapToLayerCoordinates(layer, insPt) + g = QgsGeometry.fromPointXY(transformedPoint) + f = QgsVectorLayerUtils.createFeature(layer, g, {}, layer.createExpressionContext()) + + # se la scala dipende da un campo + scaleFldName = qad_layer.get_symbolScaleFieldName(layer) + if len(scaleFldName) > 0: + f.setAttribute(scaleFldName, 1.0) + + # se la rotazione dipende da un campo + rotFldName = qad_layer.get_symbolRotationFieldName(layer) + if len(rotFldName) > 0: + f.setAttribute(rotFldName, qad_utils.toDegrees(rot)) + + return qad_layer.addFeatureToLayer(self.plugIn, layer, f, None, True, False, openForm) + + + # ============================================================================ + # doDivide + # ============================================================================ + def doDivide(self, dstLayer): + f = self.entSelClass.entity.getFeature() + if f is None: + return + + layer = self.entSelClass.entity.layer + + qadGeom = self.entSelClass.entity.getQadGeom() + # la funzione ritorna una lista con + # ( + # + # + # + # se geometria chiusa è tipo polyline la lista contiene anche + # + # <"a sinistra di" se il punto é alla sinista della parte (< 0 -> sinistra, > 0 -> destra) + dummy = getQadGeomClosestPart(qadGeom, self.entSelClass.point) + # ritorna la sotto-geometria + pathPolyline = getQadGeomAt(qadGeom, dummy[2], dummy[3]) + distance = pathPolyline.length() / self.nSegments + + self.plugIn.beginEditCommand("Feature divided", dstLayer) + + i = 1 + distanceFromStart = distance + openForm = True if self.nSegments == 2 else False + while i < self.nSegments: + pt, rot = pathPolyline.getPointFromStart(distanceFromStart) + if self.addFeature(dstLayer, pt, rot if self.objectAlignment else 0, openForm) == False: + self.plugIn.destroyEditCommand() + return False + i = i + 1 + distanceFromStart = distanceFromStart + distance + + self.plugIn.endEditCommand() + return True + + + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + currLayer, errMsg = qad_layer.getCurrLayerEditable(self.plugIn.canvas, QgsWkbTypes.PointGeometry) + if currLayer is None: + self.showErr(errMsg) + return True # fine comando + + if qad_layer.isSymbolLayer(currLayer) == False : + errMsg = QadMsg.translate("QAD", "\nCurrent layer is not a symbol layer.") + errMsg = errMsg + QadMsg.translate("QAD", "\nA symbol layer is a vector punctual layer without label.\n") + self.showErr(errMsg) + return True # fine comando + + if len(QadDimStyles.getDimListByLayer(currLayer)) > 0: + errMsg = QadMsg.translate("QAD", "\nThe current layer belongs to a dimension style.\n") + self.showErr(errMsg) + return True # fine comando + + if self.step == 0: + self.waitForEntsel(msgMapTool, msg) + return False # continua + + + # ========================================================================= + # RISPOSTA ALLA SELEZIONE DI UN'ENTITA' (da step = 0) + elif self.step == QadDIVIDECommandClassStepEnum.ASK_FOR_ENT: + if self.entSelClass.run(msgMapTool, msg) == True: + if self.entSelClass.entity.isInitialized(): + # se il layer di destinazione è di tipo simbolo + if qad_layer.isSymbolLayer(currLayer) == True: + # se il simbolo può essere ruotato + if len(qad_layer.get_symbolRotationFieldName(currLayer)) >0: + self.waitForAlignmentObjs() + else: + self.waitForSegmentNumber() + return False + else: + if self.entSelClass.canceledByUsr == True: # fine comando + return True + self.showMsg(QadMsg.translate("QAD", "No geometries in this position.")) + self.waitForEntsel(msgMapTool, msg) + return False # continua + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DI ALLINEARE GLI OGGETTI (da step = ASK_FOR_ENT) + elif self.step == QadDIVIDECommandClassStepEnum.ASK_FOR_ALIGNMENT: # dopo aver atteso una parola chiave si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + value = self.defaultValue + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: + # la parola chiave arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("QAD", "Yes") or value == "Yes": + self.objectAlignment = True + else: + self.objectAlignment = False + + self.waitForSegmentNumber() + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL NUMERO DI SEGMENTI (da step = ASK_FOR_ALIGNMENT) + # ========================================================================= + elif self.step == QadDIVIDECommandClassStepEnum.ASK_SEGMENT_NUMBER: # dopo aver atteso un numero intero si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return False + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: + # il numero di segmenti arriva come parametro della funzione + self.nSegments = msg + self.doDivide(currLayer) + return True # fine comando + return False diff --git a/qad_dsettings_cmd.py b/cmd/qad_dsettings_cmd.py similarity index 81% rename from qad_dsettings_cmd.py rename to cmd/qad_dsettings_cmd.py index 527cda0d..05452a1f 100644 --- a/qad_dsettings_cmd.py +++ b/cmd/qad_dsettings_cmd.py @@ -1,69 +1,66 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - comando DSETTINGS per impostazione disegno - - ------------------- - begin : 2013-05-22 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -from qgis.core import QgsApplication - - -from qad_dsettings_dlg import QadDSETTINGSDialog - - -from qad_generic_cmd import QadCommandClass -from qad_msg import QadMsg - - -# Classe che gestisce il comando DSETTINGS -class QadDSETTINGSCommandClass(QadCommandClass): - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadDSETTINGSCommandClass(self.plugIn) - - def getName(self): - return QadMsg.translate("Command_list", "DSETTINGS") - - def getEnglishName(self): - return "DSETTINGS" - - def connectQAction(self, action): - QObject.connect(action, SIGNAL("triggered()"), self.plugIn.runDSETTINGSCommand) - - def getIcon(self): - return QIcon(":/plugins/qad/icons/dsettings.png") - - def getNote(self): - # impostare le note esplicative del comando - return QadMsg.translate("Command_DSETTINGS", "Drafting Settings (snaps, etc.).") - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - - def run(self, msgMapTool = False, msg = None): - Form = QadDSETTINGSDialog(self.plugIn) - Form.exec_() +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + comando DSETTINGS per impostazione disegno + + ------------------- + begin : 2013-05-22 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.PyQt.QtGui import QIcon + + +from ..qad_dsettings_dlg import QadDSETTINGSDialog + + +from .qad_generic_cmd import QadCommandClass +from ..qad_msg import QadMsg + + +# Classe che gestisce il comando DSETTINGS +class QadDSETTINGSCommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadDSETTINGSCommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "DSETTINGS") + + def getEnglishName(self): + return "DSETTINGS" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runDSETTINGSCommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/dsettings.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_DSETTINGS", "Drafting Settings (snaps, etc.).") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + + def run(self, msgMapTool = False, msg = None): + Form = QadDSETTINGSDialog(self.plugIn) + Form.exec_() return True \ No newline at end of file diff --git a/cmd/qad_dsettings_ui.py b/cmd/qad_dsettings_ui.py new file mode 100644 index 00000000..19213884 --- /dev/null +++ b/cmd/qad_dsettings_ui.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file '.\cmd\qad_dsettings.ui' +# +# Created by: PyQt5 UI code generator 5.11.3 +# +# WARNING! All changes made in this file will be lost! + diff --git a/cmd/qad_ellipse_cmd.py b/cmd/qad_ellipse_cmd.py new file mode 100644 index 00000000..f700bda7 --- /dev/null +++ b/cmd/qad_ellipse_cmd.py @@ -0,0 +1,895 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin ok + + comando ELLIPSE per disegnare una ellisse + + ------------------- + begin : 2018-05-22 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.core import QgsWkbTypes, QgsPointXY +from qgis.PyQt.QtGui import QIcon +import math + + +from ..qad_ellipse import QadEllipse +from ..qad_ellipse_arc import QadEllipseArc +from ..qad_getpoint import QadGetPointDrawModeEnum +from .qad_ellipse_maptool import Qad_ellipse_maptool, Qad_ellipse_maptool_ModeEnum +from .qad_generic_cmd import QadCommandClass +from ..qad_msg import QadMsg +from ..qad_textwindow import QadInputModeEnum, QadInputTypeEnum +from .. import qad_utils +from .. import qad_layer + + +# =============================================================================== +# QadMELLIPSECommandClassStepEnum class. +# =============================================================================== +class QadELLIPSECommandClassStepEnum(): + ASK_FOR_FIRST_FINAL_AXIS_PT = 1 # richiede il primo punto finale dell'asse (0 è l'inizio del comando) + ASK_FOR_SECOND_FINAL_AXIS_PT = 2 # richiede il secondo punto finale dell'asse + ASK_DIST_TO_OTHER_AXIS = 3 # richiede di specificare la distanza dal secondo asse + ASK_ROTATION_ROUND_MAJOR_AXIS = 4 # richiede la rotazione attorno all'asse maggiore + ASK_START_ANGLE = 5 # richiede l'angolo iniziale + ASK_END_ANGLE = 6 # richiede l'angolo finale + ASK_INCLUDED_ANGLE = 7 # richiede l'angolo incluso + ASK_START_PARAMETER = 8 # richiede l'angolo parametrico iniziale + ASK_END_PARAMETER = 9 # richiede l'angolo parametrico finale + ASK_FOR_CENTER = 10 # richiede il centro + ASK_FOR_FIRST_FOCUS = 11 # richiede il primo punto di fuoco + ASK_FOR_SECOND_FOCUS = 12 # richiede il secondo punto di fuoco + ASK_FOR_PT_ON_ELLIPSE = 13 # richiede un punto sull'ellisse + ASK_AREA = 14 # richede l'area dell'ellisse + +# Classe che gestisce il comando ELLIPSE +class QadELLIPSECommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadELLIPSECommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "ELLIPSE") + + def getEnglishName(self): + return "ELLIPSE" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runELLIPSECommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/ellipse.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_ELLIPSE", "Draws an ellipse by many methods.") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + # se questo flag = True il comando serve all'interno di un altro comando per disegnare un cerchio + # che non verrà salvato su un layer + self.virtualCmd = False + self.rubberBandBorderColor = None + self.rubberBandFillColor = None + + self.arc = False # flag che stabilisce se si vuole disegnare un arco di ellisse o un ellisse intera + self.axis1Pt1 = None # primo punto finale dell'asse + self.axis1Pt2 = None # secondo punto finale dell'asse + self.distToOtherAxis = 0.0 # distanza dall'altro asse + self.centerPt = None # punto centrale dell'ellisse + self.ellipse = QadEllipse() + self.ellipseArc = QadEllipseArc() + self.rot = 0 # rotazione intorno all'asse + self.startAngle = 0.0 # l'ellisse può essere incompleta (come l'arco per il cerchio) + self.endAngle = math.pi * 2 # A startAngle of 0 and endAngle of 2pi will produce a closed Ellipse. + self.includedAngle = 0.0 + self.focus1 = None # primo punto di fuoco + self.focus2 = None # secondo punto di fuoco + + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if (self.plugIn is not None): + if self.PointMapTool is None: + self.PointMapTool = Qad_ellipse_maptool(self.plugIn) + self.PointMapTool.setRubberBandColor(self.rubberBandBorderColor, self.rubberBandFillColor) + return self.PointMapTool + else: + return None + + + def setRubberBandColor(self, rubberBandBorderColor, rubberBandFillColor): + self.rubberBandBorderColor = rubberBandBorderColor + self.rubberBandFillColor = rubberBandFillColor + if self.PointMapTool is not None: + self.PointMapTool.setRubberBandColor(self.rubberBandBorderColor, self.rubberBandFillColor) + + + # ============================================================================ + # waitForFirstFinalAxisPt + # ============================================================================ + def waitForFirstFinalAxisPt(self): + self.step = QadELLIPSECommandClassStepEnum.ASK_FOR_FIRST_FINAL_AXIS_PT + # imposto il map tool + self.getPointMapTool().setSelectionMode(Qad_ellipse_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_FIRST_FINAL_AXIS_PT) + if self.arc == False: # si vuole disegnare un ellisse intera + keyWords = QadMsg.translate("Command_ELLIPSE", "Arc") + "/" + \ + QadMsg.translate("Command_ELLIPSE", "Center") + "/" + \ + QadMsg.translate("Command_ELLIPSE", "Foci") + prompt = QadMsg.translate("Command_ELLIPSE", "Specify axis endpoint of ellipse or [{0}]: ").format(keyWords) + englishKeyWords = "Arc" + "/" + "Center" + "/" + "Foci" + keyWords += "_" + englishKeyWords + else: # si vuole disegnare un arco di ellisse + keyWords = QadMsg.translate("Command_ELLIPSE", "Center") + "/" + \ + QadMsg.translate("Command_ELLIPSE", "Foci") + prompt = QadMsg.translate("Command_ELLIPSE", "Specify axis endpoint of elliptical arc or [{0}]: ").format(keyWords) + englishKeyWords = "Center" + "/" + "Foci" + keyWords += "_" + englishKeyWords + + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, QadInputModeEnum.NONE) + + + # ============================================================================ + # waitForSecondFinalAxisPt + # ============================================================================ + def waitForSecondFinalAxisPt(self): + self.step = QadELLIPSECommandClassStepEnum.ASK_FOR_SECOND_FINAL_AXIS_PT + # imposto il map tool + self.getPointMapTool().axis1Pt1 = self.axis1Pt1 + self.getPointMapTool().centerPt = self.centerPt + self.getPointMapTool().setMode(Qad_ellipse_maptool_ModeEnum.FIRST_FINAL_AXIS_PT_KNOWN_ASK_FOR_SECOND_FINAL_AXIS_PT) + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_ELLIPSE", "Specify other endpoint of axis: ")) + + + # ============================================================================ + # waitForDistanceToOtherAxis + # ============================================================================ + def waitForDistanceToOtherAxis(self): + self.step = QadELLIPSECommandClassStepEnum.ASK_DIST_TO_OTHER_AXIS + # imposto il map tool + if self.getPointMapTool().axis1Pt1 is None: # si è partiti dal centro dell'ellisse' + self.getPointMapTool().axis1Pt1 = self.axis1Pt1 + else: + self.getPointMapTool().centerPt = self.centerPt + self.getPointMapTool().axis1Pt2 = self.axis1Pt2 + self.getPointMapTool().setMode(Qad_ellipse_maptool_ModeEnum.ASK_FOR_DIST_TO_OTHER_AXIS) + keyWords = QadMsg.translate("Command_ELLIPSE", "Rotation") + "/" + \ + QadMsg.translate("Command_ELLIPSE", "Area") + prompt = QadMsg.translate("Command_ELLIPSE", "Specify distance to other axis or [{0}]: ").format(keyWords) + + englishKeyWords = "Rotation" + "/" + "Area" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto, un numero reale o una parola chiave + # msg, inputType, default, keyWords, valore non nullo + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS | QadInputTypeEnum.FLOAT, \ + None, \ + keyWords, QadInputModeEnum.NOT_NULL) + + + # ============================================================================ + # waitForRotationAroundMajorAxis + # ============================================================================ + def waitForRotationAroundMajorAxis(self): + self.step = QadELLIPSECommandClassStepEnum.ASK_ROTATION_ROUND_MAJOR_AXIS + # imposto il map tool + self.getPointMapTool().axis1Pt2 = self.axis1Pt2 + self.getPointMapTool().centerPt = self.centerPt + self.getPointMapTool().setMode(Qad_ellipse_maptool_ModeEnum.ASK_ROTATION_ROUND_MAJOR_AXIS) + + # si appresta ad attendere un punto o un angolo + # msg, inputType, default, keyWords, valore non nullo + self.waitFor(QadMsg.translate("Command_ELLIPSE", "Specify rotation around major axis: "), \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.ANGLE, \ + None, \ + "", QadInputModeEnum.NOT_NULL) + + + # ============================================================================ + # waitForArea + # ============================================================================ + def waitArea(self): + self.step = QadELLIPSECommandClassStepEnum.ASK_AREA + # imposto il map tool + self.getPointMapTool().axis1Pt2 = self.axis1Pt2 + self.getPointMapTool().centerPt = self.centerPt + self.getPointMapTool().setMode(Qad_ellipse_maptool_ModeEnum.ASK_AREA) + + # si appresta ad attendere un valore o un angolo + # msg, inputType, default, keyWords, valore non nullo + self.waitForFloat(QadMsg.translate("Command_ELLIPSE", "Specify ellipse area: "), \ + None, \ + QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) + + + # ============================================================================ + # waitForStartAngle + # ============================================================================ + def waitForStartAngle(self): + self.step = QadELLIPSECommandClassStepEnum.ASK_START_ANGLE + # imposto il map tool + self.getPointMapTool().ellipse = self.ellipse + self.getPointMapTool().setMode(Qad_ellipse_maptool_ModeEnum.ASK_START_ANGLE) + + keyWords = QadMsg.translate("Command_ELLIPSE", "Parameter") + prompt = QadMsg.translate("Command_ELLIPSE", "Specify start angle or [{0}]: ").format(keyWords) + + englishKeyWords = "Parameter" + keyWords += "_" + englishKeyWords + + # si appresta ad attendere un punto o un angolo o una parola chiave + # msg, inputType, default, keyWords, valore non nullo + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS | QadInputTypeEnum.ANGLE, \ + None, \ + keyWords, QadInputModeEnum.NOT_NULL) + + + # ============================================================================ + # waitForEndAngle + # ============================================================================ + def waitForEndAngle(self): + self.step = QadELLIPSECommandClassStepEnum.ASK_END_ANGLE + # imposto il map tool + self.getPointMapTool().startAngle = self.startAngle + self.getPointMapTool().setMode(Qad_ellipse_maptool_ModeEnum.ASK_END_ANGLE) + + keyWords = QadMsg.translate("Command_ELLIPSE", "Parameter") + "/" + \ + QadMsg.translate("Command_ELLIPSE", "Included angle") + prompt = QadMsg.translate("Command_ELLIPSE", "Specify end angle or [{0}]: ").format(keyWords) + englishKeyWords = "Parameter" + "/" + "Included angle" + keyWords += "_" + englishKeyWords + + # si appresta ad attendere un punto o un angolo o una parola chiave + # msg, inputType, default, keyWords, valore non nullo + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS | QadInputTypeEnum.ANGLE, \ + None, \ + keyWords, QadInputModeEnum.NOT_NULL) + + + # ============================================================================ + # waitForIncludedAngle + # ============================================================================ + def waitForIncludedAngle(self): + self.step = QadELLIPSECommandClassStepEnum.ASK_INCLUDED_ANGLE + # imposto il map tool + self.getPointMapTool().startAngle = self.startAngle + self.getPointMapTool().setMode(Qad_ellipse_maptool_ModeEnum.ASK_INCLUDED_ANGLE) + + # si appresta ad attendere un punto o un angolo + # msg, inputType, default, keyWords, valore non nullo + self.waitFor(QadMsg.translate("Command_ELLIPSE", "Specify included angle for arc: "), \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.ANGLE, \ + None, \ + "", QadInputModeEnum.NOT_NULL) + + + # ============================================================================ + # waitForStartParameter + # ============================================================================ + def waitForStartParameter(self): + self.step = QadELLIPSECommandClassStepEnum.ASK_START_PARAMETER + # imposto il map tool + self.getPointMapTool().setMode(Qad_ellipse_maptool_ModeEnum.ASK_START_PARAMETER) + + keyWords = QadMsg.translate("Command_ELLIPSE", "Angle") + prompt = QadMsg.translate("Command_ELLIPSE", "Specify start parameter [{0}]: ").format(keyWords) + englishKeyWords = "Angle" + keyWords += "_" + englishKeyWords + + # si appresta ad attendere un punto o un angolo o una parola chiave + # msg, inputType, default, keyWords, valore non nullo + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS | QadInputTypeEnum.ANGLE, \ + None, \ + keyWords, QadInputModeEnum.NOT_NULL) + + + # ============================================================================ + # waitForEndParameter + # ============================================================================ + def waitForEndParameter(self): + self.step = QadELLIPSECommandClassStepEnum.ASK_END_PARAMETER + # imposto il map tool + self.getPointMapTool().startAngle = self.startAngle + self.getPointMapTool().setMode(Qad_ellipse_maptool_ModeEnum.ASK_END_PARAMETER) + + keyWords = QadMsg.translate("Command_ELLIPSE", "Angle") + "/" + \ + QadMsg.translate("Command_ELLIPSE", "Included angle") + prompt = QadMsg.translate("Command_ELLIPSE", "Specify end parameter or [{0}]: ").format(keyWords) + englishKeyWords = "Angle" + "/" + "Included angle" + keyWords += "_" + englishKeyWords + + # si appresta ad attendere un punto o un angolo o una parola chiave + # msg, inputType, default, keyWords, valore non nullo + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS | QadInputTypeEnum.ANGLE, \ + None, \ + keyWords, QadInputModeEnum.NOT_NULL) + + + # ============================================================================ + # waitForCenter + # ============================================================================ + def waitForCenter(self): + self.step = QadELLIPSECommandClassStepEnum.ASK_FOR_CENTER + # imposto il map tool + self.getPointMapTool().setMode(Qad_ellipse_maptool_ModeEnum.ASK_FOR_CENTER) + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_ELLIPSE", "Specify center of ellipse: ")) + + + # ============================================================================ + # waitForFirstFocus + # ============================================================================ + def waitForFirstFocus(self): + self.step = QadELLIPSECommandClassStepEnum.ASK_FOR_FIRST_FOCUS + # imposto il map tool + self.getPointMapTool().setMode(Qad_ellipse_maptool_ModeEnum.ASK_FOR_FIRST_FOCUS) + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_ELLIPSE", "Specify first focus point of ellipse: ")) + + + # ============================================================================ + # waitForSecondFocus + # ============================================================================ + def waitForSecondFocus(self): + self.step = QadELLIPSECommandClassStepEnum.ASK_FOR_SECOND_FOCUS + # imposto il map tool + self.getPointMapTool().focus1 = self.focus1 + self.getPointMapTool().setMode(Qad_ellipse_maptool_ModeEnum.ASK_FOR_SECOND_FOCUS) + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_ELLIPSE", "Specify second focus point of ellipse: ")) + + + # ============================================================================ + # waitForPtOnEllipse + # ============================================================================ + def waitForPtOnEllipse(self): + self.step = QadELLIPSECommandClassStepEnum.ASK_FOR_PT_ON_ELLIPSE + # imposto il map tool + self.getPointMapTool().focus2 = self.focus2 + self.getPointMapTool().setMode(Qad_ellipse_maptool_ModeEnum.ASK_FOR_PT_ON_ELLIPSE) + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_ELLIPSE", "Specify a point on ellipse: ")) + + + def run(self, msgMapTool = False, msg = None): + self.isValidPreviousInput = True # per gestire il comando anche in macro + + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + currLayer = None + if self.virtualCmd == False: # se si vuole veramente salvare l'ellisse in un layer + if self.arc == True: + # il layer corrente deve essere editabile e di tipo linea + currLayer, errMsg = qad_layer.getCurrLayerEditable(self.plugIn.canvas, [QgsWkbTypes.LineGeometry]) + if currLayer is None: + self.showErr(errMsg) + return True # fine comando + else: + # il layer corrente deve essere editabile e di tipo linea o poligono + currLayer, errMsg = qad_layer.getCurrLayerEditable(self.plugIn.canvas, [QgsWkbTypes.LineGeometry, QgsWkbTypes.PolygonGeometry]) + if currLayer is None: + self.showErr(errMsg) + return True # fine comando + self.getPointMapTool().geomType = QgsWkbTypes.LineGeometry if currLayer.geometryType() == QgsWkbTypes.LineGeometry else QgsWkbTypes.PolygonGeometry + + if self.step == 0: + self.waitForFirstFinalAxisPt() + return False # continua + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL PRIMO PUNTO FINALE DELL'ASSE (da step = 0) + elif self.step == QadELLIPSECommandClassStepEnum.ASK_FOR_FIRST_FINAL_AXIS_PT: # dopo aver atteso un punto o enter o una parola chiave si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi é stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if value is None: + if self.plugIn.lastPoint is not None: + value = self.plugIn.lastPoint + else: + return True # fine comando + + if type(value) == unicode: + if value == QadMsg.translate("Command_ELLIPSE", "Arc") or value == "Arc": + self.arc = True + self.waitForFirstFinalAxisPt() + elif value == QadMsg.translate("Command_ELLIPSE", "Center") or value == "Center": + self.waitForCenter() + elif value == QadMsg.translate("Command_ELLIPSE", "Foci") or value == "Foci": + self.waitForFirstFocus() + elif type(value) == QgsPointXY: # se é stato inserito il primo punto finale dell'asse + self.axis1Pt1 = value + self.plugIn.setLastPoint(value) + self.waitForSecondFinalAxisPt() + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL SECONDO PUNTO FINALE DELL'ASSE (da step = ASK_FOR_FIRST_FINAL_AXIS_PT) + elif self.step == QadELLIPSECommandClassStepEnum.ASK_FOR_SECOND_FINAL_AXIS_PT: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi é stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == QgsPointXY: # se é stato inserito il secondo punto finale dell'asse + self.axis1Pt2 = value + if self.centerPt is None: # non è noto il centro + self.centerPt = qad_utils.getMiddlePoint(self.axis1Pt1, self.axis1Pt2) + else: # non è noto il primo punto dell'asse -> self.axis1Pt1 + axis1Len = qad_utils.getDistance(self.centerPt, self.axis1Pt2) + angle = qad_utils.getAngleBy2Pts(self.axis1Pt2, self.centerPt) + self.axis1Pt1 = qad_utils.getPolarPointByPtAngle(self.centerPt, angle, axis1Len) + + self.plugIn.setLastPoint(value) + self.waitForDistanceToOtherAxis() + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DELLA DISTANZA DALL'ALTRO ASSE (da step = ASK_FOR_SECOND_FINAL_AXIS_PT) + elif self.step == QadELLIPSECommandClassStepEnum.ASK_DIST_TO_OTHER_AXIS: # dopo aver atteso un punto o enter o una parola chiave si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi é stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("Command_ELLIPSE", "Rotation") or value == "Rotation": + self.waitForRotationAroundMajorAxis() + elif value == QadMsg.translate("Command_ELLIPSE", "Area") or value == "Area": + self.waitArea() + elif type(value) == QgsPointXY or type(value) == float: + if type(value) == QgsPointXY: # se é stato inserito il primo punto finale dell'asse + self.distToOtherAxis = qad_utils.getDistance(self.centerPt, value) + else: # se é stato inserito un numero reale + self.distToOtherAxis = value + + if self.ellipse.fromAxis1FinalPtsAxis2Len(self.axis1Pt2, self.axis1Pt1, self.distToOtherAxis) is not None: + if self.arc == False: # se si vuole disegnare un'ellisse intera + geom = self.ellipse.asGeom(currLayer.wkbType()) + if geom is not None: + if self.virtualCmd == False: # se si vuole veramente salvare il cerchio in un layer + qad_layer.addGeomToLayer(self.plugIn, currLayer, self.mapToLayerCoordinates(currLayer, geom)) + return True # fine comando + else: # se si vuole disegnare un arco di ellisse + self.waitForStartAngle() + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DELLA ROTAZIONE INTORNO ALL'ASSE MAGGIORE (da step = ASK_FOR_SECOND_FINAL_AXIS_PT) + elif self.step == QadELLIPSECommandClassStepEnum.ASK_ROTATION_ROUND_MAJOR_AXIS: # dopo aver atteso un punto o un angolo si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi é stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == QgsPointXY or type(value) == float: + if type(value) == QgsPointXY: # se é stato inserito un punto + angle = qad_utils.getAngleBy2Pts(self.centerPt, value) + else: # se é stato inserito un numero reale + angle = value + self.distToOtherAxis = math.fabs(qad_utils.getDistance(self.axis1Pt1, self.axis1Pt2) / 2 * math.cos(angle)) + + if self.ellipse.fromAxis1FinalPtsAxis2Len(self.axis1Pt2, self.axis1Pt1, self.distToOtherAxis) is not None: + if self.arc == False: # se si vuole disegnare un'ellisse intera + geom = self.ellipse.asGeom(currLayer.wkbType()) + if geom is not None: + if self.virtualCmd == False: # se si vuole veramente salvare il cerchio in un layer + qad_layer.addGeomToLayer(self.plugIn, currLayer, self.mapToLayerCoordinates(currLayer, geom)) + return True # fine comando + else: # se si vuole disegnare un arco di ellisse + self.waitForStartAngle() + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DELL'AREA DELL'ELLISSE (da step = ASK_FOR_SECOND_FINAL_AXIS_PT) + elif self.step == QadELLIPSECommandClassStepEnum.ASK_AREA: # dopo aver atteso un numeroi riavvia il comando + if msgMapTool == True: # il valore arriva da una selezione grafica + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: # il punto arriva come parametro della funzione + value = msg + + if self.ellipse.fromAxis1FinalPtsArea(self.axis1Pt2, self.axis1Pt1, value) is not None: + if self.arc == False: # se si vuole disegnare un'ellisse intera + geom = self.ellipse.asGeom(currLayer.wkbType()) + if geom is not None: + if self.virtualCmd == False: # se si vuole veramente salvare il cerchio in un layer + qad_layer.addGeomToLayer(self.plugIn, currLayer, self.mapToLayerCoordinates(currLayer, geom)) + return True # fine comando + else: # se si vuole disegnare un arco di ellisse + self.waitForStartAngle() + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DELL'ANGOLO INIZIALE DELL'ARCO DI ELLISSE + # (da step = ASK_DIST_TO_OTHER_AXIS oppure ) + elif self.step == QadELLIPSECommandClassStepEnum.ASK_START_ANGLE: # dopo aver atteso un punto o un angolo si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi é stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == QgsPointXY or type(value) == float: + if type(value) == QgsPointXY: # se é stato inserito il primo punto finale dell'asse + ellipseAngle = qad_utils.getAngleBy2Pts(self.ellipse.center, self.ellipse.majorAxisFinalPt) + self.startAngle = qad_utils.getAngleBy2Pts(self.ellipse.center, value) - ellipseAngle + else: # se é stato inserito un numero reale + self.startAngle = value + + self.waitForEndAngle() + elif type(value) == unicode: + if value == QadMsg.translate("Command_ELLIPSE", "Parameter") or value == "Parameter": + self.waitForStartParameter() + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DELL'ANGOLO FINALE DELL'ARCO DI ELLISSE (da step = ASK_START_ANGLE) + elif self.step == QadELLIPSECommandClassStepEnum.ASK_END_ANGLE: # dopo aver atteso un punto o un angolo si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi é stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == QgsPointXY or type(value) == float: + if type(value) == QgsPointXY: # se é stato inserito il primo punto finale dell'asse + ellipseAngle = qad_utils.getAngleBy2Pts(self.ellipse.center, self.ellipse.majorAxisFinalPt) + self.endAngle = qad_utils.getAngleBy2Pts(self.ellipse.center, value) - ellipseAngle + else: # se é stato inserito un numero reale + self.endAngle = value + + self.ellipseArc.set(self.ellipse.center, self.ellipse.majorAxisFinalPt, self.ellipse.axisRatio, self.startAngle, self.endAngle) + geom = self.ellipseArc.asGeom(currLayer.wkbType()) + if geom is not None: + if self.virtualCmd == False: # se si vuole veramente salvare il cerchio in un layer + qad_layer.addGeomToLayer(self.plugIn, currLayer, self.mapToLayerCoordinates(currLayer, geom)) + return True # fine comando + + elif type(value) == unicode: + if value == QadMsg.translate("Command_ELLIPSE", "Parameter") or value == "Parameter": + self.waitForEndParameter() + elif value == QadMsg.translate("Command_ELLIPSE", "Included angle") or value == "Included angle": + self.waitForIncludedAngle() + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DELL'ANGOLO INCLUSO (da step = ASK_END_ANGLE) + elif self.step == QadELLIPSECommandClassStepEnum.ASK_INCLUDED_ANGLE: # dopo aver atteso un punto o un angolo si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi é stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == QgsPointXY or type(value) == float: + if type(value) == QgsPointXY: # se é stato inserito un punto + self.endAngle = self.startAngle + qad_utils.getAngleBy2Pts(self.ellipse.center, value) + else: # se é stato inserito un numero reale + self.endAngle = self.startAngle + value + + self.ellipseArc.set(self.ellipse.center, self.ellipse.majorAxisFinalPt, self.ellipse.axisRatio, self.startAngle, self.endAngle) + geom = self.ellipseArc.asGeom(currLayer.wkbType()) + if geom is not None: + if self.virtualCmd == False: # se si vuole veramente salvare il cerchio in un layer + qad_layer.addGeomToLayer(self.plugIn, currLayer, self.mapToLayerCoordinates(currLayer, geom)) + return True # fine comando + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DELL'ANGOLO PARAMETRICO INIZIALE DELL'ARCO DI ELLISSE + # (da step = ASK_START_ANGLE oppure ) + elif self.step == QadELLIPSECommandClassStepEnum.ASK_START_PARAMETER: # dopo aver atteso un punto o un angolo si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi é stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == QgsPointXY or type(value) == float: + if type(value) == QgsPointXY: # se é stato inserito il primo punto finale dell'asse + ellipseAngle = qad_utils.getAngleBy2Pts(self.ellipse.center, self.ellipse.majorAxisFinalPt) + self.startAngle = self.ellipse.getAngleFromParam(qad_utils.getAngleBy2Pts(self.ellipse.center, value) - ellipseAngle) + else: # se é stato inserito un numero reale + self.startAngle = self.ellipse.getAngleFromParam(value) + + self.waitForEndParameter() + elif type(value) == unicode: + if value == QadMsg.translate("Command_ELLIPSE", "Angle") or value == "Angle": + self.waitForStartAngle() + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DELL'ANGOLO PARAMETRICO FINALE DELL'ARCO DI ELLISSE (da step = ASK_START_PARAMETER) + elif self.step == QadELLIPSECommandClassStepEnum.ASK_END_PARAMETER: # dopo aver atteso un punto o un angolo si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi é stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == QgsPointXY or type(value) == float: + if type(value) == QgsPointXY: # se é stato inserito il primo punto finale dell'asse + ellipseAngle = qad_utils.getAngleBy2Pts(self.ellipse.center, self.ellipse.majorAxisFinalPt) + self.endAngle = self.ellipse.getAngleFromParam(qad_utils.getAngleBy2Pts(self.ellipse.center, value) - ellipseAngle) + else: # se é stato inserito un numero reale + self.endAngle = self.ellipse.getAngleFromParam(value) + + self.ellipseArc.set(self.ellipse.center, self.ellipse.majorAxisFinalPt, self.ellipse.axisRatio, self.startAngle, self.endAngle) + geom = self.ellipseArc.asGeom(currLayer.wkbType()) + if geom is not None: + if self.virtualCmd == False: # se si vuole veramente salvare il cerchio in un layer + qad_layer.addGeomToLayer(self.plugIn, currLayer, self.mapToLayerCoordinates(currLayer, geom)) + return True # fine comando + + elif type(value) == unicode: + if value == QadMsg.translate("Command_ELLIPSE", "Angle") or value == "Angle": + self.waitForEndAngle() + elif value == QadMsg.translate("Command_ELLIPSE", "Included angle") or value == "Included angle": + self.waitForIncludedAngle() + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL CENTRO DELL'ELLISSE (da step = ASK_FOR_FIRST_FINAL_AXIS_PT) + elif self.step == QadELLIPSECommandClassStepEnum.ASK_FOR_CENTER: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi é stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == QgsPointXY: # se é stato inserito il centro + self.centerPt = value + self.axis1Pt1 = None + self.plugIn.setLastPoint(value) + self.waitForSecondFinalAxisPt() + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL PRIMO PUNTO DI FUOCO DELL'ELLISSE (da step = ASK_FOR_FIRST_FINAL_AXIS_PT) + elif self.step == QadELLIPSECommandClassStepEnum.ASK_FOR_FIRST_FOCUS: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi é stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == QgsPointXY: # se é stato inserito il punto di fuoco + self.focus1 = value + self.plugIn.setLastPoint(value) + self.waitForSecondFocus() + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL SECONDO PUNTO DI FUOCO DELL'ELLISSE (da step = ASK_FOR_FIRST_FOCUS) + elif self.step == QadELLIPSECommandClassStepEnum.ASK_FOR_SECOND_FOCUS: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi é stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == QgsPointXY: # se é stato inserito il punto di fuoco + self.focus2 = value + self.plugIn.setLastPoint(value) + self.waitForPtOnEllipse() + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DI UN PUNTO SULL'ELLISSE (da step = ASK_FOR_SECOND_FOCUS) + elif self.step == QadELLIPSECommandClassStepEnum.ASK_FOR_PT_ON_ELLIPSE: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi é stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == QgsPointXY: # se é stato inserito il punto di fuoco + self.plugIn.setLastPoint(value) + + if self.ellipse.fromFoci(self.focus1, self.focus2, value) is not None: + if self.arc == False: # se si vuole disegnare un'ellisse intera + geom = self.ellipse.asGeom(currLayer.wkbType()) + if geom is not None: + if self.virtualCmd == False: # se si vuole veramente salvare il cerchio in un layer + qad_layer.addGeomToLayer(self.plugIn, currLayer, self.mapToLayerCoordinates(currLayer, geom)) + return True # fine comando + else: # se si vuole disegnare un arco di ellisse + self.waitForStartAngle() + + return False + + + return True + + diff --git a/cmd/qad_ellipse_maptool.py b/cmd/qad_ellipse_maptool.py new file mode 100644 index 00000000..1ca7d8e8 --- /dev/null +++ b/cmd/qad_ellipse_maptool.py @@ -0,0 +1,239 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin ok + + classe per gestire il map tool di richiesta di un punto in ambito del comando ellisse + + ------------------- + begin : 2018-05-22 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from qgis.PyQt.QtCore import * +from qgis.PyQt.QtGui import * +from qgis.core import QgsWkbTypes +import math + + +from .. import qad_utils +from ..qad_getpoint import QadGetPoint, QadGetPointDrawModeEnum +from ..qad_ellipse import QadEllipse +from ..qad_ellipse_arc import QadEllipseArc +from ..qad_rubberband import QadRubberBand + + +# =============================================================================== +# Qad_ellipse_maptool_ModeEnum class. +# =============================================================================== +class Qad_ellipse_maptool_ModeEnum(): + # noto niente si richiede il primo punto finale dell'asse + NONE_KNOWN_ASK_FOR_FIRST_FINAL_AXIS_PT = 1 + # noto il primo punto finale dell'asse si richiede il secondo punto finale dell'asse + FIRST_FINAL_AXIS_PT_KNOWN_ASK_FOR_SECOND_FINAL_AXIS_PT = 2 + # si richiede di specificare la distanza dal secondo asse + ASK_FOR_DIST_TO_OTHER_AXIS = 3 + # richiede la rotazione attorno all'asse maggiore + ASK_ROTATION_ROUND_MAJOR_AXIS = 4 + # richiede l'angolo iniziale + ASK_START_ANGLE = 5 + # richiede l'angolo finale + ASK_END_ANGLE = 6 + # richiede l'angolo incluso + ASK_INCLUDED_ANGLE = 7 + # richiede l'angolo parametrico iniziale + ASK_START_PARAMETER = 8 + # richiede l'angolo parametrico finale + ASK_END_PARAMETER = 9 + # richiede il centro + ASK_FOR_CENTER = 10 + # richiede il primo punto di fuoco + ASK_FOR_FIRST_FOCUS = 11 + # richiede il secondo punto di fuoco + ASK_FOR_SECOND_FOCUS = 12 + # richiede un punto sull'ellisse + ASK_FOR_PT_ON_ELLIPSE = 13 + # richede l'area dell'ellisse + ASK_AREA = 14 + + +# =============================================================================== +# Qad_ellipse_maptool class +# =============================================================================== +class Qad_ellipse_maptool(QadGetPoint): + + def __init__(self, plugIn): + QadGetPoint.__init__(self, plugIn) + + self.axis1Pt1 = None # primo punto finale dell'asse + self.axis1Pt2 = None # secondo punto finale dell'asse + self.distToOtherAxis = 0.0 # distanza dall'altro asse + self.rot = 0 # rotazione intorno all'asse + self.centerPt = None # punto centrale dell'ellisse + self.ellipse = None + self.ellipseArc = QadEllipseArc() + self.startAngle = 0.0 # l'ellisse può essere incompleta (come l'arco per il cerchio) + self.endAngle = math.pi * 2 # A startAngle of 0 and endAngle of 2pi will produce a closed Ellipse. + self.includedAngle = 0.0 + self.focus1 = None # primo punto di fuoco + self.focus2 = None # secondo punto di fuoco + + self.__rubberBand = QadRubberBand(self.canvas, False) + self.geomType = QgsWkbTypes.PolygonGeometry + self.mode = None + + + def setRubberBandColor(self, rubberBandBorderColor, rubberBandFillColor): + if rubberBandBorderColor is not None: + self.__rubberBand.setBorderColor(rubberBandBorderColor) + if rubberBandFillColor is not None: + self.__rubberBand.setFillColor(rubberBandFillColor) + + def hidePointMapToolMarkers(self): + QadGetPoint.hidePointMapToolMarkers(self) + self.__rubberBand.hide() + + def showPointMapToolMarkers(self): + QadGetPoint.showPointMapToolMarkers(self) + self.__rubberBand.show() + + def clear(self): + QadGetPoint.clear(self) + self.__rubberBand.reset() + self.mode = None + + + def canvasMoveEvent(self, event): + QadGetPoint.canvasMoveEvent(self, event) + + self.__rubberBand.reset() + + ellipse = None + + # noto il centro dell'ellisse, richiede di specificare la distanza dal secondo asse + if self.mode == Qad_ellipse_maptool_ModeEnum.ASK_FOR_DIST_TO_OTHER_AXIS: + dist = qad_utils.getDistance(self.centerPt, self.tmpPoint) + ellipse = QadEllipse().fromAxis1FinalPtsAxis2Len(self.axis1Pt2, self.axis1Pt1, dist) + # noto il centro dell'ellisse, richiede la rotazione attorno all'asse maggiore + elif self.mode == Qad_ellipse_maptool_ModeEnum.ASK_ROTATION_ROUND_MAJOR_AXIS: + angle = qad_utils.getAngleBy2Pts(self.centerPt, self.tmpPoint) + dist = math.fabs(qad_utils.getDistance(self.axis1Pt1, self.axis1Pt2) / 2 * math.cos(angle)) + ellipse = QadEllipse().fromAxis1FinalPtsAxis2Len(self.axis1Pt2, self.axis1Pt1, dist) + # nota l'ellisse, richiede l'angolo iniziale + elif self.mode == Qad_ellipse_maptool_ModeEnum.ASK_START_ANGLE: + ellipse = self.ellipse + # nota l'ellisse, richiede l'angolo finale + elif self.mode == Qad_ellipse_maptool_ModeEnum.ASK_END_ANGLE: + ellipseAngle = qad_utils.getAngleBy2Pts(self.ellipse.center, self.ellipse.majorAxisFinalPt) + self.endAngle = qad_utils.getAngleBy2Pts(self.ellipse.center, self.tmpPoint) - ellipseAngle + self.ellipseArc.set(self.ellipse.center, self.ellipse.majorAxisFinalPt, self.ellipse.axisRatio, self.startAngle, self.endAngle) + ellipse = self.ellipseArc + # nota l'ellisse, richiede l'angolo incluso + elif self.mode == Qad_ellipse_maptool_ModeEnum.ASK_INCLUDED_ANGLE: + includedAngle = qad_utils.getAngleBy2Pts(self.ellipse.center, self.tmpPoint) + self.endAngle = self.startAngle + includedAngle + self.ellipseArc.set(self.ellipse.center, self.ellipse.majorAxisFinalPt, self.ellipse.axisRatio, self.startAngle, self.endAngle) + ellipse = self.ellipseArc + # nota l'ellisse, richiede l'angolo parametrico iniziale + elif self.mode == Qad_ellipse_maptool_ModeEnum.ASK_START_PARAMETER: + ellipse = self.ellipse + # nota l'ellisse, richiede l'angolo parametrico finale + elif self.mode == Qad_ellipse_maptool_ModeEnum.ASK_END_PARAMETER: + ellipseAngle = qad_utils.getAngleBy2Pts(self.ellipse.center, self.ellipse.majorAxisFinalPt) + self.endAngle = self.ellipse.getAngleFromParam(qad_utils.getAngleBy2Pts(self.ellipse.center, self.tmpPoint) - ellipseAngle) + self.ellipseArc.set(self.ellipse.center, self.ellipse.majorAxisFinalPt, self.ellipse.axisRatio, self.startAngle, self.endAngle) + ellipse = self.ellipseArc + # not i fuochi dell'ellisse, richiede di specificare un punto sull'ellisse + if self.mode == Qad_ellipse_maptool_ModeEnum.ASK_FOR_PT_ON_ELLIPSE: + ellipse = QadEllipse().fromFoci(self.focus1, self.focus2, self.tmpPoint) + + if ellipse is not None: + points = ellipse.asPolyline() + + if points is not None: + if self.geomType == QgsWkbTypes.PolygonGeometry: + self.__rubberBand.setPolygon(points) + else: + self.__rubberBand.setLine(points) + + + def activate(self): + QadGetPoint.activate(self) + self.__rubberBand.show() + + + def deactivate(self): + try: # necessario perché se si chiude QGIS parte questo evento nonostante non ci sia più l'oggetto maptool ! + QadGetPoint.deactivate(self) + self.__rubberBand.hide() + except: + pass + + def setMode(self, mode): + self.mode = mode + # noto niente si richiede il primo punto finale dell'asse + if self.mode == Qad_ellipse_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_FIRST_FINAL_AXIS_PT: + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + # si richiede di specificare la distanza dal secondo asse + elif self.mode == Qad_ellipse_maptool_ModeEnum.FIRST_FINAL_AXIS_PT_KNOWN_ASK_FOR_SECOND_FINAL_AXIS_PT: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + if self.axis1Pt1 is not None: + self.setStartPoint(self.axis1Pt1) + else: + self.setStartPoint(self.centerPt) + # si richiede di specificare la distanza dal secondo asse + elif self.mode == Qad_ellipse_maptool_ModeEnum.ASK_FOR_DIST_TO_OTHER_AXIS: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.centerPt) + # richiede la rotazione attorno all'asse maggiore + elif self.mode == Qad_ellipse_maptool_ModeEnum.ASK_ROTATION_ROUND_MAJOR_AXIS: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.centerPt) + # richiede l'angolo iniziale + elif self.mode == Qad_ellipse_maptool_ModeEnum.ASK_START_ANGLE: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.ellipse.center) + # richiede l'angolo finale + elif self.mode == Qad_ellipse_maptool_ModeEnum.ASK_END_ANGLE: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.ellipse.center) + # richiede l'angolo incluso + elif self.mode == Qad_ellipse_maptool_ModeEnum.ASK_INCLUDED_ANGLE: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.ellipse.center) + # richiede l'angolo parametrico iniziale + elif self.mode == Qad_ellipse_maptool_ModeEnum.ASK_START_PARAMETER: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.ellipse.center) + # richiede l'angolo parametrico finale + elif self.mode == Qad_ellipse_maptool_ModeEnum.ASK_END_PARAMETER: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.ellipse.center) + # richiede il centro + elif self.mode == Qad_ellipse_maptool_ModeEnum.ASK_FOR_CENTER: + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + # richiede il primo punto di fuoco + elif self.mode == Qad_ellipse_maptool_ModeEnum.ASK_FOR_FIRST_FOCUS: + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + # richiede il secondo punto di fuoco + elif self.mode == Qad_ellipse_maptool_ModeEnum.ASK_FOR_SECOND_FOCUS: + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + # richiede un punto sull'ellisse + elif self.mode == Qad_ellipse_maptool_ModeEnum.ASK_FOR_PT_ON_ELLIPSE: + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + # richede l'area dell'ellisse + elif self.mode == Qad_ellipse_maptool_ModeEnum.ASK_AREA: + self.setDrawMode(QadGetPointDrawModeEnum.NONE) diff --git a/cmd/qad_entsel_cmd.py b/cmd/qad_entsel_cmd.py new file mode 100644 index 00000000..0cb2607d --- /dev/null +++ b/cmd/qad_entsel_cmd.py @@ -0,0 +1,197 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + comando da inserire in altri comandi per la selezione di una feature + + ------------------- + begin : 2013-09-18 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.core import QgsWkbTypes, QgsPointXY + + +from .qad_generic_cmd import QadCommandClass +from ..qad_msg import QadMsg +from ..qad_textwindow import QadInputTypeEnum, QadInputModeEnum +from ..qad_entity import QadEntity +from ..qad_getpoint import QadGetPointSelectionModeEnum +from .. import qad_utils +from ..qad_dim import QadDimStyles +from ..qad_variables import QadVariables + + +# =============================================================================== +# QadEntSelClass +# =============================================================================== +class QadEntSelClass(QadCommandClass): + """ + Questa classe seleziona un'entità. Non è in grado di selezionare una quotatura ma solo un componente di una quotatura. + """ + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadEntSelClass(self.plugIn) + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + self.entity = QadEntity() + self.point = None + # opzioni per limitare gli oggetti da selezionare + self.onlyEditableLayers = False + self.checkPointLayer = True + self.checkLineLayer = True + self.checkPolygonLayer = True + self.checkDimLayers = True + self.selDimEntity = False # per restituire o meno un oggetto QadDimEntity + self.msg = QadMsg.translate("QAD", "Select object: ") + self.deselectOnFinish = False + self.canceledByUsr = False # diventa true se l'utente non vuole scegliere niente (es. se usato il tasto destro del mouse) + + def __del__(self): + QadCommandClass.__del__(self) + if self.deselectOnFinish: + self.entity.deselectOnLayer() + + + # ============================================================================ + # setEntity + # ============================================================================ + def setEntity(self, layer, fid): + del self.entity + if self.selDimEntity: # se è possibile restituire un oggetto QadDimEntity + # verifico se l'entità appartiene ad uno stile di quotatura + self.entity = QadDimStyles.getDimEntity(layer, fid) + if self.entity is None: # se non è una quota + self.entity = QadEntity() + self.entity.set(layer, fid) + else: + self.entity = QadEntity() + self.entity.set(layer, fid) + + self.entity.selectOnLayer() + + + # ============================================================================ + # getLayersToCheck + # ============================================================================ + def getLayersToCheck(self): + layerList = [] + for layer in qad_utils.getVisibleVectorLayers(self.plugIn.canvas): # Tutti i layer vettoriali visibili + # considero solo i layer vettoriali che sono filtrati per tipo + if ((layer.geometryType() == QgsWkbTypes.PointGeometry and self.checkPointLayer == True) or \ + (layer.geometryType() == QgsWkbTypes.LineGeometry and self.checkLineLayer == True) or \ + (layer.geometryType() == QgsWkbTypes.PolygonGeometry and self.checkPolygonLayer == True)) and \ + (self.onlyEditableLayers == False or layer.isEditable()): + # se devo includere i layers delle quotature + if self.checkDimLayers == True or \ + len(QadDimStyles.getDimListByLayer(layer)) == 0: + layerList.append(layer) + + return layerList + + + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + # ========================================================================= + # RICHIESTA PUNTO o ENTITA' + if self.step == 0: # inizio del comando + # imposto il map tool + self.getPointMapTool().setSelectionMode(QadGetPointSelectionModeEnum.ENTITY_SELECTION) + # imposto i layer da controllare sul maptool + self.getPointMapTool().layersToCheck = self.getLayersToCheck() + + keyWords = QadMsg.translate("Command_ENTSEL", "Last") + + englishKeyWords = "Last" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(self.msg, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, QadInputModeEnum.NONE) + + self.step = 1 + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA PUNTO o ENTITA' + elif self.step == 1: # dopo aver atteso un punto si riavvia il comando + entity = None + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + self.canceledByUsr = True + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + if self.getPointMapTool().entity.isInitialized(): + entity = self.getPointMapTool().entity + else: # il punto arriva come parametro della funzione + value = msg + + if value is None: + self.canceledByUsr = True + return True # fine comando + + if type(value) == unicode: + if value == QadMsg.translate("Command_ENTSEL", "Last") or value == "Last": + # Seleziona l'ultima entità inserita + lastEnt = self.plugIn.getLastEntity() + if lastEnt is not None: + # controllo sul layer + if self.onlyEditableLayers == False or lastEnt.layer.isEditable() == True: + # controllo sul tipo + if (self.checkPointLayer == True and lastEnt.layer.geometryType() == QgsWkbTypes.PointGeometry) or \ + (self.checkLineLayer == True and lastEnt.layer.geometryType() == QgsWkbTypes.LineGeometry) or \ + (self.checkPolygonLayer == True and lastEnt.layer.geometryType() == QgsWkbTypes.PolygonGeometry): + # controllo su layer delle quotature + if self.checkDimLayers == True or QadDimStyles.isDimEntity(lastEnt) == False: + self.setEntity(lastEnt.layer, lastEnt.featureId) + elif type(value) == QgsPointXY: + if entity is None: + # cerco se ci sono entità nel punto indicato + result = qad_utils.getEntSel(self.getPointMapTool().toCanvasCoordinates(value), + self.getPointMapTool(), \ + QadVariables.get(QadMsg.translate("Environment variables", "PICKBOX")), \ + self.getLayersToCheck()) + if result is not None: + feature = result[0] + layer = result[1] + self.setEntity(layer, feature.id()) + else: + self.setEntity(entity.layer, entity.featureId) + + self.point = value + + if self.deselectOnFinish: + self.entity.deselectOnLayer() + + return True # fine comando diff --git a/qad_erase_cmd.py b/cmd/qad_erase_cmd.py similarity index 83% rename from qad_erase_cmd.py rename to cmd/qad_erase_cmd.py index 5c178710..b5a8a986 100644 --- a/qad_erase_cmd.py +++ b/cmd/qad_erase_cmd.py @@ -1,104 +1,102 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - comando ERASE per cancellare oggetti - - ------------------- - begin : 2013-08-01 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -from qad_generic_cmd import QadCommandClass -from qad_msg import QadMsg -from qad_getpoint import * -from qad_ssget_cmd import QadSSGetClass -import qad_layer - - -# Classe che gestisce il comando ERASE -class QadERASECommandClass(QadCommandClass): - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadERASECommandClass(self.plugIn) - - def getName(self): - return QadMsg.translate("Command_list", "ERASE") - - def getEnglishName(self): - return "ERASE" - - def connectQAction(self, action): - QObject.connect(action, SIGNAL("triggered()"), self.plugIn.runERASECommand) - - def getIcon(self): - return QIcon(":/plugins/qad/icons/erase.png") - - def getNote(self): - # impostare le note esplicative del comando - return QadMsg.translate("Command_ERASE", "Removes objects of the map.") - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - self.SSGetClass = QadSSGetClass(plugIn) - self.SSGetClass.onlyEditableLayers = True - - def __del__(self): - QadCommandClass.__del__(self) - del self.SSGetClass - - def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): - if self.step == 0: # quando si é in fase di selezione entità - return self.SSGetClass.getPointMapTool(drawMode) - else: - return QadCommandClass.getPointMapTool(self, drawMode) - - def run(self, msgMapTool = False, msg = None): - - #========================================================================= - # RICHIESTA PRIMO PUNTO PER SELEZIONE OGGETTI - if self.step == 0: # inizio del comando - if self.SSGetClass.run(msgMapTool, msg) == True: - # selezione terminata - self.step = 1 - return self.run(msgMapTool, msg) - else: - return False - - #========================================================================= - # CANCELLAZIONE OGGETTI - elif self.step == 1: - self.plugIn.beginEditCommand("Feature deleted", self.SSGetClass.entitySet.getLayerList()) - - for layerEntitySet in self.SSGetClass.entitySet.layerEntitySetList: - # plugIn, layer, featureIds, refresh - if qad_layer.deleteFeaturesToLayer(self.plugIn, layerEntitySet.layer, \ - layerEntitySet.featureIds, False) == False: - self.plugIn.destroyEditCommand() - return - - self.plugIn.endEditCommand() - - return True # fine comando - +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin ok + + comando ERASE per cancellare oggetti + + ------------------- + begin : 2013-08-01 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.PyQt.QtGui import QIcon + + +from .qad_generic_cmd import QadCommandClass +from ..qad_msg import QadMsg +from ..qad_getpoint import QadGetPointDrawModeEnum +from .qad_ssget_cmd import QadSSGetClass +from .. import qad_layer + + +# Classe che gestisce il comando ERASE +class QadERASECommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadERASECommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "ERASE") + + def getEnglishName(self): + return "ERASE" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runERASECommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/erase.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_ERASE", "Removes objects of the map.") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + self.SSGetClass = QadSSGetClass(plugIn) + self.SSGetClass.onlyEditableLayers = True + + def __del__(self): + QadCommandClass.__del__(self) + del self.SSGetClass + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if self.step == 0: # quando si é in fase di selezione entità + return self.SSGetClass.getPointMapTool(drawMode) + else: + return QadCommandClass.getPointMapTool(self, drawMode) + + def run(self, msgMapTool = False, msg = None): + + # ========================================================================= + # RICHIESTA PRIMO PUNTO PER SELEZIONE OGGETTI + if self.step == 0: # inizio del comando + if self.SSGetClass.run(msgMapTool, msg) == True: + # selezione terminata + self.step = 1 + return self.run(msgMapTool, msg) + else: + return False + + # ========================================================================= + # CANCELLAZIONE OGGETTI + elif self.step == 1: + self.plugIn.beginEditCommand("Feature deleted", self.SSGetClass.entitySet.getLayerList()) + + for layerEntitySet in self.SSGetClass.entitySet.layerEntitySetList: + # plugIn, layer, featureIds, refresh + if qad_layer.deleteFeaturesToLayer(self.plugIn, layerEntitySet.layer, \ + layerEntitySet.featureIds, False) == False: + self.plugIn.destroyEditCommand() + return + + self.plugIn.endEditCommand() + + return True # fine comando + diff --git a/cmd/qad_extend_cmd.py b/cmd/qad_extend_cmd.py new file mode 100644 index 00000000..e4edb752 --- /dev/null +++ b/cmd/qad_extend_cmd.py @@ -0,0 +1,476 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + comando EXTEND per estendere o tagliare oggetti grafici ok + + ------------------- + begin : 2013-07-15 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.PyQt.QtGui import QIcon +from qgis.core import QgsWkbTypes, QgsFeature, QgsPointXY, QgsGeometry + + +from ..qad_point import QadPoint +from ..qad_getpoint import QadGetPointDrawModeEnum, QadGetPointSelectionModeEnum +from ..qad_textwindow import QadInputTypeEnum, QadInputModeEnum +from .qad_pline_cmd import QadPLINECommandClass +from .qad_rectangle_cmd import QadRECTANGLECommandClass +from .qad_generic_cmd import QadCommandClass +from ..qad_entity import QadEntitySet, getSelSet, QadLayerEntitySetIterator +from ..qad_msg import QadMsg +from .. import qad_utils +from .. import qad_layer +from ..qad_variables import QadVariables +from .qad_ssget_cmd import QadSSGetClass +from ..qad_dim import QadDimStyles +from ..qad_extend_trim_fun import extendQadGeometry, trimQadGeometry +from ..qad_geom_relations import getQadGeomClosestPart, QadIntersections +from ..qad_multi_geom import fromQadGeomToQgsGeom, setQadGeomAt + + +# Classe che gestisce il comando EXTEND +class QadEXTENDCommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadEXTENDCommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "EXTEND") + + def getEnglishName(self): + return "EXTEND" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runEXTENDCommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/extend.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_EXTEND", "Extends (or trims) objects to meet the edges of other objects.") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + self.SSGetClass = QadSSGetClass(plugIn) + self.PLINECommand = None + self.RECTANGLECommand = None + self.entitySet = QadEntitySet() # entità da estendere o tagliare + self.limitEntitySet = QadEntitySet() # entità che fanno da limiti + self.edgeMode = QadVariables.get(QadMsg.translate("Environment variables", "EDGEMODE")) + self.defaultValue = None # usato per gestire il tasto dx del mouse + self.nOperationsToUndo = 0 + + def __del__(self): + QadCommandClass.__del__(self) + + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if self.step == 3: # quando si é in fase di disegno linea + return self.PLINECommand.getPointMapTool(drawMode) + elif self.step == 4: # quando si é in fase di disegno rettangolo + return self.RECTANGLECommand.getPointMapTool(drawMode) + else: + return QadCommandClass.getPointMapTool(self, drawMode) + + + def getCurrentContextualMenu(self): + if self.step == 3: # quando si é in fase di disegno linea + return self.PLINECommand.getCurrentContextualMenu() + elif self.step == 4: # quando si é in fase di disegno rettangolo + return self.RECTANGLECommand.getCurrentContextualMenu() + else: + return self.contextualMenu + + + # ============================================================================ + # extendFeatures + # ============================================================================ + def extendFeatures(self, geom, toExtend): + # geom è in map coordinates + LineTempLayer = None + self.plugIn.beginEditCommand("Feature extended" if toExtend else "Feature trimmed", \ + self.entitySet.getLayerList()) + + for limitLayerEntitySet in self.entitySet.layerEntitySetList: + layer = limitLayerEntitySet.layer + + entityIterator = QadLayerEntitySetIterator(limitLayerEntitySet) + for entity in entityIterator: + # per ciascuna entità del layer + f = entity.getFeature() + if f is None: + continue + + qadGeom = entity.getQadGeom() + if geom.whatIs() == "POINT": + # la funzione ritorna una lista con + # ( + # + # + # + # + # <"a sinistra di" se il punto é alla sinista della parte con i seguenti valori: + # - < 0 = sinistra (per linea, arco o arco di ellisse) o interno (per cerchi, ellissi) + # - > 0 = destra (per linea, arco o arco di ellisse) o esterno (per cerchi, ellissi) + # ) + result = getQadGeomClosestPart(qadGeom, geom) + intPts = [result[1]] + else: + intPts = QadIntersections.twoGeomObjects(qadGeom, geom) + + for intPt in intPts: + if toExtend: + newGeom = extendQadGeometry(qadGeom, intPt, \ + self.limitEntitySet, self.edgeMode) + if newGeom is not None: + # aggiorno la feature con la geometria estesa + extendedFeature = QgsFeature(f) + # trasformo la geometria nel crs del layer + extendedFeature.setGeometry(fromQadGeomToQgsGeom(newGeom, layer)) + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, layer, extendedFeature, False, False) == False: + self.plugIn.destroyEditCommand() + return + else: # trim + result = trimQadGeometry(qadGeom, intPt, \ + self.limitEntitySet, self.edgeMode) + if result is not None: + line1 = result[0] + line2 = result[1] + atGeom = result[2] + atSubGeom = result[3] + if layer.geometryType() == QgsWkbTypes.LineGeometry: + newQadGeom = setQadGeomAt(qadGeom, line1, atGeom, atSubGeom) + if newQadGeom is None: + self.plugIn.destroyEditCommand() + return + + trimmedFeature1 = QgsFeature(f) + # trasformo la geometria nel crs del layer + trimmedFeature1.setGeometry(fromQadGeomToQgsGeom(newQadGeom, layer)) + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, layer, trimmedFeature1, False, False) == False: + self.plugIn.destroyEditCommand() + return + if line2 is not None: + trimmedFeature2 = QgsFeature(f) + # trasformo la geometria nel crs del layer + trimmedFeature2.setGeometry(fromQadGeomToQgsGeom(line2, layer)) + # plugIn, layer, feature, coordTransform, refresh, check_validity + if qad_layer.addFeatureToLayer(self.plugIn, layer, trimmedFeature2, None, False, False, False) == False: + self.plugIn.destroyEditCommand() + return + + else: + # aggiungo le linee nei layer temporanei di QAD + if LineTempLayer is None: + LineTempLayer = qad_layer.createQADTempLayer(self.plugIn, QgsWkbTypes.LineGeometry) + self.plugIn.addLayerToLastEditCommand("Feature trimmed", LineTempLayer) + + lineGeoms = [line1] + if line2 is not None: + lineGeoms.append(line2) + + # trasformo la geometria in quella dei layer temporanei + # plugIn, pointGeoms, lineGeoms, polygonGeoms, coord, refresh + if qad_layer.addGeometriesToQADTempLayers(self.plugIn, None, lineGeoms, None, None, False) == False: + self.plugIn.destroyEditCommand() + return + + if delQadGeomAt(qadGeom, atGeom, atSubGeom) == False or updGeom.isEmpty(): # da cancellare + # plugIn, layer, feature id, refresh + if qad_layer.deleteFeatureToLayer(self.plugIn, layer, f.id(), False) == False: + self.plugIn.destroyEditCommand() + return + else: + trimmedFeature1 = QgsFeature(f) + # trasformo la geometria nel crs del layer + trimmedFeature1.setGeometry(fromQadGeomToQgsGeom(qadGeom, layer)) + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, layer, trimmedFeature1, False, False) == False: + self.plugIn.destroyEditCommand() + return + + self.plugIn.endEditCommand() + self.nOperationsToUndo = self.nOperationsToUndo + 1 + + + # ============================================================================ + # waitForObjectSel + # ============================================================================ + def waitForObjectSel(self): + self.step = 2 + # imposto il map tool + self.getPointMapTool().setSelectionMode(QadGetPointSelectionModeEnum.ENTITY_SELECTION_DYNAMIC) + # solo layer lineari editabili che non appartengano a quote + layerList = [] + for layer in qad_utils.getVisibleVectorLayers(self.plugIn.canvas): # Tutti i layer vettoriali visibili + if layer.geometryType() == QgsWkbTypes.LineGeometry and layer.isEditable(): + if len(QadDimStyles.getDimListByLayer(layer)) == 0: + layerList.append(layer) + + self.getPointMapTool().layersToCheck = layerList + self.getPointMapTool().setDrawMode(QadGetPointDrawModeEnum.NONE) + self.getPointMapTool().onlyEditableLayers = True + + keyWords = QadMsg.translate("Command_EXTEND", "Fence") + "/" + \ + QadMsg.translate("Command_EXTEND", "Crossing") + "/" + \ + QadMsg.translate("Command_EXTEND", "Edge") + "/" + \ + QadMsg.translate("Command_EXTEND", "Undo") + prompt = QadMsg.translate("Command_EXTEND", "Select the object to extend or shift-select to trim or [{0}]: ").format(keyWords) + + englishKeyWords = "Fence" + "/" + "Crossing" + "/" + "Edge" + "/" + "Undo" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, QadInputModeEnum.NONE) + + + # ============================================================================ + # run + # ============================================================================ + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + # ========================================================================= + # RICHIESTA SELEZIONE OGGETTI LIMITI + if self.step == 0: # inizio del comando + CurrSettingsMsg = QadMsg.translate("QAD", "\nCurrent settings: ") + if self.edgeMode == 0: # 0 = nessuna estensione + CurrSettingsMsg = CurrSettingsMsg + QadMsg.translate("Command_EXTEND", "Edge = No extend") + else: + CurrSettingsMsg = CurrSettingsMsg + QadMsg.translate("Command_EXTEND", "Edge = Extend") + + self.showMsg(CurrSettingsMsg) + self.showMsg(QadMsg.translate("Command_EXTEND", "\nSelect extension limits...")) + + if self.SSGetClass.run(msgMapTool, msg) == True: + # selezione terminata + self.step = 1 + return self.run(msgMapTool, msg) + + # ========================================================================= + # RISPOSTA ALLA SELEZIONE OGGETTI LIMITI + elif self.step == 1: + self.limitEntitySet.set(self.SSGetClass.entitySet) + + if self.limitEntitySet.count() == 0: + return True # fine comando + + # si appresta ad attendere la selezione degli oggetti da estendere/tagliare + self.waitForObjectSel() + return False + + # ========================================================================= + # RISPOSTA ALLA SELEZIONE OGGETTI DA ESTENDERE + elif self.step == 2: + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("Command_EXTEND", "Fence") or value == "Fence": + # Seleziona tutti gli oggetti che intersecano una polilinea + self.PLINECommand = QadPLINECommandClass(self.plugIn) + # se questo flag = True il comando serve all'interno di un altro comando per disegnare una linea + # che non verrà salvata su un layer + self.PLINECommand.virtualCmd = True + self.PLINECommand.run(msgMapTool, msg) + self.step = 3 + return False + elif value == QadMsg.translate("Command_EXTEND", "Crossing") or value == "Crossing": + # Seleziona tutti gli oggetti che intersecano un rettangolo + self.RECTANGLECommand = QadRECTANGLECommandClass(self.plugIn) + # se questo flag = True il comando serve all'interno di un altro comando per disegnare una linea + # che non verrà salvata su un layer + self.RECTANGLECommand.virtualCmd = True + self.RECTANGLECommand.run(msgMapTool, msg) + self.step = 4 + return False + elif value == QadMsg.translate("Command_EXTEND", "Edge") or value == "Edge": + # Per estendere un oggetto usando anche le estensioni degli oggetti di riferimento + # vedi variabile EDGEMODE + keyWords = QadMsg.translate("Command_EXTEND", "Extend") + "/" + \ + QadMsg.translate("Command_EXTEND", "No extend") + + if self.edgeMode == 0: # 0 = nessuna estensione + self.defaultValue = QadMsg.translate("Command_EXTEND", "No extend") + else: + self.defaultValue = QadMsg.translate("Command_EXTEND", "Extend") + prompt = QadMsg.translate("Command_EXTEND", "Specify an extension mode [{0}] <{1}>: ").format(keyWords, self.defaultValue) + + englishKeyWords = "Extend" + "/" + "No extend" + keyWords += "_" + englishKeyWords + # si appresta ad attendere enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.KEYWORDS, \ + self.defaultValue, \ + keyWords, QadInputModeEnum.NONE) + self.step = 5 + return False + elif value == QadMsg.translate("Command_EXTEND", "Undo") or value == "Undo": + if self.nOperationsToUndo > 0: + self.nOperationsToUndo = self.nOperationsToUndo - 1 + self.plugIn.undoEditCommand() + else: + self.showMsg(QadMsg.translate("QAD", "\nThe command has been canceled.")) + elif type(value) == QgsPointXY: # se é stato selezionato un punto + self.entitySet.clear() + if self.getPointMapTool().entity.isInitialized(): + self.entitySet.addEntity(self.getPointMapTool().entity) + ToExtend = True if self.getPointMapTool().shiftKey == False else False + self.extendFeatures(QadPoint().set(value), ToExtend) + else: + # cerco se ci sono entità nel punto indicato considerando + # solo layer lineari editabili che non appartengano a quote + layerList = [] + for layer in qad_utils.getVisibleVectorLayers(self.plugIn.canvas): # Tutti i layer vettoriali visibili + if layer.geometryType() == QgsWkbTypes.LineGeometry and layer.isEditable(): + if len(QadDimStyles.getDimListByLayer(layer)) == 0: + layerList.append(layer) + + result = qad_utils.getEntSel(self.getPointMapTool().toCanvasCoordinates(value), \ + self.getPointMapTool(), \ + QadVariables.get(QadMsg.translate("Environment variables", "PICKBOX")), \ + layerList) + if result is not None: + feature = result[0] + layer = result[1] + point = result[2] + self.entitySet.addEntity(QadEntity().set(layer, feature.id())) + self.extendFeatures(QadPoint().set(value), True) + else: + return True # fine comando + + # si appresta ad attendere la selezione degli oggetti da estendere/tagliare + self.waitForObjectSel() + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA PUNTO PER MODALITA' INTERCETTA (da step = 2) + elif self.step == 3: # dopo aver atteso un punto si riavvia il comando + if self.PLINECommand.run(msgMapTool, msg) == True: + if self.PLINECommand.polyline.qty() > 0: + if msgMapTool == True: # se la polilinea arriva da una selezione grafica + ToExtend = True if self.getPointMapTool().shiftKey == False else False + else: + ToExtend = True + + # cerco tutte le geometrie passanti per la polilinea considerando + # solo layer lineari editabili che non appartengano a quote + layerList = [] + for layer in qad_utils.getVisibleVectorLayers(self.plugIn.canvas): # Tutti i layer vettoriali visibili + if layer.geometryType() == QgsWkbTypes.LineGeometry and layer.isEditable(): + if len(QadDimStyles.getDimListByLayer(layer)) == 0: + layerList.append(layer) + + self.entitySet = getSelSet("F", self.getPointMapTool(), self.PLINECommand.polyline.asPolyline(), \ + layerList) + self.extendFeatures(self.PLINECommand.polyline, ToExtend) + del self.PLINECommand + self.PLINECommand = None + + # si appresta ad attendere la selezione degli oggetti da estendere/tagliare + self.waitForObjectSel() + self.getPointMapTool().refreshSnapType() # aggiorno lo snapType che può essere variato dal maptool di pline + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA PUNTO PER MODALITA' INTERSECA (da step = 2) + elif self.step == 4: # dopo aver atteso un punto si riavvia il comando + if self.RECTANGLECommand.run(msgMapTool, msg) == True: + if self.RECTANGLECommand.polyline.qty() > 0: + if msgMapTool == True: # se la polilinea arriva da una selezione grafica + ToExtend = True if self.getPointMapTool().shiftKey == False else False + else: + ToExtend = True + + # cerco tutte le geometrie passanti per il rettangolo considerando + # solo layer lineari editabili che non appartengano a quote + layerList = [] + for layer in qad_utils.getVisibleVectorLayers(self.plugIn.canvas): # Tutti i layer vettoriali visibili + if layer.geometryType() == QgsWkbTypes.LineGeometry and layer.isEditable(): + if len(QadDimStyles.getDimListByLayer(layer)) == 0: + layerList.append(layer) + + self.entitySet = getSelSet("F", self.getPointMapTool(), self.RECTANGLECommand.polyline.asPolyline(), \ + layerList) + self.extendFeatures(self.RECTANGLECommand.polyline, ToExtend) + del self.RECTANGLECommand + self.RECTANGLECommand = None + + # si appresta ad attendere la selezione degli oggetti da estendere/tagliare + self.waitForObjectSel() + self.getPointMapTool().refreshSnapType() # aggiorno lo snapType che può essere variato dal maptool di rectangle + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DI TIPO DI ESTENSIONE (da step = 2) + elif self.step == 5: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + value = self.defaultValue + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: # il valore arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("Command_EXTEND", "No extend") or value == "No extend": + self.edgeMode = 0 + QadVariables.set(QadMsg.translate("Environment variables", "EDGEMODE"), self.edgeMode) + QadVariables.save() + # si appresta ad attendere la selezione degli oggetti da estendere/tagliare + self.waitForObjectSel() + elif value == QadMsg.translate("Command_EXTEND", "Extend") or value == "Extend": + self.edgeMode = 1 + QadVariables.set(QadMsg.translate("Environment variables", "EDGEMODE"), self.edgeMode) + QadVariables.save() + # si appresta ad attendere la selezione degli oggetti da estendere/tagliare + self.waitForObjectSel() + + return False diff --git a/cmd/qad_fillet_cmd.py b/cmd/qad_fillet_cmd.py new file mode 100644 index 00000000..9e9b78b4 --- /dev/null +++ b/cmd/qad_fillet_cmd.py @@ -0,0 +1,724 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + comando FILLET per raccordare due oggetti grafici ok + + ------------------- + begin : 2014-01-30 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from qgis.core import QgsGeometry, QgsPointXY, QgsWkbTypes +from qgis.PyQt.QtGui import QIcon + + +from ..qad_getpoint import QadGetPointDrawModeEnum +from .qad_generic_cmd import QadCommandClass +from .qad_getdist_cmd import QadGetDistClass +from .qad_fillet_maptool import Qad_fillet_maptool, Qad_fillet_maptool_ModeEnum +from ..qad_msg import QadMsg +from ..qad_textwindow import QadInputTypeEnum, QadInputModeEnum +from .. import qad_utils +from .. import qad_layer +from ..qad_variables import QadVariables +from ..qad_polyline import QadPolyline +from ..qad_dim import QadDimStyles +from ..qad_fillet_fun import fillet2QadGeometries, filletAllPartsQadPolyline, filletQadPolyline +from ..qad_entity import QadEntity +from ..qad_multi_geom import fromQadGeomToQgsGeom, getQadGeomAt, setQadGeomAt +from ..qad_geom_relations import getQadGeomClosestPart + + +# Classe che gestisce il comando FILLET +class QadFILLETCommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadFILLETCommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "FILLET") + + def getEnglishName(self): + return "FILLET" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runFILLETCommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/fillet.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_FILLET", "Rounds and fillets the edges of objects.") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + self.GetDistClass = None + + self.entity1 = QadEntity() + self.atGeom1 = None + self.atSubGeom1 = None + self.partAt1 = 0 + self.pointAt1 = None + + self.entity2 = QadEntity() + self.atGeom2 = None + self.atSubGeom2 = None + self.qadPolyline2 = QadPolyline() + self.partAt2 = 0 + self.pointAt2 = None + + self.filletMode = plugIn.filletMode # modalità di raccordo; 1=Taglia-estendi, 2=Non taglia-estendi + self.radius = QadVariables.get(QadMsg.translate("Environment variables", "FILLETRAD")) + self.multi = False + self.nOperationsToUndo = 0 + + + def __del__(self): + QadCommandClass.__del__(self) + if self.GetDistClass is not None: + del self.GetDistClass + self.entity1.deselectOnLayer() + self.entity2.deselectOnLayer() + + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + # quando si é in fase di richiesta distanza + if self.step == 3 or self.step == 5 or self.step == 7: + return self.GetDistClass.getPointMapTool() + elif (self.plugIn is not None): + if self.PointMapTool is None: + self.PointMapTool = Qad_fillet_maptool(self.plugIn) + return self.PointMapTool + else: + return None + + + def getCurrentContextualMenu(self): + # quando si é in fase di richiesta distanza + if self.step == 3 or self.step == 5 or self.step == 7: + return self.GetDistClass.getCurrentContextualMenu() + else: + return self.contextualMenu + + + # ============================================================================ + # setEntityInfo + # ============================================================================ + def setEntityInfo(self, firstObj, layer, featureId, point): + """ + Setta self.entity, self.atGeom, self.atSubGeom, self.partAt, self.pointAt + di primo o del secondo oggetto da raccordare (vedi ) + """ + if firstObj: + e = self.entity1 + else: + e = self.entity2 + + e.set(layer, featureId) + qadGeom = e.getQadGeom() + """ + la funzione ritorna una lista con + ( + + + + + <"a sinistra di" se il punto é alla sinista della parte con i seguenti valori: + - < 0 = sinistra (per linea, arco o arco di ellisse) o interno (per cerchi, ellissi) + - > 0 = destra (per linea, arco o arco di ellisse) o esterno (per cerchi, ellissi) + ) + """ + res = getQadGeomClosestPart(qadGeom, point) + + if firstObj: + self.pointAt1 = res[1] + self.atGeom1 = res[2] + self.atSubGeom1 = res[3] + self.partAt1 = res[4] + else: + self.pointAt2 = res[1] + self.atGeom2 = res[2] + self.atSubGeom2 = res[3] + self.partAt2 = res[4] + + e.selectOnLayer(False) # non incrementale + return True + + + # ============================================================================ + # filletPolyline + # ============================================================================ + def filletPolyline(self): + layer = self.entity1.layer + f = self.entity1.getFeature() + qadGeom = self.entity1.getQadGeom() + subQadGeom = getQadGeomAt(qadGeom, self.atGeom1, self.atSubGeom1) + + if filletAllPartsQadPolyline(subQadGeom, self.radius) == False: return False + newQadGeom = setQadGeomAt(qadGeom, subQadGeom, self.atGeom1, self.atSubGeom1) + if newQadGeom is None: + return False + f.setGeometry(fromQadGeomToQgsGeom(newQadGeom, layer)) + + self.plugIn.beginEditCommand("Feature edited", layer) + + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, layer, f, False, False) == False: + self.plugIn.destroyEditCommand() + return False + + self.plugIn.endEditCommand() + self.nOperationsToUndo = self.nOperationsToUndo + 1 + + return True + + + # ============================================================================ + # fillet + # ============================================================================ + def fillet(self): + tolerance2ApproxCurve = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) + + # stessa entità e stessa geometria + if self.entity1.layer.id() == self.entity2.layer.id() and \ + self.entity1.featureId == self.entity2.featureId and \ + self.atGeom1 == self.atGeom2 and self.atSubGeom1 == self.atSubGeom2: + # se anche stessa parte + if self.partAt1 == self.partAt2: return False + subQadGeom = getQadGeomAt(self.entity1.getQadGeom(),self.atGeom1, self.atSubGeom1) + if subQadGeom.whatIs() == "POLYLINE": + newQadGeom = filletQadPolyline(subQadGeom, self.partAt1, self.pointAt1, self.partAt2, self.pointAt2, \ + self.filletMode, self.radius) + + if newQadGeom is None: # raccordo non possibile + msg = QadMsg.translate("Command_FILLET", "\nFillet with radius <{0}> impossible.") + #showMsg + self.showMsg(msg.format(str(self.radius))) + return False + + self.plugIn.beginEditCommand("Feature edited", [self.entity1.layer]) + + f = self.entity1.getFeature() + # trasformo la geometria nel crs del layer + f.setGeometry(fromQadGeomToQgsGeom(newQadGeom, self.entity1.layer)) + + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, self.entity1.layer, f, False, False) == False: + self.plugIn.destroyEditCommand() + return False + + self.plugIn.endEditCommand() + self.nOperationsToUndo = self.nOperationsToUndo + 1 + + return True + + # geometrie diverse + res = fillet2QadGeometries(self.entity1.getQadGeom(), self.atGeom1, self.atSubGeom1, self.partAt1, self.pointAt1, \ + self.entity2.getQadGeom(), self.atGeom2, self.atSubGeom2, self.partAt2, self.pointAt2, \ + self.filletMode, self.radius) + + if res is None: # raccordo non possibile + msg = QadMsg.translate("Command_FILLET", "\nFillet with radius <{0}> impossible.") + #showMsg + self.showMsg(msg.format(str(self.radius))) + return False + + newQadGeom = res[0] + whatToDoPoly1 = res[1] + whatToDoPoly2 = res[2] + + self.plugIn.beginEditCommand("Feature edited", [self.entity1.layer, self.entity2.layer]) + + if whatToDoPoly1 == 1: # 1=modificare + f = self.entity1.getFeature() + # trasformo la geometria nel crs del layer + f.setGeometry(fromQadGeomToQgsGeom(newQadGeom, self.entity1.layer)) + + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, self.entity1.layer, f, False, False) == False: + self.plugIn.destroyEditCommand() + return False + elif whatToDoPoly1 == 2: # 2=cancellare + # se non si tratta della stessa entità + if self.entity1 != self.entity2: + # plugIn, layer, featureId, refresh + if qad_layer.deleteFeatureToLayer(self.plugIn, self.entity1.layer, \ + self.entity1.featureId, False) == False: + self.plugIn.destroyEditCommand() + return False + + if whatToDoPoly2 == 1: # 1=modificare + f = self.entity2.getFeature() + # trasformo la geometria nel crs del layer + f.setGeometry(fromQadGeomToQgsGeom(newQadGeom, self.entity2.layer)) + + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, self.entity2.layer, f, False, False) == False: + self.plugIn.destroyEditCommand() + return False + elif whatToDoPoly2 == 2: # 2=cancellare + # se non si tratta della stessa entità + if self.entity1 != self.entity2: + # plugIn, layer, featureId, refresh + if qad_layer.deleteFeatureToLayer(self.plugIn, self.entity2.layer, \ + self.entity2.featureId, False) == False: + self.plugIn.destroyEditCommand() + return False + + if whatToDoPoly1 == 0 and whatToDoPoly2 == 0: # 0=niente + geom = QgsGeometry.fromPolylineXY(filletLinearObjectList.asPolyline(tolerance2ApproxCurve)) + # trasformo la geometria nel crs del layer + geom = fromQadGeomToQgsGeom(newQadGeom, self.entity1.layer) + + # plugIn, layer, geom, coordTransform, refresh, check_validity + if qad_layer.addGeomToLayer(self.plugIn, self.entity1.layer, geom, None, False, False) == False: + self.plugIn.destroyEditCommand() + return False + + self.plugIn.endEditCommand() + self.nOperationsToUndo = self.nOperationsToUndo + 1 + + return True + + + # ============================================================================ + # waitForFirstEntSel + # ============================================================================ + def waitForFirstEntSel(self): + self.step = 1 + # imposto il map tool + self.getPointMapTool().setMode(Qad_fillet_maptool_ModeEnum.ASK_FOR_FIRST_LINESTRING) + + # l'opzione Radius viene tradotta in italiano in "RAggio" nel contesto "waitForFirstEntSel" + keyWords = QadMsg.translate("Command_FILLET", "Undo") + "/" + \ + QadMsg.translate("Command_FILLET", "Polyline") + "/" + \ + QadMsg.translate("Command_FILLET", "Radius", "waitForFirstEntSel") + "/" + \ + QadMsg.translate("Command_FILLET", "Trim") + "/" + \ + QadMsg.translate("Command_FILLET", "Multiple") + prompt = QadMsg.translate("Command_FILLET", "Select first object or [{0}]: ").format(keyWords) + + englishKeyWords = "Undo" + "/" + "Polyline" + "/" + "Radius" + "/" + "Trim" + "/" + "Multiple" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, valore nullo non permesso + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, QadInputModeEnum.NOT_NULL) + + + # ============================================================================ + # WaitForPolyline + # ============================================================================ + def WaitForPolyline(self): + self.step = 2 + # imposto il map tool + self.getPointMapTool().setMode(Qad_fillet_maptool_ModeEnum.ASK_FOR_POLYLINE) + self.getPointMapTool().radius = self.radius + + # l'opzione Radius viene tradotta in italiano in "Raggio" nel contesto "WaitForPolyline" + keyWords = QadMsg.translate("Command_FILLET", "Radius", "WaitForPolyline") + prompt = QadMsg.translate("Command_FILLET", "Select polyline or [{0}]: ").format(keyWords) + + englishKeyWords = "Radius" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, valore nullo non permesso + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, QadInputModeEnum.NOT_NULL) + + + # ============================================================================ + # waitForFilletMode + # ============================================================================ + def waitForFilletMode(self): + self.step = 4 + # imposto il map tool + self.getPointMapTool().setMode(Qad_fillet_maptool_ModeEnum.NONE) + + keyWords = QadMsg.translate("Command_FILLET", "Trim-extend") + "/" + \ + QadMsg.translate("Command_FILLET", "No trim-extend") + + if self.filletMode == 1: + default = QadMsg.translate("Command_FILLET", "Trim-extend") + elif self.filletMode == 2: + default = QadMsg.translate("Command_FILLET", "No trim-extend") + + prompt = QadMsg.translate("Command_FILLET", "Specify trim mode [{0}] <{1}>: ").format(keyWords, default) + + englishKeyWords = "Trim-extend" + "/" + "No trim-extend" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter o una parola chiave o un numero reale + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, QadInputTypeEnum.KEYWORDS, default, \ + keyWords) + + + # ============================================================================ + # waitForSecondEntSel + # ============================================================================ + def waitForSecondEntSel(self): + self.step = 6 + # imposto il map tool + self.getPointMapTool().filletMode = self.filletMode + self.getPointMapTool().radius = self.radius + self.getPointMapTool().setEntityInfo(self.entity1, self.atGeom1, self.atSubGeom1, \ + self.partAt1, self.pointAt1) + self.getPointMapTool().setMode(Qad_fillet_maptool_ModeEnum.ASK_FOR_SECOND_LINESTRING) + + # l'opzione Radius viene tradotta in italiano in "RAggio" nel contesto "waitForSecondEntSel" + keyWords = QadMsg.translate("Command_FILLET", "Radius", "waitForSecondEntSel") + prompt = QadMsg.translate("Command_FILLET", "Select second object or shift-select to apply corner or [{0}]: ").format(keyWords) + + englishKeyWords = "Radius" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, valore nullo non permesso + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, QadInputModeEnum.NOT_NULL) + + + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + if self.step == 0: + CurrSettingsMsg = QadMsg.translate("QAD", "\nCurrent settings: ") + if self.filletMode == 1: + CurrSettingsMsg = CurrSettingsMsg + QadMsg.translate("Command_FILLET", "Mode = Trim-extend") + else: + CurrSettingsMsg = CurrSettingsMsg + QadMsg.translate("Command_FILLET", "Mode = No trim-extend") + + CurrSettingsMsg = CurrSettingsMsg + QadMsg.translate("Command_FILLET", ", Radius = ") + str(self.radius) + self.showMsg(CurrSettingsMsg) + + self.waitForFirstEntSel() + return False # continua + + # ========================================================================= + # RISPOSTA ALLA SELEZIONE PRIMO OGGETTO + elif self.step == 1: + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("Command_FILLET", "Undo") or value == "Undo": + if self.nOperationsToUndo > 0: + self.nOperationsToUndo = self.nOperationsToUndo - 1 + self.plugIn.undoEditCommand() + else: + self.showMsg(QadMsg.translate("QAD", "\nThe command has been canceled.")) + + self.waitForFirstEntSel() # si appresta ad attendere la selezione del primo oggetto + elif value == QadMsg.translate("Command_FILLET", "Polyline") or value == "Polyline": + self.WaitForPolyline() + # l'opzione Radius viene tradotta in italiano in "RAggio" nel contesto "waitForFirstEntSel" + elif value == QadMsg.translate("Command_FILLET", "Radius", "waitForFirstEntSel") or value == "Radius": + if self.GetDistClass is not None: + del self.GetDistClass + self.GetDistClass = QadGetDistClass(self.plugIn) + prompt = QadMsg.translate("Command_FILLET", "Specify fillet radius <{0}>: ") + self.GetDistClass.msg = prompt.format(str(self.radius)) + self.GetDistClass.dist = self.radius + self.GetDistClass.inputMode = QadInputModeEnum.NOT_NEGATIVE + self.step = 3 + self.GetDistClass.run(msgMapTool, msg) + elif value == QadMsg.translate("Command_FILLET", "Trim") or value == "Trim": + self.waitForFilletMode() + elif value == QadMsg.translate("Command_FILLET", "Multiple") or value == "Multiple": + self.multi = True + self.waitForFirstEntSel() # si appresta ad attendere la selezione del primo oggetto + + elif type(value) == QgsPointXY: # se é stato selezionato un punto + self.entity1.clear() + if self.getPointMapTool().entity.isInitialized(): + if self.setEntityInfo(True, self.getPointMapTool().entity.layer, \ + self.getPointMapTool().entity.featureId, value) == True: + self.waitForSecondEntSel() # si appresta ad attendere la selezione del secondo oggetto + return False + else: + # cerco se ci sono entità nel punto indicato considerando + # solo layer lineari o poligono editabili che non appartengano a quote + layerList = [] + for layer in qad_utils.getVisibleVectorLayers(self.plugIn.canvas): # Tutti i layer vettoriali visibili + if (layer.geometryType() == QgsWkbTypes.LineGeometry or layer.geometryType() == QgsWkbTypes.PolygonGeometry) and \ + layer.isEditable(): + if len(QadDimStyles.getDimListByLayer(layer)) == 0: + layerList.append(layer) + + result = qad_utils.getEntSel(self.getPointMapTool().toCanvasCoordinates(value), \ + self.getPointMapTool(), \ + QadVariables.get(QadMsg.translate("Environment variables", "PICKBOX")), \ + layerList) + if result is not None: + # result[0] = feature, result[1] = layer, result[0] = point + if self.setEntityInfo(True, result[1], result[0].id(), result[2]) == True: + self.waitForSecondEntSel() # si appresta ad attendere la selezione del secondo oggetto + return False + self.waitForFirstEntSel() # si appresta ad attendere la selezione del primo oggetto + else: + return True # fine comando + + return False + + # ========================================================================= + # RISPOSTA ALLA SELEZIONE DI UNA POLILINEA (da step = 1) + elif self.step == 2: + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == unicode: + # l'opzione Radius viene tradotta in italiano in "Raggio" nel contesto "WaitForPolyline" + if value == QadMsg.translate("Command_FILLET", "Radius", "WaitForPolyline") or value == "Radius": + if self.GetDistClass is not None: + del self.GetDistClass + self.GetDistClass = QadGetDistClass(self.plugIn) + prompt = QadMsg.translate("Command_FILLET", "Specify fillet radius <{0}>: ") + self.GetDistClass.msg = prompt.format(str(self.radius)) + self.GetDistClass.dist = self.radius + self.GetDistClass.inputMode = QadInputModeEnum.NOT_NEGATIVE + self.step = 5 + self.GetDistClass.run(msgMapTool, msg) + return False + elif type(value) == QgsPointXY: # se é stato selezionato un punto + self.entity1.clear() + if self.getPointMapTool().entity.isInitialized(): + if self.setEntityInfo(True, self.getPointMapTool().entity.layer, \ + self.getPointMapTool().entity.featureId, value) == True: + if self.filletPolyline() == False or self.multi: + self.waitForFirstEntSel() # si appresta ad attendere la selezione del primo oggetto + return False + else: + return True + else: + # cerco se ci sono entità nel punto indicato considerando + # solo layer lineari o poligono editabili che non appartengano a quote + layerList = [] + for layer in qad_utils.getVisibleVectorLayers(self.plugIn.canvas): # Tutti i layer vettoriali visibili + if (layer.geometryType() == QgsWkbTypes.LineGeometry or layer.geometryType() == QgsWkbTypes.PolygonGeometry) and \ + layer.isEditable(): + if len(QadDimStyles.getDimListByLayer(layer)) == 0: + layerList.append(layer) + + result = qad_utils.getEntSel(self.getPointMapTool().toCanvasCoordinates(value), \ + self.getPointMapTool(), \ + QadVariables.get(QadMsg.translate("Environment variables", "PICKBOX")), \ + layerList) + if result is not None: + # result[0] = feature, result[1] = layer, result[0] = point + if self.setEntityInfo(True, result[1], result[0].id(), result[2]) == True: + if self.filletPolyline() == False or self.multi: + self.waitForFirstEntSel() # si appresta ad attendere la selezione del primo oggetto + return False + else: + return True + else: + return True # fine comando + + self.WaitForPolyline() + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL RAGGIO DI RACCORDO (da step = 1) + elif self.step == 3: + if self.GetDistClass.run(msgMapTool, msg) == True: + if self.GetDistClass.dist is not None: + self.radius = self.GetDistClass.dist + QadVariables.set(QadMsg.translate("Environment variables", "FILLETRAD"), self.radius) + QadVariables.save() + self.waitForFirstEntSel() # si appresta ad attendere la selezione del primo oggetto + self.getPointMapTool().refreshSnapType() # aggiorno lo snapType che può essere variato dal maptool di distanza + return False # fine comando + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DELLA MODALITA' DI TAGLIO (da step = 1) + elif self.step == 4: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + value = self.filletMode + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("Command_FILLET", "Trim-extend") or value == "Trim-extend": + self.filletMode = 1 + elif value == QadMsg.translate("Command_FILLET", "No trim-extend") or value == "No trim-extend": + self.filletMode = 2 + self.plugIn.setFilletMode(self.filletMode) + + self.waitForFirstEntSel() # si appresta ad attendere la selezione del primo oggetto + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL RAGGIO DI RACCORDO (da step = 3) + elif self.step == 5: + if self.GetDistClass.run(msgMapTool, msg) == True: + if self.GetDistClass.dist is not None: + self.radius = self.GetDistClass.dist + QadVariables.set(QadMsg.translate("Environment variables", "FILLETRAD"), self.radius) + QadVariables.save() + self.WaitForPolyline() + self.getPointMapTool().refreshSnapType() # aggiorno lo snapType che può essere variato dal maptool di distanza + return False # fine comando + + # ========================================================================= + # RISPOSTA ALLA SELEZIONE SECONDO OGGETTO + elif self.step == 6: + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == unicode: + # l'opzione Radius viene tradotta in italiano in "RAggio" nel contesto "waitForSecondEntSel" + if value == QadMsg.translate("Command_FILLET", "Radius", "waitForSecondEntSel") or value == "Radius": + if self.GetDistClass is not None: + del self.GetDistClass + self.GetDistClass = QadGetDistClass(self.plugIn) + prompt = QadMsg.translate("Command_FILLET", "Specify fillet radius <{0}>: ") + self.GetDistClass.msg = prompt.format(str(self.radius)) + self.GetDistClass.dist = self.radius + self.GetDistClass.inputMode = QadInputModeEnum.NOT_NEGATIVE + self.step = 7 + self.GetDistClass.run(msgMapTool, msg) + return False + + elif type(value) == QgsPointXY: # se é stato selezionato un punto + self.entity2.clear() + self.qadPolyline2.removeAll() + + if self.getPointMapTool().entity.isInitialized(): + if self.setEntityInfo(False, self.getPointMapTool().entity.layer, \ + self.getPointMapTool().entity.featureId, value) == True: + if self.getPointMapTool().shiftKey == True: + dummyRadius = self.radius + self.radius = 0 + dummyFilletMode = self.filletMode + self.filletMode = 1 # modalità di raccordo; 1=Taglia-estendi + result = self.fillet() + self.radius = dummyRadius + self.filletMode = dummyFilletMode + else: + result = self.fillet() + + if result == False: + self.waitForSecondEntSel() # si appresta ad attendere la selezione del secondo oggetto + return False + + if self.multi: + self.waitForFirstEntSel() # si appresta ad attendere la selezione del primo oggetto + return False + else: + return True + else: + # cerco se ci sono entità nel punto indicato considerando + # solo layer lineari o poligono editabili che non appartengano a quote + layerList = [] + for layer in qad_utils.getVisibleVectorLayers(self.plugIn.canvas): # Tutti i layer vettoriali visibili + if (layer.geometryType() == QgsWkbTypes.LineGeometry or layer.geometryType() == QgsWkbTypes.PolygonGeometry) and \ + layer.isEditable(): + if len(QadDimStyles.getDimListByLayer(layer)) == 0: + layerList.append(layer) + + result = qad_utils.getEntSel(self.getPointMapTool().toCanvasCoordinates(value), \ + self.getPointMapTool(), \ + QadVariables.get(QadMsg.translate("Environment variables", "PICKBOX")), \ + layerList) + if result is not None: + # result[0] = feature, result[1] = layer, result[0] = point + if self.setEntityInfo(False, result[1], result[0].id(), result[2]) == True: + if self.fillet() == False: + self.waitForSecondEntSel() # si appresta ad attendere la selezione del secondo oggetto + return False + + if self.multi: + self.waitForFirstEntSel() # si appresta ad attendere la selezione del primo oggetto + return False + else: + return True + else: + return True # fine comando + + self.waitForSecondEntSel() # si appresta ad attendere la selezione del secondo oggetto + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL RAGGIO DI RACCORDO (da step = 6) + elif self.step == 7: + if self.GetDistClass.run(msgMapTool, msg) == True: + if self.GetDistClass.dist is not None: + self.radius = self.GetDistClass.dist + QadVariables.set(QadMsg.translate("Environment variables", "FILLETRAD"), self.radius) + QadVariables.save() + self.waitForSecondEntSel() # si appresta ad attendere la selezione del secondo oggetto + self.getPointMapTool().refreshSnapType() # aggiorno lo snapType che può essere variato dal maptool di distanza + return False # fine comando \ No newline at end of file diff --git a/cmd/qad_fillet_maptool.py b/cmd/qad_fillet_maptool.py new file mode 100644 index 00000000..001b8670 --- /dev/null +++ b/cmd/qad_fillet_maptool.py @@ -0,0 +1,238 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + classe per gestire il map tool in ambito del comando fillet ok + + ------------------- + begin : 2014-01-31 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from qgis.core import QgsWkbTypes, QgsGeometry + + +from .. import qad_utils +from ..qad_snapper import QadSnapTypeEnum +from ..qad_getpoint import QadGetPoint, QadGetPointSelectionModeEnum, QadGetPointDrawModeEnum +from ..qad_rubberband import QadRubberBand +from ..qad_dim import QadDimStyles +from ..qad_fillet_fun import fillet2QadGeometries, filletAllPartsQadPolyline, filletQadPolyline +from ..qad_multi_geom import fromQadGeomToQgsGeom, getQadGeomAt, setQadGeomAt +from ..qad_geom_relations import getQadGeomClosestPart + + +# =============================================================================== +# Qad_fillet_maptool_ModeEnum class. +# =============================================================================== +class Qad_fillet_maptool_ModeEnum(): + # si richiede la selezione del primo oggetto + ASK_FOR_FIRST_LINESTRING = 1 + # si richiede la selezione del secondo oggetto + ASK_FOR_SECOND_LINESTRING = 2 + # non si richiede niente + NONE = 3 + # si richiede la selezione della polilinea + ASK_FOR_POLYLINE = 4 + + +# =============================================================================== +# Qad_fillet_maptool class +# =============================================================================== +class Qad_fillet_maptool(QadGetPoint): + + def __init__(self, plugIn): + QadGetPoint.__init__(self, plugIn) + + self.filletMode = 1 # modalità di raccordo; 1=Taglia-estendi, 2=Non taglia-estendi + self.radius = 0.0 + + self.layer = None + self.entity1 = None + self.atGeom1 = None + self.atSubGeom1 = None + self.partAt1 = None + self.pointAt1 = None + + self.__rubberBand = QadRubberBand(self.canvas) + + + def hidePointMapToolMarkers(self): + QadGetPoint.hidePointMapToolMarkers(self) + self.__rubberBand.hide() + + def showPointMapToolMarkers(self): + QadGetPoint.showPointMapToolMarkers(self) + self.__rubberBand.show() + + def clear(self): + QadGetPoint.clear(self) + self.__rubberBand.reset() + self.mode = None + + def setEntityInfo(self, entity, atGeom, atSubGeom, partAt, pointAt): + """ + Setta self.entity1, elf.atGeom1, self.atSubGeom1, self.partAt1, self.pointAt1 + """ + self.entity1 = entity + self.atGeom1 = atGeom + self.atSubGeom1 = atSubGeom + self.partAt1 = partAt + self.pointAt1 = pointAt + + + def canvasMoveEvent(self, event): + QadGetPoint.canvasMoveEvent(self, event) + + self.__rubberBand.reset() + newQadGeom = None + + # si richiede la selezione del secondo oggetto + if self.mode == Qad_fillet_maptool_ModeEnum.ASK_FOR_SECOND_LINESTRING: + if self.tmpEntity.isInitialized(): + tmpQadGeom = self.tmpEntity.getQadGeom() + """ + la funzione ritorna una lista con + ( + + + + + <"a sinistra di" se il punto é alla sinista della parte con i seguenti valori: + - < 0 = sinistra (per linea, arco o arco di ellisse) o interno (per cerchi, ellissi) + - > 0 = destra (per linea, arco o arco di ellisse) o esterno (per cerchi, ellissi) + ) + """ + res = getQadGeomClosestPart(tmpQadGeom, self.tmpPoint) + tmpPointAt = res[1] + tmpAtGeom = res[2] + tmpAtSubGeom = res[3] + tmpPartAt = res[4] + + # stessa entità e stessa parte + if self.entity1.layer.id() == self.tmpEntity.layer.id() and \ + self.entity1.featureId == self.tmpEntity.featureId and \ + self.atGeom1 == tmpAtGeom and self.atSubGeom1 == tmpAtSubGeom: + # se anche stessa parte + if self.partAt1 == tmpPartAt: return + subQadGeom = getQadGeomAt(self.entity1.getQadGeom(),self.atGeom1, self.atSubGeom1) + if subQadGeom.whatIs() != "POLYLINE": return + + if self.tmpShiftKey == True: # tasto shift premuto durante il movimento del mouse + # filletMode = 1 # modalità di raccordo; 1=Taglia-estendi + # raggio = 0 + newQadGeom = filletQadPolyline(subQadGeom, self.partAt1, self.pointAt1, tmpPartAt, tmpPointAt, \ + 1, 0) + else: + newQadGeom = filletQadPolyline(subQadGeom, self.partAt1, self.pointAt1, tmpPartAt, tmpPointAt, \ + self.filletMode, self.radius) + + # geometrie diverse + else: + if self.tmpShiftKey == True: # tasto shift premuto durante il movimento del mouse + # filletMode = 1 # modalità di raccordo; 1=Taglia-estendi + # raggio = 0 + res = fillet2QadGeometries(self.entity1.getQadGeom(), self.atGeom1, self.atSubGeom1, self.partAt1, self.pointAt1, \ + tmpQadGeom, tmpAtGeom, tmpAtSubGeom, tmpPartAt, tmpPointAt, \ + 1, 0) + else: + res = fillet2QadGeometries(self.entity1.getQadGeom(), self.atGeom1, self.atSubGeom1, self.partAt1, self.pointAt1, \ + tmpQadGeom, tmpAtGeom, tmpAtSubGeom, tmpPartAt, tmpPointAt, \ + self.filletMode, self.radius) + if res is None: # raccordo non possibile + return + + newQadGeom = res[0] + + # si richiede la selezione della polilinea + elif self.mode == Qad_fillet_maptool_ModeEnum.ASK_FOR_POLYLINE: + if self.tmpEntity.isInitialized(): + tmpQadGeom = self.tmpEntity.getQadGeom() + """ + la funzione ritorna una lista con + ( + + + + + <"a sinistra di" se il punto é alla sinista della parte con i seguenti valori: + - < 0 = sinistra (per linea, arco o arco di ellisse) o interno (per cerchi, ellissi) + - > 0 = destra (per linea, arco o arco di ellisse) o esterno (per cerchi, ellissi) + ) + """ + res = getQadGeomClosestPart(tmpQadGeom, self.tmpPoint) + tmpAtGeom = res[2] + tmpAtSubGeom = res[3] + tmpSubQadGeom = getQadGeomAt(tmpQadGeom, tmpAtGeom, tmpAtSubGeom).copy() + if filletAllPartsQadPolyline(tmpSubQadGeom, self.radius): + newQadGeom = setQadGeomAt(tmpQadGeom, tmpSubQadGeom, tmpAtGeom, tmpAtSubGeom) + + if newQadGeom is not None: + self.__rubberBand.addGeometry(fromQadGeomToQgsGeom(newQadGeom, self.tmpEntity.layer), self.tmpEntity.layer) + + + def activate(self): + QadGetPoint.activate(self) + self.__rubberBand.show() + + def deactivate(self): + try: # necessario perché se si chiude QGIS parte questo evento nonostante non ci sia più l'oggetto maptool ! + QadGetPoint.deactivate(self) + self.__rubberBand.hide() + except: + pass + + def setMode(self, mode): + self.mode = mode + + # si richiede la selezione del primo oggetto + # si richiede la selezione del secondo oggetto + if self.mode == Qad_fillet_maptool_ModeEnum.ASK_FOR_FIRST_LINESTRING or \ + self.mode == Qad_fillet_maptool_ModeEnum.ASK_FOR_SECOND_LINESTRING: + + if self.mode == Qad_fillet_maptool_ModeEnum.ASK_FOR_FIRST_LINESTRING: + self.setSelectionMode(QadGetPointSelectionModeEnum.ENTITY_SELECTION) + else: + self.setSelectionMode(QadGetPointSelectionModeEnum.ENTITY_SELECTION_DYNAMIC) + + # solo layer lineari editabili che non appartengano a quote + layerList = [] + for layer in qad_utils.getVisibleVectorLayers(self.plugIn.canvas): # Tutti i layer vettoriali visibili + if layer.geometryType() == QgsWkbTypes.LineGeometry and layer.isEditable(): + if len(QadDimStyles.getDimListByLayer(layer)) == 0: + layerList.append(layer) + + self.layersToCheck = layerList + self.setSnapType(QadSnapTypeEnum.DISABLE) + # non si richiede niente + elif self.mode == Qad_fillet_maptool_ModeEnum.NONE: + self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + # si richiede la selezione della polilinea + elif self.mode == Qad_fillet_maptool_ModeEnum.ASK_FOR_POLYLINE: + self.setSelectionMode(QadGetPointSelectionModeEnum.ENTITY_SELECTION_DYNAMIC) + + # solo layer lineari o poligono editabili che non appartengano a quote + layerList = [] + for layer in qad_utils.getVisibleVectorLayers(self.plugIn.canvas): # Tutti i layer vettoriali visibili + if (layer.geometryType() == QgsWkbTypes.LineGeometry or layer.geometryType() == QgsWkbTypes.PolygonGeometry) and \ + layer.isEditable(): + if len(QadDimStyles.getDimListByLayer(layer)) == 0: + layerList.append(layer) + + self.layersToCheck = layerList + self.setSnapType(QadSnapTypeEnum.DISABLE) diff --git a/cmd/qad_generic_cmd.py b/cmd/qad_generic_cmd.py new file mode 100644 index 00000000..cd35b480 --- /dev/null +++ b/cmd/qad_generic_cmd.py @@ -0,0 +1,712 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + classe base per un comando + + ------------------- + begin : 2013-05-22 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.PyQt.QtGui import QIcon +from qgis.PyQt.QtCore import QObject +from qgis.PyQt.QtWidgets import QAction, QMenu +from qgis.core import QgsPointXY, QgsGeometry, QgsCoordinateTransform, QgsProject + + +from ..qad_msg import QadMsg +from ..qad_utils import pointToStringFmt +from ..qad_variables import QadVariables +from ..qad_textwindow import QadInputModeEnum, QadInputTypeEnum +from ..qad_getpoint import QadGetPointDrawModeEnum, QadGetPoint +from ..qad_dynamicinput import QadDynamicInputContextEnum +from ..qad_dsettings_dlg import QadDSETTINGSDialog, QadDSETTINGSTabIndexEnum +from ..qad_snapper import QadSnapTypeEnum, snapTypeEnum2Str + + +# Classe che gestisce un comando generico +class QadCommandClass(QObject): # derivato da QObject per gestire il metodo sender() + def showMsg(self, msg, displayPromptAfterMsg = False): + if self.plugIn is not None: + self.plugIn.showMsg(msg, displayPromptAfterMsg) + + def showErr(self, err): + if self.plugIn is not None: + self.plugIn.showErr(err) + + def showInputMsg(self, inputMsg, inputType, default = None, keyWords = "", \ + inputMode = QadInputModeEnum.NONE): + if self.plugIn is not None: + self.plugIn.showInputMsg(inputMsg, inputType, default, keyWords, inputMode) + + # inizializzo il menu contestuale + self.initContextualMenu(inputType, keyWords) + + + def initContextualMenu(self, inputType, keyWords): + if self.plugIn is None: + return + + if self.contextualMenu: + del self.contextualMenu + self.contextualMenu = None + +# if keyWords == "": +# if self.contextualMenu: +# del self.contextualMenu +# self.contextualMenu = None +# return + + self.contextualMenu = QadContextualMenuClass(self.plugIn, inputType, keyWords) + + + def enterActionByContextualMenu(self): + self.plugIn.showEvaluateMsg(None) + + + def cancelActionByContextualMenu(self): + self.plugIn.abortCommand() + + + def showEvaluateMsgByContextualMenu(self): + sender = self.sender() + self.plugIn.showEvaluateMsg(sender.text()) + + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if (self.plugIn is not None): + if self.PointMapTool is None: + self.PointMapTool = QadGetPoint(self.plugIn, drawMode) # per selezione di un punto + return self.PointMapTool + else: + return None + + + def getCurrentContextualMenu(self): + return self.contextualMenu + + + def hidePointMapToolMarkers(self): + if self.PointMapTool is not None: + self.PointMapTool.hidePointMapToolMarkers() + + def setMapTool(self, mapTool): + if self.plugIn is not None: + # setto il maptool per l'input via finestra grafica + self.plugIn.canvas.setMapTool(mapTool) + self.plugIn.mainAction.setChecked(True) + + + def waitForPoint(self, msg = None, \ + default = None, inputMode = QadInputModeEnum.NOT_NULL): + if msg is None: + msg = QadMsg.translate("QAD", "Specify point: ") + self.setMapTool(self.getPointMapTool()) + # setto l'input via finestra di testo + self.showInputMsg(msg, QadInputTypeEnum.POINT2D, default, "", inputMode) + + + def waitForString(self, msg, default = None, inputMode = QadInputModeEnum.NONE): + self.setMapTool(self.getPointMapTool()) + # setto l'input via finestra di testo + self.showInputMsg(msg, QadInputTypeEnum.STRING, default, "", inputMode) + + + def waitForInt(self, msg, default = None, inputMode = QadInputModeEnum.NOT_NULL): + self.setMapTool(self.getPointMapTool()) + # setto l'input via finestra di testo + self.showInputMsg(msg, QadInputTypeEnum.INT, default, "", inputMode) + + + def waitForLong(self, msg, default = None, inputMode = QadInputModeEnum.NOT_NULL): + self.setMapTool(self.getPointMapTool()) + # setto l'input via finestra di testo + self.showInputMsg(msg, QadInputTypeEnum.LONG, default, "", inputMode) + + + def waitForFloat(self, msg, default = None, inputMode = QadInputModeEnum.NOT_NULL): + self.setMapTool(self.getPointMapTool()) + # setto l'input via finestra di testo + self.showInputMsg(msg, QadInputTypeEnum.FLOAT, default, "", inputMode) + + + def waitForBool(self, msg, default = None, inputMode = QadInputModeEnum.NOT_NULL): + self.setMapTool(self.getPointMapTool()) + # setto l'input via finestra di testo + self.showInputMsg(msg, QadInputTypeEnum.BOOL, default, "", inputMode) + + + def waitForSelSet(self, msg = QadMsg.translate("QAD", "Select objects: ")): + self.getPointMapTool().setDrawMode(QadGetPointDrawModeEnum.ELASTIC_RECTANGLE) + self.setMapTool(self.getPointMapTool()) + self.getPointMapTool().getDynamicInput().context = QadDynamicInputContextEnum.NONE + # setto l'input via finestra di testo + self.showInputMsg(msg, QadInputTypeEnum.POINT2D) + + + def waitFor(self, msg, inputType, default = None, keyWords = "", \ + inputMode = QadInputModeEnum.NONE): + self.setMapTool(self.getPointMapTool()) + # setto l'input via finestra di testo + self.showInputMsg(msg, inputType, default, keyWords, inputMode) + + + def getCurrMsgFromTxtWindow(self): + if self.plugIn is not None: + return self.plugIn.getCurrMsgFromTxtWindow() + else: + return None + + def showEvaluateMsg(self, msg = None): + if self.plugIn is not None: + self.plugIn.showEvaluateMsg(msg) + + def runCommandAbortingTheCurrent(self): + self.plugIn.runCommandAbortingTheCurrent(self.getName()) + + def getToolTipText(self): + text = self.getName() + if len(self.getNote()) > 0: + text = text + "\n\n" + self.getNote() + return text + + # ============================================================================ + # funzioni da sovrascrivere con le classi ereditate da questa + # ============================================================================ + def getName(self): + """ impostare il nome del comando in maiuscolo """ + return "" + + def getEnglishName(self): + """ impostare il nome del comando in inglese maiuscolo """ + return "" + + def connectQAction(self, action): + pass + #action.triggered.connect(self.plugIn.runPLINECommand) ad esempio + + def getIcon(self): + # impostare l'icona del comando (es. QIcon(":/plugins/qad/icons/pline.svg")) + # ricordarsi di inserire l'icona in resources.qrc e di ricompilare le risorse + return None + + def getNote(self): + """ impostare le note esplicative del comando """ + return "" + + def __init__(self, plugIn): + QObject.__init__(self) + self.plugIn = plugIn + self.PointMapTool = None + self.step = 0 + self.isValidPreviousInput = True # per gestire il comando anche in macro + self.contextualMenu = None + + # inizializzare tutti i maptool necessari al comando + # esempio di struttura di un comando che richiede + # 1) un punto + # self.mapTool = QadGetPoint(self.plugIn) # per selezione di un punto + + + def __del__(self): + """ distruttore """ + self.hidePointMapToolMarkers() + + if self.PointMapTool: + self.PointMapTool.removeItems() + del self.PointMapTool + self.PointMapTool = None + + if self.contextualMenu: + #QObject.disconnect(enterAction, SIGNAL("triggered()"), self.enterActionByContextualMenu) + + del self.contextualMenu + self.contextualMenu = None + + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return None + + def run(self, msgMapTool = False, msg = None): + """ + Esegue il comando. + - msgMapTool; se True significa che arriva un valore da MapTool del comando + se false significa che il valore é nel parametro msg + - msg; valore in input al comando (usato quando msgMapTool = False) + + ritorna True se il comando é terminato altrimenti False + """ + # esempio di struttura di un comando che richiede + # 1) un punto + if self.step == 0: # inizio del comando + self.waitForPoint() # si appresta ad attendere un punto + self.step = self.step + 1 + return False + elif self.step == 1: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + pt = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + pt = msg + + return True + + def mapToLayerCoordinates(self, layer, point_geom): + # transform point o geometry coordinates from output CRS to layer's CRS + if self.plugIn is None: + return None + if type(point_geom) == QgsPointXY: + return self.plugIn.canvas.mapSettings().mapToLayerCoordinates(layer, point_geom) + + fromCrs = self.plugIn.canvas.mapSettings().destinationCrs() + toCrs = layer.crs() + + if type(point_geom) == QgsGeometry: + if fromCrs == toCrs: + return QgsGeometry(point_geom) + + # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy + coordTransform = QgsCoordinateTransform(self.plugIn.canvas.mapSettings().destinationCrs(), \ + layer.crs(), \ + QgsProject.instance()) + g = QgsGeometry(point_geom) + g.transform(coordTransform) + return g + elif (type(point_geom) == list or type(point_geom) == tuple): # lista di punti o di geometrie + res = [] + if fromCrs == toCrs: + for pt in point_geom: + if type(pt) == QgsPointXY: + res.append(QgsPointXY(pt)) + elif type(pt) == QgsGeometry: + res.append(QgsGeometry(pt)) + return res + + coordTransform = QgsCoordinateTransform(self.plugIn.canvas.mapSettings().destinationCrs(), \ + layer.crs(), \ + QgsProject.instance()) + for pt in point_geom: + if type(pt) == QgsPointXY: + res.append(coordTransform.transform(pt)) + elif type(pt) == QgsGeometry: + g = QgsGeometry(pt) + g.transform(coordTransform) + res.append(g) + return res + else: + return None + + def layerToMapCoordinates(self, layer, point_geom): + # transform point o geometry coordinates from layer's CRS to output CRS + if self.plugIn is None: + return None + if type(point_geom) == QgsPointXY: + return self.plugIn.canvas.mapSettings().layerToMapCoordinates(layer, point_geom) + elif type(point_geom) == QgsGeometry: + # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy + coordTransform = QgsCoordinateTransform(layer.crs(), \ + self.plugIn.canvas.mapSettings().destinationCrs(), \ + QgsProject.instance()) + g = QgsGeometry(point_geom) + g.transform(coordTransform) + return g + elif (type(point_geom) == list or type(point_geom) == tuple): # lista di punti o di geometrie + coordTransform = QgsCoordinateTransform(self.plugIn.canvas.mapSettings().destinationCrs(), \ + layer.crs(), \ + QgsProject.instance()) + res = [] + for pt in point_geom: + if type(pt) == QgsPointXY: + res.append(coordTransform.transform(pt)) + elif type(point_geom) == QgsGeometry: + g = QgsGeometry(point_geom) + g.transform(coordTransform) + res.append(g) + return res + else: + return None + + +# Classe che gestisce il menu contestuale dei comandi di Qad +class QadContextualMenuClass(QMenu): + + def __init__(self, plugIn, inputType, keyWords): + self.plugIn = plugIn + QMenu.__init__(self, self.plugIn.canvas) + self.connections = [] + self.localEnglishKeyWords = [] + self.localKeyWords = [] + self.initActions(inputType, keyWords) + + def __del__(self): + """ distruttore """ + self.delActions() + + + def delActions(self): + # cancello e disconnetto tutte le azioni per gli eventi + for connection in self.connections: + action = connection[0] + slot = connection[1] + action.triggered.disconnect(slot) + del self.connections[:] + + + def initActions(self, inputType, keyWords): + self.delActions() + + msg = QadMsg.translate("ContextualCmdMenu", "Enter") + action = QAction(msg, self) + self.addAction(action) + self.connections.append([action, self.enterActionByContextualMenu]) + + msg = QadMsg.translate("ContextualCmdMenu", "Cancel") + action = QAction(msg, self) + self.addAction(action) + self.connections.append([action, self.cancelActionByContextualMenu]) + + if inputType & QadInputTypeEnum.POINT2D or inputType & QadInputTypeEnum.POINT3D: + msg = QadMsg.translate("ContextualCmdMenu", "Recent Input") + recentPtsMenu = self.addMenu(msg) + + ptsHistory = self.plugIn.ptsHistory + ptsHistoryLen = len(ptsHistory) + i = ptsHistoryLen - 1 + cmdInputHistoryMax = QadVariables.get(QadMsg.translate("Environment variables", "CMDINPUTHISTORYMAX")) + # ciclo sulla storia degli ultimi punti usati + while i >= 0 and (ptsHistoryLen - i) <= cmdInputHistoryMax: + strPt = pointToStringFmt(ptsHistory[i]) + i = i - 1 + action = QAction(strPt, recentPtsMenu) + recentPtsMenu.addAction(action) + self.connections.append([action, self.showEvaluateMsgByContextualMenu]) + + # ciclo sulle opzioni correnti del comando in uso + if len(keyWords) > 0: + # inizializzo la lista di parole chiave contestuale al comando corrente (lingua locale) + # carattere separatore tra le parole chiave in lingua locale e quelle in inglese + self.localEnglishKeyWords = keyWords.split("_") + self.localKeyWords = self.localEnglishKeyWords[0].split("/") # carattere separatore delle parole chiave + + self.addSeparator() + for keyWord in self.localKeyWords: + action = QAction(keyWord, self) + self.addAction(action) + self.connections.append([action, self.showEvaluateMsgByContextualMenu]) + else: # non ci sono opzioni + del self.localEnglishKeyWords[:] # svuoto la lista + del self.localKeyWords[:] # svuoto la lista + + if inputType & QadInputTypeEnum.POINT2D or inputType & QadInputTypeEnum.POINT3D: + self.addSeparator() + osnapMenu = QadOsnapContextualMenuClass(self.plugIn) + self.addMenu(osnapMenu) + + # creo tutte le connessioni per gli eventi + for connection in self.connections: + action = connection[0] + slot = connection[1] + action.triggered.connect(slot) + + + def enterActionByContextualMenu(self): + actualCmd = self.plugIn.QadCommands.actualCommand + if actualCmd is not None: + pointMapTool = actualCmd.getPointMapTool() + if pointMapTool is not None: + dynInput = pointMapTool.getDynamicInput() + if dynInput is not None: + if dynInput.anyLockedValueEdit() == True: + if dynInput.refreshResult() == True: + dynInput.showEvaluateMsg(dynInput.resStr) + return + + self.plugIn.showEvaluateMsg(None) + + + def cancelActionByContextualMenu(self): + self.plugIn.abortCommand() + + + def showEvaluateMsgByContextualMenu(self): + sender = self.sender() + self.plugIn.showEvaluateMsg(sender.text()) + + +# Classe che gestisce il menu contestuale di osnap dei comandi di Qad +class QadOsnapContextualMenuClass(QMenu): + + def __init__(self, plugIn): + self.plugIn = plugIn + title = QadMsg.translate("ContextualCmdMenu", "Snap Overrides") + QMenu.__init__(self, title, self.plugIn.canvas) + self.connections = [] + self.initActions() + + def __del__(self): + """ distruttore """ + self.delActions() + + + def delActions(self): + # cancello e disconnetto tutte le azioni per gli eventi + for connection in self.connections: + action = connection[0] + slot = connection[1] + action.triggered.disconnect(slot) + del self.connections[:] + + + def initActions(self): + self.delActions() + + msg = QadMsg.translate("Snap", "Midpoint between 2 points") + icon = QIcon(":/plugins/qad/icons/osnap_mid2p.svg") + if icon is None: + M2PAction = QAction(msg, self) + else: + M2PAction = QAction(icon, msg, self) + self.addAction(M2PAction) + self.connections.append([M2PAction, self.addM2PActionByPopupMenu]) + + self.addSeparator() + + msg = QadMsg.translate("DSettings_Dialog", "Start / End") + icon = QIcon(":/plugins/qad/icons/osnap_endLine.svg") + if icon is None: + addEndLineSnapTypeAction = QAction(msg, self) + else: + addEndLineSnapTypeAction = QAction(icon, msg, self) + self.addAction(addEndLineSnapTypeAction) + self.connections.append([addEndLineSnapTypeAction, self.addEndLineSnapTypeByPopupMenu]) + + msg = QadMsg.translate("DSettings_Dialog", "Segment Start / End") + icon = QIcon(":/plugins/qad/icons/osnap_end.svg") + if icon is None: + addEndSnapTypeAction = QAction(msg, self) + else: + addEndSnapTypeAction = QAction(icon, msg, self) + self.addAction(addEndSnapTypeAction) + self.connections.append([addEndSnapTypeAction, self.addEndSnapTypeByPopupMenu]) + + msg = QadMsg.translate("DSettings_Dialog", "Middle point") + icon = QIcon(":/plugins/qad/icons/osnap_mid.svg") + if icon is None: + addMidSnapTypeAction = QAction(msg, self) + else: + addMidSnapTypeAction = QAction(icon, msg, self) + self.addAction(addMidSnapTypeAction) + self.connections.append([addMidSnapTypeAction, self.addMidSnapTypeByPopupMenu]) + + msg = QadMsg.translate("DSettings_Dialog", "Intersection") + icon = QIcon(":/plugins/qad/icons/osnap_int.svg") + if icon is None: + addIntSnapTypeAction = QAction(msg, self) + else: + addIntSnapTypeAction = QAction(icon, msg, self) + self.addAction(addIntSnapTypeAction) + self.connections.append([addIntSnapTypeAction, self.addIntSnapTypeByPopupMenu]) + + msg = QadMsg.translate("DSettings_Dialog", "Intersection on extension") + icon = QIcon(":/plugins/qad/icons/osnap_extInt.svg") + if icon is None: + addExtIntSnapTypeAction = QAction(msg, self) + else: + addExtIntSnapTypeAction = QAction(icon, msg, self) + self.addAction(addExtIntSnapTypeAction) + self.connections.append([addExtIntSnapTypeAction, self.addExtIntSnapTypeByPopupMenu]) + + msg = QadMsg.translate("DSettings_Dialog", "Extend") + icon = QIcon(":/plugins/qad/icons/osnap_ext.svg") + if icon is None: + addExtSnapTypeAction = QAction(msg, self) + else: + addExtSnapTypeAction = QAction(icon, msg, self) + self.addAction(addExtSnapTypeAction) + self.connections.append([addExtSnapTypeAction, self.addExtSnapTypeByPopupMenu]) + + self.addSeparator() + + msg = QadMsg.translate("DSettings_Dialog", "Center") + icon = QIcon(":/plugins/qad/icons/osnap_cen.svg") + if icon is None: + addCenSnapTypeAction = QAction(msg, self) + else: + addCenSnapTypeAction = QAction(icon, msg, self) + self.addAction(addCenSnapTypeAction) + self.connections.append([addCenSnapTypeAction, self.addCenSnapTypeByPopupMenu]) + + msg = QadMsg.translate("DSettings_Dialog", "Quadrant") + icon = QIcon(":/plugins/qad/icons/osnap_qua.svg") + if icon is None: + addQuaSnapTypeAction = QAction(msg, self) + else: + addQuaSnapTypeAction = QAction(icon, msg, self) + self.addAction(addQuaSnapTypeAction) + self.connections.append([addQuaSnapTypeAction, self.addQuaSnapTypeByPopupMenu]) + + msg = QadMsg.translate("DSettings_Dialog", "Tangent") + icon = QIcon(":/plugins/qad/icons/osnap_tan.svg") + if icon is None: + addTanSnapTypeAction = QAction(msg, self) + else: + addTanSnapTypeAction = QAction(icon, msg, self) + self.addAction(addTanSnapTypeAction) + self.connections.append([addTanSnapTypeAction, self.addTanSnapTypeByPopupMenu]) + + self.addSeparator() + + msg = QadMsg.translate("DSettings_Dialog", "Perpendicular") + icon = QIcon(":/plugins/qad/icons/osnap_per.svg") + if icon is None: + addPerSnapTypeAction = QAction(msg, self) + else: + addPerSnapTypeAction = QAction(icon, msg, self) + self.addAction(addPerSnapTypeAction) + self.connections.append([addPerSnapTypeAction, self.addPerSnapTypeByPopupMenu]) + + msg = QadMsg.translate("DSettings_Dialog", "Parallel") + icon = QIcon(":/plugins/qad/icons/osnap_par.svg") + if icon is None: + addParSnapTypeAction = QAction(msg, self) + else: + addParSnapTypeAction = QAction(icon, msg, self) + self.addAction(addParSnapTypeAction) + self.connections.append([addParSnapTypeAction, self.addParSnapTypeByPopupMenu]) + + msg = QadMsg.translate("DSettings_Dialog", "Node") + icon = QIcon(":/plugins/qad/icons/osnap_nod.svg") + if icon is None: + addNodSnapTypeAction = QAction(msg, self) + else: + addNodSnapTypeAction = QAction(icon, msg, self) + self.addAction(addNodSnapTypeAction) + self.connections.append([addNodSnapTypeAction, self.addNodSnapTypeByPopupMenu]) + + msg = QadMsg.translate("DSettings_Dialog", "Near") + icon = QIcon(":/plugins/qad/icons/osnap_nea.svg") + if icon is None: + addNeaSnapTypeAction = QAction(msg, self) + else: + addNeaSnapTypeAction = QAction(icon, msg, self) + self.addAction(addNeaSnapTypeAction) + self.connections.append([addNeaSnapTypeAction, self.addNeaSnapTypeByPopupMenu]) + + msg = QadMsg.translate("DSettings_Dialog", "Progressive") + icon = QIcon(":/plugins/qad/icons/osnap_pr.svg") + if icon is None: + addPrSnapTypeAction = QAction(msg, self) + else: + addPrSnapTypeAction = QAction(icon, msg, self) + self.addAction(addPrSnapTypeAction) + self.connections.append([addPrSnapTypeAction, self.addPrSnapTypeByPopupMenu]) + + msg = QadMsg.translate("DSettings_Dialog", "None") + icon = QIcon(":/plugins/qad/icons/osnap_disable.svg") + if icon is None: + setSnapTypeToDisableAction = QAction(msg, self) + else: + setSnapTypeToDisableAction = QAction(icon, msg, self) + self.addAction(setSnapTypeToDisableAction) + self.connections.append([setSnapTypeToDisableAction, self.setSnapTypeToDisableByPopupMenu]) + + self.addSeparator() + + msg = QadMsg.translate("DSettings_Dialog", "Object snap settings...") + icon = QIcon(":/plugins/qad/icons/dsettings.svg") + if icon is None: + DSettingsAction = QAction(msg, self) + else: + DSettingsAction = QAction(icon, msg, self) + self.addAction(DSettingsAction) + self.connections.append([DSettingsAction, self.showDSettingsByPopUpMenu]) + + # creo tutte le connessioni per gli eventi + for connection in self.connections: + action = connection[0] + slot = connection[1] + action.triggered.connect(slot) + + + # ============================================================================ + # addSnapTypeByPopupMenu + # ============================================================================ + def addSnapTypeByPopupMenu(self, _snapType): + # la funzione deve impostare lo snap ad oggetto solo temporaneamente + str = snapTypeEnum2Str(_snapType) + self.plugIn.showEvaluateMsg(str) + return +# value = QadVariables.get(QadMsg.translate("Environment variables", "OSMODE")) +# if value & QadSnapTypeEnum.DISABLE: +# value = value - QadSnapTypeEnum.DISABLE +# QadVariables.set(QadMsg.translate("Environment variables", "OSMODE"), value | _snapType) +# QadVariables.save() +# self.plugIn.refreshCommandMapToolSnapType() + + def addM2PActionByPopupMenu(self): + self.plugIn.showEvaluateMsg("_M2P") + def addEndLineSnapTypeByPopupMenu(self): + self.addSnapTypeByPopupMenu(QadSnapTypeEnum.END_PLINE) + def addEndSnapTypeByPopupMenu(self): + self.addSnapTypeByPopupMenu(QadSnapTypeEnum.END) + def addMidSnapTypeByPopupMenu(self): + self.addSnapTypeByPopupMenu(QadSnapTypeEnum.MID) + def addIntSnapTypeByPopupMenu(self): + self.addSnapTypeByPopupMenu(QadSnapTypeEnum.INT) + def addExtIntSnapTypeByPopupMenu(self): + self.addSnapTypeByPopupMenu(QadSnapTypeEnum.EXT_INT) + def addExtSnapTypeByPopupMenu(self): + self.addSnapTypeByPopupMenu(QadSnapTypeEnum.EXT) + def addCenSnapTypeByPopupMenu(self): + self.addSnapTypeByPopupMenu(QadSnapTypeEnum.CEN) + def addQuaSnapTypeByPopupMenu(self): + self.addSnapTypeByPopupMenu(QadSnapTypeEnum.QUA) + def addTanSnapTypeByPopupMenu(self): + self.addSnapTypeByPopupMenu(QadSnapTypeEnum.TAN) + def addPerSnapTypeByPopupMenu(self): + self.addSnapTypeByPopupMenu(QadSnapTypeEnum.PER) + def addParSnapTypeByPopupMenu(self): + self.addSnapTypeByPopupMenu(QadSnapTypeEnum.PAR) + def addNodSnapTypeByPopupMenu(self): + self.addSnapTypeByPopupMenu(QadSnapTypeEnum.NOD) + def addNeaSnapTypeByPopupMenu(self): + self.addSnapTypeByPopupMenu(QadSnapTypeEnum.NEA) + def addPrSnapTypeByPopupMenu(self): + self.addSnapTypeByPopupMenu(QadSnapTypeEnum.PR) + + def setSnapTypeToDisableByPopupMenu(self): + value = QadVariables.get(QadMsg.translate("Environment variables", "OSMODE")) + QadVariables.set(QadMsg.translate("Environment variables", "OSMODE"), value | QadSnapTypeEnum.DISABLE) + QadVariables.save() + self.plugIn.refreshCommandMapToolSnapType() + + def showDSettingsByPopUpMenu(self): + d = QadDSETTINGSDialog(self.plugIn) + d.exec_() + self.plugIn.refreshCommandMapToolSnapType() + self.plugIn.refreshCommandMapToolAutoSnap() + self.plugIn.refreshCommandMapToolDynamicInput() diff --git a/qad_getangle_cmd.py b/cmd/qad_getangle_cmd.py similarity index 84% rename from qad_getangle_cmd.py rename to cmd/qad_getangle_cmd.py index db47dbc4..52ded847 100644 --- a/qad_getangle_cmd.py +++ b/cmd/qad_getangle_cmd.py @@ -1,154 +1,153 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - comando da inserire in altri comandi per la richiesta di un angolo - - ------------------- - begin : 2013-12-04 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -from qad_generic_cmd import QadCommandClass -from qad_msg import QadMsg -from qad_textwindow import * -from qad_entity import * -from qad_getpoint import * -import qad_utils - - -#=============================================================================== -# QadGetAngleClass -#=============================================================================== -class QadGetAngleClass(QadCommandClass): - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadGetAngleClass(self.plugIn) - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - self.entity = QadEntity() - self.startPt = None - self.msg = QadMsg.translate("QAD", "Specify angle: ") - self.angle = None # in radianti - # memorizzo last point perchè il/i punto/i indicato/i da questa questa funzione non devono - # alterare lastpoint - self.__prevLastPoint = self.plugIn.lastPoint - - def run(self, msgMapTool = False, msg = None): - if self.plugIn.canvas.mapRenderer().destinationCrs().geographicFlag(): - self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) - return True # fine comando - - #========================================================================= - # RICHIESTA PUNTO o ENTITA' - if self.step == 0: # inizio del comando - # si appresta ad attendere un punto o un numero reale - # msg, inputType, default, keyWords, valori non nulli - self.waitFor(self.msg, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT, \ - self.angle, "", \ - QadInputModeEnum.NOT_NULL) - - if self.startPt is not None: - # imposto il map tool - self.getPointMapTool().setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.getPointMapTool().setStartPoint(self.startPt) - - self.step = 1 - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA PUNTO O NUMERO REALE - elif self.step == 1: # dopo aver atteso un punto si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto o il numero reale arriva come parametro della funzione - value = msg - - if value is None: - return True # fine comando - - if type(value) == float: - self.angle = qad_utils.toRadians(value) - return True # fine comando - elif type(value) == QgsPoint: - # il/i punto/i indicato/i da questa questa funzione non devono alterare lastpoint - self.plugIn.setLastPoint(self.__prevLastPoint) - - if self.startPt is not None: - self.angle = qad_utils.getAngleBy2Pts(self.startPt, value) - return True # fine comando - else: - self.startPt = value - # imposto il map tool - self.getPointMapTool().setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.getPointMapTool().setStartPoint(self.startPt) - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("QAD", "Specify second point: ")) - self.step = 2 - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA SECONDO PUNTO DELLA ANGOLO (da step = 1) - elif self.step == 2: # dopo aver atteso un punto si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - # il/i punto/i indicato/i da questa questa funzione non devono alterare lastpoint - self.plugIn.setLastPoint(self.__prevLastPoint) - - if qad_utils.ptNear(self.startPt, value): - self.showMsg(QadMsg.translate("QAD", "\nThe points must be different.")) - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("QAD", "Specify second point: ")) - return False - else: - self.angle = qad_utils.getAngleBy2Pts(self.startPt, value) +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + comando da inserire in altri comandi per la richiesta di un angolo + + ------------------- + begin : 2013-12-04 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.core import QgsPointXY + + +from .qad_generic_cmd import QadCommandClass +from ..qad_msg import QadMsg +from ..qad_textwindow import QadInputTypeEnum, QadInputModeEnum +from ..qad_entity import QadEntity +from ..qad_getpoint import QadGetPointDrawModeEnum +from .. import qad_utils + + +# =============================================================================== +# QadGetAngleClass +# =============================================================================== +class QadGetAngleClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadGetAngleClass(self.plugIn) + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + self.entity = QadEntity() + self.startPt = None + self.msg = QadMsg.translate("QAD", "Specify angle: ") + self.angle = None # in radianti + # memorizzo last point perchè il/i punto/i indicato/i da questa questa funzione non devono + # alterare lastpoint + self.__prevLastPoint = self.plugIn.lastPoint + + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + # ========================================================================= + # RICHIESTA PUNTO o ENTITA' + if self.step == 0: # inizio del comando + if self.startPt is not None: + # imposto il map tool + self.getPointMapTool().setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.getPointMapTool().setStartPoint(self.startPt) + + # si appresta ad attendere un punto o un numero reale + # msg, inputType, default, keyWords, valori non nulli + self.waitFor(self.msg, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.ANGLE, \ + self.angle, "", \ + QadInputModeEnum.NOT_NULL) + + self.step = 1 + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA PUNTO O NUMERO REALE + elif self.step == 1: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto o il numero reale arriva come parametro della funzione + value = msg + + if value is None: + return True # fine comando + + if type(value) == float: + self.angle = qad_utils.toRadians(value) + return True # fine comando + elif type(value) == QgsPointXY: + # il/i punto/i indicato/i da questa questa funzione non devono alterare lastpoint + self.plugIn.setLastPoint(self.__prevLastPoint) + + if self.startPt is not None: + self.angle = qad_utils.getAngleBy2Pts(self.startPt, value) + return True # fine comando + else: + self.startPt = value + # imposto il map tool + self.getPointMapTool().setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.getPointMapTool().setStartPoint(self.startPt) + prompt = QadMsg.translate("QAD", "Specify second point: ") + # si appresta ad attendere un punto + self.waitForPoint(prompt) + self.step = 2 + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA SECONDO PUNTO DELLA ANGOLO (da step = 1) + elif self.step == 2: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + # il/i punto/i indicato/i da questa questa funzione non devono alterare lastpoint + self.plugIn.setLastPoint(self.__prevLastPoint) + + if qad_utils.ptNear(self.startPt, value): + self.showMsg(QadMsg.translate("QAD", "\nThe points must be different.")) + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("QAD", "Specify second point: ")) + return False + else: + self.angle = qad_utils.getAngleBy2Pts(self.startPt, value) return True # fine comando \ No newline at end of file diff --git a/qad_getdist_cmd.py b/cmd/qad_getdist_cmd.py similarity index 85% rename from qad_getdist_cmd.py rename to cmd/qad_getdist_cmd.py index 3a590536..a45d71c2 100644 --- a/qad_getdist_cmd.py +++ b/cmd/qad_getdist_cmd.py @@ -1,151 +1,152 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - comando da inserire in altri comandi per la richiesta di una distanza - - ------------------- - begin : 2013-12-03 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa gggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -from qad_generic_cmd import QadCommandClass -from qad_msg import QadMsg -from qad_textwindow import * -from qad_getpoint import * -import qad_utils - - -#=============================================================================== -# QadGetDistClass -#=============================================================================== -class QadGetDistClass(QadCommandClass): - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadGetDistClass(self.plugIn) - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - self.entity = QadEntity() - self.startPt = None - self.msg = QadMsg.translate("QAD", "Specify the distance: ") - self.dist = None - self.inputMode = QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE - self.ctrlKey = False - - # memorizzo last point perchè il/i punto/i indicato/i da questa questa funzione non devono - # alterare lastpoint - self.__prevLastPoint = self.plugIn.lastPoint - - def run(self, msgMapTool = False, msg = None): - if self.plugIn.canvas.mapRenderer().destinationCrs().geographicFlag(): - self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) - return True # fine comando - - #========================================================================= - # RICHIESTA PUNTO o ENTITA' - if self.step == 0: # inizio del comando - # si appresta ad attendere un punto o un numero reale - # msg, inputType, default, keyWords, valori positivi - self.waitFor(self.msg, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT, \ - self.dist, "", \ - QadInputModeEnum.NOT_NULL | self.inputMode) - - if self.startPt is not None: - # imposto il map tool - self.getPointMapTool().setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.getPointMapTool().setStartPoint(self.startPt) - - self.step = 1 - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA PUNTO o numero reale - elif self.step == 1: # dopo aver atteso un punto si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - self.ctrlKey = self.getPointMapTool().ctrlKey - else: # il punto o il numero reale arriva come parametro della funzione - value = msg - - if value is None: - return True # fine comando - - if type(value) == float: - self.dist = value - return True # fine comando - elif type(value) == QgsPoint: - # il/i punto/i indicato/i da questa questa funzione non devono alterare lastpoint - self.plugIn.setLastPoint(self.__prevLastPoint) - - if self.startPt is not None: - self.dist = qad_utils.getDistance(self.startPt, value) - return True # fine comando - else: - self.startPt = value - # imposto il map tool - self.getPointMapTool().setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.getPointMapTool().setStartPoint(self.startPt) - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("QAD", "Specify second point: ")) - self.step = 2 - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA SECONDO PUNTO DELLA DISTANZA (da step = 1) - elif self.step == 2: # dopo aver atteso un punto si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - # il/i punto/i indicato/i da questa questa funzione non devono alterare lastpoint - self.plugIn.setLastPoint(self.__prevLastPoint) - - self.dist = qad_utils.getDistance(self.startPt, value) +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + comando da inserire in altri comandi per la richiesta di una distanza + + ------------------- + begin : 2013-12-03 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa gggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.core import QgsPointXY + + +from .qad_generic_cmd import QadCommandClass +from ..qad_msg import QadMsg +from ..qad_textwindow import QadInputModeEnum, QadInputTypeEnum +from ..qad_getpoint import QadGetPointDrawModeEnum +from .. import qad_utils +from ..qad_entity import QadEntity + + +# =============================================================================== +# QadGetDistClass +# =============================================================================== +class QadGetDistClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadGetDistClass(self.plugIn) + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + self.entity = QadEntity() + self.startPt = None + self.msg = QadMsg.translate("QAD", "Specify the distance: ") + self.dist = None + self.inputMode = QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE + self.ctrlKey = False + + # memorizzo last point perchè il/i punto/i indicato/i da questa questa funzione non devono + # alterare lastpoint + self.__prevLastPoint = self.plugIn.lastPoint + + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + # ========================================================================= + # RICHIESTA PUNTO o ENTITA' + if self.step == 0: # inizio del comando + # si appresta ad attendere un punto o un numero reale + # msg, inputType, default, keyWords, valori positivi + self.waitFor(self.msg, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT, \ + self.dist, "", \ + QadInputModeEnum.NOT_NULL | self.inputMode) + + if self.startPt is not None: + # imposto il map tool + self.getPointMapTool().setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.getPointMapTool().setStartPoint(self.startPt) + + self.step = 1 + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA PUNTO o numero reale + elif self.step == 1: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + self.ctrlKey = self.getPointMapTool().ctrlKey + else: # il punto o il numero reale arriva come parametro della funzione + value = msg + + if value is None: + return True # fine comando + + if type(value) == float: + self.dist = value + return True # fine comando + elif type(value) == QgsPointXY: + # il/i punto/i indicato/i da questa questa funzione non devono alterare lastpoint + self.plugIn.setLastPoint(self.__prevLastPoint) + + if self.startPt is not None: + self.dist = qad_utils.getDistance(self.startPt, value) + return True # fine comando + else: + self.startPt = value + # imposto il map tool + self.getPointMapTool().setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.getPointMapTool().setStartPoint(self.startPt) + + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("QAD", "Specify second point: ")) + + self.step = 2 + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA SECONDO PUNTO DELLA DISTANZA (da step = 1) + elif self.step == 2: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + # il/i punto/i indicato/i da questa questa funzione non devono alterare lastpoint + self.plugIn.setLastPoint(self.__prevLastPoint) + + self.dist = qad_utils.getDistance(self.startPt, value) return True # fine comando \ No newline at end of file diff --git a/cmd/qad_help_cmd.py b/cmd/qad_help_cmd.py new file mode 100644 index 00000000..860f9d77 --- /dev/null +++ b/cmd/qad_help_cmd.py @@ -0,0 +1,99 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + comando HELP che apre la guida di QAD + + ------------------- + begin : 2015-08-31 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.PyQt.QtGui import * +from qgis.core import QgsApplication +from qgis.PyQt.QtCore import QDir +import pathlib + +from ..qad_utils import getMacAddress, getQADPath +from .qad_generic_cmd import QadCommandClass +from ..qad_msg import QadMsg, qadShowPluginPDFHelp, qadShowSupportersPage + + +# Classe che gestisce il comando HELP +class QadHELPCommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadHELPCommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "HELP") + + def getEnglishName(self): + return "HELP" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runHELPCommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/help.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_HELP", "The QAD manual will be showed.") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + + def run(self, msgMapTool = False, msg = None): + qadShowPluginPDFHelp() + return True + + +# Classe che gestisce il comando SUPPORTERS +class QadSUPPORTERSCommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadSUPPORTERSCommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "SUPPORTERS") + + def getEnglishName(self): + return "SUPPORTERS" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runSUPPORTERSCommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/supporters.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_SUPPORTERS", "The QAD supporting members page will be showed.") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + + def run(self, msgMapTool = False, msg = None): + msg1 = QadMsg.translate("Command_SUPPORTERS", "Your mac address is") + msg2 = QadMsg.translate("Command_SUPPORTERS", "QAD installation path is") + self.showMsg("\n" + msg1 + " " + getMacAddress() + ". " + msg2 + " " + getQADPath()) + qadShowSupportersPage() + return True diff --git a/qad_id_cmd.py b/cmd/qad_id_cmd.py similarity index 85% rename from qad_id_cmd.py rename to cmd/qad_id_cmd.py index 48c7d8a8..4d5642e1 100644 --- a/qad_id_cmd.py +++ b/cmd/qad_id_cmd.py @@ -1,83 +1,86 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - comando ID che restituisce la coordinata di un punto selezionato - - ------------------- - begin : 2013-05-22 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -from qad_generic_cmd import QadCommandClass -from qad_msg import QadMsg - - -# Classe che gestisce il comando ID -class QadIDCommandClass(QadCommandClass): - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadIDCommandClass(self.plugIn) - - def getName(self): - return QadMsg.translate("Command_list", "ID") - - def getEnglishName(self): - return "ID" - - def connectQAction(self, action): - QObject.connect(action, SIGNAL("triggered()"), self.plugIn.runIDCommand) - - def getNote(self): - # impostare le note esplicative del comando - return QadMsg.translate("Command_ID", "Displays the coordinate values of a specified location.") - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - - def run(self, msgMapTool = False, msg = None): - if self.step == 0: # inizio del comando - self.waitForPoint() # si appresta ad attendere un punto - self.step = self.step + 1 - return False - elif self.step == 1: # dopo aver atteso un punto si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - pt = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - pt = msg - - self.plugIn.setLastPoint(pt) - self.showMsg("\n" + pt.toString()) +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + comando ID che restituisce la coordinata di un punto selezionato + + ------------------- + begin : 2013-05-22 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries + +from qgis.core import QgsPointXY +from qgis.PyQt.QtGui import QIcon + +from .qad_generic_cmd import QadCommandClass +from ..qad_msg import QadMsg + + +# Classe che gestisce il comando ID +class QadIDCommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadIDCommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "ID") + + def getEnglishName(self): + return "ID" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runIDCommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/id.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_ID", "Displays the coordinate values of a specified location.") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + + def run(self, msgMapTool = False, msg = None): + if self.step == 0: # inizio del comando + self.waitForPoint() # si appresta ad attendere un punto + self.step = self.step + 1 + return False + elif self.step == 1: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + pt = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + pt = msg + + if type(pt) == QgsPointXY: + self.plugIn.setLastPoint(pt) + self.showMsg("\n" + pt.toString()) return True \ No newline at end of file diff --git a/qad_insert_cmd.py b/cmd/qad_insert_cmd.py similarity index 78% rename from qad_insert_cmd.py rename to cmd/qad_insert_cmd.py index f3b39593..0ea5daf9 100644 --- a/qad_insert_cmd.py +++ b/cmd/qad_insert_cmd.py @@ -1,239 +1,254 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - comando INSERT per inserire un simbolo - - ------------------- - begin : 2013-12-31 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -import qad_utils -from qad_generic_cmd import QadCommandClass -import qad_layer -from qad_getpoint import * -from qad_getdist_cmd import QadGetDistClass -from qad_getangle_cmd import QadGetAngleClass -from qad_textwindow import * -from qad_msg import QadMsg - - -# Classe che gestisce il comando INSERT -class QadINSERTCommandClass(QadCommandClass): - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadINSERTCommandClass(self.plugIn) - - def getName(self): - return QadMsg.translate("Command_list", "INSERT") - - def getEnglishName(self): - return "INSERT" - - def connectQAction(self, action): - QObject.connect(action, SIGNAL("triggered()"), self.plugIn.runINSERTCommand) - - def getIcon(self): - return QIcon(":/plugins/qad/icons/insert.png") - - def getNote(self): - # impostare le note esplicative del comando - return QadMsg.translate("Command_INSERT", "Insert a symbol.") - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - self.insPt = None - self.scale = self.plugIn.lastScale - self.rot = self.plugIn.lastRot - self.GetDistClass = None - self.GetAngleClass = None - - def __del__(self): - QadCommandClass.__del__(self) - if self.GetDistClass is not None: - del self.GetDistClass - if self.GetAngleClass is not None: - del self.GetAngleClass - - def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): - # quando si é in fase di richiesta distanza (scala) - if self.step == 2: - return self.GetDistClass.getPointMapTool() - # quando si é in fase di richiesta rotazione - elif self.step == 3: - return self.GetAngleClass.getPointMapTool() - else: - return QadCommandClass.getPointMapTool(self, drawMode) - - def addFeature(self, layer): - transformedPoint = self.mapToLayerCoordinates(layer, self.insPt) - g = QgsGeometry.fromPoint(transformedPoint) - f = QgsFeature() - f.setGeometry(g) - # Add attribute fields to feature. - fields = layer.pendingFields() - f.setFields(fields) - - # assegno i valori di default - provider = layer.dataProvider() - for field in fields.toList(): - i = fields.indexFromName(field.name()) - f[field.name()] = provider.defaultValue(i) - - # se la scala dipende da un campo - scaleFldName = qad_layer.get_symbolScaleFieldName(layer) - if len(scaleFldName) > 0: - f.setAttribute(scaleFldName, self.scale) - - # se la rotazione dipende da un campo - rotFldName = qad_layer.get_symbolRotationFieldName(layer) - if len(rotFldName) > 0: - f.setAttribute(rotFldName, qad_utils.toDegrees(self.rot)) - - return qad_layer.addFeatureToLayer(self.plugIn, layer, f) - - - def run(self, msgMapTool = False, msg = None): - if self.plugIn.canvas.mapRenderer().destinationCrs().geographicFlag(): - self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) - return True # fine comando - - currLayer, errMsg = qad_layer.getCurrLayerEditable(self.plugIn.canvas, QGis.Point) - if currLayer is None: - self.showErr(errMsg) - return True # fine comando - - if qad_layer.isSymbolLayer(currLayer) == False: - errMsg = QadMsg.translate("QAD", "\nCurrent layer is not a symbol layer.") - errMsg = errMsg + QadMsg.translate("QAD", "\nA symbol layer is a vectorial punctual layer without label.\n") - self.showErr(errMsg) - return True # fine comando - - - #========================================================================= - # RICHIESTA PUNTO DI INSERIMENTO - if self.step == 0: # inizio del comando - self.waitForPoint() # si appresta ad attendere un punto - self.step = self.step + 1 - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA PUNTO DI INSERIMENTO - elif self.step == 1: # dopo aver atteso un punto si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - pt = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - pt = msg - - self.insPt = QgsPoint(pt) - self.plugIn.setLastPoint(self.insPt) - - # se la scala dipende da un campo - scaleFldName = qad_layer.get_symbolScaleFieldName(currLayer) - if len(scaleFldName) > 0: - # si appresta ad attendere la scala - self.GetDistClass = QadGetDistClass(self.plugIn) - prompt = QadMsg.translate("Command_INSERT", "Specify the symbol scale <{0}>: ") - self.GetDistClass.msg = prompt.format(str(self.scale)) - self.GetDistClass.dist = self.scale - self.GetDistClass.inputMode = QadInputModeEnum.NOT_NEGATIVE | QadInputModeEnum.NOT_ZERO - self.GetDistClass.startPt = self.insPt - self.step = 2 - self.GetDistClass.run(msgMapTool, msg) - return False - else: - # se la rotazione dipende da un campo - rotFldName = qad_layer.get_symbolRotationFieldName(currLayer) - if len(rotFldName) > 0: - if self.GetAngleClass is not None: - del self.GetAngleClass - # si appresta ad attendere l'angolo di rotazione - self.GetAngleClass = QadGetAngleClass(self.plugIn) - prompt = QadMsg.translate("Command_INSERT", "Specify the symbol rotation <{0}>: ") - self.GetAngleClass.msg = prompt.format(str(qad_utils.toDegrees(self.rot))) - self.GetAngleClass.angle = self.rot - self.GetAngleClass.startPt = self.insPt - self.step = 3 - self.GetAngleClass.run(msgMapTool, msg) - return False - else: - self.addFeature(currLayer) - - return True - - #========================================================================= - # RISPOSTA ALLA RICHIESTA SCALA (da step = 1) - elif self.step == 2: - if self.GetDistClass.run(msgMapTool, msg) == True: - if self.GetDistClass.dist is not None: - self.scale = self.GetDistClass.dist - self.plugIn.setLastScale(self.scale) - del self.GetDistClass - self.GetDistClass = None - - # se la rotazione dipende da un campo - rotFldName = qad_layer.get_symbolRotationFieldName(currLayer) - if len(rotFldName) > 0: - if self.GetAngleClass is not None: - del self.GetAngleClass - # si appresta ad attendere l'angolo di rotazione - self.GetAngleClass = QadGetAngleClass(self.plugIn) - prompt = QadMsg.translate("Command_INSERT", "Specify the symbol rotation <{0}>: ") - self.GetAngleClass.msg = prompt.format(str(qad_utils.toDegrees(self.rot))) - self.GetAngleClass.angle = self.rot - self.GetAngleClass.startPt = self.insPt - self.step = 3 - self.GetAngleClass.run(msgMapTool, msg) - return False - else: - self.addFeature(currLayer) - return True - else: - return True - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA ROTAZIONE (da step = 1 o 2) - elif self.step == 3: - if self.GetAngleClass.run(msgMapTool, msg) == True: - if self.GetAngleClass.angle is not None: - self.rot = self.GetAngleClass.angle - self.plugIn.setLastRot(self.rot) - self.addFeature(currLayer) - return True # fine comando - else: - return True - return False +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin ok + + comando INSERT per inserire un simbolo + + ------------------- + begin : 2013-12-31 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.core import QgsGeometry, QgsFeature, QgsWkbTypes, QgsPointXY, QgsVectorLayerUtils +from qgis.PyQt.QtGui import QIcon + + +from .. import qad_utils +from .qad_generic_cmd import QadCommandClass +from .. import qad_layer +from ..qad_getpoint import QadGetPointDrawModeEnum +from .qad_getdist_cmd import QadGetDistClass +from .qad_getangle_cmd import QadGetAngleClass +from ..qad_textwindow import QadInputModeEnum +from ..qad_msg import QadMsg +from ..qad_point import QadPoint + + +# Classe che gestisce il comando INSERT +class QadINSERTCommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadINSERTCommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "INSERT") + + def getEnglishName(self): + return "INSERT" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runINSERTCommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/insert.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_INSERT", "Insert a symbol.") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + self.insPt = None + self.scale = self.plugIn.lastScale + self.rot = self.plugIn.lastRot + self.GetDistClass = None + self.GetAngleClass = None + + def __del__(self): + QadCommandClass.__del__(self) + if self.GetDistClass is not None: + del self.GetDistClass + if self.GetAngleClass is not None: + del self.GetAngleClass + + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + # quando si é in fase di richiesta distanza (scala) + if self.step == 2: + return self.GetDistClass.getPointMapTool() + # quando si é in fase di richiesta rotazione + elif self.step == 3: + return self.GetAngleClass.getPointMapTool() + else: + return QadCommandClass.getPointMapTool(self, drawMode) + + + def getCurrentContextualMenu(self): + # quando si é in fase di richiesta distanza (scala) + if self.step == 2: + return self.GetDistClass.getCurrentContextualMenu() + # quando si é in fase di richiesta rotazione + elif self.step == 3: + return self.GetAngleClass.getCurrentContextualMenu() + else: + return self.contextualMenu + + + def addFeature(self, layer): + pt = QadPoint(self.insPt) + g = self.mapToLayerCoordinates(layer, pt.asGeom(layer.wkbType())) + f = QgsVectorLayerUtils.createFeature(layer, g, {}, layer.createExpressionContext()) + # f = QgsFeature() + #f.setGeometry(g) + # Add attribute fields to feature. + #fields = layer.fields() + #f.setFields(fields) + + # # assegno i valori di default + # provider = layer.dataProvider() + # for field in fields.toList(): + # i = fields.indexFromName(field.name()) + # f[field.name()] = provider.defaultValue(i) + + + # se la scala dipende da un campo + scaleFldName = qad_layer.get_symbolScaleFieldName(layer) + if len(scaleFldName) > 0: + f.setAttribute(scaleFldName, self.scale) + + # se la rotazione dipende da un campo + rotFldName = qad_layer.get_symbolRotationFieldName(layer) + if len(rotFldName) > 0: + f.setAttribute(rotFldName, qad_utils.toDegrees(self.rot)) + + return qad_layer.addFeatureToLayer(self.plugIn, layer, f) + + + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + currLayer, errMsg = qad_layer.getCurrLayerEditable(self.plugIn.canvas, QgsWkbTypes.PointGeometry) + if currLayer is None: + self.showErr(errMsg) + return True # fine comando + + if qad_layer.isSymbolLayer(currLayer) == False: + errMsg = QadMsg.translate("QAD", "\nCurrent layer is not a symbol layer.") + errMsg = errMsg + QadMsg.translate("QAD", "\nA symbol layer is a vector punctual layer without label.\n") + self.showErr(errMsg) + return True # fine comando + + + # ========================================================================= + # RICHIESTA PUNTO DI INSERIMENTO + if self.step == 0: # inizio del comando + self.waitForPoint() # si appresta ad attendere un punto + self.step = self.step + 1 + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA PUNTO DI INSERIMENTO + elif self.step == 1: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + pt = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + pt = msg + + self.insPt = QgsPointXY(pt) + self.plugIn.setLastPoint(self.insPt) + + # se la scala dipende da un campo + scaleFldName = qad_layer.get_symbolScaleFieldName(currLayer) + if len(scaleFldName) > 0: + # si appresta ad attendere la scala + self.GetDistClass = QadGetDistClass(self.plugIn) + prompt = QadMsg.translate("Command_INSERT", "Specify the symbol scale <{0}>: ") + self.GetDistClass.msg = prompt.format(str(self.scale)) + self.GetDistClass.dist = self.scale + self.GetDistClass.inputMode = QadInputModeEnum.NOT_NEGATIVE | QadInputModeEnum.NOT_ZERO + self.GetDistClass.startPt = self.insPt + self.step = 2 + self.GetDistClass.run(msgMapTool, msg) + return False + else: + # se la rotazione dipende da un campo + rotFldName = qad_layer.get_symbolRotationFieldName(currLayer) + if len(rotFldName) > 0: + if self.GetAngleClass is not None: + del self.GetAngleClass + # si appresta ad attendere l'angolo di rotazione + self.GetAngleClass = QadGetAngleClass(self.plugIn) + prompt = QadMsg.translate("Command_INSERT", "Specify the symbol rotation <{0}>: ") + self.GetAngleClass.msg = prompt.format(str(qad_utils.toDegrees(self.rot))) + self.GetAngleClass.angle = self.rot + self.GetAngleClass.startPt = self.insPt + self.step = 3 + self.GetAngleClass.run(msgMapTool, msg) + return False + else: + self.addFeature(currLayer) + + return True + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA SCALA (da step = 1) + elif self.step == 2: + if self.GetDistClass.run(msgMapTool, msg) == True: + if self.GetDistClass.dist is not None: + self.scale = self.GetDistClass.dist + self.plugIn.setLastScale(self.scale) + del self.GetDistClass + self.GetDistClass = None + + # se la rotazione dipende da un campo + rotFldName = qad_layer.get_symbolRotationFieldName(currLayer) + if len(rotFldName) > 0: + if self.GetAngleClass is not None: + del self.GetAngleClass + # si appresta ad attendere l'angolo di rotazione + self.GetAngleClass = QadGetAngleClass(self.plugIn) + prompt = QadMsg.translate("Command_INSERT", "Specify the symbol rotation <{0}>: ") + self.GetAngleClass.msg = prompt.format(str(qad_utils.toDegrees(self.rot))) + self.GetAngleClass.angle = self.rot + self.GetAngleClass.startPt = self.insPt + self.step = 3 + self.GetAngleClass.run(msgMapTool, msg) + return False + else: + self.addFeature(currLayer) + return True + else: + return True + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA ROTAZIONE (da step = 1 o 2) + elif self.step == 3: + if self.GetAngleClass.run(msgMapTool, msg) == True: + if self.GetAngleClass.angle is not None: + self.rot = self.GetAngleClass.angle + self.plugIn.setLastRot(self.rot) + self.addFeature(currLayer) + return True # fine comando + else: + return True + return False diff --git a/cmd/qad_joindisjoin_cmd.py b/cmd/qad_joindisjoin_cmd.py new file mode 100644 index 00000000..dfd3dd37 --- /dev/null +++ b/cmd/qad_joindisjoin_cmd.py @@ -0,0 +1,609 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + comando JOIN e DISJOIN per aggregare e disgregare le geometrie + (multipoint, multilinestring, poligon e multipoligon) + + ------------------- + begin : 2016-04-06 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from qgis.PyQt.QtGui import QIcon +from qgis.core import QgsWkbTypes, QgsCoordinateTransform, QgsGeometry, QgsFeature, QgsProject + + +from .qad_generic_cmd import QadCommandClass +from ..qad_entity import QadEntity +from ..qad_getpoint import QadGetPointDrawModeEnum +from .qad_ssget_cmd import QadSSGetClass +from ..qad_msg import QadMsg +from .. import qad_utils +from .. import qad_layer +from .qad_entsel_cmd import QadEntSelClass + + + +# Classe che gestisce il comando JOIN +class QadJOINCommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadJOINCommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "JOIN") + + def getEnglishName(self): + return "JOIN" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runJOINCommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/join.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_JOIN", "Join existing geometries.") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + + self.entity = QadEntity() + + self.SSGetClass = None + self.entSelClass = None + + def __del__(self): + QadCommandClass.__del__(self) + if self.SSGetClass is not None: del self.SSGetClass + + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if self.step == 1: # quando si é in fase di selezione entità + return self.entSelClass.getPointMapTool(drawMode) + elif self.step == 2: # quando si é in fase di selezione gruppo entità + return self.SSGetClass.getPointMapTool() + else: + return QadCommandClass.getPointMapTool(self, drawMode) + + + def getCurrentContextualMenu(self): + if self.step == 1: # quando si é in fase di selezione entità + return self.entSelClass.getCurrentContextualMenu() + elif self.step == 2: # quando si é in fase di selezione gruppo entità + return None # return self.SSGetClass.getCurrentContextualMenu()() + else: + return self.contextualMenu + + + def reinitSSGetClass(self): + if self.SSGetClass is not None: del self.SSGetClass + + self.SSGetClass = QadSSGetClass(self.plugIn) + self.SSGetClass.onlyEditableLayers = True + self.SSGetClass.checkDimLayers = False # scarto le quote + geometryType = self.entity.layer.geometryType() + if geometryType == QgsWkbTypes.PointGeometry: + self.SSGetClass.checkPointLayer = True + self.SSGetClass.checkLineLayer = False + self.SSGetClass.checkPolygonLayer = False + elif geometryType == QgsWkbTypes.LineGeometry: + self.SSGetClass.checkPointLayer = False + self.SSGetClass.checkLineLayer = True + self.SSGetClass.checkPolygonLayer = True + elif geometryType == QgsWkbTypes.PolygonGeometry: + self.SSGetClass.checkPointLayer = False + self.SSGetClass.checkLineLayer = True + self.SSGetClass.checkPolygonLayer = True + + + # ============================================================================ + # addEntitySetToPoint + # ============================================================================ + def addEntitySetToPoint(self, entitySet, removeOriginals = True): + """ + Aggiunge il set di entità al punto da modificare + """ + geom = self.entity.getGeometry() + layerList = [] + layerList.append(self.entity.layer) + + for layerEntitySet in entitySet.layerEntitySetList: + layer = layerEntitySet.layer + if layer.geometryType() != QgsWkbTypes.PointGeometry: + self.showMsg(QadMsg.translate("QAD", "Invalid object.")) + return False + + if removeOriginals: layerList.append(layer) + coordTransform = QgsCoordinateTransform(layer.crs(), self.entity.layer.crs(), QgsProject.instance()) + + for featureId in layerEntitySet.featureIds: + # se la feature è quella di entity è errore + if layer.id() == self.entity.layerId() and featureId == self.entity.featureId: + self.showMsg(QadMsg.translate("QAD", "Invalid object.")) + return False + + f = layerEntitySet.getFeature(featureId) + # trasformo la geometria nel crs del layer dell'entità da modificare + geomToAdd = f.geometry() + geomToAdd.transform(coordTransform) + + simplifiedGeoms = qad_utils.asPointOrPolyline(geomToAdd) + for simplifiedGeom in simplifiedGeoms: + # aggiungo una parte + if geom.addPartGeometry(simplifiedGeom) != QgsGeometry.Success: + self.showMsg(QadMsg.translate("QAD", "Invalid object.")) + return False + + f = self.entity.getFeature() + f.setGeometry(geom) + + layerList = entitySet.getLayerList() + layerList.append(self.entity.layer) + + self.plugIn.beginEditCommand("Feature edited", layerList) + + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, self.entity.layer, f, False, False) == False: + self.plugIn.destroyEditCommand() + return False + + if removeOriginals: + for layerEntitySet in entitySet.layerEntitySetList: + if qad_layer.deleteFeaturesToLayer(self.plugIn, layerEntitySet.layer, layerEntitySet.featureIds, False) == False: + self.plugIn.destroyEditCommand() + return + + self.plugIn.endEditCommand() + + return True + + + # ============================================================================ + # addEntitySetToPolyline + # ============================================================================ + def addEntitySetToPolyline(self, entitySet, removeOriginals = True): + """ + Aggiunge il set di entità alla polilinea da modificare + """ + geom = self.entity.getGeometry() + layerList = [] + layerList.append(self.entity.layer) + + for layerEntitySet in entitySet.layerEntitySetList: + layer = layerEntitySet.layer + if layer.geometryType() != QgsWkbTypes.PolygonGeometry and layer.geometryType() != QgsWkbTypes.LineGeometry: + self.showMsg(QadMsg.translate("QAD", "Invalid object.")) + return False + + if removeOriginals: layerList.append(layer) + coordTransform = QgsCoordinateTransform(layer.crs(), self.entity.layer.crs(), QgsProject.instance()) + + for featureId in layerEntitySet.featureIds: + # se la feature è quella di entity è errore + if layer.id() == self.entity.layerId() and featureId == self.entity.featureId: + self.showMsg(QadMsg.translate("QAD", "Invalid object.")) + return False + + f = layerEntitySet.getFeature(featureId) + # trasformo la geometria nel crs del layer dell'entità da modificare + geomToAdd = f.geometry() + geomToAdd.transform(coordTransform) + + # Riduco la geometria in point o polyline + simplifiedGeoms = qad_utils.asPointOrPolyline(geomToAdd) + for simplifiedGeom in simplifiedGeoms: + if geom.addPartGeometry(simplifiedGeom) != QgsGeometry.Success: + self.showMsg(QadMsg.translate("QAD", "Invalid object.")) + return False + + f = self.entity.getFeature() + f.setGeometry(geom) + + layerList = entitySet.getLayerList() + layerList.append(self.entity.layer) + + self.plugIn.beginEditCommand("Feature edited", layerList) + + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, self.entity.layer, f, False, False) == False: + self.plugIn.destroyEditCommand() + return False + + if removeOriginals: + for layerEntitySet in entitySet.layerEntitySetList: + if qad_layer.deleteFeaturesToLayer(self.plugIn, layerEntitySet.layer, layerEntitySet.featureIds, False) == False: + self.plugIn.destroyEditCommand() + return + + self.plugIn.endEditCommand() + + return True + + + # ============================================================================ + # addEntitySetToPolygon + # ============================================================================ + def addEntitySetToPolygon(self, entitySet, removeOriginals = True): + """ + Aggiunge il set di entità al poligono da modificare + """ + geom = self.entity.getGeometry() + layerList = [] + layerList.append(self.entity.layer) + + for layerEntitySet in entitySet.layerEntitySetList: + layer = layerEntitySet.layer + if layer.geometryType() != QgsWkbTypes.PolygonGeometry and layer.geometryType() != QgsWkbTypes.LineGeometry: + self.showMsg(QadMsg.translate("QAD", "Invalid object.")) + return False + + if removeOriginals: layerList.append(layer) + coordTransform = QgsCoordinateTransform(layer.crs(), self.entity.layer.crs(), QgsProject.instance()) + + for featureId in layerEntitySet.featureIds: + # se la feature è quella di entity è errore + if layer.id() == self.entity.layerId() and featureId == self.entity.featureId: + self.showMsg(QadMsg.translate("QAD", "Invalid object.")) + return False + + f = layerEntitySet.getFeature(featureId) + # trasformo la geometria nel crs del layer del poligono da modificare + geomToAdd = f.geometry() + geomToAdd.transform(coordTransform) + + # se il poligono è contenuto nella geometria da aggiungere + if geomToAdd.contains(geom): + # Riduco la geometria in point o polyline + simplifiedGeoms = qad_utils.asPointOrPolyline(geom) + # deve essere un poligono senza ring + if len(simplifiedGeoms) != 1 or \ + (simplifiedGeoms[0].isMultipart() == True or simplifiedGeoms[0].type() != QgsWkbTypes.LineGeometry): + self.showMsg(QadMsg.translate("QAD", "Invalid object.")) + return False + points = simplifiedGeoms[0].asPolyline() # vettore di punti + # aggiungo un'isola + if geomToAdd.addRing(points) != 0: # 0 in case of success + self.showMsg(QadMsg.translate("QAD", "Invalid object.")) + return False + del geom + geom = QgsGeometry.fromPolygonXY(geomToAdd.asPolygon()) + else: # se il poligono non è contenuto nella geometria da aggiungere + # Riduco la geometria in point o polyline + simplifiedGeoms = qad_utils.asPointOrPolyline(geomToAdd) + for simplifiedGeom in simplifiedGeoms: + # se la geometria da aggiungere è contenuta nel poligono + if geom.contains(simplifiedGeom): + points = simplifiedGeom.asPolyline() # vettore di punti + # aggiungo un'isola + if geom.addRing(points) != 0: # 0 in case of success + self.showMsg(QadMsg.translate("QAD", "Invalid object.")) + return False + else: + # aggiungo una parte + if geom.addPartGeometry(simplifiedGeom) != QgsGeometry.Success: + self.showMsg(QadMsg.translate("QAD", "Invalid object.")) + return False + + f = self.entity.getFeature() + f.setGeometry(geom) + + layerList = entitySet.getLayerList() + layerList.append(self.entity.layer) + + self.plugIn.beginEditCommand("Feature edited", layerList) + + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, self.entity.layer, f, False, False) == False: + self.plugIn.destroyEditCommand() + return False + + if removeOriginals: + for layerEntitySet in entitySet.layerEntitySetList: + if qad_layer.deleteFeaturesToLayer(self.plugIn, layerEntitySet.layer, layerEntitySet.featureIds, False) == False: + self.plugIn.destroyEditCommand() + return + + self.plugIn.endEditCommand() + + return True + + + # ============================================================================ + # waitForEntsel + # ============================================================================ + def waitForEntsel(self, msgMapTool, msg): + if self.entSelClass is not None: + del self.entSelClass + self.step = 1 + self.entSelClass = QadEntSelClass(self.plugIn) + self.entSelClass.msg = QadMsg.translate("Command_JOIN", "Select object to join to: ") + # scarto la selezione di quote + self.entSelClass.checkDimLayers = False + self.entSelClass.onlyEditableLayers = True + self.entSelClass.deselectOnFinish = True + + self.entSelClass.run(msgMapTool, msg) + + + # ============================================================================ + # waitForSSsel + # ============================================================================ + def waitForSSsel(self, msgMapTool, msg): + self.reinitSSGetClass() + self.step = 2 + self.showMsg(QadMsg.translate("Command_JOIN", "\nSelect objects to join: ")) + self.SSGetClass.run(msgMapTool, msg) + + + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + if self.step == 0: + self.waitForEntsel(msgMapTool, msg) # seleziona l'oggetto a cui aggregarsi + return False # continua + + # ========================================================================= + # RISPOSTA ALLA SELEZIONE ENTITA' DA MODIFICARE + elif self.step == 1: + if self.entSelClass.run(msgMapTool, msg) == True: + if self.entSelClass.entity.isInitialized(): + self.entity.set(self.entSelClass.entity) + + self.waitForSSsel(msgMapTool, msg) + else: + if self.entSelClass.canceledByUsr == True: # fine comando + return True + self.showMsg(QadMsg.translate("QAD", "No geometries in this position.")) + self.waitForEntsel(msgMapTool, msg) + + return False # continua + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL GRUPPO DI SELEZIONE (da step = 1) + elif self.step == 2: + if self.SSGetClass.run(msgMapTool, msg) == True: + if self.SSGetClass.entitySet.count() > 0: + geometryType = self.entity.layer.geometryType() + if geometryType == QgsWkbTypes.PointGeometry: + self.addEntitySetToPoint(self.SSGetClass.entitySet) + elif geometryType == QgsWkbTypes.LineGeometry: + self.addEntitySetToPolyline(self.SSGetClass.entitySet) + elif geometryType == QgsWkbTypes.PolygonGeometry: + self.addEntitySetToPolygon(self.SSGetClass.entitySet) + + return True + + self.waitForSSsel(msgMapTool, msg) + return False + + +# Classe che gestisce il comando DISJOIN +class QadDISJOINCommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadDISJOINCommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "DISJOIN") + + def getEnglishName(self): + return "DISJOIN" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runDISJOINCommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/disjoin.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_DISJOIN", "Disjoin existing geometries.") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + + self.entity = QadEntity() + + self.SSGetClass = QadSSGetClass(plugIn) + self.SSGetClass.onlyEditableLayers = False + self.SSGetClass.checkDimLayers = False # scarto le quote + + self.entSelClass = None + + self.currSubGeom = None + self.currAtSubGeom = None + + + def __del__(self): + QadCommandClass.__del__(self) + del self.SSGetClass + + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if self.step == 1: # quando si é in fase di selezione entità + return self.entSelClass.getPointMapTool(drawMode) + else: + return QadCommandClass.getPointMapTool(self, drawMode) + + + def getCurrentContextualMenu(self): + if self.step == 1: # quando si é in fase di selezione entità + return self.entSelClass.getCurrentContextualMenu() + else: + return self.contextualMenu + + + # ============================================================================ + # setCurrentSubGeom + # ============================================================================ + def setCurrentSubGeom(self, entSelClass): + """ + Setta la sottogeometria corrente + """ + self.currSubGeom = None + self.currAtSubGeom = None + + # verifico che sia stata selezionata un'entità + if entSelClass.entity.isInitialized() == False: + self.showMsg(QadMsg.translate("QAD", "No geometries in this position.")) + return False + # verifico che sia stata selezionata attraverso un punto + # (per capire quale sottogeometria è stata selezionata) + if entSelClass.point is None: return False + + self.entity.set(entSelClass.entity) + + geom = self.layerToMapCoordinates(entSelClass.entity.layer, entSelClass.entity.getGeometry()) + + # ritorna una tupla (, + # + # + # ) + dummy = qad_utils.closestSegmentWithContext(entSelClass.point, geom) + if dummy[2] is None: + return False + # ritorna la sotto-geometria al vertice e la sua posizione nella geometria (0-based) + self.currSubGeom, self.currAtSubGeom = qad_utils.getSubGeomAtVertex(geom, dummy[2]) + if self.currSubGeom is None or self.currAtSubGeom is None: + self.currSubGeom = None + self.currAtSubGeom = None + return False + + return True + + + # ============================================================================ + # disjoinCurrentSubGeomToPolygon + # ============================================================================ + def disjoinCurrentSubGeomToPolygon(self): + """ + Sconnette la sotto-geometria corrente del poligono da modificare creando una nuova entità + """ + layer = self.entity.layer + # la posizione é espressa con una lista ( []) + part = self.currAtSubGeom[0] + ring = self.currAtSubGeom[1] if len(self.currAtSubGeom) == 2 else None + + geom = self.entity.getGeometry() + gType = geom.type() + + if geom.isMultipart() == True and (gType == QgsWkbTypes.PointGeometry or gType == QgsWkbTypes.LineGeometry): + if geom.deletePart(part) == False: # disgrego una parte + self.showMsg(QadMsg.translate("QAD", "Invalid object.")) + return False + newGeom = self.mapToLayerCoordinates(layer, self.currSubGeom) + elif gType == QgsWkbTypes.PolygonGeometry: + if ring is not None: # disgrego un'isola + if geom.deleteRing(ring + 1, part) == False: # cancello una isola (Ring 0 is outer ring and can't be deleted) + self.showMsg(QadMsg.translate("QAD", "Invalid object.")) + return False + newGeom = QgsGeometry.fromPolygonXY([self.mapToLayerCoordinates(layer, self.currSubGeom).asPolyline()]) + else: # disgrego una parte + if geom.isMultipart() == False: + self.showMsg(QadMsg.translate("QAD", "Invalid object.")) + return False + + newGeom = QgsGeometry.fromPolygonXY([self.mapToLayerCoordinates(layer, self.currSubGeom).asPolyline()]) + ring = 0 + ringGeom = qad_utils.getSubGeomAt(geom, [part, ring]) + # se la parte ha delle isole + while ringGeom is not None: + # aggiungo un'isola + points = ringGeom.asPolyline() # vettore di punti + if newGeom.addRing(points) != 0: # 0 in case of success + self.showMsg(QadMsg.translate("QAD", "Invalid object.")) + return False + ring = ring + 1 + ringGeom = qad_utils.getSubGeomAt(geom, [part, ring]) + + if geom.deletePart(part) == False: # cancello una parte + self.showMsg(QadMsg.translate("QAD", "Invalid object.")) + return False + else: + self.showMsg(QadMsg.translate("QAD", "Invalid object.")) + return False + + f = self.entity.getFeature() + f.setGeometry(geom) + + self.plugIn.beginEditCommand("Feature edited", self.entity.layer) + + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, self.entity.layer, f, False, False) == False: + self.plugIn.destroyEditCommand() + return False + + # Aggiungo nuova feature + newF = QgsFeature(f) + newF.setGeometry(newGeom) + if qad_layer.addFeatureToLayer(self.plugIn, self.entity.layer, newF, None, False, False) == False: + self.plugIn.destroyEditCommand() + return False + + self.plugIn.endEditCommand() + + return True + + + # ============================================================================ + # waitForEntsel + # ============================================================================ + def waitForEntsel(self, msgMapTool, msg): + if self.entSelClass is not None: + del self.entSelClass + self.step = 1 + self.entSelClass = QadEntSelClass(self.plugIn) + self.entSelClass.msg = QadMsg.translate("Command_DISJOIN", "Select object to disjoin: ") + # scarto la selezione di quote + self.entSelClass.checkDimLayers = False + self.entSelClass.onlyEditableLayers = True + self.entSelClass.deselectOnFinish = True + + self.entSelClass.run(msgMapTool, msg) + + + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + if self.step == 0: + self.waitForEntsel(msgMapTool, msg) # seleziona l'oggetto da disgregare + return False # continua + + # ========================================================================= + # RISPOSTA ALLA SELEZIONE ENTITA' DA MODIFICARE + elif self.step == 1: + if self.entSelClass.run(msgMapTool, msg) == True: + if self.setCurrentSubGeom(self.entSelClass) == True: + if self.disjoinCurrentSubGeomToPolygon() == True: + return True + else: + if self.entSelClass.canceledByUsr == True: # fine comando + return True + self.showMsg(QadMsg.translate("QAD", "No geometries in this position.")) + + self.waitForEntsel(msgMapTool, msg) + + return False # continua \ No newline at end of file diff --git a/cmd/qad_lengthen_cmd.py b/cmd/qad_lengthen_cmd.py new file mode 100644 index 00000000..3139531a --- /dev/null +++ b/cmd/qad_lengthen_cmd.py @@ -0,0 +1,1017 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + comando ALLUNGA per allungare un oggetto + + ------------------- + begin : 2015-10-05 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from qgis.PyQt.QtGui import QIcon +from qgis.core import QgsPointXY, QgsWkbTypes + + +from .. import qad_utils +from ..qad_variables import QadVariables +from ..qad_msg import QadMsg +from ..qad_entity import QadEntity +from .qad_generic_cmd import QadCommandClass +from .qad_getdist_cmd import QadGetDistClass +from .qad_getangle_cmd import QadGetAngleClass +from ..qad_getpoint import QadGetPointDrawModeEnum +from .qad_lengthen_maptool import Qad_lengthen_maptool, Qad_lengthen_maptool_ModeEnum +from ..qad_textwindow import QadInputTypeEnum, QadInputModeEnum +from .. import qad_layer +from ..qad_arc import QadArc +from ..qad_dim import QadDimStyles +from .. import qad_grip +from ..qad_geom_relations import getQadGeomClosestVertex +from ..qad_multi_geom import fromQadGeomToQgsGeom, getQadGeomAt, setQadGeomAt, isLinearQadGeom + + +# Classe che gestisce il comando LENGTHEN +class QadLENGTHENCommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadLENGTHENCommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "LENGTHEN") + + def getEnglishName(self): + return "LENGTHEN" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runLENGTHENCommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/lengthen.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_LENGTHEN", "Lengthen an object.") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + self.OpMode = plugIn.lastOpMode_lengthen # "DElta" o "Percent" o "Total" o "DYnamic" + self.OpType = None # "length" o "Angle" + self.value = None + + self.startPt = None + self.GetDistClass = None + self.GetAngleClass = None + self.entity = QadEntity() + self.linearObject = None + self.atGeom = None + self.move_startPt = None + + self.nOperationsToUndo = 0 + + + def __del__(self): + QadCommandClass.__del__(self) + if self.GetDistClass is not None: + del self.GetDistClass + if self.GetAngleClass is not None: + del self.GetAngleClass + + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if self.step == 3: # quando si é in fase di richiesta distanza + return self.GetDistClass.getPointMapTool() + if self.step == 4: # quando si é in fase di richiesta angolo + return self.GetAngleClass.getPointMapTool() + elif (self.plugIn is not None): + if self.PointMapTool is None: + self.PointMapTool = Qad_lengthen_maptool(self.plugIn) + return self.PointMapTool + else: + return None + + + def getCurrentContextualMenu(self): + if self.step == 3: # quando si é in fase di richiesta distanza + return self.GetDistClass.getCurrentContextualMenu() + if self.step == 4: # quando si é in fase di richiesta angolo + return self.GetAngleClass.getCurrentContextualMenu() + else: + return self.contextualMenu + + + def setInfo(self, entity, point): + # setta: self.entity, self.linearObject, self.atGeom e self.move_startPt + if self.linearObject is not None: + del self.linearObject + self.linearObject = None + + self.entity.set(entity.layer, entity.featureId) + qadGeom = self.entity.getQadGeom() + + # la funzione ritorna una lista con + # ( + # + # + # + # + # + result = getQadGeomClosestVertex(qadGeom, point) + self.atGeom = result[2] + self.linearObject = getQadGeomAt(qadGeom, self.atGeom, 0).copy() + + if qad_utils.getDistance(self.linearObject.getStartPt(), point) <= \ + qad_utils.getDistance(self.linearObject.getEndPt(), point): + # si allunga dal punto iniziale + self.move_startPt = True + else: + # si allunga dal punto finale + self.move_startPt = False + + return True + + + # ============================================================================ + # lengthen + # ============================================================================ + def lengthen(self, point): + layer = self.entity.layer + f = self.entity.getFeature() + if f is None: # non c'è più la feature + return False + qadGeom = self.entity.getQadGeom() + + # ritorna una tupla (, + # + # + # ) + res = False + newLinearObject = self.linearObject.copy() + if self.OpMode == "DElta": + if self.OpType == "length": + res = newLinearObject.lengthen_delta(self.move_startPt, self.value) + elif self.OpType == "Angle": + res = newLinearObject.lengthen_deltaAngle(self.move_startPt, self.value) + elif self.OpMode == "Percent": + value = newLinearObject.length() * self.value / 100 + value = value - newLinearObject.length() + res = newLinearObject.lengthen_delta(self.move_startPt, value) + elif self.OpMode == "Total": + if self.OpType == "length": + value = self.value - newLinearObject.length() + res = newLinearObject.lengthen_delta(self.move_startPt, value) + elif self.OpType == "Angle": + if newLinearObject.whatIs() == "ARC": + value = self.value - newLinearObject.totalAngle() + res = newLinearObject.lengthen_deltaAngle(self.move_startPt, value) + elif self.OpMode == "DYnamic": + if newLinearObject.whatIs() == "POLYLINE": + if self.move_startPt: + linearObject = newLinearObject.getLinearObjectAt(0) + else: + linearObject = newLinearObject.getLinearObjectAt(-1) + else: + linearObject = newLinearObject + + gType = linearObject.whatIs() + if gType == "LINE": + newPt = qad_utils.getPerpendicularPointOnInfinityLine(linearObject.getStartPt(), linearObject.getEndPt(), point) + ang = linearObject.getTanDirectionOnStartPt() + + elif gType == "ARC": + newPt = qad_utils.getPolarPointByPtAngle(linearObject.center, \ + qad_utils.getAngleBy2Pts(linearObject.center, point), \ + linearObject.radius) + elif gType == "ELLIPSE_ARC": + pass + + if self.move_startPt: + linearObject.setStartPt(newPt) + else: + linearObject.setEndPt(newPt) + + if gType == "LINE" and newLinearObject.whatIs() == "POLYLINE" and \ + qad_utils.TanDirectionNear(ang, newLinearObject.getTanDirectionOnStartPt()) == False: + res = False + else: + res = True + + if res == False: # allungamento impossibile + return False + + updGeom = setQadGeomAt(qadGeom, newLinearObject, self.atGeom, 0) + # trasformo la geometria nel crs del layer + f.setGeometry(fromQadGeomToQgsGeom(updGeom, layer)) + + self.plugIn.beginEditCommand("Feature edited", layer) + + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, layer, f, False, False) == False: + self.plugIn.destroyEditCommand() + return False + + self.plugIn.endEditCommand() + self.nOperationsToUndo = self.nOperationsToUndo + 1 + + return True + + + + def showLength(self, entity, pt): + # visualizza la lunghezza dell'entità in unità di mappa + qadGeom = entity.getQadGeom() + if qadGeom is None: + errMsg = QadMsg.translate("QAD", "Invalid object.") + self.showErr("\n" + errMsg) + return None + + # la funzione ritorna una lista con + # ( + # + # + # + # + # + result = getQadGeomClosestVertex(qadGeom, pt) + atGeom = result[2] + LinearObjectToMisure = getQadGeomAt(qadGeom, atGeom, 0).copy() + + msg = QadMsg.translate("Command_LENGTHEN", "\nCurrent length: {0}") + msg = msg.format(str(LinearObjectToMisure.length())) + + if LinearObjectToMisure.whatIs() == "ARC": + msg = msg + QadMsg.translate("Command_LENGTHEN", ", included angle: {0}") + msg = msg.format(str(qad_utils.toDegrees(LinearObjectToMisure.totalAngle()))) + + self.showMsg(msg) + + + def waitForObjectSelToMisure(self): + self.step = 1 + # imposto il map tool + self.getPointMapTool().setMode(Qad_lengthen_maptool_ModeEnum.ASK_FOR_OBJ_TO_MISURE) + + if self.plugIn.lastOpMode_lengthen == "DElta": + self.defaultValue = QadMsg.translate("Command_LENGTHEN", "DElta") + elif self.plugIn.lastOpMode_lengthen == "Percent": + self.defaultValue = QadMsg.translate("Command_LENGTHEN", "Percent") + elif self.plugIn.lastOpMode_lengthen == "Total": + self.defaultValue = QadMsg.translate("Command_LENGTHEN", "Total") + elif self.plugIn.lastOpMode_lengthen == "DYnamic": + self.defaultValue = QadMsg.translate("Command_LENGTHEN", "DYnamic") + else: + self.defaultValue = None + + keyWords = QadMsg.translate("Command_LENGTHEN", "DElta") + "/" + \ + QadMsg.translate("Command_LENGTHEN", "Percent") + "/" + \ + QadMsg.translate("Command_LENGTHEN", "Total") + "/" + \ + QadMsg.translate("Command_LENGTHEN", "DYnamic") + if self.defaultValue is None: + prompt = QadMsg.translate("Command_LENGTHEN", "Select an object or [{0}] <{1}>: ").format(keyWords, self.defaultValue) + else: + prompt = QadMsg.translate("Command_LENGTHEN", "Select an object or [{0}] <{1}>: ").format(keyWords, self.defaultValue) + + englishKeyWords = "DElta" + "/" + "Percent" + "/" + "Total" + "/" + "DYnamic" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + self.defaultValue, \ + keyWords, QadInputModeEnum.NONE) + + + def waitForDelta(self): + self.step = 2 + self.OpMode = "DElta" + self.plugIn.setLastOpMode_lengthen(self.OpMode) + # imposto il map tool + self.getPointMapTool().setMode(Qad_lengthen_maptool_ModeEnum.ASK_FOR_DELTA) + + keyWords = QadMsg.translate("Command_LENGTHEN", "Angle") + prompt = QadMsg.translate("Command_LENGTHEN", "Enter delta length or [{0}] <{1}>: ").format(keyWords, str(self.plugIn.lastDelta_lengthen)) + + englishKeyWords = "Angle" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS | QadInputTypeEnum.FLOAT, \ + self.plugIn.lastDelta_lengthen, \ + keyWords, QadInputModeEnum.NONE) + + + def waitForDeltaLength(self, msgMapTool, msg): + self.step = 3 + self.OpType = "length" + + # si appresta ad attendere una distanza + if self.GetDistClass is not None: + del self.GetDistClass + self.GetDistClass = QadGetDistClass(self.plugIn) + prompt = QadMsg.translate("Command_LENGTHEN", "Enter delta length <{0}>: ") + self.GetDistClass.msg = prompt.format(str(self.plugIn.lastDelta_lengthen)) + self.GetDistClass.startPt = self.startPt + self.GetDistClass.dist = self.plugIn.lastDelta_lengthen + self.GetDistClass.inputMode = QadInputModeEnum.NONE + self.GetDistClass.run(msgMapTool, msg) + + + def waitForDeltaAngle(self, msgMapTool, msg): + self.step = 4 + self.OpType = "Angle" + + # si appresta ad attendere l'angolo di rotazione + if self.GetAngleClass is not None: + del self.GetAngleClass + self.GetAngleClass = QadGetAngleClass(self.plugIn) + prompt = QadMsg.translate("Command_LENGTHEN", "Enter delta angle <{0}>: ") + self.GetAngleClass.msg = prompt.format(str(qad_utils.toDegrees(self.plugIn.lastDeltaAngle_lengthen))) + self.GetAngleClass.angle = self.plugIn.lastDeltaAngle_lengthen + self.GetAngleClass.run(msgMapTool, msg) + + + def waitForObjectSel(self): + self.step = 5 + # imposto il map tool + self.getPointMapTool().setMode(Qad_lengthen_maptool_ModeEnum.ASK_FOR_OBJ_TO_LENGTHEN) + self.getPointMapTool().refreshSnapType() # aggiorno lo snapType che può essere variato dal maptool di distanza o angolo + self.getPointMapTool().OpType = self.OpType + self.getPointMapTool().value = self.value + + keyWords = QadMsg.translate("Command_LENGTHEN", "Undo") + prompt = QadMsg.translate("Command_LENGTHEN", "Select an object to change or [{0}]: ").format(QadMsg.translate("Command_LENGTHEN", "Undo")) + + englishKeyWords = "Undo" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, QadInputModeEnum.NONE) + + + def waitForPercent(self): + self.step = 6 + self.OpMode = "Percent" + self.plugIn.setLastOpMode_lengthen(self.OpMode) + + # imposto il map tool + self.getPointMapTool().setMode(Qad_lengthen_maptool_ModeEnum.ASK_FOR_PERCENT) + + prompt = QadMsg.translate("Command_LENGTHEN", "Enter percentage length <{0}>: ") + prompt = prompt.format(str(self.plugIn.lastPerc_lengthen)) + # si appresta ad attendere un numero reale + # msg, inputType, default, keyWords, valori positivi + self.waitFor(prompt, QadInputTypeEnum.FLOAT, \ + self.plugIn.lastPerc_lengthen, "", \ + QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) + + + def waitForTotal(self): + self.step = 7 + self.OpMode = "Total" + self.plugIn.setLastOpMode_lengthen(self.OpMode) + # imposto il map tool + self.getPointMapTool().setMode(Qad_lengthen_maptool_ModeEnum.ASK_FOR_TOTAL) + + keyWords = QadMsg.translate("Command_LENGTHEN", "Angle") + prompt = QadMsg.translate("Command_LENGTHEN", "Specify total length or [{0}] <{1}>: ").format(keyWords, str(self.plugIn.lastTotal_lengthen)) + + englishKeyWords = "Angle" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS | QadInputTypeEnum.FLOAT, \ + self.plugIn.lastTotal_lengthen, \ + keyWords, QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) + + + def waitForTotalLength(self, msgMapTool, msg): + self.step = 8 + self.OpType = "length" + + # si appresta ad attendere una distanza + if self.GetDistClass is not None: + del self.GetDistClass + self.GetDistClass = QadGetDistClass(self.plugIn) + prompt = QadMsg.translate("Command_LENGTHEN", "Enter total length <{0}>: ") + self.GetDistClass.msg = prompt.format(str(self.plugIn.lastTotal_lengthen)) + self.GetDistClass.startPt = self.startPt + self.GetDistClass.dist = self.plugIn.lastTotal_lengthen + self.GetDistClass.inputMode = QadInputModeEnum.NONE + self.GetDistClass.run(msgMapTool, msg) + + + def waitForTotalAngle(self, msgMapTool, msg): + self.step = 9 + self.OpType = "Angle" + + # si appresta ad attendere l'angolo di rotazione + if self.GetAngleClass is not None: + del self.GetAngleClass + self.GetAngleClass = QadGetAngleClass(self.plugIn) + prompt = QadMsg.translate("Command_LENGTHEN", "Enter total angle <{0}>: ") + self.GetAngleClass.msg = prompt.format(str(qad_utils.toDegrees(self.plugIn.lastTotalAngle_lengthen))) + self.GetAngleClass.angle = self.plugIn.lastTotalAngle_lengthen + self.GetAngleClass.run(msgMapTool, msg) + + + def waitForDynamicPt(self): + self.step = 10 + # imposto il map tool + self.getPointMapTool().setMode(Qad_lengthen_maptool_ModeEnum.ASK_FOR_DYNAMIC_POINT) + + prompt = QadMsg.translate("Command_LENGTHEN", "Specify new endpoint: ") + + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D, \ + None, \ + "", QadInputModeEnum.NONE) + + + # ============================================================================ + # run + # ============================================================================ + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + # ========================================================================= + # RICHIESTA SELEZIONE OGGETTO + if self.step == 0: # inizio del comando + # si appresta ad attendere la selezione degli oggetti da estendere/tagliare + self.waitForObjectSelToMisure() + return False + + # ========================================================================= + # RISPOSTA ALLA SELEZIONE OGGETTI DA MISURARE + elif self.step == 1: + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + value = self.defaultValue + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("Command_LENGTHEN", "DElta") or value == "DElta": + self.waitForDelta() + return False + elif value == QadMsg.translate("Command_LENGTHEN", "Percent") or value == "Percent": + self.waitForPercent() + return False + elif value == QadMsg.translate("Command_LENGTHEN", "Total") or value == "Total": + self.waitForTotal() + return False + elif value == QadMsg.translate("Command_LENGTHEN", "DYnamic") or value == "DYnamic": + self.OpMode = "DYnamic" + self.plugIn.setLastOpMode_lengthen(self.OpMode) + # si appresta ad attendere la selezione degli oggetti da allungare + self.waitForObjectSel() + return False + + elif type(value) == QgsPointXY: # se é stato selezionato un punto + if self.getPointMapTool().entity.isInitialized(): + self.showLength(self.getPointMapTool().entity, value) + else: + # cerco se ci sono entità nel punto indicato considerando + # solo layer di tipo lineari che non appartengano a quote o di tipo poligono + layerList = [] + for layer in qad_utils.getVisibleVectorLayers(self.plugIn.canvas): # Tutti i layer vettoriali visibili + if layer.geometryType() == QgsWkbTypes.LineGeometry or layer.geometryType() == QgsWkbTypes.PolygonGeometry: + if len(QadDimStyles.getDimListByLayer(layer)) == 0: + layerList.append(layer) + + result = qad_utils.getEntSel(self.getPointMapTool().toCanvasCoordinates(value), \ + self.getPointMapTool(), \ + QadVariables.get(QadMsg.translate("Environment variables", "PICKBOX")), \ + layerList) + if result is not None: + feature = result[0] + layer = result[1] + self.showLength(QadEntity().set(layer, feature.id()), value) + else: + return True # fine comando + + # si appresta ad attendere la selezione degli oggetti da misurare + self.waitForObjectSelToMisure() + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL DELTA (da step = 1) + elif self.step == 2: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + value = self.plugIn.lastDelta_lengthen # opzione di default "spostamento" + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("Command_LENGTHEN", "Angle") or value == "Angle": + self.waitForDeltaAngle(msgMapTool, msg) + elif type(value) == QgsPointXY: # se é stato inserito un punto + self.startPt = value + self.waitForDeltaLength(msgMapTool, msg) + elif type(value) == float: # se é stato inserito il delta + self.plugIn.setLastDelta_lengthen(value) + self.OpType = "length" + self.value = value + # si appresta ad attendere la selezione degli oggetti da allungare + self.waitForObjectSel() + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DELLA LUNGHEZZA DEL DELTA (da step = 2) + elif self.step == 3: # dopo aver atteso un punto o un numero reale si riavvia il comando + if self.GetDistClass.run(msgMapTool, msg) == True: + if self.GetDistClass.dist is not None: + self.plugIn.setLastDelta_lengthen(self.GetDistClass.dist) + self.value = self.GetDistClass.dist + # si appresta ad attendere la selezione degli oggetti da allungare + self.waitForObjectSel() + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DELL'ANGOLO DEL DELTA (da step = 2) + elif self.step == 4: # dopo aver atteso un punto o un numero reale si riavvia il comando + if self.GetAngleClass.run(msgMapTool, msg) == True: + if self.GetAngleClass.angle is not None: + self.plugIn.setLastDeltaAngle_lengthen(self.GetAngleClass.angle) + self.value = self.GetAngleClass.angle + # si appresta ad attendere la selezione degli oggetti da allungare + self.waitForObjectSel() + + + # ========================================================================= + # RISPOSTA ALLA SELEZIONE OGGETTI DA ALLUNGARE + elif self.step == 5: + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("Command_LENGTHEN", "Undo") or value == "Undo": + if self.nOperationsToUndo > 0: + self.nOperationsToUndo = self.nOperationsToUndo - 1 + self.plugIn.undoEditCommand() + else: + self.showMsg(QadMsg.translate("QAD", "\nThe command has been canceled.")) + elif type(value) == QgsPointXY: # se é stato selezionato un punto + if self.getPointMapTool().entity.isInitialized(): + self.setInfo(self.getPointMapTool().entity, value) + if self.OpMode != "DYnamic": + self.lengthen(value) + else: + self.waitForDynamicPt() + return False + else: + # cerco se ci sono entità nel punto indicato considerando + # solo layer lineari editabili che non appartengano a quote + layerList = [] + for layer in qad_utils.getVisibleVectorLayers(self.plugIn.canvas): # Tutti i layer vettoriali visibili + if layer.geometryType() == QgsWkbTypes.LineGeometry and layer.isEditable(): + if len(QadDimStyles.getDimListByLayer(layer)) == 0: + layerList.append(layer) + + result = qad_utils.getEntSel(self.getPointMapTool().toCanvasCoordinates(value), \ + self.getPointMapTool(), \ + QadVariables.get(QadMsg.translate("Environment variables", "PICKBOX")), \ + layerList) + if result is not None: + feature = result[0] + layer = result[1] + self.setInfo(QadEntity().set(layer, feature.id()), value) + + if self.OpMode != "DYnamic": + self.lengthen(value) + else: + self.waitForDynamicPt() + return False + else: + return True # fine comando + + # si appresta ad attendere la selezione degli oggetti da allungare + self.waitForObjectSel() + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA PERCENTUALE (da step = 1) + elif self.step == 6: # dopo aver atteso un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + value = self.plugIn.lastPerc_lengthen + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: + return False + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == float: # é stata inserita la percentuale + self.plugIn.setLastPerc_lengthen(value) + self.value = value + # si appresta ad attendere la selezione degli oggetti da allungare + self.waitForObjectSel() + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL TOTALE (da step = 1) + elif self.step == 7: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + value = self.plugIn.lastTotal_lengthen + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("Command_LENGTHEN", "Angle") or value == "Angle": + self.waitForTotalAngle(msgMapTool, msg) + elif type(value) == QgsPointXY: # se é stato inserito un punto + self.startPt = value + self.waitForTotalLength(msgMapTool, msg) + elif type(value) == float: # se é stato inserito il delta + self.plugIn.setLastTotal_lengthen(value) + self.OpType = "length" + self.value = value + # si appresta ad attendere la selezione degli oggetti da allungare + self.waitForObjectSel() + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DELLA LUNGHEZZA DEL TOTALE (da step = 7) + elif self.step == 8: # dopo aver atteso un punto o un numero reale si riavvia il comando + if self.GetDistClass.run(msgMapTool, msg) == True: + if self.GetDistClass.dist is not None: + self.plugIn.setLastTotal_lengthen(self.GetDistClass.dist) + self.value = self.GetDistClass.dist + # si appresta ad attendere la selezione degli oggetti da allungare + self.waitForObjectSel() + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DELL'ANGOLO DEL DELTA (da step = 7) + elif self.step == 9: # dopo aver atteso un punto o un numero reale si riavvia il comando + if self.GetAngleClass.run(msgMapTool, msg) == True: + if self.GetAngleClass.angle is not None: + self.plugIn.setLastTotalAngle_lengthen(self.GetAngleClass.angle) + self.value = self.GetAngleClass.angle + # si appresta ad attendere la selezione degli oggetti da allungare + self.waitForObjectSel() + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DELLA NUOVA ESTREMITA' IN MODO DINAMICO (da step = 5) + elif self.step == 10: # dopo aver atteso un punto + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == QgsPointXY: # se é stato inserito un punto + self.lengthen(value) + + # si appresta ad attendere la selezione degli oggetti da allungare + self.waitForObjectSel() + + return False + + + + +# ============================================================================ +# Classe che gestisce il comando LENGTHEN per i grip +# ============================================================================ +class QadGRIPLENGTHENCommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadGRIPLENGTHENCommandClass(self.plugIn) + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + self.entity = None + self.skipToNextGripCommand = False + self.copyEntities = False + self.basePt = QgsPointXY() + self.nOperationsToUndo = 0 + + self.linearObject = None + self.atGeom = None + self.move_startPt = None + + + def __del__(self): + QadCommandClass.__del__(self) + + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if (self.plugIn is not None): + if self.PointMapTool is None: + self.PointMapTool = Qad_lengthen_maptool(self.plugIn) + return self.PointMapTool + else: + return None + + + # ============================================================================ + # setSelectedEntityGripPoints + # ============================================================================ + def setSelectedEntityGripPoints(self, entitySetGripPoints): + # lista delle entityGripPoint con dei grip point selezionati + # setta la prima entità con un grip selezionato + self.entity = None + for entityGripPoints in entitySetGripPoints.entityGripPoints: + for gripPoint in entityGripPoints.gripPoints: + # grip point selezionato + if gripPoint.getStatus() == qad_grip.QadGripStatusEnum.SELECTED: + # verifico se l'entità appartiene ad uno stile di quotatura + if QadDimStyles.isDimEntity(entityGripPoints.entity): + return False + qadGeom = entityGripPoints.entity.getQadGeom() + + # setta: self.entity, self.linearObject, self.atGeom e self.move_startPt + self.entity = entityGripPoints.entity + + if self.linearObject is not None: + del self.linearObject + self.linearObject = None + + # la funzione ritorna una lista con + # ( + # + # + # + # + # + point = gripPoint.getPoint() + result = getQadGeomClosestVertex(qadGeom, point) + self.atGeom = result[2] + linearObject = getQadGeomAt(qadGeom, self.atGeom, 0).copy() + + if not isLinearQadGeom(linearObject): + return False + + self.linearObject = getQadGeomAt(qadGeom, self.atGeom, 0).copy() + + if qad_utils.getDistance(self.linearObject.getStartPt(), point) <= \ + qad_utils.getDistance(self.linearObject.getEndPt(), point): + # si allunga dal punto iniziale + self.move_startPt = True + else: + # si allunga dal punto finale + self.move_startPt = False + + # imposto il map tool + if self.getPointMapTool().setInfo(self.entity, point) == False: + return False + + return True + return False + + + # ============================================================================ + # lengthen + # ============================================================================ + def lengthen(self, point): + layer = self.entity.layer + f = self.entity.getFeature() + if f is None: # non c'è più la feature + return False + qadGeom = self.entity.getQadGeom() + + res = False + newLinearObject = self.linearObject.copy() + + if newLinearObject.whatIs() == "POLYLINE": + if self.move_startPt: + linearObject = newLinearObject.getLinearObjectAt(0) + else: + linearObject = newLinearObject.getLinearObjectAt(-1) + else: + linearObject = newLinearObject + + gType = linearObject.whatIs() + if gType == "LINE": + newPt = qad_utils.getPerpendicularPointOnInfinityLine(linearObject.getStartPt(), linearObject.getEndPt(), point) + ang = linearObject.getTanDirectionOnStartPt() + + elif gType == "ARC": + newPt = qad_utils.getPolarPointByPtAngle(linearObject.center, \ + qad_utils.getAngleBy2Pts(linearObject.center, point), \ + linearObject.radius) + elif gType == "ELLIPSE_ARC": + pass + + if self.move_startPt: + linearObject.setStartPt(newPt) + else: + linearObject.setEndPt(newPt) + + if gType == "LINE" and newLinearObject.whatIs() == "POLYLINE" and \ + qad_utils.TanDirectionNear(ang, linearObject.getTanDirectionOnStartPt()) == False: + res = False + else: + res = True + + if res == False: # allungamento impossibile + return False + + updGeom = setQadGeomAt(qadGeom, newLinearObject, self.atGeom, 0) + # trasformo la geometria nel crs del layer + f.setGeometry(fromQadGeomToQgsGeom(updGeom, layer)) + + self.plugIn.beginEditCommand("Feature edited", layer) + + if self.copyEntities == False: + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, layer, f, False, False) == False: + self.plugIn.destroyEditCommand() + return False + else: + # plugIn, layer, features, coordTransform, refresh, check_validity + if qad_layer.addFeatureToLayer(self.plugIn, layer, f, None, False, False) == False: + self.plugIn.destroyEditCommand() + return False + + self.plugIn.endEditCommand() + self.nOperationsToUndo = self.nOperationsToUndo + 1 + + return True + + + def waitForDynamicPt(self): + keyWords = QadMsg.translate("Command_GRIP", "Copy") + "/" + \ + QadMsg.translate("Command_GRIP", "Undo") + "/" + \ + QadMsg.translate("Command_GRIP", "eXit") + prompt = QadMsg.translate("Command_GRIPLENGTHEN", "Specify new endpoint or [{0}]: ").format(keyWords) + + englishKeyWords = "Copy" + "/" + "Undo" + "/" "eXit" + keyWords += "_" + englishKeyWords + + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, QadInputModeEnum.NONE) + self.step = 1 + # imposto il map tool + self.getPointMapTool().setMode(Qad_lengthen_maptool_ModeEnum.ASK_FOR_DYNAMIC_POINT) + + + # ============================================================================ + # run + # ============================================================================ + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + # ========================================================================= + # RICHIESTA SELEZIONE OGGETTO + if self.step == 0: # inizio del comando + self.waitForDynamicPt() + return False + + # ========================================================================= + # RISPOSTA ALLA SELEZIONE OGGETTI DA MISURARE + elif self.step == 1: + ctrlKey = False + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + if self.copyEntities == False: + self.skipToNextGripCommand = True + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + ctrlKey = self.getPointMapTool().ctrlKey + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("Command_GRIP", "Copy") or value == "Copy": + # Copia entità lasciando inalterate le originali + self.copyEntities = True + + self.waitForDynamicPt() + elif value == QadMsg.translate("Command_GRIP", "Undo") or value == "Undo": + if self.nOperationsToUndo > 0: + self.nOperationsToUndo = self.nOperationsToUndo - 1 + self.plugIn.undoEditCommand() + else: + self.showMsg(QadMsg.translate("QAD", "\nThe command has been canceled.")) + + self.waitForDynamicPt() + elif value == QadMsg.translate("Command_GRIP", "eXit") or value == "eXit": + return True # fine comando + elif type(value) == QgsPointXY: # se é stato selezionato un punto + if ctrlKey: + self.copyEntities = True + + self.lengthen(value) + + if self.copyEntities == False: + return True + + self.waitForDynamicPt() + else: + if self.copyEntities == False: + self.skipToNextGripCommand = True + return True # fine comando + + return False \ No newline at end of file diff --git a/cmd/qad_lengthen_maptool.py b/cmd/qad_lengthen_maptool.py new file mode 100644 index 00000000..fe386dbd --- /dev/null +++ b/cmd/qad_lengthen_maptool.py @@ -0,0 +1,255 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + classe per gestire il map tool in ambito del comando lengthen + + ------------------- + begin : 2015-10-06 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from qgis.core import QgsWkbTypes + + +import math + + +from .. import qad_utils +from ..qad_getpoint import QadGetPoint, QadGetPointSelectionModeEnum +from ..qad_rubberband import QadRubberBand +from ..qad_dim import QadDimStyles +from ..qad_geom_relations import getQadGeomClosestVertex +from ..qad_multi_geom import getQadGeomAt, fromQadGeomToQgsGeom +from ..qad_snapper import QadSnapTypeEnum + + +# =============================================================================== +# Qad_lengthen_maptool_ModeEnum class. +# =============================================================================== +class Qad_lengthen_maptool_ModeEnum(): + # si richiede la selezione dell'oggetto da misurare + ASK_FOR_OBJ_TO_MISURE = 1 + # si richiede il delta + ASK_FOR_DELTA = 2 + # non si richiede niente + NONE = 3 + # si richiede la selezione dell'oggetto da allungare + ASK_FOR_OBJ_TO_LENGTHEN = 4 + # si richiede la percentuale + ASK_FOR_PERCENT = 5 + # si richiede il totale + ASK_FOR_TOTAL = 6 + # si richiede il nuovo punto dell'estremità in modalità dinamica + ASK_FOR_DYNAMIC_POINT = 7 + +# =============================================================================== +# Qad_lengthen_maptool class +# =============================================================================== +class Qad_lengthen_maptool(QadGetPoint): + + def __init__(self, plugIn): + QadGetPoint.__init__(self, plugIn) + + self.OpMode = None # "DElta" o "Percent" o "Total" o "DYnamic" + self.OpType = None # "length" o "Angle" + self.value = None + self.tmpLinearObject = None + + self.__rubberBand = QadRubberBand(self.canvas) + + + def hidePointMapToolMarkers(self): + QadGetPoint.hidePointMapToolMarkers(self) + self.__rubberBand.hide() + + def showPointMapToolMarkers(self): + QadGetPoint.showPointMapToolMarkers(self) + self.__rubberBand.show() + + def clear(self): + QadGetPoint.clear(self) + self.__rubberBand.reset() + self.mode = None + + def setInfo(self, entity, point): + # setta: self.layer, self.tmpLinearObject e self.move_startPt + + if self.tmpLinearObject is not None: + del self.tmpLinearObject + self.tmpLinearObject = None + + if entity.isInitialized() == False: + return False + + self.layer = entity.layer + qadGeom = entity.getQadGeom() + + # la funzione ritorna una lista con + # ( + # + # + # + # + # + result = getQadGeomClosestVertex(qadGeom, point) + self.atGeom = result[2] + self.tmpLinearObject = getQadGeomAt(qadGeom, self.atGeom, 0).copy() + + if qad_utils.getDistance(self.tmpLinearObject.getStartPt(), point) <= \ + qad_utils.getDistance(self.tmpLinearObject.getEndPt(), point): + # si allunga dal punto iniziale + self.move_startPt = True + else: + # si allunga dal punto finale + self.move_startPt = False + + return True + + + def canvasMoveEvent(self, event): + QadGetPoint.canvasMoveEvent(self, event) + + self.__rubberBand.reset() + res = False + + # si richiede la selezione dell'oggetto da allungare + if self.mode == Qad_lengthen_maptool_ModeEnum.ASK_FOR_OBJ_TO_LENGTHEN: + if self.tmpEntity.isInitialized(): + if self.setInfo(self.tmpEntity, self.tmpPoint) == False: + return + + newTmpLinearObject = self.tmpLinearObject.copy() + if self.OpMode == "DElta": + if self.OpType == "length": + res = newTmpLinearObject.lengthen_delta(self.move_startPt, self.value) + elif self.OpType == "Angle": + res = newTmpLinearObject.lengthen_deltaAngle(self.move_startPt, self.value) + elif self.OpMode == "Percent": + value = newTmpLinearObject.length() * self.value / 100 + value = value - newTmpLinearObject.length() + res = newTmpLinearObject.lengthen_delta(self.move_startPt, value) + elif self.OpMode == "Total": + if self.OpType == "length": + value = self.value - self.tmpLinearObject.length() + res = newTmpLinearObject.lengthen_delta(self.move_startPt, value) + elif self.OpType == "Angle": + if newTmpLinearObject.whatIs() == "ARC": + value = self.value - linearObject.totalAngle() + res = newTmpLinearObject.lengthen_deltaAngle(self.move_startPt, value) + + # si richiede un punto per la nuova estremità + elif self.mode == Qad_lengthen_maptool_ModeEnum.ASK_FOR_DYNAMIC_POINT: + newTmpLinearObject = self.tmpLinearObject.copy() + + if newTmpLinearObject.whatIs() == "POLYLINE": + if self.move_startPt: + linearObject = newTmpLinearObject.getLinearObjectAt(0) + else: + linearObject = newTmpLinearObject.getLinearObjectAt(-1) + else: + linearObject = newTmpLinearObject + + gType = linearObject.whatIs() + if gType == "LINE": + newPt = qad_utils.getPerpendicularPointOnInfinityLine(linearObject.getStartPt(), linearObject.getEndPt(), self.tmpPoint) + ang = linearObject.getTanDirectionOnStartPt() + elif gType == "ARC": + newPt = qad_utils.getPolarPointByPtAngle(linearObject.center, \ + qad_utils.getAngleBy2Pts(linearObject.center, self.tmpPoint), \ + linearObject.radius) + elif gType == "ELLIPSE_ARC": + pass + + if self.move_startPt: + linearObject.setStartPt(newPt) + else: + linearObject.setEndPt(newPt) + + if gType == "LINE" and newTmpLinearObject.whatIs() == "POLYLINE" and \ + qad_utils.TanDirectionNear(ang, linearObject.getTanDirectionOnStartPt()) == False: + res = False + else: + res = True + + if res == False: # allungamento impossibile + return + geom = fromQadGeomToQgsGeom(newTmpLinearObject, self.layer) + self.__rubberBand.addGeometry(geom, self.layer) + + + def activate(self): + QadGetPoint.activate(self) + self.__rubberBand.show() + + def deactivate(self): + try: # necessario perché se si chiude QGIS parte questo evento nonostante non ci sia più l'oggetto maptool ! + QadGetPoint.deactivate(self) + self.__rubberBand.hide() + except: + pass + + def setMode(self, mode): + self.mode = mode + + # si richiede la selezione dell'oggetto da misurare + if self.mode == Qad_lengthen_maptool_ModeEnum.ASK_FOR_OBJ_TO_MISURE: + self.setSelectionMode(QadGetPointSelectionModeEnum.ENTITY_SELECTION) + + # solo layer di tipo lineari che non appartengano a quote o di tipo poligono + layerList = [] + for layer in qad_utils.getVisibleVectorLayers(self.plugIn.canvas): # Tutti i layer vettoriali visibili + if layer.geometryType() == QgsWkbTypes.LineGeometry or layer.geometryType() == QgsWkbTypes.PolygonGeometry: + if len(QadDimStyles.getDimListByLayer(layer)) == 0: + layerList.append(layer) + + self.layersToCheck = layerList + self.onlyEditableLayers = False + self.setSnapType(QadSnapTypeEnum.DISABLE) + # si richiede il delta + elif self.mode == Qad_lengthen_maptool_ModeEnum.ASK_FOR_DELTA: + self.OpMode = "DElta" + self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) + # non si richiede niente + elif self.mode == Qad_lengthen_maptool_ModeEnum.NONE: + self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) + # si richiede la selezione dell'oggetto da allungare + elif self.mode == Qad_lengthen_maptool_ModeEnum.ASK_FOR_OBJ_TO_LENGTHEN: + self.setSelectionMode(QadGetPointSelectionModeEnum.ENTITY_SELECTION_DYNAMIC) + + # solo layer lineari editabili che non appartengano a quote + layerList = [] + for layer in qad_utils.getVisibleVectorLayers(self.plugIn.canvas): # Tutti i layer vettoriali visibili + if layer.geometryType() == QgsWkbTypes.LineGeometry and layer.isEditable(): + if len(QadDimStyles.getDimListByLayer(layer)) == 0: + layerList.append(layer) + + self.layersToCheck = layerList + self.onlyEditableLayers = True + self.setSnapType(QadSnapTypeEnum.DISABLE) + # si richiede la percentuale + elif self.mode == Qad_lengthen_maptool_ModeEnum.ASK_FOR_PERCENT: + self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) + self.OpMode = "Percent" + # si richiede il totale + elif self.mode == Qad_lengthen_maptool_ModeEnum.ASK_FOR_TOTAL: + self.OpMode = "Total" + self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) + # si richiede il nuovo punto dell'estremità in modalità dinamica + elif self.mode == Qad_lengthen_maptool_ModeEnum.ASK_FOR_DYNAMIC_POINT: + self.OpMode = "DYnamic" + self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) diff --git a/cmd/qad_line_cmd.py b/cmd/qad_line_cmd.py new file mode 100644 index 00000000..7b028c70 --- /dev/null +++ b/cmd/qad_line_cmd.py @@ -0,0 +1,434 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + comando PLINE per disegnare una linea + + ------------------- + begin : 2013-07-15 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.PyQt.QtGui import QIcon +from qgis.core import QgsWkbTypes, QgsGeometry + + +from ..qad_getpoint import QadGetPointDrawModeEnum +from ..qad_line import QadLine +from .qad_line_maptool import Qad_line_maptool, Qad_line_maptool_ModeEnum +from .qad_generic_cmd import QadCommandClass +from ..qad_msg import QadMsg +from ..qad_textwindow import QadInputTypeEnum, QadInputModeEnum +from ..qad_snapper import QadSnapTypeEnum, QadSnapper +from ..qad_geom_relations import * +from .. import qad_layer +from .. import qad_utils +from ..qad_rubberband import createRubberBand +from ..qad_entity import QadEntity +from ..qad_geom_relations import getQadGeomClosestPart + + +# Classe che gestisce il comando LINE +class QadLINECommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadLINECommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "LINE") + + def getEnglishName(self): + return "LINE" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runLINECommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/line.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_LINE", "Creates straight line segments.") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + self.vertices = [] + self.rubberBand = createRubberBand(self.plugIn.canvas, QgsWkbTypes.LineGeometry) + self.firstPtTan = None + self.firstPtPer = None + self.firstEntity = None + self.firstQadGeomPart = None + # se questo flag = True il comando serve all'interno di un altro comando per disegnare una linea + # che non verrà salvata su un layer + self.virtualCmd = False + + def __del__(self): + QadCommandClass.__del__(self) + self.rubberBand.hide() + self.plugIn.canvas.scene().removeItem(self.rubberBand) + + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if (self.plugIn is not None): + if self.PointMapTool is None: + self.PointMapTool = Qad_line_maptool(self.plugIn) + return self.PointMapTool + else: + return None + + + def addVertex(self, point): + self.vertices.append(point) + self.addPointToRubberBand(point) + self.plugIn.setLastPointAndSegmentAng(self.vertices[-1]) + self.setTmpGeometriesToMapTool() + + def delLastVertex(self): + if len(self.vertices) > 0: + del self.vertices[-1] # cancello ultimo vertice + self.removeLastPointToRubberBand() + if len(self.vertices) > 0: + self.plugIn.setLastPointAndSegmentAng(self.vertices[-1]) + self.setTmpGeometriesToMapTool() + + + # ============================================================================ + # addPointToRubberBand + # ============================================================================ + def addPointToRubberBand(self, point, doUpdate = True): + numberOfVertices = self.rubberBand.numberOfVertices() + + if numberOfVertices == 2: + # per un baco non ancora capito: se la linea ha solo 2 vertici e + # hanno la stessa x o y (linea orizzontale o verticale) + # la linea non viene disegnata perciò sposto un pochino la x o la y + adjustedPoint = qad_utils.getAdjustedRubberBandVertex(self.rubberBand.getPoint(0, 0), point) + self.rubberBand.addPoint(adjustedPoint, doUpdate) + else: + self.rubberBand.addPoint(point, doUpdate) + + + # ============================================================================ + # removeLastPointToRubberBand + # ============================================================================ + def removeLastPointToRubberBand(self): + self.rubberBand.removeLastPoint() + + def addLinesToLayer(self, layer): + i = 1 + line = QadLine() + while i < len(self.vertices): + line.set(self.vertices[i - 1], self.vertices[i]) + geom = line.asGeom(layer.wkbType()) + if geom is not None: + qad_layer.addGeomToLayer(self.plugIn, layer, self.mapToLayerCoordinates(layer, geom), None, True, False, \ + True if len(self.vertices) == 2 else False) + i = i + 1 + + + # ============================================================================ + # setTmpGeometriesToMapTool + # ============================================================================ + def setTmpGeometriesToMapTool(self): + self.getPointMapTool().clearTmpGeometries() + i = 1 + while i < len(self.vertices): + # per lo snap aggiungo questa geometria temporanea + self.getPointMapTool().appendTmpGeometry(QgsGeometry.fromPolylineXY([self.vertices[i - 1], self.vertices[i]])) + i = i + 1 + + + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + if self.virtualCmd == False: # se si vuole veramente salvare la polylinea in un layer + currLayer, errMsg = qad_layer.getCurrLayerEditable(self.plugIn.canvas, QgsWkbTypes.LineGeometry) + if currLayer is None: + self.showErr(errMsg) + return True # fine comando + + # RICHIESTA PRIMO PUNTO + if self.step == 0: # inizio del comando + # imposto il map tool + self.getPointMapTool().setMode(Qad_line_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_FIRST_PT) + # si appresta ad attendere un punto o enter + self.waitForPoint(QadMsg.translate("Command_LINE", "Specify first point: ")) + self.step = 1 + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA PUNTO OPPURE MENU PRINCIPALE + elif self.step == 1: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + if self.virtualCmd == False: # se si vuole veramente salvare in un layer + self.addLinesToLayer(currLayer) + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + snapTypeOnSel = self.getPointMapTool().snapTypeOnSelection + value = self.getPointMapTool().point + entity = self.getPointMapTool().entity + else: # il punto arriva come parametro della funzione + value = msg + snapTypeOnSel = QadSnapTypeEnum.NONE + + if type(value) == unicode: + if value == QadMsg.translate("Command_LINE", "Undo") or value == "Undo": + self.delLastVertex() # cancello ultimo vertice + # imposto il map tool + if len(self.vertices) == 0: + self.getPointMapTool().setMode(Qad_line_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_FIRST_PT) + # si appresta ad attendere un punto o enter + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(QadMsg.translate("Command_LINE", "Specify first point: "), \ + QadInputTypeEnum.POINT2D, None, "", QadInputModeEnum.NONE) + return False + else: + self.getPointMapTool().firstPt = self.vertices[-1] + self.getPointMapTool().setMode(Qad_line_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT) + elif value == QadMsg.translate("Command_LINE", "Close") or value == "Close": + newPt = self.vertices[0] + self.addVertex(newPt) # aggiungo un nuovo vertice + if self.virtualCmd == False: # se si vuole veramente salvare in un layer + self.addLinesToLayer(currLayer) + return True # fine comando + else: + if len(self.vertices) == 0: # primo punto + if value is None: + if self.plugIn.lastPoint is not None: + value = self.plugIn.lastPoint + else: + return True # fine comando + + # se é stato selezionato un punto con la modalità TAN_DEF é un punto differito + if snapTypeOnSel == QadSnapTypeEnum.TAN_DEF and entity.isInitialized(): + # se era stato selezionato un punto esplicito + if (self.firstPtTan is None) and (self.firstPtPer is None): + self.firstPtPer = None + self.firstPtTan = value + self.firstEntity = QadEntity(entity) # duplico l'entità + + # la funzione ritorna una lista con + # ( + # + # + # + # se geometria chiusa è tipo polyline la lista contiene anche + # + # <"a sinistra di" se il punto é alla sinista della parte (< 0 -> sinistra, > 0 -> destra) + # ) + result = getQadGeomClosestPart(self.firstEntity.getQadGeom(), self.firstPtTan) + self.firstQadGeomPart = getQadGeomPartAt(self.firstEntity.getQadGeom(), result[2], result[3], result[4]) + + # imposto il map tool + self.getPointMapTool().tan1 = self.firstPtTan + self.getPointMapTool().entity1 = self.firstEntity + self.getPointMapTool().setMode(Qad_line_maptool_ModeEnum.FIRST_TAN_KNOWN_ASK_FOR_SECOND_PT) + + # se era stato selezionato un punto con la modalità TAN_DEF + elif self.firstPtTan is not None: + result = getQadGeomClosestPart(entity.getQadGeom(), value) + secondQadGeomPart = getQadGeomPartAt(entity.getQadGeom(), result[2], result[3], result[4]) + + tangent = QadTangency.bestTwoBasicGeomObjects(self.firstQadGeomPart, self.firstPtTan, secondQadGeomPart, value) + if tangent is not None: + # prendo il punto più vicino a valueself.firstEntity + if qad_utils.getDistance(tangent.getStartPt(), value) < qad_utils.getDistance(tangent.getEndPt(), value): + self.addVertex(tangent.getEndPt()) # aggiungo un nuovo vertice + self.addVertex(tangent.getStartPt()) # aggiungo un nuovo vertice + self.getPointMapTool().firstPt = tangent.getStartPt() + else: + self.addVertex(tangent.getStartPt()) # aggiungo un nuovo vertice + self.addVertex(tangent.getEndPt()) # aggiungo un nuovo vertice + self.getPointMapTool().firstPt = tangent.getEndPt() + # imposto il map tool + self.getPointMapTool().setMode(Qad_line_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT) + else: + self.showMsg(QadMsg.translate("Command_LINE", "\nNo tangent possible")) + + # se era stato selezionato un punto con la modalità PER_DEF + elif self.firstPtPer is not None: + result = getQadGeomClosestPart(entity.getQadGeom(), value) + secondQadGeomPart = getQadGeomPartAt(entity.getQadGeom(), result[2], result[3], result[4]) + + tangent = QadTangPerp.bestTwoBasicGeomObjects(secondQadGeomPart, value, self.firstQadGeomPart, self.firstPtPer) + if tangent is not None: + # prendo il punto più vicino a value + if qad_utils.getDistance(tangent.getStartPt(), value) < qad_utils.getDistance(tangent.getEndPt(), value): + self.addVertex(tangent.getEndPt()) # aggiungo un nuovo vertice + self.addVertex(tangent.getStartPt()) # aggiungo un nuovo vertice + self.getPointMapTool().firstPt = tangent.getStartPt() + else: + self.addVertex(tangent.getStartPt()) # aggiungo un nuovo vertice + self.addVertex(tangent.getEndPt()) # aggiungo un nuovo vertice + self.getPointMapTool().firstPt = tangent.getEndPt() + # imposto il map tool + self.getPointMapTool().setMode(Qad_line_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT) + else: + self.showMsg(QadMsg.translate("Command_LINE", "\nNo tangent possible")) + + # se é stato selezionato un punto con la modalità PER_DEF é un punto differito + elif snapTypeOnSel == QadSnapTypeEnum.PER_DEF and entity.isInitialized(): + # se era stato selezionato un punto esplicito + if (self.firstPtTan is None) and (self.firstPtPer is None): + self.firstPtTan = None + self.firstPtPer = value + self.firstEntity = QadEntity(entity) # duplico l'entità + + # la funzione ritorna una lista con + # ( + # + # + # + # se geometria chiusa è tipo polyline la lista contiene anche + # + # <"a sinistra di" se il punto é alla sinista della parte (< 0 -> sinistra, > 0 -> destra) + # ) + result = getQadGeomClosestPart(self.firstEntity.getQadGeom(), self.firstPtPer) + self.firstQadGeomPart = getQadGeomPartAt(self.firstEntity.getQadGeom(), result[2], result[3], result[4]) + + # imposto il map tool + self.getPointMapTool().per1 = self.firstPtPer + self.getPointMapTool().entity1 = self.firstEntity + self.getPointMapTool().setMode(Qad_line_maptool_ModeEnum.FIRST_PER_KNOWN_ASK_FOR_SECOND_PT) + + # se era stato selezionato un punto con la modalità TAN_DEF + elif self.firstPtTan is not None: + result = getQadGeomClosestPart(entity.getQadGeom(), value) + secondQadGeomPart = getQadGeomPartAt(entity.getQadGeom(), result[2], result[3], result[4]) + + tangent = QadTangPerp.bestTwoBasicGeomObjects(self.firstQadGeomPart, self.firstPtTan, secondQadGeomPart, value) + if tangent is not None: + # prendo il punto più vicino a value + if qad_utils.getDistance(tangent.getStartPt(), value) < qad_utils.getDistance(tangent.getEndPt(), value): + self.addVertex(tangent.getEndPt()) # aggiungo un nuovo vertice + self.addVertex(tangent.getStartPt()) # aggiungo un nuovo vertice + self.getPointMapTool().firstPt = tangent.getStartPt() + else: + self.addVertex(tangent.getStartPt()) # aggiungo un nuovo vertice + self.addVertex(tangent.getEndPt()) # aggiungo un nuovo vertice + self.getPointMapTool().firstPt = tangent.getEndPt() + # imposto il map tool + self.getPointMapTool().setMode(Qad_line_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT) + else: + self.showMsg(QadMsg.translate("Command_LINE", "\nNo perpendicular possible")) + + # se era stato selezionato un punto con la modalità PER_DEF + elif self.firstPtPer is not None: + result = getQadGeomClosestPart(entity.getQadGeom(), value) + secondQadGeomPart = getQadGeomPartAt(entity.getQadGeom(), result[2], result[3], result[4]) + + line = QadPerpPerp.bestTwoBasicGeomObjects(self.firstQadGeomPart, self.firstPtPer, secondQadGeomPart, value) + if line is not None: + # prendo il punto più vicino a value + if qad_utils.getDistance(line.getStartPt(), value) < qad_utils.getDistance(line.getEndPt(), value): + self.addVertex(line.getEndPt()) # aggiungo un nuovo vertice + self.addVertex(line.getStartPt()) # aggiungo un nuovo vertice + self.getPointMapTool().firstPt = line.getStartPt() + else: + self.addVertex(line.getStartPt()) # aggiungo un nuovo vertice + self.addVertex(line.getEndPt()) # aggiungo un nuovo vertice + self.getPointMapTool().firstPt = line.getEndPt() + # imposto il map tool + self.getPointMapTool().setMode(Qad_line_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT) + else: + self.showMsg(QadMsg.translate("Command_LINE", "\nNo perpendicular possible")) + else: # altrimenti é un punto esplicito + # se era stato selezionato un punto con la modalità TAN_DEF + if self.firstPtTan is not None: + snapper = QadSnapper() + snapper.setSnapLayers(qad_utils.getSnappableVectorLayers(self.plugIn.canvas)) + snapper.setSnapType(QadSnapTypeEnum.TAN) + snapper.setStartPoint(value) + oSnapPoints = snapper.getSnapPoint(self.firstEntity, self.firstPtTan) + # memorizzo il punto di snap in point (prendo il primo valido) + for item in oSnapPoints.items(): + points = item[1] + if points is not None: + self.addVertex(points[0]) # aggiungo un nuovo vertice + self.addVertex(value) # aggiungo un nuovo vertice + break + + if len(self.vertices) == 0: + self.showMsg(QadMsg.translate("Command_LINE", "\nNo tangent possible")) + # se era stato selezionato un punto con la modalità PER_DEF + elif self.firstPtPer is not None: + snapper = QadSnapper() + snapper.setSnapLayers(qad_utils.getSnappableVectorLayers(self.plugIn.canvas)) + snapper.setSnapType(QadSnapTypeEnum.PER) + snapper.setStartPoint(value) + oSnapPoints = snapper.getSnapPoint(self.firstEntity, self.firstPtPer) + # memorizzo il punto di snap in point (prendo il primo valido) + for item in oSnapPoints.items(): + points = item[1] + if points is not None: + self.addVertex(points[0]) # aggiungo un nuovo vertice + self.addVertex(value) # aggiungo un nuovo vertice + break + + if len(self.vertices) == 0: + self.showMsg(QadMsg.translate("Command_LINE", "\nNo perpendicular possible")) + else: + self.addVertex(value) # aggiungo un nuovo vertice + + if len(self.vertices) > 0: + # imposto il map tool + self.getPointMapTool().firstPt = value + self.getPointMapTool().setMode(Qad_line_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT) + else: # secondo punto + if value is None: + if self.virtualCmd == False: # se si vuole veramente salvare in un layer + self.addLinesToLayer(currLayer) + return True # fine comando + # se il primo punto é esplicito + if len(self.vertices) > 0 is not None: + self.addVertex(value) # aggiungo un nuovo vertice + # imposto il map tool + self.getPointMapTool().firstPt = value + self.getPointMapTool().setMode(Qad_line_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT) + + if len(self.vertices) > 2: + keyWords = QadMsg.translate("Command_LINE", "Close") + "/" + \ + QadMsg.translate("Command_LINE", "Undo") + englishKeyWords = "Close" + "/" + "Undo" + else: + keyWords = QadMsg.translate("Command_LINE", "Undo") + englishKeyWords = "Undo" + prompt = QadMsg.translate("Command_LINE", "Specify next point or [{0}]: ").format(keyWords) + + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, QadInputModeEnum.NONE) + + return False + \ No newline at end of file diff --git a/qad_line_maptool.py b/cmd/qad_line_maptool.py similarity index 83% rename from qad_line_maptool.py rename to cmd/qad_line_maptool.py index d580e889..d14d8ed2 100644 --- a/qad_line_maptool.py +++ b/cmd/qad_line_maptool.py @@ -1,155 +1,145 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - classe per gestire il map tool di richiesta di un punto in ambito del comando line - - ------------------- - begin : 2013-05-22 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -from qgis.gui import * -import math - - -import qad_utils -from qad_snapper import * -from qad_snappointsdisplaymanager import * -from qad_variables import * -from qad_getpoint import * -from qad_circle import * -from qad_rubberband import QadRubberBand - - -#=============================================================================== -# Qad_line_maptool_ModeEnum class. -#=============================================================================== -class Qad_line_maptool_ModeEnum(): - # noto niente si richiede il primo punto - NONE_KNOWN_ASK_FOR_FIRST_PT = 1 - # noto il primo punto si richiede il secondo punto - FIRST_PT_KNOWN_ASK_FOR_SECOND_PT = 2 - # nota l'entita del primo punto di tangenza si richiede il secondo punto - FIRST_TAN_KNOWN_ASK_FOR_SECOND_PT = 3 - # nota l'entita del primo punto di perpendicolarità si richiede il secondo punto - FIRST_PER_KNOWN_ASK_FOR_SECOND_PT = 4 - - -#=============================================================================== -# Qad_line_maptool class -#=============================================================================== -class Qad_line_maptool(QadGetPoint): - - def __init__(self, plugIn): - QadGetPoint.__init__(self, plugIn) - - self.firstPt = None - self.tan1 = None - self.per1 = None - self.geom1 = None - self.__rubberBand = QadRubberBand(self.canvas) - - def __del__(self): - QadGetPoint.__del__(self) - - def hidePointMapToolMarkers(self): - QadGetPoint.hidePointMapToolMarkers(self) - self.__rubberBand.hide() - - def showPointMapToolMarkers(self): - QadGetPoint.showPointMapToolMarkers(self) - self.__rubberBand.show() - - def clear(self): - QadGetPoint.clear(self) - self.__rubberBand.reset() - self.mode = None - - - def canvasMoveEvent(self, event): - QadGetPoint.canvasMoveEvent(self, event) - - self.__rubberBand.reset() - - line = None - - # noto il primo punto si richiede il secondo punto - if self.mode == Qad_line_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT: - if (self.firstPt is not None): - line = [self.firstPt, self.tmpPoint] - # nota l'entita del primo punto di tangenza si richiede il secondo punto - elif self.mode == Qad_line_maptool_ModeEnum.FIRST_TAN_KNOWN_ASK_FOR_SECOND_PT: - snapper = QadSnapper() - snapper.setSnapPointCRS(self.canvas.mapRenderer().destinationCrs()) - snapper.setSnapType(QadSnapTypeEnum.TAN) - snapper.setStartPoint(self.tmpPoint) - oSnapPoints = snapper.getSnapPoint(self.geom1, self.tan1, self.canvas.mapRenderer().destinationCrs()) - # memorizzo il punto di snap in point (prendo il primo valido) - for item in oSnapPoints.items(): - points = item[1] - if points is not None: - line = [points[0], self.tmpPoint] - break - # nota l'entita del primo punto di perpendicolarità si richiede il secondo punto - elif self.mode == Qad_line_maptool_ModeEnum.FIRST_PER_KNOWN_ASK_FOR_SECOND_PT: - snapper = QadSnapper() - snapper.setSnapPointCRS(self.canvas.mapRenderer().destinationCrs()) - snapper.setSnapType(QadSnapTypeEnum.PER) - snapper.setStartPoint(self.tmpPoint) - oSnapPoints = snapper.getSnapPoint(self.geom1, self.per1, self.canvas.mapRenderer().destinationCrs()) - # memorizzo il punto di snap in point (prendo il primo valido) - for item in oSnapPoints.items(): - points = item[1] - if points is not None: - line = [points[0], self.tmpPoint] - break - - if line is not None: - self.__rubberBand.setLine(line) - - - def activate(self): - QadGetPoint.activate(self) - self.__rubberBand.show() - - def deactivate(self): - try: # necessario perché se si chiude QGIS parte questo evento nonostante non ci sia più l'oggetto maptool ! - QadGetPoint.deactivate(self) - self.__rubberBand.hide() - except: - pass - - def setMode(self, mode): - self.mode = mode - # noto niente si richiede il primo punto - if self.mode == Qad_line_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_FIRST_PT: - self.setDrawMode(QadGetPointDrawModeEnum.NONE) - self.setStartPoint(None) - # noto il primo punto si richiede il secondo punto - elif self.mode == Qad_line_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT: - self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.setStartPoint(self.firstPt) - # nota l'entita del primo punto di tangenza si richiede il secondo punto - elif self.mode == Qad_line_maptool_ModeEnum.FIRST_TAN_KNOWN_ASK_FOR_SECOND_PT: - self.setDrawMode(QadGetPointDrawModeEnum.NONE) - # nota l'entita del primo punto di perpendicolarità si richiede il secondo punto - elif self.mode == Qad_line_maptool_ModeEnum.FIRST_PER_KNOWN_ASK_FOR_SECOND_PT: - self.setDrawMode(QadGetPointDrawModeEnum.NONE) +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + classe per gestire il map tool di richiesta di un punto in ambito del comando line + + ------------------- + begin : 2013-05-22 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from .. import qad_utils +from ..qad_snapper import QadSnapper, QadSnapTypeEnum +from ..qad_getpoint import QadGetPoint, QadGetPointDrawModeEnum +from ..qad_rubberband import QadRubberBand + + +# =============================================================================== +# Qad_line_maptool_ModeEnum class. +# =============================================================================== +class Qad_line_maptool_ModeEnum(): + # noto niente si richiede il primo punto + NONE_KNOWN_ASK_FOR_FIRST_PT = 1 + # noto il primo punto si richiede il secondo punto + FIRST_PT_KNOWN_ASK_FOR_SECOND_PT = 2 + # nota l'entita del primo punto di tangenza si richiede il secondo punto + FIRST_TAN_KNOWN_ASK_FOR_SECOND_PT = 3 + # nota l'entita del primo punto di perpendicolarità si richiede il secondo punto + FIRST_PER_KNOWN_ASK_FOR_SECOND_PT = 4 + + +# =============================================================================== +# Qad_line_maptool class +# =============================================================================== +class Qad_line_maptool(QadGetPoint): + + def __init__(self, plugIn): + QadGetPoint.__init__(self, plugIn) + + self.firstPt = None + self.tan1 = None + self.per1 = None + self.entity1 = None + self.__rubberBand = QadRubberBand(self.canvas) + + def __del__(self): + QadGetPoint.__del__(self) + + def hidePointMapToolMarkers(self): + QadGetPoint.hidePointMapToolMarkers(self) + self.__rubberBand.hide() + + def showPointMapToolMarkers(self): + QadGetPoint.showPointMapToolMarkers(self) + self.__rubberBand.show() + + def clear(self): + QadGetPoint.clear(self) + self.__rubberBand.reset() + self.mode = None + + + def canvasMoveEvent(self, event): + QadGetPoint.canvasMoveEvent(self, event) + + self.__rubberBand.reset() + + line = None + + # noto il primo punto si richiede il secondo punto + if self.mode == Qad_line_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT: + if (self.firstPt is not None): + line = [self.firstPt, self.tmpPoint] + # nota l'entita del primo punto di tangenza si richiede il secondo punto + elif self.mode == Qad_line_maptool_ModeEnum.FIRST_TAN_KNOWN_ASK_FOR_SECOND_PT: + snapper = QadSnapper() + snapper.setSnapLayers(qad_utils.getSnappableVectorLayers(self.canvas)) + snapper.setSnapType(QadSnapTypeEnum.TAN) + snapper.setStartPoint(self.tmpPoint) + oSnapPoints = snapper.getSnapPoint(self.entity1, self.tan1) + # memorizzo il punto di snap in point (prendo il primo valido) + for item in oSnapPoints.items(): + points = item[1] + if points is not None: + line = [points[0], self.tmpPoint] + break + # nota l'entita del primo punto di perpendicolarità si richiede il secondo punto + elif self.mode == Qad_line_maptool_ModeEnum.FIRST_PER_KNOWN_ASK_FOR_SECOND_PT: + snapper = QadSnapper() + snapper.setSnapLayers(qad_utils.getSnappableVectorLayers(self.canvas)) + snapper.setSnapType(QadSnapTypeEnum.PER) + snapper.setStartPoint(self.tmpPoint) + oSnapPoints = snapper.getSnapPoint(self.entity1, self.per1) + # memorizzo il punto di snap in point (prendo il primo valido) + for item in oSnapPoints.items(): + points = item[1] + if points is not None: + line = [points[0], self.tmpPoint] + break + + if line is not None: + self.__rubberBand.setLine(line) + + + def activate(self): + QadGetPoint.activate(self) + self.__rubberBand.show() + + def deactivate(self): + try: # necessario perché se si chiude QGIS parte questo evento nonostante non ci sia più l'oggetto maptool ! + QadGetPoint.deactivate(self) + self.__rubberBand.hide() + except: + pass + + def setMode(self, mode): + self.mode = mode + # noto niente si richiede il primo punto + if self.mode == Qad_line_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_FIRST_PT: + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + self.setStartPoint(None) + # noto il primo punto si richiede il secondo punto + elif self.mode == Qad_line_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.firstPt) + # nota l'entita del primo punto di tangenza si richiede il secondo punto + elif self.mode == Qad_line_maptool_ModeEnum.FIRST_TAN_KNOWN_ASK_FOR_SECOND_PT: + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + # nota l'entita del primo punto di perpendicolarità si richiede il secondo punto + elif self.mode == Qad_line_maptool_ModeEnum.FIRST_PER_KNOWN_ASK_FOR_SECOND_PT: + self.setDrawMode(QadGetPointDrawModeEnum.NONE) diff --git a/cmd/qad_mapmpedit_cmd.py b/cmd/qad_mapmpedit_cmd.py new file mode 100644 index 00000000..3ad5450a --- /dev/null +++ b/cmd/qad_mapmpedit_cmd.py @@ -0,0 +1,771 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin OK + + comando MAPMPEDIT per editare un poligono esistente + + ------------------- + begin : 2016-04-05 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from qgis.PyQt.QtGui import QIcon +from qgis.core import QgsCoordinateTransform, QgsGeometry, QgsProject, QgsFeature + + +from .qad_generic_cmd import QadCommandClass +from ..qad_getpoint import * +from .qad_pline_cmd import QadPLINECommandClass +from .qad_ssget_cmd import QadSSGetClass +from ..qad_msg import QadMsg +from ..qad_textwindow import * +from .. import qad_utils +from .. import qad_layer +from .qad_entsel_cmd import QadEntSelClass +from ..qad_geom_relations import getQadGeomClosestVertex + + +# =============================================================================== +# QadMAPMPEDITCommandOpTypeEnum class. +# =============================================================================== +class QadMAPMPEDITCommandOpTypeEnum(): + UNION = 1 # unione tra poligoni + INTERSECTION = 2 # intersezione tra poligoni + DIFFERENCE = 3 # differenza tra poligoni + + +# Classe che gestisce il comando MAPMPEDIT +class QadMAPMPEDITCommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadMAPMPEDITCommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "MAPMPEDIT") + + def getEnglishName(self): + return "MAPMPEDIT" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runMAPMPEDITCommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/mapmpedit.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_MAPMPEDIT", "Modifies existing polygon.") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + + self.poligonEntity = QadEntity() + + self.SSGetClass = QadSSGetClass(plugIn) + self.SSGetClass.onlyEditableLayers = False + self.SSGetClass.checkDimLayers = False # scarto le quote + + self.entSelClass = None + + self.currAtGeom = None + self.currAtSubGeom = None + + self.nOperationsToUndo = 0 + + def __del__(self): + QadCommandClass.__del__(self) + del self.SSGetClass + self.poligonEntity.deselectOnLayer() + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if self.step == 1 or self.step == 4: # quando si é in fase di selezione entità + return self.entSelClass.getPointMapTool(drawMode) + elif self.step == 3 or self.step == 5 or \ + self.step == 6 or self.step == 7 or self.step == 8: # quando si é in fase di selezione gruppo entità + return self.SSGetClass.getPointMapTool() + else: + return QadCommandClass.getPointMapTool(self, drawMode) + + + def getCurrentContextualMenu(self): + if self.step == 1 or self.step == 4: # quando si é in fase di selezione entità + return self.entSelClass.getCurrentContextualMenu() + elif self.step == 3 or self.step == 5 or \ + self.step == 6 or self.step == 7 or self.step == 8: # quando si é in fase di selezione gruppo entità + return None # return self.SSGetClass.getCurrentContextualMenu() + else: + return self.contextualMenu + + + def reinitSSGetClass(self): + checkPointLayer = self.SSGetClass.checkPointLayer + del self.SSGetClass + self.SSGetClass = QadSSGetClass(self.plugIn) + self.SSGetClass.onlyEditableLayers = False + self.SSGetClass.checkDimLayers = False # scarto le quote + self.SSGetClass.checkPointLayer = checkPointLayer + + + # ============================================================================ + # setCurrentSubGeom + # ============================================================================ + def setCurrentSubGeom(self, entSelClass): + """ + Setta la sottogeometria corrente + """ + self.currAtGeom = None + self.currAtSubGeom = None + + # verifico che sia stata selezionata un'entità + if entSelClass.entity.isInitialized() == False: + self.showMsg(QadMsg.translate("QAD", "No geometries in this position.")) + return False + # verifico che sia stata selezionata attraverso un punto + # (per capire quale sottogeometria è stata selezionata) + if entSelClass.point is None: return False + # verifico che sia stato selezionato lo stesso polygono che è da modificare + if self.poligonEntity != entSelClass.entity: + self.showMsg(QadMsg.translate("Command_MAPMPEDIT", "The boundary doesn't belong to the selected polygon.")) + return False + + qadGeom = entSelClass.entity.getQadGeom() + + # la funzione ritorna una lista con + # ( + # + # + # + # + # + result = getQadGeomClosestVertex(qadGeom, entSelClass.point) + self.currAtGeom = result[2] + self.currAtSubGeom = result[3] + + return True + + + # ============================================================================ + # addEntitySetToPolygon + # ============================================================================ + def addEntitySetToPolygon(self, entitySet, removeOriginals = False): + """ + Aggiunge il set di entità al poligono da modificare + """ + geom = self.poligonEntity.getGeometry() + layerList = [] + layerList.append(self.poligonEntity.layer) + + for layerEntitySet in entitySet.layerEntitySetList: + layer = layerEntitySet.layer + if layer.geometryType() != QgsWkbTypes.PolygonGeometry and layer.geometryType() != QgsWkbTypes.LineGeometry: + self.showMsg(QadMsg.translate("QAD", "Invalid object.")) + return False + + if removeOriginals: layerList.append(layer) + coordTransform = QgsCoordinateTransform(layer.crs(), self.poligonEntity.layer.crs(), QgsProject.instance()) + + for featureId in layerEntitySet.featureIds: + # se la feature è quella di polygonEntity è errore + if layer.id() == self.poligonEntity.layerId() and featureId == self.poligonEntity.featureId: + self.showMsg(QadMsg.translate("QAD", "Invalid object.")) + return False + + f = layerEntitySet.getFeature(featureId) + # trasformo la geometria nel crs del layer del poligono da modificare + geomToAdd = f.geometry() + geomToAdd.transform(coordTransform) + + # se il poligono è contenuto nella geometria da aggiungere + if geomToAdd.contains(geom): + # Riduco la geometria in point o polyline + simplifiedGeoms = qad_utils.asPointOrPolyline(geom) + # deve essere un poligono senza ring + if len(simplifiedGeoms) != 1 or \ + (simplifiedGeoms[0].isMultipart() == True or simplifiedGeoms[0].type() != QgsWkbTypes.LineGeometry): + self.showMsg(QadMsg.translate("QAD", "Invalid object.")) + return False + points = simplifiedGeoms[0].asPolyline() # vettore di punti + # aggiungo un'isola + if geomToAdd.addRing(points) != 0: # 0 in case of success + self.showMsg(QadMsg.translate("QAD", "Invalid object.")) + return False + del geom + geom = QgsGeometry.fromPolygonXY(geomToAdd.asPolygon()) + else: # se il poligono non è contenuto nella geometria da aggiungere + # Riduco la geometria in point o polyline + simplifiedGeoms = qad_utils.asPointOrPolyline(geomToAdd) + for simplifiedGeom in simplifiedGeoms: + # se la geometria da aggiungere è contenuta nel poligono + if geom.contains(simplifiedGeom): + points = simplifiedGeom.asPolyline() # vettore di punti + # aggiungo un'isola + if geom.addRing(points) != 0: # 0 in case of success + self.showMsg(QadMsg.translate("QAD", "Invalid object.")) + return False + else: + # aggiungo una parte + if geom.addPartGeometry(simplifiedGeom) != QgsGeometry.Success: + self.showMsg(QadMsg.translate("QAD", "Invalid object.")) + return False + + f = self.poligonEntity.getFeature() + f.setGeometry(geom) + + layerList = entitySet.getLayerList() + layerList.append(self.poligonEntity.layer) + + self.plugIn.beginEditCommand("Feature edited", layerList) + + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, self.poligonEntity.layer, f, False, False) == False: + self.plugIn.destroyEditCommand() + return False + + if removeOriginals: + for layerEntitySet in entitySet.layerEntitySetList: + if qad_layer.deleteFeaturesToLayer(self.plugIn, layerEntitySet.layer, layerEntitySet.featureIds, False) == False: + self.plugIn.destroyEditCommand() + return + + self.plugIn.endEditCommand() + self.nOperationsToUndo = self.nOperationsToUndo + 1 + + return True + + + # ============================================================================ + # delCurrentSubGeomToPolygon + # ============================================================================ + def delCurrentSubGeomToPolygon(self): + """ + Cancella la sotto-geometria corrente dal poligono da modificare + """ + geom = self.poligonEntity.getGeometry() + + # la posizione é espressa con una lista ( []) + part = self.currAtGeom + if self.currAtSubGeom > 0: + ring = self.currAtSubGeom + if geom.deleteRing(ring, part) == False: # cancello una isola (Ring 0 is outer ring and can't be deleted) + self.showMsg(QadMsg.translate("QAD", "Invalid object.")) + return False + else: + if geom.deletePart(part) == False: # cancello una parte + self.showMsg(QadMsg.translate("QAD", "Invalid object.")) + return False + + f = self.poligonEntity.getFeature() + f.setGeometry(geom) + + self.plugIn.beginEditCommand("Feature edited", self.poligonEntity.layer) + + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, self.poligonEntity.layer, f, False, False) == False: + self.plugIn.destroyEditCommand() + return False + + self.plugIn.endEditCommand() + self.nOperationsToUndo = self.nOperationsToUndo + 1 + + + # ============================================================================ + # unionIntersSubtractEntitySetToPolygon + # ============================================================================ + def unionIntersSubtractEntitySetToPolygon(self, entitySet, opType, removeOriginals = False): + """ + Unisce o interseca i poligoni di entitySet al poligono corrente + """ + geom = self.poligonEntity.getGeometry() + layerList = [] + layerList.append(self.poligonEntity.layer) + + geomList = [] + geomList.append(geom) + for layerEntitySet in entitySet.layerEntitySetList: + del geomList[:] + layer = layerEntitySet.layer + coordTransform = QgsCoordinateTransform(layer.crs(), self.poligonEntity.layer.crs(), QgsProject.instance()) + + if layer.geometryType() == QgsWkbTypes.PolygonGeometry: + for featureId in layerEntitySet.featureIds: + # se la feature è quella di polygonEntity è errore + if layer.id() == self.poligonEntity.layerId() and featureId == self.poligonEntity.featureId: + self.showMsg(QadMsg.translate("QAD", "Invalid object.")) + return False + f = layerEntitySet.getFeature(featureId) + # trasformo la geometria nel crs del layer del poligono da modificare + geomToAdd = f.geometry() + + geomToAdd.transform(coordTransform) + + if opType == QadMAPMPEDITCommandOpTypeEnum.UNION: geom = geom.combine(geomToAdd) + elif opType == QadMAPMPEDITCommandOpTypeEnum.INTERSECTION: geom = geom.intersection(geomToAdd) + elif opType == QadMAPMPEDITCommandOpTypeEnum.DIFFERENCE: geom = geom.difference(geomToAdd) + + if geom is None: + self.showMsg(QadMsg.translate("QAD", "Invalid object.")) + return False + + if removeOriginals and layer.id() != self.poligonEntity.layerId(): + layerList.append(layer) + + elif layer.geometryType() == QgsWkbTypes.LineGeometry: + for featureId in layerEntitySet.featureIds: + f = layerEntitySet.getFeature(featureId) + # trasformo la geometria nel crs del layer del poligono da modificare + geomToAdd = f.geometry() + geomToAdd.transform(coordTransform) + # Riduco la geometria in point o polyline + simplifiedGeoms = qad_utils.asPointOrPolyline(geomToAdd) + for simplifiedGeom in simplifiedGeoms: + if simplifiedGeoms.isMultipart() == True or simplifiedGeoms.type() != QgsWkbTypes.LineGeometry: + self.showMsg(QadMsg.translate("QAD", "Invalid object.")) + return False + points = simplifiedGeom.asPolyline() # vettore di punti + + if len(points) < 4 or points[0] != points[-1]: # polilinea chiusa con almeno 4 punti (primo e ultimo uguali) + self.showMsg(QadMsg.translate("QAD", "Invalid object.")) + return False + geomToAdd = QgsGeometry.fromPolygonXY([points]) + + if opType == QadMAPMPEDITCommandOpTypeEnum.UNION: geom = geom.combine(geomToAdd) + elif opType == QadMAPMPEDITCommandOpTypeEnum.INTERSECTION: geom = geom.intersection(geomToAdd) + elif opType == QadMAPMPEDITCommandOpTypeEnum.DIFFERENCE: geom = geom.difference(geomToAdd) + + if geom is None or geom.type() != QgsWkbTypes.PolygonGeometry: + self.showMsg(QadMsg.translate("QAD", "Invalid object.")) + return False + + if removeOriginals: layerList.append(layer) + else: + self.showMsg(QadMsg.translate("QAD", "Invalid object.")) + return False + + f = self.poligonEntity.getFeature() + f.setGeometry(geom) + + self.plugIn.beginEditCommand("Feature edited", layerList) + + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, self.poligonEntity.layer, f, False, False) == False: + self.plugIn.destroyEditCommand() + return False + + if removeOriginals: + for layerEntitySet in entitySet.layerEntitySetList: + if qad_layer.deleteFeaturesToLayer(self.plugIn, layerEntitySet.layer, layerEntitySet.featureIds, False) == False: + self.plugIn.destroyEditCommand() + return + + self.plugIn.endEditCommand() + self.nOperationsToUndo = self.nOperationsToUndo + 1 + + return True + + + # ============================================================================ + # convexHullEntitySetToPolygon + # ============================================================================ + def convexHullEntitySetToPolygon(self, entitySet, removeOriginals = False): + """ + modifica il poligono corrente in modo che includa tutti i punti delle geometrie di entitySet + """ + layerList = [] + layerList.append(self.poligonEntity.layer) + pointsForConvexHull = [] + + for layerEntitySet in entitySet.layerEntitySetList: + layer = layerEntitySet.layer + coordTransform = QgsCoordinateTransform(layer.crs(), self.poligonEntity.layer.crs(), QgsProject.instance()) + + for featureId in layerEntitySet.featureIds: + f = layerEntitySet.getFeature(featureId) + # trasformo la geometria nel crs del layer del poligono da modificare + geom = f.geometry() + geom.transform(coordTransform) + + # Riduco la geometria in point o polyline + simplifiedGeoms = qad_utils.asPointOrPolyline(geom) + for simplifiedGeom in simplifiedGeoms: + gType = simplifiedGeom.type() + if simplifiedGeom.isMultipart() == False and simplifiedGeom.isMultipart() == False: + pointsForConvexHull.extend(simplifiedGeom.asPolyline()) + else: + pointsForConvexHull.append(simplifiedGeom.asPoint()) + + if removeOriginals and layer.id() != self.poligonEntity.layerId(): + layerList.append(layer) + + geom = QgsGeometry.fromMultiPointXY(pointsForConvexHull) + geom = geom.convexHull() + if geom is None: + self.showMsg(QadMsg.translate("QAD", "Invalid object.")) + return False + + f = self.poligonEntity.getFeature() + f.setGeometry(geom) + + self.plugIn.beginEditCommand("Feature edited", layerList) + + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, self.poligonEntity.layer, f, False, False) == False: + self.plugIn.destroyEditCommand() + return False + + if removeOriginals: + for layerEntitySet in entitySet.layerEntitySetList: + if qad_layer.deleteFeaturesToLayer(self.plugIn, layerEntitySet.layer, layerEntitySet.featureIds, False) == False: + self.plugIn.destroyEditCommand() + return + + self.plugIn.endEditCommand() + self.nOperationsToUndo = self.nOperationsToUndo + 1 + + return True + + + # ============================================================================ + # dividePolygon + # ============================================================================ + def splitPolygon(self, splitLine, createNewEntities): + """ + divide il poligono corrente usando una polilinea con i vertici in in modo da generare o meno nuove entità + """ + layerList = [] + layerList.append(self.poligonEntity.layer) + + splitLineTransformed = self.mapToLayerCoordinates(self.poligonEntity.layer, splitLine) + f = self.poligonEntity.getFeature() + oldGeom = f.geometry() + result, newGeoms, topologyTestPts = oldGeom.splitGeometry(splitLineTransformed, False) # Set to true if you want to split a feature, otherwise set to false to split parts + + if result != QgsGeometry.Success or len(newGeoms) == 0: + self.showMsg(QadMsg.translate("QAD", "Invalid object.")) + return False + + f.setGeometry(oldGeom) + self.plugIn.beginEditCommand("Feature edited", layerList) + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, self.poligonEntity.layer, f, False, False) == False: + self.plugIn.destroyEditCommand() + return False + + if len(newGeoms) > 0: + newfeatures =[] + if createNewEntities: + for newGeom in newGeoms: + newfeature = QgsFeature(f) + newfeature.setGeometry(newGeom) + newfeatures.append(newfeature) + + # plugIn, layer, features, coordTransform, refresh, check_validity + if qad_layer.addFeaturesToLayer(self.plugIn, self.poligonEntity.layer, newfeatures, None, False, False) == False: + self.plugIn.destroyEditCommand() + return + else: + for newGeom in newGeoms: + if oldGeom.addPartGeometry(newGeom) != QgsGeometry.Success: + return False + f.setGeometry(oldGeom) + + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, self.poligonEntity.layer, f, False, False) == False: + self.plugIn.destroyEditCommand() + return False + self.nOperationsToUndo = self.nOperationsToUndo + 1 + + self.plugIn.endEditCommand() + + return True + + + + # ============================================================================ + # waitForEntsel + # ============================================================================ + def waitForEntsel(self, msgMapTool, msg): + if self.entSelClass is not None: + del self.entSelClass + self.step = 1 + self.entSelClass = QadEntSelClass(self.plugIn) + self.entSelClass.msg = QadMsg.translate("Command_MAPMPEDIT", "Select polygon: ") + # scarto la selezione di punti e polilinee + self.entSelClass.checkPointLayer = False + self.entSelClass.checkLineLayer = False + self.entSelClass.checkPolygonLayer = True + self.entSelClass.checkDimLayers = False + self.entSelClass.onlyEditableLayers = True + + self.entSelClass.run(msgMapTool, msg) + + + # ============================================================================ + # WaitForMainMenu + # ============================================================================ + def WaitForMainMenu(self): + self.poligonEntity.selectOnLayer(False) + keyWords = QadMsg.translate("Command_MAPMPEDIT", "Add") + "/" + \ + QadMsg.translate("Command_MAPMPEDIT", "dElete") + "/" + \ + QadMsg.translate("Command_MAPMPEDIT", "Union") + "/" + \ + QadMsg.translate("Command_MAPMPEDIT", "Substract") + "/" + \ + QadMsg.translate("Command_MAPMPEDIT", "Intersect") + "/" + \ + QadMsg.translate("Command_MAPMPEDIT", "split Objects") + "/" + \ + QadMsg.translate("Command_MAPMPEDIT", "split Parts") + "/" + \ + QadMsg.translate("Command_MAPMPEDIT", "iNclude objs") + englishKeyWords = "Add" + "/" + "dElete" + "/" + "Union" + "/" + "Substract" + "/" + "Intersect" "/" + \ + "split Objects" + "/" + "split Parts" + "/" + "iNclude objs" + + if self.nOperationsToUndo > 0: # se c'è qualcosa che si può annullare + keyWords = keyWords + "/" + QadMsg.translate("Command_MAPMPEDIT", "unDo") + englishKeyWords = englishKeyWords + "/" + "unDo" + + keyWords = keyWords + "/" + QadMsg.translate("Command_MAPMPEDIT", "eXit") + englishKeyWords = englishKeyWords + "/" + "eXit" + + default = QadMsg.translate("Command_MAPMPEDIT", "eXit") + + prompt = QadMsg.translate("Command_MAPMPEDIT", "Enter an option [{0}] <{1}>: ").format(keyWords, default) + + self.step = 2 + self.getPointMapTool().setSelectionMode(QadGetPointSelectionModeEnum.NONE) + self.getPointMapTool().setDrawMode(QadGetPointDrawModeEnum.NONE) + + keyWords += "_" + englishKeyWords + # si appresta ad attendere enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, QadInputModeEnum.NONE) + return False + + + # ============================================================================ + # waitForBoundary + # ============================================================================ + def waitForBoundary(self, msgMapTool, msg): + if self.entSelClass is not None: + del self.entSelClass + self.entSelClass = QadEntSelClass(self.plugIn) + self.entSelClass.msg = QadMsg.translate("Command_MAPMPEDIT", "Select boundary: ") + # scarto la selezione di punti e polilinee + self.entSelClass.checkPointLayer = False + self.entSelClass.checkLineLayer = False + self.entSelClass.checkPolygonLayer = True + self.entSelClass.checkDimLayers = False + self.entSelClass.onlyEditableLayers = True + + self.entSelClass.run(msgMapTool, msg) + + + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + if self.step == 0: + self.waitForEntsel(msgMapTool, msg) # seleziona il poligono da modificare + return False # continua + + # ========================================================================= + # RISPOSTA ALLA SELEZIONE POLIGONO DA MODIFICARE + elif self.step == 1: + if self.entSelClass.run(msgMapTool, msg) == True: + if self.entSelClass.entity.isInitialized(): + self.poligonEntity.set(self.entSelClass.entity.layer, self.entSelClass.entity.featureId) + layer = self.entSelClass.entity.layer + self.poligonEntity.deselectOnLayer() + self.WaitForMainMenu() + else: + if self.entSelClass.canceledByUsr == True: # fine comando + return True + self.showMsg(QadMsg.translate("QAD", "No geometries in this position.")) + self.waitForEntsel(msgMapTool, msg) + + return False # continua + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL MENU PRINCIPALE + elif self.step == 2: # dopo aver atteso una opzione si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + self.WaitForMainMenu() + return False + else: # l'opzione arriva come parametro della funzione + value = msg + + self.poligonEntity.deselectOnLayer() + + if value == QadMsg.translate("Command_MAPMPEDIT", "Add") or value == "Add": + self.SSGetClass.checkPointLayer = False # scarto i punto + self.SSGetClass.run(msgMapTool, msg) + self.step = 3 + return False + elif value == QadMsg.translate("Command_MAPMPEDIT", "dElete") or value == "Delete": + self.waitForBoundary(msgMapTool, msg) + self.step = 4 + return False + elif value == QadMsg.translate("Command_MAPMPEDIT", "Union") or value == "Union": + self.SSGetClass.checkPointLayer = False # scarto i layer puntuali + self.SSGetClass.run(msgMapTool, msg) + self.step = 5 + return False + elif value == QadMsg.translate("Command_MAPMPEDIT", "Substract") or value == "Substract": + self.SSGetClass.checkPointLayer = False # scarto i layer puntuali + self.SSGetClass.run(msgMapTool, msg) + self.step = 6 + return False + elif value == QadMsg.translate("Command_MAPMPEDIT", "Intersect") or value == "Intersect": + self.SSGetClass.checkPointLayer = False # scarto i layer puntuali + self.SSGetClass.run(msgMapTool, msg) + self.step = 7 + return False + elif value == QadMsg.translate("Command_MAPMPEDIT", "split Objects") or value == "split Objects": + # Disegna una polilinea di divisione del poligono + self.PLINECommand = QadPLINECommandClass(self.plugIn) + # se questo flag = True il comando serve all'interno di un altro comando per disegnare una linea + # che non verrà salvata su un layer + self.PLINECommand.virtualCmd = True + self.PLINECommand.run(msgMapTool, msg) + self.step = 9 + return False + elif value == QadMsg.translate("Command_MAPMPEDIT", "split Parts") or value == "split Parts": + # Disegna una polilinea di divisione del poligono + self.PLINECommand = QadPLINECommandClass(self.plugIn) + # se questo flag = True il comando serve all'interno di un altro comando per disegnare una linea + # che non verrà salvata su un layer + self.PLINECommand.virtualCmd = True + self.PLINECommand.run(msgMapTool, msg) + self.step = 10 + return False + elif value == QadMsg.translate("Command_MAPMPEDIT", "iNclude objs") or value == "iNclude objs": + self.SSGetClass.checkPointLayer = True # includo i layer puntuali + self.SSGetClass.run(msgMapTool, msg) + self.step = 8 + return False + elif value == QadMsg.translate("Command_MAPMPEDIT", "unDo") or value == "unDo": + if self.nOperationsToUndo > 0: + self.nOperationsToUndo = self.nOperationsToUndo - 1 + self.plugIn.undoEditCommand() + else: + self.showMsg(QadMsg.translate("QAD", "\nThe command has been canceled.")) + elif value == QadMsg.translate("Command_MAPMPEDIT", "eXit") or value == "eXit": + return True # fine comando + else: + return True # fine comando + + self.WaitForMainMenu() + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DELLA MODALITA' DI ADD (da step = 2) + elif self.step == 3: + if self.SSGetClass.run(msgMapTool, msg) == True: + if self.SSGetClass.entitySet.count() > 0: + self.addEntitySetToPolygon(self.SSGetClass.entitySet) + self.reinitSSGetClass() + self.WaitForMainMenu() + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DELLA MODALITA' DI DELETE (da step = 2) + elif self.step == 4: + if self.entSelClass.run(msgMapTool, msg) == True: + if self.setCurrentSubGeom(self.entSelClass) == True: + self.delCurrentSubGeomToPolygon() + self.WaitForMainMenu() + return False + else: + if self.entSelClass.canceledByUsr == True: # fine selezione entità + self.WaitForMainMenu() + else: + self.waitForBoundary(msgMapTool, msg) + return False # continua + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DELLA MODALITA' DI UNION (da step = 2) + elif self.step == 5: # dopo aver atteso una entità si riavvia il comando + if self.SSGetClass.run(msgMapTool, msg) == True: + if self.SSGetClass.entitySet.count() > 0: + self.unionIntersSubtractEntitySetToPolygon(self.SSGetClass.entitySet, QadMAPMPEDITCommandOpTypeEnum.UNION) + self.reinitSSGetClass() + self.WaitForMainMenu() + return False # continua + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DELLA MODALITA' DI SUBTRACT (da step = 2) + elif self.step == 6: # dopo aver atteso una entità si riavvia il comando + if self.SSGetClass.run(msgMapTool, msg) == True: + if self.SSGetClass.entitySet.count() > 0: + self.unionIntersSubtractEntitySetToPolygon(self.SSGetClass.entitySet, QadMAPMPEDITCommandOpTypeEnum.DIFFERENCE) + self.reinitSSGetClass() + self.WaitForMainMenu() + return False # continua + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DELLA MODALITA' DI INTERSECT (da step = 2) + elif self.step == 7: # dopo aver atteso una entità si riavvia il comando + if self.SSGetClass.run(msgMapTool, msg) == True: + if self.SSGetClass.entitySet.count() > 0: + self.unionIntersSubtractEntitySetToPolygon(self.SSGetClass.entitySet, QadMAPMPEDITCommandOpTypeEnum.INTERSECTION) + self.reinitSSGetClass() + self.WaitForMainMenu() + return False # continua + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DELLA MODALITA' DI INCLUDE OBJS (da step = 2) + elif self.step == 8: # dopo aver atteso una entità si riavvia il comando + if self.SSGetClass.run(msgMapTool, msg) == True: + if self.SSGetClass.entitySet.count() > 0: + self.convexHullEntitySetToPolygon(self.SSGetClass.entitySet) + self.reinitSSGetClass() + self.WaitForMainMenu() + return False # continua + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DELLA LINEA DI DIVISIONE (da step = 2) + elif self.step == 9: # dopo aver atteso un punto si riavvia il comando + if self.PLINECommand.run(msgMapTool, msg) == True: + self.showMsg("\n") + self.splitPolygon(self.PLINECommand.polyline.asPolyline(), True) + del self.PLINECommand + self.PLINECommand = None + self.WaitForMainMenu() + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DELLA LINEA DI DIVISIONE (da step = 2) + elif self.step == 10: # dopo aver atteso un punto si riavvia il comando + if self.PLINECommand.run(msgMapTool, msg) == True: + self.showMsg("\n") + self.splitPolygon(self.PLINECommand.polyline.asPolyline(), False) + del self.PLINECommand + self.PLINECommand = None + self.WaitForMainMenu() + return False + \ No newline at end of file diff --git a/qad_mbuffer_cmd.py b/cmd/qad_mbuffer_cmd.py similarity index 80% rename from qad_mbuffer_cmd.py rename to cmd/qad_mbuffer_cmd.py index f5e9266b..3d40b5bc 100644 --- a/qad_mbuffer_cmd.py +++ b/cmd/qad_mbuffer_cmd.py @@ -1,293 +1,297 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - comando MBUFFER per creare oggetti originati da buffer su altri oggetti - - ------------------- - begin : 2013-09-19 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -from qad_mbuffer_maptool import * -from qad_generic_cmd import QadCommandClass -from qad_msg import QadMsg -from qad_getpoint import * -from qad_textwindow import * -from qad_ssget_cmd import QadSSGetClass -from qad_entity import * -import qad_utils -import qad_layer -from qad_dim import QadDimStyles - - -# Classe che gestisce il comando MBUFFER -class QadMBUFFERCommandClass(QadCommandClass): - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadMBUFFERCommandClass(self.plugIn) - - def getName(self): - return QadMsg.translate("Command_list", "MBUFFER") - - def getEnglishName(self): - return "MBUFFER" - - def connectQAction(self, action): - QObject.connect(action, SIGNAL("triggered()"), self.plugIn.runMBUFFERCommand) - - def getIcon(self): - return QIcon(":/plugins/qad/icons/mbuffer.png") - - def getNote(self): - # impostare le note esplicative del comando - return QadMsg.translate("Command_MBUFFER", "Creates polygons by buffering selected objects.") - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - # se questo flag = True il comando serve all'interno di un altro comando per disegnare un buffer - # che non verrà salvato su un layer - self.virtualCmd = False - self.rubberBandBorderColor = None - self.rubberBandFillColor = None - self.SSGetClass = QadSSGetClass(plugIn) - self.entitySet = QadEntitySet() - self.width = 0 - self.segments = self.plugIn.segments # il numero di segmenti per l'approssimazione delle curve - - def __del__(self): - QadCommandClass.__del__(self) - del SSGetClass - - def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): - if self.step == 0: # quando si é in fase di selezione entità - return self.SSGetClass.getPointMapTool() - else: - if (self.plugIn is not None): - if self.PointMapTool is None: - self.PointMapTool = Qad_mbuffer_maptool(self.plugIn) - self.PointMapTool.setRubberBandColor(self.rubberBandBorderColor, self.rubberBandFillColor) - return self.PointMapTool - else: - return None - - - def setRubberBandColor(self, rubberBandBorderColor, rubberBandFillColor): - self.rubberBandBorderColor = rubberBandBorderColor - self.rubberBandFillColor = rubberBandFillColor - if self.PointMapTool is not None: - self.PointMapTool.setRubberBandColor(self.rubberBandBorderColor, self.rubberBandFillColor) - - - def AddGeoms(self, currLayer): - bufferGeoms = [] - tolerance = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) - - for layerEntitySet in self.entitySet.layerEntitySetList: - layer = layerEntitySet.layer - geoms = layerEntitySet.getGeometryCollection() - - for geom in geoms: - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - newGeom = self.layerToMapCoordinates(layer, geom) - g = qad_utils.ApproxCurvesOnGeom(newGeom.buffer(self.width, self.segments), \ - self.segments, self.segments, \ - tolerance) - # trasformo la geometria nel crs del layer - bufferGeoms.append(self.mapToLayerCoordinates(layer, g)) - - self.plugIn.beginEditCommand("Feature buffered", currLayer) - - # filtro le features per tipo - pointGeoms, lineGeoms, polygonGeoms = qad_utils.filterGeomsByType(bufferGeoms, \ - currLayer.geometryType()) - # aggiungo le geometrie del tipo corretto - if currLayer.geometryType() == QGis.Line: - polygonToLines = [] - # Riduco le geometrie in linee - for g in polygonGeoms: - lines = qad_utils.asPointOrPolyline(g) - for l in lines: - if l.type() == QGis.Line: - polygonToLines.append(l) - # plugIn, layer, geoms, coordTransform , refresh, check_validity - if qad_layer.addGeomsToLayer(self.plugIn, currLayer, polygonToLines, None, False, False) == False: - self.plugIn.destroyEditCommand() - return - - del polygonGeoms[:] # svuoto la lista - - # plugIn, layer, geoms, coordTransform , refresh, check_validity - if qad_layer.addGeomsToLayer(self.plugIn, currLayer, bufferGeoms, None, False, False) == False: - self.plugIn.destroyEditCommand() - return - - if pointGeoms is not None and len(pointGeoms) > 0: - PointTempLayer = qad_layer.createQADTempLayer(self.plugIn, QGis.Point) - self.plugIn.addLayerToLastEditCommand("Feature buffered", PointTempLayer) - - if lineGeoms is not None and len(lineGeoms) > 0: - LineTempLayer = qad_layer.createQADTempLayer(self.plugIn, QGis.Line) - self.plugIn.addLayerToLastEditCommand("Feature buffered", LineTempLayer) - - if polygonGeoms is not None and len(polygonGeoms) > 0: - PolygonTempLayer = qad_layer.createQADTempLayer(self.plugIn, QGis.Polygon) - self.plugIn.addLayerToLastEditCommand("Feature buffered", PolygonTempLayer) - - # aggiungo gli scarti nei layer temporanei di QAD - # trasformo la geometria in quella dei layer temporanei - # plugIn, pointGeoms, lineGeoms, polygonGeoms, coord, refresh - if qad_layer.addGeometriesToQADTempLayers(self.plugIn, pointGeoms, lineGeoms, polygonGeoms, \ - None, False) == False: - self.plugIn.destroyEditCommand() - return - - self.plugIn.endEditCommand() - - - def run(self, msgMapTool = False, msg = None): - if self.plugIn.canvas.mapRenderer().destinationCrs().geographicFlag(): - self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) - return True # fine comando - - currLayer = None - if self.virtualCmd == False: # se si vuole veramente salvare la polylinea in un layer - # il layer corrente deve essere editabile e di tipo linea o poligono - currLayer, errMsg = qad_layer.getCurrLayerEditable(self.plugIn.canvas, [QGis.Line, QGis.Polygon]) - if currLayer is None: - self.showErr(errMsg) - return True # fine comando - - # il layer corrente non deve appartenere a quotature - dimStyleList = QadDimStyles.getDimListByLayer(currLayer) - if len(dimStyleList) > 0: - dimStyleNames = "" - for i in xrange(0, len(dimStyleList), 1): - if i > 0: - dimStyleNames += ", " - dimStyleNames += dimStyleList[i].name - errMsg = QadMsg.translate("QAD", "\nCurrent layer is a layer referenced to {0} dimension style and it is not valid.\n") - self.showErr(errMsg.format(dimStyleNames)) - return True # fine comando - - #========================================================================= - # RICHIESTA SELEZIONE OGGETTI - if self.step == 0: # inizio del comando - if self.SSGetClass.run(msgMapTool, msg) == True: - # selezione terminata - self.step = 1 - self.getPointMapTool().refreshSnapType() # aggiorno lo snapType che può essere variato dal maptool di selezione entità - return self.run(msgMapTool, msg) - - #========================================================================= - # BUFFER OGGETTI - elif self.step == 1: - self.entitySet.set(self.SSGetClass.entitySet) - - if self.entitySet.count() == 0: - return True # fine comando - - # imposto il map tool - self.getPointMapTool().setMode(Qad_mbuffer_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_FIRST_PT) - if currLayer is not None: - self.getPointMapTool().geomType = QGis.Line if currLayer.geometryType() == QGis.Line else QGis.Polygon - - # si appresta ad attendere un punto o un numero reale - # msg, inputType, default, keyWords, valori positivi - msg = QadMsg.translate("Command_MBUFFER", "Specify the buffer length <{0}>: ") - self.waitFor(msg.format(str(self.plugIn.lastRadius)), \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT, \ - self.plugIn.lastRadius, "", \ - QadInputModeEnum.NOT_NULL | QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) - - self.step = 2 - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA LARGHEZZA (da step = 1) - elif self.step == 2: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == QgsPoint: - self.startPtForBufferWidth = value - - # imposto il map tool - self.getPointMapTool().startPtForBufferWidth = self.startPtForBufferWidth - self.getPointMapTool().entitySet.set(self.entitySet) - self.getPointMapTool().segments = self.segments - self.getPointMapTool().setMode(Qad_mbuffer_maptool_ModeEnum.FIRST_PT_ASK_FOR_BUFFER_WIDTH) - - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_MBUFFER", "Specify second point: ")) - self.step = 3 - return False - else: - self.width = value - self.plugIn.setLastRadius(self.width) - - if self.virtualCmd == False: # se si vuole veramente salvare i buffer in un layer - self.AddGeoms(currLayer) - - return True # fine comando - - #========================================================================= - # RISPOSTA ALLA RICHIESTA SECONDO PUNTO DELLA LARGHEZZA BUFFER (da step = 2) - elif self.step == 3: # dopo aver atteso un punto si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - self.width = qad_utils.getDistance(self.startPtForBufferWidth, value) - self.plugIn.setLastRadius(self.width) - - if self.virtualCmd == False: # se si vuole veramente salvare i buffer in un layer - self.AddGeoms(currLayer) - +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin ok + + comando MBUFFER per creare oggetti originati da buffer su altri oggetti + + ------------------- + begin : 2013-09-19 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.PyQt.QtCore import * +from qgis.PyQt.QtGui import * +from qgis.core import * + + +from ..qad_getpoint import QadGetPointDrawModeEnum +from .qad_mbuffer_maptool import Qad_mbuffer_maptool, Qad_mbuffer_maptool_ModeEnum +from .qad_generic_cmd import QadCommandClass +from ..qad_msg import QadMsg +from ..qad_textwindow import * +from .qad_ssget_cmd import QadSSGetClass +from ..qad_entity import * +from .. import qad_utils +from .. import qad_layer +from ..qad_dim import QadDimStyles +from ..qad_mbuffer_fun import buffer +from ..qad_multi_geom import fromQadGeomToQgsGeom + + +# Classe che gestisce il comando MBUFFER +class QadMBUFFERCommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadMBUFFERCommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "MBUFFER") + + def getEnglishName(self): + return "MBUFFER" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runMBUFFERCommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/mbuffer.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_MBUFFER", "Creates polygons by buffering selected objects.") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + # se questo flag = True il comando serve all'interno di un altro comando per disegnare un buffer + # che non verrà salvato su un layer + self.virtualCmd = False + self.rubberBandBorderColor = None + self.rubberBandFillColor = None + self.SSGetClass = QadSSGetClass(plugIn) + self.entitySet = QadEntitySet() + self.width = 0 + self.segments = self.plugIn.segments # il numero di segmenti per l'approssimazione delle curve + + def __del__(self): + QadCommandClass.__del__(self) + del self.SSGetClass + + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if self.step == 0: # quando si é in fase di selezione entità + return self.SSGetClass.getPointMapTool() + else: + if (self.plugIn is not None): + if self.PointMapTool is None: + self.PointMapTool = Qad_mbuffer_maptool(self.plugIn) + self.PointMapTool.setRubberBandColor(self.rubberBandBorderColor, self.rubberBandFillColor) + return self.PointMapTool + else: + return None + + + def getCurrentContextualMenu(self): + if self.step == 0: # quando si é in fase di selezione entità + return None # return self.SSGetClass.getCurrentContextualMenu() + else: + return self.contextualMenu + + + def setRubberBandColor(self, rubberBandBorderColor, rubberBandFillColor): + self.rubberBandBorderColor = rubberBandBorderColor + self.rubberBandFillColor = rubberBandFillColor + if self.PointMapTool is not None: + self.PointMapTool.setRubberBandColor(self.rubberBandBorderColor, self.rubberBandFillColor) + + + def AddGeoms(self, currLayer): + bufferGeoms = [] + + for layerEntitySet in self.entitySet.layerEntitySetList: + entityIterator = QadLayerEntitySetIterator(layerEntitySet) + for entity in entityIterator: + bufferedQadGeom = buffer(entity.getQadGeom(), self.width) + if bufferedQadGeom is not None: + # trasformo la geometria nel crs del layer + bufferGeoms.append(fromQadGeomToQgsGeom(bufferedQadGeom, currLayer)) + + self.plugIn.beginEditCommand("Feature buffered", currLayer) + + # filtro le features per tipo + pointGeoms, lineGeoms, polygonGeoms = qad_utils.filterGeomsByType(bufferGeoms, \ + currLayer.geometryType()) + # aggiungo le geometrie del tipo corretto + if currLayer.geometryType() == QgsWkbTypes.LineGeometry: + polygonToLines = [] + # Riduco le geometrie in linee + for g in polygonGeoms: + lines = qad_utils.asPointOrPolyline(g) + for l in lines: + if l.type() == QgsWkbTypes.LineGeometry: + polygonToLines.append(l) + # plugIn, layer, geoms, coordTransform , refresh, check_validity + if qad_layer.addGeomsToLayer(self.plugIn, currLayer, polygonToLines, None, False, False) == False: + self.plugIn.destroyEditCommand() + return + + del polygonGeoms[:] # svuoto la lista + + # plugIn, layer, geoms, coordTransform , refresh, check_validity + if qad_layer.addGeomsToLayer(self.plugIn, currLayer, bufferGeoms, None, False, False) == False: + self.plugIn.destroyEditCommand() + return + + if pointGeoms is not None and len(pointGeoms) > 0: + PointTempLayer = qad_layer.createQADTempLayer(self.plugIn, QgsWkbTypes.PointGeometry) + self.plugIn.addLayerToLastEditCommand("Feature buffered", PointTempLayer) + + if lineGeoms is not None and len(lineGeoms) > 0: + LineTempLayer = qad_layer.createQADTempLayer(self.plugIn, QgsWkbTypes.LineGeometry) + self.plugIn.addLayerToLastEditCommand("Feature buffered", LineTempLayer) + + if polygonGeoms is not None and len(polygonGeoms) > 0: + PolygonTempLayer = qad_layer.createQADTempLayer(self.plugIn, QgsWkbTypes.PolygonGeometry) + self.plugIn.addLayerToLastEditCommand("Feature buffered", PolygonTempLayer) + + # aggiungo gli scarti nei layer temporanei di QAD + # trasformo la geometria in quella dei layer temporanei + # plugIn, pointGeoms, lineGeoms, polygonGeoms, coord, refresh + if qad_layer.addGeometriesToQADTempLayers(self.plugIn, pointGeoms, lineGeoms, polygonGeoms, \ + currLayer.crs(), False) == False: + self.plugIn.destroyEditCommand() + return + + self.plugIn.endEditCommand() + + + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + currLayer = None + if self.virtualCmd == False: # se si vuole veramente salvare la polylinea in un layer + # il layer corrente deve essere editabile e di tipo linea o poligono + currLayer, errMsg = qad_layer.getCurrLayerEditable(self.plugIn.canvas, [QgsWkbTypes.LineGeometry, QgsWkbTypes.PolygonGeometry]) + if currLayer is None: + self.showErr(errMsg) + return True # fine comando + + # il layer corrente non deve appartenere a quotature + dimStyleList = QadDimStyles.getDimListByLayer(currLayer) + if len(dimStyleList) > 0: + dimStyleNames = "" + for i in range(0, len(dimStyleList), 1): + if i > 0: + dimStyleNames += ", " + dimStyleNames += dimStyleList[i].name + errMsg = QadMsg.translate("QAD", "\nCurrent layer is a layer referenced to {0} dimension style and it is not valid.\n") + self.showErr(errMsg.format(dimStyleNames)) + return True # fine comando + + # ========================================================================= + # RICHIESTA SELEZIONE OGGETTI + if self.step == 0: # inizio del comando + if self.SSGetClass.run(msgMapTool, msg) == True: + # selezione terminata + self.step = 1 + self.getPointMapTool().refreshSnapType() # aggiorno lo snapType che può essere variato dal maptool di selezione entità + return self.run(msgMapTool, msg) + + # ========================================================================= + # BUFFER OGGETTI + elif self.step == 1: + self.entitySet.set(self.SSGetClass.entitySet) + + if self.entitySet.count() == 0: + return True # fine comando + + # imposto il map tool + self.getPointMapTool().setMode(Qad_mbuffer_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_FIRST_PT) + if currLayer is not None: + self.getPointMapTool().geomType = QgsWkbTypes.LineGeometry if currLayer.geometryType() == QgsWkbTypes.LineGeometry else QgsWkbTypes.PolygonGeometry + + # si appresta ad attendere un punto o un numero reale + # msg, inputType, default, keyWords, valori positivi + msg = QadMsg.translate("Command_MBUFFER", "Specify the buffer length <{0}>: ") + self.waitFor(msg.format(str(self.plugIn.lastRadius)), \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT, \ + self.plugIn.lastRadius, "", \ + QadInputModeEnum.NOT_NULL | QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) + + self.step = 2 + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA LARGHEZZA (da step = 1) + elif self.step == 2: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == QgsPointXY: + self.startPtForBufferWidth = value + + # imposto il map tool + self.getPointMapTool().startPtForBufferWidth = self.startPtForBufferWidth + self.getPointMapTool().entitySet.set(self.entitySet) + self.getPointMapTool().segments = self.segments + self.getPointMapTool().setMode(Qad_mbuffer_maptool_ModeEnum.FIRST_PT_ASK_FOR_BUFFER_WIDTH) + + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_MBUFFER", "Specify second point: "), None, QadInputModeEnum.NOT_NULL) + self.step = 3 + return False + else: + self.width = value + self.plugIn.setLastRadius(self.width) + + if self.virtualCmd == False: # se si vuole veramente salvare i buffer in un layer + self.AddGeoms(currLayer) + + return True # fine comando + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA SECONDO PUNTO DELLA LARGHEZZA BUFFER (da step = 2) + elif self.step == 3: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + self.width = qad_utils.getDistance(self.startPtForBufferWidth, value) + self.plugIn.setLastRadius(self.width) + + if self.virtualCmd == False: # se si vuole veramente salvare i buffer in un layer + self.AddGeoms(currLayer) + return True # fine comando \ No newline at end of file diff --git a/cmd/qad_mbuffer_maptool.py b/cmd/qad_mbuffer_maptool.py new file mode 100644 index 00000000..a28c0943 --- /dev/null +++ b/cmd/qad_mbuffer_maptool.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + classe per gestire il map tool in ambito del comando mbuffer + + ------------------- + begin : 2013-09-19 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from ..qad_msg import QadMsg +from .. import qad_utils +from ..qad_snapper import * +from ..qad_snappointsdisplaymanager import * +from ..qad_variables import QadVariables +from ..qad_getpoint import QadGetPoint, QadGetPointDrawModeEnum +from ..qad_rubberband import QadRubberBand +from ..qad_mbuffer_fun import buffer +from ..qad_multi_geom import fromQadGeomToQgsGeom + + +# =============================================================================== +# Qad_mbuffer_maptool_ModeEnum class. +# =============================================================================== +class Qad_mbuffer_maptool_ModeEnum(): + # noto niente si richiede il primo punto + NONE_KNOWN_ASK_FOR_FIRST_PT = 1 + # noto il primo punto si richiede la larghezza del buffer + FIRST_PT_ASK_FOR_BUFFER_WIDTH = 2 + +# =============================================================================== +# Qad_mbuffer_maptool class +# =============================================================================== +class Qad_mbuffer_maptool(QadGetPoint): + + def __init__(self, plugIn): + QadGetPoint.__init__(self, plugIn) + + self.startPtForBufferWidth = None + # vedi il numero minimo di punti affinché venga riconosciuto un arco o un cerchio + # nei files qad_arc.py e qad_circle.py e qad_ellipse.py + self.segments = 12 + self.entitySet = QadEntitySet() + self.geomType = QgsWkbTypes.PolygonGeometry + self.__rubberBand = QadRubberBand(self.canvas, True) + + def setRubberBandColor(self, rubberBandBorderColor, rubberBandFillColor): + if rubberBandBorderColor is not None: + self.__rubberBand.setBorderColor(rubberBandBorderColor) + if rubberBandFillColor is not None: + self.__rubberBand.setFillColor(rubberBandFillColor) + + def hidePointMapToolMarkers(self): + QadGetPoint.hidePointMapToolMarkers(self) + self.__rubberBand.hide() + + def showPointMapToolMarkers(self): + QadGetPoint.showPointMapToolMarkers(self) + self.__rubberBand.show() + + def clear(self): + QadGetPoint.clear(self) + self.__rubberBand.reset() + self.mode = None + + + def canvasMoveEvent(self, event): + QadGetPoint.canvasMoveEvent(self, event) + + self.__rubberBand.reset() + + # noto il primo punto si richiede la larghezza del buffer + if self.mode == Qad_mbuffer_maptool_ModeEnum.FIRST_PT_ASK_FOR_BUFFER_WIDTH: + width = qad_utils.getDistance(self.startPtForBufferWidth, self.tmpPoint) + tolerance = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) + + for layerEntitySet in self.entitySet.layerEntitySetList: + entityIterator = QadLayerEntitySetIterator(layerEntitySet) + for entity in entityIterator: + bufferedQadGeom = buffer(entity.getQadGeom(), width) + if bufferedQadGeom is not None: + # trasformo la geometria nel crs del layer + self.__rubberBand.addGeometry(fromQadGeomToQgsGeom(bufferedQadGeom, entity.layer), entity.layer) + + + def activate(self): + QadGetPoint.activate(self) + self.__rubberBand.show() + + def deactivate(self): + try: # necessario perché se si chiude QGIS parte questo evento nonostante non ci sia più l'oggetto maptool ! + QadGetPoint.deactivate(self) + self.__rubberBand.hide() + except: + pass + + def setMode(self, mode): + self.mode = mode + # noto niente si richiede il primo punto + if self.mode == Qad_mbuffer_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_FIRST_PT: + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + # noto il primo punto si richiede la larghezza del buffer + elif self.mode == Qad_mbuffer_maptool_ModeEnum.FIRST_PT_ASK_FOR_BUFFER_WIDTH: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.startPtForBufferWidth) diff --git a/cmd/qad_measure_cmd.py b/cmd/qad_measure_cmd.py new file mode 100644 index 00000000..77b8e358 --- /dev/null +++ b/cmd/qad_measure_cmd.py @@ -0,0 +1,298 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + comando MEASURE per creare oggetti puntuali ad intervalli definiti lungo il perimetro o la lunghezza di un oggetto ok + + ------------------- + begin : 2016-09-12 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.core import QgsGeometry, QgsFeature, QgsWkbTypes, QgsVectorLayerUtils +from qgis.PyQt.QtGui import QIcon + + +from .qad_generic_cmd import QadCommandClass +from ..qad_msg import QadMsg +from ..qad_getpoint import QadGetPointDrawModeEnum +from .qad_entsel_cmd import QadEntSelClass +from .qad_getdist_cmd import QadGetDistClass +from ..qad_textwindow import QadInputTypeEnum, QadInputModeEnum +from .. import qad_utils +from .. import qad_layer +from ..qad_dim import QadDimStyles +from ..qad_multi_geom import getQadGeomAt +from ..qad_geom_relations import getQadGeomClosestPart + + +# =============================================================================== +# QadMEASURECommandClassStepEnum class. +# =============================================================================== +class QadMEASURECommandClassStepEnum(): + ASK_FOR_ENT = 1 # richiede la selezione di un oggetto (0 è l'inizio del comando) + ASK_FOR_ALIGNMENT = 2 # richiede l'allineamento + ASK_SEGMENT_LENGTH = 3 # richiede la lunghezza dei segmenti + + + +# Classe che gestisce il comando MEASURE +class QadMEASURECommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadMEASURECommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "MEASURE") + + def getEnglishName(self): + return "MEASURE" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runMEASURECommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/measure.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_MEASURE", "Creates punctual objects at measured intervals along the length or perimeter of an object.") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + self.entSelClass = None + self.GetDistClass = None + self.objectAlignment = True + self.segmentLength = 1 + + def __del__(self): + QadCommandClass.__del__(self) + if self.entSelClass is not None: + self.entSelClass.entity.deselectOnLayer() + del self.entSelClass + + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if self.step == QadMEASURECommandClassStepEnum.ASK_SEGMENT_LENGTH: # quando si é in fase di richiesta distanza + return self.GetDistClass.getPointMapTool() + else: + return QadCommandClass.getPointMapTool(self, drawMode) + + + def getCurrentContextualMenu(self): + if self.step == QadMEASURECommandClassStepEnum.ASK_SEGMENT_LENGTH: # quando si é in fase di richiesta distanza + return self.GetDistClass.getCurrentContextualMenu() + else: + return self.contextualMenu + + + # ============================================================================ + # waitForEntsel + # ============================================================================ + def waitForEntsel(self, msgMapTool, msg): + if self.entSelClass is not None: + del self.entSelClass + self.step = QadMEASURECommandClassStepEnum.ASK_FOR_ENT + self.entSelClass = QadEntSelClass(self.plugIn) + self.entSelClass.msg = QadMsg.translate("Command_MEASURE", "Select object to measure: ") + # scarto la selezione di punti + self.entSelClass.checkPointLayer = False + self.entSelClass.checkLineLayer = True + self.entSelClass.checkPolygonLayer = True + self.entSelClass.checkDimLayers = False + self.entSelClass.onlyEditableLayers = False + + self.entSelClass.run(msgMapTool, msg) + + + # ============================================================================ + # waitForAlignmentObjs + # ============================================================================ + def waitForAlignmentObjs(self): + self.step = QadMEASURECommandClassStepEnum.ASK_FOR_ALIGNMENT + + keyWords = QadMsg.translate("QAD", "Yes") + "/" + QadMsg.translate("QAD", "No") + self.defaultValue = QadMsg.translate("QAD", "Yes") + prompt = QadMsg.translate("Command_MEASURE", "Align with object ? [{0}] <{1}>: ").format(keyWords, self.defaultValue) + + englishKeyWords = "Yes" + "/" + "No" + keyWords += "_" + englishKeyWords + + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.KEYWORDS, \ + self.defaultValue, \ + keyWords, QadInputModeEnum.NONE) + + + # ============================================================================ + # waitForSegmentLength + # ============================================================================ + def waitForSegmentLength(self): + self.step = QadMEASURECommandClassStepEnum.ASK_SEGMENT_LENGTH + + if self.GetDistClass is not None: + del self.GetDistClass + self.GetDistClass = QadGetDistClass(self.plugIn) + + self.GetDistClass.msg = QadMsg.translate("Command_MEASURE", "Enter the length of segment: ") + self.GetDistClass.run() + + + # ============================================================================ + # addFeature + # ============================================================================ + def addFeature(self, layer, insPt, rot, openForm = True): + transformedPoint = self.mapToLayerCoordinates(layer, insPt) + g = QgsGeometry.fromPointXY(transformedPoint) + f = QgsVectorLayerUtils.createFeature(layer, g, {}, layer.createExpressionContext()) + + # se la scala dipende da un campo + scaleFldName = qad_layer.get_symbolScaleFieldName(layer) + if len(scaleFldName) > 0: + f.setAttribute(scaleFldName, 1.0) + + # se la rotazione dipende da un campo + rotFldName = qad_layer.get_symbolRotationFieldName(layer) + if len(rotFldName) > 0: + f.setAttribute(rotFldName, qad_utils.toDegrees(rot)) + + return qad_layer.addFeatureToLayer(self.plugIn, layer, f, None, True, False, openForm) + + + # ============================================================================ + # doMeasure + # ============================================================================ + def doMeasure(self, dstLayer): + qadGeom = self.entSelClass.entity.getQadGeom() + # la funzione ritorna una lista con + # ( + # + # + # + # se geometria chiusa è tipo polyline la lista contiene anche + # + # <"a sinistra di" se il punto é alla sinista della parte (< 0 -> sinistra, > 0 -> destra) + dummy = getQadGeomClosestPart(qadGeom, self.entSelClass.point) + # ritorna la sotto-geometria + pathPolyline = getQadGeomAt(qadGeom, dummy[2], dummy[3]) + + self.plugIn.beginEditCommand("Feature measured", dstLayer) + + i = 1 + distanceFromStart = self.segmentLength + length = pathPolyline.length() + openForm = True if length / distanceFromStart < 2 else False + + while distanceFromStart <= length: + pt, rot = pathPolyline.getPointFromStart(distanceFromStart) + if self.addFeature(dstLayer, pt, rot if self.objectAlignment else 0, openForm) == False: + self.plugIn.destroyEditCommand() + return False + i = i + 1 + distanceFromStart = distanceFromStart + self.segmentLength + + self.plugIn.endEditCommand() + return True + + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + currLayer, errMsg = qad_layer.getCurrLayerEditable(self.plugIn.canvas, QgsWkbTypes.PointGeometry) + if currLayer is None: + self.showErr(errMsg) + return True # fine comando + + if qad_layer.isSymbolLayer(currLayer) == False : + errMsg = QadMsg.translate("QAD", "\nCurrent layer is not a symbol layer.") + errMsg = errMsg + QadMsg.translate("QAD", "\nA symbol layer is a vector punctual layer without label.\n") + self.showErr(errMsg) + return True # fine comando + + if len(QadDimStyles.getDimListByLayer(currLayer)) > 0: + errMsg = QadMsg.translate("QAD", "\nThe current layer belongs to a dimension style.\n") + self.showErr(errMsg) + return True # fine comando + + if self.step == 0: + self.waitForEntsel(msgMapTool, msg) + return False # continua + + + # ========================================================================= + # RISPOSTA ALLA SELEZIONE DI UN'ENTITA' (da step = 0) + elif self.step == QadMEASURECommandClassStepEnum.ASK_FOR_ENT: + if self.entSelClass.run(msgMapTool, msg) == True: + if self.entSelClass.entity.isInitialized(): + # se il layer di destinazione è di tipo simbolo + if qad_layer.isSymbolLayer(currLayer) == True: + # se il simbolo può essere ruotato + if len(qad_layer.get_symbolRotationFieldName(currLayer)) >0: + self.waitForAlignmentObjs() + else: + self.waitForSegmentLength() + return False + else: + if self.entSelClass.canceledByUsr == True: # fine comando + return True + self.showMsg(QadMsg.translate("QAD", "No geometries in this position.")) + self.waitForEntsel(msgMapTool, msg) + return False # continua + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DI ALLINEARE GLI OGGETTI (da step = ASK_FOR_ENT) + elif self.step == QadMEASURECommandClassStepEnum.ASK_FOR_ALIGNMENT: # dopo aver atteso una parola chiave si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + value = self.defaultValue + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: + # la parola chiave arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("QAD", "Yes") or value == "Yes": + self.objectAlignment = True + else: + self.objectAlignment = False + + self.waitForSegmentLength() + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DELLA LUNGHEZZA DEL SEGMENTO (da step = ASK_FOR_ALIGNMENT) + # ========================================================================= + elif self.step == QadMEASURECommandClassStepEnum.ASK_SEGMENT_LENGTH: # dopo aver atteso un numero reale si riavvia il comando + if self.GetDistClass.run(msgMapTool, msg) == True: + self.getPointMapTool().refreshSnapType() # aggiorno lo snapType che può essere variato da altri maptool + if self.GetDistClass.dist is not None: + self.segmentLength = self.GetDistClass.dist + self.doMeasure(currLayer) + + del self.GetDistClass + return True # fine comando + else: + return False \ No newline at end of file diff --git a/cmd/qad_mirror_cmd.py b/cmd/qad_mirror_cmd.py new file mode 100644 index 00000000..14d17d65 --- /dev/null +++ b/cmd/qad_mirror_cmd.py @@ -0,0 +1,572 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin ok + + comando MIRROR per spostare oggetti + + ------------------- + begin : 2013-12-11 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.PyQt.QtGui import QIcon +from qgis.core import QgsPointXY, NULL + + +from .. import qad_label +from .. import qad_layer +from .. import qad_utils +from .qad_mirror_maptool import Qad_mirror_maptool, Qad_mirror_maptool_ModeEnum +from .qad_generic_cmd import QadCommandClass +from ..qad_msg import QadMsg +from ..qad_getpoint import QadGetPointDrawModeEnum +from ..qad_textwindow import QadInputTypeEnum, QadInputModeEnum +from .qad_ssget_cmd import QadSSGetClass +from ..qad_entity import QadEntityTypeEnum, QadCacheEntitySet, QadCacheEntitySetIterator +from ..qad_multi_geom import fromQadGeomToQgsGeom +from ..qad_dim import QadDimStyles, appendDimEntityIfNotExisting, QadDimEntity + +# Classe che gestisce il comando MIRROR +class QadMIRRORCommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadMIRRORCommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "MIRROR") + + def getEnglishName(self): + return "MIRROR" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runMIRRORCommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/mirror.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_MIRROR", "Creates a mirrored copy of selected objects.") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + self.SSGetClass = QadSSGetClass(plugIn) + self.SSGetClass.onlyEditableLayers = True + self.cacheEntitySet = QadCacheEntitySet() + self.firstMirrorPt = QgsPointXY() + self.secondMirrorPt = QgsPointXY() + self.copyFeatures = True + + def __del__(self): + QadCommandClass.__del__(self) + del self.SSGetClass + + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if self.step == 0: # quando si é in fase di selezione entità + return self.SSGetClass.getPointMapTool() + else: + if (self.plugIn is not None): + if self.PointMapTool is None: + self.PointMapTool = Qad_mirror_maptool(self.plugIn) + return self.PointMapTool + else: + return None + + + def getCurrentContextualMenu(self): + if self.step == 0: # quando si é in fase di selezione entità + return None # return self.SSGetClass.getCurrentContextualMenu() + else: + return self.contextualMenu + + + # ============================================================================ + # scale + # ============================================================================ + def mirror(self, entity, mirrorPt, angle, openForm): + + # verifico se l'entità appartiene ad uno stile di quotatura + if entity.whatIs() == "ENTITY": + # specchio la geometria dell'entità + qadGeom = entity.getQadGeom().copy() # la copio + qadGeom.mirror(mirrorPt, angle) + f = entity.getFeature() + f.setGeometry(fromQadGeomToQgsGeom(qadGeom, entity.layer)) + + if len(entity.rotFldName) > 0: + rotValue = f.attribute(entity.rotFldName) + rotValue = 0 if rotValue is None or rotValue == NULL else qad_utils.toRadians(rotValue) # la rotazione é in gradi nel campo della feature + ptDummy = qad_utils.getPolarPointByPtAngle(mirrorPt, rotValue, 1) + ptDummy = qad_utils.mirrorPoint(ptDummy, mirrorPt, angle) + rotValue = qad_utils.getAngleBy2Pts(mirrorPt, ptDummy) + f.setAttribute(entity.rotFldName, qad_utils.toDegrees(qad_utils.normalizeAngle(rotValue))) + + if self.copyFeatures == False: + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, entity.layer, f, False, False) == False: + return False + else: + # plugIn, layer, features, coordTransform, refresh, check_validity + if qad_layer.addFeatureToLayer(self.plugIn, entity.layer, f, None, False, False, openForm) == False: + return False + else: + # specchio la quota + if self.copyFeatures == False: + if entity.deleteToLayers(self.plugIn) == False: + return False + newDimEntity = QadDimEntity(entity) # la copio + newDimEntity.mirror(mirrorPt, angle) + if newDimEntity.addToLayers(self.plugIn) == False: + return False + + return True + + + # ============================================================================ + # mirrorGeoms + # ============================================================================ + def mirrorGeoms(self): + self.plugIn.beginEditCommand("Feature mirrored", self.cacheEntitySet.getLayerList()) + + angle = qad_utils.getAngleBy2Pts(self.firstMirrorPt, self.secondMirrorPt) + + dimElaboratedList = [] # lista delle quotature già elaborate + openForm = True if self.cacheEntitySet.count() == 1 else False + entityIterator = QadCacheEntitySetIterator(self.cacheEntitySet) + for entity in entityIterator: + qadGeom = entity.getQadGeom() # così inizializzo le info qad + # verifico se l'entità appartiene ad uno stile di quotatura + dimEntity = QadDimStyles.getDimEntity(entity) + if dimEntity is not None: + if appendDimEntityIfNotExisting(dimElaboratedList, dimEntity) == False: # quota già elaborata + continue + entity = dimEntity + + if self.mirror(entity, self.firstMirrorPt, angle, openForm) == False: + self.plugIn.destroyEditCommand() + return + + self.plugIn.endEditCommand() + + + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + # ========================================================================= + # RICHIESTA SELEZIONE OGGETTI + if self.step == 0: # inizio del comando + if self.SSGetClass.run(msgMapTool, msg) == True: + # selezione terminata + self.step = 1 + self.getPointMapTool().refreshSnapType() # aggiorno lo snapType che può essere variato dal maptool di selezione entità + return self.run(msgMapTool, msg) + + # ========================================================================= + # SPECCHIA OGGETTI + elif self.step == 1: + if self.SSGetClass.entitySet.count() == 0: + return True # fine comando + self.cacheEntitySet.appendEntitySet(self.SSGetClass.entitySet) + + # imposto il map tool + self.getPointMapTool().cacheEntitySet = self.cacheEntitySet + self.getPointMapTool().setMode(Qad_mirror_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_FIRST_PT) + + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_MIRROR", "Specify first point of mirror line: "), None, QadInputModeEnum.NOT_NULL) + self.step = 2 + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA PUNTO BASE (da step = 1) + elif self.step == 2: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_MIRROR", "Specify first point of mirror line: "), None, QadInputModeEnum.NOT_NULL) + return False + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + self.firstMirrorPt.set(value.x(), value.y()) + + # imposto il map tool + self.getPointMapTool().firstMirrorPt = self.firstMirrorPt + self.getPointMapTool().setMode(Qad_mirror_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT) + + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_MIRROR", "Specify second point of mirror line: "), None, QadInputModeEnum.NOT_NULL) + self.step = 3 + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA SECONDO PUNTO PER SPECCHIO (da step = 2) + elif self.step == 3: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_MIRROR", "Specify second point of mirror line: "), None, QadInputModeEnum.NOT_NULL) + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if qad_utils.ptNear(self.firstMirrorPt, value): + self.showMsg(QadMsg.translate("Command_MIRROR", "\nThe points must be different.")) + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_MIRROR", "Specify second point of mirror line: "), None, QadInputModeEnum.NOT_NULL) + return False + + self.secondMirrorPt.set(value.x(), value.y()) + + keyWords = QadMsg.translate("QAD", "Yes") + "/" + \ + QadMsg.translate("QAD", "No") + if self.copyFeatures == False: + default = QadMsg.translate("QAD", "Yes") + else: + default = QadMsg.translate("QAD", "No") + prompt = QadMsg.translate("Command_MIRROR", "Erase source objects ? [{0}] <{1}>: ").format(keyWords, default) + + englishKeyWords = "Yes" + "/" + "No" + keyWords += "_" + englishKeyWords + # si appresta ad attendere enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.KEYWORDS, \ + default, \ + keyWords, QadInputModeEnum.NONE) + self.getPointMapTool().setMode(Qad_mirror_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_FIRST_PT) + self.step = 4 + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DI CANCELLAZIONE OGGETTO SORGENTE (da step = 3) + elif self.step == 4: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + value = QadMsg.translate("QAD", "No") + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: # il valore arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("QAD", "Yes") or value == "Yes": + self.copyFeatures = False + elif value == QadMsg.translate("QAD", "No") or value == "No": + self.copyFeatures = True + + self.mirrorGeoms() + return True # fine comando + + return False + + + + +# Classe che gestisce il comando MIRROR per i grip +class QadGRIPMIRRORCommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadMIRRORCommandClass(self.plugIn) + + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + self.cacheEntitySet = QadCacheEntitySet() + self.basePt = QgsPointXY() + self.secondMirrorPt = QgsPointXY() + self.skipToNextGripCommand = False + self.copyEntities = False + self.nOperationsToUndo = 0 + + def __del__(self): + QadCommandClass.__del__(self) + + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if (self.plugIn is not None): + if self.PointMapTool is None: + self.PointMapTool = Qad_mirror_maptool(self.plugIn) + return self.PointMapTool + else: + return None + + + # ============================================================================ + # setSelectedEntityGripPoints + # ============================================================================ + def setSelectedEntityGripPoints(self, entitySetGripPoints): + # lista delle entityGripPoint con dei grip point selezionati + self.cacheEntitySet.clear() + + for entityGripPoints in entitySetGripPoints.entityGripPoints: + self.cacheEntitySet.appendEntity(entityGripPoints.entity) + + self.getPointMapTool().cacheEntitySet = self.cacheEntitySet + + + # ============================================================================ + # mirror + # ============================================================================ + def mirror(self, entity, mirrorPt, angle): + # entity = entità da specchiare + # pt1 e pt2 = linea di simmetria + # rotFldName = campo della tabella che memorizza la rotazione + # verifico se l'entità appartiene ad uno stile di quotatura + if entity.whatIs() == "ENTITY": + # specchio la geometria dell'entità + qadGeom = entity.getQadGeom().copy() # la copio + qadGeom.mirror(mirrorPt, angle) + f = entity.getFeature() + f.setGeometry(fromQadGeomToQgsGeom(qadGeom, entity.layer)) + + if len(entity.rotFldName) > 0 is not None: + rotValue = f.attribute(entity.rotFldName) + rotValue = 0 if rotValue is None or rotValue == NULL else qad_utils.toRadians(rotValue) # la rotazione é in gradi nel campo della feature + ptDummy = qad_utils.getPolarPointByPtAngle(mirrorPt, rotValue, 1) + ptDummy = qad_utils.mirrorPoint(ptDummy, mirrorPt, angle) + rotValue = qad_utils.getAngleBy2Pts(mirrorPt, ptDummy) + f.setAttribute(entity.rotFldName, qad_utils.toDegrees(qad_utils.normalizeAngle(rotValue))) + + if self.copyEntities == False: + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, entity.layer, f, False, False) == False: + return False + else: + # plugIn, layer, features, coordTransform, refresh, check_validity + if qad_layer.addFeatureToLayer(self.plugIn, entity.layer, f, None, False, False) == False: + return False + elif entity.whatIs() == "DIMENTITY": + # specchio la quota + if self.copyEntities == False: + if entity.deleteToLayers(self.plugIn) == False: + return False + newDimEntity = QadDimEntity(entity) # la copio + newDimEntity.mirror(mirrorPt, angle) + if newDimEntity.addToLayers(self.plugIn) == False: + return False + + return True + + + # ============================================================================ + # mirrorGeoms + # ============================================================================ + def mirrorGeoms(self): + self.plugIn.beginEditCommand("Feature mirrored", self.cacheEntitySet.getLayerList()) + + angle = qad_utils.getAngleBy2Pts(self.firstMirrorPt, self.secondMirrorPt) + + dimElaboratedList = [] # lista delle quotature già elaborate + entityIterator = QadCacheEntitySetIterator(self.cacheEntitySet) + openForm = True if self.cacheEntitySet.count() == 1 else False + for entity in entityIterator: + qadGeom = entity.getQadGeom() # così inizializzo le info qad + # verifico se l'entità appartiene ad uno stile di quotatura + dimEntity = QadDimStyles.getDimEntity(entity) + if dimEntity is not None: + if appendDimEntityIfNotExisting(dimElaboratedList, dimEntity) == False: # quota già elaborata + continue + entity = dimEntity + + if self.mirror(entity, self.firstMirrorPt, angle, openForm) == False: + self.plugIn.destroyEditCommand() + return + + + self.plugIn.endEditCommand() + self.nOperationsToUndo = self.nOperationsToUndo + 1 + + + # ============================================================================ + # waitForMirrorPoint + # ============================================================================ + def waitForMirrorPoint(self): + self.step = 1 + self.plugIn.setLastPoint(self.basePt) + # imposto il map tool + self.getPointMapTool().firstMirrorPt = self.basePt + self.getPointMapTool().setMode(Qad_mirror_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT) + + keyWords = QadMsg.translate("Command_GRIPMIRROR", "Base point") + "/" + \ + QadMsg.translate("Command_GRIPMIRROR", "Copy") + "/" + \ + QadMsg.translate("Command_GRIPMIRROR", "Undo") + "/" + \ + QadMsg.translate("Command_GRIPMIRROR", "eXit") + + prompt = QadMsg.translate("Command_GRIPMIRROR", "Specify second point or [{0}]: ").format(keyWords) + + englishKeyWords = "Base point" + "/" + "Copy" + "/" + "Undo" + "/" + "eXit" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto, un numero reale o enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, QadInputModeEnum.NONE) + + + # ============================================================================ + # waitForBasePt + # ============================================================================ + def waitForBasePt(self): + self.step = 2 + # imposto il map tool + self.getPointMapTool().setMode(Qad_mirror_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_FIRST_PT) + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_GRIPROTATE", "Specify base point: ")) + + + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + # ========================================================================= + # RICHIESTA SELEZIONE OGGETTI + if self.step == 0: # inizio del comando + if self.cacheEntitySet.isEmpty(): # non ci sono oggetti da ruotare + return True + self.showMsg(QadMsg.translate("Command_GRIPMIRROR", "\n** MIRROR **\n")) + # si appresta ad attendere il secondo punto di specchio + self.waitForMirrorPoint() + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA SECONDO PUNTO PER SPECCHIO + elif self.step == 1: # dopo aver atteso un punto si riavvia il comando + ctrlKey = False + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + value = None + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + ctrlKey = self.getPointMapTool().ctrlKey + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("Command_GRIPMIRROR", "Base point") or value == "Base point": + # si appresta ad attendere il punto base + self.waitForBasePt() + elif value == QadMsg.translate("Command_GRIPMIRROR", "Copy") or value == "Copy": + # Copia entità lasciando inalterate le originali + self.copyEntities = True + # si appresta ad attendere il secondo punto di specchio + self.waitForMirrorPoint() + elif value == QadMsg.translate("Command_GRIPMIRROR", "Undo") or value == "Undo": + if self.nOperationsToUndo > 0: + self.nOperationsToUndo = self.nOperationsToUndo - 1 + self.plugIn.undoEditCommand() + else: + self.showMsg(QadMsg.translate("QAD", "\nThe command has been canceled.")) + # si appresta ad attendere il secondo punto di specchio + self.waitForMirrorPoint() + elif value == QadMsg.translate("Command_GRIPMIRROR", "eXit") or value == "eXit": + return True # fine comando + elif type(value) == QgsPointXY: # se é stato inserito il secondo punto + if qad_utils.ptNear(self.basePt, value): + self.showMsg(QadMsg.translate("Command_GRIPMIRROR", "\nThe points must be different.")) + # si appresta ad attendere il secondo punto di specchio + self.waitForMirrorPoint() + return False + + self.secondMirrorPt.set(value.x(), value.y()) + + if ctrlKey: + self.copyEntities = True + + self.mirrorGeoms() + + if self.copyEntities == False: + return True + + # si appresta ad attendere il secondo punto di specchio + self.waitForMirrorPoint() + + else: + if self.copyEntities == False: + self.skipToNextGripCommand = True + return True # fine comando + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA PUNTO BASE (da step = 1) + elif self.step == 2: # dopo aver atteso un punto + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + pass # opzione di default "spostamento" + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == QgsPointXY: # se é stato inserito il punto base + self.basePt.set(value.x(), value.y()) + + # si appresta ad attendere il secondo punto di specchio + self.waitForMirrorPoint() + + return False diff --git a/cmd/qad_mirror_maptool.py b/cmd/qad_mirror_maptool.py new file mode 100644 index 00000000..6a66ff20 --- /dev/null +++ b/cmd/qad_mirror_maptool.py @@ -0,0 +1,135 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin ok + + classe per gestire il map tool in ambito del comando mirror + + ------------------- + begin : 2013-12-11 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from .. import qad_utils +from ..qad_getpoint import QadGetPoint, QadGetPointDrawModeEnum +from ..qad_dim import QadDimStyles, appendDimEntityIfNotExisting, QadDimEntity +from ..qad_highlight import QadHighlight +from ..qad_entity import QadEntityTypeEnum, QadCacheEntitySetIterator +from ..qad_multi_geom import fromQadGeomToQgsGeom + + +# =============================================================================== +# Qad_copy_maptool_ModeEnum class. +# =============================================================================== +class Qad_mirror_maptool_ModeEnum(): + # noto niente si richiede il primo punto della linea speculare + NONE_KNOWN_ASK_FOR_FIRST_PT = 1 + # noto il primo punto si richiede il secondo punto della linea speculare + FIRST_PT_KNOWN_ASK_FOR_SECOND_PT = 2 + +# =============================================================================== +# Qad_mirror_maptool class +# =============================================================================== +class Qad_mirror_maptool(QadGetPoint): + + def __init__(self, plugIn): + QadGetPoint.__init__(self, plugIn) + + self.firstMirrorPt = None + self.cacheEntitySet = None + self.__highlight = QadHighlight(self.canvas) + + def hidePointMapToolMarkers(self): + QadGetPoint.hidePointMapToolMarkers(self) + self.__highlight.hide() + + def showPointMapToolMarkers(self): + QadGetPoint.showPointMapToolMarkers(self) + self.__highlight.show() + + def clear(self): + QadGetPoint.clear(self) + self.__highlight.reset() + self.mode = None + + + # ============================================================================ + # mirror + # ============================================================================ + def mirror(self, entity, mirrorPt, angle): + # verifico se l'entità appartiene ad uno stile di quotatura + if entity.whatIs() == "ENTITY": + # ruoto la geometria dell'entità + qadGeom = entity.getQadGeom().copy() # la copio + qadGeom.mirror(mirrorPt, angle) + self.__highlight.addGeometry(fromQadGeomToQgsGeom(qadGeom, entity.layer), entity.layer) + elif entity.whatIs() == "DIMENTITY": + newDimEntity = QadDimEntity(entity) # la copio + # ruoto la quota + newDimEntity.mirror(mirrorPt, angle) + self.__highlight.addGeometry(newDimEntity.textualFeature.geometry(), newDimEntity.getTextualLayer()) + self.__highlight.addGeometries(newDimEntity.getLinearGeometryCollection(), newDimEntity.getLinearLayer()) + self.__highlight.addGeometries(newDimEntity.getSymbolGeometryCollection(), newDimEntity.getSymbolLayer()) + + + def setMirroredGeometries(self, newPt): + self.__highlight.reset() + + angle = qad_utils.getAngleBy2Pts(self.firstMirrorPt, newPt) + + dimElaboratedList = [] # lista delle quotature già elaborate + entityIterator = QadCacheEntitySetIterator(self.cacheEntitySet) + for entity in entityIterator: + qadGeom = entity.getQadGeom() # così inizializzo le info qad + # verifico se l'entità appartiene ad uno stile di quotatura + dimEntity = QadDimStyles.getDimEntity(entity) + if dimEntity is not None: + if appendDimEntityIfNotExisting(dimElaboratedList, dimEntity) == False: # quota già elaborata + continue + entity = dimEntity + + self.mirror(entity, self.firstMirrorPt, angle) + + + def canvasMoveEvent(self, event): + QadGetPoint.canvasMoveEvent(self, event) + + # noto il primo punto si richiede il secondo punto della linea speculare + if self.mode == Qad_mirror_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT: + self.setMirroredGeometries(self.tmpPoint) + + + def activate(self): + QadGetPoint.activate(self) + self.__highlight.show() + + def deactivate(self): + try: # necessario perché se si chiude QGIS parte questo evento nonostante non ci sia più l'oggetto maptool ! + QadGetPoint.deactivate(self) + self.__highlight.hide() + except: + pass + + def setMode(self, mode): + self.mode = mode + # noto niente si richiede il primo punto della linea speculare + if self.mode == Qad_mirror_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_FIRST_PT: + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + self.__highlight.reset() + # noto il primo punto si richiede il secondo punto della linea speculare + elif self.mode == Qad_mirror_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.firstMirrorPt) \ No newline at end of file diff --git a/cmd/qad_move_cmd.py b/cmd/qad_move_cmd.py new file mode 100644 index 00000000..a26b066c --- /dev/null +++ b/cmd/qad_move_cmd.py @@ -0,0 +1,536 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin ok + + comando MOVE per spostare oggetti + + ------------------- + begin : 2013-09-27 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.PyQt.QtGui import QIcon +from qgis.core import QgsPointXY + + +from .qad_move_maptool import Qad_move_maptool_ModeEnum, Qad_move_maptool +from .qad_generic_cmd import QadCommandClass +from ..qad_msg import QadMsg +from ..qad_getpoint import QadGetPointDrawModeEnum +from ..qad_textwindow import QadInputTypeEnum, QadInputModeEnum +from .qad_ssget_cmd import QadSSGetClass +from ..qad_entity import QadCacheEntitySet, QadEntityTypeEnum, QadCacheEntitySetIterator + +from .. import qad_utils +from .. import qad_layer +from ..qad_dim import QadDimStyles, QadDimEntity, appendDimEntityIfNotExisting +from ..qad_multi_geom import fromQadGeomToQgsGeom + +# Classe che gestisce il comando MOVE +class QadMOVECommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadMOVECommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "MOVE") + + def getEnglishName(self): + return "MOVE" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runMOVECommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/move.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_MOVE", "Moves the selected objects.") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + self.SSGetClass = QadSSGetClass(plugIn) + self.SSGetClass.onlyEditableLayers = True + self.cacheEntitySet = QadCacheEntitySet() + self.basePt = QgsPointXY() + + def __del__(self): + QadCommandClass.__del__(self) + del self.SSGetClass + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if self.step == 0: # quando si é in fase di selezione entità + return self.SSGetClass.getPointMapTool() + else: + if (self.plugIn is not None): + if self.PointMapTool is None: + self.PointMapTool = Qad_move_maptool(self.plugIn) + return self.PointMapTool + else: + return None + + + def getCurrentContextualMenu(self): + if self.step == 0: # quando si é in fase di selezione entità + return None # return self.SSGetClass.getCurrentContextualMenu() + else: + return self.contextualMenu + + + # ============================================================================ + # move + # ============================================================================ + def move(self, entity, offsetX, offsetY): + # verifico se l'entità appartiene ad uno stile di quotatura + if entity.whatIs() == "ENTITY": + # sposto la geometria dell'entità + qadGeom = entity.getQadGeom().copy() # la copio + qadGeom.move(offsetX, offsetY) + f = entity.getFeature() + f.setGeometry(fromQadGeomToQgsGeom(qadGeom, entity.layer)) + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, entity.layer, f, False, False) == False: + return False + elif entity.whatIs() == "DIMENTITY": + if entity.deleteToLayers(self.plugIn) == False: return False # cancello vecchia quota + newDimEntity = QadDimEntity(entity) # la copio + if newDimEntity.move(offsetX, offsetY) == False: return False # sposto la quota + if newDimEntity.addToLayers(self.plugIn) == False: # ricreo nuva quota + return False + + return True + + + # ============================================================================ + # moveGeoms + # ============================================================================ + def moveGeoms(self, newPt): + offsetX = newPt.x() - self.basePt.x() + offsetY = newPt.y() - self.basePt.y() + + self.plugIn.beginEditCommand("Feature moved", self.cacheEntitySet.getLayerList()) + + dimElaboratedList = [] # lista delle quotature già elaborate + entityIterator = QadCacheEntitySetIterator(self.cacheEntitySet) + + for entity in entityIterator: + qadGeom = entity.getQadGeom() # così inizializzo le info qad + # verifico se l'entità appartiene ad uno stile di quotatura + dimEntity = QadDimStyles.getDimEntity(entity) + if dimEntity is not None: + if appendDimEntityIfNotExisting(dimElaboratedList, dimEntity) == False: # quota già elaborata + continue + entity = dimEntity + + if self.move(entity, offsetX, offsetY) == False: + self.plugIn.destroyEditCommand() + return + + self.plugIn.endEditCommand() + + + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + # ========================================================================= + # RICHIESTA SELEZIONE OGGETTI + if self.step == 0: # inizio del comando + if self.SSGetClass.run(msgMapTool, msg) == True: + # selezione terminata + self.step = 1 + self.getPointMapTool().refreshSnapType() # aggiorno lo snapType che può essere variato dal maptool di selezione entità + return self.run(msgMapTool, msg) + + # ========================================================================= + # SPOSTA OGGETTI + elif self.step == 1: + if self.SSGetClass.entitySet.count() == 0: + return True # fine comando + self.cacheEntitySet.appendEntitySet(self.SSGetClass.entitySet) + + # imposto il map tool + self.getPointMapTool().cacheEntitySet = self.cacheEntitySet + self.getPointMapTool().setMode(Qad_move_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_BASE_PT) + + keyWords = QadMsg.translate("Command_MOVE", "Displacement") + prompt = QadMsg.translate("Command_MOVE", "Specify base point or [{0}] <{0}>: ").format(keyWords) + + englishKeyWords = "Displacement" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, QadInputModeEnum.NONE) + self.step = 2 + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA PUNTO BASE (da step = 1) + elif self.step == 2: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + pass # opzione di default "spostamento" + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if value is None or type(value) == unicode: + self.basePt.set(0, 0) + self.getPointMapTool().basePt = self.basePt + self.getPointMapTool().setMode(Qad_move_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_MOVE_PT) + # si appresta ad attendere un punto + msg = QadMsg.translate("Command_MOVE", "Specify the displacement fom the origin point 0,0 <{0}, {1}>: ") + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(msg.format(str(self.plugIn.lastOffsetPt.x()), str(self.plugIn.lastOffsetPt.y())), \ + QadInputTypeEnum.POINT2D, \ + self.plugIn.lastOffsetPt, \ + "", QadInputModeEnum.NONE) + self.step = 4 + elif type(value) == QgsPointXY: # se é stato inserito il punto base + self.basePt.set(value.x(), value.y()) + + # imposto il map tool + self.getPointMapTool().basePt = self.basePt + self.getPointMapTool().setMode(Qad_move_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_MOVE_PT) + + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(QadMsg.translate("Command_MOVE", "Specify the second point or : "), \ + QadInputTypeEnum.POINT2D, \ + None, \ + "", QadInputModeEnum.NONE) + self.step = 3 + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA SECONDO PUNTO PER SPOSTAMENTO (da step = 2) + elif self.step == 3: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if value is None: + newPt = QgsPointXY(self.basePt.x() * 2, self.basePt.y() * 2) + self.moveGeoms(newPt) + elif type(value) == QgsPointXY: # se é stato inserito lo spostamento con un punto + self.moveGeoms(value) + + return True # fine comando + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL PUNTO DI SPOSTAMENTO (da step = 2) + elif self.step == 4: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + self.plugIn.setLastOffsetPt(value) + self.moveGeoms(value) + return True + + +# Classe che gestisce il comando MOVE per i grip +class QadGRIPMOVECommandClass(QadCommandClass): + + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadGRIPMOVECommandClass(self.plugIn) + + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + self.cacheEntitySet = QadCacheEntitySet() + self.basePt = QgsPointXY() + self.skipToNextGripCommand = False + self.copyEntities = False + self.nOperationsToUndo = 0 + + + def __del__(self): + QadCommandClass.__del__(self) + + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if (self.plugIn is not None): + if self.PointMapTool is None: + self.PointMapTool = Qad_move_maptool(self.plugIn) + return self.PointMapTool + else: + return None + + + # ============================================================================ + # setSelectedEntityGripPoints + # ============================================================================ + def setSelectedEntityGripPoints(self, entitySetGripPoints): + # lista delle entityGripPoint con dei grip point selezionati + self.cacheEntitySet.clear() + + for entityGripPoints in entitySetGripPoints.entityGripPoints: + self.cacheEntitySet.appendEntity(entityGripPoints.entity) + + self.getPointMapTool().cacheEntitySet = self.cacheEntitySet + + + # ============================================================================ + # move + # ============================================================================ + def move(self, entity, offsetX, offsetY, openForm = True): + # verifico se l'entità appartiene ad uno stile di quotatura + if entity.whatIs() == "ENTITY": + # sposto la geometria dell'entità + qadGeom = entity.getQadGeom().copy() # la copio + qadGeom.move(offsetX, offsetY) + f = entity.getFeature() + f.setGeometry(fromQadGeomToQgsGeom(qadGeom, entity.layer)) + if self.copyEntities == False: + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, entity.layer, f, False, False) == False: + return False + else: + # plugIn, layer, features, coordTransform, refresh, check_validity + if qad_layer.addFeatureToLayer(self.plugIn, entity.layer, f, None, False, False, openForm) == False: + return False + elif entity.whatIs() == "DIMENTITY": + # stiro la quota + if self.copyEntities == False: + if entity.deleteToLayers(self.plugIn) == False: + return False + newDimEntity = QadDimEntity(entity) # la copio + newDimEntity.move(offsetX, offsetY) + if newDimEntity.addToLayers(self.plugIn) == False: + return False + + return True + + + # ============================================================================ + # moveFeatures + # ============================================================================ + def moveFeatures(self, newPt): + offsetX = newPt.x() - self.basePt.x() + offsetY = newPt.y() - self.basePt.y() + + self.plugIn.beginEditCommand("Feature moved", self.cacheEntitySet.getLayerList()) + + dimElaboratedList = [] # lista delle quotature già elaborate + entityIterator = QadCacheEntitySetIterator(self.cacheEntitySet) + openForm = True if self.cacheEntitySet.count() == 1 else False + + for entity in entityIterator: + qadGeom = entity.getQadGeom() # così inizializzo le info qad + # verifico se l'entità appartiene ad uno stile di quotatura + dimEntity = QadDimStyles.getDimEntity(entity) + if dimEntity is not None: + if appendDimEntityIfNotExisting(dimElaboratedList, dimEntity) == False: # quota già elaborata + continue + entity = dimEntity + + if self.move(entity, offsetX, offsetY, openForm) == False: + self.plugIn.destroyEditCommand() + return + + self.plugIn.endEditCommand() + self.nOperationsToUndo = self.nOperationsToUndo + 1 + + + # ============================================================================ + # waitForMovePoint + # ============================================================================ + def waitForMovePoint(self): + self.step = 1 + self.plugIn.setLastPoint(self.basePt) + # imposto il map tool + self.getPointMapTool().basePt = self.basePt + self.getPointMapTool().setMode(Qad_move_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_MOVE_PT) + + keyWords = QadMsg.translate("Command_GRIPMOVE", "Base point") + "/" + \ + QadMsg.translate("Command_GRIPMOVE", "Copy") + "/" + \ + QadMsg.translate("Command_GRIPMOVE", "Undo") + "/" + \ + QadMsg.translate("Command_GRIPMOVE", "eXit") + + prompt = QadMsg.translate("Command_GRIPMOVE", "Specify move point or [{0}]: ").format(keyWords) + + englishKeyWords = "Base point" + "/" + "Copy" + "/" + "Undo" + "/" + "eXit" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, QadInputModeEnum.NONE) + + + # ============================================================================ + # waitForBasePt + # ============================================================================ + def waitForBasePt(self): + self.step = 2 + # imposto il map tool + self.getPointMapTool().setMode(Qad_move_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_BASE_PT) + + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_GRIPMOVE", "Specify base point: ")) + + + # ============================================================================ + # run + # ============================================================================ + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + # ========================================================================= + # RICHIESTA SELEZIONE OGGETTI + if self.step == 0: # inizio del comando + if self.cacheEntitySet.isEmpty(): # non ci sono oggetti da spostare + return True + self.showMsg(QadMsg.translate("Command_GRIPMOVE", "\n** MOVE **\n")) + # si appresta ad attendere un punto di spostamento + self.waitForMovePoint() + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DI UN PUNTO DI SPOSTAMENTO + elif self.step == 1: + ctrlKey = False + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + value = None + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: + value = self.getPointMapTool().point + + ctrlKey = self.getPointMapTool().ctrlKey + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("Command_GRIPMOVE", "Base point") or value == "Base point": + # si appresta ad attendere il punto base + self.waitForBasePt() + elif value == QadMsg.translate("Command_GRIPMOVE", "Copy") or value == "Copy": + # Copia entità lasciando inalterate le originali + self.copyEntities = True + # si appresta ad attendere un punto di spostamento + self.waitForMovePoint() + elif value == QadMsg.translate("Command_GRIPMOVE", "Undo") or value == "Undo": + if self.nOperationsToUndo > 0: + self.nOperationsToUndo = self.nOperationsToUndo - 1 + self.plugIn.undoEditCommand() + else: + self.showMsg(QadMsg.translate("QAD", "\nThe command has been canceled.")) + # si appresta ad attendere un punto di spostamento + self.waitForMovePoint() + elif value == QadMsg.translate("Command_GRIPMOVE", "eXit") or value == "eXit": + return True # fine comando + elif type(value) == QgsPointXY: # se é stato selezionato un punto + if ctrlKey: + self.copyEntities = True + + self.moveFeatures(value) + + if self.copyEntities == False: + return True + # si appresta ad attendere un punto di stiramento + self.waitForMovePoint() + + else: + if self.copyEntities == False: + self.skipToNextGripCommand = True + return True # fine comando + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA PUNTO BASE (da step = 1) + elif self.step == 2: # dopo aver atteso un punto + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + pass # opzione di default "spostamento" + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == QgsPointXY: # se é stato inserito il punto base + self.basePt.set(value.x(), value.y()) + # imposto il map tool + self.getPointMapTool().basePt = self.basePt + + # si appresta ad attendere un punto di spostamento + self.waitForMovePoint() + + return False diff --git a/cmd/qad_move_maptool.py b/cmd/qad_move_maptool.py new file mode 100644 index 00000000..b223863b --- /dev/null +++ b/cmd/qad_move_maptool.py @@ -0,0 +1,141 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin ok + + classe per gestire il map tool in ambito del comando move + + ------------------- + begin : 2013-09-27 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from .. import qad_utils +from ..qad_getpoint import QadGetPoint, QadGetPointDrawModeEnum, QadGetPointSelectionModeEnum +from ..qad_highlight import QadHighlight +from ..qad_dim import QadDimStyles, appendDimEntityIfNotExisting, QadDimEntity +from ..qad_entity import QadEntity, QadEntityTypeEnum, QadCacheEntitySetIterator +from ..qad_multi_geom import fromQadGeomToQgsGeom + + +# =============================================================================== +# Qad_move_maptool_ModeEnum class. +# =============================================================================== +class Qad_move_maptool_ModeEnum(): + # noto niente si richiede il punto base + NONE_KNOWN_ASK_FOR_BASE_PT = 1 + # noto il punto base si richiede il secondo punto per lo spostamento + BASE_PT_KNOWN_ASK_FOR_MOVE_PT = 2 + + +# =============================================================================== +# Qad_move_maptool class +# =============================================================================== +class Qad_move_maptool(QadGetPoint): + + def __init__(self, plugIn): + QadGetPoint.__init__(self, plugIn) + + self.basePt = None + self.cacheEntitySet = None + self.__highlight = QadHighlight(self.canvas) + + def hidePointMapToolMarkers(self): + QadGetPoint.hidePointMapToolMarkers(self) + self.__highlight.hide() + + def showPointMapToolMarkers(self): + QadGetPoint.showPointMapToolMarkers(self) + self.__highlight.show() + + def clear(self): + QadGetPoint.clear(self) + self.__highlight.reset() + self.mode = None + + + # ============================================================================ + # move + # ============================================================================ + def move(self, entity, offsetX, offsetY): + # verifico se l'entità appartiene ad uno stile di quotatura + if entity.whatIs() == "ENTITY": + # sposto la geometria dell'entità + qadGeom = entity.getQadGeom().copy() # la copio + qadGeom.move(offsetX, offsetY) + self.__highlight.addGeometry(fromQadGeomToQgsGeom(qadGeom, entity.layer), entity.layer) + elif entity.whatIs() == "DIMENTITY": + newDimEntity = QadDimEntity(entity) # la copio + # sposto la quota + newDimEntity.move(offsetX, offsetY) + self.__highlight.addGeometry(newDimEntity.textualFeature.geometry(), newDimEntity.getTextualLayer()) + self.__highlight.addGeometries(newDimEntity.getLinearGeometryCollection(), newDimEntity.getLinearLayer()) + self.__highlight.addGeometries(newDimEntity.getSymbolGeometryCollection(), newDimEntity.getSymbolLayer()) + + + def addMovedGeometries(self, newPt): + self.__highlight.reset() + + offsetX = newPt.x() - self.basePt.x() + offsetY = newPt.y() - self.basePt.y() + + dimElaboratedList = [] # lista delle quotature già elaborate + entity = QadEntity() + + dimElaboratedList = [] # lista delle quotature già elaborate + entityIterator = QadCacheEntitySetIterator(self.cacheEntitySet) + for entity in entityIterator: + qadGeom = entity.getQadGeom() # così inizializzo le info qad + # verifico se l'entità appartiene ad uno stile di quotatura + dimEntity = QadDimStyles.getDimEntity(entity) + if dimEntity is not None: + if appendDimEntityIfNotExisting(dimElaboratedList, dimEntity) == False: # quota già elaborata + continue + entity = dimEntity + + self.move(entity, offsetX, offsetY) + + + def canvasMoveEvent(self, event): + QadGetPoint.canvasMoveEvent(self, event) + + # noto il punto base si richiede il secondo punto + if self.mode == Qad_move_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_MOVE_PT: + self.addMovedGeometries(self.tmpPoint) + + + def activate(self): + QadGetPoint.activate(self) + self.__highlight.show() + + def deactivate(self): + try: # necessario perché se si chiude QGIS parte questo evento nonostante non ci sia più l'oggetto maptool ! + QadGetPoint.deactivate(self) + self.__highlight.hide() + except: + pass + + def setMode(self, mode): + self.mode = mode + # noto niente si richiede il punto base + if self.mode == Qad_move_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_BASE_PT: + self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + self.__highlight.reset() + # noto il punto base si richiede il secondo punto + elif self.mode == Qad_move_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_MOVE_PT: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.basePt) \ No newline at end of file diff --git a/cmd/qad_mpolygon_cmd.py b/cmd/qad_mpolygon_cmd.py new file mode 100644 index 00000000..960e091d --- /dev/null +++ b/cmd/qad_mpolygon_cmd.py @@ -0,0 +1,142 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin ok + + comando MPOLYGON per disegnare un poligono + + ------------------- + begin : 2013-09-18 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.core import QgsWkbTypes +from qgis.PyQt.QtGui import QIcon + + +from .qad_generic_cmd import QadCommandClass +from .qad_pline_cmd import QadPLINECommandClass +from ..qad_msg import QadMsg +from ..qad_getpoint import QadGetPointDrawModeEnum +from ..qad_multi_geom import * +from .. import qad_layer + + +# Classe che gestisce il comando MPOLYGON +class QadMPOLYGONCommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadMPOLYGONCommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "MPOLYGON") + + def getEnglishName(self): + return "MPOLYGON" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runMPOLYGONCommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/mpolygon.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_MPOLYGON", "Draws a polygon by many methods.\nA Polygon is a closed sequence of straight line segments,\narcs or a combination of two.") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + # se questo flag = True il comando serve all'interno di un altro comando per disegnare un poligono + # che non verrà salvato su un layer + self.virtualCmd = False + self.rubberBandBorderColor = None + self.rubberBandFillColor = None + self.PLINECommand = None + + def __del__(self): + QadCommandClass.__del__(self) + if self.PLINECommand is not None: + del self.PLINECommand + + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if self.PLINECommand is not None: + return self.PLINECommand.getPointMapTool(drawMode) + else: + return QadCommandClass.getPointMapTool(self, drawMode) + + + def getCurrentContextualMenu(self): + if self.PLINECommand is not None: + return self.PLINECommand.getCurrentContextualMenu() + else: + return self.contextualMenu + + + def setRubberBandColor(self, rubberBandBorderColor, rubberBandFillColor): + self.rubberBandBorderColor = rubberBandBorderColor + self.rubberBandFillColor = rubberBandFillColor + if self.PLINECommand is not None: + self.PLINECommand.setRubberBandColor(rubberBandBorderColor, rubberBandFillColor) + + + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + if self.virtualCmd == False: # se si vuole veramente salvare la polylinea in un layer + currLayer, errMsg = qad_layer.getCurrLayerEditable(self.plugIn.canvas, QgsWkbTypes.PolygonGeometry) + if currLayer is None: + self.showErr(errMsg) + return True # fine comando + + # ========================================================================= + # RICHIESTA PRIMO PUNTO PER SELEZIONE OGGETTI + if self.step == 0: + self.PLINECommand = QadPLINECommandClass(self.plugIn, True) + self.PLINECommand.setRubberBandColor(self.rubberBandBorderColor, self.rubberBandFillColor) + # se questo flag = True il comando serve all'interno di un altro comando per disegnare una linea + # che non verrà salvata su un layer + self.PLINECommand.virtualCmd = True + self.PLINECommand.asToolForMPolygon = True # per rubberband tipo poligono + self.PLINECommand.run(msgMapTool, msg) + self.step = 1 + return False # continua + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA PUNTO (da step = 0 o 1) + elif self.step == 1: # dopo aver atteso un punto si riavvia il comando + if self.PLINECommand.run(msgMapTool, msg) == True: + if self.PLINECommand.polyline.qty() >= 2: # se ci sono almeno 2 tratti + polyline = self.PLINECommand.polyline.copy() # copio la polylinea + # se la polilinea non è chiusa + if polyline.isClosed() == False: + polyline.append(QadLine().set(polyline.getEndPt(), polyline.getStartPt())) # la chiudo con un segmento retto + if self.virtualCmd == False: # se si vuole veramente salvare la polylinea in un layer + geom = polyline.asGeom(currLayer.wkbType()) + if geom is not None: + if qad_layer.addGeomToLayer(self.plugIn, currLayer, self.mapToLayerCoordinates(currLayer, geom), None, True, True, True) == False: + self.showMsg(QadMsg.translate("Command_MPOLYGON", "\nPolygon not valid.\n")) + del polyline + else: + self.showMsg(QadMsg.translate("Command_MPOLYGON", "\nPolygon not valid.\n")) + + return True # fine + + return False diff --git a/cmd/qad_offset_cmd.py b/cmd/qad_offset_cmd.py new file mode 100644 index 00000000..8f1a4b52 --- /dev/null +++ b/cmd/qad_offset_cmd.py @@ -0,0 +1,798 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin ok + + comando OFFSET per fare l'offset di un oggetto + + ------------------- + begin : 2013-10-04 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.core import QgsPointXY, QgsWkbTypes, QgsGeometry, QgsFeature +from qgis.PyQt.QtGui import QIcon + + +from .qad_offset_maptool import Qad_offset_maptool, Qad_offset_maptool_ModeEnum +from .qad_generic_cmd import QadCommandClass +from ..qad_msg import QadMsg +from ..qad_getpoint import QadGetPointDrawModeEnum +from ..qad_textwindow import QadInputTypeEnum, QadInputModeEnum +from ..qad_entity import QadEntity +from ..qad_variables import QadVariables +from .. import qad_utils +from .. import qad_layer +from ..qad_rubberband import createRubberBand +from ..qad_offset_fun import offsetPolyline, offsetQGSGeom +from ..qad_geom_relations import getQadGeomClosestPart +from ..qad_multi_geom import getQadGeomAt, fromQgsGeomToQadGeom + + +# Classe che gestisce il comando OFFSET +class QadOFFSETCommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadOFFSETCommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "OFFSET") + + def getEnglishName(self): + return "OFFSET" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runOFFSETCommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/offset.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_OFFSET", "Creates concentric circles, parallel lines, and parallel curves.") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + self.entity = QadEntity() + self.subGeom = None + self.partNum = None + self.offset = QadVariables.get(QadMsg.translate("Environment variables", "OFFSETDIST")) + self.lastOffSetOnLeftSide = 0 + self.lastOffSetOnRightSide = 0 + self.firstPt = QgsPointXY() + self.eraseEntity = False + self.multi = False + self.OnlySegment = False + self.gapType = QadVariables.get(QadMsg.translate("Environment variables", "OFFSETGAPTYPE")) + + self.featureCache = [] # lista di (layer, feature) + self.undoFeatureCacheIndexes = [] # posizioni in featureCache dei punti di undo + self.rubberBand = createRubberBand(self.plugIn.canvas, QgsWkbTypes.LineGeometry) + self.rubberBandPolygon = createRubberBand(self.plugIn.canvas, QgsWkbTypes.PolygonGeometry) + + def __del__(self): + QadCommandClass.__del__(self) + self.rubberBand.hide() + self.plugIn.canvas.scene().removeItem(self.rubberBand) + self.rubberBandPolygon.hide() + self.plugIn.canvas.scene().removeItem(self.rubberBandPolygon) + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if (self.plugIn is not None): + if self.PointMapTool is None: + self.PointMapTool = Qad_offset_maptool(self.plugIn) + return self.PointMapTool + else: + return None + + # ============================================================================ + # addFeatureCache + # ============================================================================ + def addFeatureCache(self, newPt): + featureCacheLen = len(self.featureCache) + layer = self.entity.layer + f = self.entity.getFeature() + +# # la funzione ritorna una lista con +# # ( +# # +# # +# # +# # +# # <"a sinistra di" se il punto é alla sinista della parte con i seguenti valori: +# # - < 0 = sinistra (per linea, arco o arco di ellisse) o interno (per cerchi, ellissi) +# # - > 0 = destra (per linea, arco o arco di ellisse) o esterno (per cerchi, ellissi) +# result = getQadGeomClosestPart(self.subGeom, newPt) +# leftOf = result[5] +# +# if self.offset < 0: +# offsetDistance = result[0] # minima distanza +# else: +# offsetDistance = self.offset +# if self.multi == True: +# if leftOf < 0: # a sinistra (per linea, arco o arco di ellisse) o interno (per cerchi, ellissi) +# offsetDistance = offsetDistance + self.lastOffSetOnLeftSide +# self.lastOffSetOnLeftSide = offsetDistance +# self.getPointMapTool().lastOffSetOnLeftSide = self.lastOffSetOnLeftSide +# else: # alla destra (per linea, arco o arco di ellisse) o esterno (per cerchi, ellissi) +# offsetDistance = offsetDistance + self.lastOffSetOnRightSide +# self.lastOffSetOnRightSide = offsetDistance +# self.getPointMapTool().lastOffSetOnRightSide = self.lastOffSetOnRightSide +# +# lines = offsetPolyline(self.subGeom, \ +# offsetDistance, \ +# "left" if leftOf < 0 else "right", \ +# self.gapType) +# added = False +# for line in lines: +# pts = line.asPolyline() +# if layer.geometryType() == QgsWkbTypes.PolygonGeometry: +# if line.isClosed(): # se é una linea chiusa +# offsetGeom = QgsGeometry.fromPolygonXY([pts]) +# else: +# offsetGeom = QgsGeometry.fromPolylineXY(pts) +# else: +# offsetGeom = QgsGeometry.fromPolylineXY(pts) +# +# if offsetGeom.type() == QgsWkbTypes.LineGeometry or offsetGeom.type() == QgsWkbTypes.PolygonGeometry: +# offsetFeature = QgsFeature(f) +# # trasformo la geometria nel crs del layer +# offsetFeature.setGeometry(self.mapToLayerCoordinates(layer, offsetGeom)) +# self.featureCache.append([layer, offsetFeature]) +# self.addFeatureToRubberBand(layer, offsetFeature) +# added = True +# +# if added: +# self.undoFeatureCacheIndexes.append(featureCacheLen) + + offsetDistance = None + if self.offset > 0: + offsetDistance = self.offset + if self.multi == True: + # la funzione ritorna una lista con + # ( + # + # + # + # + # <"a sinistra di" se il punto é alla sinista della parte con i seguenti valori: + # - < 0 = sinistra (per linea, arco o arco di ellisse) o interno (per cerchi, ellissi) + # - > 0 = destra (per linea, arco o arco di ellisse) o esterno (per cerchi, ellissi) + dummy = getQadGeomClosestPart(self.subGeom, newPt) + leftOf = dummy[5] + + if leftOf < 0: # a sinistra (per linea, arco o arco di ellisse) o interno (per cerchi, ellissi) + offsetDistance = self.offset + self.lastOffSetOnLeftSide + self.lastOffSetOnLeftSide = offsetDistance + self.getPointMapTool().lastOffSetOnLeftSide = self.lastOffSetOnLeftSide + else: # alla destra + offsetDistance = self.offset + self.lastOffSetOnRightSide + self.lastOffSetOnRightSide = offsetDistance + self.getPointMapTool().lastOffSetOnRightSide = self.lastOffSetOnRightSide + + # se self.subGeom implementa il metodo isClosed + closed = self.subGeom.isClosed() if hasattr(self.subGeom, "isClosed") and callable(getattr(self.subGeom, "isClosed")) else False + + if layer.geometryType() == QgsWkbTypes.PolygonGeometry or closed == True: + qgsGeom = QgsGeometry.fromPolygonXY([self.subGeom.asPolyline()]) + else: + qgsGeom = QgsGeometry.fromPolylineXY(self.subGeom.asPolyline()) + + offsetQGSGeomList = offsetQGSGeom(qgsGeom, \ + newPt, \ + self.gapType, \ + offsetDistance) + + added = False + for g in offsetQGSGeomList: + # converto in QAD geometry per riconoscere le curve + g = fromQgsGeomToQadGeom(g).asGeom(layer.wkbType()) + + if g.type() == QgsWkbTypes.LineGeometry or g.type() == QgsWkbTypes.PolygonGeometry: + offsetFeature = QgsFeature(f) + # trasformo la geometria nel crs del layer + offsetFeature.setGeometry(self.mapToLayerCoordinates(layer, g)) + self.featureCache.append([layer, offsetFeature]) + self.addFeatureToRubberBand(layer, offsetFeature) + added = True + + if added: + self.undoFeatureCacheIndexes.append(featureCacheLen) + + + # ============================================================================ + # undoGeomsInCache + # ============================================================================ + def undoGeomsInCache(self): + tot = len(self.featureCache) + if tot > 0: + iEnd = self.undoFeatureCacheIndexes[-1] + i = tot - 1 + + del self.undoFeatureCacheIndexes[-1] # cancello ultimo undo + while i >= iEnd: + del self.featureCache[-1] # cancello feature + i = i - 1 + self.refreshRubberBand() + + + # ============================================================================ + # addFeatureToRubberBand + # ============================================================================ + def addFeatureToRubberBand(self, layer, feature): + if layer.geometryType() == QgsWkbTypes.PolygonGeometry: + if feature.geometry().type() == QgsWkbTypes.PolygonGeometry: + self.rubberBandPolygon.addGeometry(feature.geometry(), layer) + else: + self.rubberBand.addGeometry(feature.geometry(), layer) + else: + self.rubberBand.addGeometry(feature.geometry(), layer) + + + # ============================================================================ + # refreshRubberBand + # ============================================================================ + def refreshRubberBand(self): + self.rubberBand.reset(QgsWkbTypes.LineGeometry) + self.rubberBandPolygon.reset(QgsWkbTypes.PolygonGeometry) + for f in self.featureCache: + layer = f[0] + feature = f[1] + if layer.geometryType() == QgsWkbTypes.PolygonGeometry: + if feature.geometry().type() == QgsWkbTypes.PolygonGeometry: + self.rubberBandPolygon.addGeometry(feature.geometry(), layer) + else: + self.rubberBand.addGeometry(feature.geometry(), layer) + else: + self.rubberBand.addGeometry(feature.geometry(), layer) + + + # ============================================================================ + # addToLayer + # ============================================================================ + def addToLayer(self, currLayer): + featuresLayers = [] # lista di (layer, features) + + for f in self.featureCache: + layer = f[0] + feature = f[1] + found = False + for featuresLayer in featuresLayers: + if featuresLayer[0].id() == layer.id(): + found = True + featuresLayer[1].append(feature) + break + # se non c'era ancora il layer + if not found: + featuresLayers.append([layer, [feature]]) + + layerList = [] + for featuresLayer in featuresLayers: + layerList.append(featuresLayer[0]) + + PointTempLayer = None + LineTempLayer = None + PolygonTempLayer = None + self.plugIn.beginEditCommand("Feature offseted", layerList) + + for featuresLayer in featuresLayers: + # filtro le features per tipo + pointGeoms, lineGeoms, polygonGeoms = qad_utils.filterFeaturesByType(featuresLayer[1], \ + currLayer.geometryType()) + # aggiungo le features con geometria del tipo corretto + if currLayer.geometryType() == QgsWkbTypes.LineGeometry: + polygonToLines = [] + # Riduco le geometrie in linee + for g in polygonGeoms: + lines = qad_utils.asPointOrPolyline(g) + for l in lines: + if l.type() == QgsWkbTypes.LineGeometry: + polygonToLines.append(l) + # plugIn, layer, geoms, coordTransform , refresh, check_validity + if qad_layer.addGeomsToLayer(self.plugIn, currLayer, polygonToLines, None, False, False) == False: + self.plugIn.destroyEditCommand() + return + + del polygonGeoms[:] # svuoto la lista + + # plugIn, layer, features, coordTransform, refresh, check_validity + if qad_layer.addFeaturesToLayer(self.plugIn, currLayer, featuresLayer[1], None, False, False) == False: + self.plugIn.destroyEditCommand() + return + + if pointGeoms is not None and len(pointGeoms) > 0 and PointTempLayer is None: + PointTempLayer = qad_layer.createQADTempLayer(self.plugIn, QgsWkbTypes.PointGeometry) + self.plugIn.addLayerToLastEditCommand("Feature offseted", PointTempLayer) + + if lineGeoms is not None and len(lineGeoms) > 0 and LineTempLayer is None: + LineTempLayer = qad_layer.createQADTempLayer(self.plugIn, QgsWkbTypes.LineGeometry) + self.plugIn.addLayerToLastEditCommand("Feature offseted", LineTempLayer) + + if polygonGeoms is not None and len(polygonGeoms) > 0 and PolygonTempLayer is None: + PolygonTempLayer = qad_layer.createQADTempLayer(self.plugIn, QgsWkbTypes.PolygonGeometry) + self.plugIn.addLayerToLastEditCommand("Feature offseted", PolygonTempLayer) + + # aggiungo gli scarti nei layer temporanei di QAD + # trasformo la geometria in quella dei layer temporanei + # plugIn, pointGeoms, lineGeoms, polygonGeoms, coord, refresh + if qad_layer.addGeometriesToQADTempLayers(self.plugIn, pointGeoms, lineGeoms, polygonGeoms, \ + featuresLayer[0].crs(), False) == False: + self.plugIn.destroyEditCommand() + return + + self.plugIn.endEditCommand() + + + # ============================================================================ + # waitForDistance + # ============================================================================ + def waitForDistance(self): + # imposto il map tool + self.getPointMapTool().setMode(Qad_offset_maptool_ModeEnum.ASK_FOR_FIRST_OFFSET_PT) + self.getPointMapTool().gapType = self.gapType + + keyWords = QadMsg.translate("Command_OFFSET", "Through") + "/" + \ + QadMsg.translate("Command_OFFSET", "Erase") + if self.offset < 0: + default = QadMsg.translate("Command_OFFSET", "Through") + else: + default = self.offset + prompt = QadMsg.translate("Command_OFFSET", "Specify the offset distance or [{0}] <{1}>: ").format(keyWords, unicode(default)) + + englishKeyWords = "Through" + "/" + "Erase" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter o una parola chiave o un numero reale + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT | QadInputTypeEnum.KEYWORDS, \ + default, \ + keyWords, \ + QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) + self.step = 1 + + # ============================================================================ + # waitForObjectSel + # ============================================================================ + def waitForObjectSel(self): + # imposto il map tool + self.getPointMapTool().setMode(Qad_offset_maptool_ModeEnum.ASK_FOR_ENTITY_SELECTION) + self.lastOffSetOnLeftSide = 0 + self.getPointMapTool().lastOffSetOnLeftSide = self.lastOffSetOnLeftSide + self.lastOffSetOnRightSide = 0 + self.getPointMapTool().lastOffSetOnRightSide = self.lastOffSetOnRightSide + + # "Esci" "ANnulla" + keyWords = QadMsg.translate("Command_OFFSET", "Exit") + "/" + \ + QadMsg.translate("Command_OFFSET", "Undo") + default = QadMsg.translate("Command_OFFSET", "Exit") + prompt = QadMsg.translate("Command_OFFSET", "Select object to offset or [{0}] <{1}>: ").format(keyWords, default) + + englishKeyWords = "Exit" + "/" + "Undo" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + default, \ + keyWords, QadInputModeEnum.NONE) + self.step = 2 + + # ============================================================================ + # waitForSidePt + # ============================================================================ + def waitForSidePt(self): + # imposto il map tool + self.getPointMapTool().setMode(Qad_offset_maptool_ModeEnum.OFFSET_KNOWN_ASK_FOR_SIDE_PT) + + if self.multi == False: + keyWords = QadMsg.translate("Command_OFFSET", "Exit") + "/" + \ + QadMsg.translate("Command_OFFSET", "Multiple") + "/" + \ + QadMsg.translate("Command_OFFSET", "Undo") + defaultMsg = QadMsg.translate("Command_OFFSET", "Exit") + default = QadMsg.translate("Command_OFFSET", "Exit") + englishKeyWords = "Exit" + "/" + "Multiple" + "/" + "Undo" + else: + keyWords = QadMsg.translate("Command_OFFSET", "Exit") + "/" + \ + QadMsg.translate("Command_OFFSET", "Undo") + defaultMsg = QadMsg.translate("Command_OFFSET", "next object") + default = None + englishKeyWords = "Exit" + "/" + "Undo" + + if self.OnlySegment == False: + keyWords = keyWords + "/" + \ + QadMsg.translate("Command_OFFSET", "Segment") + englishKeyWords = englishKeyWords + "/" + "Segment" + + prompt = QadMsg.translate("Command_OFFSET", "Specify point on side to offset or [{0}] <{1}>: ").format(keyWords, default) + + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, valore nullo non permesso + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + default, \ + keyWords, QadInputModeEnum.NONE) + self.step = 3 + + # ============================================================================ + # waitForPassagePt + # ============================================================================ + def waitForPassagePt(self): + # imposto il map tool + self.getPointMapTool().setMode(Qad_offset_maptool_ModeEnum.ASK_FOR_PASSAGE_PT) + + if self.multi == False: + keyWords = QadMsg.translate("Command_OFFSET", "Exit") + "/" + \ + QadMsg.translate("Command_OFFSET", "Multiple") + "/" + \ + QadMsg.translate("Command_OFFSET", "Undo") + defaultMsg = QadMsg.translate("Command_OFFSET", "Exit") + default = QadMsg.translate("Command_OFFSET", "Exit") + englishKeyWords = "Exit" + "/" + "Multiple" + "/" + "Undo" + else: + keyWords = QadMsg.translate("Command_OFFSET", "Exit") + "/" + \ + QadMsg.translate("Command_OFFSET", "Undo") + defaultMsg = QadMsg.translate("Command_OFFSET", "next object") + default = None + englishKeyWords = "Exit" + "/" + "Undo" + + if self.OnlySegment == False: + keyWords = keyWords + "/" + \ + QadMsg.translate("Command_OFFSET", "Segment") + englishKeyWords = englishKeyWords + "/" + "Segment" + + prompt = QadMsg.translate("Command_OFFSET", "Specify through point or [{0}] <{1}>: ").format(keyWords, defaultMsg) + + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, valore nullo non permesso + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + default, \ + keyWords, QadInputModeEnum.NONE) + self.step = 4 + + # ============================================================================ + # run + # ============================================================================ + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + # il layer corrente deve essere editabile e di tipo linea o poligono + currLayer, errMsg = qad_layer.getCurrLayerEditable(self.plugIn.canvas, [QgsWkbTypes.LineGeometry, QgsWkbTypes.PolygonGeometry]) + if currLayer is None: + self.showErr(errMsg) + return True # fine comando + + # ========================================================================= + # RICHIESTA DISTANZA DI OFFSET + if self.step == 0: # inizio del comando + CurrSettingsMsg = QadMsg.translate("QAD", "\nCurrent settings: ") + CurrSettingsMsg = CurrSettingsMsg + QadMsg.translate("Command_OFFSET", "OFFSETGAPTYPE = ") + str(self.gapType) + if self.gapType == 0: + CurrSettingsMsg = CurrSettingsMsg + QadMsg.translate("Command_OFFSET", " (extends the segments)") + elif self.gapType == 1: + CurrSettingsMsg = CurrSettingsMsg + QadMsg.translate("Command_OFFSET", " (fillets the segments)") + elif self.gapType == 2: + CurrSettingsMsg = CurrSettingsMsg + QadMsg.translate("Command_OFFSET", " (chamfers the segments)") + + self.showMsg(CurrSettingsMsg) + + self.waitForDistance() + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DELLA DISTANZA DI OFFSET (da step = 0) + elif self.step == 1: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + if self.offset < 0: + value = QadMsg.translate("Command_OFFSET", "Through") + else: + value = self.offset + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("Command_OFFSET", "Through") or value == "Through": + self.offset = -1 + self.getPointMapTool().offset = self.offset + QadVariables.set(QadMsg.translate("Environment variables", "OFFSETDIST"), self.offset) + QadVariables.save() + # si appresta ad attendere la selezione di un oggetto + self.waitForObjectSel() + elif value == QadMsg.translate("Command_OFFSET", "Erase") or value == "Erase": + keyWords = QadMsg.translate("QAD", "Yes") + "/" + \ + QadMsg.translate("QAD", "No") + + if self.eraseEntity == True: + default = QadMsg.translate("QAD", "Yes") + else: + default = QadMsg.translate("QAD", "No") + prompt = QadMsg.translate("Command_OFFSET", "Erase source object after offsetting ? [{0}] <{1}>: ").format(keyWords, default) + + englishKeyWords = "Yes" + "/" + "No" + keyWords += "_" + englishKeyWords + # si appresta ad attendere enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.KEYWORDS, \ + default, \ + keyWords, QadInputModeEnum.NONE) + self.step = 5 + elif value == QadMsg.translate("Command_OFFSET", "Multiple") or value == "Multiple": + self.multi = True + self.waitForBasePt() + elif type(value) == QgsPointXY: # se é stato inserito il primo punto per il calcolo della distanza + self.firstPt.set(value.x(), value.y()) + # imposto il map tool + self.getPointMapTool().firstPt = self.firstPt + self.getPointMapTool().setMode(Qad_offset_maptool_ModeEnum.FIRST_OFFSET_PT_KNOWN_ASK_FOR_SECOND_PT) + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_OFFSET", "Specify second point: ")) + self.step = 6 + elif type(value) == float: + self.offset = value + self.getPointMapTool().offset = self.offset + QadVariables.set(QadMsg.translate("Environment variables", "OFFSETDIST"), self.offset) + QadVariables.save() + # si appresta ad attendere la selezione di un oggetto + self.waitForObjectSel() + + return False + + # ========================================================================= + # RISPOSTA ALLA SELEZIONE DI UN OGGETTO + elif self.step == 2: + entity = None + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + value = QadMsg.translate("Command_OFFSET", "Exit") + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: + entity = self.getPointMapTool().entity + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("Command_OFFSET", "Exit") or value == "Exit": + self.addToLayer(currLayer) + return True + elif value == QadMsg.translate("Command_OFFSET", "Undo") or value == "Undo": + self.undoGeomsInCache() + # si appresta ad attendere la selezione di un oggetto + self.waitForObjectSel() + elif type(value) == QgsPointXY: # se é stato selezionato un punto + if entity is not None and entity.isInitialized(): # se é stata selezionata una entità + self.entity.set(entity) + self.getPointMapTool().layer = self.entity.layer + + qadGeom = self.entity.getQadGeom() + + # la funzione ritorna una lista con + # ( + # + # + # + # + # <"a sinistra di" se il punto é alla sinista della parte (< 0 -> sinistra, > 0 -> destra) + result = getQadGeomClosestPart(qadGeom, value) + self.atGeom = result[2] + self.atSubGeom = result[3] + self.subGeom = getQadGeomAt(qadGeom, self.atGeom, self.atSubGeom) + self.partNum = result[4] + + self.getPointMapTool().subGeom = self.subGeom + if self.offset < 0: # richiesta di punto di passaggio + self.waitForPassagePt() + else: # richiesta la parte dell'oggetto + self.waitForSidePt() + else: + # si appresta ad attendere la selezione di un oggetto + self.waitForObjectSel() + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DI UN PUNTO PER STABILIRE LA PARTE DI OFFSET (da step = 2) + elif self.step == 3: + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + if self.multi == False: # default = esci + self.addToLayer(currLayer) + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if value is None: # oggetto successivo + # si appresta ad attendere la selezione di un oggetto + self.waitForObjectSel() + else: + if type(value) == unicode: + if value == QadMsg.translate("Command_OFFSET", "Exit") or value == "Exit": + self.addToLayer(currLayer) + return True # fine comando + elif value == QadMsg.translate("Command_OFFSET", "Multiple") or value == "Multiple": + self.multi = True + self.waitForSidePt() + elif value == QadMsg.translate("Command_OFFSET", "Undo") or value == "Undo": + self.undoGeomsInCache() + # si appresta ad attendere la selezione di un oggetto + self.waitForObjectSel() + elif value == QadMsg.translate("Command_OFFSET", "Segment") or value == "Segment": + self.OnlySegment = True + if self.subGeom.whatIs() == "POLYLINE": + self.subGeom = self.subGeom.getLinearObjectAt(self.partNum) + self.getPointMapTool().subGeom = self.subGeom + + self.waitForSidePt() + elif type(value) == QgsPointXY: # se é stato selezionato un punto + self.addFeatureCache(value) + if self.multi == False: + # si appresta ad attendere la selezione di un oggetto + self.waitForObjectSel() + else: + # richiesta la parte dell'oggetto + self.waitForSidePt() + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DI UN PUNTO DI PASSAGGIO DI OFFSET (da step = 2) + elif self.step == 4: + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + if self.multi == False: # default = esci + self.addToLayer(currLayer) + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if value is None: # oggetto successivo + # si appresta ad attendere la selezione di un oggetto + self.waitForObjectSel() + else: + if type(value) == unicode: + if value == QadMsg.translate("Command_OFFSET", "Exit") or value == "Exit": + self.addToLayer(currLayer) + return True # fine comando + elif value == QadMsg.translate("Command_OFFSET", "Multiple") or value == "Multiple": + self.multi = True + self.waitForPassagePt() + elif value == QadMsg.translate("Command_OFFSET", "Undo") or value == "Undo": + self.undoGeomsInCache() + # si appresta ad attendere la selezione di un oggetto + self.waitForObjectSel() + elif value == QadMsg.translate("Command_OFFSET", "Segment") or value == "Segment": + self.OnlySegment = True + if self.subGeom.whatIs() == "POLYLINE": + self.subGeom = self.subGeom.getLinearObjectAt(self.partNum) + self.getPointMapTool().subGeom = self.subGeom + + self.waitForPassagePt() + elif type(value) == QgsPointXY: # se é stato selezionato un punto + self.addFeatureCache(value) + if self.multi == False: + # si appresta ad attendere la selezione di un oggetto + self.waitForObjectSel() + else: + # richiesta di punto di passaggio + self.waitForPassagePt() + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DI CANCELLAZIONE OGGETTO SORGENTE (da step = 1) + elif self.step == 5: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + value = QadMsg.translate("QAD", "No") + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: # il valore arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("QAD", "Yes") or value == "Yes": + self.eraseEntity = True + self.waitForDistance() + elif value == QadMsg.translate("QAD", "No") or value == "No": + self.eraseEntity = False + self.waitForDistance() + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA SECONDO PUNTO PER LUNGHEZZA OFFSET (da step = 1) + elif self.step == 6: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == QgsPointXY: # se é stato selezionato un punto + if value == self.firstPt: + self.showMsg(QadMsg.translate("QAD", "\nThe value must be positive and not zero.")) + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_OFFSET", "Specify second point: ")) + return False + + self.offset = qad_utils.getDistance(self.firstPt, value) + self.getPointMapTool().offset = self.offset + QadVariables.set(QadMsg.translate("Environment variables", "OFFSETDIST"), self.offset) + QadVariables.save() + # si appresta ad attendere la selezione di un oggetto + self.waitForObjectSel() + + return False + + \ No newline at end of file diff --git a/cmd/qad_offset_maptool.py b/cmd/qad_offset_maptool.py new file mode 100644 index 00000000..b93b0f66 --- /dev/null +++ b/cmd/qad_offset_maptool.py @@ -0,0 +1,226 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + classe per gestire il map tool in ambito del comando offset + + ------------------- + begin : 2013-10-04 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from qgis.core import QgsWkbTypes, QgsGeometry + + +from .. import qad_utils +from ..qad_getpoint import QadGetPoint, QadGetPointDrawModeEnum, QadGetPointSelectionModeEnum +from ..qad_highlight import QadHighlight +from ..qad_dim import QadDimStyles +from ..qad_msg import QadMsg +from ..qad_offset_fun import offsetPolyline, offsetQGSGeom +from ..qad_geom_relations import getQadGeomClosestPart +from ..qad_multi_geom import fromQgsGeomToQadGeom + + +# =============================================================================== +# Qad_offset_maptool_ModeEnum class. +# =============================================================================== +class Qad_offset_maptool_ModeEnum(): + # si richiede il primo punto per calcolo offset + ASK_FOR_FIRST_OFFSET_PT = 1 + # noto il primo punto per calcolo offset si richiede il secondo punto + FIRST_OFFSET_PT_KNOWN_ASK_FOR_SECOND_PT = 2 + # nota la distanza di offset si richiede il punto per stabilire da che parte + OFFSET_KNOWN_ASK_FOR_SIDE_PT = 3 + # si richiede il punto di passaggio per stabilire da che parte e a quale offset + ASK_FOR_PASSAGE_PT = 4 + # si richiede la selezione di un oggetto + ASK_FOR_ENTITY_SELECTION = 5 + +# =============================================================================== +# Qad_offset_maptool class +# =============================================================================== +class Qad_offset_maptool(QadGetPoint): + + def __init__(self, plugIn): + QadGetPoint.__init__(self, plugIn) + + self.firstPt = None + self.layer = None + self.subGeom = None + self.subGeomAsPolyline = None # geometria sotto forma di lista di punti + self.offset = 0 + self.lastOffSetOnLeftSide = 0 + self.lastOffSetOnRightSide = 0 + self.gapType = 0 + self.__highlight = QadHighlight(self.canvas) + + def hidePointMapToolMarkers(self): + QadGetPoint.hidePointMapToolMarkers(self) + self.__highlight.hide() + + def showPointMapToolMarkers(self): + QadGetPoint.showPointMapToolMarkers(self) + self.__highlight.show() + + def clear(self): + QadGetPoint.clear(self) + self.__highlight.reset() + self.mode = None + + + def addOffSetGeometries(self, newPt): + self.__highlight.reset() + + # la funzione ritorna una lista con + # ( + # + # + # + # + # <"a sinistra di" se il punto é alla sinista della parte con i seguenti valori: + # - < 0 = sinistra (per linea, arco o arco di ellisse) o interno (per cerchi, ellissi) + # - > 0 = destra (per linea, arco o arco di ellisse) o esterno (per cerchi, ellissi) +# result = getQadGeomClosestPart(self.subGeom, newPt) +# leftOf = result[5] +# +# if self.offset < 0: +# offsetDistance = result[0] # minima distanza +# else: +# offsetDistance = self.offset +# +# if leftOf < 0: # a sinistra (per linea, arco o arco di ellisse) o interno (per cerchi, ellissi) +# offsetDistance = offsetDistance + self.lastOffSetOnLeftSide +# else: # alla destra +# offsetDistance = offsetDistance + self.lastOffSetOnRightSide + + forcedOffsetDist = None + if self.offset > 0: # se è stata impostata già una distanza devo solo verificare la direzione dell'offset + # la funzione ritorna una lista con + # ( + # + # + # + # + # <"a sinistra di" se il punto é alla sinista della parte con i seguenti valori: + # - < 0 = sinistra (per linea, arco o arco di ellisse) o interno (per cerchi, ellissi) + # - > 0 = destra (per linea, arco o arco di ellisse) o esterno (per cerchi, ellissi) + dummy = getQadGeomClosestPart(self.subGeom, newPt) + leftOf = dummy[5] + + if leftOf < 0: # a sinistra (per linea, arco o arco di ellisse) o interno (per cerchi, ellissi) + forcedOffsetDist = self.offset + self.lastOffSetOnLeftSide + else: # alla destra + forcedOffsetDist = self.offset + self.lastOffSetOnRightSide + + + # se self.subGeom implementa il metodo isClosed + closed = self.subGeom.isClosed() if hasattr(self.subGeom, "isClosed") and callable(getattr(self.subGeom, "isClosed")) else False + + if self.layer.geometryType() == QgsWkbTypes.PolygonGeometry or closed == True: + qgsGeom = QgsGeometry.fromPolygonXY([self.subGeom.asPolyline()]) + else: + qgsGeom = QgsGeometry.fromPolylineXY(self.subGeom.asPolyline()) + + offsetQGSGeomList = offsetQGSGeom(qgsGeom, \ + newPt, \ + self.gapType, \ + forcedOffsetDist) + + for g in offsetQGSGeomList: + # converto in QAD geometry per riconoscere le curve + g = fromQgsGeomToQadGeom(g).asGeom(self.layer.wkbType()) + self.__highlight.addGeometry(self.mapToLayerCoordinates(self.layer, g), self.layer) + +# lines = offsetPolyline(self.subGeom, \ +# offsetDistance, \ +# "left" if leftOf < 0 else "right", \ +# self.gapType) +# +# for line in lines: +# pts = line.asPolyline() +# if self.layer.geometryType() == QgsWkbTypes.PolygonGeometry: +# if line[0] == line[-1]: # se é una linea chiusa +# offsetGeom = QgsGeometry.fromPolygonXY([pts]) +# else: +# offsetGeom = QgsGeometry.fromPolylineXY(pts) +# else: +# offsetGeom = QgsGeometry.fromPolylineXY(pts) + +# self.__highlight.addGeometry(self.mapToLayerCoordinates(self.layer, offsetGeom), self.layer) + + + def canvasMoveEvent(self, event): + QadGetPoint.canvasMoveEvent(self, event) + + # nota la distanza di offset si richiede il punto per stabilire da che parte + if self.mode == Qad_offset_maptool_ModeEnum.OFFSET_KNOWN_ASK_FOR_SIDE_PT: + self.addOffSetGeometries(self.tmpPoint) + # si richiede il punto di passaggio per stabilire da che parte e a quale offset + elif self.mode == Qad_offset_maptool_ModeEnum.ASK_FOR_PASSAGE_PT: + self.addOffSetGeometries(self.tmpPoint) + + + def activate(self): + QadGetPoint.activate(self) + self.__highlight.show() + + def deactivate(self): + try: # necessario perché se si chiude QGIS parte questo evento nonostante non ci sia più l'oggetto maptool ! + QadGetPoint.deactivate(self) + self.__highlight.hide() + except: + pass + + def setMode(self, mode): + self.clear() + self.mode = mode + # si richiede il primo punto per calcolo offset + if self.mode == Qad_offset_maptool_ModeEnum.ASK_FOR_FIRST_OFFSET_PT: + self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + self.onlyEditableLayers = False + # noto il primo punto per calcolo offset si richiede il secondo punto + if self.mode == Qad_offset_maptool_ModeEnum.FIRST_OFFSET_PT_KNOWN_ASK_FOR_SECOND_PT: + self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.firstPt) + self.onlyEditableLayers = False + # nota la distanza di offset si richiede il punto per stabilire da che parte + elif self.mode == Qad_offset_maptool_ModeEnum.OFFSET_KNOWN_ASK_FOR_SIDE_PT: + self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + self.onlyEditableLayers = False + # si richiede il punto di passaggio per stabilire da che parte e a quale offset + elif self.mode == Qad_offset_maptool_ModeEnum.ASK_FOR_PASSAGE_PT: + self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + self.onlyEditableLayers = False + # si richiede la selezione di un oggetto + elif self.mode == Qad_offset_maptool_ModeEnum.ASK_FOR_ENTITY_SELECTION: + self.setSelectionMode(QadGetPointSelectionModeEnum.ENTITY_SELECTION) + # solo layer lineari o poligono editabili che non appartengano a quote + layerList = [] + for layer in qad_utils.getVisibleVectorLayers(self.plugIn.canvas): # Tutti i layer vettoriali visibili + if (layer.geometryType() == QgsWkbTypes.LineGeometry or layer.geometryType() == QgsWkbTypes.PolygonGeometry) and \ + layer.isEditable(): + if len(QadDimStyles.getDimListByLayer(layer)) == 0: + layerList.append(layer) + + self.layersToCheck = layerList + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + self.onlyEditableLayers = True diff --git a/cmd/qad_options_cmd.py b/cmd/qad_options_cmd.py new file mode 100644 index 00000000..788a187f --- /dev/null +++ b/cmd/qad_options_cmd.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + comando OPTIONS per impostazione disegno + + ------------------- + begin : 2016-02-10 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.PyQt.QtGui import * + + +from ..qad_options_dlg import QadOPTIONSDialog +from .qad_generic_cmd import QadCommandClass +from ..qad_msg import QadMsg + + +# Classe che gestisce il comando OPTIONS +class QadOPTIONSCommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadOPTIONSCommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "OPTIONS") + + def getEnglishName(self): + return "OPTIONS" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runOPTIONSCommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/options.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_OPTIONS", "QAD Options.") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + + def run(self, msgMapTool = False, msg = None): + Form = QadOPTIONSDialog(self.plugIn) + Form.exec_() + return True \ No newline at end of file diff --git a/cmd/qad_pedit_cmd.py b/cmd/qad_pedit_cmd.py new file mode 100644 index 00000000..5e6031e9 --- /dev/null +++ b/cmd/qad_pedit_cmd.py @@ -0,0 +1,2073 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin ok + + comando PEDIT per editare una polilinea o un poligono esistente + + ------------------- + begin : 2014-01-13 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from qgis.PyQt.QtGui import QIcon +from qgis.PyQt.QtCore import QVariant, QMetaType +from qgis.core import * +import qgis.utils + +from ..qad_getpoint import QadGetPointDrawModeEnum +from .qad_generic_cmd import QadCommandClass +from .qad_getdist_cmd import QadGetDistClass +from ..qad_polyline import QadPolyline +from .qad_pedit_maptool import Qad_pedit_maptool_ModeEnum, Qad_pedit_maptool +from .qad_ssget_cmd import QadSSGetClass +from ..qad_msg import QadMsg +from ..qad_textwindow import * +from .. import qad_utils +from .. import qad_layer +from ..qad_variables import QadVariables +from ..qad_snapper import QadSnapTypeEnum +from ..qad_snappointsdisplaymanager import QadSnapPointsDisplayManager +from ..qad_dim import QadDimStyles +from .. import qad_join_fun +from .. import qad_grip +from ..qad_entity import QadEntity, QadEntitySet, QadLayerEntitySetIterator +from ..qad_multi_geom import * +from ..qad_geom_relations import getQadGeomClosestVertex, getQadGeomClosestPart +from ..qad_layer import createMemoryLayer +from ..qad_multi_geom import fromQadGeomToQgsGeom + + +# Classe che gestisce il comando PEDIT +class QadPEDITCommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadPEDITCommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "PEDIT") + + def getEnglishName(self): + return "PEDIT" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runPEDITCommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/pedit.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_PEDIT", "Modifies existing polylines or polygon.") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + self.SSGetClass = QadSSGetClass(plugIn) + self.SSGetClass.onlyEditableLayers = True + self.SSGetClass.checkPointLayer = False # scarto i punto + self.SSGetClass.checkLineLayer = True + self.SSGetClass.checkDimLayers = False # scarto le quote + + self.entitySet = QadEntitySet() + self.entity = QadEntity() + self.atGeom = None + self.atSubGeom = None + self.polyline = QadPolyline() + self.joinToleranceDist = plugIn.joinToleranceDist + self.joinMode = plugIn.joinMode + + self.editVertexMode = None + self.nOperationsToUndo = 0 + + self.firstPt = QgsPointXY() + self.vertexAt = 0 + self.secondVertexAt = 0 + self.after = True + self.snapPointsDisplayManager = QadSnapPointsDisplayManager(self.plugIn.canvas) + self.snapPointsDisplayManager.setIconSize(QadVariables.get(QadMsg.translate("Environment variables", "AUTOSNAPSIZE"))) + self.snapPointsDisplayManager.setColor(QColor(QadVariables.get(QadMsg.translate("Environment variables", "AUTOSNAPCOLOR")))) + + self.GetDistClass = None + self.simplifyTolerance = None + + def __del__(self): + QadCommandClass.__del__(self) + del self.SSGetClass + self.entity.deselectOnLayer() + self.entitySet.deselectOnLayer() + del self.snapPointsDisplayManager + if self.GetDistClass is not None: del self.GetDistClass + + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if self.step == 2: # quando si é in fase di selezione entità + return self.SSGetClass.getPointMapTool() + # quando si é in fase di richiesta distanza + elif self.step == 12: + return self.GetDistClass.getPointMapTool() + else: + if (self.plugIn is not None): + if self.PointMapTool is None: + self.PointMapTool = Qad_pedit_maptool(self.plugIn) + return self.PointMapTool + else: + return None + + + def getCurrentContextualMenu(self): + if self.step == 2: # quando si é in fase di selezione entità + return None # return self.SSGetClass.getCurrentContextualMenu() + # quando si é in fase di richiesta distanza + elif self.step == 12: + return self.GetDistClass.getCurrentContextualMenu() + else: + return self.contextualMenu + + + # ============================================================================ + # setEntityInfo + # ============================================================================ + def setEntityInfo(self, layer, featureId, point): + """ + Setta self.entity, self.atSubGeom, self.polyline + """ + self.entity.set(layer, featureId) + if isLinearQadGeom(self.entity.getQadGeom()): + newQadGeom = convertToPolyline(self.entity.getQadGeom()) + if newQadGeom is not None: self.entity.qadGeom = newQadGeom + + # la funzione ritorna una lista con + # ( + # + # + # + # + # + result = getQadGeomClosestVertex(self.entity.qadGeom, point) + atGeom = result[2] + atSubGeom = result[3] + subGeom = getQadGeomAt(self.entity.qadGeom, atGeom, atSubGeom) + polyline = convertToPolyline(subGeom) + if polyline is None: + self.entity.deselectOnLayer() + return False + self.polyline = polyline + self.atGeom = atGeom + self.atSubGeom = atSubGeom + + self.entity.selectOnLayer(False) # non incrementale + return True + + + # ============================================================================ + # getNextVertex + # ============================================================================ + def getNextVertex(self, vertexAt): + """ + Ritorna la posizione del vertice successivo rispetto vertexAt + """ + tot = self.polyline.qty() + if vertexAt == tot - 1: # se penultimo punto + return 0 if self.polyline.isClosed() else vertexAt + 1 + elif vertexAt < tot: # se non é ultimo punto + return vertexAt + 1 + else: + return vertexAt + + + # ============================================================================ + # getPrevVertex + # ============================================================================ + def getPrevVertex(self, vertexAt): + """ + Ritorna la posizione del vertice precedente rispetto vertexAt + """ + if vertexAt == 0: # se primo punto + if self.polyline.isClosed(): + return self.polyline.qty() - 1 + else: + return vertexAt + else: + return vertexAt - 1 + + + # ============================================================================ + # displayVertexMarker + # ============================================================================ + def displayVertexMarker(self, vertexAt): + if vertexAt == self.polyline.qty(): + pt = self.polyline.getLinearObjectAt(-1).getEndPt() + else: + pt = self.polyline.getLinearObjectAt(vertexAt).getStartPt() + + # visualizzo il punto di snap + snapPoint = dict() + snapPoint[QadSnapTypeEnum.INT] = [pt] + self.snapPointsDisplayManager.show(snapPoint) + + + # ============================================================================ + # setClose + # ============================================================================ + def setClose(self, toClose): + if self.entity.isInitialized(): # selezionato solo un oggetto + qadGeom = self.entity.getQadGeom() + + layer = self.entity.layer + self.plugIn.beginEditCommand("Feature edited", layer) + + f = self.entity.getFeature() + self.polyline.setClose(toClose) + + if layer.geometryType() == QgsWkbTypes.LineGeometry: + newQadGeom = setQadGeomAt(qadGeom, self.polyline, self.atGeom, self.atSubGeom) + f.setGeometry(fromQadGeomToQgsGeom(newQadGeom, layer)) + + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, layer, f, False, False) == False: + self.plugIn.destroyEditCommand() + return + else: # layer di tipo poligono + if toClose == False: # apri + # aggiungo le linee nei layer temporanei di QAD + LineTempLayer = qad_layer.createQADTempLayer(self.plugIn, QgsWkbTypes.LineGeometry) + self.plugIn.addLayerToLastEditCommand("Feature edited", LineTempLayer) + + # trasformo la geometria in quella dei layer temporanei + lineGeoms = [fromQadGeomToQgsGeom(self.polyline, LineTempLayer)] + + # plugIn, pointGeoms, lineGeoms, polygonGeoms, coord, refresh + if qad_layer.addGeometriesToQADTempLayers(self.plugIn, None, lineGeoms, None, \ + None, False) == False: + self.plugIn.destroyEditCommand() + return + + if delQadGeomAt(g, self.atGeom, self.atSubGeom) == False: # da cancellare + # plugIn, layer, feature id, refresh + if qad_layer.deleteFeatureToLayer(self.plugIn, layer, f.id(), False) == False: + self.plugIn.destroyEditCommand() + return + else: + editedFeature = QgsFeature(f) + # trasformo la geometria nel crs del layer + editedFeature.setGeometry(fromQadGeomToQgsGeom(qadGeom, layer)) + + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, layer, editedFeature, False, False) == False: + self.plugIn.destroyEditCommand() + return + else: # selezionati più oggetti + self.plugIn.beginEditCommand("Feature edited", self.entitySet.getLayerList()) + + for layerEntitySet in self.entitySet.layerEntitySetList: + updObjects = [] + entityIterator = QadLayerEntitySetIterator(layerEntitySet) + for entity in entityIterator: + if isLinearQadGeom(entity.getQadGeom()): + entity.qadGeom = convertToPolyline(entity.getQadGeom()) + entity.qadGeom.setClose(toClose) + updFeature = QgsFeature(entity.getFeature()) + # trasformo la geometria nel crs del layer + updFeature.setGeometry(fromQadGeomToQgsGeom(entity.getQadGeom(), layerEntitySet.layer)) + updObjects.append(updFeature) + + # plugIn, layer, features, refresh, check_validity + if qad_layer.updateFeaturesToLayer(self.plugIn, layerEntitySet.layer, updObjects, False, False) == False: + self.plugIn.destroyEditCommand() + return + + self.plugIn.endEditCommand() + self.nOperationsToUndo = self.nOperationsToUndo + 1 + + + # ============================================================================ + # reverse + # ============================================================================ + def reverse(self): + if self.entity.isInitialized(): # selezionato solo un oggetto + g = self.entity.getQadGeom() + self.polyline.reverse() + setQadGeomAt(g, self.polyline, self.atGeom, self.atSubGeom) + f = self.entity.getFeature() + # trasformo la geometria nel crs del layer + f.setGeometry(fromQadGeomToQgsGeom(g, self.entity.layer)) + + self.plugIn.beginEditCommand("Feature edited", self.entity.layer) + + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, self.entity.layer, f, False, False) == False: + self.plugIn.destroyEditCommand() + return + else: # selezionati più oggetti + self.plugIn.beginEditCommand("Feature edited", self.entitySet.getLayerList()) + + for layerEntitySet in self.entitySet.layerEntitySetList: + updObjects = [] + entityIterator = QadLayerEntitySetIterator(layerEntitySet) + for entity in entityIterator: + if isLinearQadGeom(g): + entity.qadGeom = convertToPolyline(entity.getQadGeom()) + entity.qadGeom.reverse() + updFeature = QgsFeature(entity.getFeature()) + # trasformo la geometria nel crs del layer + updFeature.setGeometry(fromQadGeomToQgsGeom(entity.getQadGeom(), layerEntitySet.layer)) + updObjects.append(updFeature) + + # plugIn, layer, features, refresh, check_validity + if qad_layer.updateFeaturesToLayer(self.plugIn, layerEntitySet.layer, updObjects, False, False) == False: + self.plugIn.destroyEditCommand() + return + + self.plugIn.endEditCommand() + self.nOperationsToUndo = self.nOperationsToUndo + 1 + + + # ============================================================================ + # join + # ============================================================================ + def join(self): + tolerance2ApproxCurve = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) + crs = qgis.utils.iface.mapCanvas().mapSettings().destinationCrs() + # creo un layer temporaneo in memoria con campo numerico per + # contenere la posizione dell'entità originale nella lista newIdFeatureList + # creo un layer temporaneo in memoria + vectorLayer = createMemoryLayer("QAD_SelfJoinLines", "LineString", crs) + + provider = vectorLayer.dataProvider() + provider.addAttributes([QgsField('index', QMetaType.Int, 'Int')]) + vectorLayer.updateFields() + + if vectorLayer.startEditing() == False: + return + + # inserisco nel layer i vari oggetti lineari (WKBLineString) + layerList = [] + newIdFeatureList = [] # lista ((newId - layer - feature) ...) + i = 0 + + if self.entity.isInitialized(): # selezionato solo un oggetto + self.entitySet.removeEntity(self.entity) # elimino dal gruppo l'entità da unire + + # aggiungo l'entità a cui unirsi + layer = self.entity.layer + + if layer.geometryType() != QgsWkbTypes.LineGeometry: + return + + f = self.entity.getFeature() + g = f.geometry() + # accetto linestring o multilinestring con una sola linea + if g.type() != QgsWkbTypes.LineGeometry: + return + if g.isMultipart() == True: + if len(g.asMultiPolyline()) != 1: + return + + newFeature = QgsFeature() + newFeature.initAttributes(1) + newFeature.setAttribute(0, 0) + geom = self.polyline.asGeom() + geom.convertToSingleType() # in caso fosse multilinestring la converto in linestring + newFeature.setGeometry(geom) + i = i + 1 + + if vectorLayer.addFeature(newFeature) == False: + vectorLayer.destroyEditCommand() + return + newIdFeatureList.append([newFeature.id(), layer, f]) + + + for layerEntitySet in self.entitySet.layerEntitySetList: + layer = layerEntitySet.layer + + if layer.geometryType() != QgsWkbTypes.LineGeometry: + continue + + for f in layerEntitySet.getFeatureCollection(): + g = f.geometry() + # accetto linestring o multilinestring con una sola linea + if g.type() != QgsWkbTypes.LineGeometry: + continue + if g.isMultipart() == True: + if len(g.asMultiPolyline()) != 1: + continue + + newFeature = QgsFeature() + newFeature.initAttributes(1) + newFeature.setAttribute(0, i) + + # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy + geom = self.layerToMapCoordinates(layer, f.geometry()) + geom.convertToSingleType() # in caso fosse multilinestring la converto in linestring + newFeature.setGeometry(geom) + i = i + 1 + + if vectorLayer.addFeature(newFeature) == False: + vectorLayer.destroyEditCommand() + return + newIdFeatureList.append([newFeature.id(), layer, f]) + + vectorLayer.endEditCommand(); + vectorLayer.updateExtents() + + if provider.capabilities() & QgsVectorDataProvider.CreateSpatialIndex: + provider.createSpatialIndex() + + deleteFeatures = [] + if self.entity.isInitialized(): # selezionato solo un oggetto + featureIdToJoin = newIdFeatureList[0][0] + + # featureIdToJoin, vectorLayer, tolerance2ApproxCurve, toleranceDist, mode + deleteFeatures.extend(qad_join_fun.joinFeatureInVectorLayer(featureIdToJoin, vectorLayer, \ + tolerance2ApproxCurve, \ + self.joinToleranceDist, self.joinMode)) + else: + i = 0 + tot = len(newIdFeatureList) + while i < tot: + featureIdToJoin = newIdFeatureList[i][0] + # featureIdToJoin, vectorLayer, tolerance2ApproxCurve, toleranceDist, mode + deleteFeatures.extend(qad_join_fun.joinFeatureInVectorLayer(featureIdToJoin, vectorLayer, \ + tolerance2ApproxCurve, \ + self.joinToleranceDist, self.joinMode)) + i = i + 1 + + self.plugIn.beginEditCommand("Feature edited", self.entitySet.getLayerList()) + + if self.entity.isInitialized(): # selezionato solo un oggetto + newFeature = qad_utils.getFeatureById(vectorLayer, newIdFeatureList[0][0]) + if newFeature is None: + self.plugIn.destroyEditCommand() + return + + layer = newIdFeatureList[0][1] + f = newIdFeatureList[0][2] + + g = self.entity.getQadGeom() + newQadGeom = setQadGeomAt(g, fromQgsGeomToQadGeom(newFeature.geometry()), self.atGeom, self.atSubGeom) + # trasformo la geometria nel crs del layer + f.setGeometry(fromQadGeomToQgsGeom(newQadGeom, layer)) + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, layer, f, False, False) == False: + self.plugIn.destroyEditCommand() + return + else: + # aggiorno la geometria delle features rimaste nel layer temporaneo + # fetchAttributes, fetchGeometry, rectangle, useIntersect + for newFeature in vectorLayer.getFeatures(qad_utils.getFeatureRequest([], True, None, False)): + layer = newIdFeatureList[newFeature['index']][1] + f = newIdFeatureList[newFeature['index']][2] + + coordTransform = QgsCoordinateTransform(vectorLayer.crs(), layer.crs(), QgsProject.instance()) + g = newFeature.geometry() + g.transform(coordTransform) + f.setGeometry(g) + + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, layer, f, False, False) == False: + self.plugIn.destroyEditCommand() + return + + # cancello le features rimosse dal layer temporaneo + for newFeature in deleteFeatures: + layer = newIdFeatureList[newFeature['index']][1] + f = newIdFeatureList[newFeature['index']][2] + # plugIn, layer, feature id, refresh + if qad_layer.deleteFeatureToLayer(self.plugIn, layer, f.id(), False) == False: + self.plugIn.destroyEditCommand() + return + + self.plugIn.endEditCommand() + self.nOperationsToUndo = self.nOperationsToUndo + 1 + + + # ============================================================================ + # curve + # ============================================================================ + def curve(self, toCurve): + if self.entity.isInitialized(): # selezionato solo un oggetto + g = self.entity.getQadGeom() + self.polyline.curve(toCurve) + newQadGeom = setQadGeomAt(g, self.polyline, self.atGeom, self.atSubGeom) + f = self.entity.getFeature() + # trasformo la geometria nel crs del layer + f.setGeometry(fromQadGeomToQgsGeom(newQadGeom, self.entity.layer)) + + self.plugIn.beginEditCommand("Feature edited", self.entity.layer) + + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, self.entity.layer, f, False, False) == False: + self.plugIn.destroyEditCommand() + return + else: + self.plugIn.beginEditCommand("Feature edited", self.entitySet.getLayerList()) + + for layerEntitySet in self.entitySet.layerEntitySetList: + updObjects = [] + entityIterator = QadLayerEntitySetIterator(layerEntitySet) + for entity in entityIterator: + if isLinearQadGeom(g): + entity.qadGeom = convertToPolyline(entity.getQadGeom()) + entity.qadGeom.curve(toCurve) + updFeature = QgsFeature(entity.getFeature()) + # trasformo la geometria nel crs del layer + updFeature.setGeometry(fromQadGeomToQgsGeom(entity.getQadGeom(), layerEntitySet.layer)) + updObjects.append(updFeature) + + # plugIn, layer, features, refresh, check_validity + if qad_layer.updateFeaturesToLayer(self.plugIn, layerEntitySet.layer, updObjects, False, False) == False: + self.plugIn.destroyEditCommand() + return + + self.plugIn.endEditCommand() + self.nOperationsToUndo = self.nOperationsToUndo + 1 + + + # ============================================================================ + # simplify + # ============================================================================ + def simplify(self): + if self.entity.isInitialized(): # selezionato solo un oggetto + self.plugIn.beginEditCommand("Feature edited", self.entity.layer) + self.polyline.simplify(self.simplifyTolerance) + g = self.entity.getQadGeom() + newQadGeom = setQadGeomAt(g, self.polyline, self.atGeom, self.atSubGeom) + f = self.entity.getFeature() + # trasformo la geometria nel crs del layer + f.setGeometry(fromQadGeomToQgsGeom(newQadGeom, self.entity.layer)) + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, self.entity.layer, f, False, False) == False: + self.plugIn.destroyEditCommand() + return + else: + self.plugIn.beginEditCommand("Feature edited", self.entitySet.getLayerList()) + + for layerEntitySet in self.entitySet.layerEntitySetList: + updObjects = [] + entityIterator = QadLayerEntitySetIterator(layerEntitySet) + for entity in entityIterator: + if isLinearQadGeom(entity.getQadGeom()): + entity.qadGeom = convertToPolyline(entity.getQadGeom()) + entity.qadGeom.simplify(self.simplifyTolerance) + updFeature = QgsFeature(entity.getFeature()) + # trasformo la geometria nel crs del layer + updFeature.setGeometry(fromQadGeomToQgsGeom(entity.getQadGeom(), layerEntitySet.layer)) + updObjects.append(updFeature) + + # plugIn, layer, features, refresh, check_validity + if qad_layer.updateFeaturesToLayer(self.plugIn, layerEntitySet.layer, updObjects, False, False) == False: + self.plugIn.destroyEditCommand() + return + + self.plugIn.endEditCommand() + self.nOperationsToUndo = self.nOperationsToUndo + 1 + + + # ============================================================================ + # insertVertexAt + # ============================================================================ + def insertVertexAt(self, pt): + layer = self.entity.layer + + if self.after: # dopo + if self.vertexAt == self.polyline.qty() and self.polyline.isClosed(): + self.polyline.insertPoint(0, pt) + else: + self.polyline.insertPoint(self.vertexAt, pt) + else: # prima + if self.vertexAt == 0 and self.polyline.isClosed(): + self.polyline.insertPoint(self.polyline.qty() - 1, pt) + else: + self.polyline.insertPoint(self.vertexAt - 1, pt) + + g = self.entity.getQadGeom() + newQadGeom = setQadGeomAt(g, self.polyline, self.atGeom, self.atSubGeom) + f = self.entity.getFeature() + # trasformo la geometria nel crs del layer + f.setGeometry(fromQadGeomToQgsGeom(newQadGeom, layer)) + + self.plugIn.beginEditCommand("Feature edited", layer) + + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, layer, f, False, False) == False: + self.plugIn.destroyEditCommand() + return + + self.plugIn.endEditCommand() + self.nOperationsToUndo = self.nOperationsToUndo + 1 + + + # ============================================================================ + # moveVertexAt + # ============================================================================ + def moveVertexAt(self, pt): + layer = self.entity.layer + + self.polyline.movePoint(self.vertexAt, pt) + g = self.entity.getQadGeom() + newQadGeom = setQadGeomAt(g, self.polyline, self.atGeom, self.atSubGeom) + f = self.entity.getFeature() + # trasformo la geometria nel crs del layer + f.setGeometry(fromQadGeomToQgsGeom(newQadGeom, layer)) + + self.plugIn.beginEditCommand("Feature edited", layer) + + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, layer, f, False, False) == False: + self.plugIn.destroyEditCommand() + return + + self.plugIn.endEditCommand() + self.nOperationsToUndo = self.nOperationsToUndo + 1 + + + # ============================================================================ + # straightenFromVertexAtToSecondVertexAt + # ============================================================================ + def straightenFromVertexAtToSecondVertexAt(self): + if self.vertexAt == self.secondVertexAt: + return + + if self.vertexAt < self.secondVertexAt: + firstPt = self.polyline.getPointAtVertex(self.vertexAt) + secondPt = self.polyline.getPointAtVertex(self.secondVertexAt) + for i in range(self.vertexAt, self.secondVertexAt, 1): + self.polyline.remove(self.vertexAt) + self.polyline.insert(self.vertexAt, QadLine().set(firstPt, secondPt)) + elif self.vertexAt > self.secondVertexAt: + if self.polyline.isClosed(): + firstPt = self.polyline.getPointAtVertex(self.vertexAt) + secondPt = self.polyline.getPointAtVertex(self.secondVertexAt) + for i in range(self.vertexAt, self.polyline.qty(), 1): + self.polyline.remove(self.vertexAt) + for i in range(0, self.secondVertexAt, 1): + self.polyline.remove(0) + + self.polyline.insert(self.vertexAt, QadLine().set(firstPt, secondPt)) + else: + firstPt = self.polyline.getPointAtVertex(self.secondVertexAt) + secondPt = self.polyline.getPointAtVertex(self.vertexAt) + for i in range(self.secondVertexAt, self.vertexAt, 1): + self.polyline.remove(self.secondVertexAt) + self.polyline.insert(self.secondVertexAt, QadLine().set(firstPt, secondPt)) + + g = self.entity.getQadGeom() + newQadGeom = setQadGeomAt(g, self.polyline, self.atGeom, self.atSubGeom) + f = self.entity.getFeature() + # trasformo la geometria nel crs del layer + f.setGeometry(fromQadGeomToQgsGeom(newQadGeom, layer)) + + layer = self.entity.layer + self.plugIn.beginEditCommand("Feature edited", layer) + + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, layer, f, False, False) == False: + self.plugIn.destroyEditCommand() + return + + self.plugIn.endEditCommand() + self.nOperationsToUndo = self.nOperationsToUndo + 1 + + + # ============================================================================ + # breakFromVertexAtToSecondVertexAt + # ============================================================================ + def breakFromVertexAtToSecondVertexAt(self): + layer = self.entity.layer + + firstPt = self.polyline.getPointAtVertex(self.vertexAt) + secondPt = self.polyline.getPointAtVertex(self.secondVertexAt) + g1, g2 = self.polyline.breakOnPts(firstPt, secondPt) + if g1 is None: return + g = self.entity.getQadGeom() + newQadGeom = setQadGeomAt(g, g1, self.atGeom, self.atSubGeom) + f = self.entity.getFeature() + # trasformo la geometria nel crs del layer + f.setGeometry(fromQadGeomToQgsGeom(newQadGeom, layer)) + + self.plugIn.beginEditCommand("Feature edited", layer) + + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, self.entity.layer, f, False, False) == False: + self.plugIn.destroyEditCommand() + return + + if g2 is not None: + brokenFeature2 = QgsFeature(f) + # trasformo la geometria nel crs del layer + brokenFeature2.setGeometry(fromQadGeomToQgsGeom(g2, layer)) + # plugIn, layer, feature, coordTransform, refresh, check_validity + if qad_layer.addFeatureToLayer(self.plugIn, layer, brokenFeature2, None, False, False, False) == False: + self.plugIn.destroyEditCommand() + return + + self.polyline = g1 + self.plugIn.endEditCommand() + self.nOperationsToUndo = self.nOperationsToUndo + 1 + + + # ============================================================================ + # waitForEntsel + # ============================================================================ + def waitForEntsel(self): + # imposto il map tool + self.step = 1 + self.getPointMapTool().setMode(Qad_pedit_maptool_ModeEnum.ASK_FOR_ENTITY_SEL) + + keyWords = QadMsg.translate("Command_PEDIT", "Last") + "/" + \ + QadMsg.translate("Command_PEDIT", "Multiple") + prompt = QadMsg.translate("Command_PEDIT", "Select polyline or [{0}]: ").format(QadMsg.translate("Command_PEDIT", "Multiple")) + + englishKeyWords = "Last" + "/" + "Multiple" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, valore nullo non permesso + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, QadInputModeEnum.NOT_NULL) + + + # ============================================================================ + # WaitForMainMenu + # ============================================================================ + def WaitForMainMenu(self): + # verifico se ci sono layer di tipo linea + line = False + if self.entity.isInitialized(): # selezionato solo un oggetto + if self.entity.layer.geometryType() == QgsWkbTypes.LineGeometry: + line = True + else: + layerList = self.entitySet.getLayerList() + for layer in layerList: + if layer.geometryType() == QgsWkbTypes.LineGeometry: + line = True + break + + if line == True: # se ci sono dei layer linea + if self.entity.isInitialized(): # selezionato solo un oggetto + if self.polyline.isClosed(): # se é chiusa + keyWords = QadMsg.translate("Command_PEDIT", "Open") + "/" + englishKeyWords = "Open" + else: + keyWords = QadMsg.translate("Command_PEDIT", "Close") + "/" + englishKeyWords = "Close" + else: # selezionati più oggetti + keyWords = QadMsg.translate("Command_PEDIT", "Close") + "/" + \ + QadMsg.translate("Command_PEDIT", "Open") + "/" + englishKeyWords = "Close" + "/" + "Open" + + keyWords = keyWords + QadMsg.translate("Command_PEDIT", "Join") + "/" + englishKeyWords = englishKeyWords + "Join" + else: # se non ci sono dei layer linea + keyWords = "" + msg = "" + englishKeyWords = "" + + if self.entity.isInitialized(): # selezionato solo un oggetto + keyWords = keyWords + QadMsg.translate("Command_PEDIT", "Edit vertex") + "/" + englishKeyWords = englishKeyWords + "Edit vertex" + + keyWords = keyWords + QadMsg.translate("Command_PEDIT", "Fit") + "/" + \ + QadMsg.translate("Command_PEDIT", "Decurve") + "/" + \ + QadMsg.translate("Command_PEDIT", "Reverse") + "/" + \ + QadMsg.translate("Command_PEDIT", "Simplify") + "/" + \ + QadMsg.translate("Command_PEDIT", "Undo") + englishKeyWords = englishKeyWords + "Fit" + "/" + "Decurve" + "/" + "Reverse" + "/" + "Simplify" + "/" + "Undo" + prompt = QadMsg.translate("Command_PEDIT", "Enter an option [{0}]: ").format(keyWords) + + self.step = 3 + self.getPointMapTool().setMode(Qad_pedit_maptool_ModeEnum.NONE) + + keyWords += "_" + englishKeyWords + # si appresta ad attendere enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, QadInputModeEnum.NONE) + return False + + + # ============================================================================ + # WaitForJoin + # ============================================================================ + def WaitForJoin(self): + CurrSettingsMsg = QadMsg.translate("QAD", "\nCurrent settings: ") + CurrSettingsMsg = CurrSettingsMsg + QadMsg.translate("Command_PEDIT", "Join type = ") + if self.joinMode == 1: + CurrSettingsMsg = CurrSettingsMsg + QadMsg.translate("Command_PEDIT", "extends the segments") + elif self.joinMode == 2: + CurrSettingsMsg = CurrSettingsMsg + QadMsg.translate("Command_PEDIT", "adds segments") + elif self.joinMode == 3: + CurrSettingsMsg = CurrSettingsMsg + QadMsg.translate("Command_PEDIT", "extends and adds segments") + + self.showMsg(CurrSettingsMsg) + self.waitForDistance() + + + # ============================================================================ + # waitForDistance + # ============================================================================ + def waitForDistance(self): + # imposto il map tool + self.getPointMapTool().setMode(Qad_pedit_maptool_ModeEnum.ASK_FOR_FIRST_TOLERANCE_PT) + + keyWords = QadMsg.translate("Command_PEDIT", "Join type") + prompt = QadMsg.translate("Command_PEDIT", "Specify gap tolerance or [{0}] <{1}>: ").format(keyWords, str(self.joinToleranceDist)) + + englishKeyWords = "Join type" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter o una parola chiave o un numero reale + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT | QadInputTypeEnum.KEYWORDS, \ + self.joinToleranceDist, \ + keyWords, \ + QadInputModeEnum.NOT_NEGATIVE) + self.step = 4 + + + # ============================================================================ + # waitForJoinType + # ============================================================================ + def waitForJoinType(self): + # imposto il map tool + self.getPointMapTool().setMode(Qad_pedit_maptool_ModeEnum.NONE) + + keyWords = QadMsg.translate("Command_PEDIT", "Extend") + "/" + \ + QadMsg.translate("Command_PEDIT", "Add") + "/" + \ + QadMsg.translate("Command_PEDIT", "Both") + englishKeyWords = "Extend" + "/" + "Add" + "/" + "Both" + if self.joinMode == 1: + default = QadMsg.translate("Command_PEDIT", "Extend") + elif self.joinMode == 2: + default = QadMsg.translate("Command_PEDIT", "Add") + elif self.joinMode == 3: + default = QadMsg.translate("Command_PEDIT", "Both") + prompt = QadMsg.translate("Command_PEDIT", "Specify join type [{0}] <{1}>: ").format(keyWords, default) + + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter o una parola chiave o un numero reale + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, QadInputTypeEnum.KEYWORDS, default, \ + keyWords) + self.step = 6 + + + # ============================================================================ + # WaitForVertexEditingMenu + # ============================================================================ + def WaitForVertexEditingMenu(self): + self.getPointMapTool().setPolyline(self.polyline, self.entity.layer) + + self.displayVertexMarker(self.vertexAt) + + keyWords = QadMsg.translate("Command_PEDIT", "Next") + "/" + \ + QadMsg.translate("Command_PEDIT", "Previous") + englishKeyWords = "Next" + "/" + "Previous" + + if self.entity.layer.geometryType() == QgsWkbTypes.LineGeometry: + keyWords = keyWords + "/" + QadMsg.translate("Command_PEDIT", "Break") + englishKeyWords = englishKeyWords + "/" + "Break" + + keyWords = keyWords + "/" + QadMsg.translate("Command_PEDIT", "Insert") + "/" + \ + QadMsg.translate("Command_PEDIT", "INsert before") + "/" + \ + QadMsg.translate("Command_PEDIT", "Move") + "/" + \ + QadMsg.translate("Command_PEDIT", "Straighten") + "/" + \ + QadMsg.translate("Command_PEDIT", "eXit") + englishKeyWords = englishKeyWords + "/" + "Insert" + "/" + "INsert before" + "/" + \ + "Move" + "/" + "Straighten" + "/" + "eXit" + + prompt = QadMsg.translate("Command_PEDIT", "Enter a vertex editing option [{0}] <{1}>: ").format(keyWords, self.default) + + self.step = 8 + self.getPointMapTool().setMode(Qad_pedit_maptool_ModeEnum.ASK_FOR_VERTEX) + + keyWords += "_" + englishKeyWords + # si appresta ad attendere enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + self.default, \ + keyWords, QadInputModeEnum.NONE) + return False + + + # ============================================================================ + # waitForNewVertex + # ============================================================================ + def waitForNewVertex(self): + # imposto il map tool + self.getPointMapTool().setVertexAt(self.vertexAt, self.after) + self.getPointMapTool().setMode(Qad_pedit_maptool_ModeEnum.ASK_FOR_NEW_VERTEX) + + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_PEDIT", "Specify the position of the new vertex: ")) + self.step = 9 + + + # ============================================================================ + # waitForMoveVertex + # ============================================================================ + def waitForMoveVertex(self): + # imposto il map tool + self.getPointMapTool().setVertexAt(self.vertexAt) + self.getPointMapTool().setMode(Qad_pedit_maptool_ModeEnum.ASK_FOR_MOVE_VERTEX) + + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_PEDIT", "Specify the new vertex position: ")) + self.step = 10 + + + # ============================================================================ + # WaitForVertexEditingMenu + # ============================================================================ + def WaitForSecondVertex(self): + self.displayVertexMarker(self.secondVertexAt) + + keyWords = QadMsg.translate("Command_PEDIT", "Next") + "/" + \ + QadMsg.translate("Command_PEDIT", "Previous") + "/" + \ + QadMsg.translate("Command_PEDIT", "Go") + "/" + \ + QadMsg.translate("Command_PEDIT", "eXit") + englishKeyWords = "Next" + "/" + "Previous" + "/" + "Go" + "/" + "eXit" + prompt = QadMsg.translate("Command_PEDIT", "Enter a selection option for the second vertex [{0}] <{1}>: ").format(keyWords, self.default1) + + self.getPointMapTool().setMode(Qad_pedit_maptool_ModeEnum.ASK_FOR_VERTEX) + + keyWords += "_" + englishKeyWords + # si appresta ad attendere enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + self.default1, \ + keyWords, QadInputModeEnum.NONE) + self.step = 11 + + return False + + + # ============================================================================ + # WaitForSimplifyTolerance + # ============================================================================ + def WaitForSimplifyTolerance(self, msgMapTool, msg): + if self.GetDistClass is not None: + del self.GetDistClass + self.GetDistClass = QadGetDistClass(self.plugIn) + if self.simplifyTolerance is None: + prompt = QadMsg.translate("Command_PEDIT", "Specify tolerance: ") + else: + prompt = QadMsg.translate("Command_PEDIT", "Specify tolerance <{0}>: ") + self.GetDistClass.msg = prompt.format(str(self.simplifyTolerance)) + self.GetDistClass.dist = self.simplifyTolerance + self.GetDistClass.inputMode = QadInputModeEnum.NOT_NEGATIVE + self.step = 12 + self.GetDistClass.run(msgMapTool, msg) + + + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + if self.step == 0: + self.waitForEntsel() + return False # continua + + # ========================================================================= + # RISPOSTA ALLA SELEZIONE OGGETTI + elif self.step == 1: + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("Command_PEDIT", "Multiple") or value == "Multiple": + self.SSGetClass.checkPolygonLayer = True + self.SSGetClass.run(msgMapTool, msg) + self.step = 2 + return False + elif type(value) == QgsPointXY: # se é stato selezionato un punto + self.entity.clear() + self.polyline.removeAll() + + if self.getPointMapTool().entity.isInitialized(): + if self.setEntityInfo(self.getPointMapTool().entity.layer, \ + self.getPointMapTool().entity.featureId, value) == True: + self.WaitForMainMenu() + return False + else: + # cerco se ci sono entità nel punto indicato considerando + # solo layer lineari o poligono editabili che non appartengano a quote + layerList = [] + for layer in qad_utils.getVisibleVectorLayers(self.plugIn.canvas): # Tutti i layer vettoriali visibili + if (layer.geometryType() == QgsWkbTypes.LineGeometry or layer.geometryType() == QgsWkbTypes.PolygonGeometry) and \ + layer.isEditable(): + if len(QadDimStyles.getDimListByLayer(layer)) == 0: + layerList.append(layer) + + result = qad_utils.getEntSel(self.getPointMapTool().toCanvasCoordinates(value), + self.getPointMapTool(), \ + QadVariables.get(QadMsg.translate("Environment variables", "PICKBOX")), \ + layerList) + if result is not None: + # result[0] = feature, result[1] = layer, result[0] = point + if self.setEntityInfo(result[1], result[0].id(), result[2]) == True: + self.WaitForMainMenu() + return False + else: + return True # fine comando + + # si appresta ad attendere la selezione degli oggetti + self.waitForEntsel() + return False + + # ========================================================================= + # RISPOSTA ALLA SELEZIONE DI UN GRUPPO OGGETTI + elif self.step == 2: + if self.SSGetClass.run(msgMapTool, msg) == True: + self.entitySet.set(self.SSGetClass.entitySet) + + if self.entitySet.count() == 0: + self.waitForEntsel() + else: + self.WaitForMainMenu() + self.getPointMapTool().refreshSnapType() # aggiorno lo snapType che può essere variato dal maptool di selezione entità + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL MENU PRINCIPALE (da step = 1 e 2) + elif self.step == 3: # dopo aver atteso una opzione si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + self.WaitForMainMenu() + return False + else: # il punto arriva come parametro della funzione + value = msg + + if value == QadMsg.translate("Command_PEDIT", "Close") or value == "Close": + self.setClose(True) + elif value == QadMsg.translate("Command_PEDIT", "Open") or value == "Open": + self.setClose(False) + elif value == QadMsg.translate("Command_PEDIT", "Edit vertex") or value == "Edit vertex": + self.vertexAt = 0 + self.default = QadMsg.translate("Command_PEDIT", "Next") + self.WaitForVertexEditingMenu() + return False + elif value == QadMsg.translate("Command_PEDIT", "Join") or value == "Join": + qad_utils.deselectAll(self.plugIn.canvas.layers()) + if self.entity.isInitialized(): # selezionato solo un oggetto + self.SSGetClass.checkPolygonLayer = False # scarto i poligoni + self.SSGetClass.run(msgMapTool, msg) + self.step = 7 + return False + else: + self.WaitForJoin() + return False + elif value == QadMsg.translate("Command_PEDIT", "Fit") or value == "Fit": + self.curve(True) + elif value == QadMsg.translate("Command_PEDIT", "Decurve") or value == "Decurve": + self.curve(False) + elif value == QadMsg.translate("Command_PEDIT", "Reverse") or value == "Reverse": + self.reverse() + elif value == QadMsg.translate("Command_PEDIT", "Simplify") or value == "Simplify": + self.WaitForSimplifyTolerance(msgMapTool, msg) + return False + elif value == QadMsg.translate("Command_PEDIT", "Undo") or value == "Undo": + if self.nOperationsToUndo > 0: + self.nOperationsToUndo = self.nOperationsToUndo - 1 + self.plugIn.undoEditCommand() + else: + self.showMsg(QadMsg.translate("QAD", "\nThe command has been canceled.")) + + if self.entity.isInitialized(): # selezionato solo un oggetto + if self.atSubGeom is not None: + # ricarico la geometria ripristinata dall'annulla + self.entity.qadGeom = None + if isLinearQadGeom(self.entity.getQadGeom()): + self.entity.qadGeom = convertToPolyline(self.entity.getQadGeom()) + + subGeom = getQadGeomAt(self.entity.qadGeom, self.atGeom, self.atSubGeom).copy() + self.polyline = convertToPolyline(subGeom) + else: + return True # fine comando + + self.entity.deselectOnLayer() + self.entitySet.deselectOnLayer() + self.WaitForMainMenu() + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DELLA DISTANZA DI APPROSSIMAZIONE (da step = 3) + elif self.step == 4: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + value = self.joinToleranceDist + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("Command_PEDIT", "Join type") or value == "Join type": + # si appresta ad attendere il tipo di unione + self.waitForJoinType() + elif type(value) == QgsPointXY: # se é stato inserito il primo punto per il calcolo della distanza + # imposto il map tool + self.firstPt.set(value.x(), value.y()) + self.getPointMapTool().firstPt = self.firstPt + self.getPointMapTool().setMode(Qad_pedit_maptool_ModeEnum.FIRST_TOLERANCE_PT_KNOWN_ASK_FOR_SECOND_PT) + + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_PEDIT", "Specify second point: ")) + self.step = 5 + elif type(value) == float: + self.joinToleranceDist = value + self.plugIn.setJoinToleranceDist(self.joinToleranceDist) + self.join() + self.entity.deselectOnLayer() + self.entitySet.deselectOnLayer() + self.WaitForMainMenu() + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA SECONDO PUNTO PER DISTANZA DI APPROSSIMAZIONE (da step = 4) + elif self.step == 5: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if value == self.firstPt: + self.showMsg(QadMsg.translate("QAD", "\nThe value must be positive and not zero.")) + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_PEDIT", "Specify second point: ")) + return False + + self.joinToleranceDist = qad_utils.getDistance(self.firstPt, value) + self.plugIn.setJoinToleranceDist(self.joinToleranceDist) + self.join() + self.entity.deselectOnLayer() + self.entitySet.deselectOnLayer() + self.WaitForMainMenu() + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DELLA MODALITA' DI UNIONE (da step = 4) + elif self.step == 6: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("Command_PEDIT", "Extend") or value == "Extend": + self.joinMode = 1 + self.plugIn.setJoinMode(self.joinMode) + elif value == QadMsg.translate("Command_PEDIT", "Add") or value == "Add": + self.joinMode = 2 + self.plugIn.setJoinMode(self.joinMode) + elif value == QadMsg.translate("Command_PEDIT", "Both") or value == "Both": + self.joinMode = 3 + self.plugIn.setJoinMode(self.joinMode) + + self.WaitForJoin() + return False + + # ========================================================================= + # RISPOSTA ALLA SELEZIONE DI UN GRUPPO OGGETTI DA UNIRE (da step = 3) + elif self.step == 7: + if self.SSGetClass.run(msgMapTool, msg) == True: + self.entitySet.set(self.SSGetClass.entitySet) + + if self.entitySet.count() > 0: + self.joinToleranceDist = 0.0 + self.join() + + self.entity.deselectOnLayer() + self.entitySet.deselectOnLayer() + self.WaitForMainMenu() + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DI OPZIONI EDITAZIONE VERTICI (da step = 3) + elif self.step == 8: # dopo aver atteso un punto o una opzione si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + value = self.default + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: + value = self.getPointMapTool().point + else: # il punto o l'opzione arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("Command_PEDIT", "Next") or value == "Next": + self.default = value + self.vertexAt = self.getNextVertex(self.vertexAt) + self.WaitForVertexEditingMenu() + elif value == QadMsg.translate("Command_PEDIT", "Previous") or value == "Previous": + self.default = value + self.vertexAt = self.getPrevVertex(self.vertexAt) + self.WaitForVertexEditingMenu() + elif value == QadMsg.translate("Command_PEDIT", "Break") or value == "Break": + self.editVertexMode = QadMsg.translate("Command_PEDIT", "Break") + self.secondVertexAt = self.vertexAt + self.default1 = QadMsg.translate("Command_PEDIT", "Next") + self.WaitForSecondVertex() + return False + elif value == QadMsg.translate("Command_PEDIT", "Insert") or value == "Insert": + self.after = True + self.waitForNewVertex() + elif value == QadMsg.translate("Command_PEDIT", "INsert before") or value == "INsert before": + self.after = False + self.waitForNewVertex() + elif value == QadMsg.translate("Command_PEDIT", "Move") or value == "Move": + self.waitForMoveVertex() + elif value == QadMsg.translate("Command_PEDIT", "Straighten") or value == "Straighten": + self.editVertexMode = QadMsg.translate("Command_PEDIT", "Straighten") + self.secondVertexAt = self.vertexAt + self.default1 = QadMsg.translate("Command_PEDIT", "Next") + self.WaitForSecondVertex() + return False + elif value == QadMsg.translate("Command_PEDIT", "eXit") or value == "eXit": + self.WaitForMainMenu() + elif type(value) == QgsPointXY: # se é stato inserito un punto + # cerco l'indice del vertice più vicino al punto + # la funzione ritorna una lista con + # ( + # + # + # + # + # ) + self.vertexAt = getQadGeomClosestVertex(self.polyline, value)[5] + self.WaitForVertexEditingMenu() + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL NUOVO VERTICE DA INSERIRE (da step = 8) + elif self.step == 9: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + self.insertVertexAt(value) + self.vertexAt = self.vertexAt + (1 if self.after else -1) + self.WaitForVertexEditingMenu() + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DELLA POSIZIONE DEL VERTICE DA SPOSTARE (da step = 8) + elif self.step == 10: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + self.moveVertexAt(value) + self.WaitForVertexEditingMenu() + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL SECONDO VERTICE (da step = 8) + elif self.step == 11: # dopo aver atteso un punto o una opzione si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + value = self.default + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: + value = self.getPointMapTool().point + else: # il punto o l'opzione arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("Command_PEDIT", "Next") or value == "Next": + self.default1 = value + self.secondVertexAt = self.getNextVertex(self.secondVertexAt) + self.WaitForSecondVertex() + elif value == QadMsg.translate("Command_PEDIT", "Previous") or value == "Previous": + self.default1 = value + self.secondVertexAt = self.getPrevVertex(self.secondVertexAt) + self.WaitForSecondVertex() + elif value == QadMsg.translate("Command_PEDIT", "Go") or value == "Go": + pt = self.polyline.getPointAtVertex(self.vertexAt) + + if self.editVertexMode == QadMsg.translate("Command_PEDIT", "Break"): + self.breakFromVertexAtToSecondVertexAt() + elif self.editVertexMode == QadMsg.translate("Command_PEDIT", "Straighten"): + self.straightenFromVertexAtToSecondVertexAt() + + self.vertexAt = self.polyline.getVertexPosAtPt(pt) + self.WaitForVertexEditingMenu() + elif value == QadMsg.translate("Command_PEDIT", "eXit") or value == "eXit": + self.WaitForVertexEditingMenu() + elif type(value) == QgsPointXY: # se é stato inserito il primo punto + # cerco l'indice del vertice più vicino al punto + # la funzione ritorna una lista con + # ( + # + # + # + # + # ) + self.secondVertexAt = getQadGeomClosestVertex(self.polyline, value)[5] + self.WaitForSecondVertex() + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DELLA TOLLERANZE PER SEMPLIFICAZIONE (da step = 3) + elif self.step == 12: + if self.GetDistClass.run(msgMapTool, msg) == True: + if self.GetDistClass.dist is not None: + self.simplifyTolerance = self.GetDistClass.dist + self.simplify() + self.entity.deselectOnLayer() + self.entitySet.deselectOnLayer() + self.WaitForMainMenu() + return False # fine comando + + +# ============================================================================ +# Classe che gestisce il comando per inserire/cancellare un vertice per i grip +# ============================================================================ +class QadGRIPINSERTREMOVEVERTEXCommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadGRIPINSERTREMOVEVERTEXCommandClass(self.plugIn) + + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + self.entity = None + self.skipToNextGripCommand = False + self.copyEntities = False + self.basePt = QgsPointXY() + self.nOperationsToUndo = 0 + + self.after = True + self.insert_mode = True + self.vertexAt = 0 + self.firstPt = QgsPointXY() + self.polyline = None + + def __del__(self): + QadCommandClass.__del__(self) + + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if (self.plugIn is not None): + if self.PointMapTool is None: + self.PointMapTool = Qad_pedit_maptool(self.plugIn) + return self.PointMapTool + else: + return None + + + def setInsertVertexAfter_Mode(self): + self.after = True + self.insert_mode = True + + def setInsertVertexBefore_Mode(self): + self.after = False + self.insert_mode = True + + def setRemoveVertex_mode(self): + self.insert_mode = False + + + # ============================================================================ + # setSelectedEntityGripPoints + # ============================================================================ + def setSelectedEntityGripPoints(self, entitySetGripPoints): + # lista delle entityGripPoint con dei grip point selezionati + # setta la prima entità con un grip selezionato + # setta: self.entity, self.polyline, self.atGeom, self.atSubGeom + self.entity = None + for entityGripPoints in entitySetGripPoints.entityGripPoints: + for gripPoint in entityGripPoints.gripPoints: + # grip point selezionato + if gripPoint.getStatus() == qad_grip.QadGripStatusEnum.SELECTED: + self.firstPt.set(gripPoint.getPoint().x(), gripPoint.getPoint().y()) + + # verifico se l'entità appartiene ad uno stile di quotatura + if QadDimStyles.isDimEntity(entityGripPoints.entity): + return False + if isLinearQadGeom(entityGripPoints.entity.getQadGeom()): + newQadGeom = convertToPolyline(entityGripPoints.entity.getQadGeom()) + if newQadGeom is not None: entityGripPoints.entity.qadGeom = newQadGeom + + # la funzione ritorna una lista con + # ( + # + # + # + # se geometria chiusa è tipo polyline la lista contiene anche + # + # <"a sinistra di" se il punto é alla sinista della parte (< 0 -> sinistra, > 0 -> destra) + # ) + result = getQadGeomClosestPart(entityGripPoints.entity.getQadGeom(), self.firstPt) + self.atGeom = result[2] + self.atSubGeom = result[3] + subGeom = getQadGeomAt(entityGripPoints.entity.getQadGeom(), self.atGeom, self.atSubGeom) + polyline = convertToPolyline(subGeom) + if polyline is None: + return False + self.polyline = polyline + self.entity = entityGripPoints.entity + # setto il n. di vertice + self.vertexAt = gripPoint.nVertex + + self.getPointMapTool().setPolyline(self.polyline, self.entity.layer) + return True + + return False + + + # ============================================================================ + # insertVertexAt + # ============================================================================ + def insertVertexAt(self, pt): + layer = self.entity.layer + f = self.entity.getFeature() + if f is None: # non c'è più la feature + return False + + # faccio una copia locale + polyline = self.polyline.copy() + + if self.after: # dopo + # fromStartEndPtsAngle + if self.vertexAt == polyline.qty() and polyline.isClosed(): + polyline.insertPoint(0, pt) + else: + polyline.insertPoint(self.vertexAt, pt) + else: # prima + if self.vertexAt == 0 and polyline.isClosed(): + polyline.insertPoint(self.polyline.qty() - 1, pt) + else: + polyline.insertPoint(self.vertexAt - 1, pt) + + qadGeom = self.entity.getQadGeom() + newQadGeom = setQadGeomAt(qadGeom, polyline, self.atGeom, self.atSubGeom) + + # trasformo la geometria nel crs del layer + f.setGeometry(fromQadGeomToQgsGeom(newQadGeom, layer)) + + self.plugIn.beginEditCommand("Feature edited", layer) + + if self.copyEntities == False: + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, layer, f, False, False) == False: + self.plugIn.destroyEditCommand() + return False + else: + # plugIn, layer, features, coordTransform, refresh, check_validity + if qad_layer.addFeatureToLayer(self.plugIn, layer, f, None, False, False, False) == False: + self.plugIn.destroyEditCommand() + return False + + self.plugIn.endEditCommand() + self.nOperationsToUndo = self.nOperationsToUndo + 1 + + + # ============================================================================ + # removeVertexAt + # ============================================================================ + def removeVertexAt(self): + if self.polyline.qty() == 1: return False # non si può cancellare l'unica parte della geometria + + layer = self.entity.layer + f = self.entity.getFeature() + if f is None: # non c'è più la feature + return False + + # faccio una copia locale + polyline = self.polyline.copy() + + if self.vertexAt == 0: + polyline.remove(self.vertexAt) # rimuovo la prima parte + elif self.vertexAt == self.polyline.qty(): + polyline.remove(self.vertexAt - 1) # rimuovo la ultima parte + else: + # modifico la parte successiva + polyline.movePoint(self.vertexAt, polyline.getPointAtVertex(self.vertexAt - 1)) + polyline.remove(self.vertexAt - 1) # rimuovo la parte precedente + + qadGeom = self.entity.getQadGeom() + newQadGeom = setQadGeomAt(qadGeom, polyline, self.atGeom, self.atSubGeom) + + # trasformo la geometria nel crs del layer + f.setGeometry(fromQadGeomToQgsGeom(newQadGeom, layer)) + + self.plugIn.beginEditCommand("Feature edited", layer) + + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, layer, f, False, False) == False: + self.plugIn.destroyEditCommand() + return False + + self.plugIn.endEditCommand() + + + # ============================================================================ + # waitForBasePt + # ============================================================================ + def waitForBasePt(self): + self.step = 2 + # imposto il map tool + self.getPointMapTool().setMode(Qad_pedit_maptool_ModeEnum.ASK_FOR_BASE_PT) + + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_GRIP", "Specify base point: ")) + + + # ============================================================================ + # waitForNewVertex + # ============================================================================ + def waitForNewVertex(self): + # imposto il map tool + self.getPointMapTool().setVertexAt(self.vertexAt, self.after) + if self.basePt is not None: + self.getPointMapTool().firstPt = self.basePt + self.getPointMapTool().setMode(Qad_pedit_maptool_ModeEnum.ASK_FOR_NEW_VERTEX) + + keyWords = QadMsg.translate("Command_GRIP", "Base point") + "/" + \ + QadMsg.translate("Command_GRIP", "Copy") + "/" + \ + QadMsg.translate("Command_GRIP", "Undo") + "/" + \ + QadMsg.translate("Command_GRIP", "eXit") + prompt = QadMsg.translate("Command_GRIPINSERTREMOVEVERTEX", "Specify the position of the new vertex or [{0}]: ").format(keyWords) + + englishKeyWords = "Base point" + "/" + "Copy" + "/" + "Undo" + "/" "eXit" + keyWords += "_" + englishKeyWords + + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, valori positivi + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, QadInputModeEnum.NONE) + self.step = 1 + + + # ============================================================================ + # run + # ============================================================================ + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + # ========================================================================= + # RICHIESTA SELEZIONE OGGETTI + if self.step == 0: # inizio del comando + if self.entity is None: # non ci sono oggetti da stirare + return True + + if self.insert_mode: + self.showMsg(QadMsg.translate("Command_GRIPINSERTREMOVEVERTEX", "\n** ADD VERTEX **\n")) + # si appresta ad attendere un nuovo punto + self.waitForNewVertex() + else: + self.showMsg(QadMsg.translate("Command_GRIPINSERTREMOVEVERTEX", "\n** REMOVE VERTEX **\n")) + self.removeVertexAt() + return True + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL NUOVO VERTICE DA INSERIRE (da step = 1) + elif self.step == 1: + ctrlKey = False + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + if self.copyEntities == False: + self.skipToNextGripCommand = True + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + ctrlKey = self.getPointMapTool().ctrlKey + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("Command_GRIP", "Base point") or value == "Base point": + # si appresta ad attendere il punto base + self.waitForBasePt() + elif value == QadMsg.translate("Command_GRIP", "Copy") or value == "Copy": + # Copia entità lasciando inalterate le originali + self.copyEntities = True + # si appresta ad attendere un nuovo punto + self.waitForNewVertex() + elif value == QadMsg.translate("Command_GRIP", "Undo") or value == "Undo": + if self.nOperationsToUndo > 0: + self.nOperationsToUndo = self.nOperationsToUndo - 1 + self.plugIn.undoEditCommand() + else: + self.showMsg(QadMsg.translate("QAD", "\nThe command has been canceled.")) + # si appresta ad attendere un nuovo punto + self.waitForNewVertex() + elif value == QadMsg.translate("Command_GRIP", "eXit") or value == "eXit": + return True # fine comando + elif type(value) == QgsPointXY: + if ctrlKey: + self.copyEntities = True + + offsetX = value.x() - self.basePt.x() + offsetY = value.y() - self.basePt.y() + value.set(self.firstPt.x() + offsetX, self.firstPt.y() + offsetY) + self.insertVertexAt(value) + + if self.copyEntities == False: + return True + # si appresta ad attendere un nuovo punto + self.waitForNewVertex() + else: + if self.copyEntities == False: + self.skipToNextGripCommand = True + return True # fine comando + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA PUNTO BASE (da step = 1) + elif self.step == 2: # dopo aver atteso un punto + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + pass # opzione di default + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == QgsPointXY: # se é stato inserito il punto base + self.basePt.set(value.x(), value.y()) + # imposto il map tool + self.getPointMapTool().basePt = self.basePt + self.getPointMapTool().firstPt = self.basePt + + # si appresta ad attendere un nuovo punto + self.waitForNewVertex() + + return False + + +# ============================================================================ +# Classe che gestisce il comando per convertire in arco o in linea un segmento per i grip +# ============================================================================ +class QadGRIPARCLINECONVERTCommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadGRIPARCLINECONVERTCommandClass(self.plugIn) + + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + self.entity = None + self.skipToNextGripCommand = False + self.copyEntities = False + self.nOperationsToUndo = 0 + self.basePt = QgsPointXY() + + self.lineToArc = True + self.partAt = 0 + self.polyline = QadPolyline() + + + def __del__(self): + QadCommandClass.__del__(self) + + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if (self.plugIn is not None): + if self.PointMapTool is None: + self.PointMapTool = Qad_gripLineToArcConvert_maptool(self.plugIn) + return self.PointMapTool + else: + return None + + + def setLineToArcConvert_Mode(self): + self.lineToArc = True + + def setArcToLineConvert_Mode(self): + self.lineToArc = False + + + # ============================================================================ + # setSelectedEntityGripPoints + # ============================================================================ + def setSelectedEntityGripPoints(self, entitySetGripPoints): + # lista delle entityGripPoint con dei grip point selezionati + # setta la prima entità con un grip selezionato + self.entity = None + for entityGripPoints in entitySetGripPoints.entityGripPoints: + for gripPoint in entityGripPoints.gripPoints: + # grip point selezionato + if gripPoint.getStatus() == qad_grip.QadGripStatusEnum.SELECTED and \ + (gripPoint.gripType == qad_grip.QadGripPointTypeEnum.LINE_MID_POINT or \ + gripPoint.gripType == qad_grip.QadGripPointTypeEnum.ARC_MID_POINT): + # verifico se l'entità appartiene ad uno stile di quotatura + if QadDimStyles.isDimEntity(entityGripPoints.entity): + return False + if isLinearQadGeom(entityGripPoints.entity.getQadGeom()): + entityGripPoints.entity.qadGeom = convertToPolyline(entityGripPoints.entity.getQadGeom()) + + # setta: self.entity, self.polyline, self.atSubGeom + self.entity = entityGripPoints.entity + + firstPt = QgsPointXY(gripPoint.getPoint()) + + # la funzione ritorna una lista con + # ( + # + # + # + # + # + result = getQadGeomClosestVertex(entityGripPoints.entity.getQadGeom(), firstPt) + atGeom = result[2] + atSubGeom = result[3] + subGeom = getQadGeomAt(entityGripPoints.entity.getQadGeom(), atGeom, atSubGeom) + polyline = convertToPolyline(subGeom) + if polyline is None: + return False + self.polyline = polyline + self.atGeom = atGeom + self.atSubGeom = atSubGeom + + # setto il n. della parte + self.partAt = gripPoint.nVertex + + self.getPointMapTool().setPolyline(self.polyline, self.entity.layer, self.partAt) + + return True + + return False + + + # ============================================================================ + # convertLineToArc + # ============================================================================ + def convertLineToArc(self, pt): + layer = self.entity.layer + f = self.entity.getFeature() + if f is None: # non c'è più la feature + return False + + # faccio una copia locale + polyline = self.polyline.copy() + + tolerance2ApproxCurve = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) + linearObject = polyline.getLinearObjectAt(self.partAt) + if linearObject.whatIs() == "ARC": # se è già arco + return False + + startPt = linearObject.getStartPt() + endPt = linearObject.getEndPt() + arc = QadArc() + if arc.fromStartSecondEndPts(startPt, pt, endPt) == False: + return False + + polyline.insert(self.partAt, arc) + polyline.remove(self.partAt + 1) + + g = self.entity.getQadGeom() + newQadGeom = setQadGeomAt(g, polyline, self.atGeom, self.atSubGeom) + # trasformo la geometria nel crs del layer + f.setGeometry(fromQadGeomToQgsGeom(newQadGeom, layer)) + + self.plugIn.beginEditCommand("Feature edited", layer) + + if self.copyEntities == False: + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, layer, f, False, False) == False: + self.plugIn.destroyEditCommand() + return False + else: + # plugIn, layer, features, coordTransform, refresh, check_validity + if qad_layer.addFeatureToLayer(self.plugIn, layer, f, None, False, False, False) == False: + self.plugIn.destroyEditCommand() + return False + + self.plugIn.endEditCommand() + self.nOperationsToUndo = self.nOperationsToUndo + 1 + + + # ============================================================================ + # convertArcToLine + # ============================================================================ + def convertArcToLine(self): + layer = self.entity.layer + f = self.entity.getFeature() + if f is None: # non c'è più la feature + return False + + # faccio una copia locale + polyline = self.polyline.copy() + tolerance2ApproxCurve = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) + linearObject = polyline.getLinearObjectAt(self.partAt) + if linearObject.whatIs() == "LINE": # se è già segmento retto + return False + + line = QadLine().set(linearObject.getStartPt(), linearObject.getEndPt()) + + polyline.insert(self.partAt, line) + polyline.remove(self.partAt + 1) + + g = self.entity.getQadGeom() + newQadGeom = setQadGeomAt(g, polyline, self.atGeom, self.atSubGeom) + # trasformo la geometria nel crs del layer + f.setGeometry(fromQadGeomToQgsGeom(newQadGeom, layer)) + + self.plugIn.beginEditCommand("Feature edited", layer) + + if self.copyEntities == False: + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, layer, f, False, False) == False: + self.plugIn.destroyEditCommand() + return False + else: + # plugIn, layer, features, coordTransform, refresh, check_validity + if qad_layer.addFeatureToLayer(self.plugIn, layer, f, None, False, False, False) == False: + self.plugIn.destroyEditCommand() + return False + + self.plugIn.endEditCommand() + self.nOperationsToUndo = self.nOperationsToUndo + 1 + + + # ============================================================================ + # waitForConvertToArc + # ============================================================================ + def waitForConvertToArc(self): + # imposto il map tool + self.getPointMapTool().setMode(Qad_gripLineToArcConvert_maptool_ModeEnum.START_END_PT_KNOWN_ASK_FOR_SECOND_PT) + + keyWords = QadMsg.translate("Command_GRIP", "Copy") + "/" + \ + QadMsg.translate("Command_GRIP", "Undo") + "/" + \ + QadMsg.translate("Command_GRIP", "eXit") + prompt = QadMsg.translate("Command_GRIPARCLINECONVERT", "Specify the arc middle point or [{0}]: ").format(keyWords) + + englishKeyWords = "Copy" + "/" + "Undo" + "/" "eXit" + keyWords += "_" + englishKeyWords + + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, valori positivi + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, QadInputModeEnum.NONE) + self.step = 1 + + + # ============================================================================ + # run + # ============================================================================ + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + # ========================================================================= + # RICHIESTA SELEZIONE OGGETTI + if self.step == 0: # inizio del comando + if self.entity is None: # non ci sono oggetti da stirare + return True + + if self.lineToArc: + self.showMsg(QadMsg.translate("Command_GRIPINSERTREMOVEVERTEX", "\n** CONVERT TO ARC **\n")) + # si appresta ad attendere un punto per definire l'arco + self.waitForConvertToArc() + else: + self.showMsg(QadMsg.translate("Command_GRIPINSERTREMOVEVERTEX", "\n** CONVERT TO LINE **\n")) + self.convertArcToLine() + return True + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL NUOVO PUNTO PER DEFINIRE UN ARCO (da step = 1) + elif self.step == 1: + ctrlKey = False + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + if self.copyEntities == False: + self.skipToNextGripCommand = True + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + ctrlKey = self.getPointMapTool().ctrlKey + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("Command_GRIP", "Copy") or value == "Copy": + # Copia entità lasciando inalterate le originali + self.copyEntities = True + # si appresta ad attendere un punto per definire l'arco + self.waitForConvertToArc() + elif value == QadMsg.translate("Command_GRIP", "Undo") or value == "Undo": + if self.nOperationsToUndo > 0: + self.nOperationsToUndo = self.nOperationsToUndo - 1 + self.plugIn.undoEditCommand() + else: + self.showMsg(QadMsg.translate("QAD", "\nThe command has been canceled.")) + # si appresta ad attendere un punto per definire l'arco + self.waitForConvertToArc() + elif value == QadMsg.translate("Command_GRIP", "eXit") or value == "eXit": + return True # fine comando + elif type(value) == QgsPointXY: + if ctrlKey: + self.copyEntities = True + + self.convertLineToArc(value) + + if self.copyEntities == False: + return True + # si appresta ad attendere un punto per definire l'arco + self.waitForConvertToArc() + else: + if self.copyEntities == False: + self.skipToNextGripCommand = True + return True # fine comando + + return False diff --git a/cmd/qad_pedit_maptool.py b/cmd/qad_pedit_maptool.py new file mode 100644 index 00000000..f0b0c880 --- /dev/null +++ b/cmd/qad_pedit_maptool.py @@ -0,0 +1,339 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + classe per gestire il map tool in ambito del comando pedit + + ------------------- + begin : 2013-05-22 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from qgis.core import QgsPointXY, QgsWkbTypes, QgsGeometry + + +from .. import qad_utils +from ..qad_snapper import * +from ..qad_variables import QadVariables +from ..qad_getpoint import QadGetPoint, QadGetPointSelectionModeEnum, QadGetPointDrawModeEnum +from ..qad_polyline import QadPolyline +from ..qad_rubberband import QadRubberBand +from ..qad_highlight import QadHighlight +from ..qad_dim import QadDimStyles +from ..qad_msg import QadMsg + + +# =============================================================================== +# Qad_pedit_maptool_ModeEnum class. +# =============================================================================== +class Qad_pedit_maptool_ModeEnum(): + # si richiede la selezione di un'entità + ASK_FOR_ENTITY_SEL = 1 + # non si richiede niente + NONE = 2 + # si richiede il primo punto per calcolo distanza di approssimazione + ASK_FOR_FIRST_TOLERANCE_PT = 3 + # noto il primo punto per calcolo distanza di approssimazione si richiede il secondo punto + FIRST_TOLERANCE_PT_KNOWN_ASK_FOR_SECOND_PT = 4 + # si richiede un nuovo vertice da inserire + ASK_FOR_NEW_VERTEX = 5 + # si richiede la nuova posizione di un vertice da spostare + ASK_FOR_MOVE_VERTEX = 6 + # si richiede la posizione più vicina ad un vertice + ASK_FOR_VERTEX = 7 + # si richede il punto base (grip mode) + ASK_FOR_BASE_PT = 8 + + +# =============================================================================== +# Qad_pedit_maptool class +# =============================================================================== +class Qad_pedit_maptool(QadGetPoint): + + def __init__(self, plugIn): + QadGetPoint.__init__(self, plugIn) + + self.firstPt = None + self.mode = None + + self.layer = None + self.polyline = QadPolyline() + self.tolerance2ApproxCurve = None + self.vertexAt = 0 + self.vertexPt = None + self.after = True + self.basePt = None + self.__highlight = QadHighlight(self.canvas) + + + def hidePointMapToolMarkers(self): + QadGetPoint.hidePointMapToolMarkers(self) + self.__highlight.hide() + + def showPointMapToolMarkers(self): + QadGetPoint.showPointMapToolMarkers(self) + self.__highlight.show() + + def clear(self): + QadGetPoint.clear(self) + self.__highlight.reset() + self.mode = None + if self.basePt is not None: + del(self.basePt) + self.basePt = None + + def setPolyline(self, polyline, layer): + self.polyline.set(polyline) + self.layer = layer + self.tolerance2ApproxCurve = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) + + + def setVertexAt(self, vertexAt, after = None): + if vertexAt == self.polyline.qty(): + self.firstPt = self.polyline.getLinearObjectAt(-1).getEndPt() + else: + self.firstPt = self.polyline.getLinearObjectAt(vertexAt).getStartPt() + + self.vertexPt = QgsPointXY(self.firstPt) + self.vertexAt = vertexAt + self.after = after + + + def canvasMoveEvent(self, event): + QadGetPoint.canvasMoveEvent(self, event) + + self.__highlight.reset() + tmpPolyline = None + + # si richiede un nuovo vertice da inserire + if self.mode == Qad_pedit_maptool_ModeEnum.ASK_FOR_NEW_VERTEX: + if self.basePt is not None: + offsetX = self.tmpPoint.x() - self.basePt.x() + offsetY = self.tmpPoint.y() - self.basePt.y() + newPt = QgsPointXY(self.vertexPt.x() + offsetX, self.vertexPt.y() + offsetY) + else: + newPt = QgsPointXY(self.tmpPoint) + + tmpPolyline = self.polyline.copy() + + if self.after: # dopo + if self.vertexAt == tmpPolyline.qty() and tmpPolyline.isClosed(): + tmpPolyline.insertPoint(0, newPt) + else: + tmpPolyline.insertPoint(self.vertexAt, newPt) + else: # prima + if self.vertexAt == 0 and tmpPolyline.isClosed(): + tmpPolyline.insertPoint(tmpPolyline.qty() - 1, newPt) + else: + tmpPolyline.insertPoint(self.vertexAt - 1, newPt) + + elif self.mode == Qad_pedit_maptool_ModeEnum.ASK_FOR_MOVE_VERTEX: + newPt = QgsPointXY(self.tmpPoint) + tmpPolyline = self.polyline.copy() + tmpPolyline.movePoint(self.vertexAt, newPt) + + if tmpPolyline is not None: + if self.layer is not None: + geom = tmpPolyline.asGeom(self.layer.wkbType()) + else: + geom = tmpPolyline.asGeom(QgsWkbTypes.CurvePolygon) + + # trasformo la geometria nel crs del layer + self.__highlight.addGeometry(self.mapToLayerCoordinates(self.layer, geom), self.layer) + +# pts = tmpPolyline.asPolyline(self.tolerance2ApproxCurve) +# if self.layer.geometryType() == QgsWkbTypes.PolygonGeometry: +# geom = QgsGeometry.fromPolygonXY([pts]) +# else: +# geom = QgsGeometry.fromPolylineXY(pts) +# +# # trasformo la geometria nel crs del layer +# self.__highlight.addGeometry(self.mapToLayerCoordinates(self.layer, geom), self.layer) + + + def activate(self): + QadGetPoint.activate(self) + self.__highlight.show() + + def deactivate(self): + try: # necessario perché se si chiude QGIS parte questo evento nonostante non ci sia più l'oggetto maptool ! + QadGetPoint.deactivate(self) + self.__highlight.hide() + except: + pass + + def setMode(self, mode): + self.mode = mode + + # si richiede la selezione di un'entità + if self.mode == Qad_pedit_maptool_ModeEnum.ASK_FOR_ENTITY_SEL: + self.setSelectionMode(QadGetPointSelectionModeEnum.ENTITY_SELECTION) + + # solo layer lineari o poligono editabili che non appartengano a quote + layerList = [] + for layer in qad_utils.getVisibleVectorLayers(self.plugIn.canvas): # Tutti i layer vettoriali visibili + if (layer.geometryType() == QgsWkbTypes.LineGeometry or layer.geometryType() == QgsWkbTypes.PolygonGeometry) and \ + layer.isEditable(): + if len(QadDimStyles.getDimListByLayer(layer)) == 0: + layerList.append(layer) + + self.layersToCheck = layerList + self.setSnapType(QadSnapTypeEnum.DISABLE) + # non si richiede niente + elif self.mode == Qad_pedit_maptool_ModeEnum.NONE: + self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + # si richiede il primo punto per calcolo distanza di approssimazione + # si richiede la posizione più vicina ad un vertice + elif self.mode == Qad_pedit_maptool_ModeEnum.ASK_FOR_FIRST_TOLERANCE_PT: + self.onlyEditableLayers = False + self.checkPointLayer = True + self.checkLineLayer = True + self.checkPolygonLayer = True + self.setSnapType() + self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + # noto il primo punto per calcolo distanza di approssimazione si richiede il secondo punto + elif self.mode == Qad_pedit_maptool_ModeEnum.FIRST_TOLERANCE_PT_KNOWN_ASK_FOR_SECOND_PT or \ + self.mode == Qad_pedit_maptool_ModeEnum.ASK_FOR_NEW_VERTEX or \ + self.mode == Qad_pedit_maptool_ModeEnum.ASK_FOR_MOVE_VERTEX: + self.onlyEditableLayers = False + self.checkPointLayer = True + self.checkLineLayer = True + self.checkPolygonLayer = True + self.setSnapType() + self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.firstPt) + # si richiede la posizione più vicina ad un vertice + elif self.mode == Qad_pedit_maptool_ModeEnum.ASK_FOR_VERTEX: + self.setSnapType(QadSnapTypeEnum.DISABLE) + self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + self.setStartPoint(None) + # si richede il punto base (grip mode) + elif self.mode == Qad_pedit_maptool_ModeEnum.ASK_FOR_BASE_PT: + self.setSnapType() + self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + + +# =============================================================================== +# Qad_gripLineToArcConvert_maptool_ModeEnum class. +# =============================================================================== +class Qad_gripLineToArcConvert_maptool_ModeEnum(): + # noti il punto iniziale e finale dell'arco si richiede il punto intermedio + START_END_PT_KNOWN_ASK_FOR_SECOND_PT = 1 + # non si richiede niente + NONE = 2 + + +# =============================================================================== +# Qad_gripLineToArcConvert_maptool class +# =============================================================================== +class Qad_gripLineToArcConvert_maptool(QadGetPoint): + + def __init__(self, plugIn): + QadGetPoint.__init__(self, plugIn) + + self.firstPt = None + self.mode = None + + self.layer = None + self.polyline = QadPolyline() + self.linearObject = None + self.startPt = None + self.endPt = None + self.tolerance2ApproxCurve = None + self.__highlight = QadHighlight(self.canvas) + + + def hidePointMapToolMarkers(self): + QadGetPoint.hidePointMapToolMarkers(self) + self.__highlight.hide() + + def showPointMapToolMarkers(self): + QadGetPoint.showPointMapToolMarkers(self) + self.__highlight.show() + + def clear(self): + QadGetPoint.clear(self) + self.__highlight.reset() + self.mode = None + + def setPolyline(self, polyline, layer, partAt): + self.polyline.set(polyline) + self.layer = layer + self.tolerance2ApproxCurve = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) + self.linearObject = self.polyline.getLinearObjectAt(partAt) + self.firstPt = self.polyline.getMiddlePt() + self.startPt = self.polyline.getStartPt() + self.endPt = self.polyline.getEndPt() + + + def canvasMoveEvent(self, event): + QadGetPoint.canvasMoveEvent(self, event) + + self.__highlight.reset() + ok = False + + # noti il punto iniziale e finale dell'arco si richiede il punto intermedio + if self.mode == Qad_gripLineToArcConvert_maptool_ModeEnum.START_END_PT_KNOWN_ASK_FOR_SECOND_PT: + if self.linearObject is None: + return + arc = QadArc() + if arc.fromStartSecondEndPts(self.startPt, self.tmpPoint, self.endPt) == False: + return + if qad_utils.ptNear(self.startPt, arc.getStartPt()): + self.linearObject.setArc(arc, False) # arco non inverso + else: + self.linearObject.setArc(arc, True) # arco inverso + ok = True + + if ok: + pts = self.polyline.asPolyline(self.tolerance2ApproxCurve) + if self.layer.geometryType() == QgsWkbTypes.PolygonGeometry: + geom = QgsGeometry.fromPolygonXY([pts]) + else: + geom = QgsGeometry.fromPolylineXY(pts) + # trasformo la geometria nel crs del layer + self.__highlight.addGeometry(self.mapToLayerCoordinates(self.layer, geom), self.layer) + + + def activate(self): + QadGetPoint.activate(self) + self.__highlight.show() + + def deactivate(self): + try: # necessario perché se si chiude QGIS parte questo evento nonostante non ci sia più l'oggetto maptool ! + QadGetPoint.deactivate(self) + self.__highlight.hide() + except: + pass + + def setMode(self, mode): + self.mode = mode + + # noti il punto iniziale e finale dell'arco si richiede il punto intermedio + if self.mode == Qad_gripLineToArcConvert_maptool_ModeEnum.START_END_PT_KNOWN_ASK_FOR_SECOND_PT: + self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.firstPt) + # non si richiede niente + elif self.mode == Qad_pedit_maptool_ModeEnum.NONE: + self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) + self.setDrawMode(QadGetPointDrawModeEnum.NONE) diff --git a/cmd/qad_pline_cmd.py b/cmd/qad_pline_cmd.py new file mode 100644 index 00000000..a873340f --- /dev/null +++ b/cmd/qad_pline_cmd.py @@ -0,0 +1,1486 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin OK + + comando PLINE per disegnare una polilinea + + ------------------- + begin : 2013-05-22 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.PyQt.QtGui import QIcon +from qgis.core import QgsWkbTypes, QgsGeometry, QgsPointXY + + +from ..qad_line import QadLine +from ..qad_polyline import QadPolyline +from ..qad_getpoint import QadGetPointDrawModeEnum +from .qad_pline_maptool import Qad_pline_maptool, Qad_pline_maptool_ModeEnum +from .qad_arc_maptool import Qad_arc_maptool, Qad_arc_maptool_ModeEnum +from ..qad_arc import QadArc +from .qad_generic_cmd import QadCommandClass +from ..qad_msg import QadMsg +from ..qad_textwindow import QadInputTypeEnum, QadInputModeEnum +from .. import qad_utils +from .. import qad_layer +from ..qad_rubberband import createRubberBand +from ..qad_dim import QadDimStyles +from ..qad_multi_geom import getQadGeomAt, fromQgsGeomToQadGeom +from ..qad_geom_relations import getQadGeomClosestPart, getQadGeomBetween2Pts +from ..qad_variables import QadVariables + +# Classe che gestisce il comando PLINE +class QadPLINECommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadPLINECommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "PLINE") + + def getEnglishName(self): + return "PLINE" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runPLINECommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/pline.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_PLINE", "Creates a polyline by many methods.\n\nA polyline is a single object that is composed of line,\nand arc segments.") + + def __init__(self, plugIn, asToolForMPolygon = False): + QadCommandClass.__init__(self, plugIn) + self.polyline = QadPolyline() + self.firstVertex = None + + self.asToolForMPolygon = asToolForMPolygon + if self.asToolForMPolygon: + self.rubberBand = createRubberBand(self.plugIn.canvas, QgsWkbTypes.PolygonGeometry, False) + else: + self.rubberBand = createRubberBand(self.plugIn.canvas, QgsWkbTypes.LineGeometry) + + self.ArcPointMapTool = None + self.mode = "LINE" + # se questo flag = True il comando serve all'interno di un altro comando per disegnare una linea + # che non verrà salvata su un layer + self.virtualCmd = False + + + def __del__(self): + QadCommandClass.__del__(self) + if self.ArcPointMapTool is not None: + self.ArcPointMapTool.removeItems() + del self.ArcPointMapTool + + self.rubberBand.hide() + self.plugIn.canvas.scene().removeItem(self.rubberBand) + + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if (self.plugIn is not None): + if self.mode == "ARC": + if self.ArcPointMapTool is None: + self.ArcPointMapTool = Qad_arc_maptool(self.plugIn, self.asToolForMPolygon) # se True significa che è usato per disegnare un poligono + if self.virtualCmd == False: # se si vuole veramente salvare la polylinea in un layer + currLayer, errMsg = qad_layer.getCurrLayerEditable(self.plugIn.canvas, QgsWkbTypes.LineGeometry) + if currLayer is not None: + self.ArcPointMapTool.layer = currLayer + + return self.ArcPointMapTool + else: + if self.PointMapTool is None: + self.PointMapTool = Qad_pline_maptool(self.plugIn, self.asToolForMPolygon) # se True significa che è usato per disegnare un poligono + return self.PointMapTool + else: + return None + + + def setRubberBandColor(self, rubberBandBorderColor, rubberBandFillColor): + if rubberBandBorderColor is not None: + self.rubberBand.setBorderColor(rubberBandBorderColor) + if rubberBandFillColor is not None: + self.rubberBand.setFillColor(rubberBandFillColor) + + + def getLastSegmentAng(self): + if self.polyline.qty() == 0: + result = self.plugIn.lastSegmentAng + else: + result = self.polyline.getTanDirectionOnEndPt() + + return result + + + def getFirstPt(self): + if self.polyline.qty() == 0: + return self.firstVertex + else: + return self.polyline.getStartPt() + + + def getLastPt(self): + if self.polyline.qty() == 0: + return self.firstVertex + else: + return self.polyline.getEndPt() + + + # ============================================================================ + # WaitForArcMenu + # ============================================================================ + def WaitForArcMenu(self): + # l'opzione CEnter viene tradotta in italiano in "CEntro" nel contesto "WaitForArcMenu" + # l'opzione Undo viene tradotta in italiano in "ANNulla" nel contesto "WaitForArcMenu" + keyWords = QadMsg.translate("Command_PLINE", "Angle") + "/" + \ + QadMsg.translate("Command_PLINE", "CEnter", "WaitForArcMenu") + "/" + \ + QadMsg.translate("Command_PLINE", "Close") + "/" + \ + QadMsg.translate("Command_PLINE", "Direction") + "/" + \ + QadMsg.translate("Command_PLINE", "Line") + "/" + \ + QadMsg.translate("Command_PLINE", "Radius") + "/" + \ + QadMsg.translate("Command_PLINE", "Second point") + "/" + \ + QadMsg.translate("Command_PLINE", "Undo", "WaitForArcMenu") + englishKeyWords = "Angle" + "/" + "CEnter" + "/" + "Close" + "/" + \ + "Direction" + "/" + "Line" + "/" + "Radius" + "/" + \ + "Second point" + "/" + "Undo" + + prompt = QadMsg.translate("Command_PLINE", "Specify the final point of the arc (hold Ctrl to switch direction) or [{0}]: ").format(keyWords) + + self.arcStartPt = self.getLastPt() # ultimo vertice + self.arcTanOnStartPt = self.getLastSegmentAng() + + # Il segmento di arco é tangente al precedente segmento della polilinea + # uso il map tool per l'arco + self.mode = "ARC" + self.getPointMapTool().arcStartPt = self.arcStartPt + self.getPointMapTool().arcTanOnStartPt = self.arcTanOnStartPt + self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_PT_TAN_KNOWN_ASK_FOR_END_PT) + if self.asToolForMPolygon: + self.getPointMapTool().endVertex = self.polyline.getStartPt() + + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, QadInputModeEnum.NONE) + self.step = 101 + return + + # ============================================================================ + # WaitForLineMenu + # ============================================================================ + def WaitForLineMenu(self): + # l'opzione Undo viene tradotta in italiano in "ANnulla" nel contesto "WaitForLineMenu" + if self.polyline.qty() >= 2: + keyWords = QadMsg.translate("Command_PLINE", "Arc") + "/" + \ + QadMsg.translate("Command_PLINE", "Close") + "/" + \ + QadMsg.translate("Command_PLINE", "Length") + "/" + \ + QadMsg.translate("Command_PLINE", "Undo", "WaitForLineMenu") + "/" + \ + QadMsg.translate("Command_PLINE", "Trace") + englishKeyWords = "Arc" + "/" + "Close" + "/" + "Length" + "/" + "Undo"+ "/" + "Trace" + else: + keyWords = QadMsg.translate("Command_PLINE", "Arc") + "/" + \ + QadMsg.translate("Command_PLINE", "Length") + "/" + \ + QadMsg.translate("Command_PLINE", "Undo", "WaitForLineMenu") + "/" + \ + QadMsg.translate("Command_PLINE", "Trace") + englishKeyWords = "Arc" + "/" + "Length"+ "/" + "Undo" + "/" + "Trace" + + prompt = QadMsg.translate("Command_PLINE", "Specify next point or [{0}]: ").format(keyWords) + + self.step = 1 # MENU PRINCIPLE + + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, QadInputModeEnum.NONE) + + + # ============================================================================ + # waitForTracePt + # ============================================================================ + def waitForTracePt(self, msgMapTool, msg): + self.step = 3 + # imposto il map tool + self.getPointMapTool().setMode(Qad_pline_maptool_ModeEnum.ASK_FOR_TRACE_PT) + self.getPointMapTool().firstPt = self.getLastPt() # ultimo vertice + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_PLINE", "Select the object in the trace end point: ")) + + + # ============================================================================ + # addPointToPolyline + # ============================================================================ + def addPointToPolyline(self, pt): + if self.firstVertex is None: + self.firstVertex = QgsPointXY(pt) + self.plugIn.setLastPoint(pt) + self.getPointMapTool().setStartPoint(self.firstVertex) + if self.asToolForMPolygon: + self.getPointMapTool().endVertex = self.firstVertex + return + else: + self.addLinearObjToPolyline(QadLine().set(self.getLastPt(), pt)) + + + # ============================================================================ + # addLinearObjToPolyline + # ============================================================================ + def addLinearObjToPolyline(self, linearObj): + pts = linearObj.asPolyline() + tot = len(pts) + if tot > 0: + if self.rubberBand.numberOfVertices() > 0: + i = 1 + else: + i = 0 + tot = tot - 1 + while i < tot: + self.addPointToRubberBand(pts[i], False) + i = i + 1 + self.addPointToRubberBand(pts[-1], True) + + self.polyline.append(linearObj) + self.plugIn.setLastPoint(pts[-1]) + self.plugIn.setLastSegmentAng(self.getLastSegmentAng()) + self.getPointMapTool().setPolarAngOffset(self.plugIn.lastSegmentAng) + self.getPointMapTool().setStartPoint(pts[-1]) + self.getPointMapTool().setTmpGeometry(self.polyline.asGeom()) # per lo snap aggiungo questa geometria temporanea + + + # ============================================================================ + # removeLastLinearObjToPolyline + # ============================================================================ + def removeLastLinearObjToPolyline(self): + totLinearObjs = self.polyline.qty() + if totLinearObjs == 0: return + linearObj = self.polyline.getLinearObjectAt(-1) + lastPt = linearObj.getStartPt() + pts = linearObj.asPolyline() + tot = len(pts) + if totLinearObjs == 1: + i = 0 + else: + i = 1 + while i < tot: + self.rubberBand.removeLastPoint() + i = i + 1 + + self.polyline.remove(-1) # cancello ultima parte + self.plugIn.setLastPoint(lastPt) + self.plugIn.setLastSegmentAng(self.getLastSegmentAng()) + self.getPointMapTool().setTmpGeometry(self.polyline.asGeom()) # per lo snap aggiungo questa geometria temporanea + self.getPointMapTool().setPolarAngOffset(self.plugIn.lastSegmentAng) + self.getPointMapTool().setStartPoint(lastPt) + + + # ============================================================================ + # addPointToRubberBand + # ============================================================================ + def addPointToRubberBand(self, point, doUpdate = True): + numberOfVertices = self.rubberBand.numberOfVertices() + + if numberOfVertices == 2: + # per un baco non ancora capito: se la linea ha solo 2 vertici e + # hanno la stessa x o y (linea orizzontale o verticale) + # la linea non viene disegnata perciò sposto un pochino la x o la y + adjustedPoint = qad_utils.getAdjustedRubberBandVertex(self.rubberBand.getPoint(0, 0), point) + self.rubberBand.addPoint(adjustedPoint, doUpdate) + else: + self.rubberBand.addPoint(point, doUpdate) + + + # ============================================================================ + # removeLastPointToRubberBand + # ============================================================================ + def removeLastPointToRubberBand(self): + self.rubberBand.removeLastPoint() + + + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + if self.virtualCmd == False: # se si vuole veramente salvare la polylinea in un layer + currLayer, errMsg = qad_layer.getCurrLayerEditable(self.plugIn.canvas, QgsWkbTypes.LineGeometry) + if currLayer is None: + self.showErr(errMsg) + return True # fine comando + + # RICHIESTA PRIMO PUNTO + if self.step == 0: # inizio del comando + self.getPointMapTool().setMode(Qad_pline_maptool_ModeEnum.DRAW_LINE) # imposto la linea elastica + # si appresta ad attendere un punto o enter + # msg, inputType, default, keyWords, nessun controllo + self.waitForPoint(QadMsg.translate("Command_PLINE", "Specify start point: ")) + self.step = 1 + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA PUNTO OPPURE MENU PRINCIPALE + elif self.step == 1: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + if self.virtualCmd == False: # se si vuole veramente salvare la polylinea in un layer + geom = self.polyline.asGeom(currLayer.wkbType()) + if geom is not None: + qad_layer.addGeomToLayer(self.plugIn, currLayer, self.mapToLayerCoordinates(currLayer, geom)) + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if value is None: + if self.firstVertex is None: + if self.plugIn.lastPoint is not None: + value = self.plugIn.lastPoint + else: + return True # fine comando + else: + if self.virtualCmd == False: # se si vuole veramente salvare la polylinea in un layer + geom = self.polyline.asGeom(currLayer.wkbType()) + if geom is not None: + qad_layer.addGeomToLayer(self.plugIn, currLayer, self.mapToLayerCoordinates(currLayer, geom)) + return True # fine comando + + if type(value) == unicode: + if value == QadMsg.translate("Command_PLINE", "Arc") or value == "Arc": + self.WaitForArcMenu() + return False + elif value == QadMsg.translate("Command_PLINE", "Length") or value == "Length": + # si appresta ad attendere un punto o un numero reale + # msg, inputType, default, keyWords, valori positivi + # "Specificare lunghezza della linea: " + self.waitFor(QadMsg.translate("Command_PLINE", "Specify line length: "), \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT, \ + None, "", \ + QadInputModeEnum.NOT_NULL | QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) + self.step = 2 + return False + # l'opzione Undo viene tradotta in italiano in "ANnulla" nel contesto "WaitForLineMenu" + elif value == QadMsg.translate("Command_PLINE", "Undo", "WaitForLineMenu") or value == "Undo": + if self.polyline.qty() > 0: + self.getPointMapTool().clear() + self.getPointMapTool().setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.removeLastLinearObjToPolyline() + else: + self.showMsg(QadMsg.translate("QAD", "\nThe command has been canceled.")) + elif value == QadMsg.translate("Command_PLINE", "Close") or value == "Close": + self.polyline.setClose() + if self.virtualCmd == False: # se si vuole veramente salvare la polylinea in un layer + geom = self.polyline.asGeom(currLayer.wkbType()) + if geom is not None: + qad_layer.addGeomToLayer(self.plugIn, currLayer, self.mapToLayerCoordinates(currLayer, geom)) + return True # fine comando + elif value == QadMsg.translate("Command_PLINE", "Trace") or value == "Trace": + self.waitForTracePt(msgMapTool, msg) + return False # continua + + elif type(value) == QgsPointXY: + self.addPointToPolyline(value) + + self.WaitForLineMenu() + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA "Lunghezza" (da step = 1) + elif self.step == 2: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == QgsPointXY: + dist = qad_utils.getDistance(self.getLastPt(), value) + else: + dist = value + + newPt = qad_utils.getPolarPointByPtAngle(self.getLastPt(), self.getLastSegmentAng(), dist) + self.addPointToPolyline(newPt) + + self.WaitForLineMenu() + + self.step = 1 # torno al MENU PRINCIPLE + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA "Selezionare l'oggetto nel punto finale di ricalco: " (da step = 1) + elif self.step == 3: + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == QgsPointXY: + geom = None + layer = None + if self.getPointMapTool().entity.isInitialized(): # il punto arriva dal mouse + entSelected = True + layer = self.getPointMapTool().entity.layer + geom = self.getPointMapTool().entity.getGeometry() + else: # il punto arriva da tastiera + # cerco se ci sono entità nel punto indicato considerando + # solo layer lineari o poligono che non appartengano a quote + layerList = [] + for layer in qad_utils.getVisibleVectorLayers(self.plugIn.canvas): # Tutti i layer vettoriali visibili + if layer.geometryType() == QgsWkbTypes.LineGeometry or layer.geometryType() == QgsWkbTypes.PolygonGeometry: + if len(QadDimStyles.getDimListByLayer(layer)) == 0: + layerList.append(layer) + + result = qad_utils.getEntSel(self.getPointMapTool().toCanvasCoordinates(value), + self.getPointMapTool(), \ + QadVariables.get(QadMsg.translate("Environment variables", "PICKBOX")), \ + layerList) + if result is not None: + feature = result[0] + layer = result[1] + geom = feature.getGeometry() + + if geom is not None and layer is not None: + qadGeom = fromQgsGeomToQadGeom(geom, layer.crs()) + # la funzione ritorna una lista con + # ( + # + # + # + # + # <"a sinistra di" se il punto é alla sinista della parte (< 0 -> sinistra, > 0 -> destra) + dummy = getQadGeomClosestPart(qadGeom, value) + qadGeom = getQadGeomAt(qadGeom, dummy[2], dummy[3]) + subGeom = getQadGeomBetween2Pts(qadGeom, self.getLastPt(), dummy[1]) + if subGeom is not None: + pl = QadPolyline() + pl.fromPolyline(subGeom.asPolyline()) + tot = pl.qty() + i = 0 + while i < tot: + self.addLinearObjToPolyline(pl.getLinearObjectAt(i)) + i = i + 1 + + self.WaitForLineMenu() + self.getPointMapTool().setMode(Qad_pline_maptool_ModeEnum.DRAW_LINE) + self.getPointMapTool().setTmpGeometry(self.polyline.asGeom()) # per lo snap aggiungo questa geometria temporanea + self.getPointMapTool().setStartPoint(self.getLastPt()) + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA "Specificare punto finale dell'arco o [Angolo/CEntro/CHiudi/Direzione/LInea/Raggio/Secondo punto/ANNulla]: " (da step = 1) + elif self.step == 101: # dopo aver atteso un punto o una parola chiave si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + if self.virtualCmd == False: # se si vuole veramente salvare la polylinea in un layer + geom = self.polyline.asGeom(currLayer.wkbType()) + if geom is not None: + qad_layer.addGeomToLayer(self.plugIn, currLayer, self.mapToLayerCoordinates(currLayer, geom)) + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + ctrlPressed = self.getPointMapTool().ctrlKey + else: # il punto arriva come parametro della funzione + value = msg + ctrlPressed = False + + if value is None: + if self.virtualCmd == False: # se si vuole veramente salvare la polylinea in un layer + geom = self.polyline.asGeom(currLayer.wkbType()) + if geom is not None: + qad_layer.addGeomToLayer(self.plugIn, currLayer, self.mapToLayerCoordinates(currLayer, geom)) + return True # fine comando + + if type(value) == unicode: + if value == QadMsg.translate("Command_PLINE", "Angle") or value == "Angle": + self.arcStartPt = self.getLastPt() + + # imposto il map tool + self.getPointMapTool().arcStartPt = self.arcStartPt + self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_PT_KNOWN_ASK_FOR_ANGLE) + # si appresta ad attendere un punto o un numero reale + # msg, inputType, default, keyWords, isNullable + self.waitFor(QadMsg.translate("Command_PLINE", "Specify the included angle: "), \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.ANGLE, \ + None, "", \ + QadInputModeEnum.NOT_NULL | QadInputModeEnum.NOT_ZERO) + self.step = 102 + # l'opzione CEnter viene tradotta in italiano in "CEntro" nel contesto "WaitForArcMenu" + elif value == QadMsg.translate("Command_PLINE", "CEnter", "WaitForArcMenu") or value == "CEnter": + self.arcStartPt = self.getLastPt() + + # imposto il map tool + self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_CENTER_PT) + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_PLINE", "Specify the center of the arc: ")) + self.step = 108 + elif value == QadMsg.translate("Command_PLINE", "Close") or value == "Close": + arc = QadArc() + + if arc.fromStartEndPtsTan(self.arcStartPt, self.getFirstPt(), self.arcTanOnStartPt) == True: + self.addLinearObjToPolyline(arc) + + if self.virtualCmd == False: # se si vuole veramente salvare la polylinea in un layer + geom = self.polyline.asGeom(currLayer.wkbType()) + if geom is not None: + qad_layer.addGeomToLayer(self.plugIn, currLayer, self.mapToLayerCoordinates(currLayer, geom)) + + return True # fine comando + elif value == QadMsg.translate("Command_PLINE", "Direction") or value == "Direction": + self.arcStartPt = self.getLastPt() + + # imposto il map tool + self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_PT_KNOWN_ASK_FOR_SECOND_PT) + # si appresta ad attendere un punto o un numero reale + # msg, inputType, default, keyWords, isNullable + self.waitFor(QadMsg.translate("Command_PLINE", "Specify the tangent direction for the start point of the arc: "), \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.ANGLE, \ + None, "", QadInputModeEnum.NOT_NULL) + self.step = 112 + elif value == QadMsg.translate("Command_PLINE", "Line") or value == "Line": + self.mode = "LINE" + self.getPointMapTool().refreshSnapType() # riagggiorno lo snapType che può essere variato dal maptool dell'arco + self.getPointMapTool().setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.getPointMapTool().setStartPoint(self.getLastPt()) + self.WaitForLineMenu() + elif value == QadMsg.translate("Command_PLINE", "Radius") or value == "Radius": + self.arcStartPt = self.getLastPt() + + # imposto il map tool + self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_PT_ANGLE_KNOWN_ASK_FOR_RADIUS) + # si appresta ad attendere un punto o un numero reale + # msg, inputType, default, keyWords, valori positivi + self.waitFor(QadMsg.translate("Command_PLINE", "Specify the radius of the arc: "), \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT, \ + None, "", \ + QadInputModeEnum.NOT_NULL | QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) + self.step = 114 + elif value == QadMsg.translate("Command_PLINE", "Second point") or value == "Second point": + self.arcStartPt = self.getLastPt() + + # imposto il map tool + self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_PT_KNOWN_ASK_FOR_SECOND_PT) + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_PLINE", "Specify second point of the arc: ")) + self.step = 119 + # l'opzione Undo viene tradotta in italiano in "ANNulla" nel contesto "WaitForArcMenu" + elif value == QadMsg.translate("Command_PLINE", "Undo", "WaitForArcMenu") or value == "Undo": + if self.polyline.qty() > 0: + self.getPointMapTool().clear() + self.getPointMapTool().setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.removeLastLinearObjToPolyline() + else: + self.showMsg(QadMsg.translate("QAD", "\nThe command has been canceled.")) + self.WaitForArcMenu() + elif type(value) == QgsPointXY: # é stato inserito il punto finale dell'arco + arc = QadArc() + if arc.fromStartEndPtsTan(self.arcStartPt, value, self.arcTanOnStartPt) == True: + if ctrlPressed: # inverto angolo iniziale-finale + arc.inverseAngles() + self.addLinearObjToPolyline(arc) + + self.WaitForArcMenu() + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA "Specificare angolo inscritto: " (da step = 101) + elif self.step == 102: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == QgsPointXY: + self.arcAngle = qad_utils.getAngleBy2Pts(self.arcStartPt, value) + else: + self.arcAngle = value + + # imposto il map tool + self.getPointMapTool().arcAngle = self.arcAngle + self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_PT_ANGLE_KNOWN_ASK_FOR_END_PT) + + # l'opzione CEnter viene tradotta in italiano in "Centro" nel contesto "START_PT_ANGLE_KNOWN_ASK_FOR_END_PT" + keyWords = QadMsg.translate("Command_PLINE", "CEnter", "START_PT_ANGLE_KNOWN_ASK_FOR_END_PT") + "/" + \ + QadMsg.translate("Command_PLINE", "Radius") + englishKeyWords = "CEnter" + "/" + "Radius" + prompt = QadMsg.translate("Command_PLINE", "Specify the final point of the arc (hold Ctrl to switch direction) or [{0}]: ").format(keyWords) + + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o una parola chiave + # msg, inputType, default, keyWords, isNullable + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, QadInputModeEnum.NOT_NULL) + self.step = 103 + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA "Specificare punto finale dell'arco o [Centro/Raggio]: : " (da step = 102) + elif self.step == 103: # dopo aver atteso un punto o una parola chiave si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + ctrlPressed = self.getPointMapTool().ctrlKey + else: # il punto arriva come parametro della funzione + value = msg + ctrlPressed = False + + if type(value) == unicode: + # l'opzione CEnter viene tradotta in italiano in "Centro" nel contesto "START_PT_ANGLE_KNOWN_ASK_FOR_END_PT" + if value == QadMsg.translate("Command_PLINE", "CEnter", "START_PT_ANGLE_KNOWN_ASK_FOR_END_PT") or value == "CEnter": + # imposto il map tool + self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_PT_ANGLE_KNOWN_ASK_FOR_CENTER_PT) + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_PLINE", "Specify the center of the arc (hold Ctrl to switch direction): ")) + self.step = 104 + elif value == QadMsg.translate("Command_PLINE", "Radius") or value == "Radius": + # imposto il map tool + self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_PT_ANGLE_KNOWN_ASK_FOR_RADIUS) + # si appresta ad attendere un punto o un numero reale + # msg, inputType, default, keyWords, valori positivi + self.waitFor(QadMsg.translate("Command_PLINE", "Specify the radius of the arc: "), \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT, \ + None, "", \ + QadInputModeEnum.NOT_NULL | QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) + self.step = 105 + elif type(value) == QgsPointXY: # é stato inserito il punto finale dell'arco + arc = QadArc() + if arc.fromStartEndPtsAngle(self.arcStartPt, value, self.arcAngle) == True: + if ctrlPressed: # inverto angolo iniziale-finale + arc.inverseAngles() + self.addLinearObjToPolyline(arc) + + self.WaitForArcMenu() + return False + + # l'opzione CEnter viene tradotta in italiano in "Centro" nel contesto "START_PT_ANGLE_KNOWN_ASK_FOR_END_PT" + keyWords = QadMsg.translate("Command_PLINE", "CEnter", "START_PT_ANGLE_KNOWN_ASK_FOR_END_PT") + "/" + \ + QadMsg.translate("Command_PLINE", "Radius") + prompt = QadMsg.translate("Command_PLINE", "Specify the final point of the arc (hold Ctrl to switch direction) or [{0}]: ").format(keyWords) + + englishKeyWords = "CEnter" + "/" + "Radius" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o una parola chiave + # msg, inputType, default, keyWords, isNullable + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, QadInputModeEnum.NOT_NULL) + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA CENTRO DELL'ARCO (da step = 103) + elif self.step == 104: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + ctrlPressed = self.getPointMapTool().ctrlKey + else: # il punto arriva come parametro della funzione + value = msg + ctrlPressed = False + + arc = QadArc() + if arc.fromStartCenterPtsAngle(self.arcStartPt, value, self.arcAngle) == True: + if ctrlPressed: # inverto angolo iniziale-finale + arc.inverseAngles() + self.addLinearObjToPolyline(arc) + + self.WaitForArcMenu() + return False + + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_PLINE", "Specify the center of the arc: ")) + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA RAGGIO (da step = 103) + elif self.step == 105: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == QgsPointXY: + self.arcStartPtForRadius = value + + # imposto il map tool + self.getPointMapTool().arcStartPtForRadius = self.arcStartPtForRadius + self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_PT_ANGLE_KNOWN_ASK_FOR_SECONDPTRADIUS) + + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_PLINE", "Specify second point: ")) + self.step = 106 + else: + self.arcRadius = value + self.plugIn.setLastRadius(self.arcRadius) + + # imposto il map tool + self.getPointMapTool().arcRadius = self.arcRadius + self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_PT_ANGLE_RADIUS_KNOWN_ASK_FOR_CHORDDIRECTION) + # si appresta ad attendere un punto o un numero reale + # msg, inputType, default, keyWords, isNullable + msg = QadMsg.translate("Command_PLINE", "Specify the direction for the chord of the arc (hold Ctrl to switch direction) <{0}>: ") + self.waitFor(msg.format(str(self.getLastSegmentAng())), \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.ANGLE, \ + None, "", QadInputModeEnum.NOT_NULL) + self.step = 107 + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA SECONDO PUNTO DEL RAGGIO (da step = 105) + elif self.step == 106: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + self.arcRadius = qad_utils.getDistance(self.arcStartPtForRadius, value) + self.plugIn.setLastRadius(self.arcRadius) + + # imposto il map tool + self.getPointMapTool().arcRadius = self.arcRadius + self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_PT_ANGLE_RADIUS_KNOWN_ASK_FOR_CHORDDIRECTION) + # si appresta ad attendere un punto o un numero reale + # msg, inputType, default, keyWords, isNullable + msg = QadMsg.translate("Command_PLINE", "Specify the direction for the chord of the arc (hold Ctrl to switch direction) <{0}>: ") + self.waitFor(msg.format(str(self.getLastSegmentAng())), \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.ANGLE, \ + None, "", QadInputModeEnum.NOT_NULL) + self.step = 107 + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DIREZIONE DELLA CORDA DELL'ARCO (da step = 106 e 107) + elif self.step == 107: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + ctrlPressed = self.getPointMapTool().ctrlKey + else: # il punto arriva come parametro della funzione + value = msg + ctrlPressed = False + + if type(value) == QgsPointXY: + self.arcChordDirection = qad_utils.getAngleBy2Pts(self.arcStartPt, value) + else: + self.arcChordDirection = value + + arc = QadArc() + if arc.fromStartPtAngleRadiusChordDirection(self.arcStartPt, self.arcAngle, \ + self.arcRadius, self.arcChordDirection) == True: + if ctrlPressed: # inverto angolo iniziale-finale + arc.inverseAngles() + self.addLinearObjToPolyline(arc) + + self.WaitForArcMenu() + return False + + # imposto il map tool + self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_PT_ANGLE_RADIUS_KNOWN_ASK_FOR_CHORDDIRECTION) + # si appresta ad attendere un punto o un numero reale + # msg, inputType, default, keyWords, isNullable + msg = QadMsg.translate("Command_PLINE", "Specify the direction for the chord of the arc (hold Ctrl to switch direction) <{0}>: ") + self.waitFor(msg.format(str(self.getLastSegmentAng())), \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.ANGLE, \ + None, "", QadInputModeEnum.NOT_NULL) + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA CENTRO DELL'ARCO (da step = 101) + elif self.step == 108: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + self.arcCenterPt = value + + # imposto il map tool + self.getPointMapTool().arcCenterPt = self.arcCenterPt + self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_CENTER_PT_KNOWN_ASK_FOR_END_PT) + + keyWords = QadMsg.translate("Command_PLINE", "Angle") + "/" + \ + QadMsg.translate("Command_PLINE", "chord Length") + prompt = QadMsg.translate("Command_PLINE", "Specify the final point of the arc (hold Ctrl to switch direction) or [{0}]: ").format(keyWords) + + englishKeyWords = "Angle" + "/" + "chord Length" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o una parola chiave + # msg, inputType, default, keyWords, isNullable + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, QadInputModeEnum.NOT_NULL) + self.step = 109 + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA "Specificare punto finale dell'arco o [Angolo/Lunghezza corda]: " (da step = 108) + elif self.step == 109: # dopo aver atteso un punto o una parola chiave si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + ctrlPressed = self.getPointMapTool().ctrlKey + else: # il punto arriva come parametro della funzione + value = msg + ctrlPressed = False + + if type(value) == unicode: + if value == QadMsg.translate("Command_PLINE", "Angle") or value == "Angle": + # imposto il map tool + self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_CENTER_PT_KNOWN_ASK_FOR_ANGLE) + # si appresta ad attendere un punto o un numero reale + # msg, inputType, default, keyWords, valori != 0 + self.waitFor(QadMsg.translate("Command_PLINE", "Specify the included angle (hold Ctrl to switch direction): "), \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.ANGLE, \ + None, "", \ + QadInputModeEnum.NOT_NULL | QadInputModeEnum.NOT_ZERO) + self.step = 110 + return False + elif value == QadMsg.translate("Command_PLINE", "chord Length") or value == "chord Length": + # imposto il map tool + self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_CENTER_PT_KNOWN_ASK_FOR_CHORD) + # si appresta ad attendere un punto o un numero reale + # msg, inputType, default, keyWords, valori positivi + self.waitFor(QadMsg.translate("Command_PLINE", "Specify the chord length (hold Ctrl to switch direction): "), \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT, \ + None, "", \ + QadInputModeEnum.NOT_NULL | QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) + self.step = 111 + return False + elif type(value) == QgsPointXY: # se é stato inserito il punto finale dell'arco + self.arcEndPt = value + + arc = QadArc() + if arc.fromStartCenterEndPts(self.arcStartPt, self.arcCenterPt, self.arcEndPt) == True: + if ctrlPressed: # inverto angolo iniziale-finale + arc.inverseAngles() + self.addLinearObjToPolyline(arc) + + self.WaitForArcMenu() + return False + + keyWords = QadMsg.translate("Command_PLINE", "Angle") + "/" + \ + QadMsg.translate("Command_PLINE", "chord Length") + prompt = QadMsg.translate("Command_PLINE", "Specify the final point of the arc (hold Ctrl to switch direction) or [{0}]: ").format(keyWords) + + englishKeyWords = "Angle" + "/" + "chord Length" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o una parola chiave + # msg, inputType, default, keyWords, isNullable + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, QadInputModeEnum.NOT_NULL) + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA "Specificare angolo inscritto: " (da step = 109) + elif self.step == 110: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + ctrlPressed = self.getPointMapTool().ctrlKey + else: # il punto arriva come parametro della funzione + value = msg + ctrlPressed = False + + if type(value) == QgsPointXY: + self.arcAngle = qad_utils.getAngleBy2Pts(self.arcCenterPt, value) + else: + self.arcAngle = value + + arc = QadArc() + if arc.fromStartCenterPtsAngle(self.arcStartPt, self.arcCenterPt, self.arcAngle) == True: + if ctrlPressed: # inverto angolo iniziale-finale + arc.inverseAngles() + self.addLinearObjToPolyline(arc) + + self.WaitForArcMenu() + return False + + # si appresta ad attendere un punto o un numero reale + # msg, inputType, default, keyWords, isNullable + self.waitFor(QadMsg.translate("Command_PLINE", "Specify the included angle (hold Ctrl to switch direction): "), \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.ANGLE, \ + None, "", \ + QadInputModeEnum.NOT_NULL | QadInputModeEnum.NOT_ZERO) + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA "Specificare lunghezza della corda: " (da step = 109) + elif self.step == 111: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + ctrlPressed = self.getPointMapTool().ctrlKey + else: # il punto arriva come parametro della funzione + value = msg + ctrlPressed = False + + if type(value) == QgsPointXY: + self.arcChord = qad_utils.getDistance(self.arcStartPt, value) + else: + self.arcChord = value + + arc = QadArc() + if arc.fromStartCenterPtsChord(self.arcStartPt, self.arcCenterPt, self.arcChord) == True: + if ctrlPressed: # inverto angolo iniziale-finale + arc.inverseAngles() + self.addLinearObjToPolyline(arc) + + self.WaitForArcMenu() + return False + + # si appresta ad attendere un punto o un numero reale + # msg, inputType, default, keyWords, valori positivi + self.waitFor(QadMsg.translate("Command_PLINE", "Specify the chord length (hold Ctrl to switch direction): "), \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT, \ + None, "", \ + QadInputModeEnum.NOT_NULL | QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA "Specificare direzione tangente per il punto iniziale dell'arco: " (da step = 101) + elif self.step == 112: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == QgsPointXY: + self.arcTanOnStartPt = qad_utils.getAngleBy2Pts(self.arcStartPt, value) + else: + self.arcTanOnStartPt = value + + # imposto il map tool + self.getPointMapTool().arcTanOnStartPt = self.arcTanOnStartPt + self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_PT_TAN_KNOWN_ASK_FOR_END_PT) + + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_PLINE", "Specify the final point of the arc (hold Ctrl to switch direction): ")) + self.step = 113 + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA PUNTO FINALE DELL'ARCO (da step = 112) + elif self.step == 113: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + ctrlPressed = self.getPointMapTool().ctrlKey + else: # il punto arriva come parametro della funzione + value = msg + ctrlPressed = False + + arc = QadArc() + if arc.fromStartEndPtsTan(self.arcStartPt, value, self.arcTanOnStartPt) == True: + if ctrlPressed: # inverto angolo iniziale-finale + arc.inverseAngles() + self.addLinearObjToPolyline(arc) + + self.WaitForArcMenu() + return False + + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_PLINE", "Specify the final point of the arc (hold Ctrl to switch direction): ")) + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA RAGGIO (da step = 101) + elif self.step == 114: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == QgsPointXY: + self.arcStartPtForRadius = value + + # imposto il map tool + self.getPointMapTool().arcStartPtForRadius = self.arcStartPtForRadius + self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_PT_ANGLE_KNOWN_ASK_FOR_SECONDPTRADIUS) + + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_PLINE", "Specify second point: ")) + self.step = 115 + else: + self.arcRadius = value + self.plugIn.setLastRadius(self.arcRadius) + + # imposto il map tool + self.getPointMapTool().arcRadius = self.arcRadius + self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_PT_RADIUS_KNOWN_ASK_FOR_END_PT) + + keyWords = QadMsg.translate("Command_PLINE", "Angle") + prompt = QadMsg.translate("Command_PLINE", "Specify the final point of the arc (hold Ctrl to switch direction) or [{0}]: ").format(keyWords) + englishKeyWords = "Angle" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o un numero reale + # msg, inputType, default, keyWords, isNullable + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + None, keyWords, QadInputModeEnum.NOT_NULL) + self.step = 116 + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA SECONDO PUNTO DEL RAGGIO (da step = 114) + elif self.step == 115: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + self.arcRadius = qad_utils.getDistance(self.arcStartPtForRadius, value) + self.plugIn.setLastRadius(self.arcRadius) + + # imposto il map tool + self.getPointMapTool().arcRadius = self.arcRadius + self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_PT_RADIUS_KNOWN_ASK_FOR_END_PT) + + keyWords = QadMsg.translate("Command_PLINE", "Angle") + prompt = QadMsg.translate("Command_PLINE", "Specify the final point of the arc (hold Ctrl to switch direction) or [{0}]: ").format(keyWords) + englishKeyWords = "Angle" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o un numero reale + # msg, inputType, default, keyWords, isNullable + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + None, keyWords, QadInputModeEnum.NOT_NULL) + self.step = 116 + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA "Specificare punto finale dell'arco o [Angolo]: " (da step = 114 o 115) + elif self.step == 116: # dopo aver atteso un punto o una parola chiave si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + ctrlPressed = self.getPointMapTool().ctrlKey + else: # il punto arriva come parametro della funzione + value = msg + ctrlPressed = False + + if type(value) == unicode: + if value == QadMsg.translate("Command_PLINE", "Angle") or value == "Angle": + # imposto il map tool + self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_PT_KNOWN_ASK_FOR_ANGLE) + # si appresta ad attendere un punto o un numero reale + # msg, inputType, default, keyWords, isNullable + self.waitFor(QadMsg.translate("Command_PLINE", "Specify the included angle: "), \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.ANGLE, \ + None, "", \ + QadInputModeEnum.NOT_NULL | QadInputModeEnum.NOT_ZERO) + self.step = 117 + elif type(value) == QgsPointXY: # é stato inserito il punto finale dell'arco + arc = QadArc() + if arc.fromStartEndPtsRadius(self.arcStartPt, value, self.arcRadius) == True: + if ctrlPressed: # inverto angolo iniziale-finale + arc.inverseAngles() + self.addLinearObjToPolyline(arc) + + self.WaitForArcMenu() + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA "Specificare angolo inscritto: " (da step = 116) + elif self.step == 117: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == QgsPointXY: + self.arcAngle = qad_utils.getAngleBy2Pts(self.arcStartPt, value) + else: + self.arcAngle = value + + # imposto il map tool + self.getPointMapTool().arcAngle = self.arcAngle + self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_PT_ANGLE_RADIUS_KNOWN_ASK_FOR_CHORDDIRECTION) + # si appresta ad attendere un punto o un numero reale + # msg, inputType, default, keyWords, isNullable + msg = QadMsg.translate("Command_PLINE", "Specify the direction for the chord of the arc (hold Ctrl to switch direction) <{0}>: ") + self.waitFor(msg.format(str(self.getLastSegmentAng())), \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.ANGLE, \ + None, "", QadInputModeEnum.NOT_NULL) + self.step = 118 + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DIREZIONE DELLA CORDA DELL'ARCO (da step = 117) + elif self.step == 118: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + ctrlPressed = self.getPointMapTool().ctrlKey + else: # il punto arriva come parametro della funzione + value = msg + ctrlPressed = False + + if type(value) == QgsPointXY: + self.arcChordDirection = qad_utils.getAngleBy2Pts(self.arcStartPt, value) + else: + self.arcChordDirection = value + + arc = QadArc() + if arc.fromStartPtAngleRadiusChordDirection(self.arcStartPt, self.arcAngle, \ + self.arcRadius, self.arcChordDirection) == True: + if ctrlPressed: # inverto angolo iniziale-finale + arc.inverseAngles() + self.addLinearObjToPolyline(arc) + + self.WaitForArcMenu() + return False + + # imposto il map tool + self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_PT_ANGLE_RADIUS_KNOWN_ASK_FOR_CHORDDIRECTION) + # si appresta ad attendere un punto o un numero reale + # msg, inputType, default, keyWords, isNullable + msg = QadMsg.translate("Command_PLINE", "Specify the direction for the chord of the arc (hold Ctrl to switch direction) <{0}>: ") + self.waitFor(msg.format(str(self.getLastSegmentAng())), \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.ANGLE, \ + None, "", QadInputModeEnum.NOT_NULL) + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA SECONDO PUNTO (da step = 101) + elif self.step == 119: # dopo aver atteso un punto o una parola chiave si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + self.arcSecondPt = value + # imposto il map tool + self.getPointMapTool().arcSecondPt = self.arcSecondPt + self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_SECOND_PT_KNOWN_ASK_FOR_END_PT) + + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_PLINE", "Specify the final point of the arc: ")) + self.step = 120 + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA PUNTO FINALE DELL'ARCO (da step = 119) + elif self.step == 120: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + self.arcEndPt = value + + arc = QadArc() + if arc.fromStartSecondEndPts(self.arcStartPt, self.arcSecondPt, self.arcEndPt) == True: + self.addLinearObjToPolyline(arc) + + self.WaitForArcMenu() + return False + + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_PLINE", "Specify the final point of the arc: ")) + return False \ No newline at end of file diff --git a/cmd/qad_pline_maptool.py b/cmd/qad_pline_maptool.py new file mode 100644 index 00000000..b323a840 --- /dev/null +++ b/cmd/qad_pline_maptool.py @@ -0,0 +1,159 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + classe per gestire il map tool in ambito del comando pline + + ------------------- + begin : 2016-04-07 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from qgis.PyQt.QtCore import QSettings + + +from .. import qad_utils +from ..qad_snapper import QadSnapTypeEnum +from ..qad_getpoint import QadGetPoint, QadGetPointSelectionModeEnum, QadGetPointDrawModeEnum +from ..qad_rubberband import QadRubberBand +from ..qad_multi_geom import getQadGeomAt +from ..qad_geom_relations import getQadGeomClosestPart, getQadGeomBetween2Pts + +# =============================================================================== +# Qad_pline_maptool_ModeEnum class. +# =============================================================================== +class Qad_pline_maptool_ModeEnum(): + # non si richiede niente + NONE = 0 + # si richiede il punto finale per ricalcare un oggetto esistente + ASK_FOR_TRACE_PT = 1 + # si deve tracciare una linea + DRAW_LINE = 2 + + +# =============================================================================== +# Qad_pline_maptool class +# =============================================================================== +class Qad_pline_maptool(QadGetPoint): + + def __init__(self, plugIn, asToolForMPolygon = False): + QadGetPoint.__init__(self, plugIn) + + self.firstPt = None + self.mode = None + + self.asToolForMPolygon = asToolForMPolygon # se True significa che è usato per disegnare un poligono + if self.asToolForMPolygon: + self.__polygonRubberBand = QadRubberBand(self.plugIn.canvas, True) + self.endVertex = None # punta al vertice iniziale e finale del poligono di QadPLINECommandClass + + self.__polylineTraceRubberBand = QadRubberBand(self.plugIn.canvas, True) # da usare in trace di un oggetto esistente + settings = QSettings() + width = int(settings.value("/qgis/digitizing/line_width", 1)) + self.__polylineTraceRubberBand.setWidth(width * 2) + + def hidePointMapToolMarkers(self): + QadGetPoint.hidePointMapToolMarkers(self) + if self.asToolForMPolygon: + self.__polygonRubberBand.hide() + self.__polylineTraceRubberBand.hide() + + def showPointMapToolMarkers(self): + QadGetPoint.showPointMapToolMarkers(self) + if self.asToolForMPolygon: + self.__polygonRubberBand.show() + self.__polylineTraceRubberBand.show() + + def clear(self): + QadGetPoint.clear(self) + if self.asToolForMPolygon: + self.__polygonRubberBand.reset() + self.__polylineTraceRubberBand.reset() + self.mode = None + + def canvasMoveEvent(self, event): + QadGetPoint.canvasMoveEvent(self, event) + + if self.asToolForMPolygon == True: # se True significa che è usato per disegnare un poligono + self.__polygonRubberBand.reset() + self.__polylineTraceRubberBand.reset() + + startPoint = self.getStartPoint() + if startPoint is None: return + + points = None + + # si richiede il punto finale per ricalcare un oggetto esistente + if self.mode == Qad_pline_maptool_ModeEnum.ASK_FOR_TRACE_PT: + if self.tmpEntity.isInitialized(): + qadGeom = self.tmpEntity.getQadGeom() + dummy = getQadGeomClosestPart(qadGeom, self.tmpPoint) + qadGeom = getQadGeomAt(qadGeom, dummy[2], dummy[3]) + subGeom = getQadGeomBetween2Pts(qadGeom, startPoint, dummy[1]) + if subGeom is not None: + points = subGeom.asPolyline() + else: + points = [startPoint, self.tmpPoint] + + if self.mode == Qad_pline_maptool_ModeEnum.ASK_FOR_TRACE_PT: + if (points is not None): self.__polylineTraceRubberBand.setLine(points) + + # caso di poligono + if self.asToolForMPolygon: + if (points is not None) and (self.endVertex is not None) and (startPoint != self.endVertex): + points.insert(0, self.endVertex) + self.__polygonRubberBand.setPolygon(points) + + del startPoint + + + def activate(self): + QadGetPoint.activate(self) + if self.asToolForMPolygon: + self.__polygonRubberBand.show() + self.__polylineTraceRubberBand.show() + + def deactivate(self): + try: # necessario perché se si chiude QGIS parte questo evento nonostante non ci sia più l'oggetto maptool ! + QadGetPoint.deactivate(self) + if self.asToolForMPolygon: + self.__polygonRubberBand.hide() + self.__polylineTraceRubberBand.hide() + except: + pass + + def setMode(self, mode): + self.mode = mode + + # si richiede il punto finale per ricalcare un oggetto esistente + if self.mode == Qad_pline_maptool_ModeEnum.ASK_FOR_TRACE_PT: + self.checkPointLayer = False # scarto la selezione di punti + self.checkLineLayer = True + self.checkPolygonLayer = True + self.onlyEditableLayers = False + self.forceSnapTypeOnce(QadSnapTypeEnum.END) + + self.setSelectionMode(QadGetPointSelectionModeEnum.ENTITY_SELECTION_DYNAMIC) + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + # non si richiede niente + elif self.mode == Qad_pline_maptool_ModeEnum.NONE: + self.setSelectionMode(QadGetPointSelectionModeEnum.NONE) + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + # si deve tracciare una linea + elif self.mode == Qad_pline_maptool_ModeEnum.DRAW_LINE: + self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) # imposto la linea elastica diff --git a/qad_polygon_cmd.py b/cmd/qad_polygon_cmd.py similarity index 80% rename from qad_polygon_cmd.py rename to cmd/qad_polygon_cmd.py index ca305636..0fa24943 100644 --- a/qad_polygon_cmd.py +++ b/cmd/qad_polygon_cmd.py @@ -1,428 +1,428 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - comando POLYGON per disegnare un poligono regolare - - ------------------- - begin : 2014-11-17 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -from qad_polygon_maptool import * -from qad_generic_cmd import QadCommandClass -from qad_msg import QadMsg -from qad_textwindow import * -import qad_utils -import qad_layer - - -# Classe che gestisce il comando POLYGON -class QadPOLYGONCommandClass(QadCommandClass): - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadPOLYGONCommandClass(self.plugIn) - - def getName(self): - return QadMsg.translate("Command_list", "POLYGON") - - def getEnglishName(self): - return "POLYGON" - - def connectQAction(self, action): - QObject.connect(action, SIGNAL("triggered()"), self.plugIn.runPOLYGONCommand) - - def getIcon(self): - return QIcon(":/plugins/qad/icons/polygon.png") - - def getNote(self): - # impostare le note esplicative del comando - return QadMsg.translate("Command_POLYGON", "Draws a regular polygon.") - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - # se questo flag = True il comando serve all'interno di un altro comando per disegnare un rettangolo - # che non verrà salvato su un layer - self.virtualCmd = False - self.centerPt = None - self.firstEdgePt = None - self.vertices = [] - self.sideNumber = self.plugIn.lastPolygonSideNumber - self.constructionModeByCenter = self.plugIn.lastPolygonConstructionModeByCenter - self.area = 100 - - def __del__(self): - QadCommandClass.__del__(self) - - def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): - if (self.plugIn is not None): - if self.PointMapTool is None: - self.PointMapTool = Qad_polygon_maptool(self.plugIn) - return self.PointMapTool - else: - return None - - - def addPolygonToLayer(self, layer): - if layer.geometryType() == QGis.Line: - qad_layer.addLineToLayer(self.plugIn, layer, self.vertices) - elif layer.geometryType() == QGis.Polygon: - qad_layer.addPolygonToLayer(self.plugIn, layer, self.vertices) - - - #============================================================================ - # WaitForSideNumber - #============================================================================ - def WaitForSideNumber(self): - self.step = 1 - prompt = QadMsg.translate("Command_POLYGON", "Enter number of sides <{0}>: ") - self.waitForInt(prompt.format(str(self.sideNumber)), self.sideNumber, \ - QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) - - #============================================================================ - # WaitForCenter - #============================================================================ - def WaitForCenter(self): - self.step = 2 - self.getPointMapTool().setMode(Qad_polygon_maptool_ModeEnum.ASK_FOR_CENTER_PT) - - keyWords = QadMsg.translate("Command_POLYGON", "Edge") - prompt = QadMsg.translate("Command_POLYGON", "Specify center of polygon or [{0}]: ").format(keyWords) - - englishKeyWords = "Edge" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o enter - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - None, keyWords, QadInputModeEnum.NONE) - - #============================================================================ - # WaitForInscribedCircumscribedOption - #============================================================================ - def WaitForInscribedCircumscribedOption(self): - self.step = 3 - keyWords = QadMsg.translate("Command_POLYGON", "Inscribed in circle") + "/" + \ - QadMsg.translate("Command_POLYGON", "Circumscribed about circle") + "/" + \ - QadMsg.translate("Command_POLYGON", "Area") - prompt = QadMsg.translate("Command_POLYGON", "Enter an option [{0}] <{1}>: ").format(keyWords, \ - self.constructionModeByCenter) - - englishKeyWords = "Inscribed in circle" + "/" + "Circumscribed about circle" + "/" + "Area" - keyWords += "_" + englishKeyWords - # si appresta ad attendere una parola chiave - # msg, inputType, default, keyWords, valori positivi - self.waitFor(prompt, QadInputTypeEnum.KEYWORDS, \ - self.constructionModeByCenter, \ - keyWords, QadInputModeEnum.NONE) - - #============================================================================ - # WaitForRadius - #============================================================================ - def WaitForRadius(self, layer): - self.step = 4 - if layer is not None: - self.getPointMapTool().geomType = layer.geometryType() - self.getPointMapTool().setMode(Qad_polygon_maptool_ModeEnum.CENTER_PT_KNOWN_ASK_FOR_RADIUS) - - # si appresta ad attendere un punto o un numero reale - # msg, inputType, default, keyWords, valori positivi - prompt = QadMsg.translate("Command_CIRCLE", "Specify the circle radius <{0}>: ") - self.waitFor(prompt.format(str(self.plugIn.lastRadius)), \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT, \ - self.plugIn.lastRadius, "", \ - QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) - - #============================================================================ - # WaitForFirstEdgePt - #============================================================================ - def WaitForFirstEdgePt(self): - self.step = 5 - # imposto il map tool - self.getPointMapTool().setMode(Qad_polygon_maptool_ModeEnum.ASK_FOR_FIRST_EDGE_PT) - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_POLYGON", "Specify the first point of the edge: ")) - - #============================================================================ - # WaitForSecondEdgePt - #============================================================================ - def WaitForSecondEdgePt(self, layer): - self.step = 6 - self.getPointMapTool().firstEdgePt = self.firstEdgePt - - if layer is not None: - self.getPointMapTool().geomType = layer.geometryType() - - # imposto il map tool - self.getPointMapTool().setMode(Qad_polygon_maptool_ModeEnum.FIRST_EDGE_PT_KNOWN_ASK_FOR_SECOND_EDGE_PT) - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_POLYGON", "Specify the second point of the edge: ")) - - #============================================================================ - # WaitForArea - #============================================================================ - def WaitForArea(self): - self.step = 7 - - msg = QadMsg.translate("Command_POLYGON", "Enter the polygon area in current units <{0}>: ") - # si appresta ad attendere un numero reale - # msg, inputType, default, keyWords, valori positivi - self.waitFor(msg.format(str(self.area)), QadInputTypeEnum.FLOAT, \ - self.area, "", \ - QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) - - - #============================================================================ - # run - #============================================================================ - def run(self, msgMapTool = False, msg = None): - if self.plugIn.canvas.mapRenderer().destinationCrs().geographicFlag(): - self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) - return True # fine comando - - currLayer = None - if self.virtualCmd == False: # se si vuole veramente salvare la polylinea in un layer - # il layer corrente deve essere editabile e di tipo linea o poligono - currLayer, errMsg = qad_layer.getCurrLayerEditable(self.plugIn.canvas, [QGis.Line, QGis.Polygon]) - if currLayer is None: - self.showErr(errMsg) - return True # fine comando - - #========================================================================= - # RICHIESTA NUMERO DI LATI DEL POLIGONO - if self.step == 0: # inizio del comando - self.WaitForSideNumber() - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DEL NUMERO DI LATI DEL POLIGONO (da step = 0) - elif self.step == 1: # dopo aver atteso un punto si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - value = self.sideNumber - else: - return False - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == int: - if value < 3: - self.showErr(QadMsg.translate("Command_POLYGON", "\nEnter an integer greater than 2.")) - else: - self.sideNumber = value - self.getPointMapTool().sideNumber = self.sideNumber - self.plugIn.setLastPolygonSideNumber(self.sideNumber) - self.WaitForCenter() - else: - self.WaitForSideNumber() - - return False # continua - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DEL CENTRO DEL POLIGONO (da step = 1) - elif self.step == 2: # dopo aver atteso un punto si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - self.WaitForCenter() - return False - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("Command_POLYGON", "Edge") or value == "Edge": - self.WaitForFirstEdgePt() - elif type(value) == QgsPoint: - self.centerPt = value - self.getPointMapTool().centerPt = self.centerPt - self.WaitForInscribedCircumscribedOption() - - return False # continua - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DI POLIGONO INSCRITTO O CIRCOSCRITTO (da step = 2) - elif self.step == 3: - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - value = self.constructionModeByCenter - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - else: - value = self.getPointMapTool().point - else: # la parola chiave arriva come parametro della funzione - value = msg - - if type(value) == unicode: - self.constructionModeByCenter = value - self.plugIn.setLastPolygonConstructionModeByCenter(self.constructionModeByCenter) - self.getPointMapTool().constructionModeByCenter = self.constructionModeByCenter - if self.constructionModeByCenter == QadMsg.translate("Command_POLYGON", "Area") or self.constructionModeByCenter == "Area": - self.WaitForArea() - else: - self.WaitForRadius(currLayer) - - return False # fine comando - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DEL RAGGIO (da step = 3) - elif self.step == 4: - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == QgsPoint or type(value) == float: # se é stato inserito il raggio del cerchio - if type(value) == QgsPoint: # se é stato inserito il raggio del cerchio con un punto - self.radius = qad_utils.getDistance(self.centerPt, value) - ptStart = value - else: - self.radius = value - ptStart = None - - self.plugIn.setLastRadius(self.radius) - - if self.constructionModeByCenter == QadMsg.translate("Command_POLYGON", "Inscribed in circle") or \ - self.constructionModeByCenter == "Inscribed in circle": - mode = True - else: - mode = False - - self.vertices.extend(qad_utils.getPolygonByNsidesCenterRadius(self.sideNumber, self.centerPt, self.radius, \ - mode, ptStart)) - - if self.virtualCmd == False: # se si vuole veramente salvare i buffer in un layer - self.addPolygonToLayer(currLayer) - return True - - return False # fine comando - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DEL PRIMO PUNTO DELLO SPIGOLO (da step = 2) - elif self.step == 5: # dopo aver atteso un punto si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == QgsPoint: - self.firstEdgePt = value - self.WaitForSecondEdgePt(currLayer) - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DEL SECONDO PUNTO DELLO SPIGOLO (da step = 5) - elif self.step == 6: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == QgsPoint: - self.vertices.extend(qad_utils.getPolygonByNsidesEdgePts(self.sideNumber, self.firstEdgePt, value)) - - if self.virtualCmd == False: # se si vuole veramente salvare i buffer in un layer - self.addPolygonToLayer(currLayer) - return True - - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA AREA POLIGONO (da step = 3) - elif self.step == 7: # dopo aver atteso un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - value = self.area - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - else: - return False - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == float: # é stata inserita l'area - self.vertices.extend(qad_utils.getPolygonByNsidesArea(self.sideNumber, self.centerPt, value)) - - if self.virtualCmd == False: # se si vuole veramente salvare i buffer in un layer - self.addPolygonToLayer(currLayer) - return True - - return False +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin OK + + comando POLYGON per disegnare un poligono regolare + + ------------------- + begin : 2014-11-17 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.PyQt.QtCore import * +from qgis.PyQt.QtGui import * +from qgis.core import * + + +from ..qad_getpoint import QadGetPointDrawModeEnum +from ..qad_polyline import QadPolyline +from .qad_polygon_maptool import Qad_polygon_maptool_ModeEnum, Qad_polygon_maptool +from .qad_generic_cmd import QadCommandClass +from ..qad_msg import QadMsg +from ..qad_textwindow import QadInputTypeEnum, QadInputModeEnum +from .. import qad_utils +from .. import qad_layer + + +# Classe che gestisce il comando POLYGON +class QadPOLYGONCommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadPOLYGONCommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "POLYGON") + + def getEnglishName(self): + return "POLYGON" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runPOLYGONCommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/polygon.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_POLYGON", "Draws a regular polygon.") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + # se questo flag = True il comando serve all'interno di un altro comando per disegnare un rettangolo + # che non verrà salvato su un layer + self.virtualCmd = False + self.centerPt = None + self.firstEdgePt = None + self.polyline = QadPolyline() + self.sideNumber = self.plugIn.lastPolygonSideNumber + self.constructionModeByCenter = self.plugIn.lastPolygonConstructionModeByCenter + self.area = 100 + + def __del__(self): + QadCommandClass.__del__(self) + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if (self.plugIn is not None): + if self.PointMapTool is None: + self.PointMapTool = Qad_polygon_maptool(self.plugIn) + return self.PointMapTool + else: + return None + + + def addPolygonToLayer(self, layer): + geom = self.polyline.asGeom(layer.wkbType()) + if geom is not None: + qad_layer.addGeomToLayer(self.plugIn, layer, self.mapToLayerCoordinates(layer, geom)) + + + # ============================================================================ + # WaitForSideNumber + # ============================================================================ + def WaitForSideNumber(self): + self.step = 1 + prompt = QadMsg.translate("Command_POLYGON", "Enter number of sides <{0}>: ") + self.waitForInt(prompt.format(str(self.sideNumber)), self.sideNumber, \ + QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) + + # ============================================================================ + # WaitForCenter + # ============================================================================ + def WaitForCenter(self): + self.step = 2 + self.getPointMapTool().setMode(Qad_polygon_maptool_ModeEnum.ASK_FOR_CENTER_PT) + + keyWords = QadMsg.translate("Command_POLYGON", "Edge") + prompt = QadMsg.translate("Command_POLYGON", "Specify center of polygon or [{0}]: ").format(keyWords) + + englishKeyWords = "Edge" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + None, keyWords, QadInputModeEnum.NONE) + + # ============================================================================ + # WaitForInscribedCircumscribedOption + # ============================================================================ + def WaitForInscribedCircumscribedOption(self): + self.step = 3 + keyWords = QadMsg.translate("Command_POLYGON", "Inscribed in circle") + "/" + \ + QadMsg.translate("Command_POLYGON", "Circumscribed about circle") + "/" + \ + QadMsg.translate("Command_POLYGON", "Area") + prompt = QadMsg.translate("Command_POLYGON", "Enter an option [{0}] <{1}>: ").format(keyWords, \ + self.constructionModeByCenter) + + englishKeyWords = "Inscribed in circle" + "/" + "Circumscribed about circle" + "/" + "Area" + keyWords += "_" + englishKeyWords + # si appresta ad attendere una parola chiave + # msg, inputType, default, keyWords, valori positivi + self.waitFor(prompt, QadInputTypeEnum.KEYWORDS, \ + self.constructionModeByCenter, \ + keyWords, QadInputModeEnum.NONE) + + # ============================================================================ + # WaitForRadius + # ============================================================================ + def WaitForRadius(self, layer): + self.step = 4 + if layer is not None: + self.getPointMapTool().geomType = layer.geometryType() + self.getPointMapTool().setMode(Qad_polygon_maptool_ModeEnum.CENTER_PT_KNOWN_ASK_FOR_RADIUS) + + # si appresta ad attendere un punto o un numero reale + # msg, inputType, default, keyWords, valori positivi + prompt = QadMsg.translate("Command_CIRCLE", "Specify the circle radius <{0}>: ") + self.waitFor(prompt.format(str(self.plugIn.lastRadius)), \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT, \ + self.plugIn.lastRadius, "", \ + QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) + + # ============================================================================ + # WaitForFirstEdgePt + # ============================================================================ + def WaitForFirstEdgePt(self): + self.step = 5 + # imposto il map tool + self.getPointMapTool().setMode(Qad_polygon_maptool_ModeEnum.ASK_FOR_FIRST_EDGE_PT) + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_POLYGON", "Specify the first point of the edge: ")) + + # ============================================================================ + # WaitForSecondEdgePt + # ============================================================================ + def WaitForSecondEdgePt(self, layer): + self.step = 6 + self.getPointMapTool().firstEdgePt = self.firstEdgePt + + if layer is not None: + self.getPointMapTool().geomType = layer.geometryType() + + # imposto il map tool + self.getPointMapTool().setMode(Qad_polygon_maptool_ModeEnum.FIRST_EDGE_PT_KNOWN_ASK_FOR_SECOND_EDGE_PT) + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_POLYGON", "Specify the second point of the edge: ")) + + # ============================================================================ + # WaitForArea + # ============================================================================ + def WaitForArea(self): + self.step = 7 + + msg = QadMsg.translate("Command_POLYGON", "Enter the polygon area in current units <{0}>: ") + # si appresta ad attendere un numero reale + # msg, inputType, default, keyWords, valori positivi + self.waitFor(msg.format(str(self.area)), QadInputTypeEnum.FLOAT, \ + self.area, "", \ + QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) + + + # ============================================================================ + # run + # ============================================================================ + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + currLayer = None + if self.virtualCmd == False: # se si vuole veramente salvare la polylinea in un layer + # il layer corrente deve essere editabile e di tipo linea o poligono + currLayer, errMsg = qad_layer.getCurrLayerEditable(self.plugIn.canvas, [QgsWkbTypes.LineGeometry, QgsWkbTypes.PolygonGeometry]) + if currLayer is None: + self.showErr(errMsg) + return True # fine comando + + # ========================================================================= + # RICHIESTA NUMERO DI LATI DEL POLIGONO + if self.step == 0: # inizio del comando + self.WaitForSideNumber() + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL NUMERO DI LATI DEL POLIGONO (da step = 0) + elif self.step == 1: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + value = self.sideNumber + else: + return False + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == int: + if value < 3: + self.showErr(QadMsg.translate("Command_POLYGON", "\nEnter an integer greater than 2.")) + else: + self.sideNumber = value + self.getPointMapTool().sideNumber = self.sideNumber + self.plugIn.setLastPolygonSideNumber(self.sideNumber) + self.WaitForCenter() + else: + self.WaitForSideNumber() + + return False # continua + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL CENTRO DEL POLIGONO (da step = 1) + elif self.step == 2: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + self.WaitForCenter() + return False + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("Command_POLYGON", "Edge") or value == "Edge": + self.WaitForFirstEdgePt() + elif type(value) == QgsPointXY: + self.centerPt = value + self.getPointMapTool().centerPt = self.centerPt + self.WaitForInscribedCircumscribedOption() + + return False # continua + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DI POLIGONO INSCRITTO O CIRCOSCRITTO (da step = 2) + elif self.step == 3: + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + value = self.constructionModeByCenter + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: + value = self.getPointMapTool().point + else: # la parola chiave arriva come parametro della funzione + value = msg + + if type(value) == unicode: + self.constructionModeByCenter = value + self.plugIn.setLastPolygonConstructionModeByCenter(self.constructionModeByCenter) + self.getPointMapTool().constructionModeByCenter = self.constructionModeByCenter + if self.constructionModeByCenter == QadMsg.translate("Command_POLYGON", "Area") or self.constructionModeByCenter == "Area": + self.WaitForArea() + else: + self.WaitForRadius(currLayer) + + return False # fine comando + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL RAGGIO (da step = 3) + elif self.step == 4: + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == QgsPointXY or type(value) == float: # se é stato inserito il raggio del cerchio + if type(value) == QgsPointXY: # se é stato inserito il raggio del cerchio con un punto + self.radius = qad_utils.getDistance(self.centerPt, value) + ptStart = value + else: + self.radius = value + ptStart = None + + self.plugIn.setLastRadius(self.radius) + + if self.constructionModeByCenter == QadMsg.translate("Command_POLYGON", "Inscribed in circle") or \ + self.constructionModeByCenter == "Inscribed in circle": + mode = True + else: + mode = False + + self.polyline.getPolygonByNsidesCenterRadius(self.sideNumber, self.centerPt, self.radius, mode, ptStart) + + if self.virtualCmd == False: # se si vuole veramente salvare i buffer in un layer + self.addPolygonToLayer(currLayer) + return True + + return False # fine comando + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL PRIMO PUNTO DELLO SPIGOLO (da step = 2) + elif self.step == 5: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == QgsPointXY: + self.firstEdgePt = value + self.WaitForSecondEdgePt(currLayer) + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL SECONDO PUNTO DELLO SPIGOLO (da step = 5) + elif self.step == 6: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == QgsPointXY: + self.polyline.getPolygonByNsidesEdgePts(self.sideNumber, self.firstEdgePt, value) + + if self.virtualCmd == False: # se si vuole veramente salvare i buffer in un layer + self.addPolygonToLayer(currLayer) + return True + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA AREA POLIGONO (da step = 3) + elif self.step == 7: # dopo aver atteso un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + value = self.area + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: + return False + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == float: # é stata inserita l'area + self.polyline.getPolygonByNsidesArea(self.sideNumber, self.centerPt, value) + + if self.virtualCmd == False: # se si vuole veramente salvare i buffer in un layer + self.addPolygonToLayer(currLayer) + return True + + return False diff --git a/cmd/qad_polygon_maptool.py b/cmd/qad_polygon_maptool.py new file mode 100644 index 00000000..fb557326 --- /dev/null +++ b/cmd/qad_polygon_maptool.py @@ -0,0 +1,131 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + classe per gestire il map tool in ambito del comando Ppolygon + + ------------------- + begin : 2014-11-17 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from qgis.core import QgsWkbTypes + + +from .. import qad_utils +from ..qad_polyline import QadPolyline +from ..qad_getpoint import QadGetPoint, QadGetPointDrawModeEnum +from ..qad_rubberband import QadRubberBand +from ..qad_msg import QadMsg + + +# =============================================================================== +# Qad_polygon_maptool_ModeEnum class. +# =============================================================================== +class Qad_polygon_maptool_ModeEnum(): + # si richiede il centro + ASK_FOR_CENTER_PT = 1 + # noto il centro si richiede il raggio + CENTER_PT_KNOWN_ASK_FOR_RADIUS = 2 + # si richiede il primo punto dello spigolo + ASK_FOR_FIRST_EDGE_PT = 3 + # si richiede il secondo punto dello spigolo + FIRST_EDGE_PT_KNOWN_ASK_FOR_SECOND_EDGE_PT = 4 + +# =============================================================================== +# Qad_polygon_maptool class +# =============================================================================== +class Qad_polygon_maptool(QadGetPoint): + + def __init__(self, plugIn): + QadGetPoint.__init__(self, plugIn) + self.mode = None + + self.sideNumber = None + self.centerPt = None + self.constructionModeByCenter = None + self.firstEdgePt = None + self.polyline = QadPolyline() + + self.__rubberBand = QadRubberBand(self.canvas, True) + self.geomType = QgsWkbTypes.PolygonGeometry + + def hidePointMapToolMarkers(self): + QadGetPoint.hidePointMapToolMarkers(self) + self.__rubberBand.hide() + + def showPointMapToolMarkers(self): + QadGetPoint.showPointMapToolMarkers(self) + self.__rubberBand.show() + + def clear(self): + QadGetPoint.clear(self) + self.__rubberBand.reset() + self.mode = None + + def canvasMoveEvent(self, event): + QadGetPoint.canvasMoveEvent(self, event) + + self.__rubberBand.reset() + + result = False + + if self.mode is not None: + # noto il centro si richiede il raggio + if self.mode == Qad_polygon_maptool_ModeEnum.CENTER_PT_KNOWN_ASK_FOR_RADIUS: + radius = qad_utils.getDistance(self.centerPt, self.tmpPoint) + + InscribedOption = True if self.constructionModeByCenter == QadMsg.translate("Command_POLYGON", "Inscribed in circle") else False + result = self.polyline.getPolygonByNsidesCenterRadius(self.sideNumber, self.centerPt, radius, InscribedOption, self.tmpPoint) + # si richiede il secondo punto dello spigolo + elif self.mode == Qad_polygon_maptool_ModeEnum.FIRST_EDGE_PT_KNOWN_ASK_FOR_SECOND_EDGE_PT: + result = self.polyline.getPolygonByNsidesEdgePts(self.sideNumber, self.firstEdgePt, self.tmpPoint) + + if result == True: + vertices = self.polyline.asPolyline() + if self.geomType == QgsWkbTypes.PolygonGeometry: + self.__rubberBand.setPolygon(vertices) + else: + self.__rubberBand.setLine(vertices) + + def activate(self): + QadGetPoint.activate(self) + self.__rubberBand.show() + + def deactivate(self): + try: # necessario perché se si chiude QGIS parte questo evento nonostante non ci sia più l'oggetto maptool ! + QadGetPoint.deactivate(self) + self.__rubberBand.hide() + except: + pass + + def setMode(self, mode): + self.mode = mode + # si richiede il centro + if self.mode == Qad_polygon_maptool_ModeEnum.ASK_FOR_CENTER_PT: + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + # noto il centro si richiede il raggio + if self.mode == Qad_polygon_maptool_ModeEnum.CENTER_PT_KNOWN_ASK_FOR_RADIUS: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.centerPt) + # si richiede il primo punto dello spigolo + if self.mode == Qad_polygon_maptool_ModeEnum.ASK_FOR_FIRST_EDGE_PT: + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + # si richiede il secondo punto dello spigolo + if self.mode == Qad_polygon_maptool_ModeEnum.FIRST_EDGE_PT_KNOWN_ASK_FOR_SECOND_EDGE_PT: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.firstEdgePt) diff --git a/qad_rectangle_cmd.py b/cmd/qad_rectangle_cmd.py similarity index 82% rename from qad_rectangle_cmd.py rename to cmd/qad_rectangle_cmd.py index e9881981..563cb04d 100644 --- a/qad_rectangle_cmd.py +++ b/cmd/qad_rectangle_cmd.py @@ -1,537 +1,553 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - comando RECTANGLE per disegnare un rettangolo - - ------------------- - begin : 2013-12-02 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg -***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -from qad_rectangle_maptool import * -from qad_generic_cmd import QadCommandClass -from qad_msg import QadMsg -from qad_textwindow import * -import qad_utils -import qad_layer -from qad_getdist_cmd import QadGetDistClass -from qad_getangle_cmd import QadGetAngleClass - - -# Classe che gestisce il comando RECTANGLE -class QadRECTANGLECommandClass(QadCommandClass): - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadRECTANGLECommandClass(self.plugIn) - - def getName(self): - return QadMsg.translate("Command_list", "RECTANGLE") - - def getEnglishName(self): - return "RECTANGLE" - - def connectQAction(self, action): - QObject.connect(action, SIGNAL("triggered()"), self.plugIn.runRECTANGLECommand) - - def getIcon(self): - return QIcon(":/plugins/qad/icons/rectangle.png") - - def getNote(self): - # impostare le note esplicative del comando - return QadMsg.translate("Command_RECTANGLE", "Creates a rectangle.") - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - # se questo flag = True il comando serve all'interno di un altro comando per disegnare un rettangolo - # che non verrà salvato su un layer - self.virtualCmd = False - self.firstCorner = None - self.gapType = 0 # 0 = Angoli retti; 1 = Raccorda i segmenti; 2 = Cima i segmenti - self.gapValue1 = 0 # se gapType = 1 -> raggio di curvatura; se gapType = 2 -> prima distanza di cimatura - self.gapValue2 = 0 # se gapType = 2 -> seconda distanza di cimatura - self.area = 100 - self.dim1 = 10 - self.rot = 0 - self.vertices = [] - - self.GetDistClass = None - self.GetAngleClass = None - self.defaultValue = None # usato per gestire il tasto dx del mouse - - def __del__(self): - QadCommandClass.__del__(self) - if self.GetDistClass is not None: - del self.GetDistClass - if self.GetAngleClass is not None: - del self.GetAngleClass - - def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): - # quando si é in fase di richiesta distanza - if self.step == 3 or self.step == 4 or self.step == 5 or \ - self.step == 8 or self.step == 9 or self.step == 10 or self.step == 11: - return self.GetDistClass.getPointMapTool() - # quando si é in fase di richiesta rotazione - elif self.step == 13: - return self.GetAngleClass.getPointMapTool() - else: - if (self.plugIn is not None): - if self.PointMapTool is None: - self.PointMapTool = Qad_rectangle_maptool(self.plugIn) - return self.PointMapTool - else: - return None - - - def addRectangleToLayer(self, layer): - if layer.geometryType() == QGis.Line: - qad_layer.addLineToLayer(self.plugIn, layer, self.vertices) - elif layer.geometryType() == QGis.Polygon: - qad_layer.addPolygonToLayer(self.plugIn, layer, self.vertices) - - - #============================================================================ - # WaitForFirstCorner - #============================================================================ - def WaitForFirstCorner(self): - self.step = 1 - self.getPointMapTool().setMode(Qad_rectangle_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_FIRST_CORNER) - - keyWords = QadMsg.translate("Command_RECTANGLE", "Chamfer") + "/" + \ - QadMsg.translate("Command_RECTANGLE", "Fillet") - prompt = QadMsg.translate("Command_RECTANGLE", "Specify first corner or [{0}]: ").format(keyWords) - - englishKeyWords = "Chamfer" + "/" + "Fillet" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o enter - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - None, keyWords, QadInputModeEnum.NONE) - - #============================================================================ - # WaitForSecondCorner - #============================================================================ - def WaitForSecondCorner(self, layer): - self.step = 2 - self.getPointMapTool().rot = self.rot - self.getPointMapTool().gapType = self.gapType - self.getPointMapTool().gapValue1 = self.gapValue1 - self.getPointMapTool().gapValue2 = self.gapValue2 - self.getPointMapTool().setMode(Qad_rectangle_maptool_ModeEnum.FIRST_CORNER_KNOWN_ASK_FOR_SECOND_CORNER) - if layer is not None: - self.getPointMapTool().geomType = layer.geometryType() - - keyWords = QadMsg.translate("Command_RECTANGLE", "Area") + "/" + \ - QadMsg.translate("Command_RECTANGLE", "Dimensions") + "/" + \ - QadMsg.translate("Command_RECTANGLE", "Rotation") - prompt = QadMsg.translate("Command_RECTANGLE", "Specify other corner or [{0}]: ").format(keyWords) - - englishKeyWords = "Area" + "/" + "Dimensions" + "/" + "Rotation" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o enter - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - None, keyWords, QadInputModeEnum.NONE) - - #============================================================================ - # run - #============================================================================ - def run(self, msgMapTool = False, msg = None): - if self.plugIn.canvas.mapRenderer().destinationCrs().geographicFlag(): - self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) - return True # fine comando - - currLayer = None - if self.virtualCmd == False: # se si vuole veramente salvare la polylinea in un layer - # il layer corrente deve essere editabile e di tipo linea o poligono - currLayer, errMsg = qad_layer.getCurrLayerEditable(self.plugIn.canvas, [QGis.Line, QGis.Polygon]) - if currLayer is None: - self.showErr(errMsg) - return True # fine comando - - #========================================================================= - # RICHIESTA PRIMO PUNTO - if self.step == 0: # inizio del comando - self.WaitForFirstCorner() - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DEL PRIMO PUNTO DEL RETTANGOLO (da step = 0) - elif self.step == 1: # dopo aver atteso un punto si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - self.showMsg(QadMsg.translate("Command_RECTANGLE", "Window not correct.")) - self.WaitForFirstCorner() - return False - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("Command_RECTANGLE", "Chamfer") or value == "Chamfer": - if self.GetDistClass is not None: - del self.GetDistClass - self.GetDistClass = QadGetDistClass(self.plugIn) - prompt = QadMsg.translate("Command_RECTANGLE", "Specify first chamfer distance for rectangle <{0}>: ") - self.GetDistClass.msg = prompt.format(str(self.gapValue1)) - self.GetDistClass.dist = self.gapValue1 - self.GetDistClass.inputMode = QadInputModeEnum.NOT_NEGATIVE - self.step = 4 - self.GetDistClass.run(msgMapTool, msg) - elif value == QadMsg.translate("Command_RECTANGLE", "Fillet") or value == "Fillet": - if self.GetDistClass is not None: - del self.GetDistClass - self.GetDistClass = QadGetDistClass(self.plugIn) - prompt = QadMsg.translate("Command_RECTANGLE", "Specify rectangle fillet radius <{0}>: ") - self.GetDistClass.msg = prompt.format(str(self.gapValue1)) - self.GetDistClass.dist = self.gapValue1 - self.GetDistClass.inputMode = QadInputModeEnum.NOT_NEGATIVE - self.step = 3 - self.GetDistClass.run(msgMapTool, msg) - elif type(value) == QgsPoint: - self.firstCorner = value - self.getPointMapTool().firstCorner = self.firstCorner - self.WaitForSecondCorner(currLayer) - - return False # continua - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DEL SECONDO PUNTO DEL RETTANGOLO (da step = 1) - elif self.step == 2: # dopo aver atteso un punto si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - self.showMsg(QadMsg.translate("Command_RECTANGLE", "Window not correct.")) - self.WaitForSecondCorner(currLayer) - return False - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("Command_RECTANGLE", "Area") or value == "Area": - msg = QadMsg.translate("Command_RECTANGLE", "Enter rectangle area in current units <{0}>: ") - # si appresta ad attendere un numero reale - # msg, inputType, default, keyWords, valori positivi - self.waitFor(msg.format(str(self.area)), QadInputTypeEnum.FLOAT, \ - self.area, "", \ - QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) - self.getPointMapTool().setMode(Qad_rectangle_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_FIRST_CORNER) - - self.step = 6 - elif value == QadMsg.translate("Command_RECTANGLE", "Dimensions") or value == "Dimensions": - if self.GetDistClass is not None: - del self.GetDistClass - self.GetDistClass = QadGetDistClass(self.plugIn) - prompt = QadMsg.translate("Command_RECTANGLE", "Specify length for rectangle <{0}>: ") - self.GetDistClass.msg = prompt.format(str(self.dim1)) - self.GetDistClass.dist = self.dim1 - self.step = 10 - self.GetDistClass.run(msgMapTool, msg) - elif value == QadMsg.translate("Command_RECTANGLE", "Rotation") or value == "Rotation": - keyWords = QadMsg.translate("Command_RECTANGLE", "Points") - self.defaultValue = self.rot - prompt = QadMsg.translate("Command_RECTANGLE", "Specify rotation angle or [{0}] <{1}>: ").format(keyWords, str(qad_utils.toDegrees(self.rot))) - - englishKeyWords = "Points" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o un numero reale - # msg, inputType, default, keyWords, valori non nulli - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT | QadInputTypeEnum.KEYWORDS, \ - self.rot, keyWords) - self.getPointMapTool().setMode(Qad_rectangle_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_FIRST_CORNER) - - self.step = 12 - elif type(value) == QgsPoint: - self.vertices.extend(qad_utils.getRectByCorners(self.firstCorner, value, self.rot, \ - self.gapType, self.gapValue1, self.gapValue2)) - - if self.virtualCmd == False: # se si vuole veramente salvare i buffer in un layer - self.addRectangleToLayer(currLayer) - return True - - return False # continua - - #========================================================================= - # RISPOSTA ALLA RICHIESTA RAGGIO DI CURVATURA (da step = 1) - elif self.step == 3: - if self.GetDistClass.run(msgMapTool, msg) == True: - if self.GetDistClass.dist is not None: - self.gapValue1 = self.GetDistClass.dist - if self.gapValue1 == 0: - self.gapType = 0 # 0 = Angoli retti - else: - self.gapType = 1 # 1 = Raccorda i segmenti - - self.WaitForFirstCorner() - self.getPointMapTool().refreshSnapType() # aggiorno lo snapType che può essere variato dal maptool di distanza - return False # fine comando - - #========================================================================= - # RISPOSTA ALLA RICHIESTA PRIMA DISTANZA DI CIMATURA (da step = 1) - elif self.step == 4: - if self.GetDistClass.run(msgMapTool, msg) == True: - if self.GetDistClass.dist is not None: - self.gapValue1 = self.GetDistClass.dist - - if self.GetDistClass is not None: - del self.GetDistClass - self.GetDistClass = QadGetDistClass(self.plugIn) - prompt = QadMsg.translate("Command_RECTANGLE", "Specify second chamfer distance for rectangle <{0}>: ") - self.GetDistClass.msg = prompt.format(str(self.gapValue2)) - self.GetDistClass.dist = self.gapValue2 - self.GetDistClass.inputMode = QadInputModeEnum.NOT_NEGATIVE - self.step = 5 - self.GetDistClass.run(msgMapTool, msg) - else: - self.WaitForFirstCorner() - self.getPointMapTool().refreshSnapType() # aggiorno lo snapType che può essere variato dal maptool di distanza - return False # fine comando - - #========================================================================= - # RISPOSTA ALLA RICHIESTA SECONDA DISTANZA DI CIMATURA (da step = 1) - elif self.step == 5: - if self.GetDistClass.run(msgMapTool, msg) == True: - if self.GetDistClass.dist is not None: - self.gapValue2 = self.GetDistClass.dist - if self.gapValue1 == 0 or self.gapValue2 == 0: - self.gapType = 0 # 0 = Angoli retti - else: - self.gapType = 2 # 2 = Cima i segmenti - - self.WaitForFirstCorner() - self.getPointMapTool().refreshSnapType() # aggiorno lo snapType che può essere variato dal maptool di distanza - return False # fine comando - - #========================================================================= - # RISPOSTA ALLA RICHIESTA AREA RETTANGOLO (da step = 2) - elif self.step == 6: # dopo aver atteso un punto si riavvia il comando - keyWords = QadMsg.translate("Command_RECTANGLE", "Length") + "/" + \ - QadMsg.translate("Command_RECTANGLE", "Width") - englishKeyWords = "Length" + "/" + "Width" - - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - self.defaultValue = QadMsg.translate("Command_RECTANGLE", "Length") - prompt = QadMsg.translate("Command_RECTANGLE", "Calcolate the rectangle dimensions based on [{0}] <{1}>: ").format(keyWords, self.defaultValue) - - keyWords += "_" + englishKeyWords - # si appresta ad attendere una parola chiave - # msg, inputType, default, keyWords, valori positivi - self.waitFor(prompt, QadInputTypeEnum.KEYWORDS, \ - self.defaultValue, \ - keyWords, QadInputModeEnum.NONE) - - self.step = 7 - return False - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == float: # é stata inserita l'area - self.area = value - self.defaultValue = QadMsg.translate("Command_RECTANGLE", "Length") - prompt = QadMsg.translate("Command_RECTANGLE", "Calcolate the rectangle dimensions based on [{0}] <{1}>: ").format(keyWords, self.defaultValue) - - keyWords += "_" + englishKeyWords - # si appresta ad attendere una parola chiave - # msg, inputType, default, keyWords, valori positivi - self.waitFor(prompt, QadInputTypeEnum.KEYWORDS, \ - self.defaultValue, \ - keyWords, QadInputModeEnum.NONE) - self.step = 7 - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DELLA MODALITA' (LUNGHEZZA / LARGHEZZA) DATA L'AREA (da step = 6) - elif self.step == 7: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - value = self.defaultValue - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - else: - return False - else: # il punto arriva come parametro della funzione - value = msg - - if value == QadMsg.translate("Command_RECTANGLE", "Length") or value == "Length": - if self.GetDistClass is not None: - del self.GetDistClass - self.GetDistClass = QadGetDistClass(self.plugIn) - prompt = QadMsg.translate("Command_RECTANGLE", "Enter length for rectangle <{0}>: ") - self.GetDistClass.msg = prompt.format(str(self.dim1)) - self.GetDistClass.dist = self.dim1 - self.step = 8 - self.GetDistClass.run(msgMapTool, msg) - elif value == QadMsg.translate("Command_RECTANGLE", "Width") or value == "Width": - if self.GetDistClass is not None: - del self.GetDistClass - self.GetDistClass = QadGetDistClass(self.plugIn) - prompt = QadMsg.translate("Command_RECTANGLE", "Enter width for rectangle <{0}>: ") - self.GetDistClass.msg = prompt.format(str(self.dim1)) - self.GetDistClass.dist = self.dim1 - self.step = 9 - self.GetDistClass.run(msgMapTool, msg) - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA LUNGHEZZA RETTANGOLO DATA L'AREA (da step = 7) - elif self.step == 8: - if self.GetDistClass.run(msgMapTool, msg) == True: - if self.GetDistClass.dist is not None: - self.vertices.extend(qad_utils.getRectByAreaAndLength(self.firstCorner, self.area, self.GetDistClass.dist, \ - self.rot, self.gapType, self.gapValue1, self.gapValue2)) - if self.virtualCmd == False: # se si vuole veramente salvare i buffer in un layer - self.addRectangleToLayer(currLayer) - return True # fine comando - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA LARGHEZZA RETTANGOLO DATA L'AREA (da step = 7) - elif self.step == 9: - if self.GetDistClass.run(msgMapTool, msg) == True: - if self.GetDistClass.dist is not None: - self.vertices.extend(qad_utils.getRectByAreaAndWidth(self.firstCorner, self.area, self.GetDistClass.dist, \ - self.rot, self.gapType, self.gapValue1, self.gapValue2)) - if self.virtualCmd == False: # se si vuole veramente salvare i buffer in un layer - self.addRectangleToLayer(currLayer) - return True # fine comando - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA LUNGHEZZA RETTANGOLO (da step = 2) - elif self.step == 10: - if self.GetDistClass.run(msgMapTool, msg) == True: - if self.GetDistClass.dist is not None: - self.dim1 = self.GetDistClass.dist - - if self.GetDistClass is not None: - del self.GetDistClass - self.GetDistClass = QadGetDistClass(self.plugIn) - prompt = QadMsg.translate("Command_RECTANGLE", "Enter width for rectangle <{0}>: ") - self.GetDistClass.msg = prompt.format(str(self.dim1)) - self.GetDistClass.dist = self.dim1 - self.step = 11 - self.GetDistClass.run(msgMapTool, msg) - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA LARGHEZZA RETTANGOLO (da step = 10) - elif self.step == 11: - if self.GetDistClass.run(msgMapTool, msg) == True: - if self.GetDistClass.dist is not None: - self.vertices.extend(qad_utils.getRectByCornerAndDims(self.firstCorner, self.dim1, self.GetDistClass.dist, \ - self.rot, self.gapType, self.gapValue1, self.gapValue2)) - if self.virtualCmd == False: # se si vuole veramente salvare i buffer in un layer - self.addRectangleToLayer(currLayer) - return True # fine comando - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA ROTAZIONE RETTANGOLO (da step = 2) - elif self.step == 12: # dopo aver atteso un punto si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - value = self.defaultValue - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - else: - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("Command_RECTANGLE", "Points") or value == "Points": - # si appresta ad attendere l'angolo di rotazione - if self.GetAngleClass is not None: - del self.GetAngleClass - self.GetAngleClass = QadGetAngleClass(self.plugIn) - self.GetAngleClass.msg = QadMsg.translate("Command_RECTANGLE", "Specify first point: ") - self.GetAngleClass.angle = self.rot - self.step = 13 - self.GetAngleClass.run(msgMapTool, msg) - elif type(value) == QgsPoint: - self.rot = qad_utils.getAngleBy2Pts(self.firstCorner, value) - self.WaitForSecondCorner(currLayer) - elif type(value) == float: - self.rot = qad_utils.toRadians(value) - self.WaitForSecondCorner(currLayer) - - return False # continua - - #========================================================================= - # RISPOSTA ALLA RICHIESTA ROTAZIONE RETTANGOLO (da step = 12) - elif self.step == 13: - if self.GetAngleClass.run(msgMapTool, msg) == True: - if self.GetAngleClass.angle is not None: - self.rot = self.GetAngleClass.angle - self.plugIn.setLastRot(self.rot) - self.WaitForSecondCorner(currLayer) - self.getPointMapTool().refreshSnapType() # aggiorno lo snapType che può essere variato dal maptool di rotazione +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin OK + + comando RECTANGLE per disegnare un rettangolo + + ------------------- + begin : 2013-12-02 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg +***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.core import QgsWkbTypes, QgsPointXY +from qgis.PyQt.QtGui import QIcon + + +from ..qad_polyline import QadPolyline +from ..qad_getpoint import QadGetPointDrawModeEnum +from .qad_rectangle_maptool import Qad_rectangle_maptool_ModeEnum, Qad_rectangle_maptool +from .qad_generic_cmd import QadCommandClass +from ..qad_msg import QadMsg +from ..qad_textwindow import QadInputTypeEnum, QadInputModeEnum +from .. import qad_utils +from .. import qad_layer +from .qad_getdist_cmd import QadGetDistClass +from .qad_getangle_cmd import QadGetAngleClass + + +# Classe che gestisce il comando RECTANGLE +class QadRECTANGLECommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadRECTANGLECommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "RECTANGLE") + + def getEnglishName(self): + return "RECTANGLE" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runRECTANGLECommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/rectangle.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_RECTANGLE", "Creates a rectangle.") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + # se questo flag = True il comando serve all'interno di un altro comando per disegnare un rettangolo + # che non verrà salvato su un layer + self.virtualCmd = False + self.firstCorner = None + self.gapType = 0 # 0 = Angoli retti; 1 = Raccorda i segmenti; 2 = Cima i segmenti + self.gapValue1 = 0 # se gapType = 1 -> raggio di curvatura; se gapType = 2 -> prima distanza di cimatura + self.gapValue2 = 0 # se gapType = 2 -> seconda distanza di cimatura + self.area = 100 + self.dim1 = 10 + self.rot = 0 + self.polyline = QadPolyline() + + self.GetDistClass = None + self.GetAngleClass = None + self.defaultValue = None # usato per gestire il tasto dx del mouse + + def __del__(self): + QadCommandClass.__del__(self) + if self.GetDistClass is not None: + del self.GetDistClass + if self.GetAngleClass is not None: + del self.GetAngleClass + + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + # quando si é in fase di richiesta distanza + if self.step == 3 or self.step == 4 or self.step == 5 or \ + self.step == 8 or self.step == 9 or self.step == 10 or self.step == 11: + return self.GetDistClass.getPointMapTool() + # quando si é in fase di richiesta rotazione + elif self.step == 13: + return self.GetAngleClass.getPointMapTool() + else: + if (self.plugIn is not None): + if self.PointMapTool is None: + self.PointMapTool = Qad_rectangle_maptool(self.plugIn) + return self.PointMapTool + else: + return None + + + def getCurrentContextualMenu(self): + # quando si é in fase di richiesta distanza + if self.step == 3 or self.step == 4 or self.step == 5 or \ + self.step == 8 or self.step == 9 or self.step == 10 or self.step == 11: + return self.GetDistClass.getCurrentContextualMenu() + # quando si é in fase di richiesta rotazione + elif self.step == 13: + return self.GetAngleClass.getCurrentContextualMenu() + else: + return self.contextualMenu + + + def addRectangleToLayer(self, layer): + vertices = self.polyline.asPolyline() + if layer.geometryType() == QgsWkbTypes.LineGeometry: + qad_layer.addLineToLayer(self.plugIn, layer, vertices) + elif layer.geometryType() == QgsWkbTypes.PolygonGeometry: + qad_layer.addPolygonToLayer(self.plugIn, layer, vertices) + + + # ============================================================================ + # WaitForFirstCorner + # ============================================================================ + def WaitForFirstCorner(self): + self.step = 1 + self.getPointMapTool().setMode(Qad_rectangle_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_FIRST_CORNER) + + keyWords = QadMsg.translate("Command_RECTANGLE", "Chamfer") + "/" + \ + QadMsg.translate("Command_RECTANGLE", "Fillet") + prompt = QadMsg.translate("Command_RECTANGLE", "Specify first corner or [{0}]: ").format(keyWords) + + englishKeyWords = "Chamfer" + "/" + "Fillet" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + None, keyWords, QadInputModeEnum.NONE) + + # ============================================================================ + # WaitForSecondCorner + # ============================================================================ + def WaitForSecondCorner(self, layer): + self.step = 2 + self.getPointMapTool().rot = self.rot + self.getPointMapTool().gapType = self.gapType + self.getPointMapTool().gapValue1 = self.gapValue1 + self.getPointMapTool().gapValue2 = self.gapValue2 + self.getPointMapTool().setMode(Qad_rectangle_maptool_ModeEnum.FIRST_CORNER_KNOWN_ASK_FOR_SECOND_CORNER) + if layer is not None: + self.getPointMapTool().geomType = layer.geometryType() + + keyWords = QadMsg.translate("Command_RECTANGLE", "Area") + "/" + \ + QadMsg.translate("Command_RECTANGLE", "Dimensions") + "/" + \ + QadMsg.translate("Command_RECTANGLE", "Rotation") + prompt = QadMsg.translate("Command_RECTANGLE", "Specify other corner or [{0}]: ").format(keyWords) + + englishKeyWords = "Area" + "/" + "Dimensions" + "/" + "Rotation" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + None, keyWords, QadInputModeEnum.NONE) + + # ============================================================================ + # run + # ============================================================================ + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + currLayer = None + if self.virtualCmd == False: # se si vuole veramente salvare la polylinea in un layer + # il layer corrente deve essere editabile e di tipo linea o poligono + currLayer, errMsg = qad_layer.getCurrLayerEditable(self.plugIn.canvas, [QgsWkbTypes.LineGeometry, QgsWkbTypes.PolygonGeometry]) + if currLayer is None: + self.showErr(errMsg) + return True # fine comando + + # ========================================================================= + # RICHIESTA PRIMO PUNTO + if self.step == 0: # inizio del comando + self.WaitForFirstCorner() + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL PRIMO PUNTO DEL RETTANGOLO (da step = 0) + elif self.step == 1: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + self.showMsg(QadMsg.translate("Command_RECTANGLE", "Window not correct.")) + self.WaitForFirstCorner() + return False + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("Command_RECTANGLE", "Chamfer") or value == "Chamfer": + if self.GetDistClass is not None: + del self.GetDistClass + self.GetDistClass = QadGetDistClass(self.plugIn) + prompt = QadMsg.translate("Command_RECTANGLE", "Specify first chamfer distance for rectangle <{0}>: ") + self.GetDistClass.msg = prompt.format(str(self.gapValue1)) + self.GetDistClass.dist = self.gapValue1 + self.GetDistClass.inputMode = QadInputModeEnum.NOT_NEGATIVE + self.step = 4 + self.GetDistClass.run(msgMapTool, msg) + elif value == QadMsg.translate("Command_RECTANGLE", "Fillet") or value == "Fillet": + if self.GetDistClass is not None: + del self.GetDistClass + self.GetDistClass = QadGetDistClass(self.plugIn) + prompt = QadMsg.translate("Command_RECTANGLE", "Specify rectangle fillet radius <{0}>: ") + self.GetDistClass.msg = prompt.format(str(self.gapValue1)) + self.GetDistClass.dist = self.gapValue1 + self.GetDistClass.inputMode = QadInputModeEnum.NOT_NEGATIVE + self.step = 3 + self.GetDistClass.run(msgMapTool, msg) + elif type(value) == QgsPointXY: + self.firstCorner = value + self.getPointMapTool().firstCorner = self.firstCorner + self.WaitForSecondCorner(currLayer) + + return False # continua + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL SECONDO PUNTO DEL RETTANGOLO (da step = 1) + elif self.step == 2: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + self.showMsg(QadMsg.translate("Command_RECTANGLE", "Window not correct.")) + self.WaitForSecondCorner(currLayer) + return False + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("Command_RECTANGLE", "Area") or value == "Area": + msg = QadMsg.translate("Command_RECTANGLE", "Enter rectangle area in current units <{0}>: ") + # si appresta ad attendere un numero reale + # msg, inputType, default, keyWords, valori positivi + self.waitFor(msg.format(str(self.area)), QadInputTypeEnum.FLOAT, \ + self.area, "", \ + QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) + self.getPointMapTool().setMode(Qad_rectangle_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_FIRST_CORNER) + + self.step = 6 + elif value == QadMsg.translate("Command_RECTANGLE", "Dimensions") or value == "Dimensions": + if self.GetDistClass is not None: + del self.GetDistClass + self.GetDistClass = QadGetDistClass(self.plugIn) + prompt = QadMsg.translate("Command_RECTANGLE", "Specify length for rectangle <{0}>: ") + self.GetDistClass.msg = prompt.format(str(self.dim1)) + self.GetDistClass.dist = self.dim1 + self.step = 10 + self.GetDistClass.run(msgMapTool, msg) + elif value == QadMsg.translate("Command_RECTANGLE", "Rotation") or value == "Rotation": + keyWords = QadMsg.translate("Command_RECTANGLE", "Points") + self.defaultValue = self.rot + prompt = QadMsg.translate("Command_RECTANGLE", "Specify rotation angle or [{0}] <{1}>: ").format(keyWords, str(qad_utils.toDegrees(self.rot))) + + englishKeyWords = "Points" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o un numero reale + # msg, inputType, default, keyWords, valori non nulli + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT | QadInputTypeEnum.KEYWORDS, \ + self.rot, keyWords) + self.getPointMapTool().setMode(Qad_rectangle_maptool_ModeEnum.FIRST_CORNER_KNOWN_ASK_FOR_ROTATION) + + self.step = 12 + elif type(value) == QgsPointXY: + self.polyline.getRectByCorners(self.firstCorner, value, self.rot, \ + self.gapType, self.gapValue1, self.gapValue2) + + if self.virtualCmd == False: # se si vuole veramente salvare i buffer in un layer + self.addRectangleToLayer(currLayer) + return True + + return False # continua + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA RAGGIO DI CURVATURA (da step = 1) + elif self.step == 3: + if self.GetDistClass.run(msgMapTool, msg) == True: + if self.GetDistClass.dist is not None: + self.gapValue1 = self.GetDistClass.dist + if self.gapValue1 == 0: + self.gapType = 0 # 0 = Angoli retti + else: + self.gapType = 1 # 1 = Raccorda i segmenti + + self.WaitForFirstCorner() + self.getPointMapTool().refreshSnapType() # aggiorno lo snapType che può essere variato dal maptool di distanza + return False # fine comando + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA PRIMA DISTANZA DI CIMATURA (da step = 1) + elif self.step == 4: + if self.GetDistClass.run(msgMapTool, msg) == True: + if self.GetDistClass.dist is not None: + self.gapValue1 = self.GetDistClass.dist + + if self.GetDistClass is not None: + del self.GetDistClass + self.GetDistClass = QadGetDistClass(self.plugIn) + prompt = QadMsg.translate("Command_RECTANGLE", "Specify second chamfer distance for rectangle <{0}>: ") + self.GetDistClass.msg = prompt.format(str(self.gapValue2)) + self.GetDistClass.dist = self.gapValue2 + self.GetDistClass.inputMode = QadInputModeEnum.NOT_NEGATIVE + self.step = 5 + self.GetDistClass.run(msgMapTool, msg) + else: + self.WaitForFirstCorner() + self.getPointMapTool().refreshSnapType() # aggiorno lo snapType che può essere variato dal maptool di distanza + return False # fine comando + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA SECONDA DISTANZA DI CIMATURA (da step = 1) + elif self.step == 5: + if self.GetDistClass.run(msgMapTool, msg) == True: + if self.GetDistClass.dist is not None: + self.gapValue2 = self.GetDistClass.dist + if self.gapValue1 == 0 or self.gapValue2 == 0: + self.gapType = 0 # 0 = Angoli retti + else: + self.gapType = 2 # 2 = Cima i segmenti + + self.WaitForFirstCorner() + self.getPointMapTool().refreshSnapType() # aggiorno lo snapType che può essere variato dal maptool di distanza + return False # fine comando + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA AREA RETTANGOLO (da step = 2) + elif self.step == 6: # dopo aver atteso un punto si riavvia il comando + keyWords = QadMsg.translate("Command_RECTANGLE", "Length") + "/" + \ + QadMsg.translate("Command_RECTANGLE", "Width") + englishKeyWords = "Length" + "/" + "Width" + + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + self.defaultValue = QadMsg.translate("Command_RECTANGLE", "Length") + prompt = QadMsg.translate("Command_RECTANGLE", "Calcolate the rectangle dimensions based on [{0}] <{1}>: ").format(keyWords, self.defaultValue) + + keyWords += "_" + englishKeyWords + # si appresta ad attendere una parola chiave + # msg, inputType, default, keyWords, valori positivi + self.waitFor(prompt, QadInputTypeEnum.KEYWORDS, \ + self.defaultValue, \ + keyWords, QadInputModeEnum.NONE) + + self.step = 7 + return False + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == float: # é stata inserita l'area + self.area = value + self.defaultValue = QadMsg.translate("Command_RECTANGLE", "Length") + prompt = QadMsg.translate("Command_RECTANGLE", "Calcolate the rectangle dimensions based on [{0}] <{1}>: ").format(keyWords, self.defaultValue) + + keyWords += "_" + englishKeyWords + # si appresta ad attendere una parola chiave + # msg, inputType, default, keyWords, valori positivi + self.waitFor(prompt, QadInputTypeEnum.KEYWORDS, \ + self.defaultValue, \ + keyWords, QadInputModeEnum.NONE) + self.step = 7 + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DELLA MODALITA' (LUNGHEZZA / LARGHEZZA) DATA L'AREA (da step = 6) + elif self.step == 7: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + value = self.defaultValue + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: + return False + else: # il punto arriva come parametro della funzione + value = msg + + if value == QadMsg.translate("Command_RECTANGLE", "Length") or value == "Length": + if self.GetDistClass is not None: + del self.GetDistClass + self.GetDistClass = QadGetDistClass(self.plugIn) + prompt = QadMsg.translate("Command_RECTANGLE", "Enter length for rectangle <{0}>: ") + self.GetDistClass.msg = prompt.format(str(self.dim1)) + self.GetDistClass.dist = self.dim1 + self.step = 8 + self.GetDistClass.run(msgMapTool, msg) + elif value == QadMsg.translate("Command_RECTANGLE", "Width") or value == "Width": + if self.GetDistClass is not None: + del self.GetDistClass + self.GetDistClass = QadGetDistClass(self.plugIn) + prompt = QadMsg.translate("Command_RECTANGLE", "Enter width for rectangle <{0}>: ") + self.GetDistClass.msg = prompt.format(str(self.dim1)) + self.GetDistClass.dist = self.dim1 + self.step = 9 + self.GetDistClass.run(msgMapTool, msg) + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA LUNGHEZZA RETTANGOLO DATA L'AREA (da step = 7) + elif self.step == 8: + if self.GetDistClass.run(msgMapTool, msg) == True: + if self.GetDistClass.dist is not None: + self.polyline.getRectByAreaAndLength(self.firstCorner, self.area, self.GetDistClass.dist, \ + self.rot, self.gapType, self.gapValue1, self.gapValue2) + + if self.virtualCmd == False: # se si vuole veramente salvare i buffer in un layer + self.addRectangleToLayer(currLayer) + return True # fine comando + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA LARGHEZZA RETTANGOLO DATA L'AREA (da step = 7) + elif self.step == 9: + if self.GetDistClass.run(msgMapTool, msg) == True: + if self.GetDistClass.dist is not None: + self.polyline.getRectByAreaAndWidth(self.firstCorner, self.area, self.GetDistClass.dist, \ + self.rot, self.gapType, self.gapValue1, self.gapValue2) + if self.virtualCmd == False: # se si vuole veramente salvare i buffer in un layer + self.addRectangleToLayer(currLayer) + return True # fine comando + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA LUNGHEZZA RETTANGOLO (da step = 2) + elif self.step == 10: + if self.GetDistClass.run(msgMapTool, msg) == True: + if self.GetDistClass.dist is not None: + self.dim1 = self.GetDistClass.dist + + if self.GetDistClass is not None: + del self.GetDistClass + self.GetDistClass = QadGetDistClass(self.plugIn) + prompt = QadMsg.translate("Command_RECTANGLE", "Enter width for rectangle <{0}>: ") + self.GetDistClass.msg = prompt.format(str(self.dim1)) + self.GetDistClass.dist = self.dim1 + self.step = 11 + self.GetDistClass.run(msgMapTool, msg) + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA LARGHEZZA RETTANGOLO (da step = 10) + elif self.step == 11: + if self.GetDistClass.run(msgMapTool, msg) == True: + if self.GetDistClass.dist is not None: + self.polyline.getRectByCornerAndDims(self.firstCorner, self.dim1, self.GetDistClass.dist, \ + self.rot, self.gapType, self.gapValue1, self.gapValue2) + if self.virtualCmd == False: # se si vuole veramente salvare i buffer in un layer + self.addRectangleToLayer(currLayer) + return True # fine comando + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA ROTAZIONE RETTANGOLO (da step = 2) + elif self.step == 12: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + value = self.defaultValue + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("Command_RECTANGLE", "Points") or value == "Points": + # si appresta ad attendere l'angolo di rotazione + if self.GetAngleClass is not None: + del self.GetAngleClass + self.GetAngleClass = QadGetAngleClass(self.plugIn) + self.GetAngleClass.msg = QadMsg.translate("Command_RECTANGLE", "Specify first point: ") + self.GetAngleClass.angle = self.rot + self.step = 13 + self.GetAngleClass.run(msgMapTool, msg) + elif type(value) == QgsPointXY: + self.rot = qad_utils.getAngleBy2Pts(self.firstCorner, value) + self.WaitForSecondCorner(currLayer) + elif type(value) == float: + self.rot = qad_utils.toRadians(value) + self.WaitForSecondCorner(currLayer) + + return False # continua + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA ROTAZIONE RETTANGOLO (da step = 12) + elif self.step == 13: + if self.GetAngleClass.run(msgMapTool, msg) == True: + if self.GetAngleClass.angle is not None: + self.rot = self.GetAngleClass.angle + self.plugIn.setLastRot(self.rot) + self.WaitForSecondCorner(currLayer) + self.getPointMapTool().refreshSnapType() # aggiorno lo snapType che può essere variato dal maptool di rotazione diff --git a/cmd/qad_rectangle_maptool.py b/cmd/qad_rectangle_maptool.py new file mode 100644 index 00000000..58f7bba0 --- /dev/null +++ b/cmd/qad_rectangle_maptool.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + classe per gestire il map tool in ambito del comando rectangle + + ------------------- + begin : 2013-12-3 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from qgis.core import QgsWkbTypes + + +from ..qad_polyline import QadPolyline +from ..qad_getpoint import QadGetPoint, QadGetPointDrawModeEnum +from ..qad_rubberband import QadRubberBand + + +# =============================================================================== +# Qad_rectangle_maptool_ModeEnum class. +# =============================================================================== +class Qad_rectangle_maptool_ModeEnum(): + # noto niente si richiede il primo angolo + NONE_KNOWN_ASK_FOR_FIRST_CORNER = 1 + # noto il primo angolo si richiede l'angolo opposto + FIRST_CORNER_KNOWN_ASK_FOR_SECOND_CORNER = 2 + # noto il primo angolo si richiede la rotazione + FIRST_CORNER_KNOWN_ASK_FOR_ROTATION = 3 + +# =============================================================================== +# Qad_rotate_maptool class +# =============================================================================== +class Qad_rectangle_maptool(QadGetPoint): + + def __init__(self, plugIn): + QadGetPoint.__init__(self, plugIn) + + self.firstCorner = None + self.secondCorner = None + self.basePt = None + self.gapType = 0 # 0 = Angoli retti; 1 = Raccorda i segmenti; 2 = Cima i segmenti + self.gapValue1 = 0 # se gapType = 1 -> raggio di curvatura; se gapType = 2 -> prima distanza di cimatura + self.gapValue2 = 0 # se gapType = 2 -> seconda distanza di cimatura + self.rot = 0 + self.polyline = QadPolyline() + + self.__rubberBand = QadRubberBand(self.canvas, True) + self.geomType = QgsWkbTypes.PolygonGeometry + + def hidePointMapToolMarkers(self): + QadGetPoint.hidePointMapToolMarkers(self) + self.__rubberBand.hide() + + def showPointMapToolMarkers(self): + QadGetPoint.showPointMapToolMarkers(self) + self.__rubberBand.show() + + def clear(self): + QadGetPoint.clear(self) + self.__rubberBand.reset() + self.mode = None + + def canvasMoveEvent(self, event): + QadGetPoint.canvasMoveEvent(self, event) + + self.__rubberBand.reset() + + result = False + + # noto il primo angolo si richiede l'angolo opposto + if self.mode == Qad_rectangle_maptool_ModeEnum.FIRST_CORNER_KNOWN_ASK_FOR_SECOND_CORNER: + result = self.polyline.getRectByCorners(self.firstCorner, self.tmpPoint, self.rot, \ + self.gapType, self.gapValue1, self.gapValue2) + + if result == True: + vertices = self.polyline.asPolyline() + if self.geomType == QgsWkbTypes.PolygonGeometry: + self.__rubberBand.setPolygon(vertices) + else: + self.__rubberBand.setLine(vertices) + + def activate(self): + QadGetPoint.activate(self) + self.__rubberBand.show() + + def deactivate(self): + try: # necessario perché se si chiude QGIS parte questo evento nonostante non ci sia più l'oggetto maptool ! + QadGetPoint.deactivate(self) + self.__rubberBand.hide() + except: + pass + + def setMode(self, mode): + self.mode = mode + # noto niente si richiede il primo angolo + if self.mode == Qad_rectangle_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_FIRST_CORNER: + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + # noto il primo angolo si richiede l'angolo opposto + elif self.mode == Qad_rectangle_maptool_ModeEnum.FIRST_CORNER_KNOWN_ASK_FOR_SECOND_CORNER: + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + # noto il primo angolo si richiede la rotazione + elif self.mode == Qad_rectangle_maptool_ModeEnum.FIRST_CORNER_KNOWN_ASK_FOR_ROTATION: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.firstCorner) + diff --git a/qad_rotate_cmd.py b/cmd/qad_rotate_cmd.py similarity index 75% rename from qad_rotate_cmd.py rename to cmd/qad_rotate_cmd.py index 15fb792f..2930c600 100644 --- a/qad_rotate_cmd.py +++ b/cmd/qad_rotate_cmd.py @@ -1,853 +1,829 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - comando ROTATE per ruotare oggetti - - ------------------- - begin : 2013-09-27 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -from qad_rotate_maptool import * -from qad_generic_cmd import QadCommandClass -from qad_msg import QadMsg -from qad_getpoint import * -from qad_textwindow import * -from qad_ssget_cmd import QadSSGetClass -from qad_entity import * -import qad_utils -import qad_layer -import qad_label -from qad_dim import * - - -# Classe che gestisce il comando RUOTA -class QadROTATECommandClass(QadCommandClass): - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadROTATECommandClass(self.plugIn) - - def getName(self): - return QadMsg.translate("Command_list", "ROTATE") - - def getEnglishName(self): - return "ROTATE" - - def connectQAction(self, action): - QObject.connect(action, SIGNAL("triggered()"), self.plugIn.runROTATECommand) - - def getIcon(self): - return QIcon(":/plugins/qad/icons/rotate.png") - - def getNote(self): - # impostare le note esplicative del comando - return QadMsg.translate("Command_ROTATE", "Rotates objects around a base point.") - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - self.SSGetClass = QadSSGetClass(plugIn) - self.SSGetClass.onlyEditableLayers = True - self.entitySet = QadEntitySet() - self.basePt = None - self.copyFeatures = False - self.Pt1ReferenceAng = None - self.ReferenceAng = 0 - self.Pt1NewAng = None - - def __del__(self): - QadCommandClass.__del__(self) - del self.SSGetClass - - def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): - if self.step == 0: # quando si é in fase di selezione entità - return self.SSGetClass.getPointMapTool() - else: - if (self.plugIn is not None): - if self.PointMapTool is None: - self.PointMapTool = Qad_rotate_maptool(self.plugIn) - return self.PointMapTool - else: - return None - - - #============================================================================ - # rotate - #============================================================================ - def rotate(self, f, basePt, angle, rotFldName, layerEntitySet, entitySet): - # verifico se l'entità appartiene ad uno stile di quotatura - dimEntity = QadDimStyles.getDimEntity(layerEntitySet.layer, f.id()) - - if dimEntity is None: - # ruoto la feature e la rimuovo da entitySet (é la prima) - f.setGeometry(qad_utils.rotateQgsGeometry(f.geometry(), basePt, angle)) - - if len(rotFldName) > 0: - rotValue = f.attribute(rotFldName) - rotValue = 0 if rotValue is None else qad_utils.toRadians(rotValue) # la rotazione é in gradi nel campo della feature - rotValue = rotValue + angle - f.setAttribute(rotFldName, qad_utils.toDegrees(qad_utils.normalizeAngle(rotValue))) - - if self.copyFeatures == False: - # plugIn, layer, feature, refresh, check_validity - if qad_layer.updateFeatureToLayer(self.plugIn, layerEntitySet.layer, f, False, False) == False: - self.plugIn.destroyEditCommand() - return False - else: - # plugIn, layer, features, coordTransform, refresh, check_validity - if qad_layer.addFeatureToLayer(self.plugIn, layerEntitySet.layer, f, None, False, False) == False: - self.plugIn.destroyEditCommand() - return False - del layerEntitySet.featureIds[0] - else: - # ruoto la quota e la rimuovo da entitySet - dimEntitySet = dimEntity.getEntitySet() - if self.copyFeatures == False: - if dimEntity.deleteToLayers(self.plugIn) == False: - return False - dimEntity.rotate(self.plugIn, basePt, angle) - if dimEntity.addToLayers(self.plugIn) == False: - return False - entitySet.subtract(dimEntitySet) - - return True - - - def RotateGeoms(self, angle): - # copio entitySet - entitySet = QadEntitySet(self.entitySet) - - self.plugIn.beginEditCommand("Feature rotated", entitySet.getLayerList()) - - for layerEntitySet in entitySet.layerEntitySetList: - layer = layerEntitySet.layer - - transformedBasePt = self.mapToLayerCoordinates(layer, self.basePt) - - rotFldName = "" - if qad_layer.isTextLayer(layer): - # se la rotazione dipende da un solo campo - rotFldNames = qad_label.get_labelRotationFieldNames(layer) - if len(rotFldNames) == 1 and len(rotFldNames[0]) > 0: - rotFldName = rotFldNames[0] - elif qad_layer.isSymbolLayer(layer): - rotFldName = qad_layer.get_symbolRotationFieldName(layer) - - while len(layerEntitySet.featureIds) > 0: - featureId = layerEntitySet.featureIds[0] - f = layerEntitySet.getFeature(featureId) - - if self.rotate(f, transformedBasePt, angle, rotFldName, layerEntitySet, entitySet) == False: - self.plugIn.destroyEditCommand() - return - - self.plugIn.endEditCommand() - - - def waitForRotation(self): - # imposto il map tool - self.getPointMapTool().setMode(Qad_rotate_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_ROTATION_PT) - - keyWords = QadMsg.translate("Command_ROTATE", "Copy") + "/" + \ - QadMsg.translate("Command_ROTATE", "Reference") - prompt = QadMsg.translate("Command_ROTATE", "Specify rotation angle or [{0}] <{1}>: ").format(keyWords, \ - str(qad_utils.toDegrees(self.plugIn.lastRot))) - - englishKeyWords = "Copy" + "/" + "Reference" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto, un numero reale o enter o una parola chiave - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(prompt, QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT | QadInputTypeEnum.KEYWORDS, \ - self.plugIn.lastRot, \ - keyWords, QadInputModeEnum.NONE) - self.step = 3 - - - def waitForReferenceRot(self): - # imposto il map tool - self.getPointMapTool().setMode(Qad_rotate_maptool_ModeEnum.ASK_FOR_FIRST_PT_REFERENCE_ANG) - - msg = QadMsg.translate("Command_ROTATE", "Specify reference angle <{0}>: ") - # si appresta ad attendere un punto o enter o una parola chiave - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(msg.format(str(qad_utils.toDegrees(self.plugIn.lastReferenceRot))), \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT, \ - self.plugIn.lastReferenceRot, \ - "") - self.step = 4 - - - def waitForNewReferenceRot(self): - # imposto il map tool - self.getPointMapTool().setMode(Qad_rotate_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_NEW_ROTATION_PT) - - keyWords = QadMsg.translate("Command_ROTATE", "Points") - if self.plugIn.lastNewReferenceRot == 0: - angle = self.plugIn.lastRot - else: - angle = self.plugIn.lastNewReferenceRot - prompt = QadMsg.translate("Command_ROTATE", "Specify new angle or [{0}] <{1}>: ").format(keyWords, str(qad_utils.toDegrees(angle))) - - englishKeyWords = "Points" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o enter o una parola chiave - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT | QadInputTypeEnum.KEYWORDS, \ - angle, \ - keyWords) - self.step = 6 - - - def run(self, msgMapTool = False, msg = None): - if self.plugIn.canvas.mapRenderer().destinationCrs().geographicFlag(): - self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) - return True # fine comando - - #========================================================================= - # RICHIESTA SELEZIONE OGGETTI - if self.step == 0: # inizio del comando - if self.SSGetClass.run(msgMapTool, msg) == True: - # selezione terminata - self.step = 1 - self.getPointMapTool().refreshSnapType() # aggiorno lo snapType che può essere variato dal maptool di selezione entità - return self.run(msgMapTool, msg) - - #========================================================================= - # RUOTA OGGETTI - elif self.step == 1: - self.entitySet.set(self.SSGetClass.entitySet) - - if self.entitySet.count() == 0: - return True # fine comando - - # imposto il map tool - self.getPointMapTool().setMode(Qad_rotate_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_BASE_PT) - - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_ROTATE", "Specify base point: ")) - - self.step = 2 - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA PUNTO BASE (da step = 1) - elif self.step == 2: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - self.basePt = QgsPoint(value) - - self.getPointMapTool().basePt = self.basePt - self.getPointMapTool().entitySet.set(self.entitySet) - # si appresta ad attendere l'angolo di rotazione - self.waitForRotation() - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA SECONDO PUNTO PER ANGOLO ROTAZIONE (da step = 2) - elif self.step == 3: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("Command_ROTATE", "Copy") or value == "Copy": - self.copyFeatures = True - self.showMsg(QadMsg.translate("Command_ROTATE", "\nRotation of a copy of the selected objects.")) - # si appresta ad attendere l'angolo di rotazione - self.waitForRotation() - elif value == QadMsg.translate("Command_ROTATE", "Reference") or value == "Reference": - # si appresta ad attendere l'angolo di riferimento - self.waitForReferenceRot() - elif type(value) == QgsPoint or type(value) == float: # se é stato inserito l'angolo di rotazione - if type(value) == QgsPoint: # se é stato inserito l'angolo di rotazione con un punto - angle = qad_utils.getAngleBy2Pts(self.basePt, value) - else: - angle = qad_utils.toRadians(value) - self.plugIn.setLastRot(angle) - - self.RotateGeoms(angle) - return True # fine comando - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA PRIMO PUNTO PER ANGOLO ROTAZIONE DI RIFERIMENTO (da step = 3) - elif self.step == 4: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == float: # se é stato inserito l'angolo di rotazione - self.ReferenceAng = qad_utils.toRadians(value) - self.getPointMapTool().ReferenceAng = self.ReferenceAng - # si appresta ad attendere il nuovo angolo - self.waitForNewReferenceRot() - - elif type(value) == QgsPoint: # se é stato inserito l'angolo di rotazione con un punto - self.Pt1ReferenceAng = QgsPoint(value) - self.getPointMapTool().Pt1ReferenceAng = self.Pt1ReferenceAng - # imposto il map tool - self.getPointMapTool().setMode(Qad_rotate_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT_REFERENCE_ANG) - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_ROTATE", "Specify second point: ")) - self.step = 5 - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA SECONDO PUNTO PER ANGOLO ROTAZIONE DI RIFERIMENTO (da step = 4) - elif self.step == 5: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - angle = qad_utils.getAngleBy2Pts(self.Pt1ReferenceAng, value) - self.ReferenceAng = angle - self.getPointMapTool().ReferenceAng = self.ReferenceAng - # si appresta ad attendere il nuovo angolo - self.waitForNewReferenceRot() - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA SECONDO PUNTO PER NUOVO ANGOLO ROTAZIONE (da step = 4 e 5) - elif self.step == 6: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("Command_ROTATE", "Points") or value == "Points": - # imposto il map tool - self.getPointMapTool().setMode(Qad_rotate_maptool_ModeEnum.ASK_FOR_FIRST_NEW_ROTATION_PT) - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_ROTATE", "Specify first point: ")) - self.step = 7 - elif type(value) == QgsPoint or type(value) == float: # se é stato inserito l'angolo di rotazione - if type(value) == QgsPoint: # se é stato inserito l'angolo di rotazione con un punto - angle = qad_utils.getAngleBy2Pts(self.basePt, value) - else: - angle = qad_utils.toRadians(value) - - angle = angle - self.ReferenceAng - self.plugIn.setLastRot(angle) - self.RotateGeoms(angle) - return True # fine comando - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA PRIMO PUNTO PER NUOVO ANGOLO ROTAZIONE (da step = 6) - elif self.step == 7: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - self.Pt1NewAng = value - # imposto il map tool - self.getPointMapTool().Pt1NewAng = self.Pt1NewAng - self.getPointMapTool().setMode(Qad_rotate_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_NEW_ROTATION_PT) - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_ROTATE", "Specify second point: ")) - self.step = 8 - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA SECONDO PUNTO PER NUOVO ANGOLO ROTAZIONE (da step = 7) - elif self.step == 8: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - angle = qad_utils.getAngleBy2Pts(self.Pt1NewAng, value) - - angle = angle - self.ReferenceAng - self.plugIn.setLastRot(angle) - self.RotateGeoms(angle) - return True # fine comando - - -# Classe che gestisce il comando ROTATE per i grip -class QadGRIPROTATECommandClass(QadCommandClass): - - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadGRIPROTATECommandClass(self.plugIn) - - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - self.entitySet = QadEntitySet() - self.basePt = QgsPoint() - self.skipToNextGripCommand = False - self.copyEntities = False - self.nOperationsToUndo = 0 - self.Pt1ReferenceAng = None - self.ReferenceAng = 0 - - - def __del__(self): - QadCommandClass.__del__(self) - - - def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): - if (self.plugIn is not None): - if self.PointMapTool is None: - self.PointMapTool = Qad_rotate_maptool(self.plugIn) - return self.PointMapTool - else: - return None - - - #============================================================================ - # setSelectedEntityGripPoints - #============================================================================ - def setSelectedEntityGripPoints(self, entitySetGripPoints): - # lista delle entityGripPoint con dei grip point selezionati - self.entitySet.clear() - - for entityGripPoints in entitySetGripPoints.entityGripPoints: - self.entitySet.addEntity(entityGripPoints.entity) - self.getPointMapTool().entitySet.set(self.entitySet) - - - #============================================================================ - # rotate - #============================================================================ - def rotate(self, entity, basePt, angle, rotFldName): - # entity = entità da ruotare - # basePt = punto base - # angle = angolo di rotazione in gradi - # rotFldName = campo della tabella che memorizza la rotazione - # verifico se l'entità appartiene ad uno stile di quotatura - if entity.whatIs() == "ENTITY": - f = entity.getFeature() - # ruoto l'entità - f.setGeometry(qad_utils.rotateQgsGeometry(entity.getGeometry(), basePt, angle)) - if len(rotFldName) > 0: - rotValue = f.attribute(rotFldName) - rotValue = 0 if rotValue is None else qad_utils.toRadians(rotValue) # la rotazione é in gradi nel campo della feature - rotValue = rotValue + angle - f.setAttribute(rotFldName, qad_utils.toDegrees(qad_utils.normalizeAngle(rotValue))) - - if self.copyEntities == False: - # plugIn, layer, feature, refresh, check_validity - if qad_layer.updateFeatureToLayer(self.plugIn, entity.layer, f, False, False) == False: - return False - else: - # plugIn, layer, features, coordTransform, refresh, check_validity - if qad_layer.addFeatureToLayer(self.plugIn, entity.layer, f, None, False, False) == False: - return False - - elif entity.whatIs() == "DIMENTITY": - # stiro la quota - if self.copyEntities == False: - if entity.deleteToLayers(self.plugIn) == False: - return False - entity.rotate(self.plugIn, basePt, angle) - if entity.addToLayers(self.plugIn) == False: - return False - - return True - - - #============================================================================ - # rotateFeatures - #============================================================================ - def rotateFeatures(self, angle): - entity = QadEntity() - self.plugIn.beginEditCommand("Feature rotated", self.entitySet.getLayerList()) - - dimElaboratedList = [] # lista delle quotature già elaborate - - for layerEntitySet in self.entitySet.layerEntitySetList: - layer = layerEntitySet.layer - transformedBasePt = self.mapToLayerCoordinates(layer, self.basePt) - - rotFldName = "" - if qad_layer.isTextLayer(layer): - # se la rotazione dipende da un solo campo - rotFldNames = qad_label.get_labelRotationFieldNames(layer) - if len(rotFldNames) == 1 and len(rotFldNames[0]) > 0: - rotFldName = rotFldNames[0] - elif qad_layer.isSymbolLayer(layer): - # se la rotazione dipende da un campo - rotFldName = qad_layer.get_symbolRotationFieldName(layer) - - for featureId in layerEntitySet.featureIds: - entity.set(layer, featureId) - - # verifico se l'entità appartiene ad uno stile di quotatura - dimEntity = QadDimStyles.getDimEntity(entity) - if dimEntity is None: - if self.rotate(entity, transformedBasePt, angle, rotFldName) == False: - self.plugIn.destroyEditCommand() - return - else: - found = False - for dimElaborated in dimElaboratedList: - if dimElaborated == dimEntity: - found = True - - if found == False: # quota non ancora elaborata - dimElaboratedList.append(dimEntity) - # basePt va espresso in map cooridnate - if self.rotate(dimEntity, self.basePt, angle, rotFldName) == False: - self.plugIn.destroyEditCommand() - return - - self.plugIn.endEditCommand() - self.nOperationsToUndo = self.nOperationsToUndo + 1 - - - #============================================================================ - # waitForRotatePoint - #============================================================================ - def waitForRotatePoint(self): - self.step = 1 - self.plugIn.setLastPoint(self.basePt) - # imposto il map tool - self.getPointMapTool().basePt = self.basePt - self.getPointMapTool().setMode(Qad_rotate_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_NEW_ROTATION_PT) - - keyWords = QadMsg.translate("Command_GRIPROTATE", "Base point") + "/" + \ - QadMsg.translate("Command_GRIPROTATE", "Copy") + "/" + \ - QadMsg.translate("Command_GRIPROTATE", "Undo") + "/" + \ - QadMsg.translate("Command_GRIPROTATE", "Reference") + "/" + \ - QadMsg.translate("Command_GRIPROTATE", "eXit") - - prompt = QadMsg.translate("Command_GRIPROTATE", "Specify rotation angle or [{0}] <{1}>: ").format(keyWords, \ - str(qad_utils.toDegrees(self.plugIn.lastRot))) - - englishKeyWords = "Base point" + "/" + "Copy" + "/" + "Undo" + "/" + "Reference" + "/" + "eXit" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto, un numero reale o enter o una parola chiave - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(prompt, QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT | QadInputTypeEnum.KEYWORDS, \ - self.plugIn.lastRot, \ - keyWords, QadInputModeEnum.NONE) - - - #============================================================================ - # waitForBasePt - #============================================================================ - def waitForBasePt(self): - self.step = 2 - # imposto il map tool - self.getPointMapTool().setMode(Qad_rotate_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_BASE_PT) - - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_GRIPROTATE", "Specify base point: ")) - - - def waitForReferenceRot(self): - # imposto il map tool - self.getPointMapTool().setMode(Qad_rotate_maptool_ModeEnum.ASK_FOR_FIRST_PT_REFERENCE_ANG) - - msg = QadMsg.translate("Command_GRIPROTATE", "Specify reference angle <{0}>: ") - # si appresta ad attendere un punto o enter o una parola chiave - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(msg.format(str(qad_utils.toDegrees(self.plugIn.lastReferenceRot))), \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT, \ - self.plugIn.lastReferenceRot, \ - "") - self.step = 3 - - - #============================================================================ - # run - #============================================================================ - def run(self, msgMapTool = False, msg = None): - if self.plugIn.canvas.mapRenderer().destinationCrs().geographicFlag(): - self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) - return True # fine comando - - #========================================================================= - # RICHIESTA SELEZIONE OGGETTI - if self.step == 0: # inizio del comando - if self.entitySet.isEmpty(): # non ci sono oggetti da ruotare - return True - self.showMsg(QadMsg.translate("Command_GRIPROTATE", "\n** ROTATE **\n")) - # si appresta ad attendere un punto di rotazione - self.waitForRotatePoint() - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DI UN PUNTO DI ROTAZIONE - elif self.step == 1: - ctrlKey = False - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - value = None - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - else: - value = self.getPointMapTool().point - - ctrlKey = self.getPointMapTool().ctrlKey - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("Command_GRIPROTATE", "Base point") or value == "Base point": - # si appresta ad attendere il punto base - self.waitForBasePt() - elif value == QadMsg.translate("Command_GRIPROTATE", "Copy") or value == "Copy": - # Copia entità lasciando inalterate le originali - self.copyEntities = True - # si appresta ad attendere un punto di rotazione - self.waitForRotatePoint() - elif value == QadMsg.translate("Command_GRIPROTATE", "Undo") or value == "Undo": - if self.nOperationsToUndo > 0: - self.nOperationsToUndo = self.nOperationsToUndo - 1 - self.plugIn.undoEditCommand() - else: - self.showMsg(QadMsg.translate("QAD", "\nThe command has been canceled.")) - # si appresta ad attendere un punto di rotazione - self.waitForRotatePoint() - elif value == QadMsg.translate("Command_GRIPROTATE", "Reference") or value == "Reference": - # si appresta ad attendere l'angolo di riferimento - self.waitForReferenceRot() - elif value == QadMsg.translate("Command_GRIPROTATE", "eXit") or value == "eXit": - return True # fine comando - elif type(value) == QgsPoint or type(value) == float: # se é stato inserito l'angolo di rotazione - if type(value) == QgsPoint: # se é stato inserito l'angolo di rotazione con un punto - angle = qad_utils.getAngleBy2Pts(self.basePt, value) - else: - angle = qad_utils.toRadians(value) - angle = angle - self.ReferenceAng - self.plugIn.setLastRot(angle) - - if ctrlKey: - self.copyEntities = True - - self.rotateFeatures(angle) - - if self.copyEntities == False: - return True - - # si appresta ad attendere un punto di rotazione - self.waitForRotatePoint() - - else: - if self.copyEntities == False: - self.skipToNextGripCommand = True - return True # fine comando - - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA PUNTO BASE (da step = 1) - elif self.step == 2: # dopo aver atteso un punto - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - pass # opzione di default "spostamento" - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == QgsPoint: # se é stato inserito il punto base - self.basePt.set(value.x(), value.y()) - # imposto il map tool - self.getPointMapTool().basePt = self.basePt - - # si appresta ad attendere un punto di rotazione - self.waitForRotatePoint() - - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA PRIMO PUNTO PER ANGOLO ROTAZIONE DI RIFERIMENTO (da step = 1) - elif self.step == 3: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == float: # se é stato inserito l'angolo di rotazione - self.ReferenceAng = qad_utils.toRadians(value) - self.getPointMapTool().ReferenceAng = self.ReferenceAng - # si appresta ad attendere un punto di rotazione - self.waitForRotatePoint() - - elif type(value) == QgsPoint: # se é stato inserito l'angolo di rotazione con un punto - self.Pt1ReferenceAng = QgsPoint(value) - self.getPointMapTool().Pt1ReferenceAng = self.Pt1ReferenceAng - # imposto il map tool - self.getPointMapTool().setMode(Qad_rotate_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT_REFERENCE_ANG) - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_GRIPROTATE", "Specify second point: ")) - self.step = 4 - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA SECONDO PUNTO PER NUOVO ANGOLO ROTAZIONE (da step = 3) - elif self.step == 4: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == QgsPoint or type(value) == float: # se é stato inserito l'angolo di rotazione - if type(value) == QgsPoint: # se é stato inserito l'angolo di rotazione con un punto - self.ReferenceAng = qad_utils.getAngleBy2Pts(self.Pt1ReferenceAng, value) - else: - self.ReferenceAng = qad_utils.toRadians(value) - self.getPointMapTool().ReferenceAng = self.ReferenceAng - - # si appresta ad attendere un punto di rotazione - self.waitForRotatePoint() - - return False - +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin ok + + comando ROTATE per ruotare oggetti + + ------------------- + begin : 2013-09-27 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.core import QgsPointXY, NULL +from qgis.PyQt.QtCore import QVariant +from qgis.PyQt.QtGui import QIcon + + +from .qad_rotate_maptool import Qad_rotate_maptool_ModeEnum, Qad_rotate_maptool +from .qad_generic_cmd import QadCommandClass +from ..qad_msg import QadMsg +from ..qad_getpoint import QadGetPointDrawModeEnum +from ..qad_textwindow import QadInputTypeEnum, QadInputModeEnum +from .qad_ssget_cmd import QadSSGetClass +from ..qad_entity import QadCacheEntitySet, QadCacheEntitySetIterator, QadEntityTypeEnum +from .. import qad_utils +from .. import qad_layer +from .. import qad_label +from ..qad_dim import QadDimStyles, QadDimEntity, appendDimEntityIfNotExisting +from ..qad_multi_geom import fromQadGeomToQgsGeom + + +# Classe che gestisce il comando RUOTA +class QadROTATECommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadROTATECommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "ROTATE") + + def getEnglishName(self): + return "ROTATE" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runROTATECommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/rotate.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_ROTATE", "Rotates objects around a base point.") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + self.SSGetClass = QadSSGetClass(plugIn) + self.SSGetClass.onlyEditableLayers = True + self.cacheEntitySet = QadCacheEntitySet() + self.basePt = None + self.copyFeatures = False + self.Pt1ReferenceAng = None + self.ReferenceAng = 0 + self.Pt1NewAng = None + + def __del__(self): + QadCommandClass.__del__(self) + del self.SSGetClass + + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if self.step == 0: # quando si é in fase di selezione entità + return self.SSGetClass.getPointMapTool() + else: + if (self.plugIn is not None): + if self.PointMapTool is None: + self.PointMapTool = Qad_rotate_maptool(self.plugIn) + return self.PointMapTool + else: + return None + + + def getCurrentContextualMenu(self): + if self.step == 0: # quando si é in fase di selezione entità + return None # return self.SSGetClass.getCurrentContextualMenu() + else: + return self.contextualMenu + + + # ============================================================================ + # rotate + # ============================================================================ + def rotate(self, entity, basePt, angle, openForm): + # verifico se l'entità appartiene ad uno stile di quotatura + if entity.whatIs() == "ENTITY": + # ruoto la geometria dell'entità + qadGeom = entity.getQadGeom().copy() # la copio + qadGeom.rotate(basePt, angle) + f = entity.getFeature() + f.setGeometry(fromQadGeomToQgsGeom(qadGeom, entity.layer)) + + if len(entity.rotFldName) > 0: + rotValue = f.attribute(entity.rotFldName) + rotValue = 0 if rotValue is None or rotValue == NULL else qad_utils.toRadians(rotValue) # la rotazione é in gradi nel campo della feature + rotValue = rotValue + angle + f.setAttribute(entity.rotFldName, qad_utils.toDegrees(qad_utils.normalizeAngle(rotValue))) + + if self.copyFeatures == False: + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, entity.layer, f, False, False) == False: + return False + else: + # plugIn, layer, features, coordTransform, refresh, check_validity + if qad_layer.addFeatureToLayer(self.plugIn, entity.layer, f, None, False, False, openForm) == False: + return False + elif entity.whatIs() == "DIMENTITY": + newDimEntity = QadDimEntity(entity) # la copio + # ruoto la quota + if self.copyFeatures == False: + if dimEntity.deleteToLayers(self.plugIn) == False: + return False + newDimEntity.rotate(basePt, angle) + if newDimEntity.addToLayers(self.plugIn) == False: + return False + + return True + + + def RotateGeoms(self, angle): + self.plugIn.beginEditCommand("Feature rotated", self.cacheEntitySet.getLayerList()) + + dimElaboratedList = [] # lista delle quotature già elaborate + entityIterator = QadCacheEntitySetIterator(self.cacheEntitySet) + openForm = True if self.cacheEntitySet.count() == 1 else False + + for entity in entityIterator: + qadGeom = entity.getQadGeom() # così inizializzo le info qad + # verifico se l'entità appartiene ad uno stile di quotatura + dimEntity = QadDimStyles.getDimEntity(entity) + if dimEntity is not None: + if appendDimEntityIfNotExisting(dimElaboratedList, dimEntity) == False: # quota già elaborata + continue + entity = dimEntity + + if self.rotate(entity, self.basePt, angle, openForm) == False: + self.plugIn.destroyEditCommand() + return + + self.plugIn.endEditCommand() + + + def waitForRotation(self): + # imposto il map tool + self.getPointMapTool().setMode(Qad_rotate_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_ROTATION_PT) + + keyWords = QadMsg.translate("Command_ROTATE", "Copy") + "/" + \ + QadMsg.translate("Command_ROTATE", "Reference") + prompt = QadMsg.translate("Command_ROTATE", "Specify rotation angle or [{0}] <{1}>: ").format(keyWords, \ + str(qad_utils.toDegrees(self.plugIn.lastRot))) + + englishKeyWords = "Copy" + "/" + "Reference" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto, un numero reale o enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, QadInputTypeEnum.POINT2D | QadInputTypeEnum.ANGLE | QadInputTypeEnum.KEYWORDS, \ + self.plugIn.lastRot, \ + keyWords, QadInputModeEnum.NONE) + + self.step = 3 + + + def waitForReferenceRot(self): + # imposto il map tool + self.getPointMapTool().setMode(Qad_rotate_maptool_ModeEnum.ASK_FOR_FIRST_PT_REFERENCE_ANG) + + msg = QadMsg.translate("Command_ROTATE", "Specify reference angle <{0}>: ") + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(msg.format(str(qad_utils.toDegrees(self.plugIn.lastReferenceRot))), \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.ANGLE, \ + self.plugIn.lastReferenceRot, \ + "") + self.step = 4 + + + def waitForNewReferenceRot(self): + # imposto il map tool + self.getPointMapTool().setMode(Qad_rotate_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_NEW_ROTATION_PT) + + keyWords = QadMsg.translate("Command_ROTATE", "Points") + if self.plugIn.lastNewReferenceRot == 0: + angle = self.plugIn.lastRot + else: + angle = self.plugIn.lastNewReferenceRot + prompt = QadMsg.translate("Command_ROTATE", "Specify new angle or [{0}] <{1}>: ").format(keyWords, str(qad_utils.toDegrees(angle))) + + englishKeyWords = "Points" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.ANGLE | QadInputTypeEnum.KEYWORDS, \ + angle, \ + keyWords) + self.step = 6 + + + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + # ========================================================================= + # RICHIESTA SELEZIONE OGGETTI + if self.step == 0: # inizio del comando + if self.SSGetClass.run(msgMapTool, msg) == True: + # selezione terminata + self.step = 1 + self.getPointMapTool().refreshSnapType() # aggiorno lo snapType che può essere variato dal maptool di selezione entità + return self.run(msgMapTool, msg) + + # ========================================================================= + # RUOTA OGGETTI + elif self.step == 1: + if self.SSGetClass.entitySet.count() == 0: + return True # fine comando + self.cacheEntitySet.appendEntitySet(self.SSGetClass.entitySet) + + # imposto il map tool + self.getPointMapTool().setMode(Qad_rotate_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_BASE_PT) + + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_ROTATE", "Specify base point: ")) + + self.step = 2 + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA PUNTO BASE (da step = 1) + elif self.step == 2: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + self.basePt = QgsPointXY(value) + + self.getPointMapTool().basePt = self.basePt + self.getPointMapTool().cacheEntitySet = self.cacheEntitySet + # si appresta ad attendere l'angolo di rotazione + self.waitForRotation() + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA SECONDO PUNTO PER ANGOLO ROTAZIONE (da step = 2) + elif self.step == 3: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("Command_ROTATE", "Copy") or value == "Copy": + self.copyFeatures = True + self.showMsg(QadMsg.translate("Command_ROTATE", "\nRotation of a copy of the selected objects.")) + # si appresta ad attendere l'angolo di rotazione + self.waitForRotation() + elif value == QadMsg.translate("Command_ROTATE", "Reference") or value == "Reference": + # si appresta ad attendere l'angolo di riferimento + self.waitForReferenceRot() + elif type(value) == QgsPointXY or type(value) == float: # se é stato inserito l'angolo di rotazione + if type(value) == QgsPointXY: # se é stato inserito l'angolo di rotazione con un punto + angle = qad_utils.getAngleBy2Pts(self.basePt, value) + else: + angle = qad_utils.toRadians(value) + self.plugIn.setLastRot(angle) + + self.RotateGeoms(angle) + return True # fine comando + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA PRIMO PUNTO PER ANGOLO ROTAZIONE DI RIFERIMENTO (da step = 3) + elif self.step == 4: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == float: # se é stato inserito l'angolo di rotazione + self.ReferenceAng = qad_utils.toRadians(value) + self.getPointMapTool().ReferenceAng = self.ReferenceAng + # si appresta ad attendere il nuovo angolo + self.waitForNewReferenceRot() + + elif type(value) == QgsPointXY: # se é stato inserito l'angolo di rotazione con un punto + self.Pt1ReferenceAng = QgsPointXY(value) + self.getPointMapTool().Pt1ReferenceAng = self.Pt1ReferenceAng + # imposto il map tool + self.getPointMapTool().setMode(Qad_rotate_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT_REFERENCE_ANG) + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_ROTATE", "Specify second point: ")) + self.step = 5 + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA SECONDO PUNTO PER ANGOLO ROTAZIONE DI RIFERIMENTO (da step = 4) + elif self.step == 5: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + angle = qad_utils.getAngleBy2Pts(self.Pt1ReferenceAng, value) + self.ReferenceAng = angle + self.getPointMapTool().ReferenceAng = self.ReferenceAng + # si appresta ad attendere il nuovo angolo + self.waitForNewReferenceRot() + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA SECONDO PUNTO PER NUOVO ANGOLO ROTAZIONE (da step = 4 e 5) + elif self.step == 6: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("Command_ROTATE", "Points") or value == "Points": + # imposto il map tool + self.getPointMapTool().setMode(Qad_rotate_maptool_ModeEnum.ASK_FOR_FIRST_NEW_ROTATION_PT) + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_ROTATE", "Specify first point: ")) + self.step = 7 + elif type(value) == QgsPointXY or type(value) == float: # se é stato inserito l'angolo di rotazione + if type(value) == QgsPointXY: # se é stato inserito l'angolo di rotazione con un punto + angle = qad_utils.getAngleBy2Pts(self.basePt, value) + else: + angle = qad_utils.toRadians(value) + + angle = angle - self.ReferenceAng + self.plugIn.setLastRot(angle) + self.RotateGeoms(angle) + return True # fine comando + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA PRIMO PUNTO PER NUOVO ANGOLO ROTAZIONE (da step = 6) + elif self.step == 7: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + self.Pt1NewAng = value + # imposto il map tool + self.getPointMapTool().Pt1NewAng = self.Pt1NewAng + self.getPointMapTool().setMode(Qad_rotate_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_NEW_ROTATION_PT) + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_ROTATE", "Specify second point: ")) + self.step = 8 + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA SECONDO PUNTO PER NUOVO ANGOLO ROTAZIONE (da step = 7) + elif self.step == 8: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + angle = qad_utils.getAngleBy2Pts(self.Pt1NewAng, value) + + angle = angle - self.ReferenceAng + self.plugIn.setLastRot(angle) + self.RotateGeoms(angle) + return True # fine comando + + +# Classe che gestisce il comando ROTATE per i grip +class QadGRIPROTATECommandClass(QadCommandClass): + + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadGRIPROTATECommandClass(self.plugIn) + + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + self.cacheEntitySet = QadCacheEntitySet() + self.basePt = QgsPointXY() + self.skipToNextGripCommand = False + self.copyEntities = False + self.nOperationsToUndo = 0 + self.Pt1ReferenceAng = None + self.ReferenceAng = 0 + + + def __del__(self): + QadCommandClass.__del__(self) + + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if (self.plugIn is not None): + if self.PointMapTool is None: + self.PointMapTool = Qad_rotate_maptool(self.plugIn) + return self.PointMapTool + else: + return None + + + # ============================================================================ + # setSelectedEntityGripPoints + # ============================================================================ + def setSelectedEntityGripPoints(self, entitySetGripPoints): + # lista delle entityGripPoint con dei grip point selezionati + self.cacheEntitySet.clear() + + for entityGripPoints in entitySetGripPoints.entityGripPoints: + self.cacheEntitySet.appendEntity(entityGripPoints.entity) + + self.getPointMapTool().cacheEntitySet = self.cacheEntitySet + + + # ============================================================================ + # rotate + # ============================================================================ + def rotate(self, entity, basePt, angle, rotFldName): + # entity = entità da ruotare + # basePt = punto base + # angle = angolo di rotazione in gradi + # verifico se l'entità appartiene ad uno stile di quotatura + if entity.whatIs() == "ENTITY": + # ruoto la geometria dell'entità + qadGeom = entity.getQadGeom().copy() # la copio + qadGeom.rotate(basePt, angle) + f = entity.getFeature() + f.setGeometry(fromQadGeomToQgsGeom(qadGeom, entity.layer)) + + if len(entity.rotFldName) > 0 is not None: + rotValue = f.attribute(entity.rotFldName) + rotValue = 0 if rotValue is None or rotValue == NULL else qad_utils.toRadians(rotValue) # la rotazione é in gradi nel campo della feature + rotValue = rotValue + angle + f.setAttribute(entity.rotFldName, qad_utils.toDegrees(qad_utils.normalizeAngle(rotValue))) + + if self.copyEntities == False: + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, entity.layer, f, False, False) == False: + return False + else: + # plugIn, layer, features, coordTransform, refresh, check_validity + if qad_layer.addFeatureToLayer(self.plugIn, entity.layer, f, None, False, False) == False: + return False + + elif entity.whatIs() == "DIMENTITY": + # stiro la quota + if self.copyEntities == False: + if entity.deleteToLayers(self.plugIn) == False: + return False + newDimEntity = QadDimEntity(entity) # la copio + newDimEntity.rotate(basePt, angle) + if newDimEntity.addToLayers(self.plugIn) == False: + return False + + return True + + + # ============================================================================ + # rotateFeatures + # ============================================================================ + def rotateFeatures(self, angle): + self.plugIn.beginEditCommand("Feature rotated", self.cacheEntitySet.getLayerList()) + + entityIterator = QadCacheEntitySetIterator(self.cacheEntitySet) + for entity in entityIterator: + qadGeom = entity.getQadGeom() # così inizializzo le info qad + # verifico se l'entità appartiene ad uno stile di quotatura + dimEntity = QadDimStyles.getDimEntity(entity) + if dimEntity is not None: + if appendDimEntityIfNotExisting(dimElaboratedList, dimEntity) == False: # quota già elaborata + continue + entity = dimEntity + + if self.rotate(entity, self.basePt, angle) == False: + self.plugIn.destroyEditCommand() + return + + self.plugIn.endEditCommand() + self.nOperationsToUndo = self.nOperationsToUndo + 1 + + + # ============================================================================ + # waitForRotatePoint + # ============================================================================ + def waitForRotatePoint(self): + self.step = 1 + self.plugIn.setLastPoint(self.basePt) + # imposto il map tool + self.getPointMapTool().basePt = self.basePt + self.getPointMapTool().setMode(Qad_rotate_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_NEW_ROTATION_PT) + + keyWords = QadMsg.translate("Command_GRIPROTATE", "Base point") + "/" + \ + QadMsg.translate("Command_GRIPROTATE", "Copy") + "/" + \ + QadMsg.translate("Command_GRIPROTATE", "Undo") + "/" + \ + QadMsg.translate("Command_GRIPROTATE", "Reference") + "/" + \ + QadMsg.translate("Command_GRIPROTATE", "eXit") + + prompt = QadMsg.translate("Command_GRIPROTATE", "Specify rotation angle or [{0}]: ").format(keyWords) + + englishKeyWords = "Base point" + "/" + "Copy" + "/" + "Undo" + "/" + "Reference" + "/" + "eXit" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto, un numero reale o enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, QadInputTypeEnum.POINT2D | QadInputTypeEnum.ANGLE | QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, QadInputModeEnum.NONE) + + + # ============================================================================ + # waitForBasePt + # ============================================================================ + def waitForBasePt(self): + self.step = 2 + # imposto il map tool + self.getPointMapTool().setMode(Qad_rotate_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_BASE_PT) + + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_GRIPROTATE", "Specify base point: ")) + + + def waitForReferenceRot(self): + # imposto il map tool + self.getPointMapTool().setMode(Qad_rotate_maptool_ModeEnum.ASK_FOR_FIRST_PT_REFERENCE_ANG) + + msg = QadMsg.translate("Command_GRIPROTATE", "Specify reference angle <{0}>: ") + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(msg.format(str(qad_utils.toDegrees(self.plugIn.lastReferenceRot))), \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.ANGLE, \ + self.plugIn.lastReferenceRot, \ + "") + self.step = 3 + + + # ============================================================================ + # run + # ============================================================================ + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + # ========================================================================= + # RICHIESTA SELEZIONE OGGETTI + if self.step == 0: # inizio del comando + if self.cacheEntitySet.isEmpty(): # non ci sono oggetti da ruotare + return True + self.showMsg(QadMsg.translate("Command_GRIPROTATE", "\n** ROTATE **\n")) + # si appresta ad attendere un punto di rotazione + self.waitForRotatePoint() + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DI UN PUNTO DI ROTAZIONE + elif self.step == 1: + ctrlKey = False + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + value = None + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: + value = self.getPointMapTool().point + + ctrlKey = self.getPointMapTool().ctrlKey + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("Command_GRIPROTATE", "Base point") or value == "Base point": + # si appresta ad attendere il punto base + self.waitForBasePt() + elif value == QadMsg.translate("Command_GRIPROTATE", "Copy") or value == "Copy": + # Copia entità lasciando inalterate le originali + self.copyEntities = True + # si appresta ad attendere un punto di rotazione + self.waitForRotatePoint() + elif value == QadMsg.translate("Command_GRIPROTATE", "Undo") or value == "Undo": + if self.nOperationsToUndo > 0: + self.nOperationsToUndo = self.nOperationsToUndo - 1 + self.plugIn.undoEditCommand() + else: + self.showMsg(QadMsg.translate("QAD", "\nThe command has been canceled.")) + # si appresta ad attendere un punto di rotazione + self.waitForRotatePoint() + elif value == QadMsg.translate("Command_GRIPROTATE", "Reference") or value == "Reference": + # si appresta ad attendere l'angolo di riferimento + self.waitForReferenceRot() + elif value == QadMsg.translate("Command_GRIPROTATE", "eXit") or value == "eXit": + return True # fine comando + elif type(value) == QgsPointXY or type(value) == float: # se é stato inserito l'angolo di rotazione + if type(value) == QgsPointXY: # se é stato inserito l'angolo di rotazione con un punto + angle = qad_utils.getAngleBy2Pts(self.basePt, value) + else: + angle = qad_utils.toRadians(value) + angle = angle - self.ReferenceAng + self.plugIn.setLastRot(angle) + + if ctrlKey: + self.copyEntities = True + + self.rotateFeatures(angle) + + if self.copyEntities == False: + return True + + # si appresta ad attendere un punto di rotazione + self.waitForRotatePoint() + + else: + if self.copyEntities == False: + self.skipToNextGripCommand = True + return True # fine comando + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA PUNTO BASE (da step = 1) + elif self.step == 2: # dopo aver atteso un punto + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + pass # opzione di default "spostamento" + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == QgsPointXY: # se é stato inserito il punto base + self.basePt.set(value.x(), value.y()) + # imposto il map tool + self.getPointMapTool().basePt = self.basePt + + # si appresta ad attendere un punto di rotazione + self.waitForRotatePoint() + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA PRIMO PUNTO PER ANGOLO ROTAZIONE DI RIFERIMENTO (da step = 1) + elif self.step == 3: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == float: # se é stato inserito l'angolo di rotazione + self.ReferenceAng = qad_utils.toRadians(value) + self.getPointMapTool().ReferenceAng = self.ReferenceAng + # si appresta ad attendere un punto di rotazione + self.waitForRotatePoint() + + elif type(value) == QgsPointXY: # se é stato inserito l'angolo di rotazione con un punto + self.Pt1ReferenceAng = QgsPointXY(value) + self.getPointMapTool().Pt1ReferenceAng = self.Pt1ReferenceAng + # imposto il map tool + self.getPointMapTool().setMode(Qad_rotate_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT_REFERENCE_ANG) + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_GRIPROTATE", "Specify second point: ")) + self.step = 4 + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA SECONDO PUNTO PER NUOVO ANGOLO ROTAZIONE (da step = 3) + elif self.step == 4: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == QgsPointXY or type(value) == float: # se é stato inserito l'angolo di rotazione + if type(value) == QgsPointXY: # se é stato inserito l'angolo di rotazione con un punto + self.ReferenceAng = qad_utils.getAngleBy2Pts(self.Pt1ReferenceAng, value) + else: + self.ReferenceAng = qad_utils.toRadians(value) + self.getPointMapTool().ReferenceAng = self.ReferenceAng + + # si appresta ad attendere un punto di rotazione + self.waitForRotatePoint() + + return False + diff --git a/cmd/qad_rotate_maptool.py b/cmd/qad_rotate_maptool.py new file mode 100644 index 00000000..53b0d946 --- /dev/null +++ b/cmd/qad_rotate_maptool.py @@ -0,0 +1,180 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin ok + + classe per gestire il map tool in ambito del comando rotate + + ------------------- + begin : 2013-09-27 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from .. import qad_utils +from ..qad_getpoint import QadGetPoint, QadGetPointDrawModeEnum, QadGetPointSelectionModeEnum +from ..qad_dim import QadDimStyles, appendDimEntityIfNotExisting, QadDimEntity +from ..qad_highlight import QadHighlight +from ..qad_entity import QadCacheEntitySetIterator, QadEntityTypeEnum +from ..qad_multi_geom import fromQadGeomToQgsGeom + + +# =============================================================================== +# Qad_rotate_maptool_ModeEnum class. +# =============================================================================== +class Qad_rotate_maptool_ModeEnum(): + # noto niente si richiede il punto base + NONE_KNOWN_ASK_FOR_BASE_PT = 1 + # noto il punto base si richiede il secondo punto per l'angolo di rotazione + BASE_PT_KNOWN_ASK_FOR_ROTATION_PT = 2 + # si richiede il primo punto per l'angolo di riferimento + ASK_FOR_FIRST_PT_REFERENCE_ANG = 3 + # noto il primo punto si richiede il secondo punto per l'angolo di riferimento + FIRST_PT_KNOWN_ASK_FOR_SECOND_PT_REFERENCE_ANG = 4 + # noto il punto base si richiede il secondo punto per il nuovo angolo + BASE_PT_KNOWN_ASK_FOR_NEW_ROTATION_PT = 5 + # si richiede il primo punto per il nuovo angolo + ASK_FOR_FIRST_NEW_ROTATION_PT = 6 + # noto il primo punto si richiede il secondo punto per il nuovo angolo + FIRST_PT_KNOWN_ASK_FOR_SECOND_NEW_ROTATION_PT = 7 + +# =============================================================================== +# Qad_rotate_maptool class +# =============================================================================== +class Qad_rotate_maptool(QadGetPoint): + + def __init__(self, plugIn): + QadGetPoint.__init__(self, plugIn) + + self.basePt = None + self.Pt1ReferenceAng = None + self.ReferenceAng = 0 + self.Pt1NewAng = None + self.cacheEntitySet = None + self.__highlight = QadHighlight(self.canvas) + + def hidePointMapToolMarkers(self): + QadGetPoint.hidePointMapToolMarkers(self) + self.__highlight.hide() + + def showPointMapToolMarkers(self): + QadGetPoint.showPointMapToolMarkers(self) + self.__highlight.show() + + def clear(self): + QadGetPoint.clear(self) + self.__highlight.reset() + self.mode = None + + + # ============================================================================ + # rotate + # ============================================================================ + def rotate(self, entity, basePt, angle): + # verifico se l'entità appartiene ad uno stile di quotatura + if entity.whatIs() == "ENTITY": + # ruoto la geometria dell'entità + qadGeom = entity.getQadGeom().copy() # la copio + qadGeom.rotate(basePt, angle) + self.__highlight.addGeometry(fromQadGeomToQgsGeom(qadGeom, entity.layer), entity.layer) + elif entity.whatIs() == "DIMENTITY": + newDimEntity = QadDimEntity(entity) # la copio + # ruoto la quota + newDimEntity.rotate(basePt, angle) + self.__highlight.addGeometry(newDimEntity.textualFeature.geometry(), newDimEntity.getTextualLayer()) + self.__highlight.addGeometries(newDimEntity.getLinearGeometryCollection(), newDimEntity.getLinearLayer()) + self.__highlight.addGeometries(newDimEntity.getSymbolGeometryCollection(), newDimEntity.getSymbolLayer()) + + + # ============================================================================ + # addRotatedGeometries + # ============================================================================ + def addRotatedGeometries(self, angle): + self.__highlight.reset() + + dimElaboratedList = [] # lista delle quotature già elaborate + entityIterator = QadCacheEntitySetIterator(self.cacheEntitySet) + for entity in entityIterator: + qadGeom = entity.getQadGeom() # così inizializzo le info qad + # verifico se l'entità appartiene ad uno stile di quotatura + dimEntity = QadDimStyles.getDimEntity(entity) + if dimEntity is not None: + if appendDimEntityIfNotExisting(dimElaboratedList, dimEntity) == False: # quota già elaborata + continue + entity = dimEntity + + self.rotate(entity, self.basePt, angle) + + + def canvasMoveEvent(self, event): + QadGetPoint.canvasMoveEvent(self, event) + + # noto il punto base si richiede il secondo punto per l'angolo di rotazione + if self.mode == Qad_rotate_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_ROTATION_PT: + angle = qad_utils.getAngleBy2Pts(self.basePt, self.tmpPoint) + self.addRotatedGeometries(angle) + # noto il punto base si richiede il secondo punto per il nuovo angolo + elif self.mode == Qad_rotate_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_NEW_ROTATION_PT: + angle = qad_utils.getAngleBy2Pts(self.basePt, self.tmpPoint) + diffAngle = angle - self.ReferenceAng + self.addRotatedGeometries(diffAngle) + # noto il primo punto si richiede il secondo punto per il nuovo angolo + elif self.mode == Qad_rotate_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_NEW_ROTATION_PT: + angle = qad_utils.getAngleBy2Pts(self.Pt1NewAng, self.tmpPoint) + diffAngle = angle - self.ReferenceAng + self.addRotatedGeometries(diffAngle) + + + def activate(self): + QadGetPoint.activate(self) + self.__highlight.show() + + def deactivate(self): + try: # necessario perché se si chiude QGIS parte questo evento nonostante non ci sia più l'oggetto maptool ! + QadGetPoint.deactivate(self) + self.__highlight.hide() + except: + pass + + def setMode(self, mode): + self.mode = mode + # noto niente si richiede il punto base + if self.mode == Qad_rotate_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_BASE_PT: + self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + self.__highlight.reset() + # noto il punto base si richiede il secondo punto per l'angolo di rotazione + elif self.mode == Qad_rotate_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_ROTATION_PT: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.basePt) + # si richiede il primo punto per l'angolo di riferimento + elif self.mode == Qad_rotate_maptool_ModeEnum.ASK_FOR_FIRST_PT_REFERENCE_ANG: + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + self.__highlight.reset() + # noto il primo punto si richiede il secondo punto per l'angolo di riferimento + elif self.mode == Qad_rotate_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT_REFERENCE_ANG: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.Pt1ReferenceAng) + # noto il punto base si richiede il secondo punto per il nuovo angolo + elif self.mode == Qad_rotate_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_NEW_ROTATION_PT: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.basePt) + # si richiede il primo punto per il nuovo angolo + elif self.mode == Qad_rotate_maptool_ModeEnum.ASK_FOR_FIRST_NEW_ROTATION_PT: + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + # noto il primo punto si richiede il secondo punto per il nuovo angolo + elif self.mode == Qad_rotate_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_NEW_ROTATION_PT: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.Pt1NewAng) diff --git a/qad_scale_cmd.py b/cmd/qad_scale_cmd.py similarity index 78% rename from qad_scale_cmd.py rename to cmd/qad_scale_cmd.py index e695d713..cc995aa8 100644 --- a/qad_scale_cmd.py +++ b/cmd/qad_scale_cmd.py @@ -1,886 +1,892 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - comando SCALE per scalare oggetti - - ------------------- - begin : 2013-10-01 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -from qad_scale_maptool import * -from qad_generic_cmd import QadCommandClass -from qad_msg import QadMsg -from qad_getpoint import * -from qad_textwindow import * -from qad_ssget_cmd import QadSSGetClass -from qad_entity import * -import qad_utils -import qad_layer -import qad_label -from qad_dim import * - - -# Classe che gestisce il comando SCALA -class QadSCALECommandClass(QadCommandClass): - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadSCALECommandClass(self.plugIn) - - def getName(self): - return QadMsg.translate("Command_list", "SCALE") - - def getEnglishName(self): - return "SCALE" - - def connectQAction(self, action): - QObject.connect(action, SIGNAL("triggered()"), self.plugIn.runSCALECommand) - - def getIcon(self): - return QIcon(":/plugins/qad/icons/scale.png") - - def getNote(self): - # impostare le note esplicative del comando - return QadMsg.translate("Command_SCALE", "Enlarges or reduces selected objects.") - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - self.SSGetClass = QadSSGetClass(plugIn) - self.SSGetClass.onlyEditableLayers = True - self.entitySet = QadEntitySet() - self.basePt = None - self.copyFeatures = False - self.Pt1ReferenceLen = None - self.ReferenceLen = 1 - self.Pt1NewLen = None - - def __del__(self): - QadCommandClass.__del__(self) - del self.SSGetClass - - def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): - if self.step == 0: # quando si é in fase di selezione entità - return self.SSGetClass.getPointMapTool() - else: - if (self.plugIn is not None): - if self.PointMapTool is None: - self.PointMapTool = Qad_scale_maptool(self.plugIn) - return self.PointMapTool - else: - return None - - - #============================================================================ - # scale - #============================================================================ - def scale(self, f, basePt, scale, sizeFldName, layerEntitySet, entitySet): - # verifico se l'entità appartiene ad uno stile di quotatura - dimEntity = QadDimStyles.getDimEntity(layerEntitySet.layer, f.id()) - - if dimEntity is None: - # scalo la feature e la rimuovo da entitySet (é la prima) - f.setGeometry(qad_utils.scaleQgsGeometry(f.geometry(), basePt, scale)) - if sizeFldName is not None: - sizeValue = f.attribute(sizeFldName) - if sizeValue is None: - sizeValue = 1 - sizeValue = sizeValue * scale - f.setAttribute(sizeFldName, sizeValue) - - if self.copyFeatures == False: - # plugIn, layer, feature, refresh, check_validity - if qad_layer.updateFeatureToLayer(self.plugIn, layerEntitySet.layer, f, False, False) == False: - self.plugIn.destroyEditCommand() - return False - else: - # plugIn, layer, features, coordTransform, refresh, check_validity - if qad_layer.addFeatureToLayer(self.plugIn, layerEntitySet.layer, f, None, False, False) == False: - self.plugIn.destroyEditCommand() - return False - - del layerEntitySet.featureIds[0] - else: - # scalo la quota e la rimuovo da entitySet - dimEntitySet = dimEntity.getEntitySet() - if self.copyFeatures == False: - if dimEntity.deleteToLayers(self.plugIn) == False: - return False - dimEntity.scale(self.plugIn,basePt, scale) - if dimEntity.addToLayers(self.plugIn) == False: - return False - entitySet.subtract(dimEntitySet) - - - def scaleGeoms(self, scale): - # copio entitySet - entitySet = QadEntitySet(self.entitySet) - - self.plugIn.beginEditCommand("Feature scaled", self.entitySet.getLayerList()) - - for layerEntitySet in entitySet.layerEntitySetList: - layer = layerEntitySet.layer - - transformedBasePt = self.mapToLayerCoordinates(layer, self.basePt) - - sizeFldName = None - if qad_layer.isTextLayer(layer): - # se l'altezza testo dipende da un solo campo - sizeFldNames = qad_label.get_labelSizeFieldNames(layer) - if len(sizeFldNames) == 1 and len(sizeFldNames[0]) > 0: - sizeFldName = sizeFldNames[0] - elif qad_layer.isSymbolLayer(layer): - # se la scala dipende da un campo - sizeFldName = qad_layer.get_symbolScaleFieldName(layer) - if len(sizeFldName) == 0: - sizeFldName = None - - while len(layerEntitySet.featureIds) > 0: - featureId = layerEntitySet.featureIds[0] - f = layerEntitySet.getFeature(featureId) - - if self.scale(f, transformedBasePt, scale, sizeFldName, layerEntitySet, entitySet) == False: - self.plugIn.destroyEditCommand() - return - - self.plugIn.endEditCommand() - - - def waitForScale(self): - # imposto il map tool - self.getPointMapTool().setMode(Qad_scale_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_SCALE_PT) - - keyWords = QadMsg.translate("Command_SCALE", "Copy") + "/" + \ - QadMsg.translate("Command_SCALE", "Reference") - default = self.plugIn.lastScale - prompt = QadMsg.translate("Command_SCALE", "Specify scale factor or [{0}] <{1}>: ").format(keyWords, str(default)) - - englishKeyWords = "Copy" + "/" + "Reference" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o enter o una parola chiave - # msg, inputType, default, keyWords, valori positivi - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT | QadInputTypeEnum.KEYWORDS, \ - default, \ - keyWords, QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) - self.step = 3 - - - def waitForReferenceLen(self): - # imposto il map tool - self.getPointMapTool().setMode(Qad_scale_maptool_ModeEnum.ASK_FOR_FIRST_PT_REFERENCE_LEN) - - msg = QadMsg.translate("Command_SCALE", "Specify reference length <{0}>: ") - # si appresta ad attendere un punto o enter - # msg, inputType, default, keyWords, valori positivi - self.waitFor(msg.format(str(self.plugIn.lastReferenceLen)), \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT, \ - self.plugIn.lastReferenceLen, \ - "", QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) - self.step = 4 - - - def waitForNewReferenceLen(self): - # imposto il map tool - self.getPointMapTool().setMode(Qad_scale_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_NEW_LEN_PT) - - keyWords = QadMsg.translate("Command_SCALE", "Points") - if self.plugIn.lastNewReferenceLen == 0: - default = self.plugIn.lastScale - else: - default = self.plugIn.lastNewReferenceLen - prompt = QadMsg.translate("Command_SCALE", "Specify new length or [{0}] <{1}>: ").format(keyWords, str(default)) - - englishKeyWords = "Points" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o enter o una parola chiave - # msg, inputType, default, keyWords, valori positivi - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT | QadInputTypeEnum.KEYWORDS, \ - default, \ - keyWords, QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) - self.step = 6 - - - def run(self, msgMapTool = False, msg = None): - if self.plugIn.canvas.mapRenderer().destinationCrs().geographicFlag(): - self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) - return True # fine comando - - #========================================================================= - # RICHIESTA SELEZIONE OGGETTI - if self.step == 0: # inizio del comando - if self.SSGetClass.run(msgMapTool, msg) == True: - # selezione terminata - self.step = 1 - self.getPointMapTool().refreshSnapType() # aggiorno lo snapType che può essere variato dal maptool di selezione entità - return self.run(msgMapTool, msg) - - #========================================================================= - # RUOTA OGGETTI - elif self.step == 1: - self.entitySet.set(self.SSGetClass.entitySet) - - if self.entitySet.count() == 0: - return True # fine comando - - # imposto il map tool - self.getPointMapTool().setMode(Qad_scale_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_BASE_PT) - - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_SCALE", "Specify base point: ")) - - self.step = 2 - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA PUNTO BASE (da step = 1) - elif self.step == 2: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - self.basePt = QgsPoint(value) - - self.getPointMapTool().basePt = self.basePt - self.getPointMapTool().entitySet.set(self.entitySet) - # si appresta ad attendere la scala - self.waitForScale() - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA SECONDO PUNTO PER SCALA (da step = 2) - elif self.step == 3: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("Command_SCALE", "Copy") or value == "Copy": - self.copyFeatures = True - self.showMsg(QadMsg.translate("Command_SCALE", "\nScale of a copy of the selected objects.")) - # si appresta ad attendere la scala - self.waitForScale() - elif value == QadMsg.translate("Command_SCALE", "Reference") or value == "Reference": - # si appresta ad attendere la lunghezza di riferimento - self.waitForReferenceLen() - elif type(value) == QgsPoint or type(value) == float: # se é stato inserita la scala - if type(value) == QgsPoint: # se é stato inserita la scala con un punto - if value == self.basePt: - self.showMsg(QadMsg.translate("QAD", "\nThe value must be positive and not zero.")) - # si appresta ad attendere un punto - self.waitForScale() - return False - - scale = qad_utils.getDistance(self.basePt, value) - else: - scale = value - self.plugIn.setLastScale(scale) - - self.scaleGeoms(scale) - return True # fine comando - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA PRIMO PUNTO PER LUNGHEZZA DI RIFERIMENTO (da step = 3) - elif self.step == 4: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == float: # se é stato inserita la lunghezza - self.ReferenceLen = value - self.getPointMapTool().ReferenceLen = self.ReferenceLen - # si appresta ad attendere la nuova lunghezza - self.waitForNewReferenceLen() - - elif type(value) == QgsPoint: # se é stato inserito la scala con un punto - self.Pt1ReferenceLen = QgsPoint(value) - self.getPointMapTool().Pt1ReferenceLen = self.Pt1ReferenceLen - # imposto il map tool - self.getPointMapTool().setMode(Qad_scale_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT_REFERENCE_LEN) - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_SCALE", "Specify second point: ")) - self.step = 5 - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA SECONDO PUNTO PER LUNGHEZZA DI RIFERIMENTO (da step = 4) - elif self.step == 5: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if self.Pt1ReferenceLen == value: - self.showMsg(QadMsg.translate("QAD", "\nThe value must be positive and not zero.")) - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_SCALE", "Specify second point: ")) - return False - - length = qad_utils.getDistance(self.Pt1ReferenceLen, value) - self.ReferenceLen = length - self.getPointMapTool().ReferenceLen = self.ReferenceLen - # si appresta ad attendere la nuova lunghezza - self.waitForNewReferenceLen() - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA SECONDO PUNTO PER NUOVA LUNGHEZZA (da step = 4 e 5) - elif self.step == 6: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("Command_SCALE", "Points") or value == "Points": - # imposto il map tool - self.getPointMapTool().setMode(Qad_scale_maptool_ModeEnum.ASK_FOR_FIRST_NEW_LEN_PT) - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_SCALE", "Specify first point: ")) - self.step = 7 - elif type(value) == QgsPoint or type(value) == float: # se é stato inserita la lunghezza - if type(value) == QgsPoint: # se é stato inserito la lunghezza con un punto - if value == self.basePt: - self.showMsg(QadMsg.translate("QAD", "\nThe value must be positive and not zero.")) - # si appresta ad attendere un punto - self.waitForNewReferenceLen() - return False - - length = qad_utils.getDistance(self.basePt, value) - else: - length = value - - scale = length / self.ReferenceLen - self.plugIn.setLastScale(scale) - self.scaleGeoms(scale) - return True # fine comando - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA PRIMO PUNTO PER NUOVA LUNGHEZZA (da step = 6) - elif self.step == 7: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - self.Pt1NewLen = value - # imposto il map tool - self.getPointMapTool().Pt1NewLen = self.Pt1NewLen - self.getPointMapTool().setMode(Qad_scale_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_NEW_LEN_PT) - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_SCALE", "Specify second point: ")) - self.step = 8 - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA SECONDO PUNTO PER NUOVA LUNGHEZZA (da step = 7) - elif self.step == 8: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if value == self.Pt1NewLen: - self.showMsg(QadMsg.translate("QAD", "\nThe value must be positive and not zero.")) - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_SCALE", "Specify second point: ")) - return False - - length = qad_utils.getDistance(self.Pt1NewLen, value) - - scale = length / self.ReferenceLen - self.plugIn.setLastScale(scale) - self.scaleGeoms(scale) - return True # fine comando - - -# Classe che gestisce il comando SCALA per i grip -class QadGRIPSCALECommandClass(QadCommandClass): - - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadGRIPSCALECommandClass(self.plugIn) - - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - self.entitySet = QadEntitySet() - self.basePt = QgsPoint() - self.skipToNextGripCommand = False - self.copyEntities = False - self.nOperationsToUndo = 0 - self.Pt1ReferenceLen = None - self.ReferenceLen = 1 - self.Pt1NewLen = None - self.__referenceLenMode = False - - def __del__(self): - QadCommandClass.__del__(self) - - def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): - if (self.plugIn is not None): - if self.PointMapTool is None: - self.PointMapTool = Qad_scale_maptool(self.plugIn) - return self.PointMapTool - else: - return None - - - #============================================================================ - # setSelectedEntityGripPoints - #============================================================================ - def setSelectedEntityGripPoints(self, entitySetGripPoints): - # lista delle entityGripPoint con dei grip point selezionati - self.entitySet.clear() - - for entityGripPoints in entitySetGripPoints.entityGripPoints: - self.entitySet.addEntity(entityGripPoints.entity) - self.getPointMapTool().entitySet.set(self.entitySet) - - - #============================================================================ - # scale - #============================================================================ - def scale(self, entity, basePt, scale, sizeFldName): - # entity = entità da scalare - # basePt = punto base - # scale = fattore di scala - # sizeFldName = campo della tabella che memorizza la scala - # verifico se l'entità appartiene ad uno stile di quotatura - if entity.whatIs() == "ENTITY": - f = entity.getFeature() - # ruoto l'entità - f.setGeometry(qad_utils.scaleQgsGeometry(entity.getGeometry(), basePt, scale)) - if len(sizeFldName) > 0: - sizeValue = f.attribute(sizeFldName) - if sizeValue is None: - sizeValue = 1 - sizeValue = sizeValue * scale - f.setAttribute(sizeFldName, sizeValue) - - if self.copyEntities == False: - # plugIn, layer, feature, refresh, check_validity - if qad_layer.updateFeatureToLayer(self.plugIn, entity.layer, f, False, False) == False: - return False - else: - # plugIn, layer, features, coordTransform, refresh, check_validity - if qad_layer.addFeatureToLayer(self.plugIn, entity.layer, f, None, False, False) == False: - return False - - elif entity.whatIs() == "DIMENTITY": - # stiro la quota - if self.copyEntities == False: - if entity.deleteToLayers(self.plugIn) == False: - return False - entity.scale(self.plugIn, basePt, scale) - if entity.addToLayers(self.plugIn) == False: - return False - - - def scaleFeatures(self, scale): - entity = QadEntity() - self.plugIn.beginEditCommand("Feature scaled", self.entitySet.getLayerList()) - - dimElaboratedList = [] # lista delle quotature già elaborate - - for layerEntitySet in self.entitySet.layerEntitySetList: - layer = layerEntitySet.layer - - transformedBasePt = self.mapToLayerCoordinates(layer, self.basePt) - - sizeFldName = "" - if qad_layer.isTextLayer(layer): - # se l'altezza testo dipende da un solo campo - sizeFldNames = qad_label.get_labelSizeFieldNames(layer) - if len(sizeFldNames) == 1 and len(sizeFldNames[0]) > 0: - sizeFldName = sizeFldNames[0] - elif qad_layer.isSymbolLayer(layer): - # se la scala dipende da un campo - sizeFldName = qad_layer.get_symbolScaleFieldName(layer) - - for featureId in layerEntitySet.featureIds: - entity.set(layer, featureId) - - # verifico se l'entità appartiene ad uno stile di quotatura - dimEntity = QadDimStyles.getDimEntity(entity) - if dimEntity is None: - if self.scale(entity, transformedBasePt, scale, sizeFldName) == False: - self.plugIn.destroyEditCommand() - return - else: - found = False - for dimElaborated in dimElaboratedList: - if dimElaborated == dimEntity: - found = True - - if found == False: # quota non ancora elaborata - dimElaboratedList.append(dimEntity) - if self.scale(dimEntity, transformedBasePt, scale, sizeFldName) == False: - self.plugIn.destroyEditCommand() - return - - self.plugIn.endEditCommand() - self.nOperationsToUndo = self.nOperationsToUndo + 1 - - - def waitForScale(self): - - self.getPointMapTool().basePt = self.basePt - keyWords = QadMsg.translate("Command_GRIPSCALE", "Base point") + "/" + \ - QadMsg.translate("Command_GRIPSCALE", "Copy") + "/" + \ - QadMsg.translate("Command_GRIPSCALE", "Undo") + "/" + \ - QadMsg.translate("Command_GRIPSCALE", "Reference") + "/" + \ - QadMsg.translate("Command_GRIPSCALE", "eXit") - - default = self.plugIn.lastScale - if self.__referenceLenMode: - # imposto il map tool - self.getPointMapTool().setMode(Qad_scale_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_NEW_LEN_PT) - if self.plugIn.lastNewReferenceLen > 0: - default = self.plugIn.lastNewReferenceLen - prompt = QadMsg.translate("Command_GRIPSCALE", "Specify new length or [{0}] <{1}>: ").format(keyWords, str(default)) - else: - # imposto il map tool - self.getPointMapTool().setMode(Qad_scale_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_SCALE_PT) - prompt = QadMsg.translate("Command_GRIPSCALE", "Specify scale factor or [{0}] <{1}>: ").format(keyWords, str(default)) - - englishKeyWords = "Base point" + "/" + "Copy" + "/" + "Undo" + "/" + "Reference" + "/" + "eXit" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o enter o una parola chiave - # msg, inputType, default, keyWords, valori positivi - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT | QadInputTypeEnum.KEYWORDS, \ - default, \ - keyWords, QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) - self.step = 1 - - - #============================================================================ - # waitForBasePt - #============================================================================ - def waitForBasePt(self): - self.step = 2 - # imposto il map tool - self.getPointMapTool().setMode(Qad_scale_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_BASE_PT) - - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_GRIPSCALE", "Specify base point: ")) - - - def waitForReferenceLen(self): - self.__referenceLenMode = True - # imposto il map tool - self.getPointMapTool().setMode(Qad_scale_maptool_ModeEnum.ASK_FOR_FIRST_PT_REFERENCE_LEN) - - msg = QadMsg.translate("Command_GRIPSCALE", "Specify reference length <{0}>: ") - # si appresta ad attendere un punto o enter - # msg, inputType, default, keyWords, valori positivi - self.waitFor(msg.format(str(self.plugIn.lastReferenceLen)), \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT, \ - self.plugIn.lastReferenceLen, \ - "", QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) - self.step = 3 - - - def run(self, msgMapTool = False, msg = None): - if self.plugIn.canvas.mapRenderer().destinationCrs().geographicFlag(): - self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) - return True # fine comando - - #========================================================================= - # RICHIESTA SELEZIONE OGGETTI - if self.step == 0: # inizio del comando - if self.entitySet.isEmpty(): # non ci sono oggetti da ruotare - return True - self.showMsg(QadMsg.translate("Command_GRIPSCALE", "\n** SCALE **\n")) - # si appresta ad attendere la scala - self.waitForScale() - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DEL FATTORE DI SCALA - elif self.step == 1: - ctrlKey = False - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - value = None - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - else: - value = self.getPointMapTool().point - - ctrlKey = self.getPointMapTool().ctrlKey - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("Command_GRIPSCALE", "Base point") or value == "Base point": - # si appresta ad attendere il punto base - self.waitForBasePt() - elif value == QadMsg.translate("Command_GRIPSCALE", "Copy") or value == "Copy": - # Copia entità lasciando inalterate le originali - self.copyEntities = True - # si appresta ad attendere la scala - self.waitForScale() - elif value == QadMsg.translate("Command_GRIPSCALE", "Undo") or value == "Undo": - if self.nOperationsToUndo > 0: - self.nOperationsToUndo = self.nOperationsToUndo - 1 - self.plugIn.undoEditCommand() - else: - self.showMsg(QadMsg.translate("QAD", "\nThe command has been canceled.")) - # si appresta ad attendere la scala - self.waitForScale() - elif value == QadMsg.translate("Command_GRIPSCALE", "Reference") or value == "Reference": - # si appresta ad attendere la lunghezza di riferimento - self.waitForReferenceLen() - elif value == QadMsg.translate("Command_GRIPSCALE", "eXit") or value == "eXit": - return True # fine comando - elif type(value) == QgsPoint or type(value) == float: # se é stato inserita la scala - if type(value) == QgsPoint: # se é stato inserita la scala con un punto - if value == self.basePt: - self.showMsg(QadMsg.translate("QAD", "\nThe value must be positive and not zero.")) - # si appresta ad attendere un punto - self.waitForScale() - return False - - scale = qad_utils.getDistance(self.basePt, value) - else: - scale = value - - if self.__referenceLenMode == True: # scale rappresenta la nuova distanza di riferimento - scale = scale / self.ReferenceLen - - self.plugIn.setLastScale(scale) - - if ctrlKey: - self.copyEntities = True - - self.scaleFeatures(scale) - - if self.copyEntities == False: - return True - - # si appresta ad attendere la scala - self.waitForScale() - - else: - if self.copyEntities == False: - self.skipToNextGripCommand = True - return True # fine comando - - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA PUNTO BASE (da step = 1) - elif self.step == 2: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - self.basePt = QgsPoint(value) - - self.getPointMapTool().basePt = self.basePt - # si appresta ad attendere la scala - self.waitForScale() - - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA PRIMO PUNTO PER LUNGHEZZA DI RIFERIMENTO (da step = 1) - elif self.step == 3: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == float: # se é stato inserita la lunghezza - self.ReferenceLen = value - self.getPointMapTool().ReferenceLen = self.ReferenceLen - # si appresta ad attendere la nuova lunghezza - self.waitForScale() - - elif type(value) == QgsPoint: # se é stato inserito la scala con un punto - self.Pt1ReferenceLen = QgsPoint(value) - self.getPointMapTool().Pt1ReferenceLen = self.Pt1ReferenceLen - # imposto il map tool - self.getPointMapTool().setMode(Qad_scale_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT_REFERENCE_LEN) - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_GRIPSCALE", "Specify second point: ")) - self.step = 4 - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA SECONDO PUNTO PER LUNGHEZZA DI RIFERIMENTO (da step = 4) - elif self.step == 4: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if self.Pt1ReferenceLen == value: - self.showMsg(QadMsg.translate("QAD", "\nThe value must be positive and not zero.")) - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_GRIPSCALE", "Specify second point: ")) - return False - - length = qad_utils.getDistance(self.Pt1ReferenceLen, value) - self.ReferenceLen = length - self.getPointMapTool().ReferenceLen = self.ReferenceLen - # si appresta ad attendere la nuova lunghezza - self.waitForScale() - +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin ok + + comando SCALE per scalare oggetti + + ------------------- + begin : 2013-10-01 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.core import QgsPointXY, NULL +from qgis.PyQt.QtCore import QVariant +from qgis.PyQt.QtGui import QIcon + + +from ..qad_entity import QadCacheEntitySet, QadCacheEntitySetIterator, QadEntityTypeEnum +from .qad_scale_maptool import Qad_scale_maptool_ModeEnum, Qad_scale_maptool +from .qad_generic_cmd import QadCommandClass +from ..qad_msg import QadMsg +from ..qad_getpoint import QadGetPointDrawModeEnum +from ..qad_textwindow import QadInputTypeEnum, QadInputModeEnum +from .qad_ssget_cmd import QadSSGetClass +from .. import qad_utils +from .. import qad_layer +from .. import qad_label +from ..qad_dim import QadDimStyles, appendDimEntityIfNotExisting, QadDimEntity +from ..qad_multi_geom import fromQadGeomToQgsGeom + + +# Classe che gestisce il comando SCALA +class QadSCALECommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadSCALECommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "SCALE") + + def getEnglishName(self): + return "SCALE" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runSCALECommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/scale.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_SCALE", "Enlarges or reduces selected objects.") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + self.SSGetClass = QadSSGetClass(plugIn) + self.SSGetClass.onlyEditableLayers = True + self.cacheEntitySet = QadCacheEntitySet() + self.basePt = None + self.copyFeatures = False + self.Pt1ReferenceLen = None + self.ReferenceLen = 1 + self.Pt1NewLen = None + + def __del__(self): + QadCommandClass.__del__(self) + del self.SSGetClass + + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if self.step == 0: # quando si é in fase di selezione entità + return self.SSGetClass.getPointMapTool() + else: + if (self.plugIn is not None): + if self.PointMapTool is None: + self.PointMapTool = Qad_scale_maptool(self.plugIn) + return self.PointMapTool + else: + return None + + + def getCurrentContextualMenu(self): + if self.step == 0: # quando si é in fase di selezione entità + return None # return self.SSGetClass.getCurrentContextualMenu() + else: + return self.contextualMenu + + + # ============================================================================ + # scale + # ============================================================================ + def scale(self, entity, basePt, scale, openForm = True): + # verifico se l'entità appartiene ad uno stile di quotatura + if entity.whatIs() == "ENTITY": + # sposto la geometria dell'entità + qadGeom = entity.getQadGeom().copy() # la copio + qadGeom.scale(basePt, scale) + f = entity.getFeature() + f.setGeometry(fromQadGeomToQgsGeom(qadGeom, entity.layer)) + + sizeFldName = None + if entity.isTextualLayer: + # se l'altezza testo dipende da un solo campo + sizeFldNames = qad_label.get_labelSizeFieldNames(entity.layer) + if len(sizeFldNames) == 1 and len(sizeFldNames[0]) > 0: + sizeFldName = sizeFldNames[0] + elif entity.isSymbolLayer: + # se la scala dipende da un campo + sizeFldName = qad_layer.get_symbolScaleFieldName(entity.layer) + if len(sizeFldName) == 0: + sizeFldName = None + + if sizeFldName is not None: + sizeValue = f.attribute(sizeFldName) + if sizeValue is None or sizeValue == NULL: + sizeValue = 1 + sizeValue = sizeValue * scale + f.setAttribute(sizeFldName, sizeValue) + + if self.copyFeatures == False: + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, entity.layer, f, False, False) == False: + self.plugIn.destroyEditCommand() + return False + else: + # plugIn, layer, features, coordTransform, refresh, check_validity + if qad_layer.addFeatureToLayer(self.plugIn, entity.layer, f, None, False, False, openForm) == False: + self.plugIn.destroyEditCommand() + return False + elif entity.whatIs() == "DIMENTITY": + if self.copyFeatures == False: + if dimEntity.deleteToLayers(self.plugIn) == False: + return False + newDimEntity = QadDimEntity(entity) # la copio + newDimEntity.scale(basePt, scale) + if newDimEntity.addToLayers(self.plugIn) == False: + return False + + return True + + + # ============================================================================ + # scaleGeoms + # ============================================================================ + def scaleGeoms(self, scale): + self.plugIn.beginEditCommand("Feature scaled", self.cacheEntitySet.getLayerList()) + + dimElaboratedList = [] # lista delle quotature già elaborate + entityIterator = QadCacheEntitySetIterator(self.cacheEntitySet) + openForm = True if self.cacheEntitySet.count() == 1 else False + + for entity in entityIterator: + qadGeom = entity.getQadGeom() # così inizializzo le info qad + # verifico se l'entità appartiene ad uno stile di quotatura + dimEntity = QadDimStyles.getDimEntity(entity) + if dimEntity is not None: + if appendDimEntityIfNotExisting(dimElaboratedList, dimEntity) == False: # quota già elaborata + continue + entity = dimEntity + + if self.scale(entity, self.basePt, scale, openForm) == False: + self.plugIn.destroyEditCommand() + return + + self.plugIn.endEditCommand() + + + def waitForScale(self): + # imposto il map tool + self.getPointMapTool().setMode(Qad_scale_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_SCALE_PT) + + keyWords = QadMsg.translate("Command_SCALE", "Copy") + "/" + \ + QadMsg.translate("Command_SCALE", "Reference") + default = self.plugIn.lastScale + prompt = QadMsg.translate("Command_SCALE", "Specify scale factor or [{0}] <{1}>: ").format(keyWords, str(default)) + + englishKeyWords = "Copy" + "/" + "Reference" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, valori positivi + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT | QadInputTypeEnum.KEYWORDS, \ + default, \ + keyWords, QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) + self.step = 3 + + + def waitForReferenceLen(self): + # imposto il map tool + self.getPointMapTool().setMode(Qad_scale_maptool_ModeEnum.ASK_FOR_FIRST_PT_REFERENCE_LEN) + + msg = QadMsg.translate("Command_SCALE", "Specify reference length <{0}>: ") + # si appresta ad attendere un punto o enter + # msg, inputType, default, keyWords, valori positivi + self.waitFor(msg.format(str(self.plugIn.lastReferenceLen)), \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT, \ + self.plugIn.lastReferenceLen, \ + "", QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) + self.step = 4 + + + def waitForNewReferenceLen(self): + # imposto il map tool + self.getPointMapTool().setMode(Qad_scale_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_NEW_LEN_PT) + + keyWords = QadMsg.translate("Command_SCALE", "Points") + if self.plugIn.lastNewReferenceLen == 0: + default = self.plugIn.lastScale + else: + default = self.plugIn.lastNewReferenceLen + prompt = QadMsg.translate("Command_SCALE", "Specify new length or [{0}] <{1}>: ").format(keyWords, str(default)) + + englishKeyWords = "Points" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, valori positivi + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT | QadInputTypeEnum.KEYWORDS, \ + default, \ + keyWords, QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) + self.step = 6 + + + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + # ========================================================================= + # RICHIESTA SELEZIONE OGGETTI + if self.step == 0: # inizio del comando + if self.SSGetClass.run(msgMapTool, msg) == True: + # selezione terminata + self.step = 1 + self.getPointMapTool().refreshSnapType() # aggiorno lo snapType che può essere variato dal maptool di selezione entità + return self.run(msgMapTool, msg) + + # ========================================================================= + # RUOTA OGGETTI + elif self.step == 1: + if self.SSGetClass.entitySet.count() == 0: + return True # fine comando + self.cacheEntitySet.appendEntitySet(self.SSGetClass.entitySet) + + # imposto il map tool + self.getPointMapTool().setMode(Qad_scale_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_BASE_PT) + + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_SCALE", "Specify base point: ")) + + self.step = 2 + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA PUNTO BASE (da step = 1) + elif self.step == 2: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + self.basePt = QgsPointXY(value) + + self.getPointMapTool().basePt = self.basePt + self.getPointMapTool().cacheEntitySet = self.cacheEntitySet + # si appresta ad attendere la scala + self.waitForScale() + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA SECONDO PUNTO PER SCALA (da step = 2) + elif self.step == 3: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("Command_SCALE", "Copy") or value == "Copy": + self.copyFeatures = True + self.showMsg(QadMsg.translate("Command_SCALE", "\nScale of a copy of the selected objects.")) + # si appresta ad attendere la scala + self.waitForScale() + elif value == QadMsg.translate("Command_SCALE", "Reference") or value == "Reference": + # si appresta ad attendere la lunghezza di riferimento + self.waitForReferenceLen() + elif type(value) == QgsPointXY or type(value) == float: # se é stato inserita la scala + if type(value) == QgsPointXY: # se é stato inserita la scala con un punto + if value == self.basePt: + self.showMsg(QadMsg.translate("QAD", "\nThe value must be positive and not zero.")) + # si appresta ad attendere un punto + self.waitForScale() + return False + + scale = qad_utils.getDistance(self.basePt, value) + else: + scale = value + self.plugIn.setLastScale(scale) + + self.scaleGeoms(scale) + return True # fine comando + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA PRIMO PUNTO PER LUNGHEZZA DI RIFERIMENTO (da step = 3) + elif self.step == 4: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == float: # se é stato inserita la lunghezza + self.ReferenceLen = value + self.getPointMapTool().ReferenceLen = self.ReferenceLen + # si appresta ad attendere la nuova lunghezza + self.waitForNewReferenceLen() + + elif type(value) == QgsPointXY: # se é stato inserito la scala con un punto + self.Pt1ReferenceLen = QgsPointXY(value) + self.getPointMapTool().Pt1ReferenceLen = self.Pt1ReferenceLen + # imposto il map tool + self.getPointMapTool().setMode(Qad_scale_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT_REFERENCE_LEN) + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_SCALE", "Specify second point: ")) + self.step = 5 + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA SECONDO PUNTO PER LUNGHEZZA DI RIFERIMENTO (da step = 4) + elif self.step == 5: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if self.Pt1ReferenceLen == value: + self.showMsg(QadMsg.translate("QAD", "\nThe value must be positive and not zero.")) + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_SCALE", "Specify second point: ")) + return False + + length = qad_utils.getDistance(self.Pt1ReferenceLen, value) + self.ReferenceLen = length + self.getPointMapTool().ReferenceLen = self.ReferenceLen + # si appresta ad attendere la nuova lunghezza + self.waitForNewReferenceLen() + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA SECONDO PUNTO PER NUOVA LUNGHEZZA (da step = 4 e 5) + elif self.step == 6: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("Command_SCALE", "Points") or value == "Points": + # imposto il map tool + self.getPointMapTool().setMode(Qad_scale_maptool_ModeEnum.ASK_FOR_FIRST_NEW_LEN_PT) + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_SCALE", "Specify first point: ")) + self.step = 7 + elif type(value) == QgsPointXY or type(value) == float: # se é stato inserita la lunghezza + if type(value) == QgsPointXY: # se é stato inserito la lunghezza con un punto + if value == self.basePt: + self.showMsg(QadMsg.translate("QAD", "\nThe value must be positive and not zero.")) + # si appresta ad attendere un punto + self.waitForNewReferenceLen() + return False + + length = qad_utils.getDistance(self.basePt, value) + else: + length = value + + scale = length / self.ReferenceLen + self.plugIn.setLastScale(scale) + self.scaleGeoms(scale) + return True # fine comando + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA PRIMO PUNTO PER NUOVA LUNGHEZZA (da step = 6) + elif self.step == 7: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + self.Pt1NewLen = value + # imposto il map tool + self.getPointMapTool().Pt1NewLen = self.Pt1NewLen + self.getPointMapTool().setMode(Qad_scale_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_NEW_LEN_PT) + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_SCALE", "Specify second point: ")) + self.step = 8 + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA SECONDO PUNTO PER NUOVA LUNGHEZZA (da step = 7) + elif self.step == 8: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if value == self.Pt1NewLen: + self.showMsg(QadMsg.translate("QAD", "\nThe value must be positive and not zero.")) + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_SCALE", "Specify second point: ")) + return False + + length = qad_utils.getDistance(self.Pt1NewLen, value) + + scale = length / self.ReferenceLen + self.plugIn.setLastScale(scale) + self.scaleGeoms(scale) + return True # fine comando + + +# Classe che gestisce il comando SCALA per i grip +class QadGRIPSCALECommandClass(QadCommandClass): + + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadGRIPSCALECommandClass(self.plugIn) + + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + self.cacheEntitySet = QadCacheEntitySet() + self.basePt = QgsPointXY() + self.skipToNextGripCommand = False + self.copyEntities = False + self.nOperationsToUndo = 0 + self.Pt1ReferenceLen = None + self.ReferenceLen = 1 + self.Pt1NewLen = None + self.__referenceLenMode = False + + def __del__(self): + QadCommandClass.__del__(self) + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if (self.plugIn is not None): + if self.PointMapTool is None: + self.PointMapTool = Qad_scale_maptool(self.plugIn) + return self.PointMapTool + else: + return None + + + # ============================================================================ + # setSelectedEntityGripPoints + # ============================================================================ + def setSelectedEntityGripPoints(self, entitySetGripPoints): + # lista delle entityGripPoint con dei grip point selezionati + self.cacheEntitySet.clear() + + for entityGripPoints in entitySetGripPoints.entityGripPoints: + self.cacheEntitySet.appendEntity(entityGripPoints.entity) + + self.getPointMapTool().cacheEntitySet = self.cacheEntitySet + + + # ============================================================================ + # scale + # ============================================================================ + def scale(self, entity, basePt, scale, sizeFldName, openForm = True): + # entity = entità da scalare + # basePt = punto base + # scale = fattore di scala + # sizeFldName = campo della tabella che memorizza la scala + # verifico se l'entità appartiene ad uno stile di quotatura + if entity.whatIs() == "ENTITY": + # sposto la geometria dell'entità + qadGeom = entity.getQadGeom().copy() # la copio + qadGeom.scale(basePt, scale) + f = entity.getFeature() + f.setGeometry(fromQadGeomToQgsGeom(qadGeom, entity.layer)) + + sizeFldName = None + if entity.isTextualLayer: + # se l'altezza testo dipende da un solo campo + sizeFldNames = qad_label.get_labelSizeFieldNames(entity.layer) + if len(sizeFldNames) == 1 and len(sizeFldNames[0]) > 0: + sizeFldName = sizeFldNames[0] + elif entity.isSymbolLayer: + # se la scala dipende da un campo + sizeFldName = qad_layer.get_symbolScaleFieldName(entity.layer) + if len(sizeFldName) == 0: + sizeFldName = None + + if sizeFldName is not None: + sizeValue = f.attribute(sizeFldName) + if sizeValue is None or sizeValue == NULL: + sizeValue = 1 + sizeValue = sizeValue * scale + f.setAttribute(sizeFldName, sizeValue) + + if self.copyEntities == False: + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, entity.layer, f, False, False) == False: + return False + else: + # plugIn, layer, features, coordTransform, refresh, check_validity + if qad_layer.addFeatureToLayer(self.plugIn, entity.layer, f, None, False, False) == False: + return False + + elif entity.whatIs() == "DIMENTITY": + # stiro la quota + if self.copyEntities == False: + if entity.deleteToLayers(self.plugIn) == False: + return False + newDimEntity = QadDimEntity(entity) # la copio + newDimEntity.scale(basePt, scale) + if newDimEntity.addToLayers(self.plugIn) == False: + return False + + + def scaleFeatures(self, scale): + self.plugIn.beginEditCommand("Feature scaled", self.cacheEntitySet.getLayerList()) + + dimElaboratedList = [] # lista delle quotature già elaborate + entityIterator = QadCacheEntitySetIterator(self.cacheEntitySet) + openForm = True if self.cacheEntitySet.count() == 1 else False + + for entity in entityIterator: + qadGeom = entity.getQadGeom() # così inizializzo le info qad + # verifico se l'entità appartiene ad uno stile di quotatura + dimEntity = QadDimStyles.getDimEntity(entity) + if dimEntity is not None: + if appendDimEntityIfNotExisting(dimElaboratedList, dimEntity) == False: # quota già elaborata + continue + entity = dimEntity + + if self.scale(entity, self.basePt, scale, openForm) == False: + self.plugIn.destroyEditCommand() + return + + self.plugIn.endEditCommand() + self.nOperationsToUndo = self.nOperationsToUndo + 1 + + + def waitForScale(self): + self.getPointMapTool().basePt = self.basePt + keyWords = QadMsg.translate("Command_GRIPSCALE", "Base point") + "/" + \ + QadMsg.translate("Command_GRIPSCALE", "Copy") + "/" + \ + QadMsg.translate("Command_GRIPSCALE", "Undo") + "/" + \ + QadMsg.translate("Command_GRIPSCALE", "Reference") + "/" + \ + QadMsg.translate("Command_GRIPSCALE", "eXit") + + default = self.plugIn.lastScale + if self.__referenceLenMode: + # imposto il map tool + self.getPointMapTool().setMode(Qad_scale_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_NEW_LEN_PT) + if self.plugIn.lastNewReferenceLen > 0: + default = self.plugIn.lastNewReferenceLen + prompt = QadMsg.translate("Command_GRIPSCALE", "Specify new length or [{0}]: ").format(keyWords) + else: + # imposto il map tool + self.getPointMapTool().setMode(Qad_scale_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_SCALE_PT) + prompt = QadMsg.translate("Command_GRIPSCALE", "Specify scale factor or [{0}]: ").format(keyWords) + + englishKeyWords = "Base point" + "/" + "Copy" + "/" + "Undo" + "/" + "Reference" + "/" + "eXit" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, valori positivi + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT | QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) + self.step = 1 + + + # ============================================================================ + # waitForBasePt + # ============================================================================ + def waitForBasePt(self): + self.step = 2 + # imposto il map tool + self.getPointMapTool().setMode(Qad_scale_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_BASE_PT) + + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_GRIPSCALE", "Specify base point: ")) + + + def waitForReferenceLen(self): + self.__referenceLenMode = True + # imposto il map tool + self.getPointMapTool().setMode(Qad_scale_maptool_ModeEnum.ASK_FOR_FIRST_PT_REFERENCE_LEN) + + msg = QadMsg.translate("Command_GRIPSCALE", "Specify reference length <{0}>: ") + # si appresta ad attendere un punto o enter + # msg, inputType, default, keyWords, valori positivi + self.waitFor(msg.format(str(self.plugIn.lastReferenceLen)), \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT, \ + self.plugIn.lastReferenceLen, \ + "", QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) + self.step = 3 + + + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + # ========================================================================= + # RICHIESTA SELEZIONE OGGETTI + if self.step == 0: # inizio del comando + if self.cacheEntitySet.isEmpty(): # non ci sono oggetti da ruotare + return True + self.showMsg(QadMsg.translate("Command_GRIPSCALE", "\n** SCALE **\n")) + # si appresta ad attendere la scala + self.waitForScale() + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL FATTORE DI SCALA + elif self.step == 1: + ctrlKey = False + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + value = None + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: + value = self.getPointMapTool().point + + ctrlKey = self.getPointMapTool().ctrlKey + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("Command_GRIPSCALE", "Base point") or value == "Base point": + # si appresta ad attendere il punto base + self.waitForBasePt() + elif value == QadMsg.translate("Command_GRIPSCALE", "Copy") or value == "Copy": + # Copia entità lasciando inalterate le originali + self.copyEntities = True + # si appresta ad attendere la scala + self.waitForScale() + elif value == QadMsg.translate("Command_GRIPSCALE", "Undo") or value == "Undo": + if self.nOperationsToUndo > 0: + self.nOperationsToUndo = self.nOperationsToUndo - 1 + self.plugIn.undoEditCommand() + else: + self.showMsg(QadMsg.translate("QAD", "\nThe command has been canceled.")) + # si appresta ad attendere la scala + self.waitForScale() + elif value == QadMsg.translate("Command_GRIPSCALE", "Reference") or value == "Reference": + # si appresta ad attendere la lunghezza di riferimento + self.waitForReferenceLen() + elif value == QadMsg.translate("Command_GRIPSCALE", "eXit") or value == "eXit": + return True # fine comando + elif type(value) == QgsPointXY or type(value) == float: # se é stato inserita la scala + if type(value) == QgsPointXY: # se é stato inserita la scala con un punto + if value == self.basePt: + self.showMsg(QadMsg.translate("QAD", "\nThe value must be positive and not zero.")) + # si appresta ad attendere un punto + self.waitForScale() + return False + + scale = qad_utils.getDistance(self.basePt, value) + else: + scale = value + + if self.__referenceLenMode == True: # scale rappresenta la nuova distanza di riferimento + scale = scale / self.ReferenceLen + + self.plugIn.setLastScale(scale) + + if ctrlKey: + self.copyEntities = True + + self.scaleFeatures(scale) + + if self.copyEntities == False: + return True + + # si appresta ad attendere la scala + self.waitForScale() + + else: + if self.copyEntities == False: + self.skipToNextGripCommand = True + return True # fine comando + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA PUNTO BASE (da step = 1) + elif self.step == 2: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + self.basePt = QgsPointXY(value) + + self.getPointMapTool().basePt = self.basePt + # si appresta ad attendere la scala + self.waitForScale() + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA PRIMO PUNTO PER LUNGHEZZA DI RIFERIMENTO (da step = 1) + elif self.step == 3: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == float: # se é stato inserita la lunghezza + self.ReferenceLen = value + self.getPointMapTool().ReferenceLen = self.ReferenceLen + # si appresta ad attendere la nuova lunghezza + self.waitForScale() + + elif type(value) == QgsPointXY: # se é stato inserito la scala con un punto + self.Pt1ReferenceLen = QgsPointXY(value) + self.getPointMapTool().Pt1ReferenceLen = self.Pt1ReferenceLen + # imposto il map tool + self.getPointMapTool().setMode(Qad_scale_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT_REFERENCE_LEN) + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_GRIPSCALE", "Specify second point: ")) + self.step = 4 + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA SECONDO PUNTO PER LUNGHEZZA DI RIFERIMENTO (da step = 4) + elif self.step == 4: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if self.Pt1ReferenceLen == value: + self.showMsg(QadMsg.translate("QAD", "\nThe value must be positive and not zero.")) + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_GRIPSCALE", "Specify second point: ")) + return False + + length = qad_utils.getDistance(self.Pt1ReferenceLen, value) + self.ReferenceLen = length + self.getPointMapTool().ReferenceLen = self.ReferenceLen + # si appresta ad attendere la nuova lunghezza + self.waitForScale() + return False \ No newline at end of file diff --git a/cmd/qad_scale_maptool.py b/cmd/qad_scale_maptool.py new file mode 100644 index 00000000..bc282a5b --- /dev/null +++ b/cmd/qad_scale_maptool.py @@ -0,0 +1,183 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin ok + + classe per gestire il map tool in ambito del comando scale + + ------------------- + begin : 2013-09-27 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from .. import qad_utils +from ..qad_getpoint import QadGetPoint, QadGetPointDrawModeEnum +from ..qad_dim import QadDimStyles, QadDimEntity, appendDimEntityIfNotExisting +from ..qad_highlight import QadHighlight +from ..qad_entity import QadCacheEntitySetIterator, QadEntityTypeEnum +from ..qad_multi_geom import fromQadGeomToQgsGeom + + +# =============================================================================== +# Qad_scale_maptool_ModeEnum class. +# =============================================================================== +class Qad_scale_maptool_ModeEnum(): + # noto niente si richiede il punto base + NONE_KNOWN_ASK_FOR_BASE_PT = 1 + # noto il punto base si richiede il secondo punto per la scala + BASE_PT_KNOWN_ASK_FOR_SCALE_PT = 2 + # si richiede il primo punto per la lunghezza di riferimento + ASK_FOR_FIRST_PT_REFERENCE_LEN = 3 + # noto il primo punto si richiede il secondo punto per la lunghezza di riferimento + FIRST_PT_KNOWN_ASK_FOR_SECOND_PT_REFERENCE_LEN = 4 + # noto il punto base si richiede il secondo punto per la nuova lunghezza + BASE_PT_KNOWN_ASK_FOR_NEW_LEN_PT = 5 + # si richiede il primo punto per la nuova lunghezza + ASK_FOR_FIRST_NEW_LEN_PT = 6 + # noto il primo punto si richiede il secondo punto per la nuova lunghezza + FIRST_PT_KNOWN_ASK_FOR_SECOND_NEW_LEN_PT = 7 + +# =============================================================================== +# Qad_scale_maptool class +# =============================================================================== +class Qad_scale_maptool(QadGetPoint): + + def __init__(self, plugIn): + QadGetPoint.__init__(self, plugIn) + + self.basePt = None + self.Pt1ReferenceLen = None + self.ReferenceLen = 0 + self.Pt1NewLen = None + self.cacheEntitySet = None + self.__highlight = QadHighlight(self.canvas) + + def hidePointMapToolMarkers(self): + QadGetPoint.hidePointMapToolMarkers(self) + self.__highlight.hide() + + def showPointMapToolMarkers(self): + QadGetPoint.showPointMapToolMarkers(self) + self.__highlight.show() + + def clear(self): + QadGetPoint.clear(self) + self.__highlight.reset() + self.mode = None + + + # ============================================================================ + # scale + # ============================================================================ + def scale(self, entity, basePt, scale): + # verifico se l'entità appartiene ad uno stile di quotatura + if entity.whatIs() == "ENTITY": + # scalo la geometria dell'entità + qadGeom = entity.getQadGeom().copy() # la copio + qadGeom.scale(basePt, scale) + self.__highlight.addGeometry(fromQadGeomToQgsGeom(qadGeom, entity.layer), entity.layer) + elif entity.whatIs() == "DIMENTITY": + newDimEntity = QadDimEntity(entity) # la copio + # scalo la quota + newDimEntity.scale(basePt, scale) + self.__highlight.addGeometry(newDimEntity.textualFeature.geometry(), newDimEntity.getTextualLayer()) + self.__highlight.addGeometries(newDimEntity.getLinearGeometryCollection(), newDimEntity.getLinearLayer()) + self.__highlight.addGeometries(newDimEntity.getSymbolGeometryCollection(), newDimEntity.getSymbolLayer()) + + + # ============================================================================ + # addScaledGeometries + # ============================================================================ + def addScaledGeometries(self, scale): + self.__highlight.reset() + + if scale <= 0: return + + dimElaboratedList = [] # lista delle quotature già elaborate + entityIterator = QadCacheEntitySetIterator(self.cacheEntitySet) + for entity in entityIterator: + qadGeom = entity.getQadGeom() # così inizializzo le info qad + # verifico se l'entità appartiene ad uno stile di quotatura + dimEntity = QadDimStyles.getDimEntity(entity) + if dimEntity is not None: + if appendDimEntityIfNotExisting(dimElaboratedList, dimEntity) == False: # quota già elaborata + continue + entity = dimEntity + + self.scale(entity, self.basePt, scale) + + + def canvasMoveEvent(self, event): + QadGetPoint.canvasMoveEvent(self, event) + + # noto il punto base si richiede il secondo punto per la scala + if self.mode == Qad_scale_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_SCALE_PT: + scale = qad_utils.getDistance(self.basePt, self.tmpPoint) + self.addScaledGeometries(scale) + # noto il primo punto si richiede il secondo punto per la lunghezza di riferimento + elif self.mode == Qad_scale_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_NEW_LEN_PT: + len = qad_utils.getDistance(self.basePt, self.tmpPoint) + scale = len / self.ReferenceLen + self.addScaledGeometries(scale) + # noto il primo punto si richiede il secondo punto per la nuova lunghezza + elif self.mode == Qad_scale_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_NEW_LEN_PT: + len = qad_utils.getDistance(self.Pt1NewLen, self.tmpPoint) + scale = len / self.ReferenceLen + self.addScaledGeometries(scale) + + + def activate(self): + QadGetPoint.activate(self) + self.__highlight.show() + + def deactivate(self): + try: # necessario perché se si chiude QGIS parte questo evento nonostante non ci sia più l'oggetto maptool ! + QadGetPoint.deactivate(self) + self.__highlight.hide() + except: + pass + + def setMode(self, mode): + self.mode = mode + # noto niente si richiede il punto base + if self.mode == Qad_scale_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_BASE_PT: + self.clear() + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + self.__highlight.reset() + # noto il punto base si richiede il secondo punto per la scala + elif self.mode == Qad_scale_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_SCALE_PT: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.basePt) + # si richiede il primo punto per la lunghezza di riferimento + elif self.mode == Qad_scale_maptool_ModeEnum.ASK_FOR_FIRST_PT_REFERENCE_LEN: + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + self.__highlight.reset() + # noto il primo punto si richiede il secondo punto per la lunghezza di riferimento + elif self.mode == Qad_scale_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT_REFERENCE_LEN: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.Pt1ReferenceLen) + # noto il punto base si richiede il secondo punto per la nuova lunghezza + elif self.mode == Qad_scale_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_NEW_LEN_PT: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.basePt) + # si richiede il primo punto per la nuova lunghezza + elif self.mode == Qad_scale_maptool_ModeEnum.ASK_FOR_FIRST_NEW_LEN_PT: + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + self.__highlight.reset() + # noto il primo punto si richiede il secondo punto per la nuova lunghezza + elif self.mode == Qad_scale_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_NEW_LEN_PT: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.Pt1NewLen) diff --git a/qad_setcurrlayerbygraph_cmd.py b/cmd/qad_setcurrlayerbygraph_cmd.py similarity index 75% rename from qad_setcurrlayerbygraph_cmd.py rename to cmd/qad_setcurrlayerbygraph_cmd.py index e7beed74..4fb496f0 100644 --- a/qad_setcurrlayerbygraph_cmd.py +++ b/cmd/qad_setcurrlayerbygraph_cmd.py @@ -1,184 +1,201 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - comando SETCURRLAYERBYGRAPH per settare il layer corrente tramite selezione grafica - comando SETCURRUPDATEABLELAYERBYGRAPH per settare il layer corrente e porlo in modifica - tramite selezione grafica - - ------------------ - begin : 2013-05-22 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -from qgis.gui import * - - -from qad_generic_cmd import QadCommandClass -from qad_snapper import * -from qad_getpoint import * -from qad_entsel_cmd import QadEntSelClass -from qad_ssget_cmd import QadSSGetClass -from qad_msg import QadMsg - - -# Classe che gestisce il comando SETCURRLAYERBYGRAPH -class QadSETCURRLAYERBYGRAPHCommandClass(QadCommandClass): - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadSETCURRLAYERBYGRAPHCommandClass(self.plugIn) - - def getName(self): - return QadMsg.translate("Command_list", "SETCURRLAYERBYGRAPH") - - def getEnglishName(self): - return "SETCURRLAYERBYGRAPH" - - def connectQAction(self, action): - QObject.connect(action, SIGNAL("triggered()"), self.plugIn.runSETCURRLAYERBYGRAPHCommand) - - def getIcon(self): - return QIcon(":/plugins/qad/icons/setcurrlayerbygraph.png") - - def getNote(self): - # impostare le note esplicative del comando - return QadMsg.translate("Command_SETCURRLAYERBYGRAPH", "Sets a layer of a graphical object as current.") - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - self.EntSelClass = None - - def __del__(self): - QadCommandClass.__del__(self) - - def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): - if self.step == 0 or self.step == 1: # quando si é in fase di selezione entità - return self.EntSelClass.getPointMapTool(drawMode) - else: - return QadCommandClass.getPointMapTool(self, drawMode) - - def waitForEntsel(self, msgMapTool, msg): - if self.EntSelClass is not None: - del self.EntSelClass - self.EntSelClass = QadEntSelClass(self.plugIn) - self.EntSelClass.msg = QadMsg.translate("Command_SETCURRLAYERBYGRAPH", "Select object whose layer will be the current layer: ") - self.getPointMapTool().setSnapType(QadSnapTypeEnum.DISABLE) - self.EntSelClass.run(msgMapTool, msg) - - def run(self, msgMapTool = False, msg = None): - if self.plugIn.canvas.mapRenderer().destinationCrs().geographicFlag(): - self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) - return True # fine comando - - if self.step == 0: - self.waitForEntsel(msgMapTool, msg) - self.step = 1 - return False # continua - - elif self.step == 1: - if self.EntSelClass.run(msgMapTool, msg) == True: - if self.EntSelClass.entity.isInitialized(): - layer = self.EntSelClass.entity.layer - if self.plugIn.canvas.currentLayer() is None or \ - self.plugIn.canvas.currentLayer() != layer: - self.plugIn.canvas.setCurrentLayer(layer) - self.plugIn.iface.setActiveLayer(layer) # lancia evento di deactivate e activate dei plugin - self.plugIn.iface.legendInterface().refreshLayerSymbology(layer) - msg = QadMsg.translate("Command_SETCURRLAYERBYGRAPH", "\nThe current layer is {0}.") - self.showMsg(msg.format(layer.name())) - del self.EntSelClass - return True - else: - self.showMsg(QadMsg.translate("Command_SETCURRLAYERBYGRAPH", "No geometries in this position.")) - self.waitForEntsel(msgMapTool, msg) - return False # continua - - -# Classe che gestisce il comando SETCURRUPDATEABLELAYERBYGRAPH -class QadSETCURRUPDATEABLELAYERBYGRAPHCommandClass(QadCommandClass): - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadSETCURRUPDATEABLELAYERBYGRAPHCommandClass(self.plugIn) - - def getName(self): - return QadMsg.translate("Command_list", "SETCURRUPDATEABLELAYERBYGRAPH") - - def getEnglishName(self): - return "SETCURRUPDATEABLELAYERBYGRAPH" - - def connectQAction(self, action): - QObject.connect(action, SIGNAL("triggered()"), self.plugIn.runSETCURRUPDATEABLELAYERBYGRAPHCommand) - - def getIcon(self): - return QIcon(":/plugins/qad/icons/setcurrupdateablelayerbygraph.png") - - def getNote(self): - # impostare le note esplicative del comando - return QadMsg.translate("Command_SETCURRUPDATEABLELAYERBYGRAPH", "Sets the layers of a graphical objects as editable.") - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - self.SSGetClass = QadSSGetClass(plugIn) - self.firstTime = True - - def __del__(self): - QadCommandClass.__del__(self) - del self.SSGetClass - - def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): - if self.step == 0: # quando si é in fase di selezione entità - return self.SSGetClass.getPointMapTool(drawMode) - else: - return QadCommandClass.getPointMapTool(self, drawMode) - - def run(self, msgMapTool = False, msg = None): - if self.step == 0: # inizio del comando - if self.firstTime == True: - self.showMsg(QadMsg.translate("Command_SETCURRUPDATEABLELAYERBYGRAPH", "\nSelect objects whose layers will be the editable: ")) - self.firstTime = False - - if self.SSGetClass.run(msgMapTool, msg) == True: - # selezione terminata - self.step = 1 - return self.run(msgMapTool, msg) - else: - return False # continua - - elif self.step == 1: # dopo aver atteso la selezione di oggetti - message = "" - for layerEntitySet in self.SSGetClass.entitySet.layerEntitySetList: - layer = layerEntitySet.layer - if layer.isEditable() == False: - if layer.startEditing() == True: - self.plugIn.iface.legendInterface().refreshLayerSymbology(layer) - self.showMsg(QadMsg.translate("Command_SETCURRUPDATEABLELAYERBYGRAPH", "\nThe layer {0} is editable.").format(layer.name())) - - if len(self.SSGetClass.entitySet.layerEntitySetList) == 1: - layer = self.SSGetClass.entitySet.layerEntitySetList[0].layer - if self.plugIn.canvas.currentLayer() is None or \ - self.plugIn.canvas.currentLayer() != layer: - self.plugIn.canvas.setCurrentLayer(layer) - self.plugIn.iface.setActiveLayer(layer) # lancia evento di deactivate e activate dei plugin - self.plugIn.iface.legendInterface().refreshLayerSymbology(layer) - self.showMsg(QadMsg.translate("Command_SETCURRUPDATEABLELAYERBYGRAPH", "\nThe current layer is {0}.").format(layer.name())) - - return True +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + comando SETCURRLAYERBYGRAPH per settare il layer corrente tramite selezione grafica + comando SETCURRUPDATEABLELAYERBYGRAPH per settare il layer corrente e porlo in modifica + tramite selezione grafica + + ------------------ + begin : 2013-05-22 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from qgis.PyQt.QtGui import QIcon + + +from .qad_generic_cmd import QadCommandClass +from ..qad_snapper import QadSnapTypeEnum +from ..qad_getpoint import QadGetPointDrawModeEnum +from .qad_entsel_cmd import QadEntSelClass +from .qad_ssget_cmd import QadSSGetClass +from ..qad_msg import QadMsg + + +# Classe che gestisce il comando SETCURRLAYERBYGRAPH +class QadSETCURRLAYERBYGRAPHCommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadSETCURRLAYERBYGRAPHCommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "SETCURRLAYERBYGRAPH") + + def getEnglishName(self): + return "SETCURRLAYERBYGRAPH" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runSETCURRLAYERBYGRAPHCommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/setcurrlayerbygraph.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_SETCURRLAYERBYGRAPH", "Sets a layer of a graphical object as current.") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + self.entSelClass = None + + def __del__(self): + QadCommandClass.__del__(self) + if self.entSelClass is not None: + del self.entSelClass + + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if self.step == 0 or self.step == 1: # quando si é in fase di selezione entità + return self.entSelClass.getPointMapTool(drawMode) + else: + return QadCommandClass.getPointMapTool(self, drawMode) + + + def getCurrentContextualMenu(self): + if self.step == 0 or self.step == 1: # quando si é in fase di selezione entità + return self.entSelClass.getCurrentContextualMenu() + else: + return self.contextualMenu + + + def waitForEntsel(self, msgMapTool, msg): + if self.entSelClass is not None: + del self.entSelClass + self.entSelClass = None + self.entSelClass = QadEntSelClass(self.plugIn) + self.entSelClass.msg = QadMsg.translate("Command_SETCURRLAYERBYGRAPH", "Select object whose layer will be the current layer: ") + self.getPointMapTool().setSnapType(QadSnapTypeEnum.DISABLE) + self.entSelClass.run(msgMapTool, msg) + + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + if self.step == 0: + self.waitForEntsel(msgMapTool, msg) + self.step = 1 + return False # continua + + elif self.step == 1: + if self.entSelClass.run(msgMapTool, msg) == True: + if self.entSelClass.entity.isInitialized(): + layer = self.entSelClass.entity.layer + if self.plugIn.canvas.currentLayer() is None or \ + self.plugIn.canvas.currentLayer() != layer: + self.plugIn.canvas.setCurrentLayer(layer) + self.plugIn.iface.setActiveLayer(layer) # lancia evento di deactivate e activate dei plugin + msg = QadMsg.translate("Command_SETCURRLAYERBYGRAPH", "\nThe current layer is {0}.") + self.showMsg(msg.format(layer.name())) + del self.entSelClass + self.entSelClass = None + return True + else: + if self.entSelClass.canceledByUsr == True: # fine comando + return True + self.showMsg(QadMsg.translate("QAD", "No geometries in this position.")) + self.waitForEntsel(msgMapTool, msg) + return False # continua + + +# Classe che gestisce il comando SETCURRUPDATEABLELAYERBYGRAPH +class QadSETCURRUPDATEABLELAYERBYGRAPHCommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadSETCURRUPDATEABLELAYERBYGRAPHCommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "SETCURRUPDATEABLELAYERBYGRAPH") + + def getEnglishName(self): + return "SETCURRUPDATEABLELAYERBYGRAPH" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runSETCURRUPDATEABLELAYERBYGRAPHCommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/setcurrupdateablelayerbygraph.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_SETCURRUPDATEABLELAYERBYGRAPH", "Sets the layers of a graphical objects as editable.") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + self.SSGetClass = QadSSGetClass(plugIn) + self.firstTime = True + + def __del__(self): + QadCommandClass.__del__(self) + del self.SSGetClass + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if self.step == 0: # quando si é in fase di selezione entità + return self.SSGetClass.getPointMapTool(drawMode) + else: + return QadCommandClass.getPointMapTool(self, drawMode) + + + def getCurrentContextualMenu(self): + if self.step == 0 or self.step == 1: # quando si é in fase di selezione entità + return None # return self.SSGetClass.getCurrentContextualMenu() + else: + return self.contextualMenu + + + def run(self, msgMapTool = False, msg = None): + if self.step == 0: # inizio del comando + if self.firstTime == True: + self.showMsg(QadMsg.translate("Command_SETCURRUPDATEABLELAYERBYGRAPH", "\nSelect objects whose layers will be the editable: ")) + self.firstTime = False + + if self.SSGetClass.run(msgMapTool, msg) == True: + # selezione terminata + self.step = 1 + return self.run(msgMapTool, msg) + else: + return False # continua + + elif self.step == 1: # dopo aver atteso la selezione di oggetti + message = "" + for layerEntitySet in self.SSGetClass.entitySet.layerEntitySetList: + layer = layerEntitySet.layer + if layer.isEditable() == False: + if layer.startEditing() == True: + self.showMsg(QadMsg.translate("Command_SETCURRUPDATEABLELAYERBYGRAPH", "\nThe layer {0} is editable.").format(layer.name())) + + if len(self.SSGetClass.entitySet.layerEntitySetList) == 1: + layer = self.SSGetClass.entitySet.layerEntitySetList[0].layer + if self.plugIn.canvas.currentLayer() is None or \ + self.plugIn.canvas.currentLayer() != layer: + self.plugIn.canvas.setCurrentLayer(layer) + self.plugIn.iface.setActiveLayer(layer) # lancia evento di deactivate e activate dei plugin + self.showMsg(QadMsg.translate("Command_SETCURRUPDATEABLELAYERBYGRAPH", "\nThe current layer is {0}.").format(layer.name())) + + return True diff --git a/qad_setvar_cmd.py b/cmd/qad_setvar_cmd.py similarity index 80% rename from qad_setvar_cmd.py rename to cmd/qad_setvar_cmd.py index bbade2ab..1c70182d 100644 --- a/qad_setvar_cmd.py +++ b/cmd/qad_setvar_cmd.py @@ -1,140 +1,145 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - comando SETVAR per settare le variabili di ambiente di QAD - - ------------------- - begin : 2013-05-22 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -from qad_generic_cmd import QadCommandClass -from qad_msg import QadMsg -from qad_variables import * - - -# Classe che gestisce il comando SETVAR -class QadSETVARCommandClass(QadCommandClass): - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadSETVARCommandClass(self.plugIn) - - def getName(self): - return QadMsg.translate("Command_list", "SETVAR") - - def getEnglishName(self): - return "SETVAR" - - def connectQAction(self, action): - QObject.connect(action, SIGNAL("triggered()"), self.plugIn.runSETVARCommand) - - def getIcon(self): - return QIcon(":/plugins/qad/icons/variable.png") - - def getNote(self): - # impostare le note esplicative del comando - return QadMsg.translate("Command_SETVAR", "Sets the QAD environment variables.") - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - self.varName = "" - - - def run(self, msgMapTool = False, msg = None): - if self.step == 0: # inizio del comando - # si appresta ad attendere una stringa - self.waitForString(QadMsg.translate("Command_SETVAR", "Enter the variable name or [?]: "), \ - QadMsg.translate("Command_SETVAR", "?")) - self.step = 1 - return False - elif self.step == 1: # dopo aver atteso il nome della variabile si riavvia il comando - if msgMapTool == True: # niente può arrivare da grafica - return False - # il nome della variabile arriva come parametro della funzione - self.varName = msg - if self.varName == QadMsg.translate("Command_SETVAR", "?"): # lista delle variabili - # si appresta ad attendere una stringa - self.waitForString(QadMsg.translate("Command_SETVAR", "Enter variable(s) to list <*>: "), \ - QadMsg.translate("Command_SETVAR", "*")) - self.step = 3 - return False - else: - variable = QadVariables.getVariable(self.varName) - if variable is None: - msg = QadMsg.translate("Command_SETVAR", "\nUnknown variable. Enter {0} ? to list variable names.") - self.showErr(msg.format(QadMsg.translate("Command_list", "SETVAR"))) - return False - else: - varValue = variable.value - varDescr = variable.descr - varType = str(type(varValue)) - if len(varDescr) > 0: - self.showMsg("\n" + varDescr) - - msg = QadMsg.translate("Command_SETVAR", "Enter new value for variable {0} <{1}>: ") - if varType == "": - # si appresta ad attendere una stringa - self.waitForString(msg.format(self.varName, varValue), varValue) - elif varType == "": - # si appresta ad attendere un numero intero - self.waitForInt(msg.format(self.varName, varValue), varValue) - elif varType == "": - # si appresta ad attendere un numero reale - self.waitForFloat(msg.format(self.varName, varValue), varValue) - elif varType == "": - # si appresta ad attendere un numero reale - self.waitForBool(msg.format(self.varName, varValue), varValue) - self.step = 2 - return False - elif self.step == 2: # dopo aver atteso il valore della variabile si riavvia il comando - if msgMapTool == True: # niente può arrivare da grafica - return False - # il valore della variabile arriva come parametro della funzione - QadVariables.set(self.varName, msg) - QadVariables.save() - self.plugIn.UpdatedVariablesEvent() - - return True - elif self.step == 3: # dopo aver atteso il nome della variabile si riavvia il comando - if msgMapTool == True: # niente può arrivare da grafica - return False - - if msg == "*": - varNames = QadVariables.getVarNames() - else: - # il nome della variabile arriva come parametro della funzione - varNames = msg.strip().split(",") - - varNames.sort() - for self.varName in varNames: - self.varName = self.varName.strip() - varValue = QadVariables.get(self.varName) - if varValue is not None: - msg = "\n" + self.varName + "=" + str(varValue) - self.showMsg(msg) - - self.plugIn.UpdatedVariablesEvent() - +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + comando SETVAR per settare le variabili di ambiente di QAD + + ------------------- + begin : 2013-05-22 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.PyQt.QtGui import QIcon + + +from .qad_generic_cmd import QadCommandClass +from ..qad_msg import QadMsg +from ..qad_variables import QadVariables, QadVariableTypeEnum + + +# Classe che gestisce il comando SETVAR +class QadSETVARCommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadSETVARCommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "SETVAR") + + def getEnglishName(self): + return "SETVAR" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runSETVARCommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/variable.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_SETVAR", "Sets the QAD environment variables.") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + self.varName = "" + + + def run(self, msgMapTool = False, msg = None): + if self.step == 0: # inizio del comando + # si appresta ad attendere una stringa + self.waitForString(QadMsg.translate("Command_SETVAR", "Enter the variable name or [?]: "), \ + QadMsg.translate("Command_SETVAR", "?")) + self.step = 1 + return False + elif self.step == 1: # dopo aver atteso il nome della variabile si riavvia il comando + if msgMapTool == True: # niente può arrivare da grafica + return False + # il nome della variabile arriva come parametro della funzione + self.varName = msg + if self.varName == QadMsg.translate("Command_SETVAR", "?"): # lista delle variabili + # si appresta ad attendere una stringa + self.waitForString(QadMsg.translate("Command_SETVAR", "Enter variable(s) to list <*>: "), \ + QadMsg.translate("Command_SETVAR", "*")) + self.step = 3 + return False + else: + variable = QadVariables.getVariable(self.varName) + if variable is None: + msg = QadMsg.translate("Command_SETVAR", "\nUnknown variable. Enter {0} ? to list variable names.") + self.showErr(msg.format(QadMsg.translate("Command_list", "SETVAR"))) + return False + else: + varValue = variable.value + varDescr = variable.descr + varType = variable.typeValue + + if len(varDescr) > 0: + self.showMsg("\n" + varDescr) + + msg = QadMsg.translate("Command_SETVAR", "Enter new value for variable {0} <{1}>: ") + if varType == QadVariableTypeEnum.STRING: + # si appresta ad attendere una stringa + self.waitForString(msg.format(self.varName, varValue), varValue) + elif varType == QadVariableTypeEnum.INT: + # si appresta ad attendere un numero intero + self.waitForInt(msg.format(self.varName, varValue), varValue) + elif varType == QadVariableTypeEnum.FLOAT: + # si appresta ad attendere un numero reale + self.waitForFloat(msg.format(self.varName, varValue), varValue) + elif varType == QadVariableTypeEnum.BOOL: + # si appresta ad attendere un numero reale + self.waitForBool(msg.format(self.varName, varValue), varValue) + elif varType == QadVariableTypeEnum.COLOR: + # si appresta ad attendere un COLORE #RRGGBB + self.waitForString(msg.format(self.varName, varValue), varValue) + self.step = 2 + return False + elif self.step == 2: # dopo aver atteso il valore della variabile si riavvia il comando + if msgMapTool == True: # niente può arrivare da grafica + return False + # il valore della variabile arriva come parametro della funzione + if QadVariables.set(self.varName, msg) == False: # valore non valido + msg = QadMsg.translate("Command_SETVAR", "\nValue not valid.") + self.showErr(msg) + return False + else: # valore valido + QadVariables.save() + self.plugIn.UpdatedVariablesEvent() + return True + elif self.step == 3: # dopo aver atteso il nome della variabile si riavvia il comando + if msgMapTool == True: # niente può arrivare da grafica + return False + + if msg == "*": + varNames = QadVariables.getVarNames() + else: + # il nome della variabile arriva come parametro della funzione + varNames = msg.strip().split(",") + + varNames.sort() + for self.varName in varNames: + self.varName = self.varName.strip() + varValue = QadVariables.get(self.varName) + if varValue is not None: + msg = "\n" + self.varName + "=" + str(varValue) + self.showMsg(msg) + + self.plugIn.UpdatedVariablesEvent() + return True \ No newline at end of file diff --git a/qad_ssget_cmd.py b/cmd/qad_ssget_cmd.py similarity index 83% rename from qad_ssget_cmd.py rename to cmd/qad_ssget_cmd.py index 7063b405..40dad1a0 100644 --- a/qad_ssget_cmd.py +++ b/cmd/qad_ssget_cmd.py @@ -1,933 +1,962 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - comando da inserire in altri comandi per la selezione di un gruppo di feature - - ------------------- - begin : 2013-05-22 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -from qad_generic_cmd import QadCommandClass -from qad_msg import QadMsg -from qad_textwindow import * -from qad_entity import * -from qad_dim import QadDimStyles -from qad_getpoint import * -from qad_pline_cmd import QadPLINECommandClass -from qad_circle_cmd import QadCIRCLECommandClass -from qad_mpolygon_cmd import QadMPOLYGONCommandClass -# ho dovuto spostare in fondo questo import perché qad_mbuffer_cmd fa l'import di qad_ssget_cmd -#from qad_mbuffer_cmd import QadMBUFFERCommandClass -import qad_utils - - -#=============================================================================== -# QadSSGetClass -#=============================================================================== -class QadSSGetClass(QadCommandClass): -# Classe che gestisce la selezione di oggetti geometrici - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadSSGetClass(self.plugIn) - - def __init__(self, plugIn): - self.init(plugIn) - - def __del__(self): - QadCommandClass.__del__(self) - #self.entitySet.deselectOnLayer() - - def init(self, plugIn): - QadCommandClass.__init__(self, plugIn) - self.AddOnSelection = True # se = False significa remove - self.entitySet = QadEntitySet() - self.points = [] - self.currSelectionMode = "" - # opzioni per limitare gli oggetti da selezionare - self.onlyEditableLayers = False - self.checkPointLayer = True - self.checkLineLayer = True - self.checkPolygonLayer = True - self.checkDimLayers = True # include tutte le features che compongono le quotature selezionate - - self.help = False - # se SingleSelection = True viene selezionato il primo oggetto o gruppo di oggetti indicato, - # senza che vengano richieste altre selezioni. - self.SingleSelection = False - self.pickAdd = QadVariables.get(QadMsg.translate("Environment variables", "PICKADD")) - - # se exitAfterSelection = True il comando viene terminato dopo una qualunque selezione - # indipendentemente che sia stato selezionato o meno un oggetto o gruppo di oggetti. - # usato da QadVirtualSelCommandClass - self.exitAfterSelection = False - - # selezione degli oggetti aggiunti più recentemente al gruppo di selezione (x opzione annulla) - self.lastEntitySet = QadEntitySet() - self.PLINECommand = None - self.CIRCLECommand = None - self.MPOLYGONCommand = None - self.MBUFFERCommand = None - self.SSGetClass = None - - def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): - if self.step == 4: # quando si é in fase di disegno linea - return self.PLINECommand.getPointMapTool(drawMode) - elif self.step == 5: # quando si é in fase di disegno cerchio - return self.CIRCLECommand.getPointMapTool(drawMode) - elif self.step == 6: # quando si é in fase di selezione entità - return self.SSGetClass.getPointMapTool(drawMode) - elif self.step == 7: # quando si é in fase di disegno polygono - return self.MPOLYGONCommand.getPointMapTool(drawMode) - elif self.step == 8: # quando si é in fase di disegno buffer - return self.MBUFFERCommand.getPointMapTool(drawMode) - else: - ptMapTool = QadCommandClass.getPointMapTool(self, drawMode) - ptMapTool.setSnapType(QadSnapTypeEnum.DISABLE) - ptMapTool.setOrthoMode(0) - return ptMapTool - - - #============================================================================ - # getLayersToCheck - #============================================================================ - def getLayersToCheck(self): - layerList = [] - for layer in self.plugIn.canvas.layers(): # Tutti i layer visibili visibili - # considero solo i layer vettoriali che sono filtrati per tipo - if (layer.type() == QgsMapLayer.VectorLayer) and \ - ((layer.geometryType() == QGis.Point and self.checkPointLayer == True) or \ - (layer.geometryType() == QGis.Line and self.checkLineLayer == True) or \ - (layer.geometryType() == QGis.Polygon and self.checkPolygonLayer == True)) and \ - (self.onlyEditableLayers == False or layer.isEditable()): - # se devo includere i layers delle quotature - if self.checkDimLayers == True or \ - len(QadDimStyles.getDimListByLayer(layer)) == 0: - layerList.append(layer) - - return layerList - - - #============================================================================ - # showMsgOnAddRemove - #============================================================================ - def showMsgOnAddRemove(self, found): - msg = QadMsg.translate("Command_SSGET", " found {0}, total {1}") - self.showMsg(msg.format(found, self.entitySet.count()), True) # ripete il prompt - - - #============================================================================ - # elaborateEntity - #============================================================================ - def elaborateEntity(self, entity, shiftKey): - if self.AddOnSelection == True: # aggiungi al gruppo di selezione - if shiftKey: # se la selezione é avvenuta con shift premuto - if self.pickAdd == 0: # The objects most recently selected become the selection set - if self.entitySet.containsEntity(entity): # se l'entità era già stata selezionata - self.AddRemoveEntity(entity, False) # rimuovo l'entità - else: - self.AddRemoveEntity(entity, True) # aggiungo l'entità - else: - self.AddRemoveEntity(entity, False) # rimuovo l'entità - else: # senza tasto shift - if self.pickAdd == 0: # The objects most recently selected become the selection set - self.SetEntity(entity) - else: - self.AddRemoveEntity(entity, True) # aggiungo l'entità - else: # se si deve rimuovere dal gruppo di selezione - self.AddRemoveEntity(entity, False) # rimuovo l'entità - - - #============================================================================ - # SetEntity - #============================================================================ - def SetEntity(self, entity): - # controllo sul layer - if self.onlyEditableLayers == True and entity.layer.isEditable() == False: - self.showMsgOnAddRemove(0) - return - # controllo sul tipo - if (self.checkPointLayer == False and entity.layer.geometryType() == QGis.Point) or \ - (self.checkLineLayer == False and entity.layer.geometryType() == QGis.Line) or \ - (self.checkPolygonLayer == False and entity.layer.geometryType() == QGis.Polygon): - self.showMsgOnAddRemove(0) - return - - # controllo su layer delle quotature - # verifico se l'entità appartiene ad uno stile di quotatura - dimEntity = QadDimStyles.getDimEntity(entity) - if self.checkDimLayers == False and dimEntity is not None: - self.showMsgOnAddRemove(0) - return - - self.entitySet.deselectOnLayer() - self.entitySet.clear() - self.entitySet.addEntity(entity) - - if self.checkDimLayers == True and dimEntity is not None: - # Aggiungo i componenenti della quotatura a set - self.entitySet.unite(dimEntity.getEntitySet()) - - self.showMsgOnAddRemove(self.entitySet.count()) - self.entitySet.selectOnLayer(False) # incremental = False aaaaaaaaaaaaaaaaaaaaaaaaaa qui parte l'evento activate di qad_maptool - self.lastEntitySet.clear() - self.lastEntitySet.addEntity(entity) - - - #============================================================================ - # AddRemoveEntity - #============================================================================ - def AddRemoveEntity(self, entity, Add): - # controllo sul layer - if self.onlyEditableLayers == True and entity.layer.isEditable() == False: - self.showMsgOnAddRemove(0) - return - # controllo sul tipo - if (self.checkPointLayer == False and entity.layer.geometryType() == QGis.Point) or \ - (self.checkLineLayer == False and entity.layer.geometryType() == QGis.Line) or \ - (self.checkPolygonLayer == False and entity.layer.geometryType() == QGis.Polygon): - self.showMsgOnAddRemove(0) - return - # controllo su layer delle quotature - if self.checkDimLayers == False and len(QadDimStyles.getDimListByLayer(entity.layer)) > 0: - self.showMsgOnAddRemove(0) - return - - self.entitySet.deselectOnLayer() - if Add == True: # aggiungi al gruppo di selezione - self.entitySet.addEntity(entity) - else: # rimuovi dal gruppo di selezione - self.entitySet.removeEntity(entity) - - if self.checkDimLayers == True: - dimEntitySet = QadEntitySet() - dimEntitySet.addEntity(entity) - # La funzione verifica se le entità che fanno parte di un entitySet sono anche parte di quotatura e, - # in caso affermativo, aggiunge/rimuove tutti i componenti delle quotature all'entitySet. - QadDimStyles.addAllDimComponentsToEntitySet(dimEntitySet, self.onlyEditableLayers) - if Add == True: # aggiungi al gruppo di selezione - self.entitySet.unite(dimEntitySet) - else: # rimuovi dal gruppo di selezione - self.entitySet.subtract(dimEntitySet) - self.showMsgOnAddRemove(dimEntitySet.count()) - else: - self.showMsgOnAddRemove(1) - - self.entitySet.selectOnLayer(False) # incremental = False - self.lastEntitySet.clear() - self.lastEntitySet.addEntity(entity) - - - #============================================================================ - # elaborateSelSet - #============================================================================ - def elaborateSelSet(self, selSet, shiftKey): - if self.AddOnSelection == True: # aggiungi al gruppo di selezione - if shiftKey: # se la selezione é avvenuta con shift premuto - if self.pickAdd == 0: # The objects most recently selected become the selection set - # verifico se ci sono degli oggetti non ancora selezionati - intersectSS = QadEntitySet(selSet) - intersectSS.subtract(self.entitySet) - if intersectSS.isEmpty(): # tutti gli oggetti erano già selezionati - self.AddRemoveSelSet(selSet, False) # rimuovo il gruppo di selezione - else: - self.AddRemoveSelSet(selSet, True) # aggiungo il gruppo di selezione - else: - self.AddRemoveSelSet(selSet, False) # rimuovo il gruppo di selezione - else: # senza tasto shift - if self.pickAdd == 0: # The objects most recently selected become the selection set - self.SetSelSet(selSet) - else: - self.AddRemoveSelSet(selSet, True) # aggiungo il gruppo di selezione - else: # se si deve rimuovere dal gruppo di selezione - self.AddRemoveEntity(selSet, False) # rimuovo il gruppo di selezione - - - #============================================================================ - # SetSelSet - #============================================================================ - def SetSelSet(self, selSet): - for layerEntitySet in self.entitySet.layerEntitySetList: - # se il layer non é presente in selSet - if selSet.findLayerEntitySet(layerEntitySet) is None: - layerEntitySet.deselectOnLayer() - else: - layerEntitySet.deselectOnLayer() - - self.entitySet.set(selSet) - - if self.checkDimLayers == True: - dimEntitySet = QadEntitySet(selSet) - # La funzione verifica se le entità che fanno parte di un entitySet sono anche parte di quotatura e, - # in caso affermativo, aggiunge tutti i componenti delle quotature all'entitySet. - QadDimStyles.addAllDimComponentsToEntitySet(dimEntitySet, self.onlyEditableLayers) - self.entitySet.unite(dimEntitySet) - - self.showMsgOnAddRemove(self.entitySet.count()) - self.entitySet.selectOnLayer(False) # incremental = False - self.lastEntitySet.set(selSet) - - - #============================================================================ - # AddCurrentQgsSelectedFeatures - #============================================================================ - def AddCurrentQgsSelectedFeatures(self): - # verifico se ci sono entità correntemente selezionate - self.entitySet.initByCurrentQgsSelectedFeatures(self.getLayersToCheck()) - found = self.entitySet.count() - if found > 0: - msg = QadMsg.translate("Command_SSGET", "\nfound {0}") - self.showMsg(msg.format(found), False) # non ripete il prompt - return True - else: - return False - - - #============================================================================ - # AddRemoveSelSet - #============================================================================ - def AddRemoveSelSet(self, selSet, Add): - self.entitySet.deselectOnLayer() - if Add == True: # aggiungi al gruppo di selezione - self.entitySet.unite(selSet) - else: # rimuovi dal gruppo di selezione - self.entitySet.subtract(selSet) - - self.showMsgOnAddRemove(selSet.count()) - - self.entitySet.selectOnLayer(False) # incremental = False - self.lastEntitySet.set(selSet) - - #============================================================================ - # AddRemoveSelSetByFence - #============================================================================ - def AddRemoveSelSetByFence(self, points): - if len(points) > 1: - selSet = qad_utils.getSelSet("F", self.getPointMapTool(), points, \ - self.getLayersToCheck()) - self.elaborateSelSet(selSet, False) - - #============================================================================ - # AddRemoveSelSetByPolygon - #============================================================================ - def AddRemoveSelSetByPolygon(self, mode, points): - if len(points) > 2: - selSet = qad_utils.getSelSet(mode, self.getPointMapTool(), points, \ - self.getLayersToCheck()) - self.elaborateSelSet(selSet, False) - - #============================================================================ - # AddRemoveSelSetByGeometry - #============================================================================ - def AddRemoveSelSetByGeometry(self, mode, geom): - if type(geom) == QgsGeometry: # singola geometria - selSet = qad_utils.getSelSet(mode, self.getPointMapTool(), geom, \ - self.getLayersToCheck()) - else: # lista di geometrie - selSet = QadEntitySet() - for g in geom: - partial = qad_utils.getSelSet(mode, self.getPointMapTool(), g, \ - self.getLayersToCheck()) - selSet.unite(partial) - self.elaborateSelSet(selSet, False) - - - #============================================================================ - # WaitForFirstPoint - #============================================================================ - def WaitForFirstPoint(self): - self.step = 1 - - # "Finestra" "Ultimo" "Interseca" - # "Riquadro" "Tutto" "iNTercetta" - # "FPoligono" "IPoligono" - # "FCerchio" "ICerchio" - # "FOggetti" "IOggetti" - # "FBuffer" "IBuffer" - # "AGgiungi" "Elimina" - # "Precedente" "Annulla" - # "AUto" "SIngolo" "Help" - keyWords = QadMsg.translate("Command_SSGET", "Window") + "/" + \ - QadMsg.translate("Command_SSGET", "Last") + "/" + \ - QadMsg.translate("Command_SSGET", "Crossing") + "/" + \ - QadMsg.translate("Command_SSGET", "Box") + "/" + \ - QadMsg.translate("Command_SSGET", "All") + "/" + \ - QadMsg.translate("Command_SSGET", "Fence") + "/" + \ - QadMsg.translate("Command_SSGET", "WPolygon") + "/" + \ - QadMsg.translate("Command_SSGET", "CPolygon") + "/" + \ - QadMsg.translate("Command_SSGET", "WCircle") + "/" + \ - QadMsg.translate("Command_SSGET", "CCircle") + "/" + \ - QadMsg.translate("Command_SSGET", "WObjects") + "/" + \ - QadMsg.translate("Command_SSGET", "CObjects") + "/" + \ - QadMsg.translate("Command_SSGET", "WBuffer") + "/" + \ - QadMsg.translate("Command_SSGET", "CBuffer") + "/" + \ - QadMsg.translate("Command_SSGET", "Add") + "/" + \ - QadMsg.translate("Command_SSGET", "Remove") + "/" + \ - QadMsg.translate("Command_SSGET", "Previous") + "/" + \ - QadMsg.translate("Command_SSGET", "Undo") + "/" + \ - QadMsg.translate("Command_SSGET", "AUto") + "/" + \ - QadMsg.translate("Command_SSGET", "SIngle") + "/" + \ - QadMsg.translate("Command_SSGET", "Help") - englishKeyWords = "Window" + "/" + "Last" + "/" + "Crossing" + "/" + "Box" + "/" \ - + "All" + "/" + "Fence" + "/" + "WPolygon" + "/" + "CPolygon" + "/" \ - + "WCircle" + "/" + "CCircle" + "/" + "WObjects" + "/" + "CObjects" + "/" \ - + "WBuffer" + "/" + "CBuffer" + "/" + "Add" + "/" + "Remove" + "/" \ - + "Previous" + "/" + "Undo" + "/" + "AUto" + "/" + "SIngle" + "/" + "Help" - - if self.AddOnSelection == True: - prompt = QadMsg.translate("Command_SSGET", "Select Objects") - else: - prompt = QadMsg.translate("Command_SSGET", "Remove objects") - - if self.help == True: - prompt = prompt + QadMsg.translate("Command_SSGET", " or [{0}]").format(keyWords) - - prompt = prompt + QadMsg.translate("Command_SSGET", ": ") - - # imposto il map tool - self.getPointMapTool().setSelectionMode(QadGetPointSelectionModeEnum.ENTITY_SELECTION) - self.getPointMapTool().setDrawMode(QadGetPointDrawModeEnum.NONE) - # imposto i layer da controllare sul maptool - self.getPointMapTool().layersToCheck = self.getLayersToCheck() - self.points = [] - - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o enter o una parola chiave - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - None, \ - keyWords, QadInputModeEnum.NONE) - return - - def run(self, msgMapTool = False, msg = None): - # ritorna: - # True per selezione non terminata - # False per selezione terminata - if self.plugIn.canvas.mapRenderer().destinationCrs().geographicFlag(): - self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) - return True # errore - - #========================================================================= - # RICHIESTA PRIMO PUNTO PER SELEZIONE OGGETTI - if self.step == 0: - # if you can also select objects before you start a command - if QadVariables.get(QadMsg.translate("Environment variables", "PICKFIRST")) == 1: - if self.AddCurrentQgsSelectedFeatures() == True: - self.plugIn.setLastEntitySet(self.entitySet) - return True; - self.WaitForFirstPoint() - return False # continua - - #========================================================================= - # RISPOSTA ALLA RICHIESTA PRIMO PUNTO PER SELEZIONE OGGETTI - elif self.step == 1: # dopo aver atteso un punto o enter o una parola chiave si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - if self.entitySet.count() > 0: - self.plugIn.setLastEntitySet(self.entitySet) - return True # fine - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False # continua - - shiftKey = self.getPointMapTool().shiftKey - - # se é stata selezionata un'entità - if self.getPointMapTool().entity.isInitialized(): - value = self.getPointMapTool().entity - else: - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - shiftKey = False - value = msg - - if value is None: - if self.entitySet.count() > 0: - self.plugIn.setLastEntitySet(self.entitySet) - return True # fine - - if type(value) == unicode: - self.currSelectionMode = value - - if value == QadMsg.translate("Command_SSGET", "Window") or value == "Window" or \ - value == QadMsg.translate("Command_SSGET", "Crossing") or value == "Crossing": - # "Finestra" = Seleziona tutti gli oggetti che si trovano completamente all'interno di un rettangolo definito da due punti - # "Interseca" = Seleziona gli oggetti che intersecano o si trovano all'interno di un'area definita da due punti - # imposto il map tool - self.getPointMapTool().setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) - self.getPointMapTool().setDrawMode(QadGetPointDrawModeEnum.NONE) - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_SSGET", "First corner: ")) - self.step = 2 - if value == QadMsg.translate("Command_SSGET", "Last") or value == "Last": - # Seleziona l'ultima entità inserita - if self.plugIn.getLastEntity() is None: - self.showMsgOnAddRemove(0) - else: - self.AddRemoveEntity(self.plugIn.getLastEntity(), self.AddOnSelection) - if self.SingleSelection == True and self.entitySet.count() > 0: - self.plugIn.setLastEntitySet(self.entitySet) - return True # fine - - if self.exitAfterSelection == True: - return True # fine - - self.WaitForFirstPoint() - elif value == QadMsg.translate("Command_SSGET", "Box") or value == "Box": - # Seleziona tutti gli oggetti che intersecano o si trovano all'interno di un rettangolo specificato da due punti. - # Se i punti del rettangolo sono specificati da destra a sinistra, Riquadro equivale ad Interseca, - # altrimenti é equivalente a Finestra - # imposto il map tool - self.getPointMapTool().setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) - self.getPointMapTool().setDrawMode(QadGetPointDrawModeEnum.NONE) - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_SSGET", "First corner: ")) - self.step = 2 - elif value == QadMsg.translate("Command_SSGET", "All") or value == "All": - # Seleziona tutti gli oggetti - selSet = qad_utils.getSelSet("X", self.getPointMapTool(), None, \ - self.getLayersToCheck()) - self.elaborateSelSet(selSet, False) - if self.SingleSelection == True and self.entitySet.count() > 0: - self.plugIn.setLastEntitySet(self.entitySet) - return True # fine - - if self.exitAfterSelection == True: - return True # fine - - self.WaitForFirstPoint() - elif value == QadMsg.translate("Command_SSGET", "Fence") or value == "Fence": - # Seleziona tutti gli oggetti che intersecano una polilinea - self.PLINECommand = QadPLINECommandClass(self.plugIn) - # se questo flag = True il comando serve all'interno di un altro comando per disegnare una linea - # che non verrà salvata su un layer - self.PLINECommand.virtualCmd = True - self.PLINECommand.run(msgMapTool, msg) - self.step = 4 - elif value == QadMsg.translate("Command_SSGET", "WPolygon") or value == "WPolygon" or \ - value == QadMsg.translate("Command_SSGET", "CPolygon") or value == "CPolygon": - # "FPoligono" = Seleziona oggetti che si trovano completamente all'interno di un poligono definito da punti - # "IPoligono" = Seleziona gli oggetti che intersecano o si trovano all'interno di un poligono definito specificando dei punti - self.MPOLYGONCommand = QadMPOLYGONCommandClass(self.plugIn) - # se questo flag = True il comando serve all'interno di un altro comando per disegnare una linea - # che non verrà salvata su un layer - self.MPOLYGONCommand.virtualCmd = True - - if value == QadMsg.translate("Command_SSGET", "WPolygon") or value == "WPolygon": - self.MPOLYGONCommand.setRubberBandColor(None, getColorForWindowSelectionArea()) - else: - self.MPOLYGONCommand.setRubberBandColor(None, getColorForCrossingSelectionArea()) - - self.MPOLYGONCommand.run(msgMapTool, msg) - self.step = 7 - elif value == QadMsg.translate("Command_SSGET", "WCircle") or value == "WCircle" or \ - value == QadMsg.translate("Command_SSGET", "CCircle") or value == "CCircle": - # "FCerchio" = Seleziona oggetti che si trovano completamente all'interno di un cerchio - # "ICerchio" = Seleziona oggetti che intersecano o si trovano all'interno di un cerchio - self.CIRCLECommand = QadCIRCLECommandClass(self.plugIn) - # se questo flag = True il comando serve all'interno di un altro comando per disegnare un cerchio - # che non verrà salvata su un layer - self.CIRCLECommand.virtualCmd = True - - if value == QadMsg.translate("Command_SSGET", "WCircle") or value == "WCircle": - self.CIRCLECommand.setRubberBandColor(None, getColorForWindowSelectionArea()) - else: - self.CIRCLECommand.setRubberBandColor(None, getColorForCrossingSelectionArea()) - - self.CIRCLECommand.run(msgMapTool, msg) - self.step = 5 - elif value == QadMsg.translate("Command_SSGET", "WObjects") or value == "WObjects" or \ - value == QadMsg.translate("Command_SSGET", "CObjects") or value == "CObjects": - # "FOggetti" = Seleziona oggetti che si trovano completamente all'interno di oggetti da selezionare - # "IOggetti" = Seleziona oggetti che intersecano o si trovano all'interno di oggetti da selezionare - self.SSGetClass = QadSSGetClass(self.plugIn) - self.SSGetClass.run(msgMapTool, msg) - self.step = 6 - elif value == QadMsg.translate("Command_SSGET", "WBuffer") or value == "WBuffer" or \ - value == QadMsg.translate("Command_SSGET", "CBuffer") or value == "CBuffer": - # ho dovuto spostare questo import perché qad_mbuffer_cmd fa l'import di qad_ssget_cmd - from qad_mbuffer_cmd import QadMBUFFERCommandClass - - # "FBuffer" = Seleziona oggetti che si trovano completamente all'interno di buffer intorno ad oggetti da selezionare - # "IBuffer" = Seleziona oggetti che intersecano o si trovano all'interno di buffer intorno ad oggetti da selezionare - self.MBUFFERCommand = QadMBUFFERCommandClass(self.plugIn) - # se questo flag = True il comando serve all'interno di un altro comando per disegnare un cerchio - # che non verrà salvata su un layer - self.MBUFFERCommand.virtualCmd = True - - if value == QadMsg.translate("Command_SSGET", "WBuffer") or value == "WBuffer": - self.MBUFFERCommand.setRubberBandColor(None, getColorForWindowSelectionArea()) - else: - self.MBUFFERCommand.setRubberBandColor(None, getColorForCrossingSelectionArea()) - - self.MBUFFERCommand.run(msgMapTool, msg) - self.step = 8 - elif value == QadMsg.translate("Command_SSGET", "Add") or value == "Add": - # Passa al metodo Aggiungi: gli oggetti selezionati possono essere aggiunti al gruppo di selezione - self.AddOnSelection = True - self.WaitForFirstPoint() - elif value == QadMsg.translate("Command_SSGET", "Remove") or value == "Remove": - # Passa al metodo Rimuovi: gli oggetti possono essere rimossi dal gruppo di selezione - self.AddOnSelection = False - self.WaitForFirstPoint() - elif value == QadMsg.translate("Command_SSGET", "Previous") or value == "Previous": - # Seleziona il gruppo di selezione più recente - if self.plugIn.lastEntitySet is None: - self.showMsgOnAddRemove(0) - else: - entitySet = QadEntitySet() - entitySet.set(self.plugIn.lastEntitySet) - # controllo sul layer - if self.onlyEditableLayers == True: - entitySet.removeNotEditable() - # controllo sul tipo - if self.checkPointLayer == False: - entitySet.removeGeomType(QGis.Point) - if self.checkLineLayer == False: - entitySet.removeGeomType(QGis.Line) - if self.checkPolygonLayer == False: - entitySet.removeGeomType(QGis.Polygon) - # controllo sulle quotature - if self.checkDimLayers == False: - QadDimStyles.removeAllDimLayersFromEntitySet(entitySet) - - entitySet.removeNotExisting() - self.elaborateSelSet(entitySet, False) - if self.SingleSelection == True and self.entitySet.count() > 0: - self.plugIn.setLastEntitySet(self.entitySet) - return True # fine - - if self.exitAfterSelection == True: - return True # fine - - self.WaitForFirstPoint() - elif value == QadMsg.translate("Command_SSGET", "Undo") or value == "Undo": - # Annulla la selezione dell'oggetto aggiunto più recentemente al gruppo di selezione. - # Inverto il tipo di selezione - prevAddOnSelection = self.AddOnSelection - self.AddOnSelection = not self.AddOnSelection - self.elaborateSelSet(self.lastEntitySet, False) - # Ripristino il tipo di selezione - self.AddOnSelection = prevAddOnSelection - if self.SingleSelection == True and self.entitySet.count() > 0: - self.plugIn.setLastEntitySet(self.entitySet) - return True # fine - - if self.exitAfterSelection == True: - return True # fine - - self.WaitForFirstPoint() - elif value == QadMsg.translate("Command_SSGET", "AUto") or value == "AUto": - # Passa alla selezione automatica: vengono selezionati gli oggetti sui quali si posiziona il puntatore. - # Facendo clic su un'area vuota all'interno o all'esterno di un oggetto, - # si crea il primo angolo di un rettangolo di selezione, come per il metodo Riquadro - self.SingleSelection = False - self.WaitForFirstPoint() - elif value == QadMsg.translate("Command_SSGET", "SIngle") or value == "SIngle": - # Passa al metodo Singolo: viene selezionato il primo oggetto o gruppo di oggetti indicato, - # senza che vengano richieste altre selezioni. - self.SingleSelection = True - if self.entitySet.count() > 0: - self.plugIn.setLastEntitySet(self.entitySet) - return True # fine - self.WaitForFirstPoint() - elif value == QadMsg.translate("Command_SSGET", "Help") or value == "Help": - self.help = True - self.WaitForFirstPoint() - elif type(value) == QgsPoint: # se é stato inserito il punto iniziale del rettangolo - self.currSelectionMode = QadMsg.translate("Command_SSGET", "Box") - self.points.append(value) - self.getPointMapTool().setSelectionMode(QadGetPointSelectionModeEnum.ENTITYSET_SELECTION) - self.getPointMapTool().setDrawMode(QadGetPointDrawModeEnum.ELASTIC_RECTANGLE) - self.getPointMapTool().setStartPoint(value) - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_SSGET", "Specify opposite corner: ")) - self.step = 3 - else: # se é stata selezionata un'entità - self.elaborateEntity(value, shiftKey) - - if self.SingleSelection == True and self.entitySet.count() > 0: - self.plugIn.setLastEntitySet(self.entitySet) - return True # fine - - if self.exitAfterSelection == True: - return True # fine - - self.WaitForFirstPoint() - - return False # continua - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DEL PRIMO PUNTO DEL RETTANGOLO DA OPZIONE - # FINESTRA, INTERSECA, RIQUADRO (da step = 1) - elif self.step == 2: # dopo aver atteso un punto si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - self.showMsg(QadMsg.translate("Command_SSGET", "Window not correct.")) - self.WaitForFirstPoint() - return False - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == QgsPoint: - self.points.append(value) - self.getPointMapTool().setSelectionMode(QadGetPointSelectionModeEnum.ENTITYSET_SELECTION) - self.getPointMapTool().setDrawMode(QadGetPointDrawModeEnum.ELASTIC_RECTANGLE) - - # cambio il colore impostato da setDrawMode - if self.currSelectionMode == QadMsg.translate("Command_SSGET", "Window") or value == "Window": - self.getPointMapTool().rectangleCrossingSelectionColor = self.getPointMapTool().rectangleWindowSelectionColor - elif self.currSelectionMode == QadMsg.translate("Command_SSGET", "Crossing") or value == "Crossing": - self.getPointMapTool().rectangleWindowSelectionColor = self.getPointMapTool().rectangleCrossingSelectionColor - - self.rectangleCrossingSelectionColor = getColorForCrossingSelectionArea() - self.rectangleWindowSelectionColor = getColorForWindowSelectionArea() - - self.getPointMapTool().setStartPoint(value) - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_SSGET", "Specify opposite corner: ")) - self.step = 3 - else: - self.showMsg(QadMsg.translate("Command_SSGET", "Window not correct.")) - self.WaitForFirstPoint() - - return False # continua - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DEL SECONDO PUNTO DEL RETTANGOLO (da step = 1) - elif self.step == 3: # dopo aver atteso un punto si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - self.showMsg(QadMsg.translate("Command_SSGET", "Window not correct.")) - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_SSGET", "Specify opposite corner: ")) - return False - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - shiftKey = self.getPointMapTool().shiftKey - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - shiftKey = False - value = msg - - if type(value) == QgsPoint: - self.getPointMapTool().clear() - self.points.append(value) - - if self.currSelectionMode == QadMsg.translate("Command_SSGET", "Box") or \ - self.currSelectionMode == "Box": - if self.points[0].x() < value.x(): - mode = "W" - else: - mode = "C" - elif self.currSelectionMode == QadMsg.translate("Command_SSGET", "Window") or \ - self.currSelectionMode == "Window": - mode = "W" - else: # "Interseca" - mode = "C" - - selSet = qad_utils.getSelSet(mode, self.getPointMapTool(), self.points, \ - self.getLayersToCheck()) - self.elaborateSelSet(selSet, shiftKey) - - if self.SingleSelection == True and self.entitySet.count() > 0: - self.plugIn.setLastEntitySet(self.entitySet) - return True # fine - - if self.exitAfterSelection == True: - return True # fine - - self.WaitForFirstPoint() - else: - self.showMsg(QadMsg.translate("Command_SSGET", "Window not correct.")) - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_SSGET", "Specify opposite corner: ")) - - return False # continua - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA PUNTO PER MODALITA' INTERCETTA (da step = 1 o 4) - elif self.step == 4: # dopo aver atteso un punto si riavvia il comando - if self.PLINECommand.run(msgMapTool, msg) == True: - self.showMsg("\n") - self.AddRemoveSelSetByFence(self.PLINECommand.vertices) - del self.PLINECommand - self.PLINECommand = None - - if self.SingleSelection == True and self.entitySet.count() > 0: - self.plugIn.setLastEntitySet(self.entitySet) - return True # fine - - if self.exitAfterSelection == True: - return True # fine - - self.WaitForFirstPoint() - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA PER MODALITA' FCERCHIO e ICERCHIO (da step = 1 o 5) - elif self.step == 5: # dopo aver atteso un punto si riavvia il comando - if self.CIRCLECommand.run(msgMapTool, msg) == True: - self.showMsg("\n") - if (self.CIRCLECommand.centerPt is not None) and \ - (self.CIRCLECommand.radius is not None): - circle = QadCircle() - circle.set(self.CIRCLECommand.centerPt, self.CIRCLECommand.radius) - points = circle.asPolyline() - if self.currSelectionMode == QadMsg.translate("Command_SSGET", "WCircle") or \ - self.currSelectionMode == "WCircle": - self.AddRemoveSelSetByPolygon("WP", points) - elif self.currSelectionMode == QadMsg.translate("Command_SSGET", "CCircle") or \ - self.currSelectionMode == "CCircle": - self.AddRemoveSelSetByPolygon("CP", points) - - del self.CIRCLECommand - self.CIRCLECommand = None - - if self.SingleSelection == True and self.entitySet.count() > 0: - self.plugIn.setLastEntitySet(self.entitySet) - return True # fine - - if self.exitAfterSelection == True: - return True # fine - - self.WaitForFirstPoint() - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DI SELEZIONE DI OGGETTI PER MODALITA' FOGGETTI e IOGGETTI (da step = 1 o 6) - elif self.step == 6: # dopo aver atteso un punto si riavvia il comando - if self.SSGetClass.run(msgMapTool, msg) == True: - self.showMsg("\n") - destCRS = self.SSGetClass.getPointMapTool().canvas.mapRenderer().destinationCrs() - geoms = self.SSGetClass.entitySet.getGeometryCollection(destCRS) # trasformo la geometria - - if self.currSelectionMode == QadMsg.translate("Command_SSGET", "WObjects") or \ - self.currSelectionMode == "WObjects": - self.AddRemoveSelSetByGeometry("WO", geoms) - elif self.currSelectionMode == QadMsg.translate("Command_SSGET", "CObjects") or \ - self.currSelectionMode == "CObjects": - self.AddRemoveSelSetByGeometry("CO", geoms) - - del self.SSGetClass - self.SSGetClass = None - - if self.SingleSelection == True and self.entitySet.count() > 0: - self.plugIn.setLastEntitySet(self.entitySet) - return True # fine - - if self.exitAfterSelection == True: - return True # fine - - self.WaitForFirstPoint() - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA PER MODALITA' FPOLIGONO e IPOLIGONO (da step = 1 o 7) - elif self.step == 7: # dopo aver atteso un punto si riavvia il comando - if self.MPOLYGONCommand.run(msgMapTool, msg) == True: - self.showMsg("\n") - if self.currSelectionMode == QadMsg.translate("Command_SSGET", "WPolygon") or \ - self.currSelectionMode == "WPolygon": - self.AddRemoveSelSetByPolygon("WP", self.MPOLYGONCommand.vertices) - elif self.currSelectionMode == QadMsg.translate("Command_SSGET", "CPolygon") or \ - self.currSelectionMode == "CPolygon": - self.AddRemoveSelSetByPolygon("CP", self.MPOLYGONCommand.vertices) - - del self.MPOLYGONCommand - self.MPOLYGONCommand = None - - if self.SingleSelection == True and self.entitySet.count() > 0: - self.plugIn.setLastEntitySet(self.entitySet) - return True # fine - - if self.exitAfterSelection == True: - return True # fine - - self.WaitForFirstPoint() - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DI SELEZIONE DI OGGETTI PER MODALITA' FBUFFER e IBUFFER (da step = 1 o 8) - elif self.step == 8: # dopo aver atteso un punto si riavvia il comando - if self.MBUFFERCommand.run(msgMapTool, msg) == True: - self.showMsg("\n") - - bufferGeoms = [] - for layerEntitySet in self.MBUFFERCommand.entitySet.layerEntitySetList: - geoms = layerEntitySet.getGeometryCollection() - width = qad_utils.distMapToLayerCoordinates(self.MBUFFERCommand.width, \ - self.MBUFFERCommand.getPointMapTool().canvas,\ - layerEntitySet.layer) - for geom in geoms: - bufferGeoms.append(geom.buffer(width, self.MBUFFERCommand.segments)) - - if self.currSelectionMode == QadMsg.translate("Command_SSGET", "WBuffer") or \ - self.currSelectionMode == "WBuffer": - self.AddRemoveSelSetByGeometry("WO", bufferGeoms) - elif self.currSelectionMode == QadMsg.translate("Command_SSGET", "CBuffer") or \ - self.currSelectionMode == "CBuffer": - self.AddRemoveSelSetByGeometry("CO", bufferGeoms) - - del self.MBUFFERCommand - self.MBUFFERCommand = None - - if self.SingleSelection == True and self.entitySet.count() > 0: - self.plugIn.setLastEntitySet(self.entitySet) - return True # fine - - if self.exitAfterSelection == True: - return True # fine - - self.WaitForFirstPoint() +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + comando da inserire in altri comandi per la selezione di un gruppo di feature + + ------------------- + begin : 2013-05-22 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.PyQt.QtCore import * +from qgis.PyQt.QtGui import * +from qgis.core import * + + +from ..qad_variables import QadVariables +from .qad_generic_cmd import QadCommandClass +from ..qad_msg import QadMsg +from ..qad_textwindow import QadInputTypeEnum, QadInputModeEnum +from ..qad_entity import QadEntitySet, getSelSet +from ..qad_dim import QadDimStyles +from ..qad_getpoint import QadGetPointDrawModeEnum, QadGetPointSelectionModeEnum +from .qad_pline_cmd import QadPLINECommandClass +from .qad_circle_cmd import QadCIRCLECommandClass, QadCircle +from .qad_mpolygon_cmd import QadMPOLYGONCommandClass +from ..qad_dynamicinput import QadDynamicInputContextEnum +# ho dovuto spostare in fondo questo import perché qad_mbuffer_cmd fa l'import di qad_ssget_cmd +#from qad_mbuffer_cmd import QadMBUFFERCommandClass +from ..qad_utils import getVisibleVectorLayers, distMapToLayerCoordinates +from ..qad_rubberband import getColorForCrossingSelectionArea, \ + getColorForWindowSelectionArea + +# =============================================================================== +# QadSSGetClass +# =============================================================================== +class QadSSGetClass(QadCommandClass): +# Classe che gestisce la selezione di oggetti geometrici + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadSSGetClass(self.plugIn) + + def __init__(self, plugIn): + self.init(plugIn) + + def __del__(self): + QadCommandClass.__del__(self) + #self.entitySet.deselectOnLayer() + if self.PLINECommand is not None: del self.PLINECommand + if self.CIRCLECommand is not None: del self.CIRCLECommand + if self.MPOLYGONCommand is not None: del self.MPOLYGONCommand + if self.MBUFFERCommand is not None: del self.MBUFFERCommand + if self.SSGetClass is not None: del self.SSGetClass + + + def init(self, plugIn): + QadCommandClass.__init__(self, plugIn) + self.AddOnSelection = True # se = False significa remove + self.entitySet = QadEntitySet() + self.points = [] + self.currSelectionMode = "" + # opzioni per limitare gli oggetti da selezionare + self.onlyEditableLayers = False + self.checkPointLayer = True + self.checkLineLayer = True + self.checkPolygonLayer = True + self.checkDimLayers = True # include tutte le features che compongono le quotature selezionate + + self.help = False + # se SingleSelection = True viene selezionato il primo oggetto o gruppo di oggetti indicato, + # senza che vengano richieste altre selezioni. + self.SingleSelection = False + self.pickAdd = QadVariables.get(QadMsg.translate("Environment variables", "PICKADD")) + + # se exitAfterSelection = True il comando viene terminato dopo una qualunque selezione + # indipendentemente che sia stato selezionato o meno un oggetto o gruppo di oggetti. + # usato da QadVirtualSelCommandClass + self.exitAfterSelection = False + + # selezione degli oggetti aggiunti più recentemente al gruppo di selezione (x opzione annulla) + self.lastEntitySet = QadEntitySet() + self.PLINECommand = None + self.CIRCLECommand = None + self.MPOLYGONCommand = None + self.MBUFFERCommand = None + self.SSGetClass = None + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if self.step == 4: # quando si é in fase di disegno linea + return self.PLINECommand.getPointMapTool(drawMode) + elif self.step == 5: # quando si é in fase di disegno cerchio + return self.CIRCLECommand.getPointMapTool(drawMode) + elif self.step == 6: # quando si é in fase di selezione entità + return self.SSGetClass.getPointMapTool(drawMode) + elif self.step == 7: # quando si é in fase di disegno polygono + return self.MPOLYGONCommand.getPointMapTool(drawMode) + elif self.step == 8: # quando si é in fase di disegno buffer + return self.MBUFFERCommand.getPointMapTool(drawMode) + else: + ptMapTool = QadCommandClass.getPointMapTool(self, drawMode) + #ptMapTool.setSnapType(QadSnapTypeEnum.DISABLE) non capisco perchè + ptMapTool.setOrthoMode(0) + return ptMapTool + + + def getCurrentContextualMenu(self): + if self.step == 4: # quando si é in fase di disegno linea + return self.PLINECommand.getCurrentContextualMenu() + elif self.step == 5: # quando si é in fase di disegno cerchio + return self.CIRCLECommand.getCurrentContextualMenu() + elif self.step == 6: # quando si é in fase di selezione entità + return None # return self.SSGetClass.getCurrentContextualMenu() + elif self.step == 7: # quando si é in fase di disegno polygono + return self.MPOLYGONCommand.getCurrentContextualMenu() + elif self.step == 8: # quando si é in fase di disegno buffer + return self.MBUFFERCommand.getCurrentContextualMenu() + else: + return self.contextualMenu + + + # ============================================================================ + # getLayersToCheck + # ============================================================================ + def getLayersToCheck(self): + layerList = [] + for layer in getVisibleVectorLayers(self.plugIn.canvas): # Tutti i layer vettoriali visibili + # considero solo i layer vettoriali che sono filtrati per tipo + if ((layer.geometryType() == QgsWkbTypes.PointGeometry and self.checkPointLayer == True) or \ + (layer.geometryType() == QgsWkbTypes.LineGeometry and self.checkLineLayer == True) or \ + (layer.geometryType() == QgsWkbTypes.PolygonGeometry and self.checkPolygonLayer == True)) and \ + (self.onlyEditableLayers == False or layer.isEditable()): + # se devo includere i layers delle quotature + if self.checkDimLayers == True or \ + len(QadDimStyles.getDimListByLayer(layer)) == 0: + layerList.append(layer) + + return layerList + + + # ============================================================================ + # showMsgOnAddRemove + # ============================================================================ + def showMsgOnAddRemove(self, found): + msg = QadMsg.translate("Command_SSGET", " found {0}, total {1}") + self.showMsg(msg.format(found, self.entitySet.count()), False) # non ripete il prompt 2016 + + + # ============================================================================ + # elaborateEntity + # ============================================================================ + def elaborateEntity(self, entity, shiftKey): + if self.AddOnSelection == True: # aggiungi al gruppo di selezione + if shiftKey: # se la selezione é avvenuta con shift premuto + if self.pickAdd == 0: # The objects most recently selected become the selection set + if self.entitySet.containsEntity(entity): # se l'entità era già stata selezionata + self.AddRemoveEntity(entity, False) # rimuovo l'entità + else: + self.AddRemoveEntity(entity, True) # aggiungo l'entità + else: + self.AddRemoveEntity(entity, False) # rimuovo l'entità + else: # senza tasto shift + if self.pickAdd == 0: # The objects most recently selected become the selection set + self.SetEntity(entity) + else: + self.AddRemoveEntity(entity, True) # aggiungo l'entità + else: # se si deve rimuovere dal gruppo di selezione + self.AddRemoveEntity(entity, False) # rimuovo l'entità + + + # ============================================================================ + # SetEntity + # ============================================================================ + def SetEntity(self, entity): + # controllo sul layer + if self.onlyEditableLayers == True and entity.layer.isEditable() == False: + self.showMsgOnAddRemove(0) + return + # controllo sul tipo + if (self.checkPointLayer == False and entity.layer.geometryType() == QgsWkbTypes.PointGeometry) or \ + (self.checkLineLayer == False and entity.layer.geometryType() == QgsWkbTypes.LineGeometry) or \ + (self.checkPolygonLayer == False and entity.layer.geometryType() == QgsWkbTypes.PolygonGeometry): + self.showMsgOnAddRemove(0) + return + + # controllo su layer delle quotature + # verifico se l'entità appartiene ad uno stile di quotatura + dimEntity = QadDimStyles.getDimEntity(entity) + if self.checkDimLayers == False and dimEntity is not None: + self.showMsgOnAddRemove(0) + return + + self.entitySet.deselectOnLayer() + self.entitySet.clear() + self.entitySet.addEntity(entity) + + if self.checkDimLayers == True and dimEntity is not None: + # Aggiungo i componenenti della quotatura a set + self.entitySet.unite(dimEntity.getEntitySet()) + + self.showMsgOnAddRemove(self.entitySet.count()) + self.entitySet.selectOnLayer(False) # incremental = False aaaaaaaaaaaaaaaaaaaaaaaaaa qui parte l'evento activate di qad_maptool (se il layer non è in modifica) + self.lastEntitySet.clear() + self.lastEntitySet.addEntity(entity) + + + # ============================================================================ + # AddRemoveEntity + # ============================================================================ + def AddRemoveEntity(self, entity, Add): + # controllo sul layer + if self.onlyEditableLayers == True and entity.layer.isEditable() == False: + self.showMsgOnAddRemove(0) + return + # controllo sul tipo + if (self.checkPointLayer == False and entity.layer.geometryType() == QgsWkbTypes.PointGeometry) or \ + (self.checkLineLayer == False and entity.layer.geometryType() == QgsWkbTypes.LineGeometry) or \ + (self.checkPolygonLayer == False and entity.layer.geometryType() == QgsWkbTypes.PolygonGeometry): + self.showMsgOnAddRemove(0) + return + # controllo su layer delle quotature + if self.checkDimLayers == False and len(QadDimStyles.getDimListByLayer(entity.layer)) > 0: + self.showMsgOnAddRemove(0) + return + + self.entitySet.deselectOnLayer() + if Add == True: # aggiungi al gruppo di selezione + self.entitySet.addEntity(entity) + else: # rimuovi dal gruppo di selezione + self.entitySet.removeEntity(entity) + + if self.checkDimLayers == True: + dimEntitySet = QadEntitySet() + dimEntitySet.addEntity(entity) + # La funzione verifica se le entità che fanno parte di un entitySet sono anche parte di quotatura e, + # in caso affermativo, aggiunge/rimuove tutti i componenti delle quotature all'entitySet. + QadDimStyles.addAllDimComponentsToEntitySet(dimEntitySet, self.onlyEditableLayers) + if Add == True: # aggiungi al gruppo di selezione + self.entitySet.unite(dimEntitySet) + else: # rimuovi dal gruppo di selezione + self.entitySet.subtract(dimEntitySet) + self.showMsgOnAddRemove(dimEntitySet.count()) + else: + self.showMsgOnAddRemove(1) + + self.entitySet.selectOnLayer(False) # incremental = False + self.lastEntitySet.clear() + self.lastEntitySet.addEntity(entity) + + + # ============================================================================ + # elaborateSelSet + # ============================================================================ + def elaborateSelSet(self, selSet, shiftKey): + if self.checkDimLayers == True: + dimEntitySet = QadEntitySet(selSet) + # La funzione verifica se le entità che fanno parte di un entitySet sono anche parte di quotatura e, + # in caso affermativo, aggiunge tutti i componenti delle quotature all'entitySet. + QadDimStyles.addAllDimComponentsToEntitySet(dimEntitySet, self.onlyEditableLayers) + selSet.unite(dimEntitySet) + + if self.AddOnSelection == True: # aggiungi al gruppo di selezione + if shiftKey: # se la selezione é avvenuta con shift premuto + if self.pickAdd == 0: # The objects most recently selected become the selection set + # verifico se ci sono degli oggetti non ancora selezionati + intersectSS = QadEntitySet(selSet) + intersectSS.subtract(self.entitySet) + if intersectSS.isEmpty(): # tutti gli oggetti erano già selezionati + self.AddRemoveSelSet(selSet, False) # rimuovo il gruppo di selezione + else: + self.AddRemoveSelSet(selSet, True) # aggiungo il gruppo di selezione + else: + self.AddRemoveSelSet(selSet, False) # rimuovo il gruppo di selezione + else: # senza tasto shift + if self.pickAdd == 0: # The objects most recently selected become the selection set + self.SetSelSet(selSet) + else: + self.AddRemoveSelSet(selSet, True) # aggiungo il gruppo di selezione + else: # se si deve rimuovere dal gruppo di selezione + self.AddRemoveSelSet(selSet, False) # rimuovo il gruppo di selezione + + + # ============================================================================ + # SetSelSet + # ============================================================================ + def SetSelSet(self, selSet): + for layerEntitySet in self.entitySet.layerEntitySetList: + # se il layer non é presente in selSet + if selSet.findLayerEntitySet(layerEntitySet) is None: + layerEntitySet.deselectOnLayer() + else: + layerEntitySet.deselectOnLayer() + + self.entitySet.set(selSet) + + self.showMsgOnAddRemove(self.entitySet.count()) + self.entitySet.selectOnLayer(False) # incremental = False + self.lastEntitySet.set(selSet) + + + # ============================================================================ + # AddCurrentQgsSelectedFeatures + # ============================================================================ + def AddCurrentQgsSelectedFeatures(self): + # verifico se ci sono entità correntemente selezionate + self.entitySet.initByCurrentQgsSelectedFeatures(self.getLayersToCheck()) + found = self.entitySet.count() + if found > 0: + msg = QadMsg.translate("Command_SSGET", "\nfound {0}") + self.showMsg(msg.format(found), False) # non ripete il prompt + return True + else: + return False + + + # ============================================================================ + # AddRemoveSelSet + # ============================================================================ + def AddRemoveSelSet(self, selSet, Add): + self.entitySet.deselectOnLayer() + if Add == True: # aggiungi al gruppo di selezione + self.entitySet.unite(selSet) + else: # rimuovi dal gruppo di selezione + self.entitySet.subtract(selSet) + + self.showMsgOnAddRemove(selSet.count()) + + self.entitySet.selectOnLayer(False) # incremental = False + self.lastEntitySet.set(selSet) + + # ============================================================================ + # AddRemoveSelSetByFence + # ============================================================================ + def AddRemoveSelSetByFence(self, points): + if len(points) > 1: + selSet = getSelSet("F", self.getPointMapTool(), points, \ + self.getLayersToCheck()) + self.elaborateSelSet(selSet, False) + + # ============================================================================ + # AddRemoveSelSetByPolygon + # ============================================================================ + def AddRemoveSelSetByPolygon(self, mode, points): + if len(points) > 2: + selSet = getSelSet(mode, self.getPointMapTool(), points, \ + self.getLayersToCheck()) + self.elaborateSelSet(selSet, False) + + # ============================================================================ + # AddRemoveSelSetByGeometry + # ============================================================================ + def AddRemoveSelSetByGeometry(self, mode, geom): + if type(geom) == QgsGeometry: # singola geometria + selSet = getSelSet(mode, self.getPointMapTool(), geom, \ + self.getLayersToCheck()) + else: # lista di geometrie + selSet = QadEntitySet() + for g in geom: + partial = getSelSet(mode, self.getPointMapTool(), g, \ + self.getLayersToCheck()) + selSet.unite(partial) + self.elaborateSelSet(selSet, False) + + + # ============================================================================ + # WaitForFirstPoint + # ============================================================================ + def WaitForFirstPoint(self): + self.step = 1 + + # "Finestra" "Ultimo" "Interseca" + # "Riquadro" "Tutto" "iNTercetta" + # "FPoligono" "IPoligono" + # "FCerchio" "ICerchio" + # "FOggetti" "IOggetti" + # "FBuffer" "IBuffer" + # "AGgiungi" "Elimina" + # "Precedente" "Annulla" + # "AUto" "SIngolo" "Help" + keyWords = QadMsg.translate("Command_SSGET", "Window") + "/" + \ + QadMsg.translate("Command_SSGET", "Last") + "/" + \ + QadMsg.translate("Command_SSGET", "Crossing") + "/" + \ + QadMsg.translate("Command_SSGET", "Box") + "/" + \ + QadMsg.translate("Command_SSGET", "All") + "/" + \ + QadMsg.translate("Command_SSGET", "Fence") + "/" + \ + QadMsg.translate("Command_SSGET", "WPolygon") + "/" + \ + QadMsg.translate("Command_SSGET", "CPolygon") + "/" + \ + QadMsg.translate("Command_SSGET", "WCircle") + "/" + \ + QadMsg.translate("Command_SSGET", "CCircle") + "/" + \ + QadMsg.translate("Command_SSGET", "WObjects") + "/" + \ + QadMsg.translate("Command_SSGET", "CObjects") + "/" + \ + QadMsg.translate("Command_SSGET", "WBuffer") + "/" + \ + QadMsg.translate("Command_SSGET", "CBuffer") + "/" + \ + QadMsg.translate("Command_SSGET", "Add") + "/" + \ + QadMsg.translate("Command_SSGET", "Remove") + "/" + \ + QadMsg.translate("Command_SSGET", "Previous") + "/" + \ + QadMsg.translate("Command_SSGET", "Undo") + "/" + \ + QadMsg.translate("Command_SSGET", "AUto") + "/" + \ + QadMsg.translate("Command_SSGET", "SIngle") + "/" + \ + QadMsg.translate("Command_SSGET", "Help") + englishKeyWords = "Window" + "/" + "Last" + "/" + "Crossing" + "/" + "Box" + "/" \ + + "All" + "/" + "Fence" + "/" + "WPolygon" + "/" + "CPolygon" + "/" \ + + "WCircle" + "/" + "CCircle" + "/" + "WObjects" + "/" + "CObjects" + "/" \ + + "WBuffer" + "/" + "CBuffer" + "/" + "Add" + "/" + "Remove" + "/" \ + + "Previous" + "/" + "Undo" + "/" + "AUto" + "/" + "SIngle" + "/" + "Help" + + if self.AddOnSelection == True: + prompt = QadMsg.translate("Command_SSGET", "Select Objects") + else: + prompt = QadMsg.translate("Command_SSGET", "Remove objects") + + if self.help == True: + prompt = prompt + QadMsg.translate("Command_SSGET", " or [{0}]").format(keyWords) + + prompt = prompt + QadMsg.translate("Command_SSGET", ": ") + + # imposto il map tool + self.getPointMapTool().setSelectionMode(QadGetPointSelectionModeEnum.ENTITY_SELECTION) + self.getPointMapTool().setDrawMode(QadGetPointDrawModeEnum.NONE) + # imposto i layer da controllare sul maptool + self.getPointMapTool().layersToCheck = self.getLayersToCheck() + self.points = [] + + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, QadInputModeEnum.NONE) + # input dinamico + di = self.getPointMapTool().getDynamicInput() + di.context = QadDynamicInputContextEnum.NONE + di.showInputMsg(prompt, QadInputTypeEnum.NONE) + return + + def run(self, msgMapTool = False, msg = None): + # ritorna: + # True per selezione non terminata + # False per selezione terminata + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # errore + + # ========================================================================= + # RICHIESTA PRIMO PUNTO PER SELEZIONE OGGETTI + if self.step == 0: + # if you can also select objects before you start a command + if QadVariables.get(QadMsg.translate("Environment variables", "PICKFIRST")) == 1: + if self.AddCurrentQgsSelectedFeatures() == True: + self.plugIn.setLastEntitySet(self.entitySet) + return True; + self.WaitForFirstPoint() + return False # continua + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA PRIMO PUNTO PER SELEZIONE OGGETTI + elif self.step == 1: # dopo aver atteso un punto o enter o una parola chiave si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + if self.entitySet.count() > 0: + self.plugIn.setLastEntitySet(self.entitySet) + return True # fine + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False # continua + + shiftKey = self.getPointMapTool().shiftKey + + # se é stata selezionata un'entità + if self.getPointMapTool().entity.isInitialized(): + value = self.getPointMapTool().entity + else: + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + shiftKey = False + value = msg + + if value is None: + if self.entitySet.count() > 0: + self.plugIn.setLastEntitySet(self.entitySet) + return True # fine + + if type(value) == unicode: + self.currSelectionMode = value + + if value == QadMsg.translate("Command_SSGET", "Window") or value == "Window" or \ + value == QadMsg.translate("Command_SSGET", "Crossing") or value == "Crossing": + # "Finestra" = Seleziona tutti gli oggetti che si trovano completamente all'interno di un rettangolo definito da due punti + # "Interseca" = Seleziona gli oggetti che intersecano o si trovano all'interno di un'area definita da due punti + # imposto il map tool + self.getPointMapTool().setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) + self.getPointMapTool().setDrawMode(QadGetPointDrawModeEnum.NONE) + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_SSGET", "First corner: ")) + self.step = 2 + if value == QadMsg.translate("Command_SSGET", "Last") or value == "Last": + # Seleziona l'ultima entità inserita + if self.plugIn.getLastEntity() is None: + self.showMsgOnAddRemove(0) + else: + self.AddRemoveEntity(self.plugIn.getLastEntity(), self.AddOnSelection) + if self.SingleSelection == True and self.entitySet.count() > 0: + self.plugIn.setLastEntitySet(self.entitySet) + return True # fine + + if self.exitAfterSelection == True: + return True # fine + + self.WaitForFirstPoint() + elif value == QadMsg.translate("Command_SSGET", "Box") or value == "Box": + # Seleziona tutti gli oggetti che intersecano o si trovano all'interno di un rettangolo specificato da due punti. + # Se i punti del rettangolo sono specificati da destra a sinistra, Riquadro equivale ad Interseca, + # altrimenti é equivalente a Finestra + # imposto il map tool + self.getPointMapTool().setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) + self.getPointMapTool().setDrawMode(QadGetPointDrawModeEnum.NONE) + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_SSGET", "First corner: ")) + self.step = 2 + elif value == QadMsg.translate("Command_SSGET", "All") or value == "All": + # Seleziona tutti gli oggetti + selSet = getSelSet("X", self.getPointMapTool(), None, \ + self.getLayersToCheck()) + self.elaborateSelSet(selSet, False) + if self.SingleSelection == True and self.entitySet.count() > 0: + self.plugIn.setLastEntitySet(self.entitySet) + return True # fine + + if self.exitAfterSelection == True: + return True # fine + + self.WaitForFirstPoint() + elif value == QadMsg.translate("Command_SSGET", "Fence") or value == "Fence": + # Seleziona tutti gli oggetti che intersecano una polilinea + self.PLINECommand = QadPLINECommandClass(self.plugIn) + # se questo flag = True il comando serve all'interno di un altro comando per disegnare una linea + # che non verrà salvata su un layer + self.PLINECommand.virtualCmd = True + self.PLINECommand.run(msgMapTool, msg) + self.step = 4 + elif value == QadMsg.translate("Command_SSGET", "WPolygon") or value == "WPolygon" or \ + value == QadMsg.translate("Command_SSGET", "CPolygon") or value == "CPolygon": + # "FPoligono" = Seleziona oggetti che si trovano completamente all'interno di un poligono definito da punti + # "IPoligono" = Seleziona gli oggetti che intersecano o si trovano all'interno di un poligono definito specificando dei punti + self.MPOLYGONCommand = QadMPOLYGONCommandClass(self.plugIn) + # se questo flag = True il comando serve all'interno di un altro comando per disegnare una linea + # che non verrà salvata su un layer + self.MPOLYGONCommand.virtualCmd = True + + if value == QadMsg.translate("Command_SSGET", "WPolygon") or value == "WPolygon": + self.MPOLYGONCommand.setRubberBandColor(None, getColorForWindowSelectionArea()) + else: + self.MPOLYGONCommand.setRubberBandColor(None, getColorForCrossingSelectionArea()) + + self.MPOLYGONCommand.run(msgMapTool, msg) + self.step = 7 + elif value == QadMsg.translate("Command_SSGET", "WCircle") or value == "WCircle" or \ + value == QadMsg.translate("Command_SSGET", "CCircle") or value == "CCircle": + # "FCerchio" = Seleziona oggetti che si trovano completamente all'interno di un cerchio + # "ICerchio" = Seleziona oggetti che intersecano o si trovano all'interno di un cerchio + self.CIRCLECommand = QadCIRCLECommandClass(self.plugIn) + # se questo flag = True il comando serve all'interno di un altro comando per disegnare un cerchio + # che non verrà salvata su un layer + self.CIRCLECommand.virtualCmd = True + + if value == QadMsg.translate("Command_SSGET", "WCircle") or value == "WCircle": + self.CIRCLECommand.setRubberBandColor(None, getColorForWindowSelectionArea()) + else: + self.CIRCLECommand.setRubberBandColor(None, getColorForCrossingSelectionArea()) + + self.CIRCLECommand.run(msgMapTool, msg) + self.step = 5 + elif value == QadMsg.translate("Command_SSGET", "WObjects") or value == "WObjects" or \ + value == QadMsg.translate("Command_SSGET", "CObjects") or value == "CObjects": + # "FOggetti" = Seleziona oggetti che si trovano completamente all'interno di oggetti da selezionare + # "IOggetti" = Seleziona oggetti che intersecano o si trovano all'interno di oggetti da selezionare + self.SSGetClass = QadSSGetClass(self.plugIn) + self.SSGetClass.run(msgMapTool, msg) + self.step = 6 + elif value == QadMsg.translate("Command_SSGET", "WBuffer") or value == "WBuffer" or \ + value == QadMsg.translate("Command_SSGET", "CBuffer") or value == "CBuffer": + # ho dovuto spostare questo import perché qad_mbuffer_cmd fa l'import di qad_ssget_cmd + from .qad_mbuffer_cmd import QadMBUFFERCommandClass + + # "FBuffer" = Seleziona oggetti che si trovano completamente all'interno di buffer intorno ad oggetti da selezionare + # "IBuffer" = Seleziona oggetti che intersecano o si trovano all'interno di buffer intorno ad oggetti da selezionare + self.MBUFFERCommand = QadMBUFFERCommandClass(self.plugIn) + # se questo flag = True il comando serve all'interno di un altro comando per disegnare un cerchio + # che non verrà salvata su un layer + self.MBUFFERCommand.virtualCmd = True + + if value == QadMsg.translate("Command_SSGET", "WBuffer") or value == "WBuffer": + self.MBUFFERCommand.setRubberBandColor(None, getColorForWindowSelectionArea()) + else: + self.MBUFFERCommand.setRubberBandColor(None, getColorForCrossingSelectionArea()) + + self.MBUFFERCommand.run(msgMapTool, msg) + self.step = 8 + elif value == QadMsg.translate("Command_SSGET", "Add") or value == "Add": + # Passa al metodo Aggiungi: gli oggetti selezionati possono essere aggiunti al gruppo di selezione + self.AddOnSelection = True + self.WaitForFirstPoint() + elif value == QadMsg.translate("Command_SSGET", "Remove") or value == "Remove": + # Passa al metodo Rimuovi: gli oggetti possono essere rimossi dal gruppo di selezione + self.AddOnSelection = False + self.WaitForFirstPoint() + elif value == QadMsg.translate("Command_SSGET", "Previous") or value == "Previous": + # Seleziona il gruppo di selezione più recente + if self.plugIn.lastEntitySet is None: + self.showMsgOnAddRemove(0) + else: + entitySet = QadEntitySet() + entitySet.set(self.plugIn.lastEntitySet) + # controllo sul layer + if self.onlyEditableLayers == True: + entitySet.removeNotEditable() + # controllo sul tipo + if self.checkPointLayer == False: + entitySet.removeGeomType(QgsWkbTypes.PointGeometry) + if self.checkLineLayer == False: + entitySet.removeGeomType(QgsWkbTypes.LineGeometry) + if self.checkPolygonLayer == False: + entitySet.removeGeomType(QgsWkbTypes.PolygonGeometry) + # controllo sulle quotature + if self.checkDimLayers == False: + QadDimStyles.removeAllDimLayersFromEntitySet(entitySet) + + entitySet.removeNotExisting() + self.elaborateSelSet(entitySet, False) + if self.SingleSelection == True and self.entitySet.count() > 0: + self.plugIn.setLastEntitySet(self.entitySet) + return True # fine + + if self.exitAfterSelection == True: + return True # fine + + self.WaitForFirstPoint() + elif value == QadMsg.translate("Command_SSGET", "Undo") or value == "Undo": + # Annulla la selezione dell'oggetto aggiunto più recentemente al gruppo di selezione. + # Inverto il tipo di selezione + prevAddOnSelection = self.AddOnSelection + self.AddOnSelection = not self.AddOnSelection + self.elaborateSelSet(self.lastEntitySet, False) + # Ripristino il tipo di selezione + self.AddOnSelection = prevAddOnSelection + if self.SingleSelection == True and self.entitySet.count() > 0: + self.plugIn.setLastEntitySet(self.entitySet) + return True # fine + + if self.exitAfterSelection == True: + return True # fine + + self.WaitForFirstPoint() + elif value == QadMsg.translate("Command_SSGET", "AUto") or value == "AUto": + # Passa alla selezione automatica: vengono selezionati gli oggetti sui quali si posiziona il puntatore. + # Facendo clic su un'area vuota all'interno o all'esterno di un oggetto, + # si crea il primo angolo di un rettangolo di selezione, come per il metodo Riquadro + self.SingleSelection = False + self.WaitForFirstPoint() + elif value == QadMsg.translate("Command_SSGET", "SIngle") or value == "SIngle": + # Passa al metodo Singolo: viene selezionato il primo oggetto o gruppo di oggetti indicato, + # senza che vengano richieste altre selezioni. + self.SingleSelection = True + if self.entitySet.count() > 0: + self.plugIn.setLastEntitySet(self.entitySet) + return True # fine + self.WaitForFirstPoint() + elif value == QadMsg.translate("Command_SSGET", "Help") or value == "Help": + self.help = True + self.WaitForFirstPoint() + elif type(value) == QgsPointXY: # se é stato inserito il punto iniziale del rettangolo + self.currSelectionMode = QadMsg.translate("Command_SSGET", "Box") + self.points.append(value) + + self.getPointMapTool().setSelectionMode(QadGetPointSelectionModeEnum.ENTITYSET_SELECTION) + self.getPointMapTool().setDrawMode(QadGetPointDrawModeEnum.ELASTIC_RECTANGLE) + self.getPointMapTool().setStartPoint(value) + + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_SSGET", "Specify opposite corner: ")) + self.step = 3 + else: # se é stata selezionata un'entità + self.elaborateEntity(value, shiftKey) + + if self.SingleSelection == True and self.entitySet.count() > 0: + self.plugIn.setLastEntitySet(self.entitySet) + return True # fine + + if self.exitAfterSelection == True: + return True # fine + + self.WaitForFirstPoint() + + return False # continua + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL PRIMO PUNTO DEL RETTANGOLO DA OPZIONE + # FINESTRA, INTERSECA, RIQUADRO (da step = 1) + elif self.step == 2: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + self.showMsg(QadMsg.translate("Command_SSGET", "Window not correct.")) + self.WaitForFirstPoint() + return False + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == QgsPointXY: + self.points.append(value) + self.getPointMapTool().setSelectionMode(QadGetPointSelectionModeEnum.ENTITYSET_SELECTION) + self.getPointMapTool().setDrawMode(QadGetPointDrawModeEnum.ELASTIC_RECTANGLE) + + # cambio il colore impostato da setDrawMode + if self.currSelectionMode == QadMsg.translate("Command_SSGET", "Window") or value == "Window": + self.getPointMapTool().rectangleCrossingSelectionColor = self.getPointMapTool().rectangleWindowSelectionColor + elif self.currSelectionMode == QadMsg.translate("Command_SSGET", "Crossing") or value == "Crossing": + self.getPointMapTool().rectangleWindowSelectionColor = self.getPointMapTool().rectangleCrossingSelectionColor + + self.rectangleCrossingSelectionColor = getColorForCrossingSelectionArea() + self.rectangleWindowSelectionColor = getColorForWindowSelectionArea() + + self.getPointMapTool().setStartPoint(value) + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_SSGET", "Specify opposite corner: ")) + self.step = 3 + else: + self.showMsg(QadMsg.translate("Command_SSGET", "Window not correct.")) + self.WaitForFirstPoint() + + return False # continua + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL SECONDO PUNTO DEL RETTANGOLO (da step = 1) + elif self.step == 3: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + self.showMsg(QadMsg.translate("Command_SSGET", "Window not correct.")) + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_SSGET", "Specify opposite corner: ")) + return False + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + shiftKey = self.getPointMapTool().shiftKey + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + shiftKey = False + value = msg + + if type(value) == QgsPointXY: + self.getPointMapTool().clear() + self.points.append(value) + + if self.currSelectionMode == QadMsg.translate("Command_SSGET", "Box") or \ + self.currSelectionMode == "Box": + if self.points[0].x() < value.x(): + mode = "W" + else: + mode = "C" + elif self.currSelectionMode == QadMsg.translate("Command_SSGET", "Window") or \ + self.currSelectionMode == "Window": + mode = "W" + else: # "Interseca" + mode = "C" + + selSet = getSelSet(mode, self.getPointMapTool(), self.points, \ + self.getLayersToCheck()) + self.elaborateSelSet(selSet, shiftKey) + + if self.SingleSelection == True and self.entitySet.count() > 0: + self.plugIn.setLastEntitySet(self.entitySet) + return True # fine + + if self.exitAfterSelection == True: + return True # fine + + self.WaitForFirstPoint() + else: + self.showMsg(QadMsg.translate("Command_SSGET", "Window not correct.")) + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_SSGET", "Specify opposite corner: ")) + + return False # continua + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA PUNTO PER MODALITA' INTERCETTA (da step = 1 o 4) + elif self.step == 4: # dopo aver atteso un punto si riavvia il comando + if self.PLINECommand.run(msgMapTool, msg) == True: + self.showMsg("\n") + self.AddRemoveSelSetByFence(self.PLINECommand.polyline.asPolyline()) + del self.PLINECommand + self.PLINECommand = None + + if self.SingleSelection == True and self.entitySet.count() > 0: + self.plugIn.setLastEntitySet(self.entitySet) + return True # fine + + if self.exitAfterSelection == True: + return True # fine + + self.WaitForFirstPoint() + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA PER MODALITA' FCERCHIO e ICERCHIO (da step = 1 o 5) + elif self.step == 5: # dopo aver atteso un punto si riavvia il comando + if self.CIRCLECommand.run(msgMapTool, msg) == True: + self.showMsg("\n") + if (self.CIRCLECommand.centerPt is not None) and \ + (self.CIRCLECommand.radius is not None): + circle = QadCircle() + circle.set(self.CIRCLECommand.centerPt, self.CIRCLECommand.radius) + points = circle.asPolyline() + if self.currSelectionMode == QadMsg.translate("Command_SSGET", "WCircle") or \ + self.currSelectionMode == "WCircle": + self.AddRemoveSelSetByPolygon("WP", points) + elif self.currSelectionMode == QadMsg.translate("Command_SSGET", "CCircle") or \ + self.currSelectionMode == "CCircle": + self.AddRemoveSelSetByPolygon("CP", points) + + del self.CIRCLECommand + self.CIRCLECommand = None + + if self.SingleSelection == True and self.entitySet.count() > 0: + self.plugIn.setLastEntitySet(self.entitySet) + return True # fine + + if self.exitAfterSelection == True: + return True # fine + + self.WaitForFirstPoint() + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DI SELEZIONE DI OGGETTI PER MODALITA' FOGGETTI e IOGGETTI (da step = 1 o 6) + elif self.step == 6: # dopo aver atteso un punto si riavvia il comando + if self.SSGetClass.run(msgMapTool, msg) == True: + self.showMsg("\n") + destCRS = self.SSGetClass.getPointMapTool().canvas.mapSettings().destinationCrs() + geoms = self.SSGetClass.entitySet.getGeometryCollection(destCRS) # trasformo la geometria + + if self.currSelectionMode == QadMsg.translate("Command_SSGET", "WObjects") or \ + self.currSelectionMode == "WObjects": + self.AddRemoveSelSetByGeometry("WO", geoms) + elif self.currSelectionMode == QadMsg.translate("Command_SSGET", "CObjects") or \ + self.currSelectionMode == "CObjects": + self.AddRemoveSelSetByGeometry("CO", geoms) + + del self.SSGetClass + self.SSGetClass = None + + if self.SingleSelection == True and self.entitySet.count() > 0: + self.plugIn.setLastEntitySet(self.entitySet) + return True # fine + + if self.exitAfterSelection == True: + return True # fine + + self.WaitForFirstPoint() + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA PER MODALITA' FPOLIGONO e IPOLIGONO (da step = 1 o 7) + elif self.step == 7: # dopo aver atteso un punto si riavvia il comando + if self.MPOLYGONCommand.run(msgMapTool, msg) == True: + self.showMsg("\n") + if self.currSelectionMode == QadMsg.translate("Command_SSGET", "WPolygon") or \ + self.currSelectionMode == "WPolygon": + self.AddRemoveSelSetByPolygon("WP", self.MPOLYGONCommand.PLINECommand.polyline.asPolyline()) + elif self.currSelectionMode == QadMsg.translate("Command_SSGET", "CPolygon") or \ + self.currSelectionMode == "CPolygon": + self.AddRemoveSelSetByPolygon("CP", self.MPOLYGONCommand.PLINECommand.polyline.asPolyline()) + + del self.MPOLYGONCommand + self.MPOLYGONCommand = None + + if self.SingleSelection == True and self.entitySet.count() > 0: + self.plugIn.setLastEntitySet(self.entitySet) + return True # fine + + if self.exitAfterSelection == True: + return True # fine + + self.WaitForFirstPoint() + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DI SELEZIONE DI OGGETTI PER MODALITA' FBUFFER e IBUFFER (da step = 1 o 8) + elif self.step == 8: # dopo aver atteso un punto si riavvia il comando + if self.MBUFFERCommand.run(msgMapTool, msg) == True: + self.showMsg("\n") + + bufferGeoms = [] + for layerEntitySet in self.MBUFFERCommand.entitySet.layerEntitySetList: + geoms = layerEntitySet.getGeometryCollection() + width = distMapToLayerCoordinates(self.MBUFFERCommand.width, \ + self.MBUFFERCommand.getPointMapTool().canvas,\ + layerEntitySet.layer) + for geom in geoms: + bufferGeoms.append(geom.buffer(width, self.MBUFFERCommand.segments)) + + if self.currSelectionMode == QadMsg.translate("Command_SSGET", "WBuffer") or \ + self.currSelectionMode == "WBuffer": + self.AddRemoveSelSetByGeometry("WO", bufferGeoms) + elif self.currSelectionMode == QadMsg.translate("Command_SSGET", "CBuffer") or \ + self.currSelectionMode == "CBuffer": + self.AddRemoveSelSetByGeometry("CO", bufferGeoms) + + del self.MBUFFERCommand + self.MBUFFERCommand = None + + if self.SingleSelection == True and self.entitySet.count() > 0: + self.plugIn.setLastEntitySet(self.entitySet) + return True # fine + + if self.exitAfterSelection == True: + return True # fine + + self.WaitForFirstPoint() return False \ No newline at end of file diff --git a/cmd/qad_stretch_cmd.py b/cmd/qad_stretch_cmd.py new file mode 100644 index 00000000..3b95794d --- /dev/null +++ b/cmd/qad_stretch_cmd.py @@ -0,0 +1,921 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + comando STRETCH per stirare oggetti grafici ok + + ------------------- + begin : 2013-07-15 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.PyQt.QtGui import QIcon +from qgis.core import QgsPointXY, QgsGeometry, QgsRectangle + + +from ..qad_line import QadLine +from .qad_stretch_maptool import Qad_stretch_maptool, Qad_stretch_maptool_ModeEnum, Qad_gripStretch_maptool +from ..qad_getpoint import QadGetPointDrawModeEnum +from ..qad_textwindow import QadInputTypeEnum, QadInputModeEnum +from .qad_mpolygon_cmd import QadMPOLYGONCommandClass +from .qad_rectangle_cmd import QadRECTANGLECommandClass +from .qad_generic_cmd import QadCommandClass +from ..qad_variables import QadVariables +from ..qad_msg import QadMsg +from .. import qad_utils +from .. import qad_layer +from .. import qad_stretch_fun +from .. import qad_grip +from ..qad_entity import QadEntitySet, getSelSet, QadEntityTypeEnum, QadEntity +from ..qad_dim import QadDimStyles, QadDimEntity +from ..qad_multi_geom import fromQadGeomToQgsGeom + + + +# Classe che gestisce il comando STRETCH +class QadSTRETCHCommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadSTRETCHCommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "STRETCH") + + def getEnglishName(self): + return "STRETCH" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runSTRETCHCommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/stretch.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_STRETCH", "Stretches objects.") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + self.AddOnSelection = True # se = False significa remove + self.points = [] + self.MPOLYGONCommand = None + self.SSGeomList = [] # lista di entità da stirare con geom di selezione + self.basePt = QgsPointXY() + + def __del__(self): + QadCommandClass.__del__(self) + if self.MPOLYGONCommand is not None: + del self.MPOLYGONCommand + for SSGeom in self.SSGeomList: + SSGeom[0].deselectOnLayer() + + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if self.step == 2: # quando si é in fase di disegno linea + return self.MPOLYGONCommand.getPointMapTool(drawMode) + else: + if (self.plugIn is not None): + if self.PointMapTool is None: + self.PointMapTool = Qad_stretch_maptool(self.plugIn) + return self.PointMapTool + else: + return None + + + def getCurrentContextualMenu(self): + if self.step == 2: # quando si é in fase di disegno linea + return self.MPOLYGONCommand.getCurrentContextualMenu() + else: + return self.contextualMenu + + + def stretch(self, entity, containerGeom, offsetX, offsetY, tolerance2ApproxCurve, openForm = True): + # entity = entità da stirare + # ptList = lista dei punti da stirare + # offsetX, offsetY = spostamento da applicare + # tolerance2ApproxCurve = tolleranza per ricreare le curve + + if entity.whatIs() == "DIMENTITY": + dimEntity = entity + else: + # verifico se l'entità appartiene ad uno stile di quotatura + dimEntity = QadDimStyles.getDimEntity(entity) + + if dimEntity is None: + stretchedGeom = entity.getQadGeom() + # controllo inserito perchè con le quote, questa viene cancellata e ricreata quindi alcuni oggetti potrebbero non esistere più + if stretchedGeom is None: # se non c'è lo salto senza errore + return True + # stiro la feature + stretchedGeom = qad_stretch_fun.stretchQadGeometry(stretchedGeom, containerGeom, \ + offsetX, offsetY) + + if stretchedGeom is not None: + # trasformo la geometria nel crs del layer + f = entity.getFeature() + f.setGeometry(fromQadGeomToQgsGeom(stretchedGeom, entity.layer)) + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, entity.layer, f, False, False) == False: + return False + + else: + # stiro la quota + if dimEntity.deleteToLayers(self.plugIn) == False: + return False + newDimEntity = QadDimEntity(dimEntity) # la copio + newDimEntity.stretch(containerGeom, offsetX, offsetY) + if newDimEntity.addToLayers(self.plugIn) == False: + return False + + return True + + + # ============================================================================ + # stretchFeatures + # ============================================================================ + def stretchFeatures(self, newPt): + # mi ricavo un unico QadEntitySet con le entità selezionate + entitySet = QadEntitySet() + for SSGeom in self.SSGeomList: + entitySet.unite(SSGeom[0]) + self.plugIn.beginEditCommand("Feature stretched", entitySet.getLayerList()) + openForm = True if entitySet.count() == 1 else False + + dimElaboratedList = [] # lista delle quotature già elaborate + + tolerance2ApproxCurve = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) + offsetX = newPt.x() - self.basePt.x() + offsetY = newPt.y() - self.basePt.y() + + entity = QadEntity() + for SSGeom in self.SSGeomList: + # copio entitySet + entitySet = QadEntitySet(SSGeom[0]) + geomSel = SSGeom[1] + + for layerEntitySet in entitySet.layerEntitySetList: + layer = layerEntitySet.layer + + for featureId in layerEntitySet.featureIds: + entity.set(layer, featureId) + + # verifico se l'entità appartiene ad uno stile di quotatura + dimEntity = QadDimStyles.getDimEntity(entity) + if dimEntity is None: + if self.stretch(entity, geomSel, offsetX, offsetY, tolerance2ApproxCurve, openForm) == False: + self.plugIn.destroyEditCommand() + return + else: + found = False + for dimElaborated in dimElaboratedList: + if dimElaborated == dimEntity: + found = True + + if found == False: # quota non ancora elaborata + # aggiungo i layer dei componenti della quota + self.plugIn.addLayerListToLastEditCommand("Feature stretched", + [dimEntity.getSymbolLayer(), dimEntity.getLinearLayer(), dimEntity.getTextualLayer()]) + + dimElaboratedList.append(dimEntity) + if self.stretch(dimEntity, geomSel, offsetX, offsetY, tolerance2ApproxCurve) == False: + self.plugIn.destroyEditCommand() + return + + self.plugIn.endEditCommand() + + + # ============================================================================ + # setEntitySetGeom + # ============================================================================ + def setEntitySetGeom(self, entitySet, selGeom): + for SSGeom in self.SSGeomList: + SSGeom[0].deselectOnLayer() + del self.SSGeomList[:] # svuoto la lista + # aggiuge il gruppo di selezione con la geometria usata per la selezione + self.SSGeomList.append([entitySet, selGeom]) + entitySet.selectOnLayer(False) # incremental = False + + # ============================================================================ + # addEntitySetGeom + # ============================================================================ + def addEntitySetGeom(self, entitySet, selGeom): + # elimino dai gruppi precedenti gli oggetti presenti in entitySet + self.removeEntitySet(entitySet) + # aggiuge il gruppo di selezione con la geometria usata per la selezione + self.SSGeomList.append([entitySet, selGeom]) + entitySet.selectOnLayer(True) # incremental = True + + + # ============================================================================ + # removeEntitySet + # ============================================================================ + def removeEntitySet(self, entitySet): + # elimino dai gruppi precedenti gli oggetti presenti in entitySet + for SSGeom in self.SSGeomList: + SSGeom[0].subtract(entitySet) + for SSGeom in self.SSGeomList: + SSGeom[0].selectOnLayer(False) # incremental = False + + + # ============================================================================ + # SSGeomListIsEmpty + # ============================================================================ + def SSGeomListIsEmpty(self): + if len(self.SSGeomList) == 0: + return True + for SSGeom in self.SSGeomList: + if SSGeom[0].isEmpty() == False: + return False + return True + + + # ============================================================================ + # waitForObjectSel + # ============================================================================ + def waitForObjectSel(self): + self.step = 1 + # imposto il map tool + self.getPointMapTool().setMode(Qad_stretch_maptool_ModeEnum.ASK_FOR_FIRST_PT_RECTANGLE) + + keyWords = QadMsg.translate("Command_STRETCH", "Polygon") + "/" + \ + QadMsg.translate("Command_STRETCH", "Add") + "/" + \ + QadMsg.translate("Command_STRETCH", "Remove") + + if self.AddOnSelection == True: + prompt = QadMsg.translate("Command_STRETCH", "Select vertices") + else: + prompt = QadMsg.translate("Command_STRETCH", "Remove vertices") + prompt = prompt + QadMsg.translate("Command_STRETCH", " to stretch crossed by a selection window or [{0}]: ").format(keyWords) + + englishKeyWords = "Polygon" + "/" + "Add" + "/" + "Remove" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, QadInputModeEnum.NONE) + + + # ============================================================================ + # waitForBasePt + # ============================================================================ + def waitForBasePt(self): + self.step = 4 + # imposto il map tool + self.getPointMapTool().setMode(Qad_stretch_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_BASE_PT) + + keyWords = QadMsg.translate("Command_STRETCH", "Displacement") + prompt = QadMsg.translate("Command_STRETCH", "Specify base point or [{0}] <{0}>: ").format(keyWords) + + englishKeyWords = "Displacement" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, QadInputModeEnum.NONE) + + + # ============================================================================ + # run + # ============================================================================ + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + # ========================================================================= + # RICHIESTA SELEZIONE OGGETTI + if self.step == 0: # inizio del comando + # si appresta ad attendere la selezione degli oggetti da stirare + self.waitForObjectSel() + return False + + # ========================================================================= + # RISPOSTA ALLA SELEZIONE OGGETTI DA STIRARE + elif self.step == 1: + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + value = None + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("Command_STRETCH", "Polygon") or value == "Polygon": + # Seleziona tutti gli oggetti che sono interni al poligono + self.MPOLYGONCommand = QadMPOLYGONCommandClass(self.plugIn) + # se questo flag = True il comando serve all'interno di un altro comando per disegnare una linea + # che non verrà salvata su un layer + self.MPOLYGONCommand.virtualCmd = True + self.MPOLYGONCommand.run(msgMapTool, msg) + self.step = 2 + return False + elif value == QadMsg.translate("Command_SSGET", "Add") or value == "Add": + # Passa al metodo Aggiungi: gli oggetti selezionati possono essere aggiunti al gruppo di selezione + self.AddOnSelection = True + elif value == QadMsg.translate("Command_SSGET", "Remove") or value == "Remove": + # Passa al metodo Rimuovi: gli oggetti possono essere rimossi dal gruppo di selezione + self.AddOnSelection = False + elif type(value) == QgsPointXY: # se é stato selezionato un punto + del self.points[:] # svuoto la lista + self.points.append(value) + # imposto il map tool + self.getPointMapTool().setMode(Qad_stretch_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT_RECTANGLE) + self.getPointMapTool().setStartPoint(value) + + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_STRETCH", "Specify opposite corner: ")) + self.step = 3 + return False + else: + if self.SSGeomListIsEmpty(): + return True + # si appresta ad attendere il punto base o lo spostamento + self.waitForBasePt() + return False + + # si appresta ad attendere la selezione degli oggetti da stirare + self.waitForObjectSel() + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA PUNTO PER MODALITA' POLIGONO (da step = 1) + elif self.step == 2: # dopo aver atteso un punto si riavvia il comando + if self.MPOLYGONCommand.run(msgMapTool, msg) == True: + if self.MPOLYGONCommand.PLINECommand.polyline.qty() > 0: + # cerco tutte le geometrie intersecanti il poligono + # e considerando solo layer editabili + selSet = getSelSet("CP", self.getPointMapTool(), self.MPOLYGONCommand.PLINECommand.polyline.asPolyline(), \ + None, True, True, True, \ + True) + # se la selezione é avvenuta con shift premuto o se si deve rimuovere il gruppo selSet dal gruppo + if self.AddOnSelection == False: + self.removeEntitySet(selSet) + else: + self.setEntitySetGeom(selSet, QgsGeometry.fromPolygonXY([self.MPOLYGONCommand.PLINECommand.polyline.asPolyline()])) + + del self.MPOLYGONCommand + self.MPOLYGONCommand = None + + # si appresta ad attendere la selezione degli oggetti da stirare + self.waitForObjectSel() + self.getPointMapTool().refreshSnapType() # aggiorno lo snapType che può essere variato dal maptool di mpolygon + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA PUNTO PER MODALITA' FINESTRA (da step = 1) + elif self.step == 3: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + self.showMsg(QadMsg.translate("Command_STRETCH", "Window not correct.")) + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_STRETCH", "Specify opposite corner: ")) + return False + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + shiftKey = self.getPointMapTool().shiftKey + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + shiftKey = False + value = msg + + + if type(value) == QgsPointXY: + self.points.append(value) + # cerco tutte le geometrie intersecanti il rettangolo + # e considerando solo layer editabili + selSet = getSelSet("C", self.getPointMapTool(), self.points, \ + None, True, True, True, \ + True) + # se si deve rimuovere il gruppo entitySet dal gruppo + if self.AddOnSelection == False: + self.removeEntitySet(selSet) + else: + if shiftKey: # se la selezione é avvenuta con shift premuto + self.addEntitySetGeom(selSet, QgsGeometry.fromRect(QgsRectangle(self.points[0], self.points[1]))) + else: + self.setEntitySetGeom(selSet, QgsGeometry.fromRect(QgsRectangle(self.points[0], self.points[1]))) + # si appresta ad attendere la selezione degli oggetti da stirare + self.waitForObjectSel() + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA PUNTO BASE (da step = 1) + elif self.step == 4: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + pass # opzione di default "spostamento" + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + # imposto il map tool + self.getPointMapTool().SSGeomList = self.SSGeomList + + if value is None or type(value) == unicode: + self.basePt.set(0, 0) + self.getPointMapTool().basePt = self.basePt + self.getPointMapTool().setMode(Qad_stretch_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_MOVE_PT) + # si appresta ad attendere un punto + msg = QadMsg.translate("Command_STRETCH", "Specify the displacement from the origin point 0,0 <{0}, {1}>: ") + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(msg.format(str(self.plugIn.lastOffsetPt.x()), str(self.plugIn.lastOffsetPt.y())), \ + QadInputTypeEnum.POINT2D, \ + self.plugIn.lastOffsetPt, \ + "", QadInputModeEnum.NONE) + self.step = 5 + elif type(value) == QgsPointXY: # se é stato inserito il punto base + self.basePt.set(value.x(), value.y()) + + # imposto il map tool + self.getPointMapTool().basePt = self.basePt + self.getPointMapTool().setMode(Qad_stretch_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_MOVE_PT) + + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(QadMsg.translate("Command_STRETCH", "Specify second point or : "), \ + QadInputTypeEnum.POINT2D, \ + None, \ + "", QadInputModeEnum.NONE) + self.step = 6 + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL PUNTO DI SPOSTAMENTO (da step = 2) + elif self.step == 5: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + self.plugIn.setLastOffsetPt(value) + self.stretchFeatures(value) + return True # fine comando + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA SECONDO PUNTO PER SPOSTAMENTO (da step = 2) + elif self.step == 6: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if value is None: + newPt = QgsPointXY(self.basePt.x() * 2, self.basePt.y() * 2) + self.stretchFeatures(newPt) + elif type(value) == QgsPointXY: # se é stato inserito lo spostamento con un punto + self.stretchFeatures(value) + + return True # fine comando + + + +# ============================================================================ +# Classe che gestisce il comando STRETCH per i grip +# ============================================================================ +class QadGRIPSTRETCHCommandClass(QadCommandClass): + + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadGRIPSTRETCHCommandClass(self.plugIn) + + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + self.selectedEntityGripPoints = [] # lista in cui ogni elemento è una entità + una lista di punti da stirare + self.basePt = QgsPointXY() + self.skipToNextGripCommand = False + self.copyEntities = False + self.nOperationsToUndo = 0 + + + def __del__(self): + QadCommandClass.__del__(self) + + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if (self.plugIn is not None): + if self.PointMapTool is None: + self.PointMapTool = Qad_gripStretch_maptool(self.plugIn) + return self.PointMapTool + else: + return None + + + # ============================================================================ + # addToSelectedEntityGripPoints + # ============================================================================ + def addToSelectedEntityGripPoints(self, entityGripPoints): + # entità con lista dei grip point + i = 0 + gripPoints = entityGripPoints.gripPoints + gripPointsLen = len(gripPoints) + ptList = [] + while i < gripPointsLen: + gripPoint = gripPoints[i] + # grip point selezionato + if gripPoint.getStatus() == qad_grip.QadGripStatusEnum.SELECTED: + if gripPoint.gripType == qad_grip.QadGripPointTypeEnum.CENTER: + ptList.append(gripPoint.getPoint()) + elif gripPoint.gripType == qad_grip.QadGripPointTypeEnum.LINE_MID_POINT: + # aggiungo il vertice precedente e successivo di quello intermedio + if i > 0: + ptList.append(gripPoints[i - 1].getPoint()) + if i < gripPointsLen - 1: + # se il grip successivo è center si tratta dell'ultimo tratto di un poligono + if gripPoints[i + 1].gripType == qad_grip.QadGripPointTypeEnum.CENTER: + ptList.append(gripPoints[0].getPoint()) # prendo il primo punto + else: + ptList.append(gripPoints[i + 1].getPoint()) + elif gripPoint.gripType == qad_grip.QadGripPointTypeEnum.QUA_POINT: + ptList.append(gripPoint.getPoint()) + elif gripPoint.gripType == qad_grip.QadGripPointTypeEnum.VERTEX or \ + gripPoint.gripType == qad_grip.QadGripPointTypeEnum.END_VERTEX: + ptList.append(gripPoint.getPoint()) + elif gripPoint.gripType == qad_grip.QadGripPointTypeEnum.ARC_MID_POINT: + ptList.append(gripPoint.getPoint()) + i = i + 1 + + if len(ptList) > 0: + self.selectedEntityGripPoints.append([entityGripPoints.entity, ptList]) + + + # ============================================================================ + # setSelectedEntityGripPoints + # ============================================================================ + def setSelectedEntityGripPoints(self, entitySetGripPoints): + # lista delle entityGripPoint con dei grip point selezionati + # ritorna una lista in cui ogni elemento è una entità + una lista di punti da stirare + del self.selectedEntityGripPoints[:] # svuoto la lista + + for entityGripPoints in entitySetGripPoints.entityGripPoints: + self.addToSelectedEntityGripPoints(entityGripPoints) + self.getPointMapTool().setSelectedEntityGripPoints(self.selectedEntityGripPoints) + + # input : self.basePt e entitySetGripPoints + # cerco in entitySetGripPoints l'entità che ha un solo grip selezionato corrispondente a basePt + entityGripPoints, entityGripPoint = entitySetGripPoints.isIntersecting(self.basePt) + if entityGripPoint.getStatus() == qad_grip.QadGripStatusEnum.SELECTED and \ + len(entityGripPoints.getSelectedGripPoints()) == 1: + + entity = entityGripPoints.entity + # verifico se l'entità appartiene ad uno stile di quotatura + if QadDimStyles.isDimEntity(entity): + pass + else: + qadGeom = entity.getQadGeom(entityGripPoint.atGeom, entityGripPoint.atSubGeom) + qadGeomType = qadGeom.whatIs() + if qadGeomType == "POLYLINE": + self.getPointMapTool().prevPart, self.getPointMapTool().nextPart = qadGeom.getPrevNextLinearObjectsAtVertex(entityGripPoint.nVertex) + elif qadGeomType == "CIRCLE": + if qadGeom.isPtOnCircle(entityGripPoint.getPoint()): + line = QadLine() + line.set(qadGeom.center, entityGripPoint.getPoint()) + self.getPointMapTool().prevPart = line + elif qadGeomType == "ELLIPSE": + if qadGeom.containsPt(entityGripPoint.getPoint()): + line = QadLine() + line.set(qadGeom.center, entityGripPoint.getPoint()) + self.getPointMapTool().prevPart = line + + + # ============================================================================ + # getSelectedEntityGripPointNdx + # ============================================================================ + def getSelectedEntityGripPointNdx(self, entity): + # lista delle entityGripPoint con dei grip point selezionati + # cerca la posizione di un'entità nella lista in cui ogni elemento è una entità + una lista di punti da stirare + i = 0 + tot = len(self.selectedEntityGripPoints) + while i < tot: + selectedEntityGripPoint = self.selectedEntityGripPoints[i] + if selectedEntityGripPoint[0] == entity: + return i + i = i + 1 + return -1 + + + # ============================================================================ + # stretch + # ============================================================================ + def stretch(self, entity, ptList, offsetX, offsetY, tolerance2ApproxCurve, openForm): + # entity = entità da stirare + # ptList = lista dei punti da stirare + # offsetX, offsetY = spostamento da applicare + # tolerance2ApproxCurve = tolleranza per ricreare le curve + + if entity.whatIs() == "DIMENTITY": + dimEntity = entity + else: + # verifico se l'entità appartiene ad uno stile di quotatura + dimEntity = QadDimStyles.getDimEntity(entity) + + if dimEntity is None: + stretchedGeom = entity.getQadGeom() + # controllo inserito perchè con le quote, questa viene cancellata e ricreata quindi alcuni oggetti potrebbero non esistere più + if stretchedGeom is None: # se non c'è lo salto senza errore + return True + + # stiro la feature + stretchedGeom = qad_stretch_fun.stretchQadGeometry(stretchedGeom, ptList, \ + offsetX, offsetY) + + if stretchedGeom is not None: + # trasformo la geometria QAD in geometria GSIS nel crs del layer + f = entity.getFeature() + f.setGeometry(fromQadGeomToQgsGeom(stretchedGeom, entity.layer)) + if self.copyEntities == False: + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, entity.layer, f, False, False) == False: + return False + else: + # plugIn, layer, features, coordTransform, refresh, check_validity + if qad_layer.addFeatureToLayer(self.plugIn, entity.layer, f, None, False, False, openForm) == False: + return False + + else: + # stiro la quota + if self.copyEntities == False: + if dimEntity.deleteToLayers(self.plugIn) == False: + return False + newDimEntity = QadDimEntity(dimEntity) # la copio + newDimEntity.stretch(ptList, offsetX, offsetY) + if newDimEntity.addToLayers(self.plugIn) == False: + return False + # non so per quale motivo a volte non si aggiorna la mappa quindi forzo l'aggiornamento + self.plugIn.canvas.refresh() + + return True + + + # ============================================================================ + # stretchFeatures + # ============================================================================ + def stretchFeatures(self, newPt): + # mi ricavo un unico QadEntitySet con le entità selezionate + entitySet = QadEntitySet() + for selectedEntity in self.selectedEntityGripPoints: + entitySet.addEntity(selectedEntity[0]) + self.plugIn.beginEditCommand("Feature stretched", entitySet.getLayerList()) + openForm = True if len(self.selectedEntityGripPoints) == 1 else False + + dimElaboratedList = [] # lista delle quotature già elaborate + + for selectedEntity in self.selectedEntityGripPoints: + entity = selectedEntity[0] + ptList = selectedEntity[1] + layer = entity.layer + + tolerance2ApproxCurve = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) + offsetX = newPt.x() - self.basePt.x() + offsetY = newPt.y() - self.basePt.y() + + # verifico se l'entità appartiene ad uno stile di quotatura + dimEntity = QadDimStyles.getDimEntity(entity) + if dimEntity is None: + if self.stretch(entity, ptList, offsetX, offsetY, tolerance2ApproxCurve, openForm) == False: + self.plugIn.destroyEditCommand() + return + else: + found = False + for dimElaborated in dimElaboratedList: + if dimElaborated == dimEntity: + found = True + + if found == False: # quota non ancora elaborata + # aggiungo i layer dei componenti della quota + self.plugIn.addLayerListToLastEditCommand("Feature stretched", + [dimEntity.getSymbolLayer(), dimEntity.getLinearLayer(), dimEntity.getTextualLayer()]) + + dimEntitySet = dimEntity.getEntitySet() + # creo un'unica lista contenente i grip points di tutti i componenti della quota + dimPtlist = [] + for layerEntitySet in dimEntitySet.layerEntitySetList: + for featureId in layerEntitySet.featureIds: + componentDim = QadEntity() + componentDim.set(layerEntitySet.layer, featureId) + i = self.getSelectedEntityGripPointNdx(componentDim) + if i >= 0: + dimPtlist.extend(self.selectedEntityGripPoints[i][1]) + + dimElaboratedList.append(dimEntity) + if self.stretch(dimEntity, dimPtlist, offsetX, offsetY, tolerance2ApproxCurve, False) == False: + self.plugIn.destroyEditCommand() + return + + self.plugIn.endEditCommand() + self.nOperationsToUndo = self.nOperationsToUndo + 1 + + + # ============================================================================ + # waitForStretchPoint + # ============================================================================ + def waitForStretchPoint(self): + self.step = 1 + self.plugIn.setLastPoint(self.basePt) + # imposto il map tool + self.getPointMapTool().basePt = self.basePt + self.getPointMapTool().setMode(Qad_stretch_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_MOVE_PT) + + keyWords = QadMsg.translate("Command_GRIP", "Base point") + "/" + \ + QadMsg.translate("Command_GRIP", "Copy") + "/" + \ + QadMsg.translate("Command_GRIP", "Undo") + "/" + \ + QadMsg.translate("Command_GRIP", "eXit") + + prompt = QadMsg.translate("Command_GRIPSTRETCH", "Specify stretch point or [{0}]: ").format(keyWords) + + englishKeyWords = "Base point" + "/" + "Copy" + "/" + "Undo" + "/" + "eXit" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, QadInputModeEnum.NONE) + + + # ============================================================================ + # waitForBasePt + # ============================================================================ + def waitForBasePt(self): + self.step = 2 + # imposto il map tool + self.getPointMapTool().setMode(Qad_stretch_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_BASE_PT) + + # si appresta ad attendere un punto + self.waitForPoint(QadMsg.translate("Command_GRIPSTRETCH", "Specify base point: ")) + + + # ============================================================================ + # run + # ============================================================================ + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + # ========================================================================= + # RICHIESTA SELEZIONE OGGETTI + if self.step == 0: # inizio del comando + if len(self.selectedEntityGripPoints) == 0: # non ci sono oggetti da stirare + return True + self.showMsg(QadMsg.translate("Command_GRIPSTRETCH", "\n** STRETCH **\n")) + # si appresta ad attendere un punto di stiramento + self.waitForStretchPoint() + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DI UN PUNTO DI STIRAMENTO + elif self.step == 1: + ctrlKey = False + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + value = None + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: + value = self.getPointMapTool().point + + ctrlKey = self.getPointMapTool().ctrlKey + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("Command_GRIP", "Base point") or value == "Base point": + # si appresta ad attendere il punto base + self.waitForBasePt() + elif value == QadMsg.translate("Command_GRIP", "Copy") or value == "Copy": + # Copia entità lasciando inalterate le originali + self.copyEntities = True + # si appresta ad attendere un punto di stiramento + self.waitForStretchPoint() + elif value == QadMsg.translate("Command_GRIP", "Undo") or value == "Undo": + if self.nOperationsToUndo > 0: + self.nOperationsToUndo = self.nOperationsToUndo - 1 + self.plugIn.undoEditCommand() + else: + self.showMsg(QadMsg.translate("QAD", "\nThe command has been canceled.")) + # si appresta ad attendere un punto di stiramento + self.waitForStretchPoint() + elif value == QadMsg.translate("Command_GRIP", "eXit") or value == "eXit": + return True # fine comando + elif type(value) == QgsPointXY: # se é stato selezionato un punto + if ctrlKey: + self.copyEntities = True + + self.stretchFeatures(value) + + if self.copyEntities == False: + return True + # si appresta ad attendere un punto di stiramento + self.waitForStretchPoint() + + else: + if self.copyEntities == False: + self.skipToNextGripCommand = True + return True # fine comando + + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA PUNTO BASE (da step = 1) + elif self.step == 2: # dopo aver atteso un punto + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + pass # opzione di default "spostamento" + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == QgsPointXY: # se é stato inserito il punto base + self.basePt.set(value.x(), value.y()) + # imposto il map tool + self.getPointMapTool().basePt = self.basePt + + # si appresta ad attendere un punto di stiramento + self.waitForStretchPoint() + + return False \ No newline at end of file diff --git a/cmd/qad_stretch_maptool.py b/cmd/qad_stretch_maptool.py new file mode 100644 index 00000000..5104df71 --- /dev/null +++ b/cmd/qad_stretch_maptool.py @@ -0,0 +1,348 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + classe per gestire il map tool in ambito del comando stretch ok + + ------------------- + begin : 2014-01-08 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from .. import qad_utils +from ..qad_variables import QadVariables +from ..qad_getpoint import QadGetPoint, QadGetPointSelectionModeEnum, QadGetPointDrawModeEnum +from ..qad_multi_geom import * +from ..qad_dim import QadDimEntity, QadDimStyles +from ..qad_stretch_fun import stretchQadGeometry +from ..qad_highlight import QadHighlight +from ..qad_msg import QadMsg +from ..qad_entity import QadEntitySet, QadEntity, QadEntityTypeEnum +from ..qad_multi_geom import fromQadGeomToQgsGeom + + +# =============================================================================== +# Qad_stretch_maptool_ModeEnum class. +# =============================================================================== +class Qad_stretch_maptool_ModeEnum(): + # si richiede la selezione del primo punto del rettangolo per selezionare gli oggetti + ASK_FOR_FIRST_PT_RECTANGLE = 1 + # noto niente il primo punto del rettangolo si richiede il secondo punto + FIRST_PT_KNOWN_ASK_FOR_SECOND_PT_RECTANGLE = 2 + # noto niente si richiede il punto base + NONE_KNOWN_ASK_FOR_BASE_PT = 3 + # noto il punto base si richiede il secondo punto per lo spostamento + BASE_PT_KNOWN_ASK_FOR_MOVE_PT = 4 + + +# =============================================================================== +# Qad_stretch_maptool class +# =============================================================================== +class Qad_stretch_maptool(QadGetPoint): + + def __init__(self, plugIn): + QadGetPoint.__init__(self, plugIn) + + self.basePt = None + self.SSGeomList = [] # lista di entità da stirare con geom di selezione + self.__highlight = QadHighlight(self.canvas) + + def hidePointMapToolMarkers(self): + QadGetPoint.hidePointMapToolMarkers(self) + self.__highlight.hide() + + def showPointMapToolMarkers(self): + QadGetPoint.showPointMapToolMarkers(self) + self.__highlight.show() + + def clear(self): + QadGetPoint.clear(self) + self.__highlight.reset() + self.mode = None + + + # ============================================================================ + # stretch + # ============================================================================ + def stretch(self, entity, containerGeom, offsetX, offsetY, tolerance2ApproxCurve): + # entity = entità da stirare + # ptList = lista dei punti da stirare + # offsetX, offsetY = spostamento da applicare + # tolerance2ApproxCurve = tolleranza per ricreare le curve + # verifico se l'entità appartiene ad uno stile di quotatura + if entity.whatIs() == "ENTITY": + stretchedGeom = entity.getQadGeom() + # controllo inserito perchè con le quote, questa viene cancellata e ricreata quindi alcuni oggetti potrebbero non esistere più + if stretchedGeom is None: # se non c'è lo salto senza errore + return True + # stiro la feature + stretchedGeom = stretchQadGeometry(stretchedGeom, containerGeom, \ + offsetX, offsetY) + + if stretchedGeom is not None: + # trasformo la geometria nel crs del layer + self.__highlight.addGeometry(fromQadGeomToQgsGeom(stretchedGeom, entity.layer), entity.layer) + + elif entity.whatIs() == "DIMENTITY": + newDimEntity = QadDimEntity(entity) # la copio + # stiro la quota + newDimEntity.stretch(containerGeom, offsetX, offsetY) + self.__highlight.addGeometry(newDimEntity.textualFeature.geometry(), newDimEntity.getTextualLayer()) + self.__highlight.addGeometries(newDimEntity.getLinearGeometryCollection(), newDimEntity.getLinearLayer()) + self.__highlight.addGeometries(newDimEntity.getSymbolGeometryCollection(), newDimEntity.getSymbolLayer()) + + return True + + + # ============================================================================ + # addStretchedGeometries + # ============================================================================ + def addStretchedGeometries(self, newPt): + self.__highlight.reset() + + dimElaboratedList = [] # lista delle quotature già elaborate + + tolerance2ApproxCurve = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) + offsetX = newPt.x() - self.basePt.x() + offsetY = newPt.y() - self.basePt.y() + + entity = QadEntity() + for SSGeom in self.SSGeomList: + # copio entitySet + entitySet = QadEntitySet(SSGeom[0]) + geomSel = SSGeom[1] + + for layerEntitySet in entitySet.layerEntitySetList: + layer = layerEntitySet.layer + + for featureId in layerEntitySet.featureIds: + entity.set(layer, featureId) + + # verifico se l'entità appartiene ad uno stile di quotatura + dimEntity = QadDimStyles.getDimEntity(entity) + if dimEntity is None: + self.stretch(entity, geomSel, offsetX, offsetY, tolerance2ApproxCurve) + else: + found = False + for dimElaborated in dimElaboratedList: + if dimElaborated == dimEntity: + found = True + + if found == False: # quota non ancora elaborata + dimElaboratedList.append(dimEntity) + self.stretch(dimEntity, geomSel, offsetX, offsetY, tolerance2ApproxCurve) + + + def canvasMoveEvent(self, event): + QadGetPoint.canvasMoveEvent(self, event) + + # noto il punto base si richiede il secondo punto per l'angolo di rotazione + if self.mode == Qad_stretch_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_MOVE_PT: + self.addStretchedGeometries(self.tmpPoint) + + + def activate(self): + QadGetPoint.activate(self) + self.__highlight.show() + + def deactivate(self): + try: # necessario perché se si chiude QGIS parte questo evento nonostante non ci sia più l'oggetto maptool ! + QadGetPoint.deactivate(self) + self.__highlight.hide() + except: + pass + + def setMode(self, mode): + self.mode = mode + + # si richiede la selezione del primo punto del rettangolo per selezionare gli oggetti + if self.mode == Qad_stretch_maptool_ModeEnum.ASK_FOR_FIRST_PT_RECTANGLE: + self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + # noto niente il primo punto del rettangolo si richiede il secondo punto + elif self.mode == Qad_stretch_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT_RECTANGLE: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_RECTANGLE) + # noto niente si richiede il punto base + elif self.mode == Qad_stretch_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_BASE_PT: + self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + # noto il punto base si richiede il secondo punto + elif self.mode == Qad_stretch_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_MOVE_PT: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.basePt) + + +# =============================================================================== +# Qad_gripStretch_maptool class +# =============================================================================== +class Qad_gripStretch_maptool(QadGetPoint): + + def __init__(self, plugIn): + QadGetPoint.__init__(self, plugIn) + + self.basePt = None + self.selectedEntityGripPoints = [] # lista in cui ogni elemento è una entità + una lista di punti da stirare + self.__highlight = QadHighlight(self.canvas) + self.prevPart = None # per input dinamico + self.nextPart = None # per input dinamico + + def hidePointMapToolMarkers(self): + QadGetPoint.hidePointMapToolMarkers(self) + self.__highlight.hide() + + def showPointMapToolMarkers(self): + QadGetPoint.showPointMapToolMarkers(self) + self.__highlight.show() + + def clear(self): + QadGetPoint.clear(self) + self.__highlight.reset() + self.mode = None + + + # ============================================================================ + # setSelectedEntityGripPoints + # ============================================================================ + def setSelectedEntityGripPoints(self, selectedEntityGripPoints): + self.selectedEntityGripPoints = selectedEntityGripPoints + + + # ============================================================================ + # getSelectedEntityGripPointNdx + # ============================================================================ + def getSelectedEntityGripPointNdx(self, entity): + # lista delle entityGripPoint con dei grip point selezionati + # cerca la posizione di un'entità nella lista in cui ogni elemento è una entità + una lista di punti da stirare + i = 0 + tot = len(self.selectedEntityGripPoints) + while i < tot: + selectedEntityGripPoint = self.selectedEntityGripPoints[i] + if selectedEntityGripPoint[0] == entity: + return i + i = i + 1 + return -1 + + + # ============================================================================ + # stretch + # ============================================================================ + def stretch(self, entity, ptList, offsetX, offsetY, tolerance2ApproxCurve): + # entity = entità da stirare + # ptList = lista dei punti da stirare + # offsetX, offsetY = spostamento da applicare + # tolerance2ApproxCurve = tolleranza per ricreare le curve + # entitySet = gruppo di selezione delle entità da stirare + # verifico se l'entità appartiene ad uno stile di quotatura + if entity.whatIs() == "ENTITY": + stretchedGeom = stretchQadGeometry(entity.getQadGeom(), ptList, offsetX, offsetY) + + if stretchedGeom is not None: + # trasformo la geometria nel crs del layer + self.__highlight.addGeometry(fromQadGeomToQgsGeom(stretchedGeom, entity.layer), entity.layer) + return stretchedGeom + elif entity.whatIs() == "DIMENTITY": + # stiro la quota + entity.stretch(ptList, offsetX, offsetY) + self.__highlight.addGeometry(entity.textualFeature.geometry(), entity.getTextualLayer()) + self.__highlight.addGeometries(entity.getLinearGeometryCollection(), entity.getLinearLayer()) + self.__highlight.addGeometries(entity.getSymbolGeometryCollection(), entity.getSymbolLayer()) + + + # ============================================================================ + # addStretchedGeometries + # ============================================================================ + def addStretchedGeometries(self, newPt): + self.__highlight.reset() + + dimElaboratedList = [] # lista delle quotature già elaborate + iEnt = 0 + for selectedEntity in self.selectedEntityGripPoints: + entity = selectedEntity[0] + ptList = selectedEntity[1] + layer = entity.layer + + tolerance2ApproxCurve = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) + offsetX = newPt.x() - self.basePt.x() + offsetY = newPt.y() - self.basePt.y() + + # verifico se l'entità appartiene ad uno stile di quotatura + dimEntity = QadDimStyles.getDimEntity(entity) + if dimEntity is None: + stretchedGeom = self.stretch(entity, ptList, offsetX, offsetY, tolerance2ApproxCurve) + else: + found = False + for dimElaborated in dimElaboratedList: + if dimElaborated == dimEntity: + found = True + if found == False: # quota non ancora elaborata + dimEntitySet = dimEntity.getEntitySet() + # creo un'unica lista contenente i grip points di tutti i componenti della quota + dimPtlist = [] + for layerEntitySet in dimEntitySet.layerEntitySetList: + for featureId in layerEntitySet.featureIds: + componentDim = QadEntity() + componentDim.set(layerEntitySet.layer, featureId) + i = self.getSelectedEntityGripPointNdx(componentDim) + if i >= 0: + dimPtlist.extend(self.selectedEntityGripPoints[i][1]) + + dimElaboratedList.append(dimEntity) + self.stretch(dimEntity, dimPtlist, offsetX, offsetY, tolerance2ApproxCurve) + iEnt = iEnt + 1 + + + def canvasMoveEvent(self, event): + QadGetPoint.canvasMoveEvent(self, event) + + # noto il punto base si richiede il secondo punto per l'angolo di rotazione + if self.mode == Qad_stretch_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_MOVE_PT: + self.addStretchedGeometries(self.tmpPoint) + + + def activate(self): + QadGetPoint.activate(self) + self.__highlight.show() + + def deactivate(self): + try: # necessario perché se si chiude QGIS parte questo evento nonostante non ci sia più l'oggetto maptool ! + QadGetPoint.deactivate(self) + self.__highlight.hide() + except: + pass + + def setMode(self, mode): + self.mode = mode + + # noto niente si richiede il punto base + if self.mode == Qad_stretch_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_BASE_PT: + self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) + self.setDrawMode(QadGetPointDrawModeEnum.NONE) + self.__highlight.reset() + self.prevPart = None + self.nextPart = None + self.getDynamicInput().setPrevPoint(None) + self.getDynamicInput().setPrevPart(self.prevPart) + self.getDynamicInput().setNextPart(self.nextPart) + + # noto il punto base si richiede il secondo punto + elif self.mode == Qad_stretch_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_MOVE_PT: + self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) + self.setStartPoint(self.basePt) + self.getDynamicInput().setPrevPart(self.prevPart) + self.getDynamicInput().setNextPart(self.nextPart) + if self.prevPart is not None or self.nextPart is not None: + self.getDynamicInput().setPrevPoint(None) diff --git a/qad_text_cmd.py b/cmd/qad_text_cmd.py similarity index 80% rename from qad_text_cmd.py rename to cmd/qad_text_cmd.py index 80d68207..95bbdb9d 100644 --- a/qad_text_cmd.py +++ b/cmd/qad_text_cmd.py @@ -1,307 +1,317 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - comando TEXT per inserire un'etichetta - - ------------------- - begin : 2013-12-31 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -import qad_utils -from qad_generic_cmd import QadCommandClass -import qad_layer -import qad_label -from qad_getpoint import * -from qad_getdist_cmd import QadGetDistClass -from qad_getangle_cmd import QadGetAngleClass -from qad_textwindow import * -from qad_msg import QadMsg - - -# Classe che gestisce il comando TEXT -class QadTEXTCommandClass(QadCommandClass): - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadTEXTCommandClass(self.plugIn) - - def getName(self): - return QadMsg.translate("Command_list", "TEXT") - - def getEnglishName(self): - return "TEXT" - - def connectQAction(self, action): - QObject.connect(action, SIGNAL("triggered()"), self.plugIn.runTEXTCommand) - - def getIcon(self): - return QIcon(":/plugins/qad/icons/text.png") - - def getNote(self): - # impostare le note esplicative del comando - return QadMsg.translate("Command_TEXT", "Inserts a text.") - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - self.insPt = None - self.hText = self.plugIn.lastHText - self.rot = self.plugIn.lastRot - self.GetDistClass = None - self.GetAngleClass = None - self.labelFields = None - self.labelFieldNamesNdx = 0 - self.labelFieldValues = [] - - def __del__(self): - QadCommandClass.__del__(self) - if self.GetDistClass is not None: - del self.GetDistClass - if self.GetAngleClass is not None: - del self.GetAngleClass - - def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): - # quando si éin fase di richiesta distanza (altezza testo) - if self.step == 2: - return self.GetDistClass.getPointMapTool() - # quando si éin fase di richiesta rotazione - elif self.step == 3: - return self.GetAngleClass.getPointMapTool() - else: - return QadCommandClass.getPointMapTool(self, drawMode) - - def addFeature(self, layer): - transformedPoint = self.mapToLayerCoordinates(layer, self.insPt) - g = QgsGeometry.fromPoint(transformedPoint) - f = QgsFeature() - f.setGeometry(g) - # Add attribute fields to feature. - fields = layer.pendingFields() - f.setFields(fields) - - # assegno i valori di default - provider = layer.dataProvider() - for field in fields.toList(): - i = fields.indexFromName(field.name()) - f[field.name()] = provider.defaultValue(i) - - # se l'altezza testo dipende da un solo campo - sizeFldNames = qad_label.get_labelSizeFieldNames(layer) - if len(sizeFldNames) == 1 and len(sizeFldNames[0]) > 0: - f.setAttribute(sizeFldNames[0], self.hText) - - # se la rotazione dipende da un solo campo - rotFldNames = qad_label.get_labelRotationFieldNames(layer) - if len(rotFldNames) == 1 and len(rotFldNames[0]) > 0: - f.setAttribute(rotFldNames[0], qad_utils.toDegrees(self.rot)) - - # setto i valori degli attributi che compongono l'etichetta - i = 0 - tot = len(self.labelFields) - while i < tot: - f.setAttribute(self.labelFields[i].name(), self.labelFieldValues[i]) - i = i + 1 - - return qad_layer.addFeatureToLayer(self.plugIn, layer, f) - - def initLabelFields(self, layer): - labelFieldNames = qad_label.get_labelFieldNames(layer) - if len(labelFieldNames) > 0: - self.labelFields = QgsFields() - for field in layer.dataProvider().fields(): - if field.name() in labelFieldNames: - self.labelFields.append(QgsField(field.name(), field.type())) - - #============================================================================ - # waitForFieldValue - #============================================================================ - def waitForFieldValue(self): - self.step = 4 - - if self.labelFields is None: - return False - if self.labelFieldNamesNdx >= len(self.labelFields): - return False - field = self.labelFields[self.labelFieldNamesNdx] - prompt = QadMsg.translate("Command_TEXT", "Enter the value of attribute \"{0}\": ").format(field.name()) - if field.type() == QVariant.Double: # si appresta ad attendere un double o valore nullo - self.waitForFloat(prompt, None, QadInputModeEnum.NONE) - elif field.type() == QVariant.LongLong: # si appresta ad attendere un long a 64 bit o valore nullo - self.waitForlong(prompt, None, QadInputModeEnum.NONE) - elif field.type() == QVariant.Int: # si appresta ad attendere un integer o valore nullo - self.waitForInt(prompt, None, QadInputModeEnum.NONE) - elif field.type() == QVariant.Bool: # si appresta ad attendere un boolean o valore nullo - self.waitForBool(prompt, None, QadInputModeEnum.NONE) - else: # si appresta ad attendere una stringa o valore nullo - self.waitForString(prompt, None, QadInputModeEnum.NONE) - - return True - - - def run(self, msgMapTool = False, msg = None): - if self.plugIn.canvas.mapRenderer().destinationCrs().geographicFlag(): - self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) - return True # fine comando - - currLayer, errMsg = qad_layer.getCurrLayerEditable(self.plugIn.canvas, QGis.Point) - if currLayer is None: - self.showErr(errMsg) - return True # fine comando - - if qad_layer.isTextLayer(currLayer) == False: - errMsg = QadMsg.translate("QAD", "\nCurrent layer is not a textual layer.") - errMsg = errMsg + QadMsg.translate("QAD", "\nA textual layer is a vectorial punctual layer having a label and the symbol transparency no more than 10%.\n") - self.showErr(errMsg) - return True # fine comando - - - #========================================================================= - # RICHIESTA PUNTO DI INSERIMENTO - if self.step == 0: # inizio del comando - self.waitForPoint() # si appresta ad attendere un punto - self.step = self.step + 1 - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA PUNTO DI INSERIMENTO - elif self.step == 1: # dopo aver atteso un punto si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # éstato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool éstato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - pt = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - pt = msg - - self.insPt = QgsPoint(pt) - self.plugIn.setLastPoint(self.insPt) - - # se l'altezza testo dipende da un solo campo - sizeFldNames = qad_label.get_labelSizeFieldNames(currLayer) - if len(sizeFldNames) == 1 and len(sizeFldNames[0]) > 0: - # si appresta ad attendere la scala - self.GetDistClass = QadGetDistClass(self.plugIn) - prompt = QadMsg.translate("Command_TEXT", "Specify the text height <{0}>: ") - self.GetDistClass.msg = prompt.format(str(self.hText)) - self.GetDistClass.dist = self.hText - self.GetDistClass.inputMode = QadInputModeEnum.NOT_NEGATIVE | QadInputModeEnum.NOT_ZERO - self.GetDistClass.startPt = self.insPt - self.step = 2 - self.GetDistClass.run(msgMapTool, msg) - return False - else: - # se la rotazione dipende da un solo campo - rotFldNames = qad_label.get_labelRotationFieldNames(currLayer) - if len(rotFldNames) == 1 and len(rotFldNames[0]) > 0: - if self.GetAngleClass is not None: - del self.GetAngleClass - # si appresta ad attendere l'angolo di rotazione - self.GetAngleClass = QadGetAngleClass(self.plugIn) - prompt = QadMsg.translate("Command_TEXT", "Specify the text rotation <{0}>: ") - self.GetAngleClass.msg = prompt.format(str(qad_utils.toDegrees(self.rot))) - self.GetAngleClass.angle = self.rot - self.GetAngleClass.startPt = self.insPt - self.step = 3 - self.GetAngleClass.run(msgMapTool, msg) - return False - else: - self.initLabelFields(currLayer) - if self.waitForFieldValue() == False: - self.addFeature(currLayer) - return True - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA ALTEZZA TESTO (da step = 1) - elif self.step == 2: - if self.GetDistClass.run(msgMapTool, msg) == True: - if self.GetDistClass.dist is not None: - self.hText = self.GetDistClass.dist - self.plugIn.setLastHText(self.hText) - del self.GetDistClass - self.GetDistClass = None - - # se la rotazione dipende da un solo campo - rotFldNames = qad_label.get_labelRotationFieldNames(currLayer) - if len(rotFldNames) == 1 and len(rotFldNames[0]) > 0: - if self.GetAngleClass is not None: - del self.GetAngleClass - # si appresta ad attendere l'angolo di rotazione - self.GetAngleClass = QadGetAngleClass(self.plugIn) - prompt = QadMsg.translate("Command_TEXT", "Specify the text rotation <{0}>: ") - self.GetAngleClass.msg = prompt.format(str(qad_utils.toDegrees(self.rot))) - self.GetAngleClass.angle = self.rot - self.GetAngleClass.startPt = self.insPt - self.step = 3 - self.GetAngleClass.run(msgMapTool, msg) - return False - else: - self.initLabelFields(currLayer) - if self.waitForFieldValue() == False: - self.addFeature(currLayer) - return True - else: - return True - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA ROTAZIONE (da step = 1 o 2) - elif self.step == 3: - if self.GetAngleClass.run(msgMapTool, msg) == True: - if self.GetAngleClass.angle is not None: - self.rot = self.GetAngleClass.angle - self.plugIn.setLastRot(self.rot) - self.initLabelFields(currLayer) - if self.waitForFieldValue() == False: - self.addFeature(currLayer) - return True # fine comando - else: - return True - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DEL VALORE DI UN CAMPO - elif self.step == 4: # dopo aver atteso un valore si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - self.waitForFieldValue() - return False - # il valore arriva come parametro della funzione - self.labelFieldValues.append(msg) - self.labelFieldNamesNdx = self.labelFieldNamesNdx + 1 - if self.waitForFieldValue() == False: - self.addFeature(currLayer) - return True # fine comando - - return False - +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin ok + + comando TEXT per inserire un'etichetta + + ------------------- + begin : 2013-12-31 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.core import QgsGeometry, QgsFeature, QgsFields, QgsField, QgsWkbTypes, QgsPointXY, QgsVectorLayerUtils +from qgis.PyQt.QtCore import QVariant +from qgis.PyQt.QtGui import QIcon + + +from .. import qad_utils +from .qad_generic_cmd import QadCommandClass +from .. import qad_layer +from .. import qad_label +from ..qad_getpoint import QadGetPointDrawModeEnum +from .qad_getdist_cmd import QadGetDistClass +from .qad_getangle_cmd import QadGetAngleClass +from ..qad_dim import QadDimStyles +from ..qad_textwindow import QadInputModeEnum +from ..qad_msg import QadMsg +from ..qad_point import QadPoint + + +# Classe che gestisce il comando TEXT +class QadTEXTCommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadTEXTCommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "TEXT") + + def getEnglishName(self): + return "TEXT" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runTEXTCommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/text.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_TEXT", "Inserts a text.") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + self.insPt = None + self.hText = self.plugIn.lastHText + self.rot = self.plugIn.lastRot + self.GetDistClass = None + self.GetAngleClass = None + self.labelFields = None + self.labelFieldNamesNdx = 0 + self.labelFieldValues = [] + + def __del__(self): + QadCommandClass.__del__(self) + if self.GetDistClass is not None: + del self.GetDistClass + if self.GetAngleClass is not None: + del self.GetAngleClass + + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + # quando si éin fase di richiesta distanza (altezza testo) + if self.step == 2: + return self.GetDistClass.getPointMapTool() + # quando si éin fase di richiesta rotazione + elif self.step == 3: + return self.GetAngleClass.getPointMapTool() + else: + return QadCommandClass.getPointMapTool(self, drawMode) + + + def getCurrentContextualMenu(self): + # quando si éin fase di richiesta distanza (altezza testo) + if self.step == 2: + return self.GetDistClass.getCurrentContextualMenu() + # quando si éin fase di richiesta rotazione + elif self.step == 3: + return self.GetAngleClass.getCurrentContextualMenu() + else: + return self.contextualMenu + + + def addFeature(self, layer): + pt = QadPoint(self.insPt) + g = self.mapToLayerCoordinates(layer, pt.asGeom(layer.wkbType())) + f = QgsVectorLayerUtils.createFeature(layer, g, {}, layer.createExpressionContext()) + + # se l'altezza testo dipende da un solo campo + sizeFldNames = qad_label.get_labelSizeFieldNames(layer) + if len(sizeFldNames) == 1 and len(sizeFldNames[0]) > 0: + f.setAttribute(sizeFldNames[0], self.hText) + + # se la rotazione dipende da un solo campo + rotFldNames = qad_label.get_labelRotationFieldNames(layer) + if len(rotFldNames) == 1 and len(rotFldNames[0]) > 0: + f.setAttribute(rotFldNames[0], qad_utils.toDegrees(self.rot)) + + # setto i valori degli attributi che compongono l'etichetta + i = 0 + tot = len(self.labelFields) + while i < tot: + f.setAttribute(self.labelFields[i].name(), self.labelFieldValues[i]) + i = i + 1 + + return qad_layer.addFeatureToLayer(self.plugIn, layer, f, None, True, False, False) + + def initLabelFields(self, layer): + labelFieldNames = qad_label.get_labelFieldNames(layer) + if len(labelFieldNames) > 0: + self.labelFields = QgsFields() + for field in layer.dataProvider().fields(): + if field.name() in labelFieldNames: + self.labelFields.append(QgsField(field.name(), field.type())) + + # ============================================================================ + # waitForFieldValue + # ============================================================================ + def waitForFieldValue(self): + self.step = 4 + + if self.labelFields is None: + return False + if self.labelFieldNamesNdx >= len(self.labelFields): + return False + field = self.labelFields[self.labelFieldNamesNdx] + prompt = QadMsg.translate("Command_TEXT", "Enter the value of attribute \"{0}\": ").format(field.name()) + if field.type() == QVariant.Double: # si appresta ad attendere un double o valore nullo + self.waitForFloat(prompt, None, QadInputModeEnum.NONE) + elif field.type() == QVariant.LongLong: # si appresta ad attendere un long a 64 bit o valore nullo + self.waitForLong(prompt, None, QadInputModeEnum.NONE) + elif field.type() == QVariant.Int: # si appresta ad attendere un integer o valore nullo + self.waitForInt(prompt, None, QadInputModeEnum.NONE) + elif field.type() == QVariant.Bool: # si appresta ad attendere un boolean o valore nullo + self.waitForBool(prompt, None, QadInputModeEnum.NONE) + else: # si appresta ad attendere una stringa o valore nullo + self.waitForString(prompt, None, QadInputModeEnum.NONE) + + return True + + + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + currLayer, errMsg = qad_layer.getCurrLayerEditable(self.plugIn.canvas, QgsWkbTypes.PointGeometry) + if currLayer is None: + self.showErr(errMsg) + return True # fine comando + + if qad_layer.isTextLayer(currLayer) == False: + errMsg = QadMsg.translate("QAD", "\nCurrent layer is not a textual layer.") + errMsg = errMsg + QadMsg.translate("QAD", "\nA textual layer is a vector punctual layer having a label and the symbol transparency no more than 10%.\n") + self.showErr(errMsg) + return True # fine comando + + if len(QadDimStyles.getDimListByLayer(currLayer)) > 0: + errMsg = QadMsg.translate("QAD", "\nThe current layer belongs to a dimension style.\n") + self.showErr(errMsg) + return True # fine comando + + + # ========================================================================= + # RICHIESTA PUNTO DI INSERIMENTO + if self.step == 0: # inizio del comando + self.waitForPoint() # si appresta ad attendere un punto + self.step = self.step + 1 + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA PUNTO DI INSERIMENTO + elif self.step == 1: # dopo aver atteso un punto si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # éstato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool éstato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + pt = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + pt = msg + + self.insPt = QgsPointXY(pt) + self.plugIn.setLastPoint(self.insPt) + + # se l'altezza testo dipende da un solo campo + sizeFldNames = qad_label.get_labelSizeFieldNames(currLayer) + if len(sizeFldNames) == 1 and len(sizeFldNames[0]) > 0: + # si appresta ad attendere la scala + self.GetDistClass = QadGetDistClass(self.plugIn) + prompt = QadMsg.translate("Command_TEXT", "Specify the text height <{0}>: ") + self.GetDistClass.msg = prompt.format(str(self.hText)) + self.GetDistClass.dist = self.hText + self.GetDistClass.inputMode = QadInputModeEnum.NOT_NEGATIVE | QadInputModeEnum.NOT_ZERO + self.GetDistClass.startPt = self.insPt + self.step = 2 + self.GetDistClass.run(msgMapTool, msg) + return False + else: + # se la rotazione dipende da un solo campo + rotFldNames = qad_label.get_labelRotationFieldNames(currLayer) + if len(rotFldNames) == 1 and len(rotFldNames[0]) > 0: + if self.GetAngleClass is not None: + del self.GetAngleClass + # si appresta ad attendere l'angolo di rotazione + self.GetAngleClass = QadGetAngleClass(self.plugIn) + prompt = QadMsg.translate("Command_TEXT", "Specify the text rotation <{0}>: ") + self.GetAngleClass.msg = prompt.format(str(qad_utils.toDegrees(self.rot))) + self.GetAngleClass.angle = self.rot + self.GetAngleClass.startPt = self.insPt + self.step = 3 + self.GetAngleClass.run(msgMapTool, msg) + return False + else: + self.initLabelFields(currLayer) + if self.waitForFieldValue() == False: + self.addFeature(currLayer) + return True + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA ALTEZZA TESTO (da step = 1) + elif self.step == 2: + if self.GetDistClass.run(msgMapTool, msg) == True: + if self.GetDistClass.dist is not None: + self.hText = self.GetDistClass.dist + self.plugIn.setLastHText(self.hText) + del self.GetDistClass + self.GetDistClass = None + + # se la rotazione dipende da un solo campo + rotFldNames = qad_label.get_labelRotationFieldNames(currLayer) + if len(rotFldNames) == 1 and len(rotFldNames[0]) > 0: + if self.GetAngleClass is not None: + del self.GetAngleClass + # si appresta ad attendere l'angolo di rotazione + self.GetAngleClass = QadGetAngleClass(self.plugIn) + prompt = QadMsg.translate("Command_TEXT", "Specify the text rotation <{0}>: ") + self.GetAngleClass.msg = prompt.format(str(qad_utils.toDegrees(self.rot))) + self.GetAngleClass.angle = self.rot + self.GetAngleClass.startPt = self.insPt + self.step = 3 + self.GetAngleClass.run(msgMapTool, msg) + return False + else: + self.initLabelFields(currLayer) + if self.waitForFieldValue() == False: + self.addFeature(currLayer) + return True + else: + return True + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA ROTAZIONE (da step = 1 o 2) + elif self.step == 3: + if self.GetAngleClass.run(msgMapTool, msg) == True: + if self.GetAngleClass.angle is not None: + self.rot = self.GetAngleClass.angle + self.plugIn.setLastRot(self.rot) + self.initLabelFields(currLayer) + if self.waitForFieldValue() == False: + self.addFeature(currLayer) + return True # fine comando + else: + return True + return False + + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DEL VALORE DI UN CAMPO + elif self.step == 4: # dopo aver atteso un valore si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + self.waitForFieldValue() + return False + # il valore arriva come parametro della funzione + self.labelFieldValues.append(msg) + self.labelFieldNamesNdx = self.labelFieldNamesNdx + 1 + if self.waitForFieldValue() == False: + self.addFeature(currLayer) + return True # fine comando + + return False + diff --git a/cmd/qad_trim_cmd.py b/cmd/qad_trim_cmd.py new file mode 100644 index 00000000..8f740eea --- /dev/null +++ b/cmd/qad_trim_cmd.py @@ -0,0 +1,466 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + comando TRIM per tagliare o estendere oggetti grafici ok + + ------------------- + begin : 2013-07-15 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.core import QgsWkbTypes, QgsFeature, QgsPointXY, QgsGeometry +from qgis.PyQt.QtGui import QIcon + + +from ..qad_point import QadPoint +from ..qad_getpoint import QadGetPointDrawModeEnum, QadGetPointSelectionModeEnum +from ..qad_textwindow import QadInputTypeEnum, QadInputModeEnum +from .qad_pline_cmd import QadPLINECommandClass +from .qad_rectangle_cmd import QadRECTANGLECommandClass +from .qad_generic_cmd import QadCommandClass +from ..qad_msg import QadMsg +from .. import qad_utils +from .. import qad_layer +from .qad_ssget_cmd import QadSSGetClass +from ..qad_dim import QadDimStyles +from ..qad_extend_trim_fun import extendQadGeometry, trimQadGeometry +from ..qad_entity import QadEntitySet, getSelSet, QadLayerEntitySetIterator +from ..qad_variables import QadVariables +from ..qad_multi_geom import fromQadGeomToQgsGeom, setQadGeomAt +from ..qad_geom_relations import getQadGeomClosestPart, QadIntersections + + +# Classe che gestisce il comando TRIM +class QadTRIMCommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadTRIMCommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "TRIM") + + def getEnglishName(self): + return "TRIM" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runTRIMCommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/trim.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_TRIM", "Trims (or extends) objects to meet the edges of other objects.") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + self.SSGetClass = QadSSGetClass(plugIn) + self.PLINECommand = None + self.RECTANGLECommand = None + self.entitySet = QadEntitySet() # entità da tagliare o estendere + self.limitEntitySet = QadEntitySet() # entità che fanno da limiti + self.edgeMode = QadVariables.get(QadMsg.translate("Environment variables", "EDGEMODE")) + self.defaultValue = None # usato per gestire il tasto dx del mouse + self.nOperationsToUndo = 0 + + def __del__(self): + QadCommandClass.__del__(self) + + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if self.step == 3: # quando si é in fase di disegno linea + return self.PLINECommand.getPointMapTool(drawMode) + elif self.step == 4: # quando si é in fase di disegno rettangolo + return self.RECTANGLECommand.getPointMapTool(drawMode) + else: + return QadCommandClass.getPointMapTool(self, drawMode) + + + def getCurrentContextualMenu(self): + if self.step == 3: # quando si é in fase di disegno linea + return self.PLINECommand.getCurrentContextualMenu() + elif self.step == 4: # quando si é in fase di disegno rettangolo + return self.RECTANGLECommand.getCurrentContextualMenu() + else: + return self.contextualMenu + + + # ============================================================================ + # trimFeatures + # ============================================================================ + def trimFeatures(self, geom, toExtend): + # geom è in map coordinates + LineTempLayer = None + self.plugIn.beginEditCommand("Feature extended" if toExtend else "Feature trimmed", \ + self.entitySet.getLayerList()) + + for limitLayerEntitySet in self.entitySet.layerEntitySetList: + layer = limitLayerEntitySet.layer + + entityIterator = QadLayerEntitySetIterator(limitLayerEntitySet) + for entity in entityIterator: + # per ciascuna entità del layer + f = entity.getFeature() + if f is None: + continue + + qadGeom = entity.getQadGeom() + if geom.whatIs() == "POINT": + # la funzione ritorna una lista con + # ( + # + # + # + # + # <"a sinistra di" se il punto é alla sinista della parte con i seguenti valori: + # - < 0 = sinistra (per linea, arco o arco di ellisse) o interno (per cerchi, ellissi) + # - > 0 = destra (per linea, arco o arco di ellisse) o esterno (per cerchi, ellissi) + # ) + result = getQadGeomClosestPart(qadGeom, geom) + intPts = [result[1]] + else: + intPts = QadIntersections.twoGeomObjects(qadGeom, geom) + + for intPt in intPts: + if toExtend: + newGeom = extendQadGeometry(qadGeom, intPt, \ + self.limitEntitySet, self.edgeMode) + if newGeom is not None: + # aggiorno la feature con la geometria estesa + extendedFeature = QgsFeature(f) + # trasformo la geometria nel crs del layer + extendedFeature.setGeometry(fromQadGeomToQgsGeom(newGeom, layer)) + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, layer, extendedFeature, False, False) == False: + self.plugIn.destroyEditCommand() + return + else: # trim + result = trimQadGeometry(qadGeom, intPt, \ + self.limitEntitySet, self.edgeMode) + if result is not None: + line1 = result[0] + line2 = result[1] + atGeom = result[2] + atSubGeom = result[3] + if layer.geometryType() == QgsWkbTypes.LineGeometry: + newQadGeom = setQadGeomAt(qadGeom, line1, atGeom, atSubGeom) + if newQadGeom is None: + self.plugIn.destroyEditCommand() + return + + trimmedFeature1 = QgsFeature(f) + # trasformo la geometria nel crs del layer + trimmedFeature1.setGeometry(fromQadGeomToQgsGeom(newQadGeom, layer)) + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, layer, trimmedFeature1, False, False) == False: + self.plugIn.destroyEditCommand() + return + if line2 is not None: + trimmedFeature2 = QgsFeature(f) + # trasformo la geometria nel crs del layer + trimmedFeature2.setGeometry(fromQadGeomToQgsGeom(line2, layer)) + # plugIn, layer, feature, coordTransform, refresh, check_validity + if qad_layer.addFeatureToLayer(self.plugIn, layer, trimmedFeature2, None, False, False, False) == False: + self.plugIn.destroyEditCommand() + return + + else: + # aggiungo le linee nei layer temporanei di QAD + if LineTempLayer is None: + LineTempLayer = qad_layer.createQADTempLayer(self.plugIn, QgsWkbTypes.LineGeometry) + self.plugIn.addLayerToLastEditCommand("Feature trimmed", LineTempLayer) + + lineGeoms = [line1] + if line2 is not None: + lineGeoms.append(line2) + + # trasformo la geometria in quella dei layer temporanei + # plugIn, pointGeoms, lineGeoms, polygonGeoms, coord, refresh + if qad_layer.addGeometriesToQADTempLayers(self.plugIn, None, lineGeoms, None, None, False) == False: + self.plugIn.destroyEditCommand() + return + + if delQadGeomAt(qadGeom, atGeom, atSubGeom) == False or updGeom.isEmpty(): # da cancellare + # plugIn, layer, feature id, refresh + if qad_layer.deleteFeatureToLayer(self.plugIn, layer, f.id(), False) == False: + self.plugIn.destroyEditCommand() + return + else: + trimmedFeature1 = QgsFeature(f) + # trasformo la geometria nel crs del layer + trimmedFeature1.setGeometry(fromQadGeomToQgsGeom(qadGeom, layer)) + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(self.plugIn, layer, trimmedFeature1, False, False) == False: + self.plugIn.destroyEditCommand() + return + + self.plugIn.endEditCommand() + self.nOperationsToUndo = self.nOperationsToUndo + 1 + + + # ============================================================================ + # waitForObjectSel + # ============================================================================ + def waitForObjectSel(self): + self.step = 2 + # imposto il map tool + self.getPointMapTool().setSelectionMode(QadGetPointSelectionModeEnum.ENTITY_SELECTION_DYNAMIC) + # solo layer lineari editabili che non appartengano a quote + layerList = [] + for layer in qad_utils.getVisibleVectorLayers(self.plugIn.canvas): # Tutti i layer vettoriali visibili + if layer.geometryType() == QgsWkbTypes.LineGeometry and layer.isEditable(): + if len(QadDimStyles.getDimListByLayer(layer)) == 0: + layerList.append(layer) + + self.getPointMapTool().layersToCheck = layerList + self.getPointMapTool().setDrawMode(QadGetPointDrawModeEnum.NONE) + self.getPointMapTool().onlyEditableLayers = True + + keyWords = QadMsg.translate("Command_TRIM", "Fence") + "/" + \ + QadMsg.translate("Command_TRIM", "Crossing") + "/" + \ + QadMsg.translate("Command_TRIM", "Edge") + "/" + \ + QadMsg.translate("Command_TRIM", "Undo") + prompt = QadMsg.translate("Command_TRIM", "Select the object to trim or shift-select to extend or [{0}]: ").format(keyWords) + + englishKeyWords = "Fence" + "/" + "Crossing" + "/" + "Edge" + "/" + "Undo" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un punto o enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ + None, \ + keyWords, QadInputModeEnum.NONE) + + + # ============================================================================ + # run + # ============================================================================ + def run(self, msgMapTool = False, msg = None): + if self.plugIn.canvas.mapSettings().destinationCrs().isGeographic(): + self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) + return True # fine comando + + # ========================================================================= + # RICHIESTA SELEZIONE OGGETTI LIMITI + if self.step == 0: # inizio del comando + CurrSettingsMsg = QadMsg.translate("QAD", "\nCurrent settings: ") + if self.edgeMode == 0: # 0 = nessuna estensione + CurrSettingsMsg = CurrSettingsMsg + QadMsg.translate("Command_TRIM", "Edge = No extend") + else: + CurrSettingsMsg = CurrSettingsMsg + QadMsg.translate("Command_TRIM", "Edge = Extend") + + self.showMsg(CurrSettingsMsg) + self.showMsg(QadMsg.translate("Command_TRIM", "\nSelect trim limits...")) + + if self.SSGetClass.run(msgMapTool, msg) == True: + # selezione terminata + self.step = 1 + return self.run(msgMapTool, msg) + + # ========================================================================= + # RISPOSTA ALLA SELEZIONE OGGETTI LIMITI + elif self.step == 1: + self.limitEntitySet.set(self.SSGetClass.entitySet) + + if self.limitEntitySet.count() == 0: + return True # fine comando + + # si appresta ad attendere la selezione degli oggetti da estendere/tagliare + self.waitForObjectSel() + return False + + # ========================================================================= + # RISPOSTA ALLA SELEZIONE OGGETTI DA ESTENDERE + elif self.step == 2: + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("Command_TRIM", "Fence") or value == "Fence": + # Seleziona tutti gli oggetti che intersecano una polilinea + self.PLINECommand = QadPLINECommandClass(self.plugIn) + # se questo flag = True il comando serve all'interno di un altro comando per disegnare una linea + # che non verrà salvata su un layer + self.PLINECommand.virtualCmd = True + self.PLINECommand.run(msgMapTool, msg) + self.step = 3 + return False + elif value == QadMsg.translate("Command_TRIM", "Crossing") or value == "Crossing": + # Seleziona tutti gli oggetti che intersecano un rettangolo + self.RECTANGLECommand = QadRECTANGLECommandClass(self.plugIn) + # se questo flag = True il comando serve all'interno di un altro comando per disegnare una linea + # che non verrà salvata su un layer + self.RECTANGLECommand.virtualCmd = True + self.RECTANGLECommand.run(msgMapTool, msg) + self.step = 4 + return False + elif value == QadMsg.translate("Command_TRIM", "Edge") or value == "Edge": + # Per estendere un oggetto usando anche le estensioni degli oggetti di riferimento + # vedi variabile EDGEMODE + keyWords = QadMsg.translate("Command_TRIM", "Extend") + "/" + \ + QadMsg.translate("Command_TRIM", "No extend") + if self.edgeMode == 0: # 0 = nessuna estensione + self.defaultValue = QadMsg.translate("Command_TRIM", "No extend") + else: + self.defaultValue = QadMsg.translate("Command_TRIM", "Extend") + prompt = QadMsg.translate("Command_TRIM", "Specify an extension mode [{0}] <{1}>: ").format(keyWords, self.defaultValue) + + englishKeyWords = "Extend" + "/" + "No extend" + keyWords += "_" + englishKeyWords + # si appresta ad attendere enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.KEYWORDS, \ + self.defaultValue, \ + keyWords, QadInputModeEnum.NONE) + self.step = 5 + return False + elif value == QadMsg.translate("Command_TRIM", "Undo") or value == "Undo": + if self.nOperationsToUndo > 0: + self.nOperationsToUndo = self.nOperationsToUndo - 1 + self.plugIn.undoEditCommand() + else: + self.showMsg(QadMsg.translate("QAD", "\nThe command has been canceled.")) + elif type(value) == QgsPointXY: # se é stato selezionato un punto + self.entitySet.clear() + if self.getPointMapTool().entity.isInitialized(): + self.entitySet.addEntity(self.getPointMapTool().entity) + ToExtend = True if self.getPointMapTool().shiftKey == True else False + self.trimFeatures(QadPoint().set(value), ToExtend) + else: + # cerco se ci sono entità nel punto indicato considerando + # solo layer lineari editabili che non appartengano a quote + layerList = [] + for layer in qad_utils.getVisibleVectorLayers(self.plugIn.canvas): # Tutti i layer vettoriali visibili + if layer.geometryType() == QgsWkbTypes.LineGeometry and layer.isEditable(): + if len(QadDimStyles.getDimListByLayer(layer)) == 0: + layerList.append(layer) + + result = qad_utils.getEntSel(self.getPointMapTool().toCanvasCoordinates(value), + self.getPointMapTool(), \ + QadVariables.get(QadMsg.translate("Environment variables", "PICKBOX")), \ + layerList) + if result is not None: + feature = result[0] + layer = result[1] + point = result[2] + self.entitySet.addEntity(QadEntity().set(layer, feature.id())) + self.trimFeatures(QadPoint().set(value), False) + else: + return True # fine comando + + # si appresta ad attendere la selezione degli oggetti da estendere/tagliare + self.waitForObjectSel() + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA PUNTO PER MODALITA' INTERCETTA (da step = 2) + elif self.step == 3: # dopo aver atteso un punto si riavvia il comando + if self.PLINECommand.run(msgMapTool, msg) == True: + if self.PLINECommand.polyline.qty() > 0: + if msgMapTool == True: # se la polilinea arriva da una selezione grafica + ToExtend = True if self.getPointMapTool().shiftKey == True else False + else: + ToExtend = False + + # cerco tutte le geometrie passanti per la polilinea saltando i layer punto e poligono + # e considerando solo layer editabili + self.entitySet = getSelSet("F", self.getPointMapTool(), self.PLINECommand.polyline.asPolyline(), \ + None, False, True, False, \ + True) + self.trimFeatures(self.PLINECommand.polyline, ToExtend) + del self.PLINECommand + self.PLINECommand = None + + # si appresta ad attendere la selezione degli oggetti da estendere/tagliare + self.waitForObjectSel() + self.getPointMapTool().refreshSnapType() # aggiorno lo snapType che può essere variato dal maptool di pline + + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA PUNTO PER MODALITA' INTERSECA (da step = 2) + elif self.step == 4: # dopo aver atteso un punto si riavvia il comando + if self.RECTANGLECommand.run(msgMapTool, msg) == True: + if self.RECTANGLECommand.polyline.qty() > 0: + if msgMapTool == True: # se la polilinea arriva da una selezione grafica + ToExtend = True if self.getPointMapTool().shiftKey == True else False + else: + ToExtend = False + + # cerco tutte le geometrie passanti per la polilinea saltando i layer punto e poligono + # e considerando solo layer editabili + self.entitySet = getSelSet("F", self.getPointMapTool(), self.RECTANGLECommand.polyline.asPolyline(), \ + None, False, True, False, \ + True) + self.trimFeatures(self.RECTANGLECommand.polyline, ToExtend) + del self.RECTANGLECommand + self.RECTANGLECommand = None + + # si appresta ad attendere la selezione degli oggetti da estendere/tagliare + self.waitForObjectSel() + self.getPointMapTool().refreshSnapType() # aggiorno lo snapType che può essere variato dal maptool di rectangle + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DI TIPO DI ESTENSIONE (da step = 2) + elif self.step == 5: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + value = self.defaultValue + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + else: # il valore arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("Command_TRIM", "No extend") or value == "No extend": + self.edgeMode = 0 + QadVariables.set(QadMsg.translate("Environment variables", "EDGEMODE"), self.edgeMode) + QadVariables.save() + # si appresta ad attendere la selezione degli oggetti da estendere/tagliare + self.waitForObjectSel() + elif value == QadMsg.translate("Command_TRIM", "Extend") or value == "Extend": + self.edgeMode = 1 + QadVariables.set(QadMsg.translate("Environment variables", "EDGEMODE"), self.edgeMode) + QadVariables.save() + # si appresta ad attendere la selezione degli oggetti da estendere/tagliare + self.waitForObjectSel() + + return False diff --git a/qad_undoredo_cmd.py b/cmd/qad_undoredo_cmd.py similarity index 91% rename from qad_undoredo_cmd.py rename to cmd/qad_undoredo_cmd.py index f9d85204..7109dce0 100644 --- a/qad_undoredo_cmd.py +++ b/cmd/qad_undoredo_cmd.py @@ -1,195 +1,192 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - comando UNDO e REDO di QAD - - ------------------- - begin : 2013-05-22 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -from qad_generic_cmd import QadCommandClass -from qad_msg import QadMsg -from qad_textwindow import * - - -# Classe che gestisce il comando UNDO -class QadUNDOCommandClass(QadCommandClass): - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadUNDOCommandClass(self.plugIn) - - def getName(self): - return QadMsg.translate("Command_list", "UNDO") - - def getEnglishName(self): - return "UNDO" - - def connectQAction(self, action): - QObject.connect(action, SIGNAL("triggered()"), self.plugIn.runUNDOCommand) - - def getIcon(self): - return QIcon(":/plugins/qad/icons/undo.png") - - def getNote(self): - # impostare le note esplicative del comando - return QadMsg.translate("Command_UNDO", "Reverses the effect of commands.") - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - - def run(self, msgMapTool = False, msg = None): - self.isValidPreviousInput = True # per gestire il comando anche in macro - - if self.step == 0: # inizio del comando - keyWords = QadMsg.translate("Command_UNDO", "BEgin") + "/" + \ - QadMsg.translate("Command_UNDO", "End") + "/" + \ - QadMsg.translate("Command_UNDO", "Mark") + "/" + \ - QadMsg.translate("Command_UNDO", "Back") - default = 1 - prompt = QadMsg.translate("Command_UNDO", "Enter the number of operations to undo or [{0}] <{1}>: ").format(keyWords, str(default)) - - englishKeyWords = "BEgin" + "/" + "End" + "/" + "Mark" + "/" + "Back" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un numero intero positivo o enter o una parola chiave - # msg, inputType, default, keyWords, valori positivi - self.waitFor(prompt, \ - QadInputTypeEnum.INT | QadInputTypeEnum.KEYWORDS, \ - default, \ - keyWords, QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) - self.step = 1 - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA NUMERO INTERO (da step = 0) - elif self.step == 1: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - self.plugIn.undoEditCommand() - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("Command_UNDO", "BEgin") or value == "BEgin": - self.plugIn.insertBeginGroup() - elif value == QadMsg.translate("Command_UNDO", "End") or value == "End": - if self.plugIn.insertEndGroup() == False: - self.showMsg(QadMsg.translate("Command_UNDO", "\nNo open group.")) - elif value == QadMsg.translate("Command_UNDO", "Mark") or value == "Mark": - if self.plugIn.insertBookmark() == False: - self.showMsg(QadMsg.translate("Command_UNDO", "\nA mark can't be inserted into a group.")) - elif value == QadMsg.translate("Command_UNDO", "Back") or value == "Back": - if self.plugIn.getPrevBookmarkPos() == -1: # non ci sono bookmark precedenti - keyWords = QadMsg.translate("QAD", "Yes") + "/" + \ - QadMsg.translate("QAD", "No") - default = QadMsg.translate("QAD", "Yes") - prompt = QadMsg.translate("Command_UNDO", "This will undo everything. OK ? <{0}>: ").format(default) - - englishKeyWords = "Yes" + "/" + "No" - keyWords += "_" + englishKeyWords - # si appresta ad attendere enter o una parola chiave - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(prompt, \ - QadInputTypeEnum.KEYWORDS, \ - default, \ - keyWords, QadInputModeEnum.NONE) - self.step = 2 - return False - else: - self.plugIn.undoUntilBookmark() - elif type(value) == int: - self.plugIn.undoEditCommand(value) - - return True - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DI ANNULLARE TUTTO (da step = 1) - elif self.step == 2: # dopo aver atteso una parola chiave si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - self.plugIn.undoUntilBookmark() - self.showMsg(QadMsg.translate("Command_UNDO", "All has been undone.")) - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("QAD", "Yes") or value == "Yes": - self.showMsg(QadMsg.translate("Command_UNDO", "All has been undone.")) - self.plugIn.undoUntilBookmark() - - return True # fine comando - - -# Classe che gestisce il comando REDO -class QadREDOCommandClass(QadCommandClass): - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadREDOCommandClass(self.plugIn) - - def getName(self): - return QadMsg.translate("Command_list", "REDO") - - def getEnglishName(self): - return "REDO" - - def connectQAction(self, action): - QObject.connect(action, SIGNAL("triggered()"), self.plugIn.runREDOCommand) - - def getIcon(self): - return QIcon(":/plugins/qad/icons/redo.png") - - def getNote(self): - # impostare le note esplicative del comando - return QadMsg.translate("Command_UNDO", "Reverses the effects of previous UNDO.") - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - - def run(self, msgMapTool = False, msg = None): - self.plugIn.redoEditCommand() +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + comando UNDO e REDO di QAD + + ------------------- + begin : 2013-05-22 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.PyQt.QtGui import QIcon + +from .qad_generic_cmd import QadCommandClass +from ..qad_msg import QadMsg +from ..qad_textwindow import QadInputTypeEnum, QadInputModeEnum + + +# Classe che gestisce il comando UNDO +class QadUNDOCommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadUNDOCommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "UNDO") + + def getEnglishName(self): + return "UNDO" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runUNDOCommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/undo.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_UNDO", "Reverses the effect of commands.") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + + def run(self, msgMapTool = False, msg = None): + self.isValidPreviousInput = True # per gestire il comando anche in macro + + if self.step == 0: # inizio del comando + keyWords = QadMsg.translate("Command_UNDO", "BEgin") + "/" + \ + QadMsg.translate("Command_UNDO", "End") + "/" + \ + QadMsg.translate("Command_UNDO", "Mark") + "/" + \ + QadMsg.translate("Command_UNDO", "Back") + default = 1 + prompt = QadMsg.translate("Command_UNDO", "Enter the number of operations to undo or [{0}] <{1}>: ").format(keyWords, str(default)) + + englishKeyWords = "BEgin" + "/" + "End" + "/" + "Mark" + "/" + "Back" + keyWords += "_" + englishKeyWords + # si appresta ad attendere un numero intero positivo o enter o una parola chiave + # msg, inputType, default, keyWords, valori positivi + self.waitFor(prompt, \ + QadInputTypeEnum.INT | QadInputTypeEnum.KEYWORDS, \ + default, \ + keyWords, QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) + self.step = 1 + return False + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA NUMERO INTERO (da step = 0) + elif self.step == 1: # dopo aver atteso un punto o un numero reale si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + self.plugIn.undoEditCommand() + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("Command_UNDO", "BEgin") or value == "BEgin": + self.plugIn.insertBeginGroup() + elif value == QadMsg.translate("Command_UNDO", "End") or value == "End": + if self.plugIn.insertEndGroup() == False: + self.showMsg(QadMsg.translate("Command_UNDO", "\nNo open group.")) + elif value == QadMsg.translate("Command_UNDO", "Mark") or value == "Mark": + if self.plugIn.insertBookmark() == False: + self.showMsg(QadMsg.translate("Command_UNDO", "\nA mark can't be inserted into a group.")) + elif value == QadMsg.translate("Command_UNDO", "Back") or value == "Back": + if self.plugIn.getPrevBookmarkPos() == -1: # non ci sono bookmark precedenti + keyWords = QadMsg.translate("QAD", "Yes") + "/" + \ + QadMsg.translate("QAD", "No") + default = QadMsg.translate("QAD", "Yes") + prompt = QadMsg.translate("Command_UNDO", "This will undo everything. OK ? <{0}>: ").format(default) + + englishKeyWords = "Yes" + "/" + "No" + keyWords += "_" + englishKeyWords + # si appresta ad attendere enter o una parola chiave + # msg, inputType, default, keyWords, nessun controllo + self.waitFor(prompt, \ + QadInputTypeEnum.KEYWORDS, \ + default, \ + keyWords, QadInputModeEnum.NONE) + self.step = 2 + return False + else: + self.plugIn.undoUntilBookmark() + elif type(value) == int: + self.plugIn.undoEditCommand(value) + + return True + + # ========================================================================= + # RISPOSTA ALLA RICHIESTA DI ANNULLARE TUTTO (da step = 1) + elif self.step == 2: # dopo aver atteso una parola chiave si riavvia il comando + if msgMapTool == True: # il punto arriva da una selezione grafica + # la condizione seguente si verifica se durante la selezione di un punto + # é stato attivato un altro plugin che ha disattivato Qad + # quindi stato riattivato il comando che torna qui senza che il maptool + # abbia selezionato un punto + if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto + if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse + self.plugIn.undoUntilBookmark() + self.showMsg(QadMsg.translate("Command_UNDO", "All has been undone.")) + return True # fine comando + else: + self.setMapTool(self.getPointMapTool()) # riattivo il maptool + return False + + value = self.getPointMapTool().point + else: # il punto arriva come parametro della funzione + value = msg + + if type(value) == unicode: + if value == QadMsg.translate("QAD", "Yes") or value == "Yes": + self.showMsg(QadMsg.translate("Command_UNDO", "All has been undone.")) + self.plugIn.undoUntilBookmark() + + return True # fine comando + + +# Classe che gestisce il comando REDO +class QadREDOCommandClass(QadCommandClass): + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadREDOCommandClass(self.plugIn) + + def getName(self): + return QadMsg.translate("Command_list", "REDO") + + def getEnglishName(self): + return "REDO" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runREDOCommand) + + def getIcon(self): + return QIcon(":/plugins/qad/icons/redo.svg") + + def getNote(self): + # impostare le note esplicative del comando + return QadMsg.translate("Command_UNDO", "Reverses the effects of previous UNDO.") + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + + def run(self, msgMapTool = False, msg = None): + self.plugIn.redoEditCommand() return True \ No newline at end of file diff --git a/compila_risorse.bat b/compila_risorse.bat index c724b526..ffe3a8f1 100644 --- a/compila_risorse.bat +++ b/compila_risorse.bat @@ -1,2 +1,12 @@ -call pyrcc4 -o qad_rc.py qad.qrc -call pyrcc4 -o qad_dsettings_rc.py qad_dsettings.qrc \ No newline at end of file +set OSGEO4W_ROOT=C:\Program Files\QGIS 3.22.6 + +call "%OSGEO4W_ROOT%\bin\o4w_env.bat" + +path %OSGEO4W_ROOT%\apps\qgis-ltr\bin;%OSGEO4W_ROOT%\apps\grass\grass78\lib;%OSGEO4W_ROOT%\apps\grass\grass78\bin;%PATH% + +cd /d %~dp0 + +@ECHO ON +call pyrcc5 -o qad_rc.py qad.qrc +call pyrcc5 -o qad_dsettings_rc.py qad_dsettings.qrc +rem call pyrcc5 -o incrementalSum_rc.py incrementalSum.qrc \ No newline at end of file diff --git a/compila_ui.bat b/compila_ui.bat index e680a19e..48fad300 100644 --- a/compila_ui.bat +++ b/compila_ui.bat @@ -1,6 +1,24 @@ -call pyuic4 -o qad_ui.py qad.ui -call pyuic4 -o qad_dsettings_ui.py qad_dsettings.ui -call pyuic4 -o qad_dimstyle_ui.py qad_dimstyle.ui -call pyuic4 -o qad_dimstyle_details_ui.py qad_dimstyle_details.ui -call pyuic4 -o qad_dimstyle_new_ui.py qad_dimstyle_new.ui -call pyuic4 -o qad_dimstyle_diff_ui.py qad_dimstyle_diff.ui \ No newline at end of file +set OSGEO4W_ROOT=C:\Program Files\QGIS 3.22.6 + +call "%OSGEO4W_ROOT%\bin\o4w_env.bat" +rem call "%OSGEO4W_ROOT%\bin\qgis-ltr-designer.bat" +rem call "%OSGEO4W_ROOT%\bin\python-qgis-ltr.bat" + +path %OSGEO4W_ROOT%\apps\qgis-ltr\bin;%OSGEO4W_ROOT%\apps\grass\grass78\lib;%OSGEO4W_ROOT%\apps\grass\grass78\bin;%PATH% + +cd /d %~dp0 + +@ECHO ON +call pyuic5 --from-imports -o .\qad_dsettings_ui.py .\qad_dsettings.ui +call pyuic5 --from-imports -o .\qad_pointerinput_settings_ui.py .\qad_pointerinput_settings.ui +call pyuic5 --from-imports -o .\qad_dimensioninput_settings_ui.py .\qad_dimensioninput_settings.ui +call pyuic5 --from-imports -o .\qad_dimstyle_ui.py .\qad_dimstyle.ui +call pyuic5 --from-imports -o .\qad_dimstyle_details_ui.py .\qad_dimstyle_details.ui +call pyuic5 --from-imports -o .\qad_dimstyle_new_ui.py .\qad_dimstyle_new.ui +call pyuic5 --from-imports -o .\qad_dimstyle_diff_ui.py .\qad_dimstyle_diff.ui +call pyuic5 --from-imports -o .\qad_options_ui.py .\qad_options.ui +call pyuic5 --from-imports -o .\qad_gripcolor_ui.py .\qad_gripcolor.ui +call pyuic5 --from-imports -o .\qad_windowcolor_ui.py .\qad_windowcolor.ui +call pyuic5 --from-imports -o .\qad_tooltip_appearance_ui.py .\qad_tooltip_appearance.ui +call pyuic5 --from-imports -o .\qad_rightclick_ui.py .\qad_rightclick.ui + \ No newline at end of file diff --git a/gitignore b/gitignore new file mode 100644 index 00000000..ccd1cce1 --- /dev/null +++ b/gitignore @@ -0,0 +1,16 @@ +# SVN +*.svn + +# ECLIPSE +*.settings +*.project +*.pydevproject + +# COMPILED PYTHON +*.pyc + +# debug +qad_debug.* + +Thumbs.db +*.psproj diff --git a/help/Considerazione per la scrittura dei manuali ed esportazione in PDF.txt b/help/Considerazione per la scrittura dei manuali ed esportazione in PDF.txt new file mode 100644 index 00000000..42565ab5 --- /dev/null +++ b/help/Considerazione per la scrittura dei manuali ed esportazione in PDF.txt @@ -0,0 +1,19 @@ +Considerazione per la scrittura dei manuali ed esportazione in PDF. + +Tutti i paragrafi che dovranno avere accesso diretto devo avere un segnalibro. +Il nome del segnalibro non deve contenere spazi oppure _ (" ", "_") ed incominciare con una lettera. +Nominare il segnalibro come il paragrafo togliendo gli spazi o i _ ed usando le lettere maiuscole per separare le parole(es. "Importazione dati da DB Oracle" diventa "ImportazioneDatiDaDBOracle" + +Quando si vuole esportare in PDF: +in OpenOffice writer +menu File +voce Esporta nel formato PDF... + +-Pagina Generale +Esporta segnalibri + +-Pagina Collegamenti +Esporta segnalibri come segnaposto PDF + +-Pagina Visualizzazione iniziale +Segnalibri e pagina \ No newline at end of file diff --git a/help/help_en/100_DYNTRECKINGVECTORCOLOR.htm b/help/help_en/100_DYNTRECKINGVECTORCOLOR.htm new file mode 100644 index 00000000..ac7c44f4 --- /dev/null +++ b/help/help_en/100_DYNTRECKINGVECTORCOLOR.htm @@ -0,0 +1,20 @@ + + + + + + + +DYNTRECKINGVECTORCOLOR + + + +
+

DYNTRECKINGVECTORCOLOR

+

Set the Autotreck vector +color (RGB). Global variable.

+
+ + diff --git a/help/help_en/100_EDGEMODE.htm b/help/help_en/100_EDGEMODE.htm new file mode 100644 index 00000000..1ad8f97f --- /dev/null +++ b/help/help_en/100_EDGEMODE.htm @@ -0,0 +1,21 @@ + + + + + + + +EDGEMODE + + + +
+

EDGEMODE

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/101_EDGEMODE.htm b/help/help_en/101_EDGEMODE.htm new file mode 100644 index 00000000..6219313b --- /dev/null +++ b/help/help_en/101_EDGEMODE.htm @@ -0,0 +1,21 @@ + + + + + + + +EDGEMODE + + + +
+

EDGEMODE

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/101_FILLETRAD.htm b/help/help_en/101_FILLETRAD.htm new file mode 100644 index 00000000..d3196f18 --- /dev/null +++ b/help/help_en/101_FILLETRAD.htm @@ -0,0 +1,21 @@ + + + + + + + +FILLETRAD + + + +
+

FILLETRAD

+

The same as the +most popular CAD. Project +variable.

+
+ + diff --git a/help/help_en/102_ELLIPSEMINSEGMENTQTY.htm b/help/help_en/102_ELLIPSEMINSEGMENTQTY.htm new file mode 100644 index 00000000..3242f875 --- /dev/null +++ b/help/help_en/102_ELLIPSEMINSEGMENTQTY.htm @@ -0,0 +1,20 @@ + + + + + + + +ELLIPSEMINSEGMENTQTY + + + +
+

ELLIPSEMINSEGMENTQTY

+

Minimum number of segments +to approximate an ellipse. Valid values from 8 to 999, integer +type, default value 12. Project variable.

+
+ + diff --git a/help/help_en/102_GRIPCOLOR.htm b/help/help_en/102_GRIPCOLOR.htm new file mode 100644 index 00000000..65ecd9e7 --- /dev/null +++ b/help/help_en/102_GRIPCOLOR.htm @@ -0,0 +1,21 @@ + + + + + + + +GRIPCOLOR + + + +
+

GRIPCOLOR

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/103_ELLIPSEARCMINSEGMENTQTY.htm b/help/help_en/103_ELLIPSEARCMINSEGMENTQTY.htm new file mode 100644 index 00000000..9002d6d0 --- /dev/null +++ b/help/help_en/103_ELLIPSEARCMINSEGMENTQTY.htm @@ -0,0 +1,20 @@ + + + + + + + +ELLIPSEARCMINSEGMENTQTY + + + +
+

ELLIPSEARCMINSEGMENTQTY

+

Minimum number of segments +to approximate an arc of ellipse. Valid values from 8 to 999, +integer type, default value 12. Project variable.

+
+ + diff --git a/help/help_en/103_GRIPCONTOUR.htm b/help/help_en/103_GRIPCONTOUR.htm new file mode 100644 index 00000000..6f1b88ae --- /dev/null +++ b/help/help_en/103_GRIPCONTOUR.htm @@ -0,0 +1,21 @@ + + + + + + + +GRIPCONTOUR + + + +
+

GRIPCONTOUR

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/104_FILLETRAD.htm b/help/help_en/104_FILLETRAD.htm new file mode 100644 index 00000000..35c9936d --- /dev/null +++ b/help/help_en/104_FILLETRAD.htm @@ -0,0 +1,21 @@ + + + + + + + +FILLETRAD + + + +
+

FILLETRAD

+

The same as the +most popular CAD. Project +variable.

+
+ + diff --git a/help/help_en/104_GRIPHOT.htm b/help/help_en/104_GRIPHOT.htm new file mode 100644 index 00000000..dbb49915 --- /dev/null +++ b/help/help_en/104_GRIPHOT.htm @@ -0,0 +1,21 @@ + + + + + + + +GRIPHOT + + + +
+

GRIPHOT

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/105_GRIPCOLOR.htm b/help/help_en/105_GRIPCOLOR.htm new file mode 100644 index 00000000..f03e51e1 --- /dev/null +++ b/help/help_en/105_GRIPCOLOR.htm @@ -0,0 +1,21 @@ + + + + + + + +GRIPCOLOR + + + +
+

GRIPCOLOR

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/105_GRIPHOVER.htm b/help/help_en/105_GRIPHOVER.htm new file mode 100644 index 00000000..df67ec8d --- /dev/null +++ b/help/help_en/105_GRIPHOVER.htm @@ -0,0 +1,21 @@ + + + + + + + +GRIPHOVER + + + +
+

GRIPHOVER

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/106_GRIPCONTOUR.htm b/help/help_en/106_GRIPCONTOUR.htm new file mode 100644 index 00000000..406cf007 --- /dev/null +++ b/help/help_en/106_GRIPCONTOUR.htm @@ -0,0 +1,21 @@ + + + + + + + +GRIPCONTOUR + + + +
+

GRIPCONTOUR

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/106_GRIPMULTIFUNCTIONAL.htm b/help/help_en/106_GRIPMULTIFUNCTIONAL.htm new file mode 100644 index 00000000..c178cea1 --- /dev/null +++ b/help/help_en/106_GRIPMULTIFUNCTIONAL.htm @@ -0,0 +1,23 @@ + + + + + + + +GRIPMULTIFUNCTIONAL + + + +
+

GRIPMULTIFUNCTIONAL

+

Specifies the access +methods to multi-functional grips. Global variable.
+0 = Access to multi-functional grips is disabled.
+2 = Access multi-functional grips with the dynamic menu and the Hot +Grip shortcut menu.

+
+ + diff --git a/help/help_en/107_GRIPHOT.htm b/help/help_en/107_GRIPHOT.htm new file mode 100644 index 00000000..6e7c6621 --- /dev/null +++ b/help/help_en/107_GRIPHOT.htm @@ -0,0 +1,21 @@ + + + + + + + +GRIPHOT + + + +
+

GRIPHOT

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/107_GRIPOBJLIMIT.htm b/help/help_en/107_GRIPOBJLIMIT.htm new file mode 100644 index 00000000..cc1b1377 --- /dev/null +++ b/help/help_en/107_GRIPOBJLIMIT.htm @@ -0,0 +1,19 @@ + + + + + + + +GRIPOBJLIMIT + + + +
+

GRIPOBJLIMIT

+

Come i CAD più popolari. Global variable.

+
+ + diff --git a/help/help_en/108_GRIPHOVER.htm b/help/help_en/108_GRIPHOVER.htm new file mode 100644 index 00000000..823e7779 --- /dev/null +++ b/help/help_en/108_GRIPHOVER.htm @@ -0,0 +1,21 @@ + + + + + + + +GRIPHOVER + + + +
+

GRIPHOVER

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/108_GRIPS.htm b/help/help_en/108_GRIPS.htm new file mode 100644 index 00000000..6703c940 --- /dev/null +++ b/help/help_en/108_GRIPS.htm @@ -0,0 +1,21 @@ + + + + + + + +GRIPS + + + +
+

GRIPS

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/109_GRIPMULTIFUNCTIONAL.htm b/help/help_en/109_GRIPMULTIFUNCTIONAL.htm new file mode 100644 index 00000000..463fa082 --- /dev/null +++ b/help/help_en/109_GRIPMULTIFUNCTIONAL.htm @@ -0,0 +1,23 @@ + + + + + + + +GRIPMULTIFUNCTIONAL + + + +
+

GRIPMULTIFUNCTIONAL

+

Specifies the access +methods to multi-functional grips. Global variable.
+0 = Access to multi-functional grips is disabled.
+2 = Access multi-functional grips with the dynamic menu and the Hot +Grip shortcut menu.

+
+ + diff --git a/help/help_en/109_GRIPSIZE.htm b/help/help_en/109_GRIPSIZE.htm new file mode 100644 index 00000000..863b4b19 --- /dev/null +++ b/help/help_en/109_GRIPSIZE.htm @@ -0,0 +1,21 @@ + + + + + + + +GRIPSIZE + + + +
+

GRIPSIZE

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/10_Coordinatespecifiedinacoordinatereferencesystemdifferentfromthecurrentone.htm b/help/help_en/10_Coordinatespecifiedinacoordinatereferencesystemdifferentfromthecurrentone.htm index cfc8d9ee..d7aeeeaf 100644 --- a/help/help_en/10_Coordinatespecifiedinacoordinatereferencesystemdifferentfromthecurrentone.htm +++ b/help/help_en/10_Coordinatespecifiedinacoordinatereferencesystemdifferentfromthecurrentone.htm @@ -1,85 +1,85 @@ - - - - - - - -Coordinate specified in a coordinate reference system -different from the current one - - - -
-

Coordinate -specified in a coordinate reference system different from the -current one

-

If the coordinate reference -system is projected:

-

enter x,y (SRID). For -example 1491621.64817, 4915622.63154 (EPSG:3003) is a point with -coordinate X=1491621.64817 and Y=4915622.63154 in the projected -coordinate reference system EPSG:3003
-

-

If the coordinate reference -system is geographic:

-

enter latitude, longitude -(SRID). For example 44º 24' 48N/ 08º 50' 15E (EPSG:4326) is a point -with latitude 44 degrees 24 minutes 48 seconds and longitude 6 -degrees 50 minutes 15 seconds in the geographic coordinate -reference system EPSG:4326.

-

Latitude and Longitude -values can be set using the following notations:

-
    -
  • Decimal Degrees (DDD) - In -this notation, decimal precision is set in the 'degree' -coordinate. For example, 49.11675953666N
  • -
  • Degrees, Minutes, and -Seconds (DMS) - In this notation, decimal precision is set in the -'seconds' coordinate. For example, 49 7'20.06"N
  • -
  • Degrees, Minutes with -Decimal Seconds (DMM) - In this notation, decimal precision is set -in the 'minutes' coordinate. For example, 49 7.0055722"N. (Here, -20.06 seconds above is divided by 3600 to get the decimal minute -value for 20.06 seconds.)
  • -
-

Latitude and Longitude -syntax is specified as follows:

-
    -
  • Numeric Values - Simply -separate each coordinate notation with a white space and the entry -will be recognized correctly. For example, you can indicate a DMS -notation as: 37 24 23.3. You could indicate a DMM notation as 49 -7.0055722.
    -You can also use the character (°) for degrees, the single quote -mark (') for minutes and the double quote mark (") for seconds, as -follows: 49°7'20.06"
  • -
  • Direction Notation -(North/South, East/West)
    -Use 'N', 'S', 'E', or 'W' to indicate direction. The letter can be -entered either upper or lower case and it can be placed before or -after the coordinate value. For example: N 37 24 23.3 is the same -as 37 24 23.3 N
    -You can also use the minus sign (-) to indicate a westerly or -southerly position. When you use this kind of notation, do not -specify a letter symbol. Additionally, you do not need to use a -plus sign (+) to indicate northerly/easterly directions.
    So, -for example this is a valid entry: 37 25 19.07, --122 05 08.40
  • -
  • Entering Latitude, -Longitude Pairs
    -When entering latitudinal or longitudinal pairs, the first -coordinate is interpreted as latitude unless you use a direction -letter to clarify (E or W). For example, you can enter longitude -first as: 122 05 08.40 W 37 25 19.07 N
    -However, you cannot use the minus sign to enter longitude -first:-122 05 08.40 37 25 19.07
    -You can separate pair entries with a space, a comma, or a slash: -37.7 N 122.2 W or 37.7 N,122.2 W or 37.7 N/122.2 W
  • -
-
- - + + + + + + + +Coordinate specified in a coordinate reference system +different from the current one + + + +
+

Coordinate +specified in a coordinate reference system different from the +current one

+

If the coordinate reference +system is projected:

+

enter x,y (SRID). For +example 1491621.64817, 4915622.63154 (EPSG:3003) is a point with +coordinate X=1491621.64817 and Y=4915622.63154 in the projected +coordinate reference system EPSG:3003
+

+

If the coordinate reference +system is geographic:

+

enter latitude, longitude +(SRID). For example 44º 24' 48N/ 08º 50' 15E (EPSG:4326) is a point +with latitude 44 degrees 24 minutes 48 seconds and longitude 6 +degrees 50 minutes 15 seconds in the geographic coordinate +reference system EPSG:4326.

+

Latitude and Longitude +values can be set using the following notations:

+
    +
  • Decimal Degrees (DDD) - In +this notation, decimal precision is set in the 'degree' +coordinate. For example, 49.11675953666N
  • +
  • Degrees, Minutes, and +Seconds (DMS) - In this notation, decimal precision is set in the +'seconds' coordinate. For example, 49 7'20.06"N
  • +
  • Degrees, Minutes with +Decimal Seconds (DMM) - In this notation, decimal precision is set +in the 'minutes' coordinate. For example, 49 7. 3343333"N. (Here, +20.06 seconds above is divided by 60 to get the decimal minute +value for 20.06 seconds.)
  • +
+

Latitude and Longitude +syntax is specified as follows:

+
    +
  • Numeric Values - Simply +separate each coordinate notation with a white space and the entry +will be recognized correctly. For example, you can indicate a DMS +notation as: 37 24 23.3. You could indicate a DMM notation as 49 +7.0055722.
    +You can also use the character (°) for degrees, the single quote +mark (') for minutes and the double quote mark (") for seconds, as +follows: 49°7'20.06"
  • +
  • Direction Notation +(North/South, East/West)
    +Use 'N', 'S', 'E', or 'W' to indicate direction. The letter can be +entered either upper or lower case and it can be placed before or +after the coordinate value. For example: N 37 24 23.3 is the same +as 37 24 23.3 N
    +You can also use the minus sign (-) to indicate a westerly or +southerly position. When you use this kind of notation, do not +specify a letter symbol. Additionally, you do not need to use a +plus sign (+) to indicate northerly/easterly directions.
    So, +for example this is a valid entry: 37 25 19.07, +-122 05 08.40
  • +
  • Entering Latitude, +Longitude Pairs
    +When entering latitudinal or longitudinal pairs, the first +coordinate is interpreted as latitude unless you use a direction +letter to clarify (E or W). For example, you can enter longitude +first as: 122 05 08.40 W 37 25 19.07 N
    +However, you cannot use the minus sign to enter longitude +first:-122 05 08.40 37 25 19.07
    +You can separate pair entries with a space, a comma, or a slash: +37.7 N 122.2 W or 37.7 N,122.2 W or 37.7 N/122.2 W
  • +
+
+ + diff --git a/help/help_en/110_GRIPOBJLIMIT.htm b/help/help_en/110_GRIPOBJLIMIT.htm new file mode 100644 index 00000000..f47c8630 --- /dev/null +++ b/help/help_en/110_GRIPOBJLIMIT.htm @@ -0,0 +1,19 @@ + + + + + + + +GRIPOBJLIMIT + + + +
+

GRIPOBJLIMIT

+

Come i CAD più popolari. Global variable.

+
+ + diff --git a/help/help_en/110_INPUTSEARCHDELAY.htm b/help/help_en/110_INPUTSEARCHDELAY.htm new file mode 100644 index 00000000..d89601eb --- /dev/null +++ b/help/help_en/110_INPUTSEARCHDELAY.htm @@ -0,0 +1,21 @@ + + + + + + + +INPUTSEARCHDELAY + + + +
+

INPUTSEARCHDELAY

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/111_GRIPS.htm b/help/help_en/111_GRIPS.htm new file mode 100644 index 00000000..3047358a --- /dev/null +++ b/help/help_en/111_GRIPS.htm @@ -0,0 +1,21 @@ + + + + + + + +GRIPS + + + +
+

GRIPS

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/111_INPUTSEARCHOPTIONS.htm b/help/help_en/111_INPUTSEARCHOPTIONS.htm new file mode 100644 index 00000000..a7eed9b0 --- /dev/null +++ b/help/help_en/111_INPUTSEARCHOPTIONS.htm @@ -0,0 +1,21 @@ + + + + + + + +INPUTSEARCHOPTIONS + + + +
+

INPUTSEARCHOPTIONS

+

The same as +AUTOCOMPLETEMODE system variable of the most +popular CAD. Global variable.

+
+ + diff --git a/help/help_en/112_GRIPSIZE.htm b/help/help_en/112_GRIPSIZE.htm new file mode 100644 index 00000000..cb90f887 --- /dev/null +++ b/help/help_en/112_GRIPSIZE.htm @@ -0,0 +1,21 @@ + + + + + + + +GRIPSIZE + + + +
+

GRIPSIZE

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/112_MAXARRAY.htm b/help/help_en/112_MAXARRAY.htm new file mode 100644 index 00000000..c15ceb34 --- /dev/null +++ b/help/help_en/112_MAXARRAY.htm @@ -0,0 +1,21 @@ + + + + + + + +MAXARRAY + + + +
+

MAXARRAY

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/113_INPUTSEARCHDELAY.htm b/help/help_en/113_INPUTSEARCHDELAY.htm new file mode 100644 index 00000000..e1f2702f --- /dev/null +++ b/help/help_en/113_INPUTSEARCHDELAY.htm @@ -0,0 +1,21 @@ + + + + + + + +INPUTSEARCHDELAY + + + +
+

INPUTSEARCHDELAY

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/113_OFFSETDIST.htm b/help/help_en/113_OFFSETDIST.htm new file mode 100644 index 00000000..be75f4f0 --- /dev/null +++ b/help/help_en/113_OFFSETDIST.htm @@ -0,0 +1,21 @@ + + + + + + + +OFFSETDIST + + + +
+

OFFSETDIST

+

The same as the +most popular CAD. Project +variable.

+
+ + diff --git a/help/help_en/114_INPUTSEARCHOPTIONS.htm b/help/help_en/114_INPUTSEARCHOPTIONS.htm new file mode 100644 index 00000000..11c9baf6 --- /dev/null +++ b/help/help_en/114_INPUTSEARCHOPTIONS.htm @@ -0,0 +1,21 @@ + + + + + + + +INPUTSEARCHOPTIONS + + + +
+

INPUTSEARCHOPTIONS

+

The same as +AUTOCOMPLETEMODE system variable of the most +popular CAD. Global variable.

+
+ + diff --git a/help/help_en/114_OFFSETGAPTYPE.htm b/help/help_en/114_OFFSETGAPTYPE.htm new file mode 100644 index 00000000..52f81ba8 --- /dev/null +++ b/help/help_en/114_OFFSETGAPTYPE.htm @@ -0,0 +1,21 @@ + + + + + + + +OFFSETGAPTYPE + + + +
+

OFFSETGAPTYPE

+

The same as the +most popular CAD. Project +variable.

+
+ + diff --git a/help/help_en/115_MAXARRAY.htm b/help/help_en/115_MAXARRAY.htm new file mode 100644 index 00000000..d95fcf80 --- /dev/null +++ b/help/help_en/115_MAXARRAY.htm @@ -0,0 +1,21 @@ + + + + + + + +MAXARRAY + + + +
+

MAXARRAY

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/115_ORTHOMODE.htm b/help/help_en/115_ORTHOMODE.htm new file mode 100644 index 00000000..10ec01f1 --- /dev/null +++ b/help/help_en/115_ORTHOMODE.htm @@ -0,0 +1,21 @@ + + + + + + + +ORTHOMODE + + + +
+

ORTHOMODE

+

The same as the +most popular CAD. Project +variable.

+
+ + diff --git a/help/help_en/116_OFFSETDIST.htm b/help/help_en/116_OFFSETDIST.htm new file mode 100644 index 00000000..6a0ad3d4 --- /dev/null +++ b/help/help_en/116_OFFSETDIST.htm @@ -0,0 +1,21 @@ + + + + + + + +OFFSETDIST + + + +
+

OFFSETDIST

+

The same as the +most popular CAD. Project +variable.

+
+ + diff --git a/help/help_en/116_OSMODE.htm b/help/help_en/116_OSMODE.htm new file mode 100644 index 00000000..f672922f --- /dev/null +++ b/help/help_en/116_OSMODE.htm @@ -0,0 +1,21 @@ + + + + + + + +OSMODE + + + +
+

OSMODE

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/117_OFFSETGAPTYPE.htm b/help/help_en/117_OFFSETGAPTYPE.htm new file mode 100644 index 00000000..f7c58299 --- /dev/null +++ b/help/help_en/117_OFFSETGAPTYPE.htm @@ -0,0 +1,21 @@ + + + + + + + +OFFSETGAPTYPE + + + +
+

OFFSETGAPTYPE

+

The same as the +most popular CAD. Project +variable.

+
+ + diff --git a/help/help_en/117_OSPROGRDISTANCE.htm b/help/help_en/117_OSPROGRDISTANCE.htm new file mode 100644 index 00000000..dc3a8219 --- /dev/null +++ b/help/help_en/117_OSPROGRDISTANCE.htm @@ -0,0 +1,21 @@ + + + + + + + +OSPROGRDISTANCE + + + +
+

OSPROGRDISTANCE

+

Progressive distance for +<Progressive distance> snap mode. Real type, default value 0. +Project variable.

+
+ + diff --git a/help/help_en/118_ORTHOMODE.htm b/help/help_en/118_ORTHOMODE.htm new file mode 100644 index 00000000..50de0a25 --- /dev/null +++ b/help/help_en/118_ORTHOMODE.htm @@ -0,0 +1,21 @@ + + + + + + + +ORTHOMODE + + + +
+

ORTHOMODE

+

The same as the +most popular CAD. Project +variable.

+
+ + diff --git a/help/help_en/118_PICKADD.htm b/help/help_en/118_PICKADD.htm new file mode 100644 index 00000000..caddf1ac --- /dev/null +++ b/help/help_en/118_PICKADD.htm @@ -0,0 +1,21 @@ + + + + + + + +PICKADD + + + +
+

PICKADD

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/119_OSMODE.htm b/help/help_en/119_OSMODE.htm new file mode 100644 index 00000000..cfe82471 --- /dev/null +++ b/help/help_en/119_OSMODE.htm @@ -0,0 +1,21 @@ + + + + + + + +OSMODE + + + +
+

OSMODE

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/119_PICKBOX.htm b/help/help_en/119_PICKBOX.htm new file mode 100644 index 00000000..2df22b22 --- /dev/null +++ b/help/help_en/119_PICKBOX.htm @@ -0,0 +1,21 @@ + + + + + + + +PICKBOX + + + +
+

PICKBOX

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/11_DYNAMICINPUT.htm b/help/help_en/11_DYNAMICINPUT.htm new file mode 100644 index 00000000..a6a977aa --- /dev/null +++ b/help/help_en/11_DYNAMICINPUT.htm @@ -0,0 +1,30 @@ + + + + + + + +DYNAMIC INPUT + + + +
+

DYNAMIC +INPUT

+

You can turn off dynamic +input temporarily by holding down the F12 key. Dynamic input +provides a command interface near the cursor in the drawing +area.

+

Run DSETTINGS command to +set dynamic input properties.

+
+

 

+

 

+
+ + diff --git a/help/help_en/11_Selectingobjects.htm b/help/help_en/11_Selectingobjects.htm deleted file mode 100644 index eb592c91..00000000 --- a/help/help_en/11_Selectingobjects.htm +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - -Selecting objects - - - -
-

Selecting -objects

-

When a command ask to select -the objects (usually with the message "select objects") you can -type the letter "H" for Help to show all options.

-

The <WCircle> and -<CCerchio> options select respectively objects that are -Inside/intersecting a circle and objects only inside a -circle.

-

The <WObjectsi> e -<Cobjectsi> options select respectively objects that -are Inside/intersecting existing objects and -objects only inside existing objects.

-

The <FBuffer> e -<IBuffer> options select respectively objects that -are Inside/intersecting a buffer and objects only -inside a buffer.

-
- - diff --git a/help/help_en/120_OSPROGRDISTANCE.htm b/help/help_en/120_OSPROGRDISTANCE.htm new file mode 100644 index 00000000..653ec354 --- /dev/null +++ b/help/help_en/120_OSPROGRDISTANCE.htm @@ -0,0 +1,21 @@ + + + + + + + +OSPROGRDISTANCE + + + +
+

OSPROGRDISTANCE

+

Progressive distance for +<Progressive distance> snap mode. Real type, default value 0. +Project variable.

+
+ + diff --git a/help/help_en/120_PICKBOXCOLOR.htm b/help/help_en/120_PICKBOXCOLOR.htm new file mode 100644 index 00000000..23fe245d --- /dev/null +++ b/help/help_en/120_PICKBOXCOLOR.htm @@ -0,0 +1,20 @@ + + + + + + + +PICKBOXCOLOR + + + +
+

PICKBOXCOLOR

+

Sets the object selection +target color. Global variable.

+
+ + diff --git a/help/help_en/121_PICKADD.htm b/help/help_en/121_PICKADD.htm new file mode 100644 index 00000000..b5497363 --- /dev/null +++ b/help/help_en/121_PICKADD.htm @@ -0,0 +1,21 @@ + + + + + + + +PICKADD + + + +
+

PICKADD

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/121_PICKFIRST.htm b/help/help_en/121_PICKFIRST.htm new file mode 100644 index 00000000..822a6bfb --- /dev/null +++ b/help/help_en/121_PICKFIRST.htm @@ -0,0 +1,21 @@ + + + + + + + +PICKFIRST + + + +
+

PICKFIRST

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/122_PICKBOX.htm b/help/help_en/122_PICKBOX.htm new file mode 100644 index 00000000..3eb961bf --- /dev/null +++ b/help/help_en/122_PICKBOX.htm @@ -0,0 +1,21 @@ + + + + + + + +PICKBOX + + + +
+

PICKBOX

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/122_POLARANG.htm b/help/help_en/122_POLARANG.htm new file mode 100644 index 00000000..467a4e0b --- /dev/null +++ b/help/help_en/122_POLARANG.htm @@ -0,0 +1,21 @@ + + + + + + + +POLARANG + + + +
+

POLARANG

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/123_PICKBOXCOLOR.htm b/help/help_en/123_PICKBOXCOLOR.htm new file mode 100644 index 00000000..b0a277d0 --- /dev/null +++ b/help/help_en/123_PICKBOXCOLOR.htm @@ -0,0 +1,20 @@ + + + + + + + +PICKBOXCOLOR + + + +
+

PICKBOXCOLOR

+

Sets the object selection +target color. Global variable.

+
+ + diff --git a/help/help_en/123_POLARMODE.htm b/help/help_en/123_POLARMODE.htm new file mode 100644 index 00000000..a0d3c33f --- /dev/null +++ b/help/help_en/123_POLARMODE.htm @@ -0,0 +1,22 @@ + + + + + + + +POLARMODE + + + +
+

POLARMODE

+

The same as the +most popular CAD. The value 4 is not supported (use +additional polar tracking angles). Global +variable.

+
+ + diff --git a/help/help_en/124_PICKFIRST.htm b/help/help_en/124_PICKFIRST.htm new file mode 100644 index 00000000..c9bd6d78 --- /dev/null +++ b/help/help_en/124_PICKFIRST.htm @@ -0,0 +1,21 @@ + + + + + + + +PICKFIRST + + + +
+

PICKFIRST

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/124_SELECTIONAREA.htm b/help/help_en/124_SELECTIONAREA.htm new file mode 100644 index 00000000..1a30e7a3 --- /dev/null +++ b/help/help_en/124_SELECTIONAREA.htm @@ -0,0 +1,21 @@ + + + + + + + +SELECTIONAREA + + + +
+

SELECTIONAREA

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/125_POLARANG.htm b/help/help_en/125_POLARANG.htm new file mode 100644 index 00000000..b634aa4c --- /dev/null +++ b/help/help_en/125_POLARANG.htm @@ -0,0 +1,21 @@ + + + + + + + +POLARANG + + + +
+

POLARANG

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/125_SELECTIONAREAOPACITY.htm b/help/help_en/125_SELECTIONAREAOPACITY.htm new file mode 100644 index 00000000..a22c8a26 --- /dev/null +++ b/help/help_en/125_SELECTIONAREAOPACITY.htm @@ -0,0 +1,21 @@ + + + + + + + +SELECTIONAREAOPACITY + + + +
+

SELECTIONAREAOPACITY

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/126_POLARMODE.htm b/help/help_en/126_POLARMODE.htm new file mode 100644 index 00000000..425e7a2b --- /dev/null +++ b/help/help_en/126_POLARMODE.htm @@ -0,0 +1,22 @@ + + + + + + + +POLARMODE + + + +
+

POLARMODE

+

The same as the +most popular CAD. The value 4 is not supported (use +additional polar tracking angles). Global +variable.

+
+ + diff --git a/help/help_en/126_SUPPORTPATH.htm b/help/help_en/126_SUPPORTPATH.htm new file mode 100644 index 00000000..3968a481 --- /dev/null +++ b/help/help_en/126_SUPPORTPATH.htm @@ -0,0 +1,20 @@ + + + + + + + +SUPPORTPATH + + + +
+

SUPPORTPATH

+

Searching path for support +files. Character type. Global variable.

+
+ + diff --git a/help/help_en/127_SELECTIONAREA.htm b/help/help_en/127_SELECTIONAREA.htm new file mode 100644 index 00000000..2b992153 --- /dev/null +++ b/help/help_en/127_SELECTIONAREA.htm @@ -0,0 +1,21 @@ + + + + + + + +SELECTIONAREA + + + +
+

SELECTIONAREA

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/127_SHOWTEXTWINDOW.htm b/help/help_en/127_SHOWTEXTWINDOW.htm new file mode 100644 index 00000000..29d2bbae --- /dev/null +++ b/help/help_en/127_SHOWTEXTWINDOW.htm @@ -0,0 +1,20 @@ + + + + + + + +SHOWTEXTWINDOW + + + +
+

SHOWTEXTWINDOW

+

Show the text window at +startup. Bool type, default value true. Global variable.

+
+ + diff --git a/help/help_en/128_SELECTIONAREAOPACITY.htm b/help/help_en/128_SELECTIONAREAOPACITY.htm new file mode 100644 index 00000000..19113212 --- /dev/null +++ b/help/help_en/128_SELECTIONAREAOPACITY.htm @@ -0,0 +1,21 @@ + + + + + + + +SELECTIONAREAOPACITY + + + +
+

SELECTIONAREAOPACITY

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/128_TOLERANCE2APPROXCURVE.htm b/help/help_en/128_TOLERANCE2APPROXCURVE.htm new file mode 100644 index 00000000..fa644913 --- /dev/null +++ b/help/help_en/128_TOLERANCE2APPROXCURVE.htm @@ -0,0 +1,21 @@ + + + + + + + +TOLERANCE2APPROXCURVE + + + +
+

TOLERANCE2APPROXCURVE

+

Maximum error approximating +a curve to segments. Valid values from 0.000001, real type, default +value 0.1. Project variable.

+
+ + diff --git a/help/help_en/129_SHORTCUTMENU.htm b/help/help_en/129_SHORTCUTMENU.htm new file mode 100644 index 00000000..142e2fd5 --- /dev/null +++ b/help/help_en/129_SHORTCUTMENU.htm @@ -0,0 +1,20 @@ + + + + + + + +SHORTCUTMENU + + + +
+

SHORTCUTMENU

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/129_TOOLTIPTRANSPARENCY.htm b/help/help_en/129_TOOLTIPTRANSPARENCY.htm new file mode 100644 index 00000000..455cee34 --- /dev/null +++ b/help/help_en/129_TOOLTIPTRANSPARENCY.htm @@ -0,0 +1,21 @@ + + + + + + + +TOOLTIPTRANSPARENCY + + + +
+

TOOLTIPTRANSPARENCY

+

Sets the transparency for +drafting tooltips. Valid values from 0 to 100. Global +variable.

+
+ + diff --git a/help/help_en/12_Dimensioning.htm b/help/help_en/12_Dimensioning.htm deleted file mode 100644 index 93bbfde7..00000000 --- a/help/help_en/12_Dimensioning.htm +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - -Dimensioning - - - -
-

Dimensioning

-

Dimension style is a set of -properties that determine the appearance of dimensions. These -properties are stored in files with the extension .dim and are -loaded at QAD startup. Dimension files must be saved in the folders specified by -SUPPORTPATH variable or in the QAD installation folder (i.e. in -windows xp "C:\Documents and Settings\current -user\.qgis2\python\plugins\qad ").

-

QAD stores the elements -constituting a dimension in 3 different layers::

-

-·        -Text layer for storing dimension text

-

-·        -Symbol layers to store punctual dimension objects (dimension -points, arrow symbols ...)

-

-·        -Linear layers to store linear dimension objects (dimension line, -extension lines ...)

-
- - diff --git a/help/help_en/12_Selectingobjects.htm b/help/help_en/12_Selectingobjects.htm new file mode 100644 index 00000000..2dfa3b51 --- /dev/null +++ b/help/help_en/12_Selectingobjects.htm @@ -0,0 +1,38 @@ + + + + + + + +Selecting objects + + + +
+

Selecting +objects

+

When a command ask to select +the objects (usually with the message "select objects") you can +type the letter "H" for Help to show all options.

+

The <WCircle> and +<CCircle> options select respectively objects that are +Inside/intersecting a circle and objects only inside a +circle.

+

The <WObjects> +and <Cobjects> +options select respectively objects that are +Inside/intersecting existing objects and objects only inside +existing objects.

+

The <FBuffer> +and <IBuffer> +options select respectively objects that are +Inside/intersecting a buffer and +objects only inside a +buffer.

+
+ + diff --git a/help/help_en/130_SHORTCUTMENUDURATION.htm b/help/help_en/130_SHORTCUTMENUDURATION.htm new file mode 100644 index 00000000..8b0b7211 --- /dev/null +++ b/help/help_en/130_SHORTCUTMENUDURATION.htm @@ -0,0 +1,20 @@ + + + + + + + +SHORTCUTMENUDURATION + + + +
+

SHORTCUTMENUDURATION

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/130_TOOLTIPSIZE.htm b/help/help_en/130_TOOLTIPSIZE.htm new file mode 100644 index 00000000..e09c05ab --- /dev/null +++ b/help/help_en/130_TOOLTIPSIZE.htm @@ -0,0 +1,21 @@ + + + + + + + +TOOLTIPSIZE + + + +
+

TOOLTIPSIZE

+

Sets the display size for +drafting tooltips, and dynamic input text. Valid values from -3 to +6. Global variable.

+
+ + diff --git a/help/help_en/131_SUPPORTPATH.htm b/help/help_en/131_SUPPORTPATH.htm new file mode 100644 index 00000000..29cea7f7 --- /dev/null +++ b/help/help_en/131_SUPPORTPATH.htm @@ -0,0 +1,20 @@ + + + + + + + +SUPPORTPATH + + + +
+

SUPPORTPATH

+

Searching path for support +files. Character type. Global variable.

+
+ + diff --git a/help/help_en/131_WINDOWAREACOLOR.htm b/help/help_en/131_WINDOWAREACOLOR.htm new file mode 100644 index 00000000..ed3ec32b --- /dev/null +++ b/help/help_en/131_WINDOWAREACOLOR.htm @@ -0,0 +1,20 @@ + + + + + + + +WINDOWAREACOLOR + + + +
+

WINDOWAREACOLOR

+

The same as the +most popular CAD. Global variable.

+
+ + diff --git a/help/help_en/132_SHOWTEXTWINDOW.htm b/help/help_en/132_SHOWTEXTWINDOW.htm new file mode 100644 index 00000000..66d3ad6b --- /dev/null +++ b/help/help_en/132_SHOWTEXTWINDOW.htm @@ -0,0 +1,20 @@ + + + + + + + +SHOWTEXTWINDOW + + + +
+

SHOWTEXTWINDOW

+

Show the text window at +startup. Bool type, default value true. Global variable.

+
+ + diff --git a/help/help_en/133_TOLERANCE2APPROXCURVE.htm b/help/help_en/133_TOLERANCE2APPROXCURVE.htm new file mode 100644 index 00000000..81122e0a --- /dev/null +++ b/help/help_en/133_TOLERANCE2APPROXCURVE.htm @@ -0,0 +1,21 @@ + + + + + + + +TOLERANCE2APPROXCURVE + + + +
+

TOLERANCE2APPROXCURVE

+

Maximum error approximating +a curve to segments. Valid values from 0.000001, real type, default +value 0.1. Project variable.

+
+ + diff --git a/help/help_en/134_TOLERANCE2COINCIDENT.htm b/help/help_en/134_TOLERANCE2COINCIDENT.htm new file mode 100644 index 00000000..fd9d3f47 --- /dev/null +++ b/help/help_en/134_TOLERANCE2COINCIDENT.htm @@ -0,0 +1,20 @@ + + + + + + + +TOLERANCE2COINCIDENT + + + +
+

TOLERANCE2COINCIDENT

+

Maximum error approximating +two coincident points. Floating type, default 0.0000001. Project +variable

+
+ + diff --git a/help/help_en/135_TOOLTIPTRANSPARENCY.htm b/help/help_en/135_TOOLTIPTRANSPARENCY.htm new file mode 100644 index 00000000..bfbeb386 --- /dev/null +++ b/help/help_en/135_TOOLTIPTRANSPARENCY.htm @@ -0,0 +1,21 @@ + + + + + + + +TOOLTIPTRANSPARENCY + + + +
+

TOOLTIPTRANSPARENCY

+

Sets the transparency for +drafting tooltips. Valid values from 0 to 100. Global +variable.

+
+ + diff --git a/help/help_en/136_TOOLTIPSIZE.htm b/help/help_en/136_TOOLTIPSIZE.htm new file mode 100644 index 00000000..58ef8dc5 --- /dev/null +++ b/help/help_en/136_TOOLTIPSIZE.htm @@ -0,0 +1,21 @@ + + + + + + + +TOOLTIPSIZE + + + +
+

TOOLTIPSIZE

+

Sets the display size for +drafting tooltips, and dynamic input text. Valid values from -3 to +6. Global variable.

+
+ + diff --git a/help/help_en/137_WINDOWAREACOLOR.htm b/help/help_en/137_WINDOWAREACOLOR.htm new file mode 100644 index 00000000..5d33cff1 --- /dev/null +++ b/help/help_en/137_WINDOWAREACOLOR.htm @@ -0,0 +1,20 @@ + + + + + + + +WINDOWAREACOLOR + + + +
+

WINDOWAREACOLOR

+

The same as the +most popular CAD. Global variable.

+
+ + diff --git a/help/help_en/13_Dimensioning.htm b/help/help_en/13_Dimensioning.htm new file mode 100644 index 00000000..45802d4b --- /dev/null +++ b/help/help_en/13_Dimensioning.htm @@ -0,0 +1,43 @@ + + + + + + + +Dimensioning + + + +
+

Dimensioning

+

Dimension style is a set of +properties that determine the appearance of dimensions. These +properties are stored in files with the extension .dim and are +loaded on QAD startup or on loading a project. Dimension files must be saved in +the current project folder or in the QAD installation folder (i.e. +in windows 8 "  C:\Users\current +user\AppData\Roaming\QGIS\QGIS3\profiles\default\python\plugins\qad").

+

QAD stores the elements +constituting a dimension in 3 different layers::

+

+·         +Text layer for storing dimension text

+

+·         +Symbol layers to store punctual dimension +objects (dimension points, arrow symbols ...)

+

+·         +Linear layers to store linear dimension objects +(dimension line, extension lines ...)

+
+ + diff --git a/help/help_en/13_Textlayermodelfordimensioning.htm b/help/help_en/13_Textlayermodelfordimensioning.htm deleted file mode 100644 index be8b46a9..00000000 --- a/help/help_en/13_Textlayermodelfordimensioning.htm +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - -Text layer model for dimensioning: - - - -
-

Text layer -model for dimensioning:

-

The main element of a -dimension is the text. Its textual layer must have the -following fields:

-
    -
  • a character field to store -the dimension text
  • -
  • a character field to store -the font Of the dimension text
  • -
  • a real number field to store the dimension -text height (in map unit)
  • -
  • a real number field to -store text rotation (degree counterclockwise where zero = -horizontal to the right)
  • -
-

Optional fields:

-
    -
  • an integer number field to -store the unique ID of the dimension (necessary if you want to -group the objects of a dimension, and implement the erasing and -editing features of an existing dimension)
  • -
  • a character field to store -the color of the dimension text
  • -
  • a character field to store -the dimension style name (required if you want to use the editing -features of an existing dimension)
  • -
  • a character field (2 -characters) to store the dimension style (linear, aligned ...) -according to the following scheme:
    -"AL" = linear aligned dimension
    -"AN" = angular dimension
    -"BL" = baseline and continued dimension
    -"DI" = diameters of arcs and circles dimension
    -"LD" = creates a line that connects annotation to a feature
    -"LI" = dimensions using only the horizontal or vertical components -of the locations
    -"RA" = radial dimension
    -"AR" = measure the length along a circle or arc
    -
    -(required if you want to use the editing features of an existing -dimension)
  • -
-

 

-

The textual layer must be -defined with labels as follows:

-
    -
  • The font must be read from -a field that stores the font character of the dimension text (tab -<Labels>-<Text>)
  • -
  • The size must be read by a -real number field that stores the dimension text height (in map -units, tab <Labels>-<Text>)
  • -
  • The rotation must be read -by a real number field that stores the dimension text rotation -(degree counterclockwise where zero = horizontal to the right), -option <Preserve data rotation values> activated, (tab -<Labels>-<Placement>)
  • -
  • Placement <Around -point> with distance = 0 (tab -<Labels>-<Placement>)
  • -
  • <Show all label for -this layer> option enabled (tab -<Labels>-<Rendering>)
  • -
  • <Show upside-down -labels> option with value <always> (tab -<Labels>-<Rendering>)
  • -
  • <Discourage labels from -covering features> option disabled (tab -<Labels>-<Rendering>)
  • -
-

Optional settings:

-
    -
  • The color can be read from -a character field that stores the dimension text color (tab -<Labels>-<Text>)
  • -
-
- - diff --git a/help/help_en/14_Modellodellayersimboliperlaquotatura.htm b/help/help_en/14_Modellodellayersimboliperlaquotatura.htm deleted file mode 100644 index 8ffd698f..00000000 --- a/help/help_en/14_Modellodellayersimboliperlaquotatura.htm +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - -Modello del layer simboli per la quotatura: - - - -
-

Modello del layer simboli per -la quotatura:

-

The dimension symbols (arrows, etc.) should be -stored in a layer with the following fields:

-
    -
  • a real number field to store dimension text -rotation (degree counterclockwise where zero = horizontal to the -right)
  • -
-

Optional fields:

-
    -
  • a character field to store -the symbol name
  • -
  • a real number field to -store the symbol scale
  • -
  • a character field (2 -characters) field to store the punctual object type according to -the following scheme:
    -"B1" = first arrow block ("Block 1")
    -"B2" = second arrow block ("Block 2")
    -"LB" = leader arrow block ("Leader Block")
    -"AB" = arc symbol ("Arc Block")
    -"D1" = dimension point 1
    -"D2" = dimension point 2
    -
    -(required if you want to use the editing features of an existing -dimension)
  • -
  • an integer number field to -store the unique ID of the dimension (necessary if you want to -group the objects of a dimension, and implement the erasing and -editing features of an existing dimension)
  • -
-

 

-

The symbol layer must be -defined with a style set as follows:

-
    -
  • <Style>-<Single -Symbol> option enabled
  • -
  • <Style>-<map -units> option enabled
  • -
  • Set the size of the symbol -so that the width of the arrow is 1 map unit (tab -<Style>)
  • -
  • The rotation must be read by -a real number field that stores the symbol rotation through the -formula "360-<field that stores the rotation>" -(degree counterclockwise where zero = -horizontal to the right, <Style>-<Advanced> option "rotation field -name"-<Espressione>)
  • -
  • The scale can be read by a -real number field that stores the scale of the symbol -(<Style>-<Advanced>-<Size scale field>-“ field -that stores the scale” and <Style>-< Advanced ->-<Size scale field >-<Scale diameter>)
  • -
-

The arrow symbol when -inserted with rotation = 0 must be horizontal with the arrow -pointing to the right and its insertion point should be on the tip -of the arrow.

-
- - diff --git a/help/help_en/14_Textlayermodelfordimensioning.htm b/help/help_en/14_Textlayermodelfordimensioning.htm new file mode 100644 index 00000000..97e9d13d --- /dev/null +++ b/help/help_en/14_Textlayermodelfordimensioning.htm @@ -0,0 +1,200 @@ + + + + + + + +Text layer model for dimensioning: + + + +
+

Text layer +model for dimensioning:

+

The main element of a +dimension is the text. Its textual layer must have the following +fields:

+
    +
  • a character field to store +the dimension text
  • +
  • a character field to store +the font Of the dimension text
  • +
  • a real number field to +store the dimension text height (in map unit)
  • +
  • a real number field to +store text rotation (degree counterclockwise where zero = +horizontal to the right)
  • +
+

Optional fields:

+
    +
  • an integer number field to +store the unique ID of the dimension
    +This field is required if you want to group the objects of the same +dimension and implement the erasing and editing features of an +existing dimension. Because it must be a unique value field, +actually, it is supported for PostGIS table only where you have to +create a serial type not null field which is the primary key of the +table. (i.e. “id”). In addition to this you have to create another +bigint type field which will be managed by QAD to store the +dimension ID (i.e. “dim_id”). Shape files don’t let QAD group +objects of the same dimension so, after drawing a dimension, every +objects will be independent each one from the other.
  • +
  • a character field to store +the color of the dimension text
  • +
  • a character field to store +the dimension style name (required if you want to use the editing +features of an existing dimension)
  • +
  • a character field (2 +characters) to store the dimension style (linear, aligned ...) +according to the following scheme:
    +"AL" = linear aligned dimension
    +"AN" = angular dimension
    +"BL" = baseline and continued dimension
    +"DI" = diameters of arcs and circles dimension
    +"LD" = creates a line that connects annotation to a feature
    +"LI" = dimensions using only the horizontal or vertical components +of the locations
    +"RA" = radial dimension
    +"AR" = measure the length along a circle or arc
    +
    +(required if you want to use the editing features of an existing +dimension)
  • +
+

An SQL example to create a +PostGIS table and indexes for dimension text:

+

CREATE TABLE +qad_dimension.dim_text

+

(

+

  id serial NOT +NULL,

+

  text character +varying(50) NOT NULL,

+

  font character +varying(50) NOT NULL,

+

  h_text double precision +NOT NULL,

+

  rot double precision NOT +NULL,

+

  color character +varying(10) NOT NULL,

+

  dim_style character +varying(50) NOT NULL,

+

  dim_type character +varying(2) NOT NULL,

+

  geom +geometry(Point,3003),

+

  dim_id bigint NOT +NULL,

+

  CONSTRAINT dim_text_pkey +PRIMARY KEY (id)

+

)

+

WITH (

+

  OIDS=FALSE

+

);

+

 

+

CREATE INDEX +dim_text_dim_id

+

  ON +qad_dimension.dim_text

+

  USING btree

+

  (dim_id);

+

 

+

CREATE INDEX +sidx_dim_text_geom

+

  ON +qad_dimension.dim_text

+

  USING gist

+

  (geom);

+

 

+

The textual layer must be +defined with labels as follows:

+
    +
  • The font must be read from +a field that stores the font character of the dimension text (tab +<Labels>-<Text>)
  • +
  • The size must be read by a +real number field that stores the dimension text height (in map +units, tab <Labels>-<Text>)
  • +
  • The rotation must be read +by a real number field that stores the dimension text rotation +(degree counterclockwise where zero = horizontal to the right), +option <Preserve data rotation values> activated, (tab +<Labels>-<Placement>)
  • +
  • Placement <Around +point> with distance = 0 (tab +<Labels>-<Placement>)
  • +
  • <Show all label for +this layer> option enabled (tab +<Labels>-<Rendering>)
  • +
  • <Show upside-down +labels> option with value <always> (tab +<Labels>-<Rendering>)
  • +
  • <Discourage labels from +covering features> option disabled (tab +<Labels>-<Rendering>)
  • +
+

Optional settings:

+
    +
  • The color can be read from +a character field that stores the dimension text color (tab +<Labels>-<Text>)
  • +
+
+ + diff --git a/help/help_en/15_Linearlayermodelfordimensioning.htm b/help/help_en/15_Linearlayermodelfordimensioning.htm deleted file mode 100644 index 6e60792e..00000000 --- a/help/help_en/15_Linearlayermodelfordimensioning.htm +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - -Linear layer model for dimensioning: - - - -
-

Linear layer -model for dimensioning:

-

Linear elements of a -dimension (dimension line, extension lines ...) must be stored in a -linear layer with the following fields:

-
    -
  • No mandatory -fields
  • -
-

Optional fields:

-
    -
  • a character field to store -the color of the dimension -lines
  • -
  • a character field to store -the linetype of the dimension lines
  • -
  • a character field (2 -characters) field to store the linear object type according to the -following scheme:
    -"D1" = Dimension line 1
    -"D2" = Dimension line 2
    -"E1" = Extension line 1"
    -"E2" = Extension line 2
    -"L" = leader line when the text is outside the dimension
    -
    -(required if you want to use the editing features of an existing -dimension)
  • -
  • an integer number field to -store the unique ID of the dimension (necessary if you want to -group the objects of a dimension, and implement the erasing and -editing features of an existing dimension)
  • -
-

 

-

The linear layer must be -defined with the style set as follows:

-

Optional settings:

-
    -
  • The color can be read from -a character field that stores the dimension line color
  • -
  • The linetype can be read -from a character field that stores the linetype of dimension -lines
  • -
-

 

-

Dimension commands (DIMLINEAR, -DIMALIGNED) refer to the current dimension style. To set the -current dimension style run DIMSTYLE command.

-
- - diff --git a/help/help_en/15_Symbollayermodelfordimensioning.htm b/help/help_en/15_Symbollayermodelfordimensioning.htm new file mode 100644 index 00000000..85c17297 --- /dev/null +++ b/help/help_en/15_Symbollayermodelfordimensioning.htm @@ -0,0 +1,170 @@ + + + + + + + +Symbol layer model for dimensioning: + + + +
+

Symbol layer model for +dimensioning:

+

The dimension symbols +(arrows, etc.) should be stored in a layer with the following +fields:

+
    +
  • a real number field to +store dimension text rotation (degree counterclockwise where zero = +horizontal to the right, use expression +“360-rotation_field”)
  • +
+

Optional fields:

+
    +
  • a character field to store +the symbol name
  • +
  • a real number field to +store the symbol scale
  • +
  • a character field (2 +characters) field to store the punctual object type according to +the following scheme:
    +"B1" = first arrow block ("Block 1")
    +"B2" = second arrow block ("Block 2")
    +"LB" = leader arrow block ("Leader Block")
    +"AB" = arc symbol ("Arc Block")
    +"D1" = dimension point 1
    +"D2" = dimension point 2
    +
    +(required if you want to use the editing features of an existing +dimension)
  • +
  • an integer number field to +store the unique ID of the dimension (necessary if you want to +group the objects of a dimension, and implement the erasing and +editing features of an existing dimension)
  • +
+

An SQL example to create a +PostGIS table and indexes for dimension symbol:

+

CREATE TABLE +qad_dimension.dim_symbol

+

(

+

  name character +varying(50),

+

  scale double +precision,

+

  rot double +precision,

+

  color character +varying(10),

+

  type character varying(2) +NOT NULL,

+

  id_parent bigint NOT +NULL,

+

  geom +geometry(Point,3003),

+

  id serial NOT +NULL,

+

  CONSTRAINT +dim_symbol_pkey PRIMARY KEY (id)

+

)

+

WITH (

+

  OIDS=FALSE

+

);

+

 

+

CREATE INDEX +dim_symbol_id_parent

+

  ON +qad_dimension.dim_symbol

+

  USING btree

+

  (id_parent);

+

 

+

CREATE INDEX +sidx_dim_symbol_geom

+

  ON +qad_dimension.dim_symbol

+

  USING gist

+

  (geom);

+

 

+

The symbol layer must be +defined with a style set as follows:

+
    +
  • <Style>-<Single +Symbol> option enabled
  • +
  • <Style>-<map +units> option enabled
  • +
  • Set the size of the symbol +so that the width of the arrow is 1 map unit (tab +<Style>)
  • +
  • The rotation must be read by +a real number field that stores the symbol rotation through the +formula "360-<field that stores the rotation>" +(degree counterclockwise where zero = +horizontal to the right, <Style>- +option "rotation"-<Expression>)
  • +
  • The scale can be read by a +real number field that stores the scale of the symbol +(<Style>-<Advanced>-option <Size>-“ field that +stores the scale”)
  • +
+

The arrow symbol when +inserted with rotation = 0 must be horizontal with the arrow +pointing to the right and its insertion point should be on the tip +of the arrow.

+
+ + diff --git a/help/help_en/16_Commandscustomization.htm b/help/help_en/16_Commandscustomization.htm deleted file mode 100644 index 8b04e426..00000000 --- a/help/help_en/16_Commandscustomization.htm +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - -Commands customization - - - -
-

Commands -customization

-

It is possible customize -the commands (shortcuts) by a -file named qad_<language>_<region>.pgp -(utf-8).

-

Language is the current -QGIS language (mandatory) and region is the current linguistic -region (optional). For example qad_pt_br.pgp is the file in -portuguese language of region Brazil, qad_en.pgp is the English -version of the pgp file. The file is searched by QAD following the -paths in the system variable SUPPORTPATH.

-
- - diff --git a/help/help_en/16_Linearlayermodelfordimensioning.htm b/help/help_en/16_Linearlayermodelfordimensioning.htm new file mode 100644 index 00000000..6ab81347 --- /dev/null +++ b/help/help_en/16_Linearlayermodelfordimensioning.htm @@ -0,0 +1,152 @@ + + + + + + + +Linear layer model for dimensioning: + + + +
+

Linear layer +model for dimensioning:

+

Linear elements of a +dimension (dimension line, extension lines ...) must be stored in a +linear layer with the following fields:

+
    +
  • No mandatory +fields
  • +
+

Optional fields:

+
    +
  • a character field to store +the color of the dimension +lines
  • +
  • a character field to store +the linetype of the dimension lines
  • +
  • a character field (2 +characters) field to store the linear object type according to the +following scheme:
    +"D1" = Dimension line 1
    +"D2" = Dimension line 2
    +“X1” = Extension of dimension line 1
    +“X2” = Extension of dimension line 2
    +"E1" = Extension line 1"
    +"E2" = Extension line 2
    +"L" = leader line when the text is outside the dimension
    +“CL” = Center line for center marker of an arc or circle
    +
    +(required if you want to use the editing features of an existing +dimension)
  • +
  • an integer number field to +store the unique ID of the dimension (necessary if you want to +group the objects of a dimension, and implement the erasing and +editing features of an existing dimension)
  • +
+

 

+

An SQL example to create a +PostGIS table and indexes for dimension lines:

+

CREATE TABLE +qad_dimension.dim_line

+

(

+

  line_type character +varying(50),

+

  color character +varying(10),

+

  type character varying(2) +NOT NULL,

+

  id_parent bigint NOT +NULL,

+

  geom +geometry(LineString,3003),

+

  id serial NOT +NULL,

+

  CONSTRAINT dim_line_pkey +PRIMARY KEY (id)

+

)

+

WITH (

+

  OIDS=FALSE

+

);

+

 

+

CREATE INDEX +dim_line_id_parent

+

  ON +qad_dimension.dim_line

+

  USING btree

+

  (id_parent);

+

 

+

CREATE INDEX +sidx_dim_line_geom

+

  ON +qad_dimension.dim_line

+

  USING gist

+

  (geom);

+

 

+

The linear layer must be +defined with the style set as follows:

+

Optional settings:

+
    +
  • The color can be read from +a character field that stores the dimension line color
  • +
  • The linetype can be read +from a character field that stores the linetype of dimension +lines
  • +
+

 

+

Dimension commands refer to +the current dimension style. To set the current dimension style run +DIMSTYLE command.

+
+ + diff --git a/help/help_en/17_Commands.htm b/help/help_en/17_Commands.htm deleted file mode 100644 index 739b39d1..00000000 --- a/help/help_en/17_Commands.htm +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - -Commands - - - -
-

Commands

-

QAD commands haven’t the -same options as those of the most popular -CAD since QGIS context is different -(usually graphical options), some commands have more options. This -manual describes only the options not present in the -corresponding most popular -CADcommands.

-

The commands are activated by menu -VECTOR->QAD or toolbar or command line. The commands and their -options can be specified in English by prefixing the character "_" -to the name (e.g. _ LINE) regardless of the language used in -QGIS.

-

QAD command can be -interrupted at any moment by the activation of another tool. To -resume the paused command and make active the QAD environment use -the QAD item in the QAD menu or press the button -"image" in the toolbar.

-

As you type the name of a -command QAD will display a list of commands that begin with what -has been written Typing "*" the list of all -QAD commands will -appear.

-

To choose an option, type -the capital letters for this option or click on the option that you -want.

-
- - diff --git a/help/help_en/17_Commandscustomization.htm b/help/help_en/17_Commandscustomization.htm new file mode 100644 index 00000000..ac0ae5c8 --- /dev/null +++ b/help/help_en/17_Commandscustomization.htm @@ -0,0 +1,31 @@ + + + + + + + +Commands customization + + + +
+

Commands +customization

+

It is possible customize +the commands (shortcuts) by a +file named qad_<language>_<region>.pgp +(utf-8).

+

<language> is the +current QGIS language (mandatory) and <region> is the current +linguistic region (optional). For example qad_pt_br.pgp is the file +in portuguese language of region Brazil, qad_en.pgp is the English +version of the pgp file. The file is searched by QAD following the +paths in the system variable SUPPORTPATH.

+
+ + diff --git a/help/help_en/18_ARC.htm b/help/help_en/18_ARC.htm deleted file mode 100644 index 40b89d0e..00000000 --- a/help/help_en/18_ARC.htm +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - -ARC - - - -
-

ARC

-

Draw an arc.

-
- - diff --git a/help/help_en/18_Commands.htm b/help/help_en/18_Commands.htm new file mode 100644 index 00000000..b6043e9b --- /dev/null +++ b/help/help_en/18_Commands.htm @@ -0,0 +1,39 @@ + + + + + + + +Commands + + + +
+

Commands

+

The commands are activated by menu +VECTOR->QAD or toolbar or command line. The commands and their +options can be specified in English by prefixing the character "_" +to the name (e.g. _ LINE) regardless of the language used in +QGIS.

+

QAD command can be +interrupted at any moment by the activation of another tool. To +resume the paused command and make active the QAD environment use +the QAD item in the QAD menu or press the button +"qad" in the +toolbar.

+

As you type the name of a +command QAD will display a list of commands that begin with what +has been written Typing "*" the list of all +QAD commands will +appear.

+

To choose an option, type +the capital letters for this option or click on the option that you +want.

+
+ + diff --git a/help/help_en/19_ARC.htm b/help/help_en/19_ARC.htm new file mode 100644 index 00000000..9cd7a138 --- /dev/null +++ b/help/help_en/19_ARC.htm @@ -0,0 +1,19 @@ + + + + + + + +ARC + + + +
+

ARC

+

Draw an arc.

+
+ + diff --git a/help/help_en/19_BREAK.htm b/help/help_en/19_BREAK.htm deleted file mode 100644 index 961185f1..00000000 --- a/help/help_en/19_BREAK.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -BREAK - - - -
-

BREAK

-

Breaks the selected -object.

-
- - diff --git a/help/help_en/1_QADQuantumAidedDesign.htm b/help/help_en/1_QADQuantumAidedDesign.htm index 96e29205..f9671ff8 100644 --- a/help/help_en/1_QADQuantumAidedDesign.htm +++ b/help/help_en/1_QADQuantumAidedDesign.htm @@ -1,33 +1,35 @@ - - - - - - - -QAD (Quantum Aided Design) - - - -
-

QAD -(Quantum Aided Design)

-
-

 

-
-
-

Introduction

-

The idea of QAD is to add -QGIS of all CAD commands for editing geometries in an professional -way.

-
-

 

-
- - + + + + + + + +QAD (Quantum Aided Design) + + + +
+

QAD (Quantum Aided +Design)

+
+

 

+
+
+

Introduction

+

The idea of QAD is to add +QGIS of all CAD commands for editing geometries in an professional +way.

+
+

 

+
+ + diff --git a/help/help_en/20_ARRAY.htm b/help/help_en/20_ARRAY.htm new file mode 100644 index 00000000..5f427283 --- /dev/null +++ b/help/help_en/20_ARRAY.htm @@ -0,0 +1,20 @@ + + + + + + + +ARRAY + + + +
+

ARRAY

+

Creates copies of objects +arranged in a pattern.

+
+ + diff --git a/help/help_en/20_CIRCLE.htm b/help/help_en/20_CIRCLE.htm deleted file mode 100644 index ce05a193..00000000 --- a/help/help_en/20_CIRCLE.htm +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - -CIRCLE - - - -
-

CIRCLE

-

Draws a circle.

-
- - diff --git a/help/help_en/21_ARRAYPATH.htm b/help/help_en/21_ARRAYPATH.htm new file mode 100644 index 00000000..5e89da18 --- /dev/null +++ b/help/help_en/21_ARRAYPATH.htm @@ -0,0 +1,20 @@ + + + + + + + +ARRAYPATH + + + +
+

ARRAYPATH

+

Evenly distributes object +copies along a path or a portion of a path.

+
+ + diff --git a/help/help_en/21_COPY.htm b/help/help_en/21_COPY.htm deleted file mode 100644 index 10ed2389..00000000 --- a/help/help_en/21_COPY.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -COPY - - - -
-

COPY

-

Copies one or more -objects.

-
- - diff --git a/help/help_en/22_ARRAYPOLAR.htm b/help/help_en/22_ARRAYPOLAR.htm new file mode 100644 index 00000000..fed4b8ae --- /dev/null +++ b/help/help_en/22_ARRAYPOLAR.htm @@ -0,0 +1,20 @@ + + + + + + + +ARRAYPOLAR + + + +
+

ARRAYPOLAR

+

Evenly distributes object +copies in a circular pattern around a center point.

+
+ + diff --git a/help/help_en/22_DIMALIGNED.htm b/help/help_en/22_DIMALIGNED.htm deleted file mode 100644 index 3197b9f6..00000000 --- a/help/help_en/22_DIMALIGNED.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -DIMALIGNED - - - -
-

DIMALIGNED

-

Draws an aligned -dimension.

-
- - diff --git a/help/help_en/23_ARRAYRECT.htm b/help/help_en/23_ARRAYRECT.htm new file mode 100644 index 00000000..9321a7d8 --- /dev/null +++ b/help/help_en/23_ARRAYRECT.htm @@ -0,0 +1,20 @@ + + + + + + + +ARRAYRECT + + + +
+

ARRAYRECT

+

Distributes object copies +into any combination of rows and columns.

+
+ + diff --git a/help/help_en/23_DIMLINEAR.htm b/help/help_en/23_DIMLINEAR.htm deleted file mode 100644 index 387244a8..00000000 --- a/help/help_en/23_DIMLINEAR.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -DIMLINEAR - - - -
-

DIMLINEAR

-

Draws a linear -dimension.

-
- - diff --git a/help/help_en/24_BREAK.htm b/help/help_en/24_BREAK.htm new file mode 100644 index 00000000..1cad00a3 --- /dev/null +++ b/help/help_en/24_BREAK.htm @@ -0,0 +1,20 @@ + + + + + + + +BREAK + + + +
+

BREAK

+

Breaks the selected +object.

+
+ + diff --git a/help/help_en/24_DIMSTYLE.htm b/help/help_en/24_DIMSTYLE.htm deleted file mode 100644 index 5c8c0158..00000000 --- a/help/help_en/24_DIMSTYLE.htm +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - -DIMSTYLE - - - -
-

DIMSTYLE

-

Creates, modifies, compare -dimensioning styles. It sets the current dimensioning -style.

-
- - diff --git a/help/help_en/25_CIRCLE.htm b/help/help_en/25_CIRCLE.htm new file mode 100644 index 00000000..e8628206 --- /dev/null +++ b/help/help_en/25_CIRCLE.htm @@ -0,0 +1,19 @@ + + + + + + + +CIRCLE + + + +
+

CIRCLE

+

Draws a circle.

+
+ + diff --git a/help/help_en/25_DSETTINGS.htm b/help/help_en/25_DSETTINGS.htm deleted file mode 100644 index ac648e52..00000000 --- a/help/help_en/25_DSETTINGS.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -DSETTINGS - - - -
-

DSETTINGS

-

Set some properties to -draw.

-
- - diff --git a/help/help_en/26_COPY.htm b/help/help_en/26_COPY.htm new file mode 100644 index 00000000..28286f65 --- /dev/null +++ b/help/help_en/26_COPY.htm @@ -0,0 +1,20 @@ + + + + + + + +COPY + + + +
+

COPY

+

Copies one or more +objects.

+
+ + diff --git a/help/help_en/26_EDITPL.htm b/help/help_en/26_EDITPL.htm deleted file mode 100644 index cb5fd7db..00000000 --- a/help/help_en/26_EDITPL.htm +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - -EDITPL - - - -
-

EDITPL

-

Modifies a -polyline.

-
- - diff --git a/help/help_en/27_DIMALIGNED.htm b/help/help_en/27_DIMALIGNED.htm new file mode 100644 index 00000000..eb9b7b1d --- /dev/null +++ b/help/help_en/27_DIMALIGNED.htm @@ -0,0 +1,20 @@ + + + + + + + +DIMALIGNED + + + +
+

DIMALIGNED

+

Draws an aligned +dimension.

+
+ + diff --git a/help/help_en/27_ERASE.htm b/help/help_en/27_ERASE.htm deleted file mode 100644 index 9d40df83..00000000 --- a/help/help_en/27_ERASE.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -ERASE - - - -
-

ERASE

-

Erase one or more -objects.

-
- - diff --git a/help/help_en/28_DIMARC.htm b/help/help_en/28_DIMARC.htm new file mode 100644 index 00000000..2278f105 --- /dev/null +++ b/help/help_en/28_DIMARC.htm @@ -0,0 +1,20 @@ + + + + + + + +DIMARC + + + +
+

DIMARC

+

Draws a length arc +dimension.

+
+ + diff --git a/help/help_en/28_EXTEND.htm b/help/help_en/28_EXTEND.htm deleted file mode 100644 index 6540f021..00000000 --- a/help/help_en/28_EXTEND.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -EXTEND - - - -
-

EXTEND

-

Extend one or more -objects..

-
- - diff --git a/help/help_en/29_DIMLINEAR.htm b/help/help_en/29_DIMLINEAR.htm new file mode 100644 index 00000000..43a80537 --- /dev/null +++ b/help/help_en/29_DIMLINEAR.htm @@ -0,0 +1,20 @@ + + + + + + + +DIMLINEAR + + + +
+

DIMLINEAR

+

Draws a linear +dimension.

+
+ + diff --git a/help/help_en/29_FILLET.htm b/help/help_en/29_FILLET.htm deleted file mode 100644 index 45bd293a..00000000 --- a/help/help_en/29_FILLET.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -FILLET - - - -
-

FILLET

-

Rounds and fillets the -edges of existing object.

-
- - diff --git a/help/help_en/30_DIMRADIUS.htm b/help/help_en/30_DIMRADIUS.htm new file mode 100644 index 00000000..567664d2 --- /dev/null +++ b/help/help_en/30_DIMRADIUS.htm @@ -0,0 +1,20 @@ + + + + + + + +DIMRADIUS + + + +
+

DIMRADIUS

+

Draws a radial +dimension.

+
+ + diff --git a/help/help_en/30_HELP.htm b/help/help_en/30_HELP.htm deleted file mode 100644 index 89066afc..00000000 --- a/help/help_en/30_HELP.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -HELP - - - -
-

HELP

-

Displays the QAD -manual.

-
- - diff --git a/help/help_en/31_DIMSTYLE.htm b/help/help_en/31_DIMSTYLE.htm new file mode 100644 index 00000000..b2889fb1 --- /dev/null +++ b/help/help_en/31_DIMSTYLE.htm @@ -0,0 +1,21 @@ + + + + + + + +DIMSTYLE + + + +
+

DIMSTYLE

+

Creates, modifies, compare +dimensioning styles. It sets the current dimensioning +style.

+
+ + diff --git a/help/help_en/31_ID.htm b/help/help_en/31_ID.htm deleted file mode 100644 index 6ac31e85..00000000 --- a/help/help_en/31_ID.htm +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - -ID - - - -
-

ID

-

It shows the coordinate of -the specified position.

-
- - diff --git a/help/help_en/32_DIVIDE.htm b/help/help_en/32_DIVIDE.htm new file mode 100644 index 00000000..ebfb775e --- /dev/null +++ b/help/help_en/32_DIVIDE.htm @@ -0,0 +1,21 @@ + + + + + + + +DIVIDE + + + +
+

DIVIDE

+

Creates evenly spaced +punctual objects along the length or perimeter of an +object.

+
+ + diff --git a/help/help_en/32_INSERT.htm b/help/help_en/32_INSERT.htm deleted file mode 100644 index 60999522..00000000 --- a/help/help_en/32_INSERT.htm +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - -INSERT - - - -
-

INSERT

-

Insert a symbol. If the -symbol scale is derived from a field then the command will ask the -factor scale. If the symbol rotation is derived from a field than -the command will ask the rotation (degree). Only for symbol -layer.

-
- - diff --git a/help/help_en/33_DSETTINGS.htm b/help/help_en/33_DSETTINGS.htm new file mode 100644 index 00000000..80bf800b --- /dev/null +++ b/help/help_en/33_DSETTINGS.htm @@ -0,0 +1,20 @@ + + + + + + + +DSETTINGS + + + +
+

DSETTINGS

+

Set some properties to +draw.

+
+ + diff --git a/help/help_en/33_LENGTHEN.htm b/help/help_en/33_LENGTHEN.htm deleted file mode 100644 index e316bd00..00000000 --- a/help/help_en/33_LENGTHEN.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -LENGTHEN - - - -
-

LENGTHEN

-

Lengthen an -object.

-
- - diff --git a/help/help_en/34_ELLIPSE.htm b/help/help_en/34_ELLIPSE.htm new file mode 100644 index 00000000..698d48a7 --- /dev/null +++ b/help/help_en/34_ELLIPSE.htm @@ -0,0 +1,19 @@ + + + + + + + +ELLIPSE + + + +
+

ELLIPSE

+

Draws ellipse or arc of +ellipse.

+
+ + diff --git a/help/help_en/34_ERASE.htm b/help/help_en/34_ERASE.htm new file mode 100644 index 00000000..4c1addf4 --- /dev/null +++ b/help/help_en/34_ERASE.htm @@ -0,0 +1,20 @@ + + + + + + + +ERASE + + + +
+

ERASE

+

Erases one or more +objects.

+
+ + diff --git a/help/help_en/34_LINE.htm b/help/help_en/34_LINE.htm deleted file mode 100644 index 523d4898..00000000 --- a/help/help_en/34_LINE.htm +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - -LINE - - - -
-

LINE

-

Draws a line.

-
- - diff --git a/help/help_en/35_ERASE.htm b/help/help_en/35_ERASE.htm new file mode 100644 index 00000000..84aedd34 --- /dev/null +++ b/help/help_en/35_ERASE.htm @@ -0,0 +1,20 @@ + + + + + + + +ERASE + + + +
+

ERASE

+

Erases one or more +objects.

+
+ + diff --git a/help/help_en/35_EXTEND.htm b/help/help_en/35_EXTEND.htm new file mode 100644 index 00000000..d89b699a --- /dev/null +++ b/help/help_en/35_EXTEND.htm @@ -0,0 +1,20 @@ + + + + + + + +EXTEND + + + +
+

EXTEND

+

Extends one or more +objects.

+
+ + diff --git a/help/help_en/35_MBUFFER.htm b/help/help_en/35_MBUFFER.htm deleted file mode 100644 index 4cfc68b2..00000000 --- a/help/help_en/35_MBUFFER.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -MBUFFER - - - -
-

MBUFFER

-

Draws a buffer around the -selected objects. Select the objects and specify the buffer -width.

-
- - diff --git a/help/help_en/36_EXTEND.htm b/help/help_en/36_EXTEND.htm new file mode 100644 index 00000000..4cc68d97 --- /dev/null +++ b/help/help_en/36_EXTEND.htm @@ -0,0 +1,20 @@ + + + + + + + +EXTEND + + + +
+

EXTEND

+

Extends one or more +objects.

+
+ + diff --git a/help/help_en/36_FILLET.htm b/help/help_en/36_FILLET.htm new file mode 100644 index 00000000..43ca4d3b --- /dev/null +++ b/help/help_en/36_FILLET.htm @@ -0,0 +1,20 @@ + + + + + + + +FILLET + + + +
+

FILLET

+

Rounds and fillets the +edges of existing object.

+
+ + diff --git a/help/help_en/36_MIRROR.htm b/help/help_en/36_MIRROR.htm deleted file mode 100644 index c37a66ba..00000000 --- a/help/help_en/36_MIRROR.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -MIRROR - - - -
-

MIRROR

-

Creates a mirrored copy of selected objects.

-
- - diff --git a/help/help_en/37_FILLET.htm b/help/help_en/37_FILLET.htm new file mode 100644 index 00000000..dca188a8 --- /dev/null +++ b/help/help_en/37_FILLET.htm @@ -0,0 +1,20 @@ + + + + + + + +FILLET + + + +
+

FILLET

+

Rounds and fillets the +edges of existing object.

+
+ + diff --git a/help/help_en/37_HELP.htm b/help/help_en/37_HELP.htm new file mode 100644 index 00000000..15ac9083 --- /dev/null +++ b/help/help_en/37_HELP.htm @@ -0,0 +1,20 @@ + + + + + + + +HELP + + + +
+

HELP

+

Displays the QAD +manual.

+
+ + diff --git a/help/help_en/37_MOVE.htm b/help/help_en/37_MOVE.htm deleted file mode 100644 index b7ade372..00000000 --- a/help/help_en/37_MOVE.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -MOVE - - - -
-

MOVE

-

Moves the selected -objects.

-
- - diff --git a/help/help_en/38_HELP.htm b/help/help_en/38_HELP.htm new file mode 100644 index 00000000..22bc3521 --- /dev/null +++ b/help/help_en/38_HELP.htm @@ -0,0 +1,20 @@ + + + + + + + +HELP + + + +
+

HELP

+

Displays the QAD +manual.

+
+ + diff --git a/help/help_en/38_ID.htm b/help/help_en/38_ID.htm new file mode 100644 index 00000000..c6b07a53 --- /dev/null +++ b/help/help_en/38_ID.htm @@ -0,0 +1,20 @@ + + + + + + + +ID + + + +
+

ID

+

It shows the coordinate of +the specified position.

+
+ + diff --git a/help/help_en/38_MPOLYGON.htm b/help/help_en/38_MPOLYGON.htm deleted file mode 100644 index 03c4f27e..00000000 --- a/help/help_en/38_MPOLYGON.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -MPOLYGON - - - -
-

MPOLYGON

-

Draws a polygon using the -same options of the PLINE command.

-
- - diff --git a/help/help_en/39_ID.htm b/help/help_en/39_ID.htm new file mode 100644 index 00000000..38762990 --- /dev/null +++ b/help/help_en/39_ID.htm @@ -0,0 +1,20 @@ + + + + + + + +ID + + + +
+

ID

+

It shows the coordinate of +the specified position.

+
+ + diff --git a/help/help_en/39_INSERT.htm b/help/help_en/39_INSERT.htm new file mode 100644 index 00000000..77e5e96f --- /dev/null +++ b/help/help_en/39_INSERT.htm @@ -0,0 +1,23 @@ + + + + + + + +INSERT + + + +
+

INSERT

+

Inserts a symbol. If the +symbol scale is derived from a field then the command will ask the +factor scale. If the symbol rotation is derived from a field than +the command will ask the rotation (degree). Only for symbol +layer.

+
+ + diff --git a/help/help_en/39_OFFSET.htm b/help/help_en/39_OFFSET.htm deleted file mode 100644 index 2086300c..00000000 --- a/help/help_en/39_OFFSET.htm +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - -OFFSET - - - -
-

OFFSET

-

Draws concentric circles, -parallel lines and arcs.

-
- - diff --git a/help/help_en/3_Workphilosophy.htm b/help/help_en/3_Workphilosophy.htm index b4b32181..1eb3fd35 100644 --- a/help/help_en/3_Workphilosophy.htm +++ b/help/help_en/3_Workphilosophy.htm @@ -1,29 +1,36 @@ - - - - - - - -Work philosophy - - - -
-

Work -philosophy

-

QAD has a different logic by -QGIS and closer to popular CAD software.

-

To reduce learning time, QAD -is inspired to the most popular CAD. This manual assumes that you -have already the knowledge of the most popular CAD environment and -commands. Otherwise use the appropriate documentation (there is a -large amount of manuals) or search the command on -internet.

-

The current reference system -of the project must be a projected coordinate system and not a -geographic system.

-
- - + + + + + + + +Work philosophy + + + +
+

Work +philosophy

+

QAD has a different logic by +QGIS and closer to popular CAD software.

+

To reduce learning time, QAD +is inspired to the most popular CAD. This manual assumes that you +have already the knowledge of the most popular CAD environment and +commands. Otherwise use the appropriate documentation (there is a +large amount of manuals) or search the command on +internet.

+

QAD commands haven’t the +same options as those of the most popular +CADsince QGIS context is different +(usually graphical options), some commands have more options. This +manual describes only the options not present in the +corresponding most popular +CADcommands.

+

The current reference system +of the project must be a projected coordinate system and not a +geographic system.

+
+ + diff --git a/help/help_en/40_INSERT.htm b/help/help_en/40_INSERT.htm new file mode 100644 index 00000000..2d6fc473 --- /dev/null +++ b/help/help_en/40_INSERT.htm @@ -0,0 +1,23 @@ + + + + + + + +INSERT + + + +
+

INSERT

+

Inserts a symbol. If the +symbol scale is derived from a field then the command will ask the +factor scale. If the symbol rotation is derived from a field than +the command will ask the rotation (degree). Only for symbol +layer.

+
+ + diff --git a/help/help_en/40_LENGTHEN.htm b/help/help_en/40_LENGTHEN.htm new file mode 100644 index 00000000..f7f77566 --- /dev/null +++ b/help/help_en/40_LENGTHEN.htm @@ -0,0 +1,20 @@ + + + + + + + +LENGTHEN + + + +
+

LENGTHEN

+

Lengthen an +object.

+
+ + diff --git a/help/help_en/40_PLINEA.htm b/help/help_en/40_PLINEA.htm deleted file mode 100644 index 00e325dd..00000000 --- a/help/help_en/40_PLINEA.htm +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - -PLINEA - - - -
-

PLINEA

-

Draws a polyline. The -<Trace> option is used to trace an existing object. During -the digitizing, point to any point of an existing object to trace, -select the <Trace> option and select the same object in the -final trace point.

-
- - diff --git a/help/help_en/41_LENGTHEN.htm b/help/help_en/41_LENGTHEN.htm new file mode 100644 index 00000000..3f65046c --- /dev/null +++ b/help/help_en/41_LENGTHEN.htm @@ -0,0 +1,20 @@ + + + + + + + +LENGTHEN + + + +
+

LENGTHEN

+

Lengthen an +object.

+
+ + diff --git a/help/help_en/41_LINE.htm b/help/help_en/41_LINE.htm new file mode 100644 index 00000000..b6321392 --- /dev/null +++ b/help/help_en/41_LINE.htm @@ -0,0 +1,19 @@ + + + + + + + +LINE + + + +
+

LINE

+

Draws a line.

+
+ + diff --git a/help/help_en/41_POLYGON.htm b/help/help_en/41_POLYGON.htm deleted file mode 100644 index adda59bd..00000000 --- a/help/help_en/41_POLYGON.htm +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - -POLYGON - - - -
-

POLYGON

-

Draws a regular polygon. -After specifing the center, the <Area> option calculate the -polygon.

-
- - diff --git a/help/help_en/42_LINE.htm b/help/help_en/42_LINE.htm new file mode 100644 index 00000000..f0511873 --- /dev/null +++ b/help/help_en/42_LINE.htm @@ -0,0 +1,19 @@ + + + + + + + +LINE + + + +
+

LINE

+

Draws a line.

+
+ + diff --git a/help/help_en/42_MAPMPEDIT.htm b/help/help_en/42_MAPMPEDIT.htm new file mode 100644 index 00000000..5bd6fc4a --- /dev/null +++ b/help/help_en/42_MAPMPEDIT.htm @@ -0,0 +1,45 @@ + + + + + + + +MAPMPEDIT + + + +
+

MAPMPEDIT

+

It modifies the selected +polygon geometry.

+
    +
  • The <Add> option +adds an existing geometry to the selected polygon +(e.g. a +ring).
  • +
  • The <Delete> option +deletes a geometry to the selected polygon (e.g. a ring).
  • +
  • The <Union> option +modifies the geometry of the selected polygon with the result of +the union of the same geometry with a group of polygon.
  • +
  • The <Subtract> +option modifies the geometry of the selected polygon with the +result of the subtraction of the same geometry with a group of +polygon.
  • +
  • The <Intersect> +option modifies the geometry of the selected polygon with the +result of the intersection of the same geometry with a group of +polygon.
  • +
  • The <include Objs> +option modifies the geometry of the selected polygon to include the +geometries of a group of objects.
  • +
  • The <Undo> option +undoes the last operation.
  • +
+
+ + diff --git a/help/help_en/42_RECTANGLE.htm b/help/help_en/42_RECTANGLE.htm deleted file mode 100644 index 364dddaf..00000000 --- a/help/help_en/42_RECTANGLE.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -RECTANGLE - - - -
-

RECTANGLE

-

Draws a -rectangle.

-
- - diff --git a/help/help_en/43_MAPMPEDIT.htm b/help/help_en/43_MAPMPEDIT.htm new file mode 100644 index 00000000..d6f93152 --- /dev/null +++ b/help/help_en/43_MAPMPEDIT.htm @@ -0,0 +1,45 @@ + + + + + + + +MAPMPEDIT + + + +
+

MAPMPEDIT

+

It modifies the selected +polygon geometry.

+
    +
  • The <Add> option +adds an existing geometry to the selected polygon +(e.g. a +ring).
  • +
  • The <Delete> option +deletes a geometry to the selected polygon (e.g. a ring).
  • +
  • The <Union> option +modifies the geometry of the selected polygon with the result of +the union of the same geometry with a group of polygon.
  • +
  • The <Subtract> +option modifies the geometry of the selected polygon with the +result of the subtraction of the same geometry with a group of +polygon.
  • +
  • The <Intersect> +option modifies the geometry of the selected polygon with the +result of the intersection of the same geometry with a group of +polygon.
  • +
  • The <include Objs> +option modifies the geometry of the selected polygon to include the +geometries of a group of objects.
  • +
  • The <Undo> option +undoes the last operation.
  • +
+
+ + diff --git a/help/help_en/43_MBUFFER.htm b/help/help_en/43_MBUFFER.htm new file mode 100644 index 00000000..af4fcb7b --- /dev/null +++ b/help/help_en/43_MBUFFER.htm @@ -0,0 +1,21 @@ + + + + + + + +MBUFFER + + + +
+

MBUFFER

+

Draws a buffer around the +selected objects. Select the objects and specify the buffer +width.

+
+ + diff --git a/help/help_en/43_REDO.htm b/help/help_en/43_REDO.htm deleted file mode 100644 index 3c502ecb..00000000 --- a/help/help_en/43_REDO.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -REDO - - - -
-

REDO

-

Redo the changes undone by -the UNDO command.

-
- - diff --git a/help/help_en/44_MBUFFER.htm b/help/help_en/44_MBUFFER.htm new file mode 100644 index 00000000..0f53f9af --- /dev/null +++ b/help/help_en/44_MBUFFER.htm @@ -0,0 +1,21 @@ + + + + + + + +MBUFFER + + + +
+

MBUFFER

+

Draws a buffer around the +selected objects. Select the objects and specify the buffer +width.

+
+ + diff --git a/help/help_en/44_MEASURE.htm b/help/help_en/44_MEASURE.htm new file mode 100644 index 00000000..38c5dd52 --- /dev/null +++ b/help/help_en/44_MEASURE.htm @@ -0,0 +1,21 @@ + + + + + + + +MEASURE + + + +
+

MEASURE

+

Creates punctual objects at +measured intervals along the length or perimeter of an +object.

+
+ + diff --git a/help/help_en/44_ROTATE.htm b/help/help_en/44_ROTATE.htm deleted file mode 100644 index 3474c34a..00000000 --- a/help/help_en/44_ROTATE.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -ROTATE - - - -
-

ROTATE

-

Rotate the selected -objects.

-
- - diff --git a/help/help_en/45_MEASURE.htm b/help/help_en/45_MEASURE.htm new file mode 100644 index 00000000..0bad8897 --- /dev/null +++ b/help/help_en/45_MEASURE.htm @@ -0,0 +1,21 @@ + + + + + + + +MEASURE + + + +
+

MEASURE

+

Creates punctual objects at +measured intervals along the length or perimeter of an +object.

+
+ + diff --git a/help/help_en/45_MIRROR.htm b/help/help_en/45_MIRROR.htm new file mode 100644 index 00000000..4f4ef3be --- /dev/null +++ b/help/help_en/45_MIRROR.htm @@ -0,0 +1,20 @@ + + + + + + + +MIRROR + + + +
+

MIRROR

+

Creates a mirrored copy of selected objects.

+
+ + diff --git a/help/help_en/45_SCALE.htm b/help/help_en/45_SCALE.htm deleted file mode 100644 index 850daeef..00000000 --- a/help/help_en/45_SCALE.htm +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - -SCALE - - - -
-

SCALE

-

Scale the selected -objects.

-
- - diff --git a/help/help_en/46_MIRROR.htm b/help/help_en/46_MIRROR.htm new file mode 100644 index 00000000..ae1fe2ba --- /dev/null +++ b/help/help_en/46_MIRROR.htm @@ -0,0 +1,20 @@ + + + + + + + +MIRROR + + + +
+

MIRROR

+

Creates a mirrored copy of selected objects.

+
+ + diff --git a/help/help_en/46_MOVE.htm b/help/help_en/46_MOVE.htm new file mode 100644 index 00000000..169c73c1 --- /dev/null +++ b/help/help_en/46_MOVE.htm @@ -0,0 +1,20 @@ + + + + + + + +MOVE + + + +
+

MOVE

+

Moves the selected +objects.

+
+ + diff --git a/help/help_en/46_SETCURRLAYERBYGRAPH.htm b/help/help_en/46_SETCURRLAYERBYGRAPH.htm deleted file mode 100644 index b70f9858..00000000 --- a/help/help_en/46_SETCURRLAYERBYGRAPH.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -SETCURRLAYERBYGRAPH - - - -
-

SETCURRLAYERBYGRAPH

-

Sets the current layer -selecting an object.

-
- - diff --git a/help/help_en/47_MOVE.htm b/help/help_en/47_MOVE.htm new file mode 100644 index 00000000..65486c09 --- /dev/null +++ b/help/help_en/47_MOVE.htm @@ -0,0 +1,20 @@ + + + + + + + +MOVE + + + +
+

MOVE

+

Moves the selected +objects.

+
+ + diff --git a/help/help_en/47_MPOLYGON.htm b/help/help_en/47_MPOLYGON.htm new file mode 100644 index 00000000..9da9e8bd --- /dev/null +++ b/help/help_en/47_MPOLYGON.htm @@ -0,0 +1,20 @@ + + + + + + + +MPOLYGON + + + +
+

MPOLYGON

+

Draws a polygon using the +same options of the PLINE command.

+
+ + diff --git a/help/help_en/47_SETCURRUPDATEABLELAYERBYGRAPH.htm b/help/help_en/47_SETCURRUPDATEABLELAYERBYGRAPH.htm deleted file mode 100644 index cdf06776..00000000 --- a/help/help_en/47_SETCURRUPDATEABLELAYERBYGRAPH.htm +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - -SETCURRUPDATEABLELAYERBYGRAPH - - - -
-

SETCURRUPDATEABLELAYERBYGRAPH

-

Sets edit mode to the -layers of the selected objects. If you specify only one layer it -becomes the current one.

-
- - diff --git a/help/help_en/48_MPOLYGON.htm b/help/help_en/48_MPOLYGON.htm new file mode 100644 index 00000000..cb9e023c --- /dev/null +++ b/help/help_en/48_MPOLYGON.htm @@ -0,0 +1,20 @@ + + + + + + + +MPOLYGON + + + +
+

MPOLYGON

+

Draws a polygon using the +same options of the PLINE command.

+
+ + diff --git a/help/help_en/48_OFFSET.htm b/help/help_en/48_OFFSET.htm new file mode 100644 index 00000000..1b88be84 --- /dev/null +++ b/help/help_en/48_OFFSET.htm @@ -0,0 +1,20 @@ + + + + + + + +OFFSET + + + +
+

OFFSET

+

Draws concentric circles, +parallel lines and arcs.

+
+ + diff --git a/help/help_en/48_SETVAR.htm b/help/help_en/48_SETVAR.htm deleted file mode 100644 index 46f6b1ab..00000000 --- a/help/help_en/48_SETVAR.htm +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - -SETVAR - - - -
-

SETVAR

-

Lists or modifies the -values of QAD variables. Once specified the QAD variable name, a -short decription and the type of the variable value (real, integer, -character, boolean) is shown.

-
- - diff --git a/help/help_en/49_OFFSET.htm b/help/help_en/49_OFFSET.htm new file mode 100644 index 00000000..b1a615f2 --- /dev/null +++ b/help/help_en/49_OFFSET.htm @@ -0,0 +1,20 @@ + + + + + + + +OFFSET + + + +
+

OFFSET

+

Draws concentric circles, +parallel lines and arcs.

+
+ + diff --git a/help/help_en/49_OPTIONS.htm b/help/help_en/49_OPTIONS.htm new file mode 100644 index 00000000..74f08cda --- /dev/null +++ b/help/help_en/49_OPTIONS.htm @@ -0,0 +1,20 @@ + + + + + + + +OPTIONS + + + +
+

OPTIONS

+

Customizes the program +settings.

+
+ + diff --git a/help/help_en/49_STRETCH.htm b/help/help_en/49_STRETCH.htm deleted file mode 100644 index 5f8120a5..00000000 --- a/help/help_en/49_STRETCH.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -STRETCH - - - -
-

STRETCH

-

Stetches the selected -objects.

-
- - diff --git a/help/help_en/4_Layer.htm b/help/help_en/4_Layer.htm index 3cc076bb..78ecc3e3 100644 --- a/help/help_en/4_Layer.htm +++ b/help/help_en/4_Layer.htm @@ -1,35 +1,35 @@ - - - - - - - -Layer - - - -
-

Layer

-

QAD supports all types of -vector layers of QGIS with a distinction regarding the point layer. -In fact, QAD manage layers of QGIS distinguishing between symbols -layers and text layers. The first display symbols while the second -display texts.

-

The text layer is a layer that -displays labels only. It is a QGIS point layer with the following -characteristics:

-
    -
  1. the symbol must have a -minimum of 90% transparency
  2. -
  3. must have a label
  4. -
-

Layers that are not textual -will be considered as symbol layers.

-

 

-
- - + + + + + + + +Layer + + + +
+

Layer

+

QAD supports all types of +vector layers of QGIS with a distinction regarding the point layer. +In fact, QAD manage layers of QGIS distinguishing between symbols +layers and text layers. The first displays symbols while the second +displays texts.

+

The text layer is a layer that +displays labels only. It is a QGIS point layer with the following +characteristics:

+
    +
  1. the symbol must have a +minimum of 90% transparency
  2. +
  3. must have a label
  4. +
+

Layers that are not textual +will be considered as symbol layers.

+

 

+
+ + diff --git a/help/help_en/50_OPTIONS.htm b/help/help_en/50_OPTIONS.htm new file mode 100644 index 00000000..c278a7af --- /dev/null +++ b/help/help_en/50_OPTIONS.htm @@ -0,0 +1,20 @@ + + + + + + + +OPTIONS + + + +
+

OPTIONS

+

Customizes the program +settings.

+
+ + diff --git a/help/help_en/50_PEDIT.htm b/help/help_en/50_PEDIT.htm new file mode 100644 index 00000000..b52a5177 --- /dev/null +++ b/help/help_en/50_PEDIT.htm @@ -0,0 +1,21 @@ + + + + + + + +PEDIT + + + +
+

PEDIT

+

Modifies a polyline. The +<Simplify> option asks for a tolerance value used to simplify +the geometry.

+
+ + diff --git a/help/help_en/50_TEXT.htm b/help/help_en/50_TEXT.htm deleted file mode 100644 index 37336ef0..00000000 --- a/help/help_en/50_TEXT.htm +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - -TEXT - - - -
-

TEXT

-

Inser a text. If the height -text is derived from a field then the command will ask the text -height. If the text rotation is derived from a field then the -command will ask the rotation (degree). At the end the command will -ask the value of the text. Only for textual layer.

-
- - diff --git a/help/help_en/51_PEDIT.htm b/help/help_en/51_PEDIT.htm new file mode 100644 index 00000000..96b6f40e --- /dev/null +++ b/help/help_en/51_PEDIT.htm @@ -0,0 +1,21 @@ + + + + + + + +PEDIT + + + +
+

PEDIT

+

Modifies a polyline. The +<Simplify> option asks for a tolerance value used to simplify +the geometry.

+
+ + diff --git a/help/help_en/51_PLINE.htm b/help/help_en/51_PLINE.htm new file mode 100644 index 00000000..4e3580c1 --- /dev/null +++ b/help/help_en/51_PLINE.htm @@ -0,0 +1,23 @@ + + + + + + + +PLINE + + + +
+

PLINE

+

Draws a polyline. The +<Trace> option is used to trace an existing object. During +the digitizing, point to any point of an existing object to trace, +select the <Trace> option and select the same object in the +final trace point.

+
+ + diff --git a/help/help_en/51_TRIM.htm b/help/help_en/51_TRIM.htm deleted file mode 100644 index a0510c16..00000000 --- a/help/help_en/51_TRIM.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -TRIM - - - -
-

TRIM

-

Trims the selected -objects.

-
- - diff --git a/help/help_en/52_PLINE.htm b/help/help_en/52_PLINE.htm new file mode 100644 index 00000000..cd55e28d --- /dev/null +++ b/help/help_en/52_PLINE.htm @@ -0,0 +1,23 @@ + + + + + + + +PLINE + + + +
+

PLINE

+

Draws a polyline. The +<Trace> option is used to trace an existing object. During +the digitizing, point to any point of an existing object to trace, +select the <Trace> option and select the same object in the +final trace point.

+
+ + diff --git a/help/help_en/52_POLYGON.htm b/help/help_en/52_POLYGON.htm new file mode 100644 index 00000000..26509a44 --- /dev/null +++ b/help/help_en/52_POLYGON.htm @@ -0,0 +1,21 @@ + + + + + + + +POLYGON + + + +
+

POLYGON

+

Draws a regular polygon. +After specifing the center, the <Area> option calculate the +polygon.

+
+ + diff --git a/help/help_en/52_UNDO.htm b/help/help_en/52_UNDO.htm deleted file mode 100644 index b22ea2af..00000000 --- a/help/help_en/52_UNDO.htm +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - -UNDO - - - -
-

UNDO

-

Undo changes made by -QAD.

-

QAD commands that create, -modify or erase objects affect all visible and editable layers, and -not only the current layer as QGIS does. That's why QAD uses its -undo/redo system that operates on all layers involved into QAD -commands

-

If the user will run the -Undo/Redo command of QGIS, QAD will lose alignment with the history -of the changes made by its commands and then the undo/redo stack -will be cleared.

-

 

-
- - diff --git a/help/help_en/53_Gripmode.htm b/help/help_en/53_Gripmode.htm deleted file mode 100644 index 20a9a09f..00000000 --- a/help/help_en/53_Gripmode.htm +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - -Grip mode - - - -
-

Grip mode

-

You can drag grips to -perform any stretch, move, rotate, scale, or mirror -operations.

-

The editing operation you -choose to perform is called a grip mode.

-

Grips are small, -solid-filled squares that are displayed at strategic points on -objects that you have selected with a pointing device. You can drag -these grips to stretch, move, rotate, scale, or mirror objects -quickly.

-

When grips are turned on, -you can select the objects you want to manipulate before entering a -command, and then you can manipulate the objects with the pointing -device.

-

Note: Grips are not -displayed on objects that are on locked layers.

-

To copy the selected -object, press and hold the Ctrl key while you’re manipulating -it.

-

To Edit Objects Using -Grips:

-
    -
  1. Select the object to -edit.
  2. -
  3. Select and move grips to -stretch the object.
    -Note: In the case of some object grips, for example, symbol or text -reference grips, stretch will move the object rather than stretch -it.
  4. -
  5. Press Enter, Spacebar or -right-click to cycle to the move, rotate, scale, or mirror grip -modes.  
  6. -
  7. Hover over a grip to view -and access the multifunctional grip menu (if -available).
  8. -
-

 

-
- - diff --git a/help/help_en/53_POLYGON.htm b/help/help_en/53_POLYGON.htm new file mode 100644 index 00000000..534783ca --- /dev/null +++ b/help/help_en/53_POLYGON.htm @@ -0,0 +1,21 @@ + + + + + + + +POLYGON + + + +
+

POLYGON

+

Draws a regular polygon. +After specifing the center, the <Area> option calculate the +polygon.

+
+ + diff --git a/help/help_en/53_RECTANGLE.htm b/help/help_en/53_RECTANGLE.htm new file mode 100644 index 00000000..13989806 --- /dev/null +++ b/help/help_en/53_RECTANGLE.htm @@ -0,0 +1,20 @@ + + + + + + + +RECTANGLE + + + +
+

RECTANGLE

+

Draws a +rectangle.

+
+ + diff --git a/help/help_en/54_RECTANGLE.htm b/help/help_en/54_RECTANGLE.htm new file mode 100644 index 00000000..238302bd --- /dev/null +++ b/help/help_en/54_RECTANGLE.htm @@ -0,0 +1,20 @@ + + + + + + + +RECTANGLE + + + +
+

RECTANGLE

+

Draws a +rectangle.

+
+ + diff --git a/help/help_en/54_REDO.htm b/help/help_en/54_REDO.htm new file mode 100644 index 00000000..06b46741 --- /dev/null +++ b/help/help_en/54_REDO.htm @@ -0,0 +1,20 @@ + + + + + + + +REDO + + + +
+

REDO

+

Redo the changes undone by +the UNDO command.

+
+ + diff --git a/help/help_en/54_Systemvariables.htm b/help/help_en/54_Systemvariables.htm deleted file mode 100644 index 04d3643d..00000000 --- a/help/help_en/54_Systemvariables.htm +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - -System variables - - - -
-

System variables

-

System variables are settings that control how certain -commands work. They can be integer, real, char, bool or RGB color -type (i.e. “#FF0000”). They are saved and loaded into QAD.INI file -of the current QGIS project folder if exists or in the QAD.INI file -located in the installation folder.

-
- - diff --git a/help/help_en/55_ARCMINSEGMENTQTY.htm b/help/help_en/55_ARCMINSEGMENTQTY.htm deleted file mode 100644 index 4c528d1d..00000000 --- a/help/help_en/55_ARCMINSEGMENTQTY.htm +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - -ARCMINSEGMENTQTY - - - -
-

ARCMINSEGMENTQTY

-

Minimum number of segments -to approximate an arc. Valid values from 4 to 999, integer type, -default value 12.

-
- - diff --git a/help/help_en/55_REDO.htm b/help/help_en/55_REDO.htm new file mode 100644 index 00000000..f6c4e836 --- /dev/null +++ b/help/help_en/55_REDO.htm @@ -0,0 +1,20 @@ + + + + + + + +REDO + + + +
+

REDO

+

Redo the changes undone by +the UNDO command.

+
+ + diff --git a/help/help_en/55_ROTATE.htm b/help/help_en/55_ROTATE.htm new file mode 100644 index 00000000..719c1776 --- /dev/null +++ b/help/help_en/55_ROTATE.htm @@ -0,0 +1,20 @@ + + + + + + + +ROTATE + + + +
+

ROTATE

+

Rotate the selected +objects.

+
+ + diff --git a/help/help_en/56_AUTOSNAP.htm b/help/help_en/56_AUTOSNAP.htm deleted file mode 100644 index f9c01884..00000000 --- a/help/help_en/56_AUTOSNAP.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -AUTOSNAP - - - -
-

AUTOSNAP

-

The same as the -most popular CAD.

-
- - diff --git a/help/help_en/56_ROTATE.htm b/help/help_en/56_ROTATE.htm new file mode 100644 index 00000000..80eca9e8 --- /dev/null +++ b/help/help_en/56_ROTATE.htm @@ -0,0 +1,20 @@ + + + + + + + +ROTATE + + + +
+

ROTATE

+

Rotate the selected +objects.

+
+ + diff --git a/help/help_en/56_SCALE.htm b/help/help_en/56_SCALE.htm new file mode 100644 index 00000000..1aebd700 --- /dev/null +++ b/help/help_en/56_SCALE.htm @@ -0,0 +1,20 @@ + + + + + + + +SCALE + + + +
+

SCALE

+

Scale the selected +objects.

+
+ + diff --git a/help/help_en/57_CIRCLEMINSEGMENTQTY.htm b/help/help_en/57_CIRCLEMINSEGMENTQTY.htm deleted file mode 100644 index 42ce2b32..00000000 --- a/help/help_en/57_CIRCLEMINSEGMENTQTY.htm +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - -CIRCLEMINSEGMENTQTY - - - -
-

CIRCLEMINSEGMENTQTY

-

Minimum number of segments -to approximate a circle. Valid values from 6 to 999, integer type, -default value 12.

-
- - diff --git a/help/help_en/57_SCALE.htm b/help/help_en/57_SCALE.htm new file mode 100644 index 00000000..61e69ce2 --- /dev/null +++ b/help/help_en/57_SCALE.htm @@ -0,0 +1,20 @@ + + + + + + + +SCALE + + + +
+

SCALE

+

Scale the selected +objects.

+
+ + diff --git a/help/help_en/57_SETCURRLAYERBYGRAPH.htm b/help/help_en/57_SETCURRLAYERBYGRAPH.htm new file mode 100644 index 00000000..99458015 --- /dev/null +++ b/help/help_en/57_SETCURRLAYERBYGRAPH.htm @@ -0,0 +1,20 @@ + + + + + + + +SETCURRLAYERBYGRAPH + + + +
+

SETCURRLAYERBYGRAPH

+

Sets the current layer +selecting an object.

+
+ + diff --git a/help/help_en/58_CMDINPUTHISTORYMAX.htm b/help/help_en/58_CMDINPUTHISTORYMAX.htm deleted file mode 100644 index 3dce5fd9..00000000 --- a/help/help_en/58_CMDINPUTHISTORYMAX.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -CMDINPUTHISTORYMAX - - - -
-

CMDINPUTHISTORYMAX

-

The same as the -most popular CAD.

-
- - diff --git a/help/help_en/58_SETCURRLAYERBYGRAPH.htm b/help/help_en/58_SETCURRLAYERBYGRAPH.htm new file mode 100644 index 00000000..7c378aa9 --- /dev/null +++ b/help/help_en/58_SETCURRLAYERBYGRAPH.htm @@ -0,0 +1,20 @@ + + + + + + + +SETCURRLAYERBYGRAPH + + + +
+

SETCURRLAYERBYGRAPH

+

Sets the current layer +selecting an object.

+
+ + diff --git a/help/help_en/58_SETCURRUPDATEABLELAYERBYGRAPH.htm b/help/help_en/58_SETCURRUPDATEABLELAYERBYGRAPH.htm new file mode 100644 index 00000000..13256b4d --- /dev/null +++ b/help/help_en/58_SETCURRUPDATEABLELAYERBYGRAPH.htm @@ -0,0 +1,21 @@ + + + + + + + +SETCURRUPDATEABLELAYERBYGRAPH + + + +
+

SETCURRUPDATEABLELAYERBYGRAPH

+

Sets edit mode to the +layers of the selected objects. If you specify only one layer it +becomes the current one.

+
+ + diff --git a/help/help_en/59_COPYMODE.htm b/help/help_en/59_COPYMODE.htm deleted file mode 100644 index 9f984da7..00000000 --- a/help/help_en/59_COPYMODE.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -COPYMODE - - - -
-

COPYMODE

-

The same as the -most popular CAD.

-
- - diff --git a/help/help_en/59_SETCURRUPDATEABLELAYERBYGRAPH.htm b/help/help_en/59_SETCURRUPDATEABLELAYERBYGRAPH.htm new file mode 100644 index 00000000..c7538590 --- /dev/null +++ b/help/help_en/59_SETCURRUPDATEABLELAYERBYGRAPH.htm @@ -0,0 +1,21 @@ + + + + + + + +SETCURRUPDATEABLELAYERBYGRAPH + + + +
+

SETCURRUPDATEABLELAYERBYGRAPH

+

Sets edit mode to the +layers of the selected objects. If you specify only one layer it +becomes the current one.

+
+ + diff --git a/help/help_en/59_SETVAR.htm b/help/help_en/59_SETVAR.htm new file mode 100644 index 00000000..eb36269a --- /dev/null +++ b/help/help_en/59_SETVAR.htm @@ -0,0 +1,22 @@ + + + + + + + +SETVAR + + + +
+

SETVAR

+

Lists or modifies the +values of QAD variables. Once specified the QAD variable name, a +short decription and the type of the variable value (real, integer, +character, boolean) is shown.

+
+ + diff --git a/help/help_en/5_Textlayermodel.htm b/help/help_en/5_Textlayermodel.htm index be615f9e..f8098bad 100644 --- a/help/help_en/5_Textlayermodel.htm +++ b/help/help_en/5_Textlayermodel.htm @@ -1,48 +1,48 @@ - - - - - - - -Text layer model: - - - -
-

Text layer -model:

-

The text layer must have the -following fields:

-
    -
  • a character field to store -text
  • -
-

Optional fields:

-
    -
  • a real number field to store -the text height (map unit)
  • -
  • a real number field to -store text rotation (degree counterclockwise where zero = -horizontal to the right)
  • -
-

The textual layer must be -defined with labels set as follows:

-
    -
  • the size can be read from -a real number field that stores the text height (in map units, tab -<Labels>-<Text>, if set than the -TEXT command will ask for it)
  • -
  • the rotation can be read -from a real number field that stores text rotation (degree -counterclockwise where zero = horizontal to the right), -<Preserve data rotation values> option enabled (tab -<Labels>-<Placement>, if set -than the TEXT command will ask for it)
  • -
-

 

-
- - + + + + + + + +Text layer model: + + + +
+

Text layer +model:

+

The text layer must have the +following fields:

+
    +
  • a character field to store +text
  • +
+

Optional fields:

+
    +
  • a real number field to store +the text height (map unit)
  • +
  • a real number field to +store text rotation (degree counterclockwise where zero = +horizontal to the right)
  • +
+

The textual layer must be +defined with labels set as follows:

+
    +
  • the size can be read from +a real number field that stores the text height (in map units, tab +<Labels>-<Text>, if set than the +TEXT command will ask for it)
  • +
  • the rotation can be read +from a real number field that stores text rotation (degree +counterclockwise where zero = horizontal to the right), +<Preserve data rotation values> option enabled (tab +<Labels>-<Placement>, if set +than the TEXT command will ask for it)
  • +
+

 

+
+ + diff --git a/help/help_en/60_CROSSINGAREACOLOR.htm b/help/help_en/60_CROSSINGAREACOLOR.htm deleted file mode 100644 index 9df3b829..00000000 --- a/help/help_en/60_CROSSINGAREACOLOR.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -CROSSINGAREACOLOR - - - -
-

CROSSINGAREACOLOR

-

The same as the -most popular CAD.

-
- - diff --git a/help/help_en/60_SETVAR.htm b/help/help_en/60_SETVAR.htm new file mode 100644 index 00000000..3a651025 --- /dev/null +++ b/help/help_en/60_SETVAR.htm @@ -0,0 +1,22 @@ + + + + + + + +SETVAR + + + +
+

SETVAR

+

Lists or modifies the +values of QAD variables. Once specified the QAD variable name, a +short decription and the type of the variable value (real, integer, +character, boolean) is shown.

+
+ + diff --git a/help/help_en/60_STRETCH.htm b/help/help_en/60_STRETCH.htm new file mode 100644 index 00000000..44e81729 --- /dev/null +++ b/help/help_en/60_STRETCH.htm @@ -0,0 +1,20 @@ + + + + + + + +STRETCH + + + +
+

STRETCH

+

Stetches the selected +objects.

+
+ + diff --git a/help/help_en/61_CURSORCOLOR.htm b/help/help_en/61_CURSORCOLOR.htm deleted file mode 100644 index cf639555..00000000 --- a/help/help_en/61_CURSORCOLOR.htm +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - -CURSORCOLOR - - - -
-

CURSORCOLOR

-

Cross pointer color. Valid -values are valid RGB colors, color type, default value red -=“#FF0000”.

-
- - diff --git a/help/help_en/61_STRETCH.htm b/help/help_en/61_STRETCH.htm new file mode 100644 index 00000000..b0c4af62 --- /dev/null +++ b/help/help_en/61_STRETCH.htm @@ -0,0 +1,20 @@ + + + + + + + +STRETCH + + + +
+

STRETCH

+

Stetches the selected +objects.

+
+ + diff --git a/help/help_en/61_TEXT.htm b/help/help_en/61_TEXT.htm new file mode 100644 index 00000000..4bf25a85 --- /dev/null +++ b/help/help_en/61_TEXT.htm @@ -0,0 +1,23 @@ + + + + + + + +TEXT + + + +
+

TEXT

+

Inser a text. If the height +text is derived from a field then the command will ask the text +height. If the text rotation is derived from a field then the +command will ask the rotation (degree). At the end the command will +ask the value of the text. Only for textual layer.

+
+ + diff --git a/help/help_en/62_CURSORSIZE.htm b/help/help_en/62_CURSORSIZE.htm deleted file mode 100644 index 42b50aef..00000000 --- a/help/help_en/62_CURSORSIZE.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -CURSORSIZE - - - -
-

CURSORSIZE

-

The same as the -most popular CAD.

-
- - diff --git a/help/help_en/62_TEXT.htm b/help/help_en/62_TEXT.htm new file mode 100644 index 00000000..40548111 --- /dev/null +++ b/help/help_en/62_TEXT.htm @@ -0,0 +1,23 @@ + + + + + + + +TEXT + + + +
+

TEXT

+

Inser a text. If the height +text is derived from a field then the command will ask the text +height. If the text rotation is derived from a field then the +command will ask the rotation (degree). At the end the command will +ask the value of the text. Only for textual layer.

+
+ + diff --git a/help/help_en/62_TRIM.htm b/help/help_en/62_TRIM.htm new file mode 100644 index 00000000..84ca5468 --- /dev/null +++ b/help/help_en/62_TRIM.htm @@ -0,0 +1,20 @@ + + + + + + + +TRIM + + + +
+

TRIM

+

Trims the selected +objects.

+
+ + diff --git a/help/help_en/63_DIMSTYLE.htm b/help/help_en/63_DIMSTYLE.htm deleted file mode 100644 index e8d370f3..00000000 --- a/help/help_en/63_DIMSTYLE.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -DIMSTYLE - - - -
-

DIMSTYLE

-

The same as the -most popular CAD.

-
- - diff --git a/help/help_en/63_TRIM.htm b/help/help_en/63_TRIM.htm new file mode 100644 index 00000000..04e9c2e2 --- /dev/null +++ b/help/help_en/63_TRIM.htm @@ -0,0 +1,20 @@ + + + + + + + +TRIM + + + +
+

TRIM

+

Trims the selected +objects.

+
+ + diff --git a/help/help_en/63_UNDO.htm b/help/help_en/63_UNDO.htm new file mode 100644 index 00000000..c068d41f --- /dev/null +++ b/help/help_en/63_UNDO.htm @@ -0,0 +1,32 @@ + + + + + + + +UNDO + + + +
+

UNDO

+

Undo changes made by +QAD.

+

QAD commands that create, +modify or erase objects affect all visible and editable layers, and +not only the current layer as QGIS does. That's why QAD uses its +undo/redo system that operates on all layers involved into QAD +commands

+

If the user will run the +Undo/Redo command of QGIS, QAD will lose alignment with the history +of the changes made by its commands and then the undo/redo stack +will be cleared.

+

 

+
+ + diff --git a/help/help_en/64_EDGEMODE.htm b/help/help_en/64_EDGEMODE.htm deleted file mode 100644 index e02baab4..00000000 --- a/help/help_en/64_EDGEMODE.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -EDGEMODE - - - -
-

EDGEMODE

-

The same as the -most popular CAD.

-
- - diff --git a/help/help_en/64_Gripmode.htm b/help/help_en/64_Gripmode.htm new file mode 100644 index 00000000..e6ad4386 --- /dev/null +++ b/help/help_en/64_Gripmode.htm @@ -0,0 +1,58 @@ + + + + + + + +Grip mode + + + +
+

Grip mode

+

You can drag grips to +perform any stretch, move, rotate, scale, or mirror +operations.

+

The editing operation you +choose to perform is called a grip mode.

+

Grips are small, +solid-filled squares that are displayed at strategic points on +objects that you have selected with a pointing device. You can drag +these grips to stretch, move, rotate, scale, or mirror objects +quickly.

+

When grips are turned on, +you can select the objects you want to manipulate before entering a +command, and then you can manipulate the objects with the pointing +device.

+

Note: Grips are not +displayed on objects that are on locked layers.

+

To copy the selected +object, press and hold the Ctrl key while you’re manipulating +it.

+

To Edit Objects Using +Grips:

+
    +
  1. Select the object to +edit.
  2. +
  3. Select and move grips to +stretch the object.
    +Note: In the case of some object grips, for example, symbol or text +reference grips, stretch will move the object rather than stretch +it.
  4. +
  5. Press Enter, Spacebar or +right-click to cycle to the move, rotate, scale, or mirror grip +modes.  
  6. +
  7. Hover over a grip to view +and access the multifunctional grip menu (if +available).
  8. +
+

 

+
+ + diff --git a/help/help_en/64_UNDO.htm b/help/help_en/64_UNDO.htm new file mode 100644 index 00000000..8c5555c4 --- /dev/null +++ b/help/help_en/64_UNDO.htm @@ -0,0 +1,32 @@ + + + + + + + +UNDO + + + +
+

UNDO

+

Undo changes made by +QAD.

+

QAD commands that create, +modify or erase objects affect all visible and editable layers, and +not only the current layer as QGIS does. That's why QAD uses its +undo/redo system that operates on all layers involved into QAD +commands

+

If the user will run the +Undo/Redo command of QGIS, QAD will lose alignment with the history +of the changes made by its commands and then the undo/redo stack +will be cleared.

+

 

+
+ + diff --git a/help/help_en/65_FILLETRAD.htm b/help/help_en/65_FILLETRAD.htm deleted file mode 100644 index 925cbd66..00000000 --- a/help/help_en/65_FILLETRAD.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -FILLETRAD - - - -
-

FILLETRAD

-

The same as the -most popular CAD.

-
- - diff --git a/help/help_en/65_Gripmode.htm b/help/help_en/65_Gripmode.htm new file mode 100644 index 00000000..ca8dbb74 --- /dev/null +++ b/help/help_en/65_Gripmode.htm @@ -0,0 +1,58 @@ + + + + + + + +Grip mode + + + +
+

Grip mode

+

You can drag grips to +perform any stretch, move, rotate, scale, or mirror +operations.

+

The editing operation you +choose to perform is called a grip mode.

+

Grips are small, +solid-filled squares that are displayed at strategic points on +objects that you have selected with a pointing device. You can drag +these grips to stretch, move, rotate, scale, or mirror objects +quickly.

+

When grips are turned on, +you can select the objects you want to manipulate before entering a +command, and then you can manipulate the objects with the pointing +device.

+

Note: Grips are not +displayed on objects that are on locked layers.

+

To copy the selected +object, press and hold the Ctrl key while you’re manipulating +it.

+

To Edit Objects Using +Grips:

+
    +
  1. Select the object to +edit.
  2. +
  3. Select and move grips to +stretch the object.
    +Note: In the case of some object grips, for example, symbol or text +reference grips, stretch will move the object rather than stretch +it.
  4. +
  5. Press Enter, Spacebar or +right-click to cycle to the move, rotate, scale, or mirror grip +modes.  
  6. +
  7. Hover over a grip to view +and access the multifunctional grip menu (if +available).
  8. +
+

 

+
+ + diff --git a/help/help_en/65_Systemvariables.htm b/help/help_en/65_Systemvariables.htm new file mode 100644 index 00000000..3dec7003 --- /dev/null +++ b/help/help_en/65_Systemvariables.htm @@ -0,0 +1,32 @@ + + + + + + + +System variables + + + +
+

System variables

+

System variables are settings that control how certain +commands work. They can be integer, real, char, bool or RGB color +type (i.e. “#FF0000”).

+

A variable is called “global” when its value doesn’t change +when the current project change. These variables are saved and +loaded into the QAD.INI file located in the installation +folder.

+

A variable is called “project” when its value change when +the current project change. These variables are saved and loaded +into <current project name>_QAD.INI file of the current QGIS +project folder.

+
+ + diff --git a/help/help_en/66_APBOX.htm b/help/help_en/66_APBOX.htm new file mode 100644 index 00000000..bce1d278 --- /dev/null +++ b/help/help_en/66_APBOX.htm @@ -0,0 +1,21 @@ + + + + + + + +APBOX + + + +
+

APBOX

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/66_GRIPCOLOR.htm b/help/help_en/66_GRIPCOLOR.htm deleted file mode 100644 index 1d5e549d..00000000 --- a/help/help_en/66_GRIPCOLOR.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -GRIPCOLOR - - - -
-

GRIPCOLOR

-

The same as the -most popular CAD.

-
- - diff --git a/help/help_en/66_Systemvariables.htm b/help/help_en/66_Systemvariables.htm new file mode 100644 index 00000000..c6e08649 --- /dev/null +++ b/help/help_en/66_Systemvariables.htm @@ -0,0 +1,32 @@ + + + + + + + +System variables + + + +
+

System variables

+

System variables are settings that control how certain +commands work. They can be integer, real, char, bool or RGB color +type (i.e. “#FF0000”).

+

A variable is called “global” when its value doesn’t change +when the current project change. These variables are saved and +loaded into the QAD.INI file located in the installation +folder.

+

A variable is called “project” when its value change when +the current project change. These variables are saved and loaded +into <current project name>_QAD.INI file of the current QGIS +project folder.

+
+ + diff --git a/help/help_en/67_APBOX.htm b/help/help_en/67_APBOX.htm new file mode 100644 index 00000000..f6e99a6d --- /dev/null +++ b/help/help_en/67_APBOX.htm @@ -0,0 +1,21 @@ + + + + + + + +APBOX + + + +
+

APBOX

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/67_APERTURE.htm b/help/help_en/67_APERTURE.htm new file mode 100644 index 00000000..e4bcfbf2 --- /dev/null +++ b/help/help_en/67_APERTURE.htm @@ -0,0 +1,21 @@ + + + + + + + +APERTURE + + + +
+

APERTURE

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/67_GRIPCONTOUR.htm b/help/help_en/67_GRIPCONTOUR.htm deleted file mode 100644 index 43267416..00000000 --- a/help/help_en/67_GRIPCONTOUR.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -GRIPCONTOUR - - - -
-

GRIPCONTOUR

-

The same as the -most popular CAD.

-
- - diff --git a/help/help_en/68_APERTURE.htm b/help/help_en/68_APERTURE.htm new file mode 100644 index 00000000..77282b54 --- /dev/null +++ b/help/help_en/68_APERTURE.htm @@ -0,0 +1,21 @@ + + + + + + + +APERTURE + + + +
+

APERTURE

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/68_ARCMINSEGMENTQTY.htm b/help/help_en/68_ARCMINSEGMENTQTY.htm new file mode 100644 index 00000000..979a5a5b --- /dev/null +++ b/help/help_en/68_ARCMINSEGMENTQTY.htm @@ -0,0 +1,21 @@ + + + + + + + +ARCMINSEGMENTQTY + + + +
+

ARCMINSEGMENTQTY

+

Minimum number of segments +to approximate an arc. Valid values from 4 to 999, integer type, +default value 12. Project variable.

+
+ + diff --git a/help/help_en/68_GRIPHOT.htm b/help/help_en/68_GRIPHOT.htm deleted file mode 100644 index 5284c764..00000000 --- a/help/help_en/68_GRIPHOT.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -GRIPHOT - - - -
-

GRIPHOT

-

The same as the -most popular CAD.

-
- - diff --git a/help/help_en/69_ARCMINSEGMENTQTY.htm b/help/help_en/69_ARCMINSEGMENTQTY.htm new file mode 100644 index 00000000..050d8ec4 --- /dev/null +++ b/help/help_en/69_ARCMINSEGMENTQTY.htm @@ -0,0 +1,21 @@ + + + + + + + +ARCMINSEGMENTQTY + + + +
+

ARCMINSEGMENTQTY

+

Minimum number of segments +to approximate an arc. Valid values from 4 to 999, integer type, +default value 12. Project variable.

+
+ + diff --git a/help/help_en/69_AUTOSNAP.htm b/help/help_en/69_AUTOSNAP.htm new file mode 100644 index 00000000..91215859 --- /dev/null +++ b/help/help_en/69_AUTOSNAP.htm @@ -0,0 +1,21 @@ + + + + + + + +AUTOSNAP + + + +
+

AUTOSNAP

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/69_GRIPOVER.htm b/help/help_en/69_GRIPOVER.htm deleted file mode 100644 index c4a770e4..00000000 --- a/help/help_en/69_GRIPOVER.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -GRIPOVER - - - -
-

GRIPOVER

-

The same as the -most popular CAD.

-
- - diff --git a/help/help_en/6_Symbollayermodel.htm b/help/help_en/6_Symbollayermodel.htm index fcbe6de6..5c00378f 100644 --- a/help/help_en/6_Symbollayermodel.htm +++ b/help/help_en/6_Symbollayermodel.htm @@ -1,56 +1,56 @@ - - - - - - - -Symbol layer model: - - - -
-

Symbol layer -model:

-

The symbol layers may have the -following optional fields:

-
    -
  • a real number field to store -the symbol rotation (degree -counterclockwise where zero = horizontal to the right)
  • -
  • a real number field to store -the symbol scale
  • -
-

The symbol layer can be -defined with a style set as follows:

-
    -
  • If you decide to handle the -rotation or scale of symbols then the <Style>-<Single -Symbol> option and <Style>-<map units> option must -be enabled
  • -
  • The rotation could be read by -a real number field that stores the symbol rotation through the -formula "360-<field that stores the rotation>" -(degree counterclockwise where zero = -horizontal to the right, <Style>-<Advanced> option "rotation field -name"-<Espressione>, -if set than the INSERT command will ask for -it)
  • -
  • The scale can be read by a -real number field that stores the scale of the symbol -(<Style>-<Advanced>-<Size scale field>-“ -field that stores the scale” and <Style>-< Advanced ->-<Size scale field >-<Scale diameter>, -if set than the INSERT command will ask for -it)
  • -
-

 

-
- - + + + + + + + +Symbol layer model: + + + +
+

Symbol layer +model:

+

The symbol layers may have the +following optional fields:

+
    +
  • a real number field to store +the symbol rotation (degree +counterclockwise where zero = horizontal to the right)
  • +
  • a real number field to store +the symbol scale
  • +
+

The symbol layer can be +defined with a style set as follows:

+
    +
  • If you decide to handle the +rotation or scale of symbols then the <Style>-<Single +Symbol> option and <Style>-<map units> option must +be enabled
  • +
  • The rotation could be read by +a real number field that stores the symbol rotation through the +formula "360-<field that stores the rotation>" +(degree counterclockwise where zero = +horizontal to the right, <Style>-<Advanced> option "rotation field +name"-<Espressione>, +if set than the INSERT command will ask for +it)
  • +
  • The scale can be read by a +real number field that stores the scale of the symbol +(<Style>-<Advanced>-<Size scale field>-“ +field that stores the scale” and <Style>-< Advanced +>-<Size scale field >-<Scale diameter>, +if set than the INSERT command will ask for +it)
  • +
+

 

+
+ + diff --git a/help/help_en/70_AUTOSNAP.htm b/help/help_en/70_AUTOSNAP.htm new file mode 100644 index 00000000..6175f07a --- /dev/null +++ b/help/help_en/70_AUTOSNAP.htm @@ -0,0 +1,21 @@ + + + + + + + +AUTOSNAP + + + +
+

AUTOSNAP

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/70_AUTOSNAPCOLOR.htm b/help/help_en/70_AUTOSNAPCOLOR.htm new file mode 100644 index 00000000..297895e8 --- /dev/null +++ b/help/help_en/70_AUTOSNAPCOLOR.htm @@ -0,0 +1,20 @@ + + + + + + + +AUTOSNAPCOLOR + + + +
+

AUTOSNAPCOLOR

+

Color of the snap markers. +Global variable.

+
+ + diff --git a/help/help_en/70_GRIPS.htm b/help/help_en/70_GRIPS.htm deleted file mode 100644 index eda55a31..00000000 --- a/help/help_en/70_GRIPS.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -GRIPS - - - -
-

GRIPS

-

The same as the -most popular CAD.

-
- - diff --git a/help/help_en/71_AUTOSNAPCOLOR.htm b/help/help_en/71_AUTOSNAPCOLOR.htm new file mode 100644 index 00000000..696072c9 --- /dev/null +++ b/help/help_en/71_AUTOSNAPCOLOR.htm @@ -0,0 +1,20 @@ + + + + + + + +AUTOSNAPCOLOR + + + +
+

AUTOSNAPCOLOR

+

Color of the snap markers. +Global variable.

+
+ + diff --git a/help/help_en/71_AUTOSNAPSIZE.htm b/help/help_en/71_AUTOSNAPSIZE.htm new file mode 100644 index 00000000..2a360874 --- /dev/null +++ b/help/help_en/71_AUTOSNAPSIZE.htm @@ -0,0 +1,20 @@ + + + + + + + +AUTOSNAPSIZE + + + +
+

AUTOSNAPSIZE

+

Dimension of the snap +markers in pixel. Global variable.

+
+ + diff --git a/help/help_en/71_GRIPSIZE.htm b/help/help_en/71_GRIPSIZE.htm deleted file mode 100644 index a6ecf693..00000000 --- a/help/help_en/71_GRIPSIZE.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -GRIPSIZE - - - -
-

GRIPSIZE

-

The same as the -most popular CAD.

-
- - diff --git a/help/help_en/72_AUTOSNAPSIZE.htm b/help/help_en/72_AUTOSNAPSIZE.htm new file mode 100644 index 00000000..9a64cb54 --- /dev/null +++ b/help/help_en/72_AUTOSNAPSIZE.htm @@ -0,0 +1,20 @@ + + + + + + + +AUTOSNAPSIZE + + + +
+

AUTOSNAPSIZE

+

Dimension of the snap +markers in pixel. Global variable.

+
+ + diff --git a/help/help_en/72_AUTOTRACKINGVECTORCOLOR.htm b/help/help_en/72_AUTOTRACKINGVECTORCOLOR.htm new file mode 100644 index 00000000..6e505e6e --- /dev/null +++ b/help/help_en/72_AUTOTRACKINGVECTORCOLOR.htm @@ -0,0 +1,20 @@ + + + + + + + +AUTOTRACKINGVECTORCOLOR + + + +
+

AUTOTRACKINGVECTORCOLOR

+

Color of the autotrack +vector. Global variable.

+
+ + diff --git a/help/help_en/72_INPUTSEARCHDELAY.htm b/help/help_en/72_INPUTSEARCHDELAY.htm deleted file mode 100644 index bdf78d33..00000000 --- a/help/help_en/72_INPUTSEARCHDELAY.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -INPUTSEARCHDELAY - - - -
-

INPUTSEARCHDELAY

-

The same as the -most popular CAD.

-
- - diff --git a/help/help_en/73_AUTOTRACKINGVECTORCOLOR.htm b/help/help_en/73_AUTOTRACKINGVECTORCOLOR.htm new file mode 100644 index 00000000..3217f228 --- /dev/null +++ b/help/help_en/73_AUTOTRACKINGVECTORCOLOR.htm @@ -0,0 +1,20 @@ + + + + + + + +AUTOTRACKINGVECTORCOLOR + + + +
+

AUTOTRACKINGVECTORCOLOR

+

Color of the autotrack +vector. Global variable.

+
+ + diff --git a/help/help_en/73_CIRCLEMINSEGMENTQTY.htm b/help/help_en/73_CIRCLEMINSEGMENTQTY.htm new file mode 100644 index 00000000..2b73bc18 --- /dev/null +++ b/help/help_en/73_CIRCLEMINSEGMENTQTY.htm @@ -0,0 +1,21 @@ + + + + + + + +CIRCLEMINSEGMENTQTY + + + +
+

CIRCLEMINSEGMENTQTY

+

Minimum number of segments +to approximate a circle. Valid values from 6 to 999, integer type, +default value 12. Project variable.

+
+ + diff --git a/help/help_en/73_INPUTSEARCHOPTIONS.htm b/help/help_en/73_INPUTSEARCHOPTIONS.htm deleted file mode 100644 index d667d037..00000000 --- a/help/help_en/73_INPUTSEARCHOPTIONS.htm +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - -INPUTSEARCHOPTIONS - - - -
-

INPUTSEARCHOPTIONS

-

The same as -AUTOCOMPLETEMODE system variable of the most -popular CAD.

-
- - diff --git a/help/help_en/74_CIRCLEMINSEGMENTQTY.htm b/help/help_en/74_CIRCLEMINSEGMENTQTY.htm new file mode 100644 index 00000000..88e9691e --- /dev/null +++ b/help/help_en/74_CIRCLEMINSEGMENTQTY.htm @@ -0,0 +1,21 @@ + + + + + + + +CIRCLEMINSEGMENTQTY + + + +
+

CIRCLEMINSEGMENTQTY

+

Minimum number of segments +to approximate a circle. Valid values from 6 to 999, integer type, +default value 12. Project variable.

+
+ + diff --git a/help/help_en/74_CMDHISTORYBACKCOLOR.htm b/help/help_en/74_CMDHISTORYBACKCOLOR.htm new file mode 100644 index 00000000..b4e87547 --- /dev/null +++ b/help/help_en/74_CMDHISTORYBACKCOLOR.htm @@ -0,0 +1,20 @@ + + + + + + + +CMDHISTORYBACKCOLOR + + + +
+

CMDHISTORYBACKCOLOR

+

Command history background +color. Global variable.

+
+ + diff --git a/help/help_en/74_OFFSETDIST.htm b/help/help_en/74_OFFSETDIST.htm deleted file mode 100644 index 9eb32920..00000000 --- a/help/help_en/74_OFFSETDIST.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -OFFSETDIST - - - -
-

OFFSETDIST

-

The same as the -most popular CAD.

-
- - diff --git a/help/help_en/75_CMDHISTORYBACKCOLOR.htm b/help/help_en/75_CMDHISTORYBACKCOLOR.htm new file mode 100644 index 00000000..71ed48de --- /dev/null +++ b/help/help_en/75_CMDHISTORYBACKCOLOR.htm @@ -0,0 +1,20 @@ + + + + + + + +CMDHISTORYBACKCOLOR + + + +
+

CMDHISTORYBACKCOLOR

+

Command history background +color. Global variable.

+
+ + diff --git a/help/help_en/75_CMDHISTORYFORECOLOR.htm b/help/help_en/75_CMDHISTORYFORECOLOR.htm new file mode 100644 index 00000000..17afb705 --- /dev/null +++ b/help/help_en/75_CMDHISTORYFORECOLOR.htm @@ -0,0 +1,20 @@ + + + + + + + +CMDHISTORYFORECOLOR + + + +
+

CMDHISTORYFORECOLOR

+

Command history text color. +Global variable.

+
+ + diff --git a/help/help_en/75_OFFSETGAPTYPE.htm b/help/help_en/75_OFFSETGAPTYPE.htm deleted file mode 100644 index a9ecde44..00000000 --- a/help/help_en/75_OFFSETGAPTYPE.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -OFFSETGAPTYPE - - - -
-

OFFSETGAPTYPE

-

The same as the -most popular CAD.

-
- - diff --git a/help/help_en/76_CMDHISTORYFORECOLOR.htm b/help/help_en/76_CMDHISTORYFORECOLOR.htm new file mode 100644 index 00000000..892a04dc --- /dev/null +++ b/help/help_en/76_CMDHISTORYFORECOLOR.htm @@ -0,0 +1,20 @@ + + + + + + + +CMDHISTORYFORECOLOR + + + +
+

CMDHISTORYFORECOLOR

+

Command history text color. +Global variable.

+
+ + diff --git a/help/help_en/76_CMDINPUTHISTORYMAX.htm b/help/help_en/76_CMDINPUTHISTORYMAX.htm new file mode 100644 index 00000000..bb52b9a5 --- /dev/null +++ b/help/help_en/76_CMDINPUTHISTORYMAX.htm @@ -0,0 +1,21 @@ + + + + + + + +CMDINPUTHISTORYMAX + + + +
+

CMDINPUTHISTORYMAX

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/76_ORTHOMODE.htm b/help/help_en/76_ORTHOMODE.htm deleted file mode 100644 index 17ce71fe..00000000 --- a/help/help_en/76_ORTHOMODE.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -ORTHOMODE - - - -
-

ORTHOMODE

-

The same as the -most popular CAD.

-
- - diff --git a/help/help_en/77_CMDINPUTHISTORYMAX.htm b/help/help_en/77_CMDINPUTHISTORYMAX.htm new file mode 100644 index 00000000..c7deca3e --- /dev/null +++ b/help/help_en/77_CMDINPUTHISTORYMAX.htm @@ -0,0 +1,21 @@ + + + + + + + +CMDINPUTHISTORYMAX + + + +
+

CMDINPUTHISTORYMAX

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/77_CMDLINEBACKCOLOR.htm b/help/help_en/77_CMDLINEBACKCOLOR.htm new file mode 100644 index 00000000..5a4bd60d --- /dev/null +++ b/help/help_en/77_CMDLINEBACKCOLOR.htm @@ -0,0 +1,20 @@ + + + + + + + +CMDLINEBACKCOLOR + + + +
+

CMDLINEBACKCOLOR

+

Active prompt background +color. Global variable.

+
+ + diff --git a/help/help_en/77_OSCOLOR.htm b/help/help_en/77_OSCOLOR.htm deleted file mode 100644 index 54c3b53a..00000000 --- a/help/help_en/77_OSCOLOR.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -OSCOLOR - - - -
-

OSCOLOR

-

The same as the -most popular CAD.

-
- - diff --git a/help/help_en/78_CMDLINEBACKCOLOR.htm b/help/help_en/78_CMDLINEBACKCOLOR.htm new file mode 100644 index 00000000..f088ae84 --- /dev/null +++ b/help/help_en/78_CMDLINEBACKCOLOR.htm @@ -0,0 +1,20 @@ + + + + + + + +CMDLINEBACKCOLOR + + + +
+

CMDLINEBACKCOLOR

+

Active prompt background +color. Global variable.

+
+ + diff --git a/help/help_en/78_CMDLINEFORECOLOR.htm b/help/help_en/78_CMDLINEFORECOLOR.htm new file mode 100644 index 00000000..74dcb161 --- /dev/null +++ b/help/help_en/78_CMDLINEFORECOLOR.htm @@ -0,0 +1,20 @@ + + + + + + + +CMDLINEFORECOLOR + + + +
+

CMDLINEFORECOLOR

+

Active prompt color. Global +variable.

+
+ + diff --git a/help/help_en/78_OSMODE.htm b/help/help_en/78_OSMODE.htm deleted file mode 100644 index f39e2791..00000000 --- a/help/help_en/78_OSMODE.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -OSMODE - - - -
-

OSMODE

-

The same as the -most popular CAD.

-
- - diff --git a/help/help_en/79_CMDLINEFORECOLOR.htm b/help/help_en/79_CMDLINEFORECOLOR.htm new file mode 100644 index 00000000..14ad4073 --- /dev/null +++ b/help/help_en/79_CMDLINEFORECOLOR.htm @@ -0,0 +1,20 @@ + + + + + + + +CMDLINEFORECOLOR + + + +
+

CMDLINEFORECOLOR

+

Active prompt color. Global +variable.

+
+ + diff --git a/help/help_en/79_CMDLINEOPTBACKCOLOR.htm b/help/help_en/79_CMDLINEOPTBACKCOLOR.htm new file mode 100644 index 00000000..60042de1 --- /dev/null +++ b/help/help_en/79_CMDLINEOPTBACKCOLOR.htm @@ -0,0 +1,20 @@ + + + + + + + +CMDLINEOPTBACKCOLOR + + + +
+

CMDLINEOPTBACKCOLOR

+

Command option keyword +background color. Global variable.

+
+ + diff --git a/help/help_en/79_OSPROGRDISTANCE.htm b/help/help_en/79_OSPROGRDISTANCE.htm deleted file mode 100644 index 380e6c2d..00000000 --- a/help/help_en/79_OSPROGRDISTANCE.htm +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - -OSPROGRDISTANCE - - - -
-

OSPROGRDISTANCE

-

Progressive distance for -<Progressive distance> snap mode. Real type, default value -0.

-
- - diff --git a/help/help_en/7_Arcsandcircles.htm b/help/help_en/7_Arcsandcircles.htm index 53255395..b9c5af8e 100644 --- a/help/help_en/7_Arcsandcircles.htm +++ b/help/help_en/7_Arcsandcircles.htm @@ -1,41 +1,41 @@ - - - - - - - -Arcs and circles - - - -
-

Arcs and -circles

-

QAD supports approximating -arcs and circles in small segments.

-

-·        -For arcs the number of these segments depends on -TOLERANCE2APPROXCURVE and ARCMINSEGMENTQTY variables (minimum -number of segments to be used for the approximation)

-

-·        -For circles the number of these segments depends on -TOLERANCE2APPROXCURVE and CIRCLEMINSEGMENTQTY variables -(minimum number of segments to be used for the -approximation)

-

image

-

Maximum approximation -error

-
- - + + + + + + + +Arcs and circles + + + +
+

Arcs and +circles

+

QAD supports approximating +arcs and circles in small segments.

+

+·         +For arcs the number of these segments depends on +TOLERANCE2APPROXCURVE and ARCMINSEGMENTQTY variables (minimum +number of segments to be used for the approximation)

+

+·         +For circles the number of these segments depends on +TOLERANCE2APPROXCURVE and CIRCLEMINSEGMENTQTY variables +(minimum number of segments to be used for the +approximation)

+

image

+

Maximum approximation +error

+
+ + diff --git a/help/help_en/7_Arcsarcsofellipseellipsesandcircles.htm b/help/help_en/7_Arcsarcsofellipseellipsesandcircles.htm new file mode 100644 index 00000000..62c3c5c9 --- /dev/null +++ b/help/help_en/7_Arcsarcsofellipseellipsesandcircles.htm @@ -0,0 +1,57 @@ + + + + + + + +Arcs, arcs of ellipse, ellipses and circles + + + +
+

Arcs, arcs of ellipse, ellipses +and circles

+

QAD supports approximating +arcs, arcs of ellipse, ellipses and circles in small +segments.

+

+·         +For arcs the number of these segments depends on +TOLERANCE2APPROXCURVE and ARCMINSEGMENTQTY variables (minimum +number of segments to be used for the approximation)

+

+·         +For arcs of ellipse the number of these segments +depends on TOLERANCE2APPROXCURVE and ELLIPSEARCMINSEGMENTQTY +variables (minimum number of segments to be used for the +approximation)

+

+·         +For ellipses the number of these segments depends +on TOLERANCE2APPROXCURVE and ELLIPSEMINSEGMENTQTY variables +(minimum number of segments to be used for the +approximation)

+

+·         +For circles the number of these segments depends on +TOLERANCE2APPROXCURVE and CIRCLEMINSEGMENTQTY variables +(minimum number of segments to be used for the +approximation)

+

image

+

Maximum approximation +error

+
+ + diff --git a/help/help_en/80_CMDLINEOPTBACKCOLOR.htm b/help/help_en/80_CMDLINEOPTBACKCOLOR.htm new file mode 100644 index 00000000..77fe9291 --- /dev/null +++ b/help/help_en/80_CMDLINEOPTBACKCOLOR.htm @@ -0,0 +1,20 @@ + + + + + + + +CMDLINEOPTBACKCOLOR + + + +
+

CMDLINEOPTBACKCOLOR

+

Command option keyword +background color. Global variable.

+
+ + diff --git a/help/help_en/80_CMDLINEOPTCOLOR.htm b/help/help_en/80_CMDLINEOPTCOLOR.htm new file mode 100644 index 00000000..bea1a274 --- /dev/null +++ b/help/help_en/80_CMDLINEOPTCOLOR.htm @@ -0,0 +1,20 @@ + + + + + + + +CMDLINEOPTCOLOR + + + +
+

CMDLINEOPTCOLOR

+

Command option keyword +color. Global variable.

+
+ + diff --git a/help/help_en/80_OSSIZE.htm b/help/help_en/80_OSSIZE.htm deleted file mode 100644 index 72364a4e..00000000 --- a/help/help_en/80_OSSIZE.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -OSSIZE - - - -
-

OSSIZE

-

The same as the -most popular CAD.

-
- - diff --git a/help/help_en/81_CMDLINEOPTCOLOR.htm b/help/help_en/81_CMDLINEOPTCOLOR.htm new file mode 100644 index 00000000..1b3107b5 --- /dev/null +++ b/help/help_en/81_CMDLINEOPTCOLOR.htm @@ -0,0 +1,20 @@ + + + + + + + +CMDLINEOPTCOLOR + + + +
+

CMDLINEOPTCOLOR

+

Command option keyword +color. Global variable.

+
+ + diff --git a/help/help_en/81_CMDLINEOPTHIGHLIGHTEDCOLOR.htm b/help/help_en/81_CMDLINEOPTHIGHLIGHTEDCOLOR.htm new file mode 100644 index 00000000..273869aa --- /dev/null +++ b/help/help_en/81_CMDLINEOPTHIGHLIGHTEDCOLOR.htm @@ -0,0 +1,20 @@ + + + + + + + +CMDLINEOPTHIGHLIGHTEDCOLOR + + + +
+

CMDLINEOPTHIGHLIGHTEDCOLOR

+

Command option highlighted +color. Global variable.

+
+ + diff --git a/help/help_en/81_PICKADD.htm b/help/help_en/81_PICKADD.htm deleted file mode 100644 index a9762717..00000000 --- a/help/help_en/81_PICKADD.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -PICKADD - - - -
-

PICKADD

-

The same as the -most popular CAD.

-
- - diff --git a/help/help_en/82_CMDLINEOPTHIGHLIGHTEDCOLOR.htm b/help/help_en/82_CMDLINEOPTHIGHLIGHTEDCOLOR.htm new file mode 100644 index 00000000..2b4bde13 --- /dev/null +++ b/help/help_en/82_CMDLINEOPTHIGHLIGHTEDCOLOR.htm @@ -0,0 +1,20 @@ + + + + + + + +CMDLINEOPTHIGHLIGHTEDCOLOR + + + +
+

CMDLINEOPTHIGHLIGHTEDCOLOR

+

Command option highlighted +color. Global variable.

+
+ + diff --git a/help/help_en/82_COPYMODE.htm b/help/help_en/82_COPYMODE.htm new file mode 100644 index 00000000..c14b7792 --- /dev/null +++ b/help/help_en/82_COPYMODE.htm @@ -0,0 +1,21 @@ + + + + + + + +COPYMODE + + + +
+

COPYMODE

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/82_PICKBOX.htm b/help/help_en/82_PICKBOX.htm deleted file mode 100644 index 40b49566..00000000 --- a/help/help_en/82_PICKBOX.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -PICKBOX - - - -
-

PICKBOX

-

The same as the -most popular CAD.

-
- - diff --git a/help/help_en/83_COPYMODE.htm b/help/help_en/83_COPYMODE.htm new file mode 100644 index 00000000..120d023b --- /dev/null +++ b/help/help_en/83_COPYMODE.htm @@ -0,0 +1,21 @@ + + + + + + + +COPYMODE + + + +
+

COPYMODE

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/83_CROSSINGAREACOLOR.htm b/help/help_en/83_CROSSINGAREACOLOR.htm new file mode 100644 index 00000000..27ba4724 --- /dev/null +++ b/help/help_en/83_CROSSINGAREACOLOR.htm @@ -0,0 +1,21 @@ + + + + + + + +CROSSINGAREACOLOR + + + +
+

CROSSINGAREACOLOR

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/83_PICKBOXCOLOR.htm b/help/help_en/83_PICKBOXCOLOR.htm deleted file mode 100644 index 1f2877a3..00000000 --- a/help/help_en/83_PICKBOXCOLOR.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -PICKBOXCOLOR - - - -
-

PICKBOXCOLOR

-

The same as the -most popular CAD.

-
- - diff --git a/help/help_en/84_CROSSINGAREACOLOR.htm b/help/help_en/84_CROSSINGAREACOLOR.htm new file mode 100644 index 00000000..6b7743ef --- /dev/null +++ b/help/help_en/84_CROSSINGAREACOLOR.htm @@ -0,0 +1,21 @@ + + + + + + + +CROSSINGAREACOLOR + + + +
+

CROSSINGAREACOLOR

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/84_CURSORCOLOR.htm b/help/help_en/84_CURSORCOLOR.htm new file mode 100644 index 00000000..adb6989e --- /dev/null +++ b/help/help_en/84_CURSORCOLOR.htm @@ -0,0 +1,21 @@ + + + + + + + +CURSORCOLOR + + + +
+

CURSORCOLOR

+

Cross pointer color. Valid +values are valid RGB colors, color type, default value red +=“#FF0000”. Global variable.

+
+ + diff --git a/help/help_en/84_POLARANG.htm b/help/help_en/84_POLARANG.htm deleted file mode 100644 index a05fe656..00000000 --- a/help/help_en/84_POLARANG.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -POLARANG - - - -
-

POLARANG

-

The same as the -most popular CAD.

-
- - diff --git a/help/help_en/85_CURSORCOLOR.htm b/help/help_en/85_CURSORCOLOR.htm new file mode 100644 index 00000000..d45b77dd --- /dev/null +++ b/help/help_en/85_CURSORCOLOR.htm @@ -0,0 +1,21 @@ + + + + + + + +CURSORCOLOR + + + +
+

CURSORCOLOR

+

Cross pointer color. Valid +values are valid RGB colors, color type, default value red +=“#FF0000”. Global variable.

+
+ + diff --git a/help/help_en/85_CURSORSIZE.htm b/help/help_en/85_CURSORSIZE.htm new file mode 100644 index 00000000..28dfbe5c --- /dev/null +++ b/help/help_en/85_CURSORSIZE.htm @@ -0,0 +1,21 @@ + + + + + + + +CURSORSIZE + + + +
+

CURSORSIZE

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/85_SELECTIONAREA.htm b/help/help_en/85_SELECTIONAREA.htm deleted file mode 100644 index b6cc32e8..00000000 --- a/help/help_en/85_SELECTIONAREA.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -SELECTIONAREA - - - -
-

SELECTIONAREA

-

The same as the -most popular CAD.

-
- - diff --git a/help/help_en/86_CURSORSIZE.htm b/help/help_en/86_CURSORSIZE.htm new file mode 100644 index 00000000..0fdfad7f --- /dev/null +++ b/help/help_en/86_CURSORSIZE.htm @@ -0,0 +1,21 @@ + + + + + + + +CURSORSIZE + + + +
+

CURSORSIZE

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/86_DELOBJ.htm b/help/help_en/86_DELOBJ.htm new file mode 100644 index 00000000..a2ca7f08 --- /dev/null +++ b/help/help_en/86_DELOBJ.htm @@ -0,0 +1,23 @@ + + + + + + + +DELOBJ + + + +
+

DELOBJ

+

It controls whether the +original geometry is retained or removed. Global variable.
+0 = All defining geometry is retained.
+1 = Deletes all defining geometry.
+-1 = Displays prompts to delete all defining geometry.

+
+ + diff --git a/help/help_en/86_SELECTIONAREAOPACITY.htm b/help/help_en/86_SELECTIONAREAOPACITY.htm deleted file mode 100644 index 4a0a0727..00000000 --- a/help/help_en/86_SELECTIONAREAOPACITY.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -SELECTIONAREAOPACITY - - - -
-

SELECTIONAREAOPACITY

-

The same as the -most popular CAD.

-
- - diff --git a/help/help_en/87_DELOBJ.htm b/help/help_en/87_DELOBJ.htm new file mode 100644 index 00000000..51557c64 --- /dev/null +++ b/help/help_en/87_DELOBJ.htm @@ -0,0 +1,23 @@ + + + + + + + +DELOBJ + + + +
+

DELOBJ

+

It controls whether the +original geometry is retained or removed. Global variable.
+0 = All defining geometry is retained.
+1 = Deletes all defining geometry.
+-1 = Displays prompts to delete all defining geometry.

+
+ + diff --git a/help/help_en/87_DIMSTYLE.htm b/help/help_en/87_DIMSTYLE.htm new file mode 100644 index 00000000..86b01e1b --- /dev/null +++ b/help/help_en/87_DIMSTYLE.htm @@ -0,0 +1,21 @@ + + + + + + + +DIMSTYLE + + + +
+

DIMSTYLE

+

The same as the +most popular CAD. Project +variable.

+
+ + diff --git a/help/help_en/87_SUPPORTPATH.htm b/help/help_en/87_SUPPORTPATH.htm deleted file mode 100644 index 4af7ae9e..00000000 --- a/help/help_en/87_SUPPORTPATH.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -SUPPORTPATH - - - -
-

SUPPORTPATH

-

Searching path for support -files. Character type.

-
- - diff --git a/help/help_en/88_DIMSTYLE.htm b/help/help_en/88_DIMSTYLE.htm new file mode 100644 index 00000000..822a8736 --- /dev/null +++ b/help/help_en/88_DIMSTYLE.htm @@ -0,0 +1,21 @@ + + + + + + + +DIMSTYLE + + + +
+

DIMSTYLE

+

The same as the +most popular CAD. Project +variable.

+
+ + diff --git a/help/help_en/88_DYNDIGRIP.htm b/help/help_en/88_DYNDIGRIP.htm new file mode 100644 index 00000000..641eb437 --- /dev/null +++ b/help/help_en/88_DYNDIGRIP.htm @@ -0,0 +1,25 @@ + + + + + + + +DYNDIGRIP + + + +
+

DYNDIGRIP

+

Turns Dynamic Input +features on and off. Global variable.
+0 = None.
+1 = Resulting dimension.
+2 = Length change dimension.
+4 = Absolute angle dimension   .
+8 Angle change dimension.

+
+ + diff --git a/help/help_en/88_SHOWTEXTWINDOW.htm b/help/help_en/88_SHOWTEXTWINDOW.htm deleted file mode 100644 index 31f12b4c..00000000 --- a/help/help_en/88_SHOWTEXTWINDOW.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -SHOWTEXTWINDOW - - - -
-

SHOWTEXTWINDOW

-

Show the text window at -startup. Bool type, default value true.

-
- - diff --git a/help/help_en/89_DYNDIGRIP.htm b/help/help_en/89_DYNDIGRIP.htm new file mode 100644 index 00000000..58aaf3d4 --- /dev/null +++ b/help/help_en/89_DYNDIGRIP.htm @@ -0,0 +1,25 @@ + + + + + + + +DYNDIGRIP + + + +
+

DYNDIGRIP

+

Turns Dynamic Input +features on and off. Global variable.
+0 = None.
+1 = Resulting dimension.
+2 = Length change dimension.
+4 = Absolute angle dimension   .
+8 Angle change dimension.

+
+ + diff --git a/help/help_en/89_DYNDIVIS.htm b/help/help_en/89_DYNDIVIS.htm new file mode 100644 index 00000000..794ec8d2 --- /dev/null +++ b/help/help_en/89_DYNDIVIS.htm @@ -0,0 +1,21 @@ + + + + + + + +DYNDIVIS + + + +
+

DYNDIVIS

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/89_TOLERANCE2APPROXCURVE.htm b/help/help_en/89_TOLERANCE2APPROXCURVE.htm deleted file mode 100644 index 33303104..00000000 --- a/help/help_en/89_TOLERANCE2APPROXCURVE.htm +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - -TOLERANCE2APPROXCURVE - - - -
-

TOLERANCE2APPROXCURVE

-

Maximum error approximating -a curve to segments. Valid values from 0.000001, real type, default -value 0.1.

-
- - diff --git a/help/help_en/8_OSNAP.htm b/help/help_en/8_OSNAP.htm index a544a3b1..1e904e50 100644 --- a/help/help_en/8_OSNAP.htm +++ b/help/help_en/8_OSNAP.htm @@ -1,79 +1,80 @@ - - - - - - - -OSNAP - - - -
-

OSNAP

-

The F3 key -activates/deactivates the osnap mode.

-

To change the osnap -mode:

-
    -
  1. When a command ask for a -point press CTRL + right mouse button to chosse a different snap -mode.
  2. -
  3. When a command ask for a -point type in the text window:
    -"NONE" = no snap
    -"END" = endpoints of each segment
    -"END_PL" = endpoint of the entire polyline
    -"MID" = midpoint
    -"CEN" = center (centroid)
    -"NOD" = point object
    -"QUA" = quadrant point
    -"INT" = intersection
    -"INS" = insertion point
    -"PER" = perpendicular point
    -"TAN" = tangent
    -"NEA" = closest point
    -"APP" = apparent intersection
    -"EXT" = Extension
    -"PAR" = Parallel
    -"INT_EXT" = intersection on extension
    -"PR" = progressive distance (may be followed by a number to set a -progressive distance different from default)
  4. -
  5. -Using the setvar command to set the OSMODE variable -with a combination a bit using the following -schema:
    0 = -None
    1 = -endpoint
    2 = -midpoint
    4 = -center (centroid)
    8 = point object
    -16 = quadrant point
    32 = -intersection
    64 = insertion point
    -128 = perpendicular point
    256 = tangent
    512 = closest -point
    1024 = -clear all object snaps
    -2048 = apparent intersection
    4096 = -extension
    8192 -= parallel
    16384 = osnap disabled
    -65536 = progressive distance
    131072 = intersection on -extension
    2097152 = endpoints of the entire polyline
  6. -
  7. -Run DSETTING command
  8. -
-
- - + + + + + + + +OSNAP + + + +
+

OSNAP

+

The F3 key +activates/deactivates the osnap mode.

+

To change the osnap +mode:

+
    +
  1. When a command ask for a +point press CTRL + right mouse button to choose a different snap +mode.
  2. +
  3. When a command ask for a +point type in the text window:
    +"NONE" = no snap
    +"END" = endpoints of each segment
    +"END_PL" = endpoint of the entire polyline
    +"MID" = midpoint
    +"CEN" = center (centroid)
    +"NOD" = point object
    +"QUA" = quadrant point
    +"INT" = intersection
    +"INS" = insertion point
    +"PER" = perpendicular point
    +"TAN" = tangent
    +"NEA" = closest point
    +"APP" = apparent intersection
    +"EXT" = Extension
    +"PAR" = Parallel
    +"INT_EXT" = intersection on extension
    +"PR" = progressive distance (may be followed by a number to set a +progressive distance different from default)
  4. +
  5. +Using the setvar command to set the OSMODE variable +with a combination a bit using the following +schema:
    0 = +None
    1 = +endpoint
    2 = +midpoint
    4 = +center (centroid)
    8 = point object
    +16 = quadrant point
    32 = +intersection
    64 = insertion point
    +128 = perpendicular point
    256 = tangent
    512 = closest +point
    1024 = +clear all object snaps
    +2048 = apparent intersection
    4096 = +extension
    8192 += parallel
    16384 = osnap disabled
    +65536 = progressive distance
    131072 = intersection on +extension
    2097152 = endpoints of the entire polyline
  6. +
  7. +Run DSETTING command
  8. +
+
+ + diff --git a/help/help_en/90_DYNDIVIS.htm b/help/help_en/90_DYNDIVIS.htm new file mode 100644 index 00000000..baf3c082 --- /dev/null +++ b/help/help_en/90_DYNDIVIS.htm @@ -0,0 +1,21 @@ + + + + + + + +DYNDIVIS + + + +
+

DYNDIVIS

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/90_DYNEDITFORECOLOR.htm b/help/help_en/90_DYNEDITFORECOLOR.htm new file mode 100644 index 00000000..0d297f84 --- /dev/null +++ b/help/help_en/90_DYNEDITFORECOLOR.htm @@ -0,0 +1,20 @@ + + + + + + + +DYNEDITFORECOLOR + + + +
+

DYNEDITFORECOLOR

+

Set the dynamic input text +color (RGB). Global variable.

+
+ + diff --git a/help/help_en/90_WINDOWAREACOLOR.htm b/help/help_en/90_WINDOWAREACOLOR.htm deleted file mode 100644 index ae374c2b..00000000 --- a/help/help_en/90_WINDOWAREACOLOR.htm +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - -WINDOWAREACOLOR - - - -
-

WINDOWAREACOLOR

-

The same as the -most popular CAD.

-
- - diff --git a/help/help_en/91_DYNEDITBACKCOLOR.htm b/help/help_en/91_DYNEDITBACKCOLOR.htm new file mode 100644 index 00000000..9095390a --- /dev/null +++ b/help/help_en/91_DYNEDITBACKCOLOR.htm @@ -0,0 +1,20 @@ + + + + + + + +DYNEDITBACKCOLOR + + + +
+

DYNEDITBACKCOLOR

+

Set the dynamic input +background text color (RGB). Global variable.

+
+ + diff --git a/help/help_en/91_DYNEDITFORECOLOR.htm b/help/help_en/91_DYNEDITFORECOLOR.htm new file mode 100644 index 00000000..3253f399 --- /dev/null +++ b/help/help_en/91_DYNEDITFORECOLOR.htm @@ -0,0 +1,20 @@ + + + + + + + +DYNEDITFORECOLOR + + + +
+

DYNEDITFORECOLOR

+

Set the dynamic input text +color (RGB). Global variable.

+
+ + diff --git a/help/help_en/92_DYNEDITBACKCOLOR.htm b/help/help_en/92_DYNEDITBACKCOLOR.htm new file mode 100644 index 00000000..b5666dd7 --- /dev/null +++ b/help/help_en/92_DYNEDITBACKCOLOR.htm @@ -0,0 +1,20 @@ + + + + + + + +DYNEDITBACKCOLOR + + + +
+

DYNEDITBACKCOLOR

+

Set the dynamic input +background text color (RGB). Global variable.

+
+ + diff --git a/help/help_en/92_DYNEDITBORDERCOLOR.htm b/help/help_en/92_DYNEDITBORDERCOLOR.htm new file mode 100644 index 00000000..3c882234 --- /dev/null +++ b/help/help_en/92_DYNEDITBORDERCOLOR.htm @@ -0,0 +1,20 @@ + + + + + + + +DYNEDITBORDERCOLOR + + + +
+

DYNEDITBORDERCOLOR

+

Set the dynamic input +border color (RGB). Global variable.

+
+ + diff --git a/help/help_en/93_DYNEDITBORDERCOLOR.htm b/help/help_en/93_DYNEDITBORDERCOLOR.htm new file mode 100644 index 00000000..0cb92ab4 --- /dev/null +++ b/help/help_en/93_DYNEDITBORDERCOLOR.htm @@ -0,0 +1,20 @@ + + + + + + + +DYNEDITBORDERCOLOR + + + +
+

DYNEDITBORDERCOLOR

+

Set the dynamic input +border color (RGB). Global variable.

+
+ + diff --git a/help/help_en/93_DYNMODE.htm b/help/help_en/93_DYNMODE.htm new file mode 100644 index 00000000..35dc6326 --- /dev/null +++ b/help/help_en/93_DYNMODE.htm @@ -0,0 +1,21 @@ + + + + + + + +DYNMODE + + + +
+

DYNMODE

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/94_DYNMODE.htm b/help/help_en/94_DYNMODE.htm new file mode 100644 index 00000000..5548db51 --- /dev/null +++ b/help/help_en/94_DYNMODE.htm @@ -0,0 +1,21 @@ + + + + + + + +DYNMODE + + + +
+

DYNMODE

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/94_DYNPICOORDS.htm b/help/help_en/94_DYNPICOORDS.htm new file mode 100644 index 00000000..8abed1f5 --- /dev/null +++ b/help/help_en/94_DYNPICOORDS.htm @@ -0,0 +1,21 @@ + + + + + + + +DYNPICOORDS + + + +
+

DYNPICOORDS

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/95_DYNPICOORDS.htm b/help/help_en/95_DYNPICOORDS.htm new file mode 100644 index 00000000..66006b74 --- /dev/null +++ b/help/help_en/95_DYNPICOORDS.htm @@ -0,0 +1,21 @@ + + + + + + + +DYNPICOORDS + + + +
+

DYNPICOORDS

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/95_DYNPIFORMAT.htm b/help/help_en/95_DYNPIFORMAT.htm new file mode 100644 index 00000000..97180ec3 --- /dev/null +++ b/help/help_en/95_DYNPIFORMAT.htm @@ -0,0 +1,21 @@ + + + + + + + +DYNPIFORMAT + + + +
+

DYNPIFORMAT

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/96_DYNPIFORMAT.htm b/help/help_en/96_DYNPIFORMAT.htm new file mode 100644 index 00000000..608f2f73 --- /dev/null +++ b/help/help_en/96_DYNPIFORMAT.htm @@ -0,0 +1,21 @@ + + + + + + + +DYNPIFORMAT + + + +
+

DYNPIFORMAT

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/96_DYNPIVIS.htm b/help/help_en/96_DYNPIVIS.htm new file mode 100644 index 00000000..f6883091 --- /dev/null +++ b/help/help_en/96_DYNPIVIS.htm @@ -0,0 +1,22 @@ + + + + + + + +DYNPIVIS + + + +
+

DYNPIVIS

+

Controls when pointer input +is displayed. Global variable.
+1 = Automatically at a prompt for a point
+2 = Always

+
+ + diff --git a/help/help_en/97_DYNPIVIS.htm b/help/help_en/97_DYNPIVIS.htm new file mode 100644 index 00000000..3e4f7082 --- /dev/null +++ b/help/help_en/97_DYNPIVIS.htm @@ -0,0 +1,22 @@ + + + + + + + +DYNPIVIS + + + +
+

DYNPIVIS

+

Controls when pointer input +is displayed. Global variable.
+1 = Automatically at a prompt for a point
+2 = Always

+
+ + diff --git a/help/help_en/97_DYNPROMPT.htm b/help/help_en/97_DYNPROMPT.htm new file mode 100644 index 00000000..382ce8ea --- /dev/null +++ b/help/help_en/97_DYNPROMPT.htm @@ -0,0 +1,21 @@ + + + + + + + +DYNPROMPT + + + +
+

DYNPROMPT

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/98_DYNPROMPT.htm b/help/help_en/98_DYNPROMPT.htm new file mode 100644 index 00000000..f3a0d026 --- /dev/null +++ b/help/help_en/98_DYNPROMPT.htm @@ -0,0 +1,21 @@ + + + + + + + +DYNPROMPT + + + +
+

DYNPROMPT

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/98_DYNTOOLTIPS.htm b/help/help_en/98_DYNTOOLTIPS.htm new file mode 100644 index 00000000..0057827d --- /dev/null +++ b/help/help_en/98_DYNTOOLTIPS.htm @@ -0,0 +1,21 @@ + + + + + + + +DYNTOOLTIPS + + + +
+

DYNTOOLTIPS

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/99_DYNTOOLTIPS.htm b/help/help_en/99_DYNTOOLTIPS.htm new file mode 100644 index 00000000..1863f6de --- /dev/null +++ b/help/help_en/99_DYNTOOLTIPS.htm @@ -0,0 +1,21 @@ + + + + + + + +DYNTOOLTIPS + + + +
+

DYNTOOLTIPS

+

The same as the +most popular CAD. Global +variable.

+
+ + diff --git a/help/help_en/99_DYNTRECKINGVECTORCOLOR.htm b/help/help_en/99_DYNTRECKINGVECTORCOLOR.htm new file mode 100644 index 00000000..daf2368a --- /dev/null +++ b/help/help_en/99_DYNTRECKINGVECTORCOLOR.htm @@ -0,0 +1,20 @@ + + + + + + + +DYNTRECKINGVECTORCOLOR + + + +
+

DYNTRECKINGVECTORCOLOR

+

Set the Autotreck vector +color (RGB). Global variable.

+
+ + diff --git a/help/help_en/9_Howtospecifyapoint.htm b/help/help_en/9_Howtospecifyapoint.htm index 1ee39be1..f30ed465 100644 --- a/help/help_en/9_Howtospecifyapoint.htm +++ b/help/help_en/9_Howtospecifyapoint.htm @@ -1,51 +1,51 @@ - - - - - - - -How to specify a point - - - -
-

How to specify a -point

-

The coordinates of a point can -be expressed using the following syntax:

-

-1)      -x,y

-

-2)      -@length<angle (from the previous point you -move to a distance using an angle)

-

-3)      -@ x,y (from the previous point you move to a -distance in the X axis and to another distance in the Y -axis)

-

-4)      -@ (previous point)

-

-5)      -length (from the previous point you move to a -distance using the current mouse position)

-

-6)      -Coordinate specified in a coordinate reference -system different from the current one

-

- 

-
- - + + + + + + + +How to specify a point + + + +
+

How to specify a +point

+

The coordinates of a point can +be expressed using the following syntax:

+

+1)      +x,y

+

+2)      +@length<angle (from the previous point you +move to a distance using an angle)

+

+3)      +@ x,y (from the previous point you move to a +distance in the X axis and to another distance in the Y +axis)

+

+4)      +@ (previous point)

+

+5)      +length (from the previous point you move to a +distance using the current mouse position)

+

+6)      +Coordinate specified in a coordinate reference +system different from the current one

+

+ 

+
+ + diff --git a/help/help_en/css/chmprocessor.css b/help/help_en/css/chmprocessor.css index 7b7a10ff..86bee385 100644 --- a/help/help_en/css/chmprocessor.css +++ b/help/help_en/css/chmprocessor.css @@ -1,116 +1,116 @@ -body { - font-family: Arial; - font-size: 12px; -} - -img -{ - border: 0; -} - -h1 -{ - margin: 5px 0 5px 10px; - font-size: 24pt; - font-weight:bold; -} - -.header -{ - background-color: #e5ecf9; -} - -.header-options -{ - float:right; - margin: 5px 10px; -} - -#content-tabs -{ - height: 100%; -} - -.contentpane {} - -#topicsList , #searchTopic -{ - width:100%; -} - -#searchTopic -{ - margin-bottom: 5px; -} - -#aboutDlg -{ - padding: 10px 40px; -} -#aboutDlg img -{ - vertical-align:middle; - margin-right:15px; -} -#aboutDlg a -{ - font-weight:bold; -} - -/* - * GLOBAL LAYOUT STYLES - */ -.ui-layout-content { - overflow: auto; /* add scrolling to content-divs (panel-wrappers) */ - border-top: 0 !important; /* tab-buttons above this DIV already has a border-bottom */ -} - -/* - * TAB-THEME ADJUSTMENTS - */ -.ui-tabs-nav li { - white-space: nowrap; -} -.ui-tabs-nav li a { - font-size: 1em !important; - padding: 4px 1.5ex 3px !important; -} -.ui-tabs-panel { - font-size: 1em !important; - padding: 0 1em !important; -} - -/* - * WEST-PANE TABS - * - * These tabs 'fill' the pane, - * so the pane-border acts as the tab-border - */ -.ui-layout-pane-west { - padding: 0 !important; - overflow: hidden; - } - .ui-layout-pane-west .ui-tabs-nav { - /* don't need border or rounded corners - tabs 'fill' the pane */ - border-top: 0; - border-left: 0; - border-right: 0; - padding-bottom: 0 !important; - -moz-border-radius: 0; - -webkit-border-radius: 0; - } - -.ui-layout-center -{ - padding: 0 !important; -} - -.jstree-default.jstree-focused -{ - background: transparent !important; -} - +body { + font-family: Arial; + font-size: 12px; +} + +img +{ + border: 0; +} + +h1 +{ + margin: 5px 0 5px 10px; + font-size: 24pt; + font-weight:bold; +} + +.header +{ + background-color: #e5ecf9; +} + +.header-options +{ + float:right; + margin: 5px 10px; +} + +#content-tabs +{ + height: 100%; +} + +.contentpane {} + +#topicsList , #searchTopic +{ + width:100%; +} + +#searchTopic +{ + margin-bottom: 5px; +} + +#aboutDlg +{ + padding: 10px 40px; +} +#aboutDlg img +{ + vertical-align:middle; + margin-right:15px; +} +#aboutDlg a +{ + font-weight:bold; +} + +/* + * GLOBAL LAYOUT STYLES + */ +.ui-layout-content { + overflow: auto; /* add scrolling to content-divs (panel-wrappers) */ + border-top: 0 !important; /* tab-buttons above this DIV already has a border-bottom */ +} + +/* + * TAB-THEME ADJUSTMENTS + */ +.ui-tabs-nav li { + white-space: nowrap; +} +.ui-tabs-nav li a { + font-size: 1em !important; + padding: 4px 1.5ex 3px !important; +} +.ui-tabs-panel { + font-size: 1em !important; + padding: 0 1em !important; +} + +/* + * WEST-PANE TABS + * + * These tabs 'fill' the pane, + * so the pane-border acts as the tab-border + */ +.ui-layout-pane-west { + padding: 0 !important; + overflow: hidden; + } + .ui-layout-pane-west .ui-tabs-nav { + /* don't need border or rounded corners - tabs 'fill' the pane */ + border-top: 0; + border-left: 0; + border-right: 0; + padding-bottom: 0 !important; + -moz-border-radius: 0; + -webkit-border-radius: 0; + } + +.ui-layout-center +{ + padding: 0 !important; +} + +.jstree-default.jstree-focused +{ + background: transparent !important; +} + .ui-widget { - font-family: Arial !important; + font-family: Arial !important; font-size: 12px !important; } \ No newline at end of file diff --git a/help/help_en/css/layout-default-latest.css b/help/help_en/css/layout-default-latest.css index aa382de3..14e507b5 100644 --- a/help/help_en/css/layout-default-latest.css +++ b/help/help_en/css/layout-default-latest.css @@ -1,224 +1,224 @@ -/* - * Default Layout Theme - * - * Created for jquery.layout - * - * Copyright (c) 2010 - * Fabrizio Balliano (http://www.fabrizioballiano.net) - * Kevin Dalman (http://allpro.net) - * - * Dual licensed under the GPL (http://www.gnu.org/licenses/gpl.html) - * and MIT (http://www.opensource.org/licenses/mit-license.php) licenses. - * - * Last Updated: 2010-02-10 - * NOTE: For best code readability, view this with a fixed-space font and tabs equal to 4-chars - */ - -/* - * DEFAULT FONT - * Just to make demo-pages look better - not actually relevant to Layout! - */ -body { - font-family: Geneva, Arial, Helvetica, sans-serif; - font-size: 100%; - *font-size: 80%; -} - -/* - * PANES & CONTENT-DIVs - */ -.ui-layout-pane { /* all 'panes' */ - background: #FFF; - border: 1px solid #BBB; - padding: 10px; - overflow: auto; - /* DO NOT add scrolling (or padding) to 'panes' that have a content-div, - otherwise you may get double-scrollbars - on the pane AND on the content-div - - use ui-layout-wrapper class if pane has a content-div - - use ui-layout-container if pane has an inner-layout - */ - } - /* (scrolling) content-div inside pane allows for fixed header(s) and/or footer(s) */ - .ui-layout-content { - padding: 10px; - position: relative; /* contain floated or positioned elements */ - overflow: auto; /* add scrolling to content-div */ - } - -/* - * UTILITY CLASSES - * Must come AFTER pane-class above so will override - * These classes are NOT auto-generated and are NOT used by Layout - */ -.layout-child-container, -.layout-content-container { - padding: 0; - overflow: hidden; -} -.layout-child-container { - border: 0; /* remove border because inner-layout-panes probably have borders */ -} -.layout-scroll { - overflow: auto; -} -.layout-hide { - display: none; -} - -/* - * RESIZER-BARS - */ -.ui-layout-resizer { /* all 'resizer-bars' */ - background: #DDD; - border: 1px solid #BBB; - border-width: 0; - } - .ui-layout-resizer-drag { /* REAL resizer while resize in progress */ - } - .ui-layout-resizer-hover { /* affects both open and closed states */ - } - /* NOTE: It looks best when 'hover' and 'dragging' are set to the same color, - otherwise color shifts while dragging when bar can't keep up with mouse */ - .ui-layout-resizer-open-hover , /* hover-color to 'resize' */ - .ui-layout-resizer-dragging { /* resizer beging 'dragging' */ - background: #C4E1A4; - } - .ui-layout-resizer-dragging { /* CLONED resizer being dragged */ - border: 1px solid #BBB; - } - .ui-layout-resizer-north-dragging, - .ui-layout-resizer-south-dragging { - border-width: 1px 0; - } - .ui-layout-resizer-west-dragging, - .ui-layout-resizer-east-dragging { - border-width: 0 1px; - } - /* NOTE: Add a 'dragging-limit' color to provide visual feedback when resizer hits min/max size limits */ - .ui-layout-resizer-dragging-limit { /* CLONED resizer at min or max size-limit */ - background: #E1A4A4; /* red */ - } - - .ui-layout-resizer-closed-hover { /* hover-color to 'slide open' */ - background: #EBD5AA; - } - .ui-layout-resizer-sliding { /* resizer when pane is 'slid open' */ - opacity: .10; /* show only a slight shadow */ - filter: alpha(opacity=10); - } - .ui-layout-resizer-sliding-hover { /* sliding resizer - hover */ - opacity: 1.00; /* on-hover, show the resizer-bar normally */ - filter: alpha(opacity=100); - } - /* sliding resizer - add 'outside-border' to resizer on-hover - * this sample illustrates how to target specific panes and states */ - .ui-layout-resizer-north-sliding-hover { border-bottom-width: 1px; } - .ui-layout-resizer-south-sliding-hover { border-top-width: 1px; } - .ui-layout-resizer-west-sliding-hover { border-right-width: 1px; } - .ui-layout-resizer-east-sliding-hover { border-left-width: 1px; } - -/* - * TOGGLER-BUTTONS - */ -.ui-layout-toggler { - border: 1px solid #BBB; /* match pane-border */ - background-color: #BBB; - } - .ui-layout-resizer-hover .ui-layout-toggler { - opacity: .60; - filter: alpha(opacity=60); - } - .ui-layout-toggler-hover , /* need when NOT resizable */ - .ui-layout-resizer-hover .ui-layout-toggler-hover { /* need specificity when IS resizable */ - background-color: #FC6; - opacity: 1.00; - filter: alpha(opacity=100); - } - .ui-layout-toggler-north , - .ui-layout-toggler-south { - border-width: 0 1px; /* left/right borders */ - } - .ui-layout-toggler-west , - .ui-layout-toggler-east { - border-width: 1px 0; /* top/bottom borders */ - } - /* hide the toggler-button when the pane is 'slid open' */ - .ui-layout-resizer-sliding .ui-layout-toggler { - display: none; - } - /* - * style the text we put INSIDE the togglers - */ - .ui-layout-toggler .content { - color: #666; - font-size: 12px; - font-weight: bold; - width: 100%; - padding-bottom: 0.35ex; /* to 'vertically center' text inside text-span */ - } - -/* - * PANE-MASKS - * these styles are hard-coded on mask elems, but are also - * included here as !important to ensure will overrides any generic styles - */ -.ui-layout-mask { - border: none !important; - padding: 0 !important; - margin: 0 !important; - overflow: hidden !important; - position: absolute !important; - opacity: 0 !important; - filter: Alpha(Opacity="0") !important; -} -.ui-layout-mask-inside-pane { /* masks always inside pane EXCEPT when pane is an iframe */ - top: 0 !important; - left: 0 !important; - width: 100% !important; - height: 100% !important; -} -div.ui-layout-mask {} /* standard mask for iframes */ -iframe.ui-layout-mask {} /* extra mask for objects/applets */ - -/* - * Default printing styles - */ -@media print { - /* - * Unless you want to print the layout as it appears onscreen, - * these html/body styles are needed to allow the content to 'flow' - */ - html { - height: auto !important; - overflow: visible !important; - } - body.ui-layout-container { - position: static !important; - top: auto !important; - bottom: auto !important; - left: auto !important; - right: auto !important; - /* only IE6 has container width & height set by Layout */ - _width: auto !important; - _height: auto !important; - } - .ui-layout-resizer, .ui-layout-toggler { - display: none !important; - } - /* - * Default pane print styles disables positioning, borders and backgrounds. - * You can modify these styles however it suit your needs. - */ - .ui-layout-pane { - border: none !important; - background: transparent !important; - position: relative !important; - top: auto !important; - bottom: auto !important; - left: auto !important; - right: auto !important; - width: auto !important; - height: auto !important; - overflow: visible !important; - } +/* + * Default Layout Theme + * + * Created for jquery.layout + * + * Copyright (c) 2010 + * Fabrizio Balliano (http://www.fabrizioballiano.net) + * Kevin Dalman (http://allpro.net) + * + * Dual licensed under the GPL (http://www.gnu.org/licenses/gpl.html) + * and MIT (http://www.opensource.org/licenses/mit-license.php) licenses. + * + * Last Updated: 2010-02-10 + * NOTE: For best code readability, view this with a fixed-space font and tabs equal to 4-chars + */ + +/* + * DEFAULT FONT + * Just to make demo-pages look better - not actually relevant to Layout! + */ +body { + font-family: Geneva, Arial, Helvetica, sans-serif; + font-size: 100%; + *font-size: 80%; +} + +/* + * PANES & CONTENT-DIVs + */ +.ui-layout-pane { /* all 'panes' */ + background: #FFF; + border: 1px solid #BBB; + padding: 10px; + overflow: auto; + /* DO NOT add scrolling (or padding) to 'panes' that have a content-div, + otherwise you may get double-scrollbars - on the pane AND on the content-div + - use ui-layout-wrapper class if pane has a content-div + - use ui-layout-container if pane has an inner-layout + */ + } + /* (scrolling) content-div inside pane allows for fixed header(s) and/or footer(s) */ + .ui-layout-content { + padding: 10px; + position: relative; /* contain floated or positioned elements */ + overflow: auto; /* add scrolling to content-div */ + } + +/* + * UTILITY CLASSES + * Must come AFTER pane-class above so will override + * These classes are NOT auto-generated and are NOT used by Layout + */ +.layout-child-container, +.layout-content-container { + padding: 0; + overflow: hidden; +} +.layout-child-container { + border: 0; /* remove border because inner-layout-panes probably have borders */ +} +.layout-scroll { + overflow: auto; +} +.layout-hide { + display: none; +} + +/* + * RESIZER-BARS + */ +.ui-layout-resizer { /* all 'resizer-bars' */ + background: #DDD; + border: 1px solid #BBB; + border-width: 0; + } + .ui-layout-resizer-drag { /* REAL resizer while resize in progress */ + } + .ui-layout-resizer-hover { /* affects both open and closed states */ + } + /* NOTE: It looks best when 'hover' and 'dragging' are set to the same color, + otherwise color shifts while dragging when bar can't keep up with mouse */ + .ui-layout-resizer-open-hover , /* hover-color to 'resize' */ + .ui-layout-resizer-dragging { /* resizer beging 'dragging' */ + background: #C4E1A4; + } + .ui-layout-resizer-dragging { /* CLONED resizer being dragged */ + border: 1px solid #BBB; + } + .ui-layout-resizer-north-dragging, + .ui-layout-resizer-south-dragging { + border-width: 1px 0; + } + .ui-layout-resizer-west-dragging, + .ui-layout-resizer-east-dragging { + border-width: 0 1px; + } + /* NOTE: Add a 'dragging-limit' color to provide visual feedback when resizer hits min/max size limits */ + .ui-layout-resizer-dragging-limit { /* CLONED resizer at min or max size-limit */ + background: #E1A4A4; /* red */ + } + + .ui-layout-resizer-closed-hover { /* hover-color to 'slide open' */ + background: #EBD5AA; + } + .ui-layout-resizer-sliding { /* resizer when pane is 'slid open' */ + opacity: .10; /* show only a slight shadow */ + filter: alpha(opacity=10); + } + .ui-layout-resizer-sliding-hover { /* sliding resizer - hover */ + opacity: 1.00; /* on-hover, show the resizer-bar normally */ + filter: alpha(opacity=100); + } + /* sliding resizer - add 'outside-border' to resizer on-hover + * this sample illustrates how to target specific panes and states */ + .ui-layout-resizer-north-sliding-hover { border-bottom-width: 1px; } + .ui-layout-resizer-south-sliding-hover { border-top-width: 1px; } + .ui-layout-resizer-west-sliding-hover { border-right-width: 1px; } + .ui-layout-resizer-east-sliding-hover { border-left-width: 1px; } + +/* + * TOGGLER-BUTTONS + */ +.ui-layout-toggler { + border: 1px solid #BBB; /* match pane-border */ + background-color: #BBB; + } + .ui-layout-resizer-hover .ui-layout-toggler { + opacity: .60; + filter: alpha(opacity=60); + } + .ui-layout-toggler-hover , /* need when NOT resizable */ + .ui-layout-resizer-hover .ui-layout-toggler-hover { /* need specificity when IS resizable */ + background-color: #FC6; + opacity: 1.00; + filter: alpha(opacity=100); + } + .ui-layout-toggler-north , + .ui-layout-toggler-south { + border-width: 0 1px; /* left/right borders */ + } + .ui-layout-toggler-west , + .ui-layout-toggler-east { + border-width: 1px 0; /* top/bottom borders */ + } + /* hide the toggler-button when the pane is 'slid open' */ + .ui-layout-resizer-sliding .ui-layout-toggler { + display: none; + } + /* + * style the text we put INSIDE the togglers + */ + .ui-layout-toggler .content { + color: #666; + font-size: 12px; + font-weight: bold; + width: 100%; + padding-bottom: 0.35ex; /* to 'vertically center' text inside text-span */ + } + +/* + * PANE-MASKS + * these styles are hard-coded on mask elems, but are also + * included here as !important to ensure will overrides any generic styles + */ +.ui-layout-mask { + border: none !important; + padding: 0 !important; + margin: 0 !important; + overflow: hidden !important; + position: absolute !important; + opacity: 0 !important; + filter: Alpha(Opacity="0") !important; +} +.ui-layout-mask-inside-pane { /* masks always inside pane EXCEPT when pane is an iframe */ + top: 0 !important; + left: 0 !important; + width: 100% !important; + height: 100% !important; +} +div.ui-layout-mask {} /* standard mask for iframes */ +iframe.ui-layout-mask {} /* extra mask for objects/applets */ + +/* + * Default printing styles + */ +@media print { + /* + * Unless you want to print the layout as it appears onscreen, + * these html/body styles are needed to allow the content to 'flow' + */ + html { + height: auto !important; + overflow: visible !important; + } + body.ui-layout-container { + position: static !important; + top: auto !important; + bottom: auto !important; + left: auto !important; + right: auto !important; + /* only IE6 has container width & height set by Layout */ + _width: auto !important; + _height: auto !important; + } + .ui-layout-resizer, .ui-layout-toggler { + display: none !important; + } + /* + * Default pane print styles disables positioning, borders and backgrounds. + * You can modify these styles however it suit your needs. + */ + .ui-layout-pane { + border: none !important; + background: transparent !important; + position: relative !important; + top: auto !important; + bottom: auto !important; + left: auto !important; + right: auto !important; + width: auto !important; + height: auto !important; + overflow: visible !important; + } } \ No newline at end of file diff --git a/help/help_en/embeddedstyles.css b/help/help_en/embeddedstyles.css index c4f3580d..a7f8473e 100644 --- a/help/help_en/embeddedstyles.css +++ b/help/help_en/embeddedstyles.css @@ -1,125 +1,141 @@ - - - /* Font Definitions */ - @font-face - {font-family:Wingdings; - panose-1:5 0 0 0 0 0 0 0 0 0;} -@font-face - {font-family:"Cambria Math"; - panose-1:2 4 5 3 5 4 6 3 2 4;} -@font-face - {font-family:Cambria; - panose-1:2 4 5 3 5 4 6 3 2 4;} -@font-face - {font-family:Calibri; - panose-1:2 15 5 2 2 2 4 3 2 4;} - /* Style Definitions */ - p.MsoNormal, li.MsoNormal, div.MsoNormal - {margin-top:0cm; - margin-right:0cm; - margin-bottom:10.0pt; - margin-left:0cm; - line-height:115%; - font-size:11.0pt; - font-family:"Calibri","sans-serif";} -h1 - {mso-style-link:"Titolo 1 Carattere"; - margin-top:24.0pt; - margin-right:0cm; - margin-bottom:0cm; - margin-left:0cm; - margin-bottom:.0001pt; - line-height:115%; - page-break-after:avoid; - font-size:14.0pt; - font-family:"Cambria","serif"; - color:#365F91;} -h2 - {mso-style-link:"Titolo 2 Carattere"; - margin-top:10.0pt; - margin-right:0cm; - margin-bottom:0cm; - margin-left:0cm; - margin-bottom:.0001pt; - line-height:115%; - page-break-after:avoid; - font-size:13.0pt; - font-family:"Cambria","serif"; - color:#4F81BD;} -h3 - {mso-style-link:"Titolo 3 Carattere"; - margin-top:10.0pt; - margin-right:0cm; - margin-bottom:0cm; - margin-left:0cm; - margin-bottom:.0001pt; - line-height:115%; - page-break-after:avoid; - font-size:11.0pt; - font-family:"Cambria","serif"; - color:#4F81BD;} -p.MsoListParagraph, li.MsoListParagraph, div.MsoListParagraph - {margin-top:0cm; - margin-right:0cm; - margin-bottom:10.0pt; - margin-left:36.0pt; - line-height:115%; - font-size:11.0pt; - font-family:"Calibri","sans-serif";} -p.MsoListParagraphCxSpFirst, li.MsoListParagraphCxSpFirst, div.MsoListParagraphCxSpFirst - {margin-top:0cm; - margin-right:0cm; - margin-bottom:0cm; - margin-left:36.0pt; - margin-bottom:.0001pt; - line-height:115%; - font-size:11.0pt; - font-family:"Calibri","sans-serif";} -p.MsoListParagraphCxSpMiddle, li.MsoListParagraphCxSpMiddle, div.MsoListParagraphCxSpMiddle - {margin-top:0cm; - margin-right:0cm; - margin-bottom:0cm; - margin-left:36.0pt; - margin-bottom:.0001pt; - line-height:115%; - font-size:11.0pt; - font-family:"Calibri","sans-serif";} -p.MsoListParagraphCxSpLast, li.MsoListParagraphCxSpLast, div.MsoListParagraphCxSpLast - {margin-top:0cm; - margin-right:0cm; - margin-bottom:10.0pt; - margin-left:36.0pt; - line-height:115%; - font-size:11.0pt; - font-family:"Calibri","sans-serif";} -span.Titolo1Carattere - {mso-style-name:"Titolo 1 Carattere"; - mso-style-link:"Titolo 1"; - font-family:"Cambria","serif"; - color:#365F91; - font-weight:bold;} -span.Titolo2Carattere - {mso-style-name:"Titolo 2 Carattere"; - mso-style-link:"Titolo 2"; - font-family:"Cambria","serif"; - color:#4F81BD; - font-weight:bold;} -span.Titolo3Carattere - {mso-style-name:"Titolo 3 Carattere"; - mso-style-link:"Titolo 3"; - font-family:"Cambria","serif"; - color:#4F81BD; - font-weight:bold;} -span.hilite - {mso-style-name:hilite;} -@page WordSection1 - {size:595.3pt 841.9pt; - margin:70.85pt 2.0cm 2.0cm 2.0cm;} -div.WordSection1 - {page:WordSection1;} - /* List Definitions */ - ol - {margin-bottom:0cm;} -ul - {margin-bottom:0cm;} - + + + /* Font Definitions */ + @font-face + {font-family:Wingdings; + panose-1:5 0 0 0 0 0 0 0 0 0;} +@font-face + {font-family:"Cambria Math"; + panose-1:2 4 5 3 5 4 6 3 2 4;} +@font-face + {font-family:Cambria; + panose-1:2 4 5 3 5 4 6 3 2 4;} +@font-face + {font-family:Calibri; + panose-1:2 15 5 2 2 2 4 3 2 4;} +@font-face + {font-family:Tahoma; + panose-1:2 11 6 4 3 5 4 4 2 4;} + /* Style Definitions */ + p.MsoNormal, li.MsoNormal, div.MsoNormal + {margin-top:0cm; + margin-right:0cm; + margin-bottom:10.0pt; + margin-left:0cm; + line-height:115%; + font-size:11.0pt; + font-family:"Calibri","sans-serif";} +h1 + {mso-style-link:"Titolo 1 Carattere"; + margin-top:24.0pt; + margin-right:0cm; + margin-bottom:0cm; + margin-left:0cm; + margin-bottom:.0001pt; + line-height:115%; + page-break-after:avoid; + font-size:14.0pt; + font-family:"Cambria","serif"; + color:#365F91;} +h2 + {mso-style-link:"Titolo 2 Carattere"; + margin-top:10.0pt; + margin-right:0cm; + margin-bottom:0cm; + margin-left:0cm; + margin-bottom:.0001pt; + line-height:115%; + page-break-after:avoid; + font-size:13.0pt; + font-family:"Cambria","serif"; + color:#4F81BD;} +h3 + {mso-style-link:"Titolo 3 Carattere"; + margin-top:10.0pt; + margin-right:0cm; + margin-bottom:0cm; + margin-left:0cm; + margin-bottom:.0001pt; + line-height:115%; + page-break-after:avoid; + font-size:11.0pt; + font-family:"Cambria","serif"; + color:#4F81BD;} +p.MsoAcetate, li.MsoAcetate, div.MsoAcetate + {mso-style-link:"Testo fumetto Carattere"; + margin:0cm; + margin-bottom:.0001pt; + font-size:8.0pt; + font-family:"Tahoma","sans-serif";} +p.MsoListParagraph, li.MsoListParagraph, div.MsoListParagraph + {margin-top:0cm; + margin-right:0cm; + margin-bottom:10.0pt; + margin-left:36.0pt; + line-height:115%; + font-size:11.0pt; + font-family:"Calibri","sans-serif";} +p.MsoListParagraphCxSpFirst, li.MsoListParagraphCxSpFirst, div.MsoListParagraphCxSpFirst + {margin-top:0cm; + margin-right:0cm; + margin-bottom:0cm; + margin-left:36.0pt; + margin-bottom:.0001pt; + line-height:115%; + font-size:11.0pt; + font-family:"Calibri","sans-serif";} +p.MsoListParagraphCxSpMiddle, li.MsoListParagraphCxSpMiddle, div.MsoListParagraphCxSpMiddle + {margin-top:0cm; + margin-right:0cm; + margin-bottom:0cm; + margin-left:36.0pt; + margin-bottom:.0001pt; + line-height:115%; + font-size:11.0pt; + font-family:"Calibri","sans-serif";} +p.MsoListParagraphCxSpLast, li.MsoListParagraphCxSpLast, div.MsoListParagraphCxSpLast + {margin-top:0cm; + margin-right:0cm; + margin-bottom:10.0pt; + margin-left:36.0pt; + line-height:115%; + font-size:11.0pt; + font-family:"Calibri","sans-serif";} +span.Titolo1Carattere + {mso-style-name:"Titolo 1 Carattere"; + mso-style-link:"Titolo 1"; + font-family:"Cambria","serif"; + color:#365F91; + font-weight:bold;} +span.Titolo2Carattere + {mso-style-name:"Titolo 2 Carattere"; + mso-style-link:"Titolo 2"; + font-family:"Cambria","serif"; + color:#4F81BD; + font-weight:bold;} +span.Titolo3Carattere + {mso-style-name:"Titolo 3 Carattere"; + mso-style-link:"Titolo 3"; + font-family:"Cambria","serif"; + color:#4F81BD; + font-weight:bold;} +span.hilite + {mso-style-name:hilite;} +span.TestofumettoCarattere + {mso-style-name:"Testo fumetto Carattere"; + mso-style-link:"Testo fumetto"; + font-family:"Tahoma","sans-serif";} +.MsoChpDefault + {font-size:10.0pt; + font-family:"Calibri","sans-serif";} +@page WordSection1 + {size:595.3pt 841.9pt; + margin:70.85pt 2.0cm 2.0cm 2.0cm;} +div.WordSection1 + {page:WordSection1;} + /* List Definitions */ + ol + {margin-bottom:0cm;} +ul + {margin-bottom:0cm;} + diff --git a/help/help_en/index.html b/help/help_en/index.html index 5ec3ed9d..69f7aa08 100644 --- a/help/help_en/index.html +++ b/help/help_en/index.html @@ -1,363 +1,502 @@ - - - - - - - - - - - - - - - - - - - - -
-
-"Previous" Next Home -Print topic -About...
-
- - -
- - -
- - - -
-
-
- - -
-
- -
-

Help image Generated with -chmProcessor

-
- - + + + + + + + + + + + + + + + + + + + + +
+
+"Previous" Next Home +Print topic +About...
+
+ + +
+ + +
+ +
+
+ +
+
+ +
+
+
+ + +
+
+ +
+

Help image Generated with +chmProcessor

+
+ + diff --git a/help/help_en/js/chmprocessor.js b/help/help_en/js/chmprocessor.js index 1dd8b432..041983be 100644 --- a/help/help_en/js/chmprocessor.js +++ b/help/help_en/js/chmprocessor.js @@ -1,537 +1,537 @@ -/* -* chmProcessor - Word converter to CHM -* Copyright (C) 2008 Toni Bennasar Obrador -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation, either version 3 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program. If not, see . -*/ - -// TODO: Add new needed translations - -var pageLayout; // a var is required because this page utilizes: pageLayout.allowOverflow() method - - -// Returns the last index of a character into a string. Returns < 0 if it was not found -// character: Character to search -if (!String.prototype.lastIndexOf) { - String.prototype.lastIndexOf = function(character) { - for (i = (this.length - 1); i >= 0; i--) { - if (this.charAt(i) == character) - return i; - } - return -1; - } -} - -// Returns the file name of a URL -function getUrlFileName(url) { - idx = url.lastIndexOf('/'); - if (idx >= 0) - return url.substring(idx + 1); - else - return url; -} - -// Loads a URL into the current topic iframe -// url: Relative topic URL to load -function loadUrlOnFrame(url) { - - var iframeSelector = $("#mainFrame"); - try { - // This will fail on chrome with file:// protocol - var currentUrl = getUrlFileName(iframeSelector.prop("contentWindow").location.href); - if (currentUrl.toLowerCase() == url.toLowerCase()) - return; - } - catch (ex) { } - - if ("onhashchange" in window) - // Browser supports hash changes. Use replace because it does not store an history browser point - iframeSelector.prop("contentWindow").location.replace(url); - else - // Browser does not support hash changes. Set the src attribute: It stores an history browser point - $("#mainFrame").attr("src", url); - -} - -// Select a tree node by its URL -// url: string with the URL to search -function selectByUrl(url) { - var fileName = getUrlFileName(url); - fileName = decodeURIComponent(fileName); // Needed if the hash part(xxx on a.html#xxx) contains spaces, it happens with word generated hashes - // Do tree selection, with hash: - var linkSelected = $('#treediv a[href="' + fileName + '"]').first(); - var loadFrame = true; - if (linkSelected.length == 0) { - // Not found: Do the selection without hash: - var parts = fileName.split("#"); - linkSelected = $('#treediv a[href^="' + parts[0] + '"]').first(); - loadFrame = false; - } - $("#treediv").jstree("select_node", linkSelected.parent(), loadFrame); - if( loadFrame ) - // Load the URL on the frame - loadUrlOnFrame(fileName); - else if( fileName.indexOf("search.aspx") == 0 ) { - // We are on the full text search page. Set the hash: - changeHash(fileName); - } -}; - -// Process a tree link text -function cleanTitleText(linkText) { - // Remove line breaks and leading spaces - var cleanText = $.trim( linkText.replace("\n", " ") ); - // Replace extra spaces by a single one - return cleanText.replace(/\s+/g, " "); -} - -function parseTitle(title) { - var result = new Object(); - result.instance = 0; - result.title = title; - - if (title.charAt(0) != "!") - return result; - title = title.substring(1); - var idx = title.indexOf("!"); - if( idx <= 0 ) - return result; - var number = parseInt(title.substring(0, idx)); - if( isNaN(number) ) - return result; - - result.instance = number - 1; - result.title = title.substring(idx + 1); - return result; -} - -// Select a tree node by its title -// title: Tree node title to select -function selectByTitle(title) { - - title = cleanTitleText(title).toLowerCase(); - - if (title == "") - // Select the first node - $("#treediv").jstree("select_node", $("#treediv li:first"), true); - else { - // Select the node by title - - // Check for instance number: - var searchInstance = parseTitle(title); - var titleSelector = $("#treediv a") - .filter(function(index) { - return cleanTitleText($(this).text()).toLowerCase() == searchInstance.title; - }); - if (searchInstance.instance == 0) - titleSelector = titleSelector.first(); - else - titleSelector = titleSelector.eq(searchInstance.instance); - - $("#treediv").jstree("select_node", - titleSelector.parent(), - true - ); - } -} - -////////////////////////////////////////////////// -// URL HASH HANDLERS -////////////////////////////////////////////////// - -// Return the window current hash -function getCurrentHash() { - // Firefox returns the hash unescaped, so decodeURIComponent fails... - /*var title = window.location.hash; - if (title.charAt(0) == '#') - title = title.substring(1);*/ - var hash = location.href.split("#")[1]; - if (!hash) - hash = ""; - return hash; -} - -// Hash change event handler -function hashChanged() { - var title = getCurrentHash(); - title = decodeURIComponent(title); - if (title.indexOf("search.aspx") == 0) - // Its a full text search URL - loadUrlOnFrame(title); - else - // Its a section title - selectByTitle(title); -} - -// Set a new URL hash -// linkSelector: The jquery tree title link, or a string with the search page URL -function changeHash(linkSelector) { - - if (!("onhashchange" in window)) - // Browser does not support hash change. Do nothing. - return; - - var newHash = null; - if (typeof linkSelector == 'string' || linkSelector instanceof String) { - // Is the url to the search page: - newHash = linkSelector; - } - else { - newHash = cleanTitleText(linkSelector.text()); - - // Check if its a duplicated title - var titleInstance = linkSelector.prop("titleInstance"); - if (titleInstance) - // Save instance number: - newHash = "!" + titleInstance + "!" + newHash; - - // The first node should no have hash: - if (cleanTitleText($("#treediv a:first").text()) == newHash) - newHash = ""; - } - - // Avoid to put the same hash twice - if (window.location.hash == newHash) - return; - - newHash = encodeURIComponent(newHash); - window.location.hash = newHash; -} - -////////////////////////////////////////////////// -// SET INITIAL TOPIC -// For compatibilty with frames template ("www.example.com/help/index.html?topic=topic title") -////////////////////////////////////////////////// - -// Returns the string value of the parameter strParamName on the url -// Returns an empty string if the parameter was not found -function getURLParam(url, strParamName) { - - var strReturn = ""; - var strHref = url; - var idxStart = strHref.indexOf("?"); - if (idxStart >= 0) { - var strQueryString = strHref.substr(idxStart+1); - var aQueryString = strQueryString.split("&"); - for (var iParam = 0; iParam < aQueryString.length; iParam++) { - if (aQueryString[iParam].indexOf(strParamName + "=") == 0) { - var aParam = aQueryString[iParam].split("="); - strReturn = aParam[1]; - break; - } - } - } - return decodeURIComponent(strReturn); -} - -// Sets the initial topic selection -function setInitialTopic() { - - if (getCurrentHash()) - // There is an initial hash: Select it - hashChanged(); - else { - // Check the old way to set the selected topic ("topic" url parameter) - var initialTopic = getURLParam(window.location.href, "topic"); - if (initialTopic != "") - // Select the initial topic - selectByTitle(initialTopic); - else - // Select the first node - $("#treediv").jstree("select_node", $("#treediv li:first"), true); - } -} - -////////////////////////////////////////////////// -// NAVIGATION LINKS (NEXT, PREVIOUS, HOME) -////////////////////////////////////////////////// - -// Array with pages file names -var pageNames = new Array(); - -// Array with first anchor of each page -var anchorNames = new Array(); - -function getCurrentPageIndex() { - // Get the current topic page filename. This will fail on chrome local filesystem - try { - var fileName = getUrlFileName($("#mainFrame").prop("contentWindow").location.href.split("#")[0]); - //return pageNames.indexOf(fileName); < Fails on IE - return jQuery.inArray(fileName, pageNames); - } - catch (ex) { - return -1; - } -} - -// Set the current selected page by its index -function setCurrentPageIndex(idx) { - if( idx < 0 || idx >= pageNames.length ) - return; - selectByUrl(pageNames[idx] + "#" + anchorNames[idx]); -} - -function increasePageIndex(idxIncrease) { - var currentIdx = getCurrentPageIndex(); - if (currentIdx < 0) - return; - setCurrentPageIndex(currentIdx + idxIncrease); -} - -// Setup of home, next and previous links -function initializeNavigationLinks() { - - // Setup arrays - var lastPage = null; - var titlesCount = new Array(); - $("#treediv a").each(function() { - - // Setup arrays of each page first anchor - var pageHrefParts = $(this).attr("href").split("#"); - if (!lastPage || pageHrefParts[0] != lastPage) { - // New page: - pageNames[pageNames.length] = pageHrefParts[0]; - anchorNames[anchorNames.length] = pageHrefParts[1]; - lastPage = pageHrefParts[0]; - } - - // If there is reapeated titles, save its instance number. Used for page hash. - var title = cleanTitleText($(this).text()); - var count = titlesCount[title]; - if (!count) - count = 0; - count++; - if (count > 1) - $(this).prop("titleInstance", count); - titlesCount[title] = count; - - }); - - // If there is a single page, hide navigation links: - if (pageNames.length <= 1) { - $("#lnkPrevious").hide(); - $("#lnkNext").hide(); - $("#lnkHome").hide(); - } - - // Link event handlers - $("#lnkPrevious").click(function(e) { - e.preventDefault(); - increasePageIndex(-1); - }); - $("#lnkNext").click(function(e) { - e.preventDefault(); - increasePageIndex(1); - }); - $("#lnkHome").click(function(e) { - e.preventDefault(); - setCurrentPageIndex(0); - }); - - // Other actions - $("#lnkPrint").click(function(e) { - e.preventDefault(); - $("#mainFrame").focus().prop("contentWindow").print(); - }); - $("#lnkAbout").click(function(e) { - e.preventDefault(); - $("#aboutDlg").dialog("open"); - }); -} - -////////////////////////////////////////////////// -// PAGE INITIALIZATION -////////////////////////////////////////////////// - -$(document).ready(function() { - - // Create the tree - $("#treediv").jstree({ - // the `plugins` array allows you to configure the active plugins on this instance - "plugins": ["themes", "html_data", "ui", "hotkeys"], - // Single selection - "ui": { "select_limit": 1 }, - // Open/close node animation duration - "core": { "animation": 100 } - }) - // Tree node selection event: - .bind("select_node.jstree", function(event, data) { - // `data.rslt.obj` is the jquery extended node that was clicked - - // Open the selected node - $("#treediv").jstree("open_node", data.rslt.obj, false, false); - - // Get the tree node link - var link = data.rslt.obj.find("a:first"); - var url = link.attr("href"); - - // Load it as the current topic - loadUrlOnFrame(url); - - // Set the URL hash with the title - changeHash(link); - - }) - // Set initial selection - .bind("loaded.jstree", function(e, data) { - - // Travese the tree nodes to handle repeated titles and next / previous buttons - initializeNavigationLinks(); - - // Set the initial topic - setInitialTopic(); - }); - - // If a link is pressed into the frame, search and select the new URL into the tree: - $('#mainFrame').load(function() { - try { - // This will throw an exception with chrome on local system file - var url = getUrlFileName($(this).get(0).contentWindow.document.location.href); - selectByUrl(url); - } - catch (ex) { } - }); - - // Create contents tabs - $(".ui-layout-west").tabs({ - activate: function(event, ui) { - // Set the focus on the search fields when we change the current tab: - var tabId = ui.newPanel.attr('id'); - if (tabId == 'tab-index') - $("#searchTopic").focus(); - else if (tabId == 'tab-search') - $("#searchText").focus(); - } - }); - - // Create layouts - pageLayout = $('body').layout({ - west__size: .25 - , center__maskContents: true // IMPORTANT - enable iframe masking - }); - - // Set index events: - // Topic index textbox: - $("#searchTopic") - .keyup(function(e) { - if (e.which == 13) { - // Enter was pressed: Load the selected URL: - selectByUrl($("#topicsList").val()); - } - else { - // Select the first list topic starting with the typed text: - var currentText = $(this).val().toLowerCase(); - $("#topicsList").val( - $("#topicsList > option") - .filter(function(index) { - return cleanTitleText($(this).text()).toLowerCase().indexOf(currentText) == 0; - }) - .first() - .val() - ); - } - }); - // Index listbox: - $("#topicsList") - .keyup(function(e) { - if (e.which == 13) - // Enter was pressed: Load the selected URL: - selectByUrl($("#topicsList").val()); - }) - .change(function() { - // Selected topic changed: Set the topic textbox with the title: - $("#searchTopic").val($("#topicsList > option:selected").text()); - }) - .click(function() { - // Load the selected URL: - selectByUrl($("#topicsList").val()); - }); - - // Set text search type: - // Disable submit if there is nothing to search: - $("#searchText").keyup(function(e) { - $("#btnSearch").prop('disabled', $("#searchText").val() == ''); - }); - // Initial check: - $("#btnSearch").prop('disabled', $("#searchText").val() == ''); - if (fullSearch) { - // Hide the result list: - $("#searchResult").remove(); - // Handle the submit event: - $("#searchform").submit(function(e) { - e.preventDefault(); - loadUrlOnFrame("search.aspx?q=" + encodeURIComponent($("#searchText").val())); - }); - } - else { - // Handle the search form submit event: - $("#searchform").submit(function(e) { - - var searchText = $("#searchText").val(); - if (searchText == '') { - // Nothing to search: - e.preventDefault(); - return; - } - - // Clear previous search results: - var searchResultOptions = $("#searchResult").prop("options"); - searchResultOptions.length = 0; - - // Do the search over the tree: All links with the text, case insensitive - $("#treediv a") - .filter(function(index) { - return $(this).text().toLowerCase().indexOf(searchText) >= 0; - }) - .each(function() { - // Add an option to the search listbox with the text and the URL of the link: - searchResultOptions[searchResultOptions.length] = - new Option(cleanTitleText($(this).text()), $(this).attr('href')); - }); - - // Cancel submit - e.preventDefault(); - }); - - $("#searchResult") - .click(function() { - // Load the selected URL: - selectByUrl($("#searchResult").val()); - }) - .keyup(function(e) { - if (e.which == 13) - // Enter was pressed: Load the selected URL: - selectByUrl($("#searchResult").val()); - }); - } - - // About dialog: - $("#aboutDlg").dialog({ - modal: true, - autoOpen: false, - width: "auto", - buttons: { - Ok: function() { - $(this).dialog("close"); - } - } - }); - - if ("onhashchange" in window) { - // Browser supports hash change: Add the event handler - window.onhashchange = hashChanged; - } - -}); - +/* +* chmProcessor - Word converter to CHM +* Copyright (C) 2008 Toni Bennasar Obrador +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ + +// TODO: Add new needed translations + +var pageLayout; // a var is required because this page utilizes: pageLayout.allowOverflow() method + + +// Returns the last index of a character into a string. Returns < 0 if it was not found +// character: Character to search +if (!String.prototype.lastIndexOf) { + String.prototype.lastIndexOf = function(character) { + for (i = (this.length - 1); i >= 0; i--) { + if (this.charAt(i) == character) + return i; + } + return -1; + } +} + +// Returns the file name of a URL +function getUrlFileName(url) { + idx = url.lastIndexOf('/'); + if (idx >= 0) + return url.substring(idx + 1); + else + return url; +} + +// Loads a URL into the current topic iframe +// url: Relative topic URL to load +function loadUrlOnFrame(url) { + + var iframeSelector = $("#mainFrame"); + try { + // This will fail on chrome with file:// protocol + var currentUrl = getUrlFileName(iframeSelector.prop("contentWindow").location.href); + if (currentUrl.toLowerCase() == url.toLowerCase()) + return; + } + catch (ex) { } + + if ("onhashchange" in window) + // Browser supports hash changes. Use replace because it does not store an history browser point + iframeSelector.prop("contentWindow").location.replace(url); + else + // Browser does not support hash changes. Set the src attribute: It stores an history browser point + $("#mainFrame").attr("src", url); + +} + +// Select a tree node by its URL +// url: string with the URL to search +function selectByUrl(url) { + var fileName = getUrlFileName(url); + fileName = decodeURIComponent(fileName); // Needed if the hash part(xxx on a.html#xxx) contains spaces, it happens with word generated hashes + // Do tree selection, with hash: + var linkSelected = $('#treediv a[href="' + fileName + '"]').first(); + var loadFrame = true; + if (linkSelected.length == 0) { + // Not found: Do the selection without hash: + var parts = fileName.split("#"); + linkSelected = $('#treediv a[href^="' + parts[0] + '"]').first(); + loadFrame = false; + } + $("#treediv").jstree("select_node", linkSelected.parent(), loadFrame); + if( loadFrame ) + // Load the URL on the frame + loadUrlOnFrame(fileName); + else if( fileName.indexOf("search.aspx") == 0 ) { + // We are on the full text search page. Set the hash: + changeHash(fileName); + } +}; + +// Process a tree link text +function cleanTitleText(linkText) { + // Remove line breaks and leading spaces + var cleanText = $.trim( linkText.replace("\n", " ") ); + // Replace extra spaces by a single one + return cleanText.replace(/\s+/g, " "); +} + +function parseTitle(title) { + var result = new Object(); + result.instance = 0; + result.title = title; + + if (title.charAt(0) != "!") + return result; + title = title.substring(1); + var idx = title.indexOf("!"); + if( idx <= 0 ) + return result; + var number = parseInt(title.substring(0, idx)); + if( isNaN(number) ) + return result; + + result.instance = number - 1; + result.title = title.substring(idx + 1); + return result; +} + +// Select a tree node by its title +// title: Tree node title to select +function selectByTitle(title) { + + title = cleanTitleText(title).toLowerCase(); + + if (title == "") + // Select the first node + $("#treediv").jstree("select_node", $("#treediv li:first"), true); + else { + // Select the node by title + + // Check for instance number: + var searchInstance = parseTitle(title); + var titleSelector = $("#treediv a") + .filter(function(index) { + return cleanTitleText($(this).text()).toLowerCase() == searchInstance.title; + }); + if (searchInstance.instance == 0) + titleSelector = titleSelector.first(); + else + titleSelector = titleSelector.eq(searchInstance.instance); + + $("#treediv").jstree("select_node", + titleSelector.parent(), + true + ); + } +} + +////////////////////////////////////////////////// +// URL HASH HANDLERS +////////////////////////////////////////////////// + +// Return the window current hash +function getCurrentHash() { + // Firefox returns the hash unescaped, so decodeURIComponent fails... + /*var title = window.location.hash; + if (title.charAt(0) == '#') + title = title.substring(1);*/ + var hash = location.href.split("#")[1]; + if (!hash) + hash = ""; + return hash; +} + +// Hash change event handler +function hashChanged() { + var title = getCurrentHash(); + title = decodeURIComponent(title); + if (title.indexOf("search.aspx") == 0) + // Its a full text search URL + loadUrlOnFrame(title); + else + // Its a section title + selectByTitle(title); +} + +// Set a new URL hash +// linkSelector: The jquery tree title link, or a string with the search page URL +function changeHash(linkSelector) { + + if (!("onhashchange" in window)) + // Browser does not support hash change. Do nothing. + return; + + var newHash = null; + if (typeof linkSelector == 'string' || linkSelector instanceof String) { + // Is the url to the search page: + newHash = linkSelector; + } + else { + newHash = cleanTitleText(linkSelector.text()); + + // Check if its a duplicated title + var titleInstance = linkSelector.prop("titleInstance"); + if (titleInstance) + // Save instance number: + newHash = "!" + titleInstance + "!" + newHash; + + // The first node should no have hash: + if (cleanTitleText($("#treediv a:first").text()) == newHash) + newHash = ""; + } + + // Avoid to put the same hash twice + if (window.location.hash == newHash) + return; + + newHash = encodeURIComponent(newHash); + window.location.hash = newHash; +} + +////////////////////////////////////////////////// +// SET INITIAL TOPIC +// For compatibilty with frames template ("www.example.com/help/index.html?topic=topic title") +////////////////////////////////////////////////// + +// Returns the string value of the parameter strParamName on the url +// Returns an empty string if the parameter was not found +function getURLParam(url, strParamName) { + + var strReturn = ""; + var strHref = url; + var idxStart = strHref.indexOf("?"); + if (idxStart >= 0) { + var strQueryString = strHref.substr(idxStart+1); + var aQueryString = strQueryString.split("&"); + for (var iParam = 0; iParam < aQueryString.length; iParam++) { + if (aQueryString[iParam].indexOf(strParamName + "=") == 0) { + var aParam = aQueryString[iParam].split("="); + strReturn = aParam[1]; + break; + } + } + } + return decodeURIComponent(strReturn); +} + +// Sets the initial topic selection +function setInitialTopic() { + + if (getCurrentHash()) + // There is an initial hash: Select it + hashChanged(); + else { + // Check the old way to set the selected topic ("topic" url parameter) + var initialTopic = getURLParam(window.location.href, "topic"); + if (initialTopic != "") + // Select the initial topic + selectByTitle(initialTopic); + else + // Select the first node + $("#treediv").jstree("select_node", $("#treediv li:first"), true); + } +} + +////////////////////////////////////////////////// +// NAVIGATION LINKS (NEXT, PREVIOUS, HOME) +////////////////////////////////////////////////// + +// Array with pages file names +var pageNames = new Array(); + +// Array with first anchor of each page +var anchorNames = new Array(); + +function getCurrentPageIndex() { + // Get the current topic page filename. This will fail on chrome local filesystem + try { + var fileName = getUrlFileName($("#mainFrame").prop("contentWindow").location.href.split("#")[0]); + //return pageNames.indexOf(fileName); < Fails on IE + return jQuery.inArray(fileName, pageNames); + } + catch (ex) { + return -1; + } +} + +// Set the current selected page by its index +function setCurrentPageIndex(idx) { + if( idx < 0 || idx >= pageNames.length ) + return; + selectByUrl(pageNames[idx] + "#" + anchorNames[idx]); +} + +function increasePageIndex(idxIncrease) { + var currentIdx = getCurrentPageIndex(); + if (currentIdx < 0) + return; + setCurrentPageIndex(currentIdx + idxIncrease); +} + +// Setup of home, next and previous links +function initializeNavigationLinks() { + + // Setup arrays + var lastPage = null; + var titlesCount = new Array(); + $("#treediv a").each(function() { + + // Setup arrays of each page first anchor + var pageHrefParts = $(this).attr("href").split("#"); + if (!lastPage || pageHrefParts[0] != lastPage) { + // New page: + pageNames[pageNames.length] = pageHrefParts[0]; + anchorNames[anchorNames.length] = pageHrefParts[1]; + lastPage = pageHrefParts[0]; + } + + // If there is reapeated titles, save its instance number. Used for page hash. + var title = cleanTitleText($(this).text()); + var count = titlesCount[title]; + if (!count) + count = 0; + count++; + if (count > 1) + $(this).prop("titleInstance", count); + titlesCount[title] = count; + + }); + + // If there is a single page, hide navigation links: + if (pageNames.length <= 1) { + $("#lnkPrevious").hide(); + $("#lnkNext").hide(); + $("#lnkHome").hide(); + } + + // Link event handlers + $("#lnkPrevious").click(function(e) { + e.preventDefault(); + increasePageIndex(-1); + }); + $("#lnkNext").click(function(e) { + e.preventDefault(); + increasePageIndex(1); + }); + $("#lnkHome").click(function(e) { + e.preventDefault(); + setCurrentPageIndex(0); + }); + + // Other actions + $("#lnkPrint").click(function(e) { + e.preventDefault(); + $("#mainFrame").focus().prop("contentWindow").print(); + }); + $("#lnkAbout").click(function(e) { + e.preventDefault(); + $("#aboutDlg").dialog("open"); + }); +} + +////////////////////////////////////////////////// +// PAGE INITIALIZATION +////////////////////////////////////////////////// + +$(document).ready(function() { + + // Create the tree + $("#treediv").jstree({ + // the `plugins` array allows you to configure the active plugins on this instance + "plugins": ["themes", "html_data", "ui", "hotkeys"], + // Single selection + "ui": { "select_limit": 1 }, + // Open/close node animation duration + "core": { "animation": 100 } + }) + // Tree node selection event: + .bind("select_node.jstree", function(event, data) { + // `data.rslt.obj` is the jquery extended node that was clicked + + // Open the selected node + $("#treediv").jstree("open_node", data.rslt.obj, false, false); + + // Get the tree node link + var link = data.rslt.obj.find("a:first"); + var url = link.attr("href"); + + // Load it as the current topic + loadUrlOnFrame(url); + + // Set the URL hash with the title + changeHash(link); + + }) + // Set initial selection + .bind("loaded.jstree", function(e, data) { + + // Travese the tree nodes to handle repeated titles and next / previous buttons + initializeNavigationLinks(); + + // Set the initial topic + setInitialTopic(); + }); + + // If a link is pressed into the frame, search and select the new URL into the tree: + $('#mainFrame').load(function() { + try { + // This will throw an exception with chrome on local system file + var url = getUrlFileName($(this).get(0).contentWindow.document.location.href); + selectByUrl(url); + } + catch (ex) { } + }); + + // Create contents tabs + $(".ui-layout-west").tabs({ + activate: function(event, ui) { + // Set the focus on the search fields when we change the current tab: + var tabId = ui.newPanel.attr('id'); + if (tabId == 'tab-index') + $("#searchTopic").focus(); + else if (tabId == 'tab-search') + $("#searchText").focus(); + } + }); + + // Create layouts + pageLayout = $('body').layout({ + west__size: .25 + , center__maskContents: true // IMPORTANT - enable iframe masking + }); + + // Set index events: + // Topic index textbox: + $("#searchTopic") + .keyup(function(e) { + if (e.which == 13) { + // Enter was pressed: Load the selected URL: + selectByUrl($("#topicsList").val()); + } + else { + // Select the first list topic starting with the typed text: + var currentText = $(this).val().toLowerCase(); + $("#topicsList").val( + $("#topicsList > option") + .filter(function(index) { + return cleanTitleText($(this).text()).toLowerCase().indexOf(currentText) == 0; + }) + .first() + .val() + ); + } + }); + // Index listbox: + $("#topicsList") + .keyup(function(e) { + if (e.which == 13) + // Enter was pressed: Load the selected URL: + selectByUrl($("#topicsList").val()); + }) + .change(function() { + // Selected topic changed: Set the topic textbox with the title: + $("#searchTopic").val($("#topicsList > option:selected").text()); + }) + .click(function() { + // Load the selected URL: + selectByUrl($("#topicsList").val()); + }); + + // Set text search type: + // Disable submit if there is nothing to search: + $("#searchText").keyup(function(e) { + $("#btnSearch").prop('disabled', $("#searchText").val() == ''); + }); + // Initial check: + $("#btnSearch").prop('disabled', $("#searchText").val() == ''); + if (fullSearch) { + // Hide the result list: + $("#searchResult").remove(); + // Handle the submit event: + $("#searchform").submit(function(e) { + e.preventDefault(); + loadUrlOnFrame("search.aspx?q=" + encodeURIComponent($("#searchText").val())); + }); + } + else { + // Handle the search form submit event: + $("#searchform").submit(function(e) { + + var searchText = $("#searchText").val(); + if (searchText == '') { + // Nothing to search: + e.preventDefault(); + return; + } + + // Clear previous search results: + var searchResultOptions = $("#searchResult").prop("options"); + searchResultOptions.length = 0; + + // Do the search over the tree: All links with the text, case insensitive + $("#treediv a") + .filter(function(index) { + return $(this).text().toLowerCase().indexOf(searchText) >= 0; + }) + .each(function() { + // Add an option to the search listbox with the text and the URL of the link: + searchResultOptions[searchResultOptions.length] = + new Option(cleanTitleText($(this).text()), $(this).attr('href')); + }); + + // Cancel submit + e.preventDefault(); + }); + + $("#searchResult") + .click(function() { + // Load the selected URL: + selectByUrl($("#searchResult").val()); + }) + .keyup(function(e) { + if (e.which == 13) + // Enter was pressed: Load the selected URL: + selectByUrl($("#searchResult").val()); + }); + } + + // About dialog: + $("#aboutDlg").dialog({ + modal: true, + autoOpen: false, + width: "auto", + buttons: { + Ok: function() { + $(this).dialog("close"); + } + } + }); + + if ("onhashchange" in window) { + // Browser supports hash change: Add the event handler + window.onhashchange = hashChanged; + } + +}); + diff --git a/help/help_en/js/jquery.layout-latest.min.js b/help/help_en/js/jquery.layout-latest.min.js index 3f0dfea4..161d5361 100644 --- a/help/help_en/js/jquery.layout-latest.min.js +++ b/help/help_en/js/jquery.layout-latest.min.js @@ -1,142 +1,142 @@ -/* - jquery.layout 1.3.0 - Release Candidate 30.79 - $Date: 2013-01-01 08:00:00 (Tue, 1 Jan 2013) $ - $Rev: 303007 $ - - Copyright (c) 2013 Kevin Dalman (http://allpro.net) - Based on work by Fabrizio Balliano (http://www.fabrizioballiano.net) - - Dual licensed under the GPL (http://www.gnu.org/licenses/gpl.html) - and MIT (http://www.opensource.org/licenses/mit-license.php) licenses. - - SEE: http://layout.jquery-dev.net/LICENSE.txt - - Changelog: http://layout.jquery-dev.net/changelog.cfm#1.3.0.rc30.79 - - Docs: http://layout.jquery-dev.net/documentation.html - Tips: http://layout.jquery-dev.net/tips.html - Help: http://groups.google.com/group/jquery-ui-layout -*/ -(function(b){var a=Math.min,d=Math.max,c=Math.floor,f=function(a){return"string"===b.type(a)},j=function(a,d){if(b.isArray(d))for(var c=0,j=d.length;c').appendTo("body"),c={width:d.css("width")-d[0].clientWidth,height:d.height()-d[0].clientHeight}; -d.remove();window.scrollbarWidth=c.width;window.scrollbarHeight=c.height;return a.match(/^(width|height)$/)?c[a]:c},showInvisibly:function(b,a){if(b&&b.length&&(a||"none"===b.css("display"))){var d=b[0].style,d={display:d.display||"",visibility:d.visibility||""};b.css({display:"block",visibility:"hidden"});return d}return{}},getElementDimensions:function(a,c){var f={css:{},inset:{}},j=f.css,h={bottom:0},k=b.layout.cssNum,p=a.offset(),O,R,D;f.offsetLeft=p.left;f.offsetTop=p.top;c||(c={});b.each(["Left", -"Right","Top","Bottom"],function(d,k){O=j["border"+k]=b.layout.borderWidth(a,k);R=j["padding"+k]=b.layout.cssNum(a,"padding"+k);D=k.toLowerCase();f.inset[D]=0<=c[D]?c[D]:R;h[D]=f.inset[D]+O});j.width=a.width();j.height=a.height();j.top=k(a,"top",!0);j.bottom=k(a,"bottom",!0);j.left=k(a,"left",!0);j.right=k(a,"right",!0);f.outerWidth=a.outerWidth();f.outerHeight=a.outerHeight();f.innerWidth=d(0,f.outerWidth-h.left-h.right);f.innerHeight=d(0,f.outerHeight-h.top-h.bottom);f.layoutWidth=a.innerWidth(); -f.layoutHeight=a.innerHeight();return f},getElementStyles:function(b,a){var d={},c=b[0].style,f=a.split(","),k=["Top","Bottom","Left","Right"],j=["Color","Style","Width"],h,p,D,x,A,r;for(x=0;xA;A++)if(p=k[A],"border"===h)for(r=0;3>r;r++)D=j[r],d[h+p+D]=c[h+p+D];else d[h+p]=c[h+p];else d[h]=c[h];return d},cssWidth:function(a,c){if(0>=c)return 0;var f=!b.layout.browser.boxModel?"border-box":b.support.boxSizing?a.css("boxSizing"): -"content-box",j=b.layout.borderWidth,h=b.layout.cssNum,k=c;"border-box"!==f&&(k-=j(a,"Left")+j(a,"Right"));"content-box"===f&&(k-=h(a,"paddingLeft")+h(a,"paddingRight"));return d(0,k)},cssHeight:function(a,c){if(0>=c)return 0;var f=!b.layout.browser.boxModel?"border-box":b.support.boxSizing?a.css("boxSizing"):"content-box",j=b.layout.borderWidth,h=b.layout.cssNum,k=c;"border-box"!==f&&(k-=j(a,"Top")+j(a,"Bottom"));"content-box"===f&&(k-=h(a,"paddingTop")+h(a,"paddingBottom"));return d(0,k)},cssNum:function(a, -d,c){a.jquery||(a=b(a));var f=b.layout.showInvisibly(a);d=b.css(a[0],d,!0);c=c&&"auto"==d?d:Math.round(parseFloat(d)||0);a.css(f);return c},borderWidth:function(a,d){a.jquery&&(a=a[0]);var c="border"+d.substr(0,1).toUpperCase()+d.substr(1);return"none"===b.css(a,c+"Style",!0)?0:Math.round(parseFloat(b.css(a,c+"Width",!0))||0)},isMouseOverElem:function(a,d){var c=b(d||this),f=c.offset(),j=f.top,f=f.left,k=f+c.outerWidth(),c=j+c.outerHeight(),h=a.pageX,p=a.pageY;return b.layout.browser.msie&&0>h&&0> -p||h>=f&&h<=k&&p>=j&&p<=c},msg:function(a,d,c,f){b.isPlainObject(a)&&window.debugData?("string"===typeof d?(f=c,c=d):"object"===typeof c&&(f=c,c=null),c=c||"log( )",f=b.extend({sort:!1,returnHTML:!1,display:!1},f),!0===d||f.display?debugData(a,c,f):window.console&&console.log(debugData(a,c,f))):d?alert(a):window.console?console.log(a):(d=b("#layoutLogger"),d.length||(d=b('
XLayout console.log
    ').appendTo("body"), -d.css("left",b(window).width()-d.outerWidth()-5),b.ui.draggable&&d.draggable({handle:":first-child"})),d.children("ul").append('
  • '+a.replace(/\/g,">")+"
  • "))}};var h=navigator.userAgent.toLowerCase(),p=/(chrome)[ \/]([\w.]+)/.exec(h)||/(webkit)[ \/]([\w.]+)/.exec(h)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(h)||/(msie) ([\w.]+)/.exec(h)||0>h.indexOf("compatible")&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(h)|| -[],h=p[1]||"",p=p[2]||0,x="msie"===h;b.layout.browser={version:p,safari:"webkit"===h,webkit:"chrome"===h,msie:x,isIE6:x&&6==p,boxModel:!x||!1!==b.support.boxModel};h&&(b.layout.browser[h]=!0);x&&b(function(){b.layout.browser.boxModel=b.support.boxModel});b.layout.defaults={name:"",containerClass:"ui-layout-container",inset:null,scrollToBookmarkOnLoad:!0,resizeWithWindow:!0,resizeWithWindowDelay:200,resizeWithWindowMaxDelay:0,maskPanesEarly:!1,onresizeall_start:null,onresizeall_end:null,onload_start:null, -onload_end:null,onunload_start:null,onunload_end:null,initPanes:!0,showErrorMessages:!0,showDebugMessages:!1,zIndex:null,zIndexes:{pane_normal:0,content_mask:1,resizer_normal:2,pane_sliding:100,pane_animate:1E3,resizer_drag:1E4},errors:{pane:"pane",selector:"selector",addButtonError:"Error Adding Button\nInvalid ",containerMissing:"UI Layout Initialization Error\nThe specified layout-container does not exist.",centerPaneMissing:"UI Layout Initialization Error\nThe center-pane element does not exist.\nThe center-pane is a required element.", -noContainerHeight:"UI Layout Initialization Warning\nThe layout-container \"CONTAINER\" has no height.\nTherefore the layout is 0-height and hence 'invisible'!",callbackError:"UI Layout Callback Error\nThe EVENT callback is not a valid function."},panes:{applyDemoStyles:!1,closable:!0,resizable:!0,slidable:!0,initClosed:!1,initHidden:!1,contentSelector:".ui-layout-content",contentIgnoreSelector:".ui-layout-ignore",findNestedContent:!1,paneClass:"ui-layout-pane",resizerClass:"ui-layout-resizer",togglerClass:"ui-layout-toggler", -buttonClass:"ui-layout-button",minSize:0,maxSize:0,spacing_open:6,spacing_closed:6,togglerLength_open:50,togglerLength_closed:50,togglerAlign_open:"center",togglerAlign_closed:"center",togglerContent_open:"",togglerContent_closed:"",resizerDblClickToggle:!0,autoResize:!0,autoReopen:!0,resizerDragOpacity:1,maskContents:!1,maskObjects:!1,maskZindex:null,resizingGrid:!1,livePaneResizing:!1,liveContentResizing:!1,liveResizingTolerance:1,sliderCursor:"pointer",slideTrigger_open:"click",slideTrigger_close:"mouseleave", -slideDelay_open:300,slideDelay_close:300,hideTogglerOnSlide:!1,preventQuickSlideClose:b.layout.browser.webkit,preventPrematureSlideClose:!1,tips:{Open:"Open",Close:"Close",Resize:"Resize",Slide:"Slide Open",Pin:"Pin",Unpin:"Un-Pin",noRoomToOpen:"Not enough room to show this panel.",minSizeWarning:"Panel has reached its minimum size",maxSizeWarning:"Panel has reached its maximum size"},showOverflowOnHover:!1,enableCursorHotkey:!0,customHotkeyModifier:"SHIFT",fxName:"slide",fxSpeed:null,fxSettings:{}, -fxOpacityFix:!0,animatePaneSizing:!1,children:null,containerSelector:"",initChildren:!0,destroyChildren:!0,resizeChildren:!0,triggerEventsOnLoad:!1,triggerEventsDuringLiveResize:!0,onshow_start:null,onshow_end:null,onhide_start:null,onhide_end:null,onopen_start:null,onopen_end:null,onclose_start:null,onclose_end:null,onresize_start:null,onresize_end:null,onsizecontent_start:null,onsizecontent_end:null,onswap_start:null,onswap_end:null,ondrag_start:null,ondrag_end:null},north:{paneSelector:".ui-layout-north", -size:"auto",resizerCursor:"n-resize",customHotkey:""},south:{paneSelector:".ui-layout-south",size:"auto",resizerCursor:"s-resize",customHotkey:""},east:{paneSelector:".ui-layout-east",size:200,resizerCursor:"e-resize",customHotkey:""},west:{paneSelector:".ui-layout-west",size:200,resizerCursor:"w-resize",customHotkey:""},center:{paneSelector:".ui-layout-center",minWidth:0,minHeight:0}};b.layout.optionsMap={layout:"name instanceKey stateManagement effects inset zIndexes errors zIndex scrollToBookmarkOnLoad showErrorMessages maskPanesEarly outset resizeWithWindow resizeWithWindowDelay resizeWithWindowMaxDelay onresizeall onresizeall_start onresizeall_end onload onload_start onload_end onunload onunload_start onunload_end".split(" "), -center:"paneClass contentSelector contentIgnoreSelector findNestedContent applyDemoStyles triggerEventsOnLoad showOverflowOnHover maskContents maskObjects liveContentResizing containerSelector children initChildren resizeChildren destroyChildren onresize onresize_start onresize_end onsizecontent onsizecontent_start onsizecontent_end".split(" "),noDefault:["paneSelector","resizerCursor","customHotkey"]};b.layout.transformData=function(a,d){var c=d?{panes:{},center:{}}:{},f,j,k,h,p,x,D;if("object"!== -typeof a)return c;for(j in a){f=c;p=a[j];k=j.split("__");D=k.length-1;for(x=0;x<=D;x++)h=k[x],x===D?f[h]=b.isPlainObject(p)?b.layout.transformData(p):p:(f[h]||(f[h]={}),f=f[h])}return c};b.layout.backwardCompatibility={map:{applyDefaultStyles:"applyDemoStyles",childOptions:"children",initChildLayout:"initChildren",destroyChildLayout:"destroyChildren",resizeChildLayout:"resizeChildren",resizeNestedLayout:"resizeChildren",resizeWhileDragging:"livePaneResizing",resizeContentWhileDragging:"liveContentResizing", -triggerEventsWhileDragging:"triggerEventsDuringLiveResize",maskIframesOnResize:"maskContents",useStateCookie:"stateManagement.enabled","cookie.autoLoad":"stateManagement.autoLoad","cookie.autoSave":"stateManagement.autoSave","cookie.keys":"stateManagement.stateKeys","cookie.name":"stateManagement.cookie.name","cookie.domain":"stateManagement.cookie.domain","cookie.path":"stateManagement.cookie.path","cookie.expires":"stateManagement.cookie.expires","cookie.secure":"stateManagement.cookie.secure", -noRoomToOpenTip:"tips.noRoomToOpen",togglerTip_open:"tips.Close",togglerTip_closed:"tips.Open",resizerTip:"tips.Resize",sliderTip:"tips.Slide"},renameOptions:function(a){function d(b,c){for(var f=b.split("."),k=f.length-1,j={branch:a,key:f[k]},r=0,q;rw)return!0;var m={38:"north",40:"south",37:"west",39:"east"},a=e.shiftKey,g=e.ctrlKey,t,n,d,c;g&&(37<=w&&40>=w)&&r[m[w]].enableCursorHotkey?c=m[w]:(g||a)&&b.each(k.borderPanes,function(e, -b){t=r[b];n=t.customHotkey;d=t.customHotkeyModifier;if(a&&"SHIFT"==d||g&&"CTRL"==d||g&&a)if(n&&w===(isNaN(n)||9>=n?n.toUpperCase().charCodeAt(0):n))return c=b,!1});if(!c||!y[c]||!r[c].closable||q[c].isHidden)return!0;na(c);e.stopPropagation();return e.returnValue=!1}function x(e){if(H()){this&&this.tagName&&(e=this);var w;f(e)?w=y[e]:b(e).data("layoutRole")?w=b(e):b(e).parents().each(function(){if(b(this).data("layoutRole"))return w=b(this),!1});if(w&&w.length){var m=w.data("layoutEdge");e=q[m];e.cssSaved&& -X(m);if(e.isSliding||e.isResizing||e.isClosed)e.cssSaved=!1;else{var a={zIndex:r.zIndexes.resizer_normal+1},g={},t=w.css("overflow"),n=w.css("overflowX"),d=w.css("overflowY");"visible"!=t&&(g.overflow=t,a.overflow="visible");n&&!n.match(/(visible|auto)/)&&(g.overflowX=n,a.overflowX="visible");d&&!d.match(/(visible|auto)/)&&(g.overflowY=n,a.overflowY="visible");e.cssSaved=g;w.css(a);b.each(k.allPanes,function(e,b){b!=m&&X(b)})}}}}function X(e){if(H()){this&&this.tagName&&(e=this);var w;f(e)?w=y[e]: -b(e).data("layoutRole")?w=b(e):b(e).parents().each(function(){if(b(this).data("layoutRole"))return w=b(this),!1});if(w&&w.length){e=w.data("layoutEdge");e=q[e];var m=e.cssSaved||{};!e.isSliding&&!e.isResizing&&w.css("zIndex",r.zIndexes.pane_normal);w.css(m);e.cssSaved=!1}}}var G=b.layout.browser,k=b.layout.config,Q=b.layout.cssWidth,O=b.layout.cssHeight,R=b.layout.getElementDimensions,D=b.layout.getElementStyles,Ma=b.layout.getEventObject,A=b.layout.parsePaneName,r=b.extend(!0,{},b.layout.defaults); -r.effects=b.extend(!0,{},b.layout.effects);var q={id:"layout"+b.now(),initialized:!1,paneResizing:!1,panesSliding:{},container:{innerWidth:0,innerHeight:0,outerWidth:0,outerHeight:0,layoutWidth:0,layoutHeight:0},north:{childIdx:0},south:{childIdx:0},east:{childIdx:0},west:{childIdx:0},center:{childIdx:0}},ba={north:null,south:null,east:null,west:null,center:null},M={data:{},set:function(e,b,m){M.clear(e);M.data[e]=setTimeout(b,m)},clear:function(e){var b=M.data;b[e]&&(clearTimeout(b[e]),delete b[e])}}, -ca=function(e,w,m){var a=r;(a.showErrorMessages&&!m||m&&a.showDebugMessages)&&b.layout.msg(a.name+" / "+e,!1!==w);return!1},C=function(e,w,m){var a=w&&f(w),g=a?q[w]:q,t=a?r[w]:r,n=r.name,d=e+(e.match(/_/)?"":"_end"),c=d.match(/_end$/)?d.substr(0,d.length-4):"",l=t[d]||t[c],h="NC",k=[];!a&&"boolean"===b.type(w)&&(m=w,w="");if(l)try{f(l)&&(l.match(/,/)?(k=l.split(","),l=eval(k[0])):l=eval(l)),b.isFunction(l)&&(h=k.length?l(k[1]):a?l(w,y[w],g,t,n):l(z,g,t,n))}catch(j){ca(r.errors.callbackError.replace(/EVENT/, -b.trim((w||"")+" "+d)),!1),"string"===b.type(j)&&string.length&&ca("Exception: "+j,!1)}!m&&!1!==h&&(a?(m=y[w],t=r[w],g=q[w],m.triggerHandler("layoutpane"+d,[w,m,g,t,n]),c&&m.triggerHandler("layoutpane"+c,[w,m,g,t,n])):(u.triggerHandler("layout"+d,[z,g,t,n]),c&&u.triggerHandler("layout"+c,[z,g,t,n])));a&&"onresize_end"===e&&db(w+"",!0);return h},eb=function(e){if(!G.mozilla){var b=y[e];"IFRAME"===q[e].tagName?b.css(k.hidden).css(k.visible):b.find("IFRAME").css(k.hidden).css(k.visible)}},ya=function(e){var b= -y[e];e=k[e].dir;b={minWidth:1001-Q(b,1E3),minHeight:1001-O(b,1E3)};"horz"===e&&(b.minSize=b.minHeight);"vert"===e&&(b.minSize=b.minWidth);return b},fa=function(e,w,m){m||(m=k[e].dir);f(w)&&w.match(/%/)&&(w="100%"===w?-1:parseInt(w,10)/100);if(0===w)return 0;if(1<=w)return parseInt(w,10);var a=r,g=0;"horz"==m?g=v.innerHeight-(y.north?a.north.spacing_open:0)-(y.south?a.south.spacing_open:0):"vert"==m&&(g=v.innerWidth-(y.west?a.west.spacing_open:0)-(y.east?a.east.spacing_open:0));if(-1===w)return g; -if(0b&&(b=100);M.clear("winResize");M.set("winResize",function(){M.clear("winResize");M.clear("winResizeRepeater");var b=R(u,e.inset);(b.innerWidth!==v.innerWidth||b.innerHeight!==v.innerHeight)&&oa()},b);M.data.winResizeRepeater||lb()},lb=function(){var e= -Number(r.resizeWithWindowMaxDelay);0"),l=l.toggler=g.closable?P[a]=b("
    "):!1;!d.isVisible&&g.slidable&&j.attr("title",g.tips.Slide).css("cursor",g.sliderCursor);j.attr("id",c?c+"-resizer":"").data({parentLayout:z,layoutPane:z[a],layoutEdge:a,layoutRole:"resizer"}).css(k.resizers.cssReq).css("zIndex",r.zIndexes.resizer_normal).css(g.applyDemoStyles?k.resizers.cssDemo:{}).addClass(n+" "+n+h).hover(Oa,da).hover(fb,gb).appendTo(u); -g.resizerDblClickToggle&&j.bind("dblclick."+K,na);l&&(l.attr("id",c?c+"-toggler":"").data({parentLayout:z,layoutPane:z[a],layoutEdge:a,layoutRole:"toggler"}).css(k.togglers.cssReq).css(g.applyDemoStyles?k.togglers.cssDemo:{}).addClass(f+" "+f+h).hover(Oa,da).bind("mouseenter",fb).appendTo(j),g.togglerContent_open&&b(""+g.togglerContent_open+"").data({layoutEdge:a,layoutRole:"togglerContent"}).data("layoutRole","togglerContent").data("layoutEdge",a).addClass("content content-open").css("display", -"none").appendTo(l),g.togglerContent_closed&&b(""+g.togglerContent_closed+"").data({layoutEdge:a,layoutRole:"togglerContent"}).addClass("content content-closed").css("display","none").appendTo(l),pb(a));var g=a,B=b.layout.plugins.draggable,g=g?g.split(","):k.borderPanes;b.each(g,function(e,a){var g=r[a];if(!B||!y[a]||!g.resizable)return g.resizable=!1,!0;var m=q[a],w=r.zIndexes,d=k[a],c="horz"==d.dir?"top":"left",t=F[a],n=g.resizerClass,f=0,l,h,E=n+"-drag",j=n+"-"+a+"-drag",J=n+"-dragging", -zb=n+"-"+a+"-dragging",cb=n+"-dragging-limit",v=n+"-"+a+"-dragging-limit",x=!1;m.isClosed||t.attr("title",g.tips.Resize).css("cursor",g.resizerCursor);t.draggable({containment:u[0],axis:"horz"==d.dir?"y":"x",delay:0,distance:1,grid:g.resizingGrid,helper:"clone",opacity:g.resizerDragOpacity,addClasses:!1,zIndex:w.resizer_drag,start:function(e,w){g=r[a];m=q[a];h=g.livePaneResizing;if(!1===C("ondrag_start",a))return!1;m.isResizing=!0;q.paneResizing=a;M.clear(a+"_closeSlider");Y(a);l=m.resizerPosition; -f=w.position[c];t.addClass(E+" "+j);x=!1;b("body").disableSelection();va(a,{resizing:!0})},drag:function(e,b){x||(b.helper.addClass(J+" "+zb).css({right:"auto",bottom:"auto"}).children().css("visibility","hidden"),x=!0,m.isSliding&&y[a].css("zIndex",w.pane_sliding));var d=0;b.position[c]l.max&&(b.position[c]=l.max,d=1);d?(b.helper.addClass(cb+" "+v),window.defaultStatus=0d&&a.match(/(south|east)/)?g.tips.maxSizeWarning: -g.tips.minSizeWarning):(b.helper.removeClass(cb+" "+v),window.defaultStatus="");h&&Math.abs(b.position[c]-f)>=g.liveResizingTolerance&&(f=b.position[c],p(e,b,a))},stop:function(e,g){b("body").enableSelection();window.defaultStatus="";t.removeClass(E+" "+j);m.isResizing=!1;q.paneResizing=!1;p(e,g,a,!0)}})});var p=function(b,e,a,g){var m=e.position,w=k[a];b=r[a];e=q[a];var d;switch(a){case "north":d=m.top;break;case "west":d=m.left;break;case "south":d=v.layoutHeight-m.top-b.spacing_open;break;case "east":d= -v.layoutWidth-m.left-b.spacing_open}d-=v.inset[w.side];g?(!1!==C("ondrag_end",a)&&Ca(a,d,!1,!0),za(!0),e.isSliding&&va(a,{resizing:!0})):Math.abs(d-e.size)j.maxSize)return Va(f,!1),!c&&h.tips.noRoomToOpen&&alert(h.tips.noRoomToOpen),a();b?wa(f,!0):j.isSliding?wa(f,!1):h.slidable&&ma(f,!1); -j.noRoom=!1;ha(f);p=j.isShowing;delete j.isShowing;l=!d&&j.isClosed&&"none"!=h.fxName_open;j.isMoving=!0;j.isVisible=!0;j.isClosed=!1;p&&(j.isHidden=!1);l?(Ga(f,!0),n.show(h.fxName_open,h.fxSettings_open,h.fxSpeed_open,function(){Ga(f,!1);j.isVisible&&g();a()})):(rb(f),g(),a())}})}},Ua=function(a,d){var c=y[a],f=F[a],g=P[a],h=r[a],n=q[a],j=k[a].side,p=h.resizerClass,l=h.togglerClass,u="-"+a;f.css(j,v.inset[j]+ga(a)).removeClass(p+"-closed "+p+u+"-closed").addClass(p+"-open "+p+u+"-open");n.isSliding? -f.addClass(p+"-sliding "+p+u+"-sliding"):f.removeClass(p+"-sliding "+p+u+"-sliding");da(0,f);h.resizable&&b.layout.plugins.draggable?f.draggable("enable").css("cursor",h.resizerCursor).attr("title",h.tips.Resize):n.isSliding||f.css("cursor","default");g&&(g.removeClass(l+"-closed "+l+u+"-closed").addClass(l+"-open "+l+u+"-open").attr("title",h.tips.Close),da(0,g),g.children(".content-closed").hide(),g.children(".content-open").css("display","block"));Va(a,!n.isSliding);b.extend(n,R(c));q.initialized&& -(qa(),pa(a,!0));if(!d&&(q.initialized||h.triggerEventsOnLoad)&&c.is(":visible"))C("onopen_end",a),n.isShowing&&C("onshow_end",a),q.initialized&&C("onresize_end",a)},sb=function(a){function b(){g.isClosed?g.isMoving||ra(c,!0):wa(c,!0)}if(H()){var d=Ma(a),c=A.call(this,a),g=q[c];a=r[c].slideDelay_open;d&&d.stopImmediatePropagation();g.isClosed&&d&&"mouseenter"===d.type&&0g.maxSize?ka(a,g.maxSize,c,!0,f):g.sizec?d(0,e.attempt-(e.actual-c)):d(0,e.attempt+(c-e.actual));h.cssSize=("horz"==k[n].dir?O:Q)(y[n],h.attempt);l.css(ua,h.cssSize);h.actual="width"==ua?l.outerWidth():l.outerHeight();h.correct=c===h.actual;1===a.length&&(ca(t,!1,!0),ca(e,!1,!0));ca(h,!1,!0);if(3B.width){var h=h.minWidth-j.outerWidth,B=r.east.minSize||0,x=r.west.minSize||0,Z=q.east.size,z=q.west.size,A=Z,D=z;0B)&&(A=d(Z-B,Z-h),h-=Z-A);0x)&&(D=d(z-x,z-h),h-=z-D);if(0===h){Z&&Z!=B&&ka("east",A,!0,!0,f);z&&z!=x&&ka("west",D,!0,!0,f);ia("center", -c,f);k.css(u);return}}}else{j.isVisible&&!j.noVerticalRoom&&b.extend(j,R(k),ya(e));if(!f&&!j.noVerticalRoom&&B.height===j.outerHeight)return k.css(u),!0;l.top=B.top;l.bottom=B.bottom;j.newSize=B.height;l.height=O(k,B.height);j.maxHeight=l.height;p=0<=j.maxHeight;p||(j.noVerticalRoom=!0)}p?(!c&&q.initialized&&C("onresize_start",e),k.css(l),"center"!==e&&qa(e),j.noRoom&&(!j.isClosed&&!j.isHidden)&&ha(e),j.isVisible&&(b.extend(j,R(k)),q.initialized&&pa(e))):!j.noRoom&&j.isVisible&&ha(e);k.css(u);delete j.newSize; -delete j.newWidth;delete j.newHeight;if(!j.isVisible)return!0;"center"===e&&(j=G.isIE6||!G.boxModel,y.north&&(j||"IFRAME"==q.north.tagName)&&y.north.css("width",Q(y.north,v.innerWidth)),y.south&&(j||"IFRAME"==q.south.tagName)&&y.south.css("width",Q(y.south,v.innerWidth)));!c&&q.initialized&&C("onresize_end",e)}})},oa=function(a){A(a);if(u.is(":visible"))if(q.initialized){if(!0===a&&b.isPlainObject(r.outset)&&u.css(r.outset),b.extend(v,R(u,r.inset)),v.outerHeight){!0===a&&ob();if(!1===C("onresizeall_start"))return!1; -var d,c,f;b.each(["south","north","east","west"],function(a,b){y[b]&&(c=r[b],f=q[b],f.autoResize&&f.size!=c.size?ka(b,c.size,!0,!0,!0):(Y(b),ha(b,!1,!0,!0)))});ia("",!0,!0);qa();b.each(k.allPanes,function(a,b){(d=y[b])&&q[b].isVisible&&C("onresize_end",b)});C("onresizeall_end")}}else Aa()},db=function(a,d){var c=A.call(this,a);r[c].resizeChildren&&(d||Ba(c),c=ba[c],b.isPlainObject(c)&&b.each(c,function(a,b){b.destroyed||b.resizeAll()}))},pa=function(a,c){if(H()){var h=A.call(this,a),h=h?h.split(","): -k.allPanes;b.each(h,function(a,e){function h(a){return d(u.css.paddingBottom,parseInt(a.css("marginBottom"),10)||0)}function j(){var a=r[e].contentIgnoreSelector,a=p.nextAll().not(".ui-layout-mask").not(a||":lt(0)"),b=a.filter(":visible"),d=b.filter(":last");v={top:p[0].offsetTop,height:p.outerHeight(),numFooters:a.length,hiddenFooters:a.length-b.length,spaceBelow:0};v.spaceAbove=v.top;v.bottom=v.top+v.height;v.spaceBelow=d.length?d[0].offsetTop+d.outerHeight()-v.bottom+h(d):h(p)}var m=y[e],p=U[e], -l=r[e],u=q[e],v=u.content;if(!m||!p||!m.is(":visible"))return!0;if(!p.length&&(Sa(e,!1),!p))return;if(!1!==C("onsizecontent_start",e)){if(!u.isMoving&&!u.isResizing||l.liveContentResizing||c||void 0==v.top)j(),0A)x=A,z=0;else if(f(z))switch(z){case "top":case "left":z=0;break;case "bottom":case "right":z=A-x;break; -default:z=c((A-x)/2)}else h=parseInt(z,10),z=0<=z?h:A-x+h;if("horz"===l){var D=Q(p,x);p.css({width:D,height:O(p,B),left:z,top:0});p.children(".content").each(function(){u=b(this);u.css("marginLeft",c((D-u.outerWidth())/2))})}else{var C=O(p,x);p.css({height:C,width:Q(p,B),top:z,left:0});p.children(".content").each(function(){u=b(this);u.css("marginTop",c((C-u.outerHeight())/2))})}da(0,p)}if(!q.initialized&&(e.initHidden||g.isHidden))j.hide(),p&&p.hide()}}})},pb=function(a){if(H()){var b=A.call(this, -a);a=P[b];var d=r[b];a&&(d.closable=!0,a.bind("click."+K,function(a){a.stopPropagation();na(b)}).css("visibility","visible").css("cursor","pointer").attr("title",q[b].isClosed?d.tips.Open:d.tips.Close).show())}},Va=function(a,d){b.layout.plugins.buttons&&b.each(q[a].pins,function(c,f){b.layout.buttons.setPinState(z,b(f),a,d)})},u=b(this).eq(0);if(!u.length)return ca(r.errors.containerMissing);if(u.data("layoutContainer")&&u.data("layout"))return u.data("layout");var y={},U={},F={},P={},ea=b([]),v= -q.container,K=q.id,z={options:r,state:q,container:u,panes:y,contents:U,resizers:F,togglers:P,hide:Ta,show:Fa,toggle:na,open:ra,close:ja,slideOpen:sb,slideClose:Wa,slideToggle:function(a){a=A.call(this,a);na(a,!0)},setSizeLimits:Y,_sizePane:ka,sizePane:Ca,sizeContent:pa,swapPanes:function(a,c){function f(a){var d=y[a],c=U[a];return!d?!1:{pane:a,P:d?d[0]:!1,C:c?c[0]:!1,state:b.extend(!0,{},q[a]),options:b.extend(!0,{},r[a])}}function h(a,c){if(a){var e=a.P,f=a.C,g=a.pane,j=k[c],m=b.extend(!0,{},q[c]), -n=r[c],w={resizerCursor:n.resizerCursor};b.each(["fxName","fxSpeed","fxSettings"],function(a,b){w[b+"_open"]=n[b+"_open"];w[b+"_close"]=n[b+"_close"];w[b+"_size"]=n[b+"_size"]});y[c]=b(e).data({layoutPane:z[c],layoutEdge:c}).css(k.hidden).css(j.cssReq);U[c]=f?b(f):!1;r[c]=b.extend(!0,{},a.options,w);q[c]=b.extend(!0,{},a.state);e.className=e.className.replace(RegExp(n.paneClass+"-"+g,"g"),n.paneClass+"-"+c);Pa(c);j.dir!=k[g].dir?(e=p[c]||0,Y(c),e=d(e,q[c].minSize),Ca(c,e,!0,!0)):F[c].css(j.side,v.inset[j.side]+ -(q[c].isVisible?ga(c):0));a.state.isVisible&&!m.isVisible?Ua(c,!0):(Da(c),ma(c,!0));a=null}}if(H()){var g=A.call(this,a);q[g].edge=c;q[c].edge=g;if(!1===C("onswap_start",g)||!1===C("onswap_start",c))q[g].edge=g,q[c].edge=c;else{var j=f(g),n=f(c),p={};p[g]=j?j.state.size:0;p[c]=n?n.state.size:0;y[g]=!1;y[c]=!1;q[g]={};q[c]={};P[g]&&P[g].remove();P[c]&&P[c].remove();F[g]&&F[g].remove();F[c]&&F[c].remove();F[g]=F[c]=P[g]=P[c]=!1;h(j,c);h(n,g);j=n=p=null;y[g]&&y[g].css(k.visible);y[c]&&y[c].css(k.visible); -oa();C("onswap_end",g);C("onswap_end",c)}}},showMasks:va,hideMasks:za,initContent:Sa,addPane:ib,removePane:Ra,createChildren:Qa,refreshChildren:Ba,enableClosable:pb,disableClosable:function(a,b){if(H()){var c=A.call(this,a),d=P[c];d&&(r[c].closable=!1,q[c].isClosed&&ra(c,!1,!0),d.unbind("."+K).css("visibility",b?"hidden":"visible").css("cursor","default").attr("title",""))}},enableSlidable:function(a){if(H()){a=A.call(this,a);var b=F[a];b&&b.data("draggable")&&(r[a].slidable=!0,q[a].isClosed&&ma(a, -!0))}},disableSlidable:function(a){if(H()){a=A.call(this,a);var b=F[a];b&&(r[a].slidable=!1,q[a].isSliding?ja(a,!1,!0):(ma(a,!1),b.css("cursor","default").attr("title",""),da(null,b[0])))}},enableResizable:function(a){if(H()){a=A.call(this,a);var b=F[a],c=r[a];b&&b.data("draggable")&&(c.resizable=!0,b.draggable("enable"),q[a].isClosed||b.css("cursor",c.resizerCursor).attr("title",c.tips.Resize))}},disableResizable:function(a){if(H()){a=A.call(this,a);var b=F[a];b&&b.data("draggable")&&(r[a].resizable= -!1,b.draggable("disable").css("cursor","default").attr("title",""),da(null,b[0]))}},allowOverflow:x,resetOverflow:X,destroy:function(a,c){b(window).unbind("."+K);b(document).unbind("."+K);"object"===typeof a?A(a):c=a;u.clearQueue().removeData("layout").removeData("layoutContainer").removeClass(r.containerClass).unbind("."+K);ea.remove();b.each(k.allPanes,function(a,b){Ra(b,!1,!0,c)});u.data("layoutCSS")&&!u.data("layoutRole")&&u.css(u.data("layoutCSS")).removeData("layoutCSS");"BODY"===v.tagName&& -(u=b("html")).data("layoutCSS")&&u.css(u.data("layoutCSS")).removeData("layoutCSS");j(z,b.layout.onDestroy);mb();for(var d in z)d.match(/^(container|options)$/)||delete z[d];z.destroyed=!0;return z},initPanes:H,resizeAll:oa,runCallbacks:C,hasParentLayout:!1,children:ba,north:!1,south:!1,west:!1,east:!1,center:!1},Xa;var V,Ya,N,Ha,la,sa,W;h=b.layout.transformData(h,!0);h=b.layout.backwardCompatibility.renameAllOptions(h);if(!b.isEmptyObject(h.panes)){V=b.layout.optionsMap.noDefault;la=0;for(sa=V.length;la< -sa;la++)N=V[la],delete h.panes[N];V=b.layout.optionsMap.layout;la=0;for(sa=V.length;lab.inArray(N,Bb)&&0>b.inArray(N,V)&&(h.panes[N]||(h.panes[N]=b.isPlainObject(Ha)?b.extend(!0,{},Ha):Ha),delete h[N]);b.extend(!0,r,h);b.each(k.allPanes,function(a,c){k[c]=b.extend(!0,{},k.panes,k[c]);Ya=r.panes;W=r[c];if("center"===c){V=b.layout.optionsMap.center;a=0;for(sa=V.length;av.innerHeight&&ca(L.errors.noContainerHeight.replace(/CONTAINER/,v.ref)))}ta(u,"minWidth")&&u.parent().css("overflowX","auto");ta(u,"minHeight")&&u.parent().css("overflowY","auto")}catch(Db){}nb();b(window).bind("unload."+K,mb);j(z,b.layout.onLoad);Cb.initPanes&&Aa();delete tb.creatingLayout;Xa=q.initialized}return"cancel"===Xa?null:z}})(jQuery); -(function(b){b.ui||(b.ui={});b.ui.cookie={acceptsCookies:!!navigator.cookieEnabled,read:function(a){for(var d=document.cookie,d=d?d.split(";"):[],c,f=0,j=d.length;fb.inArray(T,p)||(S=h[T][I],void 0!=S&&("isClosed"==I&&h[T].isSliding&&(S=!0),(x[T]||(x[T]={}))[j[I]?j[I]:I]=S));f&&b.each(p,function(c,d){G=a.children[d];X=h.stateData[d];b.isPlainObject(G)&&!b.isEmptyObject(G)&&(k=x[d]||(x[d]={}),k.children||(k.children={}),b.each(G,function(a,c){c.state.initialized?k.children[a]=b.layout.state.readState(c): -X&&(X.children&&X.children[a])&&(k.children[a]=b.extend(!0,{},X.children[a]))}))});return x},encodeJSON:function(a){function d(a){var f=[],j=0,h,p,x,I=b.isArray(a);for(h in a)p=a[h],x=typeof p,"string"==x?p='"'+p+'"':"object"==x&&(p=d(p)),f[j++]=(!I?'"'+h+'":':"")+p;return(I?"[":"{")+f.join(",")+(I?"]":"}")}return d(a)},decodeJSON:function(a){try{return b.parseJSON?b.parseJSON(a):window.eval("("+a+")")||{}}catch(d){return{}}},_create:function(a){var d=b.layout.state,c=a.options.stateManagement;b.extend(a, -{readCookie:function(){return d.readCookie(a)},deleteCookie:function(){d.deleteCookie(a)},saveCookie:function(b,c){return d.saveCookie(a,b,c)},loadCookie:function(){return d.loadCookie(a)},loadState:function(b,c){d.loadState(a,b,c)},readState:function(b){return d.readState(a,b)},encodeJSON:d.encodeJSON,decodeJSON:d.decodeJSON});a.state.stateData={};if(c.autoLoad)if(b.isPlainObject(c.autoLoad))b.isEmptyObject(c.autoLoad)||a.loadState(c.autoLoad);else if(c.enabled)if(b.isFunction(c.autoLoad)){var f= -{};try{f=c.autoLoad(a,a.state,a.options,a.options.name||"")}catch(j){}f&&(b.isPlainObject(f)&&!b.isEmptyObject(f))&&a.loadState(f)}else a.loadCookie()},_unload:function(a){var d=a.options.stateManagement;if(d.enabled&&d.autoSave)if(b.isFunction(d.autoSave))try{d.autoSave(a,a.state,a.options,a.options.name||"")}catch(c){}else a.saveCookie()}};b.layout.onCreate.push(b.layout.state._create);b.layout.onUnload.push(b.layout.state._unload);b.layout.plugins.buttons=!0;b.layout.defaults.autoBindCustomButtons= -!1;b.layout.optionsMap.layout.push("autoBindCustomButtons");b.layout.buttons={init:function(a){var d=a.options.name||"",c;b.each("toggle open close pin toggle-slide open-slide".split(" "),function(f,j){b.each(b.layout.config.borderPanes,function(f,p){b(".ui-layout-button-"+j+"-"+p).each(function(){c=b(this).data("layoutName")||b(this).attr("layoutName");(void 0==c||c===d)&&a.bindButton(this,j,p)})})})},get:function(a,d,c,f){var j=b(d);a=a.options;var h=a.errors.addButtonError;j.length?0>b.inArray(c, -b.layout.config.borderPanes)?(b.layout.msg(h+" "+a.errors.pane+": "+c,!0),j=b("")):(d=a[c].buttonClass+"-"+f,j.addClass(d+" "+d+"-"+c).data("layoutName",a.name)):b.layout.msg(h+" "+a.errors.selector+": "+d,!0);return j},bind:function(a,d,c,f){var j=b.layout.buttons;switch(c.toLowerCase()){case "toggle":j.addToggle(a,d,f);break;case "open":j.addOpen(a,d,f);break;case "close":j.addClose(a,d,f);break;case "pin":j.addPin(a,d,f);break;case "toggle-slide":j.addToggle(a,d,f,!0);break;case "open-slide":j.addOpen(a, -d,f,!0)}return a},addToggle:function(a,d,c,f){b.layout.buttons.get(a,d,c,"toggle").click(function(b){a.toggle(c,!!f);b.stopPropagation()});return a},addOpen:function(a,d,c,f){b.layout.buttons.get(a,d,c,"open").attr("title",a.options[c].tips.Open).click(function(b){a.open(c,!!f);b.stopPropagation()});return a},addClose:function(a,d,c){b.layout.buttons.get(a,d,c,"close").attr("title",a.options[c].tips.Close).click(function(b){a.close(c);b.stopPropagation()});return a},addPin:function(a,d,c){var f=b.layout.buttons, -j=f.get(a,d,c,"pin");if(j.length){var h=a.state[c];j.click(function(d){f.setPinState(a,b(this),c,h.isSliding||h.isClosed);h.isSliding||h.isClosed?a.open(c):a.close(c);d.stopPropagation()});f.setPinState(a,j,c,!h.isClosed&&!h.isSliding);h.pins.push(d)}return a},setPinState:function(a,b,c,f){var j=b.attr("pin");if(!(j&&f===("down"==j))){a=a.options[c];var j=a.buttonClass+"-pin",h=j+"-"+c;c=j+"-up "+h+"-up";j=j+"-down "+h+"-down";b.attr("pin",f?"down":"up").attr("title",f?a.tips.Unpin:a.tips.Pin).removeClass(f? -c:j).addClass(f?j:c)}},syncPinBtns:function(a,d,c){b.each(a.state[d].pins,function(f,j){b.layout.buttons.setPinState(a,b(j),d,c)})},_load:function(a){var d=b.layout.buttons;b.extend(a,{bindButton:function(b,c,h){return d.bind(a,b,c,h)},addToggleBtn:function(b,c,h){return d.addToggle(a,b,c,h)},addOpenBtn:function(b,c,h){return d.addOpen(a,b,c,h)},addCloseBtn:function(b,c){return d.addClose(a,b,c)},addPinBtn:function(b,c){return d.addPin(a,b,c)}});for(var c=0;4>c;c++)a.state[b.layout.config.borderPanes[c]].pins= -[];a.options.autoBindCustomButtons&&d.init(a)},_unload:function(){}};b.layout.onLoad.push(b.layout.buttons._load);b.layout.plugins.browserZoom=!0;b.layout.defaults.browserZoomCheckInterval=1E3;b.layout.optionsMap.layout.push("browserZoomCheckInterval");b.layout.browserZoom={_init:function(a){!1!==b.layout.browserZoom.ratio()&&b.layout.browserZoom._setTimer(a)},_setTimer:function(a){if(!a.destroyed){var d=a.options,c=a.state,f=a.hasParentLayout?5E3:Math.max(d.browserZoomCheckInterval,100);setTimeout(function(){if(!a.destroyed&& -d.resizeWithWindow){var f=b.layout.browserZoom.ratio();f!==c.browserZoom&&(c.browserZoom=f,a.resizeAll());b.layout.browserZoom._setTimer(a)}},f)}},ratio:function(){function a(a,b){return(100*(parseInt(a,10)/parseInt(b,10))).toFixed()}var d=window,c=screen,f=document,j=f.documentElement||f.body,h=b.layout.browser,p=h.version,x,I,T;return h.msie&&8').appendTo("body"),c={width:d.css("width")-d[0].clientWidth,height:d.height()-d[0].clientHeight}; +d.remove();window.scrollbarWidth=c.width;window.scrollbarHeight=c.height;return a.match(/^(width|height)$/)?c[a]:c},showInvisibly:function(b,a){if(b&&b.length&&(a||"none"===b.css("display"))){var d=b[0].style,d={display:d.display||"",visibility:d.visibility||""};b.css({display:"block",visibility:"hidden"});return d}return{}},getElementDimensions:function(a,c){var f={css:{},inset:{}},j=f.css,h={bottom:0},k=b.layout.cssNum,p=a.offset(),O,R,D;f.offsetLeft=p.left;f.offsetTop=p.top;c||(c={});b.each(["Left", +"Right","Top","Bottom"],function(d,k){O=j["border"+k]=b.layout.borderWidth(a,k);R=j["padding"+k]=b.layout.cssNum(a,"padding"+k);D=k.toLowerCase();f.inset[D]=0<=c[D]?c[D]:R;h[D]=f.inset[D]+O});j.width=a.width();j.height=a.height();j.top=k(a,"top",!0);j.bottom=k(a,"bottom",!0);j.left=k(a,"left",!0);j.right=k(a,"right",!0);f.outerWidth=a.outerWidth();f.outerHeight=a.outerHeight();f.innerWidth=d(0,f.outerWidth-h.left-h.right);f.innerHeight=d(0,f.outerHeight-h.top-h.bottom);f.layoutWidth=a.innerWidth(); +f.layoutHeight=a.innerHeight();return f},getElementStyles:function(b,a){var d={},c=b[0].style,f=a.split(","),k=["Top","Bottom","Left","Right"],j=["Color","Style","Width"],h,p,D,x,A,r;for(x=0;xA;A++)if(p=k[A],"border"===h)for(r=0;3>r;r++)D=j[r],d[h+p+D]=c[h+p+D];else d[h+p]=c[h+p];else d[h]=c[h];return d},cssWidth:function(a,c){if(0>=c)return 0;var f=!b.layout.browser.boxModel?"border-box":b.support.boxSizing?a.css("boxSizing"): +"content-box",j=b.layout.borderWidth,h=b.layout.cssNum,k=c;"border-box"!==f&&(k-=j(a,"Left")+j(a,"Right"));"content-box"===f&&(k-=h(a,"paddingLeft")+h(a,"paddingRight"));return d(0,k)},cssHeight:function(a,c){if(0>=c)return 0;var f=!b.layout.browser.boxModel?"border-box":b.support.boxSizing?a.css("boxSizing"):"content-box",j=b.layout.borderWidth,h=b.layout.cssNum,k=c;"border-box"!==f&&(k-=j(a,"Top")+j(a,"Bottom"));"content-box"===f&&(k-=h(a,"paddingTop")+h(a,"paddingBottom"));return d(0,k)},cssNum:function(a, +d,c){a.jquery||(a=b(a));var f=b.layout.showInvisibly(a);d=b.css(a[0],d,!0);c=c&&"auto"==d?d:Math.round(parseFloat(d)||0);a.css(f);return c},borderWidth:function(a,d){a.jquery&&(a=a[0]);var c="border"+d.substr(0,1).toUpperCase()+d.substr(1);return"none"===b.css(a,c+"Style",!0)?0:Math.round(parseFloat(b.css(a,c+"Width",!0))||0)},isMouseOverElem:function(a,d){var c=b(d||this),f=c.offset(),j=f.top,f=f.left,k=f+c.outerWidth(),c=j+c.outerHeight(),h=a.pageX,p=a.pageY;return b.layout.browser.msie&&0>h&&0> +p||h>=f&&h<=k&&p>=j&&p<=c},msg:function(a,d,c,f){b.isPlainObject(a)&&window.debugData?("string"===typeof d?(f=c,c=d):"object"===typeof c&&(f=c,c=null),c=c||"log( )",f=b.extend({sort:!1,returnHTML:!1,display:!1},f),!0===d||f.display?debugData(a,c,f):window.console&&console.log(debugData(a,c,f))):d?alert(a):window.console?console.log(a):(d=b("#layoutLogger"),d.length||(d=b('
    XLayout console.log
      ').appendTo("body"), +d.css("left",b(window).width()-d.outerWidth()-5),b.ui.draggable&&d.draggable({handle:":first-child"})),d.children("ul").append('
    • '+a.replace(/\/g,">")+"
    • "))}};var h=navigator.userAgent.toLowerCase(),p=/(chrome)[ \/]([\w.]+)/.exec(h)||/(webkit)[ \/]([\w.]+)/.exec(h)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(h)||/(msie) ([\w.]+)/.exec(h)||0>h.indexOf("compatible")&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(h)|| +[],h=p[1]||"",p=p[2]||0,x="msie"===h;b.layout.browser={version:p,safari:"webkit"===h,webkit:"chrome"===h,msie:x,isIE6:x&&6==p,boxModel:!x||!1!==b.support.boxModel};h&&(b.layout.browser[h]=!0);x&&b(function(){b.layout.browser.boxModel=b.support.boxModel});b.layout.defaults={name:"",containerClass:"ui-layout-container",inset:null,scrollToBookmarkOnLoad:!0,resizeWithWindow:!0,resizeWithWindowDelay:200,resizeWithWindowMaxDelay:0,maskPanesEarly:!1,onresizeall_start:null,onresizeall_end:null,onload_start:null, +onload_end:null,onunload_start:null,onunload_end:null,initPanes:!0,showErrorMessages:!0,showDebugMessages:!1,zIndex:null,zIndexes:{pane_normal:0,content_mask:1,resizer_normal:2,pane_sliding:100,pane_animate:1E3,resizer_drag:1E4},errors:{pane:"pane",selector:"selector",addButtonError:"Error Adding Button\nInvalid ",containerMissing:"UI Layout Initialization Error\nThe specified layout-container does not exist.",centerPaneMissing:"UI Layout Initialization Error\nThe center-pane element does not exist.\nThe center-pane is a required element.", +noContainerHeight:"UI Layout Initialization Warning\nThe layout-container \"CONTAINER\" has no height.\nTherefore the layout is 0-height and hence 'invisible'!",callbackError:"UI Layout Callback Error\nThe EVENT callback is not a valid function."},panes:{applyDemoStyles:!1,closable:!0,resizable:!0,slidable:!0,initClosed:!1,initHidden:!1,contentSelector:".ui-layout-content",contentIgnoreSelector:".ui-layout-ignore",findNestedContent:!1,paneClass:"ui-layout-pane",resizerClass:"ui-layout-resizer",togglerClass:"ui-layout-toggler", +buttonClass:"ui-layout-button",minSize:0,maxSize:0,spacing_open:6,spacing_closed:6,togglerLength_open:50,togglerLength_closed:50,togglerAlign_open:"center",togglerAlign_closed:"center",togglerContent_open:"",togglerContent_closed:"",resizerDblClickToggle:!0,autoResize:!0,autoReopen:!0,resizerDragOpacity:1,maskContents:!1,maskObjects:!1,maskZindex:null,resizingGrid:!1,livePaneResizing:!1,liveContentResizing:!1,liveResizingTolerance:1,sliderCursor:"pointer",slideTrigger_open:"click",slideTrigger_close:"mouseleave", +slideDelay_open:300,slideDelay_close:300,hideTogglerOnSlide:!1,preventQuickSlideClose:b.layout.browser.webkit,preventPrematureSlideClose:!1,tips:{Open:"Open",Close:"Close",Resize:"Resize",Slide:"Slide Open",Pin:"Pin",Unpin:"Un-Pin",noRoomToOpen:"Not enough room to show this panel.",minSizeWarning:"Panel has reached its minimum size",maxSizeWarning:"Panel has reached its maximum size"},showOverflowOnHover:!1,enableCursorHotkey:!0,customHotkeyModifier:"SHIFT",fxName:"slide",fxSpeed:null,fxSettings:{}, +fxOpacityFix:!0,animatePaneSizing:!1,children:null,containerSelector:"",initChildren:!0,destroyChildren:!0,resizeChildren:!0,triggerEventsOnLoad:!1,triggerEventsDuringLiveResize:!0,onshow_start:null,onshow_end:null,onhide_start:null,onhide_end:null,onopen_start:null,onopen_end:null,onclose_start:null,onclose_end:null,onresize_start:null,onresize_end:null,onsizecontent_start:null,onsizecontent_end:null,onswap_start:null,onswap_end:null,ondrag_start:null,ondrag_end:null},north:{paneSelector:".ui-layout-north", +size:"auto",resizerCursor:"n-resize",customHotkey:""},south:{paneSelector:".ui-layout-south",size:"auto",resizerCursor:"s-resize",customHotkey:""},east:{paneSelector:".ui-layout-east",size:200,resizerCursor:"e-resize",customHotkey:""},west:{paneSelector:".ui-layout-west",size:200,resizerCursor:"w-resize",customHotkey:""},center:{paneSelector:".ui-layout-center",minWidth:0,minHeight:0}};b.layout.optionsMap={layout:"name instanceKey stateManagement effects inset zIndexes errors zIndex scrollToBookmarkOnLoad showErrorMessages maskPanesEarly outset resizeWithWindow resizeWithWindowDelay resizeWithWindowMaxDelay onresizeall onresizeall_start onresizeall_end onload onload_start onload_end onunload onunload_start onunload_end".split(" "), +center:"paneClass contentSelector contentIgnoreSelector findNestedContent applyDemoStyles triggerEventsOnLoad showOverflowOnHover maskContents maskObjects liveContentResizing containerSelector children initChildren resizeChildren destroyChildren onresize onresize_start onresize_end onsizecontent onsizecontent_start onsizecontent_end".split(" "),noDefault:["paneSelector","resizerCursor","customHotkey"]};b.layout.transformData=function(a,d){var c=d?{panes:{},center:{}}:{},f,j,k,h,p,x,D;if("object"!== +typeof a)return c;for(j in a){f=c;p=a[j];k=j.split("__");D=k.length-1;for(x=0;x<=D;x++)h=k[x],x===D?f[h]=b.isPlainObject(p)?b.layout.transformData(p):p:(f[h]||(f[h]={}),f=f[h])}return c};b.layout.backwardCompatibility={map:{applyDefaultStyles:"applyDemoStyles",childOptions:"children",initChildLayout:"initChildren",destroyChildLayout:"destroyChildren",resizeChildLayout:"resizeChildren",resizeNestedLayout:"resizeChildren",resizeWhileDragging:"livePaneResizing",resizeContentWhileDragging:"liveContentResizing", +triggerEventsWhileDragging:"triggerEventsDuringLiveResize",maskIframesOnResize:"maskContents",useStateCookie:"stateManagement.enabled","cookie.autoLoad":"stateManagement.autoLoad","cookie.autoSave":"stateManagement.autoSave","cookie.keys":"stateManagement.stateKeys","cookie.name":"stateManagement.cookie.name","cookie.domain":"stateManagement.cookie.domain","cookie.path":"stateManagement.cookie.path","cookie.expires":"stateManagement.cookie.expires","cookie.secure":"stateManagement.cookie.secure", +noRoomToOpenTip:"tips.noRoomToOpen",togglerTip_open:"tips.Close",togglerTip_closed:"tips.Open",resizerTip:"tips.Resize",sliderTip:"tips.Slide"},renameOptions:function(a){function d(b,c){for(var f=b.split("."),k=f.length-1,j={branch:a,key:f[k]},r=0,q;rw)return!0;var m={38:"north",40:"south",37:"west",39:"east"},a=e.shiftKey,g=e.ctrlKey,t,n,d,c;g&&(37<=w&&40>=w)&&r[m[w]].enableCursorHotkey?c=m[w]:(g||a)&&b.each(k.borderPanes,function(e, +b){t=r[b];n=t.customHotkey;d=t.customHotkeyModifier;if(a&&"SHIFT"==d||g&&"CTRL"==d||g&&a)if(n&&w===(isNaN(n)||9>=n?n.toUpperCase().charCodeAt(0):n))return c=b,!1});if(!c||!y[c]||!r[c].closable||q[c].isHidden)return!0;na(c);e.stopPropagation();return e.returnValue=!1}function x(e){if(H()){this&&this.tagName&&(e=this);var w;f(e)?w=y[e]:b(e).data("layoutRole")?w=b(e):b(e).parents().each(function(){if(b(this).data("layoutRole"))return w=b(this),!1});if(w&&w.length){var m=w.data("layoutEdge");e=q[m];e.cssSaved&& +X(m);if(e.isSliding||e.isResizing||e.isClosed)e.cssSaved=!1;else{var a={zIndex:r.zIndexes.resizer_normal+1},g={},t=w.css("overflow"),n=w.css("overflowX"),d=w.css("overflowY");"visible"!=t&&(g.overflow=t,a.overflow="visible");n&&!n.match(/(visible|auto)/)&&(g.overflowX=n,a.overflowX="visible");d&&!d.match(/(visible|auto)/)&&(g.overflowY=n,a.overflowY="visible");e.cssSaved=g;w.css(a);b.each(k.allPanes,function(e,b){b!=m&&X(b)})}}}}function X(e){if(H()){this&&this.tagName&&(e=this);var w;f(e)?w=y[e]: +b(e).data("layoutRole")?w=b(e):b(e).parents().each(function(){if(b(this).data("layoutRole"))return w=b(this),!1});if(w&&w.length){e=w.data("layoutEdge");e=q[e];var m=e.cssSaved||{};!e.isSliding&&!e.isResizing&&w.css("zIndex",r.zIndexes.pane_normal);w.css(m);e.cssSaved=!1}}}var G=b.layout.browser,k=b.layout.config,Q=b.layout.cssWidth,O=b.layout.cssHeight,R=b.layout.getElementDimensions,D=b.layout.getElementStyles,Ma=b.layout.getEventObject,A=b.layout.parsePaneName,r=b.extend(!0,{},b.layout.defaults); +r.effects=b.extend(!0,{},b.layout.effects);var q={id:"layout"+b.now(),initialized:!1,paneResizing:!1,panesSliding:{},container:{innerWidth:0,innerHeight:0,outerWidth:0,outerHeight:0,layoutWidth:0,layoutHeight:0},north:{childIdx:0},south:{childIdx:0},east:{childIdx:0},west:{childIdx:0},center:{childIdx:0}},ba={north:null,south:null,east:null,west:null,center:null},M={data:{},set:function(e,b,m){M.clear(e);M.data[e]=setTimeout(b,m)},clear:function(e){var b=M.data;b[e]&&(clearTimeout(b[e]),delete b[e])}}, +ca=function(e,w,m){var a=r;(a.showErrorMessages&&!m||m&&a.showDebugMessages)&&b.layout.msg(a.name+" / "+e,!1!==w);return!1},C=function(e,w,m){var a=w&&f(w),g=a?q[w]:q,t=a?r[w]:r,n=r.name,d=e+(e.match(/_/)?"":"_end"),c=d.match(/_end$/)?d.substr(0,d.length-4):"",l=t[d]||t[c],h="NC",k=[];!a&&"boolean"===b.type(w)&&(m=w,w="");if(l)try{f(l)&&(l.match(/,/)?(k=l.split(","),l=eval(k[0])):l=eval(l)),b.isFunction(l)&&(h=k.length?l(k[1]):a?l(w,y[w],g,t,n):l(z,g,t,n))}catch(j){ca(r.errors.callbackError.replace(/EVENT/, +b.trim((w||"")+" "+d)),!1),"string"===b.type(j)&&string.length&&ca("Exception: "+j,!1)}!m&&!1!==h&&(a?(m=y[w],t=r[w],g=q[w],m.triggerHandler("layoutpane"+d,[w,m,g,t,n]),c&&m.triggerHandler("layoutpane"+c,[w,m,g,t,n])):(u.triggerHandler("layout"+d,[z,g,t,n]),c&&u.triggerHandler("layout"+c,[z,g,t,n])));a&&"onresize_end"===e&&db(w+"",!0);return h},eb=function(e){if(!G.mozilla){var b=y[e];"IFRAME"===q[e].tagName?b.css(k.hidden).css(k.visible):b.find("IFRAME").css(k.hidden).css(k.visible)}},ya=function(e){var b= +y[e];e=k[e].dir;b={minWidth:1001-Q(b,1E3),minHeight:1001-O(b,1E3)};"horz"===e&&(b.minSize=b.minHeight);"vert"===e&&(b.minSize=b.minWidth);return b},fa=function(e,w,m){m||(m=k[e].dir);f(w)&&w.match(/%/)&&(w="100%"===w?-1:parseInt(w,10)/100);if(0===w)return 0;if(1<=w)return parseInt(w,10);var a=r,g=0;"horz"==m?g=v.innerHeight-(y.north?a.north.spacing_open:0)-(y.south?a.south.spacing_open:0):"vert"==m&&(g=v.innerWidth-(y.west?a.west.spacing_open:0)-(y.east?a.east.spacing_open:0));if(-1===w)return g; +if(0b&&(b=100);M.clear("winResize");M.set("winResize",function(){M.clear("winResize");M.clear("winResizeRepeater");var b=R(u,e.inset);(b.innerWidth!==v.innerWidth||b.innerHeight!==v.innerHeight)&&oa()},b);M.data.winResizeRepeater||lb()},lb=function(){var e= +Number(r.resizeWithWindowMaxDelay);0"),l=l.toggler=g.closable?P[a]=b("
      "):!1;!d.isVisible&&g.slidable&&j.attr("title",g.tips.Slide).css("cursor",g.sliderCursor);j.attr("id",c?c+"-resizer":"").data({parentLayout:z,layoutPane:z[a],layoutEdge:a,layoutRole:"resizer"}).css(k.resizers.cssReq).css("zIndex",r.zIndexes.resizer_normal).css(g.applyDemoStyles?k.resizers.cssDemo:{}).addClass(n+" "+n+h).hover(Oa,da).hover(fb,gb).appendTo(u); +g.resizerDblClickToggle&&j.bind("dblclick."+K,na);l&&(l.attr("id",c?c+"-toggler":"").data({parentLayout:z,layoutPane:z[a],layoutEdge:a,layoutRole:"toggler"}).css(k.togglers.cssReq).css(g.applyDemoStyles?k.togglers.cssDemo:{}).addClass(f+" "+f+h).hover(Oa,da).bind("mouseenter",fb).appendTo(j),g.togglerContent_open&&b(""+g.togglerContent_open+"").data({layoutEdge:a,layoutRole:"togglerContent"}).data("layoutRole","togglerContent").data("layoutEdge",a).addClass("content content-open").css("display", +"none").appendTo(l),g.togglerContent_closed&&b(""+g.togglerContent_closed+"").data({layoutEdge:a,layoutRole:"togglerContent"}).addClass("content content-closed").css("display","none").appendTo(l),pb(a));var g=a,B=b.layout.plugins.draggable,g=g?g.split(","):k.borderPanes;b.each(g,function(e,a){var g=r[a];if(!B||!y[a]||!g.resizable)return g.resizable=!1,!0;var m=q[a],w=r.zIndexes,d=k[a],c="horz"==d.dir?"top":"left",t=F[a],n=g.resizerClass,f=0,l,h,E=n+"-drag",j=n+"-"+a+"-drag",J=n+"-dragging", +zb=n+"-"+a+"-dragging",cb=n+"-dragging-limit",v=n+"-"+a+"-dragging-limit",x=!1;m.isClosed||t.attr("title",g.tips.Resize).css("cursor",g.resizerCursor);t.draggable({containment:u[0],axis:"horz"==d.dir?"y":"x",delay:0,distance:1,grid:g.resizingGrid,helper:"clone",opacity:g.resizerDragOpacity,addClasses:!1,zIndex:w.resizer_drag,start:function(e,w){g=r[a];m=q[a];h=g.livePaneResizing;if(!1===C("ondrag_start",a))return!1;m.isResizing=!0;q.paneResizing=a;M.clear(a+"_closeSlider");Y(a);l=m.resizerPosition; +f=w.position[c];t.addClass(E+" "+j);x=!1;b("body").disableSelection();va(a,{resizing:!0})},drag:function(e,b){x||(b.helper.addClass(J+" "+zb).css({right:"auto",bottom:"auto"}).children().css("visibility","hidden"),x=!0,m.isSliding&&y[a].css("zIndex",w.pane_sliding));var d=0;b.position[c]l.max&&(b.position[c]=l.max,d=1);d?(b.helper.addClass(cb+" "+v),window.defaultStatus=0d&&a.match(/(south|east)/)?g.tips.maxSizeWarning: +g.tips.minSizeWarning):(b.helper.removeClass(cb+" "+v),window.defaultStatus="");h&&Math.abs(b.position[c]-f)>=g.liveResizingTolerance&&(f=b.position[c],p(e,b,a))},stop:function(e,g){b("body").enableSelection();window.defaultStatus="";t.removeClass(E+" "+j);m.isResizing=!1;q.paneResizing=!1;p(e,g,a,!0)}})});var p=function(b,e,a,g){var m=e.position,w=k[a];b=r[a];e=q[a];var d;switch(a){case "north":d=m.top;break;case "west":d=m.left;break;case "south":d=v.layoutHeight-m.top-b.spacing_open;break;case "east":d= +v.layoutWidth-m.left-b.spacing_open}d-=v.inset[w.side];g?(!1!==C("ondrag_end",a)&&Ca(a,d,!1,!0),za(!0),e.isSliding&&va(a,{resizing:!0})):Math.abs(d-e.size)j.maxSize)return Va(f,!1),!c&&h.tips.noRoomToOpen&&alert(h.tips.noRoomToOpen),a();b?wa(f,!0):j.isSliding?wa(f,!1):h.slidable&&ma(f,!1); +j.noRoom=!1;ha(f);p=j.isShowing;delete j.isShowing;l=!d&&j.isClosed&&"none"!=h.fxName_open;j.isMoving=!0;j.isVisible=!0;j.isClosed=!1;p&&(j.isHidden=!1);l?(Ga(f,!0),n.show(h.fxName_open,h.fxSettings_open,h.fxSpeed_open,function(){Ga(f,!1);j.isVisible&&g();a()})):(rb(f),g(),a())}})}},Ua=function(a,d){var c=y[a],f=F[a],g=P[a],h=r[a],n=q[a],j=k[a].side,p=h.resizerClass,l=h.togglerClass,u="-"+a;f.css(j,v.inset[j]+ga(a)).removeClass(p+"-closed "+p+u+"-closed").addClass(p+"-open "+p+u+"-open");n.isSliding? +f.addClass(p+"-sliding "+p+u+"-sliding"):f.removeClass(p+"-sliding "+p+u+"-sliding");da(0,f);h.resizable&&b.layout.plugins.draggable?f.draggable("enable").css("cursor",h.resizerCursor).attr("title",h.tips.Resize):n.isSliding||f.css("cursor","default");g&&(g.removeClass(l+"-closed "+l+u+"-closed").addClass(l+"-open "+l+u+"-open").attr("title",h.tips.Close),da(0,g),g.children(".content-closed").hide(),g.children(".content-open").css("display","block"));Va(a,!n.isSliding);b.extend(n,R(c));q.initialized&& +(qa(),pa(a,!0));if(!d&&(q.initialized||h.triggerEventsOnLoad)&&c.is(":visible"))C("onopen_end",a),n.isShowing&&C("onshow_end",a),q.initialized&&C("onresize_end",a)},sb=function(a){function b(){g.isClosed?g.isMoving||ra(c,!0):wa(c,!0)}if(H()){var d=Ma(a),c=A.call(this,a),g=q[c];a=r[c].slideDelay_open;d&&d.stopImmediatePropagation();g.isClosed&&d&&"mouseenter"===d.type&&0g.maxSize?ka(a,g.maxSize,c,!0,f):g.sizec?d(0,e.attempt-(e.actual-c)):d(0,e.attempt+(c-e.actual));h.cssSize=("horz"==k[n].dir?O:Q)(y[n],h.attempt);l.css(ua,h.cssSize);h.actual="width"==ua?l.outerWidth():l.outerHeight();h.correct=c===h.actual;1===a.length&&(ca(t,!1,!0),ca(e,!1,!0));ca(h,!1,!0);if(3B.width){var h=h.minWidth-j.outerWidth,B=r.east.minSize||0,x=r.west.minSize||0,Z=q.east.size,z=q.west.size,A=Z,D=z;0B)&&(A=d(Z-B,Z-h),h-=Z-A);0x)&&(D=d(z-x,z-h),h-=z-D);if(0===h){Z&&Z!=B&&ka("east",A,!0,!0,f);z&&z!=x&&ka("west",D,!0,!0,f);ia("center", +c,f);k.css(u);return}}}else{j.isVisible&&!j.noVerticalRoom&&b.extend(j,R(k),ya(e));if(!f&&!j.noVerticalRoom&&B.height===j.outerHeight)return k.css(u),!0;l.top=B.top;l.bottom=B.bottom;j.newSize=B.height;l.height=O(k,B.height);j.maxHeight=l.height;p=0<=j.maxHeight;p||(j.noVerticalRoom=!0)}p?(!c&&q.initialized&&C("onresize_start",e),k.css(l),"center"!==e&&qa(e),j.noRoom&&(!j.isClosed&&!j.isHidden)&&ha(e),j.isVisible&&(b.extend(j,R(k)),q.initialized&&pa(e))):!j.noRoom&&j.isVisible&&ha(e);k.css(u);delete j.newSize; +delete j.newWidth;delete j.newHeight;if(!j.isVisible)return!0;"center"===e&&(j=G.isIE6||!G.boxModel,y.north&&(j||"IFRAME"==q.north.tagName)&&y.north.css("width",Q(y.north,v.innerWidth)),y.south&&(j||"IFRAME"==q.south.tagName)&&y.south.css("width",Q(y.south,v.innerWidth)));!c&&q.initialized&&C("onresize_end",e)}})},oa=function(a){A(a);if(u.is(":visible"))if(q.initialized){if(!0===a&&b.isPlainObject(r.outset)&&u.css(r.outset),b.extend(v,R(u,r.inset)),v.outerHeight){!0===a&&ob();if(!1===C("onresizeall_start"))return!1; +var d,c,f;b.each(["south","north","east","west"],function(a,b){y[b]&&(c=r[b],f=q[b],f.autoResize&&f.size!=c.size?ka(b,c.size,!0,!0,!0):(Y(b),ha(b,!1,!0,!0)))});ia("",!0,!0);qa();b.each(k.allPanes,function(a,b){(d=y[b])&&q[b].isVisible&&C("onresize_end",b)});C("onresizeall_end")}}else Aa()},db=function(a,d){var c=A.call(this,a);r[c].resizeChildren&&(d||Ba(c),c=ba[c],b.isPlainObject(c)&&b.each(c,function(a,b){b.destroyed||b.resizeAll()}))},pa=function(a,c){if(H()){var h=A.call(this,a),h=h?h.split(","): +k.allPanes;b.each(h,function(a,e){function h(a){return d(u.css.paddingBottom,parseInt(a.css("marginBottom"),10)||0)}function j(){var a=r[e].contentIgnoreSelector,a=p.nextAll().not(".ui-layout-mask").not(a||":lt(0)"),b=a.filter(":visible"),d=b.filter(":last");v={top:p[0].offsetTop,height:p.outerHeight(),numFooters:a.length,hiddenFooters:a.length-b.length,spaceBelow:0};v.spaceAbove=v.top;v.bottom=v.top+v.height;v.spaceBelow=d.length?d[0].offsetTop+d.outerHeight()-v.bottom+h(d):h(p)}var m=y[e],p=U[e], +l=r[e],u=q[e],v=u.content;if(!m||!p||!m.is(":visible"))return!0;if(!p.length&&(Sa(e,!1),!p))return;if(!1!==C("onsizecontent_start",e)){if(!u.isMoving&&!u.isResizing||l.liveContentResizing||c||void 0==v.top)j(),0A)x=A,z=0;else if(f(z))switch(z){case "top":case "left":z=0;break;case "bottom":case "right":z=A-x;break; +default:z=c((A-x)/2)}else h=parseInt(z,10),z=0<=z?h:A-x+h;if("horz"===l){var D=Q(p,x);p.css({width:D,height:O(p,B),left:z,top:0});p.children(".content").each(function(){u=b(this);u.css("marginLeft",c((D-u.outerWidth())/2))})}else{var C=O(p,x);p.css({height:C,width:Q(p,B),top:z,left:0});p.children(".content").each(function(){u=b(this);u.css("marginTop",c((C-u.outerHeight())/2))})}da(0,p)}if(!q.initialized&&(e.initHidden||g.isHidden))j.hide(),p&&p.hide()}}})},pb=function(a){if(H()){var b=A.call(this, +a);a=P[b];var d=r[b];a&&(d.closable=!0,a.bind("click."+K,function(a){a.stopPropagation();na(b)}).css("visibility","visible").css("cursor","pointer").attr("title",q[b].isClosed?d.tips.Open:d.tips.Close).show())}},Va=function(a,d){b.layout.plugins.buttons&&b.each(q[a].pins,function(c,f){b.layout.buttons.setPinState(z,b(f),a,d)})},u=b(this).eq(0);if(!u.length)return ca(r.errors.containerMissing);if(u.data("layoutContainer")&&u.data("layout"))return u.data("layout");var y={},U={},F={},P={},ea=b([]),v= +q.container,K=q.id,z={options:r,state:q,container:u,panes:y,contents:U,resizers:F,togglers:P,hide:Ta,show:Fa,toggle:na,open:ra,close:ja,slideOpen:sb,slideClose:Wa,slideToggle:function(a){a=A.call(this,a);na(a,!0)},setSizeLimits:Y,_sizePane:ka,sizePane:Ca,sizeContent:pa,swapPanes:function(a,c){function f(a){var d=y[a],c=U[a];return!d?!1:{pane:a,P:d?d[0]:!1,C:c?c[0]:!1,state:b.extend(!0,{},q[a]),options:b.extend(!0,{},r[a])}}function h(a,c){if(a){var e=a.P,f=a.C,g=a.pane,j=k[c],m=b.extend(!0,{},q[c]), +n=r[c],w={resizerCursor:n.resizerCursor};b.each(["fxName","fxSpeed","fxSettings"],function(a,b){w[b+"_open"]=n[b+"_open"];w[b+"_close"]=n[b+"_close"];w[b+"_size"]=n[b+"_size"]});y[c]=b(e).data({layoutPane:z[c],layoutEdge:c}).css(k.hidden).css(j.cssReq);U[c]=f?b(f):!1;r[c]=b.extend(!0,{},a.options,w);q[c]=b.extend(!0,{},a.state);e.className=e.className.replace(RegExp(n.paneClass+"-"+g,"g"),n.paneClass+"-"+c);Pa(c);j.dir!=k[g].dir?(e=p[c]||0,Y(c),e=d(e,q[c].minSize),Ca(c,e,!0,!0)):F[c].css(j.side,v.inset[j.side]+ +(q[c].isVisible?ga(c):0));a.state.isVisible&&!m.isVisible?Ua(c,!0):(Da(c),ma(c,!0));a=null}}if(H()){var g=A.call(this,a);q[g].edge=c;q[c].edge=g;if(!1===C("onswap_start",g)||!1===C("onswap_start",c))q[g].edge=g,q[c].edge=c;else{var j=f(g),n=f(c),p={};p[g]=j?j.state.size:0;p[c]=n?n.state.size:0;y[g]=!1;y[c]=!1;q[g]={};q[c]={};P[g]&&P[g].remove();P[c]&&P[c].remove();F[g]&&F[g].remove();F[c]&&F[c].remove();F[g]=F[c]=P[g]=P[c]=!1;h(j,c);h(n,g);j=n=p=null;y[g]&&y[g].css(k.visible);y[c]&&y[c].css(k.visible); +oa();C("onswap_end",g);C("onswap_end",c)}}},showMasks:va,hideMasks:za,initContent:Sa,addPane:ib,removePane:Ra,createChildren:Qa,refreshChildren:Ba,enableClosable:pb,disableClosable:function(a,b){if(H()){var c=A.call(this,a),d=P[c];d&&(r[c].closable=!1,q[c].isClosed&&ra(c,!1,!0),d.unbind("."+K).css("visibility",b?"hidden":"visible").css("cursor","default").attr("title",""))}},enableSlidable:function(a){if(H()){a=A.call(this,a);var b=F[a];b&&b.data("draggable")&&(r[a].slidable=!0,q[a].isClosed&&ma(a, +!0))}},disableSlidable:function(a){if(H()){a=A.call(this,a);var b=F[a];b&&(r[a].slidable=!1,q[a].isSliding?ja(a,!1,!0):(ma(a,!1),b.css("cursor","default").attr("title",""),da(null,b[0])))}},enableResizable:function(a){if(H()){a=A.call(this,a);var b=F[a],c=r[a];b&&b.data("draggable")&&(c.resizable=!0,b.draggable("enable"),q[a].isClosed||b.css("cursor",c.resizerCursor).attr("title",c.tips.Resize))}},disableResizable:function(a){if(H()){a=A.call(this,a);var b=F[a];b&&b.data("draggable")&&(r[a].resizable= +!1,b.draggable("disable").css("cursor","default").attr("title",""),da(null,b[0]))}},allowOverflow:x,resetOverflow:X,destroy:function(a,c){b(window).unbind("."+K);b(document).unbind("."+K);"object"===typeof a?A(a):c=a;u.clearQueue().removeData("layout").removeData("layoutContainer").removeClass(r.containerClass).unbind("."+K);ea.remove();b.each(k.allPanes,function(a,b){Ra(b,!1,!0,c)});u.data("layoutCSS")&&!u.data("layoutRole")&&u.css(u.data("layoutCSS")).removeData("layoutCSS");"BODY"===v.tagName&& +(u=b("html")).data("layoutCSS")&&u.css(u.data("layoutCSS")).removeData("layoutCSS");j(z,b.layout.onDestroy);mb();for(var d in z)d.match(/^(container|options)$/)||delete z[d];z.destroyed=!0;return z},initPanes:H,resizeAll:oa,runCallbacks:C,hasParentLayout:!1,children:ba,north:!1,south:!1,west:!1,east:!1,center:!1},Xa;var V,Ya,N,Ha,la,sa,W;h=b.layout.transformData(h,!0);h=b.layout.backwardCompatibility.renameAllOptions(h);if(!b.isEmptyObject(h.panes)){V=b.layout.optionsMap.noDefault;la=0;for(sa=V.length;la< +sa;la++)N=V[la],delete h.panes[N];V=b.layout.optionsMap.layout;la=0;for(sa=V.length;lab.inArray(N,Bb)&&0>b.inArray(N,V)&&(h.panes[N]||(h.panes[N]=b.isPlainObject(Ha)?b.extend(!0,{},Ha):Ha),delete h[N]);b.extend(!0,r,h);b.each(k.allPanes,function(a,c){k[c]=b.extend(!0,{},k.panes,k[c]);Ya=r.panes;W=r[c];if("center"===c){V=b.layout.optionsMap.center;a=0;for(sa=V.length;av.innerHeight&&ca(L.errors.noContainerHeight.replace(/CONTAINER/,v.ref)))}ta(u,"minWidth")&&u.parent().css("overflowX","auto");ta(u,"minHeight")&&u.parent().css("overflowY","auto")}catch(Db){}nb();b(window).bind("unload."+K,mb);j(z,b.layout.onLoad);Cb.initPanes&&Aa();delete tb.creatingLayout;Xa=q.initialized}return"cancel"===Xa?null:z}})(jQuery); +(function(b){b.ui||(b.ui={});b.ui.cookie={acceptsCookies:!!navigator.cookieEnabled,read:function(a){for(var d=document.cookie,d=d?d.split(";"):[],c,f=0,j=d.length;fb.inArray(T,p)||(S=h[T][I],void 0!=S&&("isClosed"==I&&h[T].isSliding&&(S=!0),(x[T]||(x[T]={}))[j[I]?j[I]:I]=S));f&&b.each(p,function(c,d){G=a.children[d];X=h.stateData[d];b.isPlainObject(G)&&!b.isEmptyObject(G)&&(k=x[d]||(x[d]={}),k.children||(k.children={}),b.each(G,function(a,c){c.state.initialized?k.children[a]=b.layout.state.readState(c): +X&&(X.children&&X.children[a])&&(k.children[a]=b.extend(!0,{},X.children[a]))}))});return x},encodeJSON:function(a){function d(a){var f=[],j=0,h,p,x,I=b.isArray(a);for(h in a)p=a[h],x=typeof p,"string"==x?p='"'+p+'"':"object"==x&&(p=d(p)),f[j++]=(!I?'"'+h+'":':"")+p;return(I?"[":"{")+f.join(",")+(I?"]":"}")}return d(a)},decodeJSON:function(a){try{return b.parseJSON?b.parseJSON(a):window.eval("("+a+")")||{}}catch(d){return{}}},_create:function(a){var d=b.layout.state,c=a.options.stateManagement;b.extend(a, +{readCookie:function(){return d.readCookie(a)},deleteCookie:function(){d.deleteCookie(a)},saveCookie:function(b,c){return d.saveCookie(a,b,c)},loadCookie:function(){return d.loadCookie(a)},loadState:function(b,c){d.loadState(a,b,c)},readState:function(b){return d.readState(a,b)},encodeJSON:d.encodeJSON,decodeJSON:d.decodeJSON});a.state.stateData={};if(c.autoLoad)if(b.isPlainObject(c.autoLoad))b.isEmptyObject(c.autoLoad)||a.loadState(c.autoLoad);else if(c.enabled)if(b.isFunction(c.autoLoad)){var f= +{};try{f=c.autoLoad(a,a.state,a.options,a.options.name||"")}catch(j){}f&&(b.isPlainObject(f)&&!b.isEmptyObject(f))&&a.loadState(f)}else a.loadCookie()},_unload:function(a){var d=a.options.stateManagement;if(d.enabled&&d.autoSave)if(b.isFunction(d.autoSave))try{d.autoSave(a,a.state,a.options,a.options.name||"")}catch(c){}else a.saveCookie()}};b.layout.onCreate.push(b.layout.state._create);b.layout.onUnload.push(b.layout.state._unload);b.layout.plugins.buttons=!0;b.layout.defaults.autoBindCustomButtons= +!1;b.layout.optionsMap.layout.push("autoBindCustomButtons");b.layout.buttons={init:function(a){var d=a.options.name||"",c;b.each("toggle open close pin toggle-slide open-slide".split(" "),function(f,j){b.each(b.layout.config.borderPanes,function(f,p){b(".ui-layout-button-"+j+"-"+p).each(function(){c=b(this).data("layoutName")||b(this).attr("layoutName");(void 0==c||c===d)&&a.bindButton(this,j,p)})})})},get:function(a,d,c,f){var j=b(d);a=a.options;var h=a.errors.addButtonError;j.length?0>b.inArray(c, +b.layout.config.borderPanes)?(b.layout.msg(h+" "+a.errors.pane+": "+c,!0),j=b("")):(d=a[c].buttonClass+"-"+f,j.addClass(d+" "+d+"-"+c).data("layoutName",a.name)):b.layout.msg(h+" "+a.errors.selector+": "+d,!0);return j},bind:function(a,d,c,f){var j=b.layout.buttons;switch(c.toLowerCase()){case "toggle":j.addToggle(a,d,f);break;case "open":j.addOpen(a,d,f);break;case "close":j.addClose(a,d,f);break;case "pin":j.addPin(a,d,f);break;case "toggle-slide":j.addToggle(a,d,f,!0);break;case "open-slide":j.addOpen(a, +d,f,!0)}return a},addToggle:function(a,d,c,f){b.layout.buttons.get(a,d,c,"toggle").click(function(b){a.toggle(c,!!f);b.stopPropagation()});return a},addOpen:function(a,d,c,f){b.layout.buttons.get(a,d,c,"open").attr("title",a.options[c].tips.Open).click(function(b){a.open(c,!!f);b.stopPropagation()});return a},addClose:function(a,d,c){b.layout.buttons.get(a,d,c,"close").attr("title",a.options[c].tips.Close).click(function(b){a.close(c);b.stopPropagation()});return a},addPin:function(a,d,c){var f=b.layout.buttons, +j=f.get(a,d,c,"pin");if(j.length){var h=a.state[c];j.click(function(d){f.setPinState(a,b(this),c,h.isSliding||h.isClosed);h.isSliding||h.isClosed?a.open(c):a.close(c);d.stopPropagation()});f.setPinState(a,j,c,!h.isClosed&&!h.isSliding);h.pins.push(d)}return a},setPinState:function(a,b,c,f){var j=b.attr("pin");if(!(j&&f===("down"==j))){a=a.options[c];var j=a.buttonClass+"-pin",h=j+"-"+c;c=j+"-up "+h+"-up";j=j+"-down "+h+"-down";b.attr("pin",f?"down":"up").attr("title",f?a.tips.Unpin:a.tips.Pin).removeClass(f? +c:j).addClass(f?j:c)}},syncPinBtns:function(a,d,c){b.each(a.state[d].pins,function(f,j){b.layout.buttons.setPinState(a,b(j),d,c)})},_load:function(a){var d=b.layout.buttons;b.extend(a,{bindButton:function(b,c,h){return d.bind(a,b,c,h)},addToggleBtn:function(b,c,h){return d.addToggle(a,b,c,h)},addOpenBtn:function(b,c,h){return d.addOpen(a,b,c,h)},addCloseBtn:function(b,c){return d.addClose(a,b,c)},addPinBtn:function(b,c){return d.addPin(a,b,c)}});for(var c=0;4>c;c++)a.state[b.layout.config.borderPanes[c]].pins= +[];a.options.autoBindCustomButtons&&d.init(a)},_unload:function(){}};b.layout.onLoad.push(b.layout.buttons._load);b.layout.plugins.browserZoom=!0;b.layout.defaults.browserZoomCheckInterval=1E3;b.layout.optionsMap.layout.push("browserZoomCheckInterval");b.layout.browserZoom={_init:function(a){!1!==b.layout.browserZoom.ratio()&&b.layout.browserZoom._setTimer(a)},_setTimer:function(a){if(!a.destroyed){var d=a.options,c=a.state,f=a.hasParentLayout?5E3:Math.max(d.browserZoomCheckInterval,100);setTimeout(function(){if(!a.destroyed&& +d.resizeWithWindow){var f=b.layout.browserZoom.ratio();f!==c.browserZoom&&(c.browserZoom=f,a.resizeAll());b.layout.browserZoom._setTimer(a)}},f)}},ratio:function(){function a(a,b){return(100*(parseInt(a,10)/parseInt(b,10))).toFixed()}var d=window,c=screen,f=document,j=f.documentElement||f.body,h=b.layout.browser,p=h.version,x,I,T;return h.msie&&8-, if set than the TEXT + command will ask for it) + +- the rotation can be read from a real number field that stores text + rotation (degree counterclockwise where zero = horizontal to the + right), option enabled (tab + -, if set than the TEXT command will ask for it) + +Symbol layer model: +~~~~~~~~~~~~~~~~~~~ + +The symbol layers may have the following optional fields: + +- a real number field to store the symbol rotation (degree + counterclockwise where zero = horizontal to the right) + +- a real number field to store the symbol scale + +The symbol layer can be defined with a style set as follows: + +- If you decide to handle the rotation or scale of symbols then the + + + + + + + + + + + + + + + + diff --git a/icons/arcBy3Points.png b/icons/arcBy3Points.png deleted file mode 100644 index e4283d98..00000000 Binary files a/icons/arcBy3Points.png and /dev/null differ diff --git a/icons/arcBy3Points.svg b/icons/arcBy3Points.svg new file mode 100644 index 00000000..89e0df97 --- /dev/null +++ b/icons/arcBy3Points.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + diff --git a/icons/arcByCenterStartAngle.png b/icons/arcByCenterStartAngle.png deleted file mode 100644 index 35b2c5de..00000000 Binary files a/icons/arcByCenterStartAngle.png and /dev/null differ diff --git a/icons/arcByCenterStartAngle.svg b/icons/arcByCenterStartAngle.svg new file mode 100644 index 00000000..e4e21d02 --- /dev/null +++ b/icons/arcByCenterStartAngle.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/arcByCenterStartEnd.png b/icons/arcByCenterStartEnd.png deleted file mode 100644 index 40c60cd2..00000000 Binary files a/icons/arcByCenterStartEnd.png and /dev/null differ diff --git a/icons/arcByCenterStartEnd.svg b/icons/arcByCenterStartEnd.svg new file mode 100644 index 00000000..eea2ce95 --- /dev/null +++ b/icons/arcByCenterStartEnd.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + diff --git a/icons/arcByCenterStartLength.png b/icons/arcByCenterStartLength.png deleted file mode 100644 index 05a511ad..00000000 Binary files a/icons/arcByCenterStartLength.png and /dev/null differ diff --git a/icons/arcByCenterStartLength.svg b/icons/arcByCenterStartLength.svg new file mode 100644 index 00000000..36be422e --- /dev/null +++ b/icons/arcByCenterStartLength.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/icons/arcByStartCenterAngle.png b/icons/arcByStartCenterAngle.png deleted file mode 100644 index ad60e76c..00000000 Binary files a/icons/arcByStartCenterAngle.png and /dev/null differ diff --git a/icons/arcByStartCenterAngle.svg b/icons/arcByStartCenterAngle.svg new file mode 100644 index 00000000..5a67241d --- /dev/null +++ b/icons/arcByStartCenterAngle.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/arcByStartCenterEndPoints.png b/icons/arcByStartCenterEndPoints.png deleted file mode 100644 index be4f0796..00000000 Binary files a/icons/arcByStartCenterEndPoints.png and /dev/null differ diff --git a/icons/arcByStartCenterEndPoints.svg b/icons/arcByStartCenterEndPoints.svg new file mode 100644 index 00000000..b8e8205b --- /dev/null +++ b/icons/arcByStartCenterEndPoints.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/icons/arcByStartCenterLength.png b/icons/arcByStartCenterLength.png deleted file mode 100644 index 1eaae055..00000000 Binary files a/icons/arcByStartCenterLength.png and /dev/null differ diff --git a/icons/arcByStartCenterLength.svg b/icons/arcByStartCenterLength.svg new file mode 100644 index 00000000..469e10a6 --- /dev/null +++ b/icons/arcByStartCenterLength.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/icons/arcByStartEndAngle.png b/icons/arcByStartEndAngle.png deleted file mode 100644 index 0e5e541f..00000000 Binary files a/icons/arcByStartEndAngle.png and /dev/null differ diff --git a/icons/arcByStartEndAngle.svg b/icons/arcByStartEndAngle.svg new file mode 100644 index 00000000..a87c3e05 --- /dev/null +++ b/icons/arcByStartEndAngle.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/icons/arcByStartEndRadius.png b/icons/arcByStartEndRadius.png deleted file mode 100644 index db508b29..00000000 Binary files a/icons/arcByStartEndRadius.png and /dev/null differ diff --git a/icons/arcByStartEndRadius.svg b/icons/arcByStartEndRadius.svg new file mode 100644 index 00000000..b55df763 --- /dev/null +++ b/icons/arcByStartEndRadius.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/arcByStartEndTan.png b/icons/arcByStartEndTan.png deleted file mode 100644 index 49323f81..00000000 Binary files a/icons/arcByStartEndTan.png and /dev/null differ diff --git a/icons/arcByStartEndTan.svg b/icons/arcByStartEndTan.svg new file mode 100644 index 00000000..952a3698 --- /dev/null +++ b/icons/arcByStartEndTan.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + diff --git a/icons/arrayPath.svg b/icons/arrayPath.svg new file mode 100644 index 00000000..7495d077 --- /dev/null +++ b/icons/arrayPath.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/arrayPolar.svg b/icons/arrayPolar.svg new file mode 100644 index 00000000..bf0c00d7 --- /dev/null +++ b/icons/arrayPolar.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/arrayRect.svg b/icons/arrayRect.svg new file mode 100644 index 00000000..6773575e --- /dev/null +++ b/icons/arrayRect.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/break.png b/icons/break.png deleted file mode 100644 index 030a33d9..00000000 Binary files a/icons/break.png and /dev/null differ diff --git a/icons/break.svg b/icons/break.svg new file mode 100644 index 00000000..756a361a --- /dev/null +++ b/icons/break.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + diff --git a/icons/breakBy1Point.svg b/icons/breakBy1Point.svg new file mode 100644 index 00000000..9acdfc4c --- /dev/null +++ b/icons/breakBy1Point.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + diff --git a/icons/circle.png b/icons/circle.png deleted file mode 100644 index 3e8758b4..00000000 Binary files a/icons/circle.png and /dev/null differ diff --git a/icons/circle.svg b/icons/circle.svg new file mode 100644 index 00000000..0edbbe11 --- /dev/null +++ b/icons/circle.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + diff --git a/icons/circleBy2Points.png b/icons/circleBy2Points.png deleted file mode 100644 index f89c8720..00000000 Binary files a/icons/circleBy2Points.png and /dev/null differ diff --git a/icons/circleBy2Points.svg b/icons/circleBy2Points.svg new file mode 100644 index 00000000..15dfded3 --- /dev/null +++ b/icons/circleBy2Points.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + diff --git a/icons/circleBy2TansRadius.png b/icons/circleBy2TansRadius.png deleted file mode 100644 index c7cf8f40..00000000 Binary files a/icons/circleBy2TansRadius.png and /dev/null differ diff --git a/icons/circleBy2TansRadius.svg b/icons/circleBy2TansRadius.svg new file mode 100644 index 00000000..9c4d09fb --- /dev/null +++ b/icons/circleBy2TansRadius.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + diff --git a/icons/circleBy3Points.png b/icons/circleBy3Points.png deleted file mode 100644 index 870c6ad6..00000000 Binary files a/icons/circleBy3Points.png and /dev/null differ diff --git a/icons/circleBy3Points.svg b/icons/circleBy3Points.svg new file mode 100644 index 00000000..f4e8097f --- /dev/null +++ b/icons/circleBy3Points.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + diff --git a/icons/circleBy3Tans.png b/icons/circleBy3Tans.png deleted file mode 100644 index 8964a8d5..00000000 Binary files a/icons/circleBy3Tans.png and /dev/null differ diff --git a/icons/circleBy3Tans.svg b/icons/circleBy3Tans.svg new file mode 100644 index 00000000..ddd499b8 --- /dev/null +++ b/icons/circleBy3Tans.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + diff --git a/icons/circleByCenterDiameter.png b/icons/circleByCenterDiameter.png deleted file mode 100644 index f5cd6298..00000000 Binary files a/icons/circleByCenterDiameter.png and /dev/null differ diff --git a/icons/circleByCenterDiameter.svg b/icons/circleByCenterDiameter.svg new file mode 100644 index 00000000..85fcc77d --- /dev/null +++ b/icons/circleByCenterDiameter.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + diff --git a/icons/circleByCenterRadius.png b/icons/circleByCenterRadius.png deleted file mode 100644 index b41cfd81..00000000 Binary files a/icons/circleByCenterRadius.png and /dev/null differ diff --git a/icons/circleByCenterRadius.svg b/icons/circleByCenterRadius.svg new file mode 100644 index 00000000..704b9576 --- /dev/null +++ b/icons/circleByCenterRadius.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + diff --git a/icons/copy.png b/icons/copy.png deleted file mode 100644 index 8dd48c49..00000000 Binary files a/icons/copy.png and /dev/null differ diff --git a/icons/copy.svg b/icons/copy.svg new file mode 100644 index 00000000..2bde2af3 --- /dev/null +++ b/icons/copy.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/copyEnt.png b/icons/copyEnt.png deleted file mode 100644 index 194a668f..00000000 Binary files a/icons/copyEnt.png and /dev/null differ diff --git a/icons/copyEnt.svg b/icons/copyEnt.svg new file mode 100644 index 00000000..47714c75 --- /dev/null +++ b/icons/copyEnt.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + diff --git a/icons/dimAligned.png b/icons/dimAligned.png deleted file mode 100644 index 39e5f91a..00000000 Binary files a/icons/dimAligned.png and /dev/null differ diff --git a/icons/dimAligned.svg b/icons/dimAligned.svg new file mode 100644 index 00000000..233af098 --- /dev/null +++ b/icons/dimAligned.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + diff --git a/icons/dimArc.svg b/icons/dimArc.svg new file mode 100644 index 00000000..5f8b2b8a --- /dev/null +++ b/icons/dimArc.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/icons/dimLinear.png b/icons/dimLinear.png deleted file mode 100644 index 49bf9853..00000000 Binary files a/icons/dimLinear.png and /dev/null differ diff --git a/icons/dimLinear.svg b/icons/dimLinear.svg new file mode 100644 index 00000000..6e419928 --- /dev/null +++ b/icons/dimLinear.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + diff --git a/icons/dimRadius.svg b/icons/dimRadius.svg new file mode 100644 index 00000000..72e3a05e --- /dev/null +++ b/icons/dimRadius.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + diff --git a/icons/dimStyle.png b/icons/dimStyle.png deleted file mode 100644 index a3bd09e3..00000000 Binary files a/icons/dimStyle.png and /dev/null differ diff --git a/icons/dimStyle.svg b/icons/dimStyle.svg new file mode 100644 index 00000000..e74b31e9 --- /dev/null +++ b/icons/dimStyle.svg @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/disjoin.svg b/icons/disjoin.svg new file mode 100644 index 00000000..163621cb --- /dev/null +++ b/icons/disjoin.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + diff --git a/icons/divide.svg b/icons/divide.svg new file mode 100644 index 00000000..eb0d06d1 --- /dev/null +++ b/icons/divide.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + diff --git a/icons/down_key.svg b/icons/down_key.svg new file mode 100644 index 00000000..c5b64c28 --- /dev/null +++ b/icons/down_key.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + diff --git a/icons/dsettings.png b/icons/dsettings.png deleted file mode 100644 index b8a87d85..00000000 Binary files a/icons/dsettings.png and /dev/null differ diff --git a/icons/dsettings.svg b/icons/dsettings.svg new file mode 100644 index 00000000..8e9cac4c --- /dev/null +++ b/icons/dsettings.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/dsettings/dimension_input.png b/icons/dsettings/dimension_input.png new file mode 100644 index 00000000..9c11b146 Binary files /dev/null and b/icons/dsettings/dimension_input.png differ diff --git a/icons/dsettings/dynamic_prompts.png b/icons/dsettings/dynamic_prompts.png new file mode 100644 index 00000000..49b3cc33 Binary files /dev/null and b/icons/dsettings/dynamic_prompts.png differ diff --git a/icons/dsettings/pointer_input.png b/icons/dsettings/pointer_input.png new file mode 100644 index 00000000..f44cca75 Binary files /dev/null and b/icons/dsettings/pointer_input.png differ diff --git a/icons/ellipse.svg b/icons/ellipse.svg new file mode 100644 index 00000000..b70702cf --- /dev/null +++ b/icons/ellipse.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + diff --git a/icons/ellipseArc.svg b/icons/ellipseArc.svg new file mode 100644 index 00000000..db2156c7 --- /dev/null +++ b/icons/ellipseArc.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + diff --git a/icons/ellipseBy4Points.svg b/icons/ellipseBy4Points.svg new file mode 100644 index 00000000..2b301cfb --- /dev/null +++ b/icons/ellipseBy4Points.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + diff --git a/icons/ellipseByAxis1Point.svg b/icons/ellipseByAxis1Point.svg new file mode 100644 index 00000000..90153fc8 --- /dev/null +++ b/icons/ellipseByAxis1Point.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + diff --git a/icons/ellipseByCenter2Points.svg b/icons/ellipseByCenter2Points.svg new file mode 100644 index 00000000..2710ea62 --- /dev/null +++ b/icons/ellipseByCenter2Points.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + diff --git a/icons/ellipseByCenter3Points.svg b/icons/ellipseByCenter3Points.svg new file mode 100644 index 00000000..d18662d6 --- /dev/null +++ b/icons/ellipseByCenter3Points.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + diff --git a/icons/ellipseByExtent.svg b/icons/ellipseByExtent.svg new file mode 100644 index 00000000..6e0255ba --- /dev/null +++ b/icons/ellipseByExtent.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + diff --git a/icons/ellipseByFociPoint.svg b/icons/ellipseByFociPoint.svg new file mode 100644 index 00000000..72cd1433 --- /dev/null +++ b/icons/ellipseByFociPoint.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + diff --git a/icons/ellipseFromCenter.svg b/icons/ellipseFromCenter.svg new file mode 100644 index 00000000..dd775ee0 --- /dev/null +++ b/icons/ellipseFromCenter.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + diff --git a/icons/empty.png b/icons/empty.png deleted file mode 100644 index 7dfca0c8..00000000 Binary files a/icons/empty.png and /dev/null differ diff --git a/icons/erase.png b/icons/erase.png deleted file mode 100644 index aec4ec1f..00000000 Binary files a/icons/erase.png and /dev/null differ diff --git a/icons/erase.svg b/icons/erase.svg new file mode 100644 index 00000000..3ebd047b --- /dev/null +++ b/icons/erase.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + diff --git a/icons/extend.png b/icons/extend.png deleted file mode 100644 index 2b88ed82..00000000 Binary files a/icons/extend.png and /dev/null differ diff --git a/icons/extend.svg b/icons/extend.svg new file mode 100644 index 00000000..cbf585a2 --- /dev/null +++ b/icons/extend.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + diff --git a/icons/fillet.png b/icons/fillet.png deleted file mode 100644 index 7cf2e761..00000000 Binary files a/icons/fillet.png and /dev/null differ diff --git a/icons/fillet.svg b/icons/fillet.svg new file mode 100644 index 00000000..b63b1a63 --- /dev/null +++ b/icons/fillet.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + diff --git a/icons/help.png b/icons/help.png deleted file mode 100644 index d60425f7..00000000 Binary files a/icons/help.png and /dev/null differ diff --git a/icons/help.svg b/icons/help.svg new file mode 100644 index 00000000..14197c51 --- /dev/null +++ b/icons/help.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + diff --git a/icons/id.svg b/icons/id.svg new file mode 100644 index 00000000..0de3e21e --- /dev/null +++ b/icons/id.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + diff --git a/icons/insert.png b/icons/insert.png deleted file mode 100644 index d3a0aa8c..00000000 Binary files a/icons/insert.png and /dev/null differ diff --git a/icons/insert.svg b/icons/insert.svg new file mode 100644 index 00000000..9fa72919 --- /dev/null +++ b/icons/insert.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + diff --git a/icons/join.svg b/icons/join.svg new file mode 100644 index 00000000..b9c64cf2 --- /dev/null +++ b/icons/join.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + diff --git a/icons/lamp_on.png b/icons/lamp_on.png deleted file mode 100644 index 1dd6b01b..00000000 Binary files a/icons/lamp_on.png and /dev/null differ diff --git a/icons/lamp_on.svg b/icons/lamp_on.svg new file mode 100644 index 00000000..e7da0f62 --- /dev/null +++ b/icons/lamp_on.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + ! + + diff --git a/icons/lengthen.png b/icons/lengthen.png deleted file mode 100644 index 5778cd8d..00000000 Binary files a/icons/lengthen.png and /dev/null differ diff --git a/icons/lengthen.svg b/icons/lengthen.svg new file mode 100644 index 00000000..df3584bb --- /dev/null +++ b/icons/lengthen.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + diff --git a/icons/line.png b/icons/line.png deleted file mode 100644 index 8a4a43d7..00000000 Binary files a/icons/line.png and /dev/null differ diff --git a/icons/line.svg b/icons/line.svg new file mode 100644 index 00000000..67ed23c4 --- /dev/null +++ b/icons/line.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + diff --git a/icons/locked.svg b/icons/locked.svg new file mode 100644 index 00000000..f049270f --- /dev/null +++ b/icons/locked.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/mapmpedit.svg b/icons/mapmpedit.svg new file mode 100644 index 00000000..674e55a4 --- /dev/null +++ b/icons/mapmpedit.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/mbuffer.png b/icons/mbuffer.png deleted file mode 100644 index 7bd5d83b..00000000 Binary files a/icons/mbuffer.png and /dev/null differ diff --git a/icons/mbuffer.svg b/icons/mbuffer.svg new file mode 100644 index 00000000..1261224c --- /dev/null +++ b/icons/mbuffer.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + diff --git a/icons/measure.svg b/icons/measure.svg new file mode 100644 index 00000000..2d35f9e4 --- /dev/null +++ b/icons/measure.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/icons/mirror.png b/icons/mirror.png deleted file mode 100644 index 94428742..00000000 Binary files a/icons/mirror.png and /dev/null differ diff --git a/icons/mirror.svg b/icons/mirror.svg new file mode 100644 index 00000000..98513a01 --- /dev/null +++ b/icons/mirror.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + diff --git a/icons/move.png b/icons/move.png deleted file mode 100644 index 9d2a5e5c..00000000 Binary files a/icons/move.png and /dev/null differ diff --git a/icons/move.svg b/icons/move.svg new file mode 100644 index 00000000..f4d2d73a --- /dev/null +++ b/icons/move.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + diff --git a/icons/mpolygon.png b/icons/mpolygon.png deleted file mode 100644 index cb6d44ca..00000000 Binary files a/icons/mpolygon.png and /dev/null differ diff --git a/icons/mpolygon.svg b/icons/mpolygon.svg new file mode 100644 index 00000000..23066774 --- /dev/null +++ b/icons/mpolygon.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/offset.png b/icons/offset.png deleted file mode 100644 index c18cf34d..00000000 Binary files a/icons/offset.png and /dev/null differ diff --git a/icons/offset.svg b/icons/offset.svg new file mode 100644 index 00000000..673393a8 --- /dev/null +++ b/icons/offset.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + diff --git a/icons/options.svg b/icons/options.svg new file mode 100644 index 00000000..a3f8a458 --- /dev/null +++ b/icons/options.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + diff --git a/icons/osnap.svg b/icons/osnap.svg new file mode 100644 index 00000000..89c4435d --- /dev/null +++ b/icons/osnap.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/osnap_cen.png b/icons/osnap_cen.png deleted file mode 100644 index eb00fa28..00000000 Binary files a/icons/osnap_cen.png and /dev/null differ diff --git a/icons/osnap_cen.svg b/icons/osnap_cen.svg new file mode 100644 index 00000000..232de0c9 --- /dev/null +++ b/icons/osnap_cen.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + diff --git a/icons/osnap_disable.png b/icons/osnap_disable.png deleted file mode 100644 index 8a1616a1..00000000 Binary files a/icons/osnap_disable.png and /dev/null differ diff --git a/icons/osnap_disable.svg b/icons/osnap_disable.svg new file mode 100644 index 00000000..bd47f696 --- /dev/null +++ b/icons/osnap_disable.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/osnap_end.png b/icons/osnap_end.png deleted file mode 100644 index 532bb164..00000000 Binary files a/icons/osnap_end.png and /dev/null differ diff --git a/icons/osnap_end.svg b/icons/osnap_end.svg new file mode 100644 index 00000000..aa45d193 --- /dev/null +++ b/icons/osnap_end.svg @@ -0,0 +1,35 @@ + + + + + + + Layer 1 + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/icons/osnap_endLine.png b/icons/osnap_endLine.png deleted file mode 100644 index 03995eb0..00000000 Binary files a/icons/osnap_endLine.png and /dev/null differ diff --git a/icons/osnap_endLine.svg b/icons/osnap_endLine.svg new file mode 100644 index 00000000..2d9cf589 --- /dev/null +++ b/icons/osnap_endLine.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/osnap_ext.png b/icons/osnap_ext.png deleted file mode 100644 index 607c3c74..00000000 Binary files a/icons/osnap_ext.png and /dev/null differ diff --git a/icons/osnap_ext.svg b/icons/osnap_ext.svg new file mode 100644 index 00000000..6dab8c4f --- /dev/null +++ b/icons/osnap_ext.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + diff --git a/icons/osnap_extInt.png b/icons/osnap_extInt.png deleted file mode 100644 index 9eedc178..00000000 Binary files a/icons/osnap_extInt.png and /dev/null differ diff --git a/icons/osnap_extInt.svg b/icons/osnap_extInt.svg new file mode 100644 index 00000000..ddd9c4a7 --- /dev/null +++ b/icons/osnap_extInt.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/icons/osnap_int.png b/icons/osnap_int.png deleted file mode 100644 index 0e8d2c1e..00000000 Binary files a/icons/osnap_int.png and /dev/null differ diff --git a/icons/osnap_int.svg b/icons/osnap_int.svg new file mode 100644 index 00000000..86a6f215 --- /dev/null +++ b/icons/osnap_int.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + diff --git a/icons/osnap_mid.png b/icons/osnap_mid.png deleted file mode 100644 index 7cb8babe..00000000 Binary files a/icons/osnap_mid.png and /dev/null differ diff --git a/icons/osnap_mid.svg b/icons/osnap_mid.svg new file mode 100644 index 00000000..a546d644 --- /dev/null +++ b/icons/osnap_mid.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + diff --git a/icons/osnap_mid2p.svg b/icons/osnap_mid2p.svg new file mode 100644 index 00000000..9e17a0c1 --- /dev/null +++ b/icons/osnap_mid2p.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/osnap_nea.png b/icons/osnap_nea.png deleted file mode 100644 index 3ef653bd..00000000 Binary files a/icons/osnap_nea.png and /dev/null differ diff --git a/icons/osnap_nea.svg b/icons/osnap_nea.svg new file mode 100644 index 00000000..dc9755c7 --- /dev/null +++ b/icons/osnap_nea.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/osnap_nod.png b/icons/osnap_nod.png deleted file mode 100644 index 3b54aeca..00000000 Binary files a/icons/osnap_nod.png and /dev/null differ diff --git a/icons/osnap_nod.svg b/icons/osnap_nod.svg new file mode 100644 index 00000000..e2f8ca95 --- /dev/null +++ b/icons/osnap_nod.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + diff --git a/icons/osnap_par.png b/icons/osnap_par.png deleted file mode 100644 index 0b2b7922..00000000 Binary files a/icons/osnap_par.png and /dev/null differ diff --git a/icons/osnap_par.svg b/icons/osnap_par.svg new file mode 100644 index 00000000..a422896c --- /dev/null +++ b/icons/osnap_par.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + diff --git a/icons/osnap_per.png b/icons/osnap_per.png deleted file mode 100644 index 22a5f32d..00000000 Binary files a/icons/osnap_per.png and /dev/null differ diff --git a/icons/osnap_per.svg b/icons/osnap_per.svg new file mode 100644 index 00000000..bc43013a --- /dev/null +++ b/icons/osnap_per.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + diff --git a/icons/osnap_pr.png b/icons/osnap_pr.png deleted file mode 100644 index 477a1c80..00000000 Binary files a/icons/osnap_pr.png and /dev/null differ diff --git a/icons/osnap_pr.svg b/icons/osnap_pr.svg new file mode 100644 index 00000000..538c35e7 --- /dev/null +++ b/icons/osnap_pr.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + diff --git a/icons/osnap_qua.png b/icons/osnap_qua.png deleted file mode 100644 index b82abc0d..00000000 Binary files a/icons/osnap_qua.png and /dev/null differ diff --git a/icons/osnap_qua.svg b/icons/osnap_qua.svg new file mode 100644 index 00000000..f235851b --- /dev/null +++ b/icons/osnap_qua.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/osnap_tan.png b/icons/osnap_tan.png deleted file mode 100644 index 8cc828c5..00000000 Binary files a/icons/osnap_tan.png and /dev/null differ diff --git a/icons/osnap_tan.svg b/icons/osnap_tan.svg new file mode 100644 index 00000000..10dbb475 --- /dev/null +++ b/icons/osnap_tan.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/pedit.png b/icons/pedit.png deleted file mode 100644 index 5c999516..00000000 Binary files a/icons/pedit.png and /dev/null differ diff --git a/icons/pedit.svg b/icons/pedit.svg new file mode 100644 index 00000000..8b83e675 --- /dev/null +++ b/icons/pedit.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/pline.png b/icons/pline.png deleted file mode 100644 index fd26cf67..00000000 Binary files a/icons/pline.png and /dev/null differ diff --git a/icons/pline.svg b/icons/pline.svg new file mode 100644 index 00000000..b2f63b72 --- /dev/null +++ b/icons/pline.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + diff --git a/icons/polygon.png b/icons/polygon.png deleted file mode 100644 index fdc22e00..00000000 Binary files a/icons/polygon.png and /dev/null differ diff --git a/icons/polygon.svg b/icons/polygon.svg new file mode 100644 index 00000000..3d0cb1f4 --- /dev/null +++ b/icons/polygon.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + diff --git a/icons/qad.png b/icons/qad.png deleted file mode 100644 index 320e215e..00000000 Binary files a/icons/qad.png and /dev/null differ diff --git a/icons/qad.svg b/icons/qad.svg new file mode 100644 index 00000000..73dabc2e --- /dev/null +++ b/icons/qad.svg @@ -0,0 +1,205 @@ + + + + diff --git a/icons/rectangle.png b/icons/rectangle.png deleted file mode 100644 index 7cc0d204..00000000 Binary files a/icons/rectangle.png and /dev/null differ diff --git a/icons/rectangle.svg b/icons/rectangle.svg new file mode 100644 index 00000000..1ee33926 --- /dev/null +++ b/icons/rectangle.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + diff --git a/icons/redo.png b/icons/redo.png deleted file mode 100644 index 0cb2a88d..00000000 Binary files a/icons/redo.png and /dev/null differ diff --git a/icons/redo.svg b/icons/redo.svg new file mode 100644 index 00000000..2aa78938 --- /dev/null +++ b/icons/redo.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + diff --git a/icons/rotate.png b/icons/rotate.png deleted file mode 100644 index bc56e5a2..00000000 Binary files a/icons/rotate.png and /dev/null differ diff --git a/icons/rotate.svg b/icons/rotate.svg new file mode 100644 index 00000000..df1860e5 --- /dev/null +++ b/icons/rotate.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + diff --git a/icons/scale.png b/icons/scale.png deleted file mode 100644 index 33a62466..00000000 Binary files a/icons/scale.png and /dev/null differ diff --git a/icons/scale.svg b/icons/scale.svg new file mode 100644 index 00000000..d2254c3e --- /dev/null +++ b/icons/scale.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/icons/setcurrlayerbygraph.png b/icons/setcurrlayerbygraph.png deleted file mode 100644 index bd63d28f..00000000 Binary files a/icons/setcurrlayerbygraph.png and /dev/null differ diff --git a/icons/setcurrlayerbygraph.svg b/icons/setcurrlayerbygraph.svg new file mode 100644 index 00000000..b91e7f58 --- /dev/null +++ b/icons/setcurrlayerbygraph.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/setcurrupdateablelayerbygraph.png b/icons/setcurrupdateablelayerbygraph.png deleted file mode 100644 index 7394f57c..00000000 Binary files a/icons/setcurrupdateablelayerbygraph.png and /dev/null differ diff --git a/icons/setcurrupdateablelayerbygraph.svg b/icons/setcurrupdateablelayerbygraph.svg new file mode 100644 index 00000000..5f3559e9 --- /dev/null +++ b/icons/setcurrupdateablelayerbygraph.svg @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/stretch.png b/icons/stretch.png deleted file mode 100644 index 65f03e80..00000000 Binary files a/icons/stretch.png and /dev/null differ diff --git a/icons/stretch.svg b/icons/stretch.svg new file mode 100644 index 00000000..9ad6f707 --- /dev/null +++ b/icons/stretch.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/supporters.svg b/icons/supporters.svg new file mode 100644 index 00000000..99bfbe90 --- /dev/null +++ b/icons/supporters.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + diff --git a/icons/text.png b/icons/text.png deleted file mode 100644 index b774b696..00000000 Binary files a/icons/text.png and /dev/null differ diff --git a/icons/text.svg b/icons/text.svg new file mode 100644 index 00000000..317294ea --- /dev/null +++ b/icons/text.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/trim.png b/icons/trim.png deleted file mode 100644 index 4b8aa9d8..00000000 Binary files a/icons/trim.png and /dev/null differ diff --git a/icons/trim.svg b/icons/trim.svg new file mode 100644 index 00000000..87f51701 --- /dev/null +++ b/icons/trim.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + diff --git a/icons/u.png b/icons/u.png deleted file mode 100644 index 6e0b2bbe..00000000 Binary files a/icons/u.png and /dev/null differ diff --git a/icons/u.svg b/icons/u.svg new file mode 100644 index 00000000..f6b8e515 --- /dev/null +++ b/icons/u.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + diff --git a/icons/undo.png b/icons/undo.png deleted file mode 100644 index 6e0b2bbe..00000000 Binary files a/icons/undo.png and /dev/null differ diff --git a/icons/undo.svg b/icons/undo.svg new file mode 100644 index 00000000..15f7e458 --- /dev/null +++ b/icons/undo.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + diff --git a/icons/variable.png b/icons/variable.png deleted file mode 100644 index a22bfcbd..00000000 Binary files a/icons/variable.png and /dev/null differ diff --git a/icons/variable.svg b/icons/variable.svg new file mode 100644 index 00000000..b6f2fb67 --- /dev/null +++ b/icons/variable.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + diff --git a/metadata.txt b/metadata.txt index 69ce46b8..b8b82a49 100644 --- a/metadata.txt +++ b/metadata.txt @@ -1,40 +1,40 @@ -# This file contains metadata for your plugin. Beginning -# with version 1.8 this is the preferred way to supply information about a -# plugin. The current method of embedding metadata in __init__.py will -# be supported until version 2.0 - -# This file should be included when you package your plugin. - -# Mandatory items: - - -[general] -name=QAD -qgisMinimumVersion=2.0 -qgisMaximumVersion=2.99 -description=Quantum Aided Design: cad like commands in QGIS -version=2.4 - -# end of mandatory metadata - -# Optional items: - -# Uncomment the following line and add your changelog entries: -# changelog= - -# tags are comma separated with spaces allowed -tags=cad,digitizing,vector - -homepage=iiiii -tracker=xxxxx -repository=yyyy -icon=icons/qad.png -# experimental flag -experimental=True - -# deprecated flag (applies to the whole plugin, not just a single version -deprecated=False - -# Author contact information -author=iiiii -email=hhhhh +# This file contains metadata for your plugin. Beginning +# with version 1.8 this is the preferred way to supply information about a +# plugin. The current method of embedding metadata in __init__.py will +# be supported until version 2.0 + +# This file should be included when you package your plugin. + +# Mandatory items: + +[general] +name=QAD +email=gam17@riseup.net +author=gam17 +qgisMinimumVersion=3.40 +description=Quantum Aided Design: cad like commands in QGIS +about=Quantum Aided Design: cad like commands in QGIS +version=3.0.8 +tracker=https://github.com/gam17/QAD/issues +repository=https://github.com/gam17/QAD + +# end of mandatory metadata + +# Optional items: + +category=Vector + +# tags are comma separated with spaces allowed +tags=cad,circle,digitizing,dimensioning,ellipse,polygon,polyline,rectangle,snap,vector + +homepage=https://qadplugin.wordpress.com +icon=icons/qad.png +# experimental flag +experimental=False + +# deprecated flag (applies to the whole plugin, not just a single version +deprecated=False + +qgisMaximumVersion=3.99 + + \ No newline at end of file diff --git a/qad.ini b/qad.ini index 64868e47..0932f670 100644 --- a/qad.ini +++ b/qad.ini @@ -1,34 +1,61 @@ -DIMSTYLE = standard -SUPPORTPATH = C:\Users\poltinir.MASTER.000\.qgis2\python\plugins\Qad\support -GRIPCONTOUR = #939393 -GRIPCOLOR = #100DD6 -GRIPHOVER = #FF7F7F -SELECTIONAREA = 1 -EDGEMODE = 0 -OSCOLOR = #FF0000 -GRIPS = 2 -CURSORCOLOR = #FF0000 -PICKBOXCOLOR = #FF0000 -SELECTIONAREAOPACITY = 25 -SHOWTEXTWINDOW = True -ORTHOMODE = 0 -CIRCLEMINSEGMENTQTY = 12 -TOLERANCE2APPROXCURVE = 0.1 -FILLETRAD = 0.0 -GRIPSIZE = 10 -PICKBOX = 5 -OSPROGRDISTANCE = 0.0 -OFFSETGAPTYPE = 0 -POLARANG = 90.0 -GRIPHOT = #FF0000 -OFFSETDIST = -1.0 -CMDINPUTHISTORYMAX = 20 -WINDOWAREACOLOR = #1F78B4 -ARCMINSEGMENTQTY = 12 -CROSSINGAREACOLOR = #33A02C -COPYMODE = 0 -OSSIZE = 13 -PICKADD = 0 -OSMODE = 2097153 -CURSORSIZE = 5 -AUTOSNAP = 63 +APBOX = 0 +APERTURE = 10 +AUTOSNAP = 63 +AUTOSNAPCOLOR = #33A02C +AUTOSNAPSIZE = 5 +AUTOTRECKINGVECTORCOLOR = #33A02C +CMDHISTORYBACKCOLOR = #C8C8C8 +CMDHISTORYFORECOLOR = #000000 +CMDINPUTHISTORYMAX = 20 +CMDLINEBACKCOLOR = #FFFFFF +CMDLINEFORECOLOR = #000000 +CMDLINEOPTBACKCOLOR = #D2D2D2 +CMDLINEOPTCOLOR = #0000FF +CMDLINEOPTHIGHLIGHTEDCOLOR = #B3B3B3 +COPYMODE = 0 +CROSSINGAREACOLOR = #00FF3F +CURSORCOLOR = #FF0000 +CURSORSIZE = 5 +DELOBJ = 1 +DYNDIGRIP = 31 +DYNDIVIS = 1 +DYNEDITFORECOLOR = #000000 +DYNEDITBACKCOLOR = #939393 +DYNEDITBORDERCOLOR = #000000 +DYNMODE = 3 +DYNPICOORDS = 0 +DYNPIFORMAT = 0 +DYNPIVIS = 1 +DYNPROMPT = 1 +DYNTOOLTIPS = 1 +DYNTRECKINGVECTORCOLOR = #969C9A +EDGEMODE = 0 +GRIPCOLOR = #100DD6 +GRIPCONTOUR = #939393 +GRIPHOT = #FF0000 +GRIPHOVER = #FF7F7F +GRIPMULTIFUNCTIONAL = 3 +GRIPOBJLIMIT = 100 +GRIPS = 1 +GRIPSIZE = 5 +INPUTSEARCHDELAY = 500 +INPUTSEARCHOPTIONS = 15 +MAXARRAY = 100000 +OFFSETGAPTYPE = 0 +OSMODE = 33 +PICKADD = 0 +PICKBOX = 5 +PICKBOXCOLOR = #FF0000 +PICKFIRST = 1 +POLARADDANG = +POLARANG = 90.0 +POLARMODE = 0 +SELECTIONAREA = 1 +SELECTIONAREAOPACITY = 25 +SHORTCUTMENU = 11 +SHORTCUTMENUDURATION = 250 +SUPPORTPATH = C:\Users\PoltiniR\AppData\Roaming\QGIS\QGIS3\profiles\default\python\plugins\qad\support +SHOWTEXTWINDOW = True +TOOLTIPTRANSPARENCY = 0 +TOOLTIPSIZE = 0 +WINDOWAREACOLOR = #007EFF diff --git a/qad.psproj b/qad.psproj new file mode 100644 index 00000000..8a696ae2 --- /dev/null +++ b/qad.psproj @@ -0,0 +1,20 @@ +[PyScripter] +Version=2.4.3.0 + +[Project] +ClassName=TProjectRootNode +StoreRelativePaths=FALSE +ShowFileExtensions=FALSE + +[Project\ChildNodes\Node0] +ClassName=TProjectFilesNode + +[Project\ChildNodes\Node1] +ClassName=TProjectRunConfiguationsNode + +[Project\ChildNodes] +Count=2 + +[Project\ExtraPythonPath] +Count=0 + diff --git a/qad.py b/qad.py index f116dcca..eb17bcf7 100644 --- a/qad.py +++ b/qad.py @@ -1,1551 +1,2225 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - comandi di editazione geometria stile CAD - - ------------------- - begin : 2014-11-03 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries - -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -# Initialize Qt resources from file qad_rc.py -import qad_rc -import math - - -import qad_utils -from qad_maptool import QadMapTool -from qad_variables import * -from qad_textwindow import * -from qad_commands import * -from qad_entity import * -from qad_dim import QadDimStyles -import qad_layer -import qad_undoredo - -class Qad(QObject): - """ - Classe plug in di Qad - """ - - # UI - toolBar = None - dimToolBar = None - menu = None - translator = None - - - # Map Tool attivo. Quando non ci sono comandi che necessitano di input dalla finestra grafica - # QadMapTool é quello attivo - tool = None - # Finestra grafica - canvas = None - # Finestra testuale - TextWindow = None - # Classe che gestisce i comandi - QadCommands = None - # Azione corrente - currentAction = None - # Finestra testuale già collegata - __alreadyDockedTextWindow = False - # ultimo punto selezionato - lastPoint = None - # coeff angolare ultimo segmento - lastSegmentAng = 0.0 - # ultima rotazione - lastRot = 0.0 - # ultima altezza testo - lastHText = 1.0 - # ultimo angolo di riferimento (es. comando ruota) - lastReferenceRot = 0.0 - # ultimo nuovo angolo di riferimento (es. comando ruota) - lastNewReferenceRot = 0.0 - # ultimo raggio - lastRadius = 1.0 - # ultimo punto di offset - lastOffsetPt = QgsPoint(0, 0) - # ultima lunghezza di riferimento (es. comando scala) - lastReferenceLen = 1.0 - # ultima lunghezza di riferimento (es. comando scala) - lastNewReferenceLen = 1.0 - # ultimo fattore di scala (es. comando scala) - lastScale = 1.0 - # numero di segmenti per l'approssimazione delle curve (es. buffer) - segments = 10 - # ultima entità inserita - lastEntity = None - # ultimo set di entità - lastEntitySet = None - # tipo di unione (es. editpl->unione) - joinMode = 1 # 1=Estendi, 2=Aggiungi, 3=Entrambi - # distanza di approssimazione nell'unione (es. editpl->unione) - joinToleranceDist = 0.0 - # modalità di raccordo in comando raccordo - filletMode = 1 # 1=Taglia-estendi, 2=Non taglia-estendi - # ultimo numero di lati per poligono - lastPolygonSideNumber = 4 - # ultima opzione di costruzione del poligono conoscendo il centro - # "Inscritto nel cerchio", Circoscritto intorno al cerchio", "Area" - lastPolygonConstructionModeByCenter = QadMsg.translate("Command_POLYGON", "Inscribed in circle") - # ultimo delta usato nel comando lengthen - lastDelta_lengthen = 0.0 - # ultimo delta angolo usato nel comando lengthen - lastDeltaAngle_lengthen = 0.0 - # ultima percentuale usata nel comando lengthen - lastPerc_lengthen = 100.0 - # ultima lunghezza totale usato nel comando lengthen - lastTotal_lengthen = 1.0 - # ultimo angolo totale usato nel comando lengthen - lastTotalAngle_lengthen = 0.0 - # ultima modalità operativa del comando lengthen - lastOpMode_lengthen = "DElta" - - # flag per identificare se un comando di QAD é attivo oppure no - isQadActive = False - - # Quotatura - dimTextEntitySetRecodeOnSave = QadLayerEntitySet() # entity set dei testi delle quote da riallineare in salvataggio - beforeCommitChangesDimLayer = None # layer da cui é scaturito il salvataggio delle quotature - isSaveControlledByQAD = False - - def version(self): - return "2.8.005" - - def setLastPointAndSegmentAng(self, point, segmentAng = None): - # memorizzo il coeff angolare ultimo segmento e l'ultimo punto selezionato - if segmentAng is None: - if self.lastPoint is not None: - self.setLastSegmentAng(qad_utils.getAngleBy2Pts(self.lastPoint, point)) - else: - self.setLastSegmentAng(segmentAng) - self.setLastPoint(point) - - def setLastPoint(self, point): - # memorizzo l'ultimo punto selezionato - self.lastPoint = point - - def setLastSegmentAng(self, segmentAng): - # memorizzo il coeff angolare ultimo segmento - self.lastSegmentAng = qad_utils.normalizeAngle(segmentAng) - - def setLastRot(self, rot): - # memorizzo l'ultima rotazione in radianti - self.lastRot = qad_utils.normalizeAngle(rot) - - def setLastHText(self, hText): - # memorizzo l'ultima altezza testo - if hText > 0: - self.lastHText = hText - - def setLastReferenceRot(self, rot): - # memorizzo l'ultimo angolo di riferimento (es. comando ruota) in radianti - self.lastReferenceRot = qad_utils.normalizeAngle(rot) - - def setLastNewReferenceRot(self, rot): - # memorizzo l'ultimo nuovo angolo di riferimento (es. comando ruota) in radianti - self.lastNewReferenceRot = qad_utils.normalizeAngle(rot) - - def setLastRadius(self, radius): - # memorizzo l'ultimo raggio - if radius > 0: - self.lastRadius = radius - - def setLastOffsetPt(self, offSetPt): - # memorizzo l'ultimo punto di offset - # la x del punto rappresenta l'offset X - # la y del punto rappresenta l'offset Y - self.lastOffsetPt.set(offSetPt.x(), offSetPt.y()) - - def setLastReferenceLen(self, length): - # memorizzo l'ultima lunghezza di riferimento (es. comando scale) - self.lastReferenceLen = length - - def setLastNewReferenceRot(self, length): - # memorizzo l'ultima nuova lunghezza di riferimento (es. comando scale) - self.lastNewReferenceLen = length - - def setLastScale(self, scale): - # memorizzo l'ultimo fattore di scala - if scale > 0: - self.lastScale = scale - - def setNSegmentsToApproxCurve(self, segments): - # memorizzo il numero di segmenti per l'approssimazione delle curve (es. buffer) - if segments > 1: - self.segments = int(segments) - - def setLastEntity(self, layer, featureId): - # memorizzo l'ultimo entità creata - if self.lastEntity is None: - self.lastEntity = QadEntity() - self.lastEntity.set(layer, featureId) - - def getLastEntity(self): - if self.lastEntity is None: - return None - else: - if self.lastEntity.exists() == False: # non esiste più - return None - else: - return self.lastEntity - - def setLastEntitySet(self, entitySet): - # memorizzo l'ultimo set di entità - if self.lastEntitySet is None: - self.lastEntitySet = QadEntitySet() - self.lastEntitySet.set(entitySet) - - def setJoinMode(self, joinMode): - # memorizzo tipo di unione (es. editpl->unione); 1=Estendi, 2=Aggiungi, 3=Entrambi - if joinMode == 1 or joinMode == 2 or joinMode == 3: - self.joinMode = int(joinMode) - - def setJoinToleranceDist(self, joinToleranceDist): - # memorizzo la distanza di approssimazione nell'unione (es. editpl->unione) - if joinToleranceDist >= 0: - self.joinToleranceDist = joinToleranceDist - - def setFilletMode(self, filletMode): - # memorizzo modalità di raccordo in comando raccordo; 1=Taglia-estendi, 2=Non taglia-estendi - if filletMode == 1 or filletMode == 2: - self.filletMode = int(filletMode) - - def setLastPolygonSideNumber(self, polygonSideNumber): - # memorizzo l'ultimo numero di lati del poligono - if polygonSideNumber > 2: - self.lastPolygonSideNumber = polygonSideNumber - - def setLastPolygonConstructionModeByCenter(self, mode): - # memorizzo ultima opzione di costruzione del poligono conoscendo il centro - # "Inscritto nel cerchio", Circoscritto intorno al cerchio", "Area" - self.lastPolygonConstructionModeByCenter = mode - - def setLastDelta_lengthen(self, lastDelta_lengthen): - # ultimo delta usato nel comando lengthen - self.lastDelta_lengthen = lastDelta_lengthen - - def setLastDeltaAngle_lengthen(self, lastDeltaAngle_lengthen): - # ultimo delta angolo usato nel comando lengthen - self.lastDeltaAngle_lengthen = qad_utils.normalizeAngle(lastDeltaAngle_lengthen) - - def setLastPerc_lengthen(self, lastPerc_lengthen): - # ultima percentuale usata nel comando lengthen - if lastPerc_lengthen > 0: - self.lastPerc_lengthen = lastPerc_lengthen - - def setLastTotal_lengthen(self, lastTotal_lengthen): - # ultima lunghezza totale usato nel comando lengthen - if lastTotal_lengthen > 0: - self.lastTotal_lengthen = lastTotal_lengthen - - def setLastTotalAngle_lengthen(self, lastTotalAngle_lengthen): - # ultimo angolo totale usato nel comando lengthen - self.lastTotalAngle_lengthen = qad_utils.normalizeAngle(lastTotalAngle_lengthen) - - def setLastOpMode_lengthen(self, opMode): - # memorizzo modalità operativa del comando lengthen: "DElta" o "Percent" o "Total" o "DYnamic" - if opMode == "DElta" or opMode == "Percent" or opMode == "Total" or opMode == "DYnamic": - self.lastOpMode_lengthen = opMode - - def loadDimStyles(self): - global QadDimStyles - # carico gli stili di quotatura - QadDimStyles.load() - # questa variabile non avrebbe senso perchè si dovrebbe usare la variabile globale QadDimStyles - # per un motivo sconosciuto quando si generano gli eventi tipo beforeCommitChanges - # la variabile globale QadDimStyles risulta essere None anche se QAD non lo hai mai posto a quel valore - # se invece uso una variabile del plugin che punta a QadDimStyles questa non viene messa a None - self.mQadDimStyle = QadDimStyles - - - #============================================================================ - # __initLocalization - #============================================================================ - # inizializza la localizzazione delle traduzioni e dell'help in linea - def __initLocalization(self, locale): - localePath = os.path.join(self.plugin_dir, 'i18n', 'qad_{}.qm'.format(locale)) - - if os.path.exists(localePath): - self.translator = QTranslator() - self.translator.load(localePath) - if qVersion() > '4.3.3': - QCoreApplication.installTranslator(self.translator) - return True - else: - return False - - - #============================================================================ - # __init__ - #============================================================================ - def __init__(self, iface): - - QObject.__init__(self) - - # Save reference to the QGIS interface - self.iface = iface - - # initialize plugin directory - self.plugin_dir = os.path.dirname(__file__) - - # initialize locale - userLocaleList = QSettings().value("locale/userLocale").split("_") - language = userLocaleList[0] - region = userLocaleList[1] if len(userLocaleList) > 1 else "" - # provo a caricare la lingua e la regione selezionate - if self.__initLocalization(language + "_" + region) == False: - # provo a caricare la lingua - self.__initLocalization(language) - - self.canvas = self.iface.mapCanvas() - self.tool = QadMapTool(self) - - # Lista dei comandi - self.QadCommands = QadCommandsClass(self) - - # inizializzzione sul caricamento del progetto - self.initOnProjectLoaded() - - def initOnProjectLoaded(self): - # carico le variabili d'ambiente - QadVariables.load() - # carico gli stili di quotatura - self.loadDimStyles() - # Gestore di Undo/Redo - self.undoStack = qad_undoredo.QadUndoStack() - - def initGui(self): - # creo tutte le azioni e le collego ai comandi - self.initActions() - - # Connect to signals - QObject.connect(self.canvas, SIGNAL("mapToolSet(QgsMapTool*)"), self.deactivate) - - # Add menu - self.menu = QMenu(QadMsg.translate("QAD", "QAD")) - self.menu.addAction(self.mainAction) - self.menu.addAction(self.help_action) - - self.menu.addAction(self.u_action) - self.menu.addAction(self.undo_action) - self.menu.addAction(self.redo_action) - - # crea il menu Draw - self.drawMenu = self.createDrawMenu() - self.menu.addMenu(self.drawMenu) - - # menu Edit - self.editMenu = self.createEditMenu() - self.menu.addMenu(self.editMenu) - - # menu Tools - self.toolsMenu = self.createToolsMenu() - self.menu.addMenu(self.toolsMenu) - - # menu Dim - self.dimMenu = self.createDimMenu() - self.menu.addMenu(self.dimMenu) - - # aggiunge il menu al menu vector di QGIS - self.iface.vectorMenu().addMenu(self.menu) - -# menu_bar = self.iface.mainWindow().menuBar() -# actions = menu_bar.actions() -# lastAction = actions[ len( actions ) - 1 ] -# menu_bar.insertMenu(lastAction, self.menu ) - - # aggiunge le toolbar - self.toolBar = self.iface.addToolBar("QAD") - self.toolBar.setObjectName("QAD") - self.toolBar.addAction(self.mainAction) - self.toolBar.addAction(self.help_action) - - # aggiunge le toolbar per i comandi - self.toolBar.addAction(self.setCurrLayerByGraph_action) - self.toolBar.addAction(self.setCurrUpdateableLayerByGraph_action) - self.toolBar.addAction(self.u_action) - self.toolBar.addAction(self.redo_action) - self.toolBar.addAction(self.line_action) - self.toolBar.addAction(self.pline_action) - # arco - self.arcToolButton = self.createArcToolButton() - self.toolBar.addWidget(self.arcToolButton) - # cerchio - self.circleToolButton = self.createCircleToolButton() - self.toolBar.addWidget(self.circleToolButton) - - self.toolBar.addAction(self.rectangle_action) - self.toolBar.addAction(self.polygon_action) - self.toolBar.addAction(self.mpolygon_action) - self.toolBar.addAction(self.mbuffer_action) - self.toolBar.addAction(self.insert_action) - self.toolBar.addAction(self.text_action) - - self.toolBar.addAction(self.erase_action) - self.toolBar.addAction(self.rotate_action) - self.toolBar.addAction(self.move_action) - self.toolBar.addAction(self.scale_action) - self.toolBar.addAction(self.copy_action) - self.toolBar.addAction(self.offset_action) - self.toolBar.addAction(self.extend_action) - self.toolBar.addAction(self.trim_action) - self.toolBar.addAction(self.mirror_action) - self.toolBar.addAction(self.stretch_action) - self.toolBar.addAction(self.lengthen_action) - self.toolBar.addAction(self.break_action) - self.toolBar.addAction(self.pedit_action) - self.toolBar.addAction(self.fillet_action) - self.toolBar.addAction(self.dsettings_action) - self.enableUndoRedoButtons() - - # aggiunge la toolbar per la quotatura - self.dimToolBar = self.createDimToolBar() - - # Inizializzo la finestra di testo - self.TextWindow = QadTextWindow(self) - self.TextWindow.initGui() - - # aggiungo i segnali di aggiunta e rimozione di layer per collegare ogni layer - # all'evento per sapere se la modifica fatta su quel layer - # é stata fatta da QAD o dall'esterno - QObject.connect(QgsMapLayerRegistry.instance(), SIGNAL("layerWasAdded(QgsMapLayer *)"), self.layerAdded) - QObject.connect(QgsMapLayerRegistry.instance(), SIGNAL("layerWillBeRemoved(QString)"), self.removeLayer) - QObject.connect(self.iface, SIGNAL("projectRead()"), self.onProjectLoaded) - - self.showTextWindow(QadVariables.get(QadMsg.translate("Environment variables", "SHOWTEXTWINDOW"), True)) - self.setStandardMapTool() - - - def unload(self): - self.abortCommand() - # Disconnect to signals - QObject.disconnect(self.canvas, SIGNAL("mapToolSet(QgsMapTool*)"), self.deactivate) - QObject.disconnect(QgsMapLayerRegistry.instance(), SIGNAL("layerWasAdded(QgsMapLayer *)"), self.layerAdded) - QObject.disconnect(QgsMapLayerRegistry.instance(), SIGNAL("layerWillBeRemoved(QString)"), self.removeLayer) - QObject.disconnect(self.iface, SIGNAL("projectRead()"), self.onProjectLoaded) - - # Remove the plugin menu item and icon - self.iface.removePluginVectorMenu("&QAD", self.mainAction) - self.iface.removeToolBarIcon(self.mainAction) - - # remove toolbars and menubars - if self.toolBar is not None: - del self.toolBar - if self.dimToolBar is not None: - del self.dimToolBar - if self.menu is not None: - del self.menu - if self.TextWindow is not None: - self.TextWindow.close() - if self.tool: - if self.canvas.mapTool() == self.tool: - self.canvas.unsetMapTool(self.tool) - elif self.QadCommands.actualCommand is not None: - if self.canvas.mapTool() == self.QadCommands.actualCommand.getPointMapTool(): - self.canvas.unsetMapTool(self.QadCommands.actualCommand.getPointMapTool()) - - self.tool.removeItems() - del self.tool - - - def onProjectLoaded(self): - self.initOnProjectLoaded() - self.showTextWindow(QadVariables.get(QadMsg.translate("Environment variables", "SHOWTEXTWINDOW"), True)) - self.setStandardMapTool() - - - #============================================================================ - # INIZIO - Gestione ACTION (da chiamare prima di creare MENU e TOOLBAR) - #============================================================================ - def initActions(self): - # Creo le azioni e le collego ai comandi - - self.mainAction = QAction(QIcon(":/plugins/qad/icons/qad.png"), \ - QadMsg.translate("QAD", "QAD"), self.iface.mainWindow()) - self.mainAction.setCheckable(True) - QObject.connect(self.mainAction, SIGNAL("triggered()"), self.run) - - # PLINE - cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "PLINE")) - self.pline_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) - self.pline_action.setToolTip(cmd.getToolTipText()) - cmd.connectQAction(self.pline_action) - - # SETCURRLAYERBYGRAPH - cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "SETCURRLAYERBYGRAPH")) - self.setCurrLayerByGraph_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) - self.setCurrLayerByGraph_action.setToolTip(cmd.getToolTipText()) - cmd.connectQAction(self.setCurrLayerByGraph_action) - # SETCURRUPDATEABLELAYERBYGRAPH - cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "SETCURRUPDATEABLELAYERBYGRAPH")) - self.setCurrUpdateableLayerByGraph_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) - self.setCurrUpdateableLayerByGraph_action.setToolTip(cmd.getToolTipText()) - cmd.connectQAction(self.setCurrUpdateableLayerByGraph_action) - - # ARC - cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "ARC")) - self.arc_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) - self.arc_action.setToolTip(cmd.getToolTipText()) - cmd.connectQAction(self.arc_action) - # ARC BY 3 POINTS (MACRO) - self.arcBy3Points_action = QAction(QIcon(":/plugins/qad/icons/arcBy3Points.png"), \ - QadMsg.translate("Command_ARC", "Arc passing through 3 points"), \ - self.iface.mainWindow()) - QObject.connect(self.arcBy3Points_action, SIGNAL("triggered()"), self.runARCBY3POINTSCommand) - # ARC BY START CENTER END POINTS (MACRO) - self.arcByStartCenterEndPoints_action = QAction(QIcon(":/plugins/qad/icons/arcByStartCenterEndPoints.png"), \ - QadMsg.translate("Command_ARC", "Arc defined by start, central and final points"), \ - self.iface.mainWindow()) - QObject.connect(self.arcByStartCenterEndPoints_action, SIGNAL("triggered()"), self.runARC_BY_START_CENTER_END_Command) - # ARC BY START CENTER ANGLE (MACRO) - self.arcByStartCenterAngle_action = QAction(QIcon(":/plugins/qad/icons/arcByStartCenterAngle.png"), \ - QadMsg.translate("Command_ARC", "Arc defined by start, central points and angle"), \ - self.iface.mainWindow()) - QObject.connect(self.arcByStartCenterAngle_action, SIGNAL("triggered()"), self.runARC_BY_START_CENTER_ANGLE_Command) - # ARC BY START CENTER LENGTH (MACRO) - self.arcByStartCenterLength_action = QAction(QIcon(":/plugins/qad/icons/arcByStartCenterLength.png"), \ - QadMsg.translate("Command_ARC", "Arc defined by start, central points and cord length"), \ - self.iface.mainWindow()) - QObject.connect(self.arcByStartCenterLength_action, SIGNAL("triggered()"), self.runARC_BY_START_CENTER_LENGTH_Command) - # ARC BY START END ANGLE (MACRO) - self.arcByStartEndAngle_action = QAction(QIcon(":/plugins/qad/icons/arcByStartEndAngle.png"), \ - QadMsg.translate("Command_ARC", "Arc defined by start, final points and angle"), \ - self.iface.mainWindow()) - QObject.connect(self.arcByStartEndAngle_action, SIGNAL("triggered()"), self.runARC_BY_START_END_ANGLE_Command) - # ARC BY START END TAN (MACRO) - self.arcByStartEndTan_action = QAction(QIcon(":/plugins/qad/icons/arcByStartEndTan.png"), \ - QadMsg.translate("Command_ARC", "Arc defined by start, final points and tangent"), \ - self.iface.mainWindow()) - QObject.connect(self.arcByStartEndTan_action, SIGNAL("triggered()"), self.runARC_BY_START_END_TAN_Command) - # ARC BY START END RADIUS (MACRO) - self.arcByStartEndRadius_action = QAction(QIcon(":/plugins/qad/icons/arcByStartEndRadius.png"), \ - QadMsg.translate("Command_ARC", "Arc defined by start, final points and radius"), \ - self.iface.mainWindow()) - QObject.connect(self.arcByStartEndRadius_action, SIGNAL("triggered()"), self.runARC_BY_START_END_RADIUS_Command) - # ARC BY CENTER START END (MACRO) - self.arcByCenterStartEnd_action = QAction(QIcon(":/plugins/qad/icons/arcByCenterStartEnd.png"), \ - QadMsg.translate("Command_ARC", "Arc defined by central, start and final points"), \ - self.iface.mainWindow()) - QObject.connect(self.arcByCenterStartEnd_action, SIGNAL("triggered()"), self.runARC_BY_CENTER_START_END_Command) - # ARC BY CENTER START ANGLE (MACRO) - self.arcByCenterStartAngle_action = QAction(QIcon(":/plugins/qad/icons/arcByCenterStartAngle.png"), \ - QadMsg.translate("Command_ARC", "Arc defined by central, start points and angle"), \ - self.iface.mainWindow()) - QObject.connect(self.arcByCenterStartAngle_action, SIGNAL("triggered()"), self.runARC_BY_CENTER_START_ANGLE_Command) - # ARC BY CENTER START LENGTH (MACRO) - self.arcByCenterStartLength_action = QAction(QIcon(":/plugins/qad/icons/arcByCenterStartLength.png"), \ - QadMsg.translate("Command_ARC", "Arc defined by central, start points and cord length"), \ - self.iface.mainWindow()) - QObject.connect(self.arcByCenterStartLength_action, SIGNAL("triggered()"), self.runARC_BY_CENTER_START_LENGTH_Command) - - # CIRCLE - cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "CIRCLE")) - self.circle_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) - self.circle_action.setToolTip(cmd.getToolTipText()) - cmd.connectQAction(self.circle_action) - # CIRCLE BY CENTER RADIUS (MACRO) - self.circleByCenterRadius_action = QAction(QIcon(":/plugins/qad/icons/circleByCenterRadius.png"), \ - QadMsg.translate("Command_CIRCLE", "Circle defined by central point and radius"), \ - self.iface.mainWindow()) - QObject.connect(self.circleByCenterRadius_action, SIGNAL("triggered()"), self.runCIRCLE_BY_CENTER_RADIUS_Command) - # CIRCLE BY CENTER DIAMETER (MACRO) - self.circleByCenterDiameter_action = QAction(QIcon(":/plugins/qad/icons/circleByCenterDiameter.png"), \ - QadMsg.translate("Command_CIRCLE", "Circle defined by central point and diameter"), \ - self.iface.mainWindow()) - QObject.connect(self.circleByCenterDiameter_action, SIGNAL("triggered()"), self.runCIRCLE_BY_CENTER_DIAMETER_Command) - # CIRCLE BY 2 POINTS (MACRO) - self.circleBy2Points_action = QAction(QIcon(":/plugins/qad/icons/circleBy2Points.png"), \ - QadMsg.translate("Command_CIRCLE", "Circle defined by 2 points"), \ - self.iface.mainWindow()) - QObject.connect(self.circleBy2Points_action, SIGNAL("triggered()"), self.runCIRCLE_BY_2POINTS_Command) - # CIRCLE BY 3 POINTS (MACRO) - self.circleBy3Points_action = QAction(QIcon(":/plugins/qad/icons/circleBy3Points.png"), \ - QadMsg.translate("Command_CIRCLE", "Circle defined by 3 points"), \ - self.iface.mainWindow()) - QObject.connect(self.circleBy3Points_action, SIGNAL("triggered()"), self.runCIRCLE_BY_3POINTS_Command) - # CIRCLE BY TANGEN TANGENT RADIUS (MACRO) - self.circleBy2TansRadius_action = QAction(QIcon(":/plugins/qad/icons/circleBy2TansRadius.png"), \ - QadMsg.translate("Command_CIRCLE", "Circle defined by 2 tangent points and radius"), \ - self.iface.mainWindow()) - QObject.connect(self.circleBy2TansRadius_action, SIGNAL("triggered()"), self.runCIRCLE_BY_2TANS_RADIUS_Command) - # CIRCLE BY TANGEN TANGENT TANGENT (MACRO) - self.circleBy3Tans_action = QAction(QIcon(":/plugins/qad/icons/circleBy3Tans.png"), \ - QadMsg.translate("Command_CIRCLE", "Circle defined by 3 tangent points"), \ - self.iface.mainWindow()) - QObject.connect(self.circleBy3Tans_action, SIGNAL("triggered()"), self.runCIRCLE_BY_3TANS_Command) - - # DSETTINGS - cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "DSETTINGS")) - self.dsettings_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) - self.dsettings_action.setToolTip(cmd.getToolTipText()) - cmd.connectQAction(self.dsettings_action) - - # LINE - cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "LINE")) - self.line_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) - self.line_action.setToolTip(cmd.getToolTipText()) - cmd.connectQAction(self.line_action) - - # ERASE - cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "ERASE")) - self.erase_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) - self.erase_action.setToolTip(cmd.getToolTipText()) - cmd.connectQAction(self.erase_action) - - # MPOLYGON - cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "MPOLYGON")) - self.mpolygon_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) - self.mpolygon_action.setToolTip(cmd.getToolTipText()) - cmd.connectQAction(self.mpolygon_action) - - # MBUFFER - cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "MBUFFER")) - self.mbuffer_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) - self.mbuffer_action.setToolTip(cmd.getToolTipText()) - cmd.connectQAction(self.mbuffer_action) - - # ROTATE - cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "ROTATE")) - self.rotate_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) - self.rotate_action.setToolTip(cmd.getToolTipText()) - cmd.connectQAction(self.rotate_action) - - # MOVE - cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "MOVE")) - self.move_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) - self.move_action.setToolTip(cmd.getToolTipText()) - cmd.connectQAction(self.move_action) - - # SCALE - cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "SCALE")) - self.scale_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) - self.scale_action.setToolTip(cmd.getToolTipText()) - cmd.connectQAction(self.scale_action) - - # COPY - cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "COPY")) - self.copy_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) - self.copy_action.setToolTip(cmd.getToolTipText()) - cmd.connectQAction(self.copy_action) - - # OFFSET - cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "OFFSET")) - self.offset_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) - self.offset_action.setToolTip(cmd.getToolTipText()) - cmd.connectQAction(self.offset_action) - - # EXTEND - cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "EXTEND")) - self.extend_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) - self.extend_action.setToolTip(cmd.getToolTipText()) - cmd.connectQAction(self.extend_action) - - # TRIM - cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "TRIM")) - self.trim_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) - self.trim_action.setToolTip(cmd.getToolTipText()) - cmd.connectQAction(self.trim_action) - - # RECTANGLE - cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "RECTANGLE")) - self.rectangle_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) - self.rectangle_action.setToolTip(cmd.getToolTipText()) - cmd.connectQAction(self.rectangle_action) - - # POLYGON - cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "POLYGON")) - self.polygon_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) - self.polygon_action.setToolTip(cmd.getToolTipText()) - cmd.connectQAction(self.polygon_action) - - # MIRROR - cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "MIRROR")) - self.mirror_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) - self.mirror_action.setToolTip(cmd.getToolTipText()) - cmd.connectQAction(self.mirror_action) - - # UNDO - cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "UNDO")) - self.undo_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) - self.undo_action.setToolTip(cmd.getToolTipText()) - cmd.connectQAction(self.undo_action) - # UNDO OF ONLY ONE OPERATION (MACRO) - self.u_action = QAction(QIcon(":/plugins/qad/icons/u.png"), \ - QadMsg.translate("Command_UNDO", "Undo last operation"), \ - self.iface.mainWindow()) - QObject.connect(self.u_action, SIGNAL("triggered()"), self.runU_Command) - - # REDO - cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "REDO")) - self.redo_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) - self.redo_action.setToolTip(cmd.getToolTipText()) - cmd.connectQAction(self.redo_action) - - # INSERT - cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "INSERT")) - self.insert_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) - self.insert_action.setToolTip(cmd.getToolTipText()) - cmd.connectQAction(self.insert_action) - - # TEXT - cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "TEXT")) - self.text_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) - self.text_action.setToolTip(cmd.getToolTipText()) - cmd.connectQAction(self.text_action) - - # STRETCH - cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "STRETCH")) - self.stretch_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) - self.stretch_action.setToolTip(cmd.getToolTipText()) - cmd.connectQAction(self.stretch_action) - - # LENGTHEN - cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "LENGTHEN")) - self.lengthen_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) - self.lengthen_action.setToolTip(cmd.getToolTipText()) - cmd.connectQAction(self.lengthen_action) - - # BREAK - cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "BREAK")) - self.break_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) - self.break_action.setToolTip(cmd.getToolTipText()) - cmd.connectQAction(self.break_action) - - # PEDIT - cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "PEDIT")) - self.pedit_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) - self.pedit_action.setToolTip(cmd.getToolTipText()) - cmd.connectQAction(self.pedit_action) - - # FILLET - cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "FILLET")) - self.fillet_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) - self.fillet_action.setToolTip(cmd.getToolTipText()) - cmd.connectQAction(self.fillet_action) - - # DIMLINEAR - cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "DIMLINEAR")) - self.dimLinear_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) - self.dimLinear_action.setToolTip(cmd.getToolTipText()) - cmd.connectQAction(self.dimLinear_action) - # DIMALIGNED - cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "DIMALIGNED")) - self.dimAligned_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) - self.dimAligned_action.setToolTip(cmd.getToolTipText()) - cmd.connectQAction(self.dimAligned_action) - # DIMSTYLE - cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "DIMSTYLE")) - self.dimStyle_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) - self.dimStyle_action.setToolTip(cmd.getToolTipText()) - cmd.connectQAction(self.dimStyle_action) - - # HELP - cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "HELP")) - self.help_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) - self.help_action.setToolTip(cmd.getToolTipText()) - cmd.connectQAction(self.help_action) - - - #============================================================================ - # FINE - Gestione ACTION - #============================================================================ - - - def UpdatedVariablesEvent(self): - # aggiorna in base alle nuove impostazioni delle variabili - self.tool.UpdatedVariablesEvent() - - - #============================================================================ - # INIZIO - Gestione MENU (da chiamare prima di creare TOOLBAR) - #============================================================================ - def createArcMenu(self): - # menu Draw - arcMenu = QMenu(QadMsg.translate("Command_list", "ARC")) - cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "ARC")) - arcMenu.setIcon(cmd.getIcon()) - arcMenu.addAction(self.arcBy3Points_action) - arcMenu.addSeparator() - arcMenu.addAction(self.arcByStartCenterEndPoints_action) - arcMenu.addAction(self.arcByStartCenterAngle_action) - arcMenu.addAction(self.arcByStartCenterLength_action) - arcMenu.addSeparator() - arcMenu.addAction(self.arcByStartEndAngle_action) - arcMenu.addAction(self.arcByStartEndTan_action) - arcMenu.addAction(self.arcByStartEndRadius_action) - arcMenu.addSeparator() - arcMenu.addAction(self.arcByCenterStartEnd_action) - arcMenu.addAction(self.arcByCenterStartAngle_action) - arcMenu.addAction(self.arcByCenterStartLength_action) - return arcMenu - - def createCircleMenu(self): - # menu Draw - circleMenu = QMenu(QadMsg.translate("Command_list", "CIRCLE")) - cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "CIRCLE")) - circleMenu.setIcon(cmd.getIcon()) - circleMenu.addAction(self.circleByCenterRadius_action) - circleMenu.addAction(self.circleByCenterDiameter_action) - circleMenu.addSeparator() - circleMenu.addAction(self.circleBy2Points_action) - circleMenu.addAction(self.circleBy3Points_action) - circleMenu.addSeparator() - circleMenu.addAction(self.circleBy2TansRadius_action) - circleMenu.addAction(self.circleBy3Tans_action) - return circleMenu - - def createDrawMenu(self): - # menu Draw - drawMenu = QMenu(QadMsg.translate("QAD", "Draw")) - drawMenu.addAction(self.line_action) - drawMenu.addAction(self.pline_action) - - # menu arco - self.arcMenu = self.createArcMenu() - drawMenu.addMenu(self.arcMenu) - # menu cerchio - self.circleMenu = self.createCircleMenu() - drawMenu.addMenu(self.circleMenu) - - drawMenu.addAction(self.rectangle_action) - drawMenu.addAction(self.polygon_action) - drawMenu.addAction(self.mpolygon_action) - drawMenu.addAction(self.mbuffer_action) - drawMenu.addSeparator() - drawMenu.addAction(self.insert_action) - drawMenu.addAction(self.text_action) - return drawMenu - - def createEditMenu(self): - # menu Edit - editMenu = QMenu(QadMsg.translate("QAD", "Edit")) - editMenu.addAction(self.erase_action) - editMenu.addAction(self.rotate_action) - editMenu.addAction(self.move_action) - editMenu.addAction(self.scale_action) - editMenu.addAction(self.copy_action) - editMenu.addAction(self.offset_action) - editMenu.addAction(self.extend_action) - editMenu.addAction(self.trim_action) - editMenu.addAction(self.mirror_action) - editMenu.addAction(self.stretch_action) - editMenu.addAction(self.lengthen_action) - editMenu.addAction(self.break_action) - editMenu.addAction(self.pedit_action) - editMenu.addAction(self.fillet_action) - return editMenu - - def createToolsMenu(self): - # menu Tools - toolsMenu = QMenu(QadMsg.translate("QAD", "Tools")) - toolsMenu.addAction(self.setCurrLayerByGraph_action) - toolsMenu.addAction(self.setCurrUpdateableLayerByGraph_action) - toolsMenu.addAction(self.dsettings_action) - return toolsMenu - - def createDimMenu(self): - # menu Dim - dimMenu = QMenu(QadMsg.translate("QAD", "Dimensioning")) - dimMenu.addAction(self.dimLinear_action) - dimMenu.addAction(self.dimAligned_action) - dimMenu.addAction(self.dimStyle_action) - return dimMenu - - #============================================================================ - # FINE - Gestione MENU - #============================================================================ - - - #============================================================================ - # INIZIO - Gestione TOOLBAR - #============================================================================ - def createArcToolButton(self): - arcToolButton = QToolButton(self.toolBar) - arcToolButton.setPopupMode(QToolButton.MenuButtonPopup) - arcToolButton.setMenu(self.arcMenu) - arcToolButton.setDefaultAction(self.arcMenu.actions()[0]) # prima voce di menu - arcToolButton.triggered.connect(self.arcToolButtonTriggered) - return arcToolButton - def arcToolButtonTriggered(self, action): - self.arcToolButton.setDefaultAction(action) - - def createCircleToolButton(self): - circleToolButton = QToolButton(self.toolBar) - circleToolButton.setPopupMode(QToolButton.MenuButtonPopup) - circleToolButton.setMenu(self.circleMenu) - circleToolButton.setDefaultAction(self.circleMenu.actions()[0]) # prima voce di menu - circleToolButton.triggered.connect(self.circleToolButtonTriggered) - return circleToolButton - def circleToolButtonTriggered(self, action): - self.circleToolButton.setDefaultAction(action) - - def createDimToolBar(self): - # aggiunge la toolbar per la quotatura - toolBar = self.iface.addToolBar(QadMsg.translate("QAD", "QAD - Dimensioning")) - toolBar.setObjectName(QadMsg.translate("QAD", "QAD - Dimensioning")) - toolBar.addAction(self.dimLinear_action) - toolBar.addAction(self.dimAligned_action) - toolBar.addAction(self.dimStyle_action) - return toolBar - - - #============================================================================ - # FINE - Gestione TOOLBAR - #============================================================================ - - - #============================================================================ - # INIZIO - Gestione Layer - #============================================================================ - - # se viene salvato per prima un layer di quote testuale: - # 1) beforeCommitChanges su layer testuale - # 2) committedFeaturesAdded su layer testuale - # 3) editingStopped su layer testuale che scatena - # 1) committedFeaturesAdded su layer linee - # 2) committedFeaturesAdded su layer simboli - - # se viene salvato per prima un layer di quote simbolo o linea: - # 1) beforeCommitChanges su layer simbolo o linea - # 2) committedFeaturesAdded su layer testuale - # 3) editingStopped su layer testuale che scatena - # 1) committedFeaturesAdded su layer linea - # 2) committedFeaturesAdded su layer simboli - - def layerAdded(self, layer): - QObject.connect(layer, SIGNAL("layerModified()"), self.layerModified) - QObject.connect(layer, SIGNAL("beforeCommitChanges()"), self.beforeCommitChanges) - QObject.connect(layer, SIGNAL("committedFeaturesAdded(QString, QgsFeatureList)"), self.committedFeaturesAdded) - # vedi qgsvectorlayer.cpp funzione QgsVectorLayer::commitChanges() - # devo predere l'ultimo segnale vhe viene emesso dal salvataggio QGIS - # questo segnale arriva alla fine del salvataggio di un layer dalla versione 2.3 di QGIS - #QObject.connect(layer, SIGNAL("repaintRequested()"), self.repaintRequested) - # questo segnale arriva alla fine del salvataggio di un layer alla versione 2.2 di QGIS - QObject.connect(layer, SIGNAL("editingStopped()"), self.editingStopped) - - - def removeLayer(self, layerId): - # viene rimosso un layer quindi lo tolgo dallo stack - self.undoStack.clearByLayer(layerId) - self.enableUndoRedoButtons() - - - def editingStopped(self): - # questo segnale arriva alla fine del salvataggio di un layer alla versione 2.2 di QGIS - # se bisogna fare la ricodifica delle quote - if self.dimTextEntitySetRecodeOnSave.isEmpty() == False: - layer = self.dimTextEntitySetRecodeOnSave.layer - # ricavo gli stili di quotatura - dimStyleList = self.mQadDimStyle.getDimListByLayer(layer) - for dimStyle in dimStyleList: - if dimStyle.getInValidErrMsg() is None: # stile valido - # cerco tutte le feature in self.dimTextEntitySetRecodeOnSave che appartengono allo stile - # di quotatura dimStyle - textAddedFeatures = dimStyle.getFilteredFeatureCollection(self.dimTextEntitySetRecodeOnSave) - # salvo gli oggetti di quello stile di quotatura aggiornando i reference - self.isSaveControlledByQAD = True - # ricodifica - dimStyle.updateTextReferencesOnSave(self, textAddedFeatures) - - self.dimTextEntitySetRecodeOnSave.clear() - - for dimStyle in dimStyleList: - if dimStyle.getInValidErrMsg() is None: # stile valido - # salvataggio - dimStyle.commitChanges(self.beforeCommitChangesDimLayer) - self.beforeCommitChangesDimLayer = None - self.isSaveControlledByQAD = False - dimStyle.startEditing() - elif self.isSaveControlledByQAD == False: - layer = self.sender() - # verifico se il layer che si è appena finito di salvare appartiene ad uno o più stili di quotatura - dimStyleList = self.mQadDimStyle.getDimListByLayer(layer) - for dimStyle in dimStyleList: - if dimStyle.getInValidErrMsg() is None: # stile valido - # salvataggio - self.isSaveControlledByQAD = True - dimStyle.commitChanges(self.beforeCommitChangesDimLayer) - self.beforeCommitChangesDimLayer = None - self.isSaveControlledByQAD = False - dimStyle.startEditing() - - - def repaintRequested(self): - # questo segnale arriva alla fine del salvataggio di un layer dalla versione 2.3 di QGIS - # se bisogna fare la ricodifica delle quote - if self.dimTextEntitySetRecodeOnSave.isEmpty() == False: - # ricavo gli stili di quotatura - dimStyleList = self.mQadDimStyle.getDimListByLayer(self.dimTextEntitySetRecodeOnSave.layer) - for dimStyle in dimStyleList: - if dimStyle.getInValidErrMsg() is None: # stile valido - # cerco tutte le feature in self.dimTextEntitySetRecodeOnSave che appartengono allo stile - # di quotatura dimStyle - textAddedFeatures = dimStyle.getFilteredFeatureCollection(self.dimTextEntitySetRecodeOnSave) - # salvo gli oggetti di quello stile di quotatura aggiornando i reference - self.isSaveControlledByQAD = True - # ricodifica - dimStyle.updateTextReferencesOnSave(self, textAddedFeatures) - - self.dimTextEntitySetRecodeOnSave.clear() - - for dimStyle in dimStyleList: - if dimStyle.getInValidErrMsg() is None: # stile valido - # salvataggio - dimStyle.commitChanges(self.beforeCommitChangesDimLayer) - self.beforeCommitChangesDimLayer = None - self.isSaveControlledByQAD = False - dimStyle.startEditing() - - - def beforeCommitChanges(self): - if self.isSaveControlledByQAD == False: - layer = self.sender() - # verifico se il layer che si sta per salvare appartiene ad uno o più stili di quotatura - dimStyleList = self.mQadDimStyle.getDimListByLayer(layer) - for dimStyle in dimStyleList: - if dimStyle.getInValidErrMsg() is None: # stile valido - if dimStyle.getTextualLayer().id() != layer.id(): # se non si tratta del layer dei testi di quota - self.beforeCommitChangesDimLayer = layer # memorizzo il layer da cui é scaturito il salvataggio delle quotature - self.isSaveControlledByQAD = True - dimStyle.textCommitChangesOnSave() # salvo i testi delle quote per ricodifica ID - dimStyle.startEditing() - self.isSaveControlledByQAD = False - - - def committedFeaturesAdded(self, layerId, addedFeatures): - layer = qad_layer.getLayerById(layerId) - # verifico se il layer che é stato salvato appartiene ad uno o più stili di quotatura - dimStyleList = self.mQadDimStyle.getDimListByLayer(layer) - for dimStyle in dimStyleList: - if dimStyle.getInValidErrMsg() is None: # stile valido - # se si tratta del layer testuale delle quote - if dimStyle.getTextualLayer().id() == layerId: - # mi memorizzo le features testuali da riallineare - self.dimTextEntitySetRecodeOnSave.set(dimStyle.getTextualLayer(), addedFeatures) - return - - - def layerModified(self): - if self.isQadActive == False: - # la modifica fatta su quel layer é stata fatta dall'esterno di QAD - # quindi ho perso la sincronizzazione con lo stack di undo di QAD che - # viene svuotato perché ormai inutilizzabile - self.undoStack.clear() - self.enableUndoRedoButtons() - - - #============================================================================ - # INIZIO - Gestione UNDO e REDO - #============================================================================ - - - def enableUndoRedoButtons(self): - self.undo_action.setEnabled(self.undoStack.isUndoAble()) - self.u_action.setEnabled(self.undoStack.isUndoAble()) - self.redo_action.setEnabled(self.undoStack.isRedoAble()) - - def beginEditCommand(self, text, layerList): - if type(layerList) == list or type(layerList) == tuple: - # layerList é una lista di layer - self.undoStack.beginEditCommand(text, layerList) - else: - # layerList é un solo layer - self.undoStack.beginEditCommand(text, [layerList]) - - - def destroyEditCommand(self): - # pulisco le entità selezionate e i grip points correnti - self.tool.clearEntitySet() - self.tool.clearEntityGripPoints() - - self.isQadActive = True - self.undoStack.destroyEditCommand() - self.isQadActive = False - self.enableUndoRedoButtons() - - - def endEditCommand(self): - self.isQadActive = True - self.undoStack.endEditCommand(self.canvas) - self.isQadActive = False - self.enableUndoRedoButtons() - - def undoEditCommand(self, nTimes = 1): - # pulisco le entità selezionate e i grip points correnti - self.tool.clearEntitySet() - self.tool.clearEntityGripPoints() - - self.isQadActive = True - self.undoStack.undoEditCommand(self.canvas, nTimes) - self.isQadActive = False - self.enableUndoRedoButtons() - - - def redoEditCommand(self, nTimes = 1): - # pulisco le entità selezionate e i grip points correnti - self.tool.clearEntitySet() - self.tool.clearEntityGripPoints() - - self.isQadActive = True - self.undoStack.redoEditCommand(self.canvas, nTimes) - self.isQadActive = False - self.enableUndoRedoButtons() - - - def addLayerToLastEditCommand(self, text, layer): - self.undoStack.addLayerToLastEditCommand(text, layer) - - def insertBeginGroup(self, text = "Group"): - self.undoStack.insertBeginGroup(text) - - def insertEndGroup(self): - return self.undoStack.insertEndGroup() - - def insertBookmark(self, text = "Bookmark"): - return self.undoStack.insertBookmark(text) - - def getPrevBookmarkPos(self): - return self.undoStack.getPrevBookmarkPos(self.undoStack.index) - - def undoUntilBookmark(self): - # pulisco le entità selezionate e i grip points correnti - self.tool.clearEntitySet() - self.tool.clearEntityGripPoints() - - self.isQadActive = True - self.undoStack.undoUntilBookmark(self.canvas) - self.isQadActive = False - self.enableUndoRedoButtons() - - - #============================================================================ - # FINE - Gestione UNDO e REDO - #============================================================================ - - def run(self): - self.setStandardMapTool() - self.showTextWindow() - - def deactivate(self): - self.mainAction.setChecked(False) - - def setStandardMapTool(self): - mc = self.canvas - mc.setMapTool(self.tool) - self.mainAction.setChecked(True) - - def keyPressEvent(self, event): - self.TextWindow.keyPressEvent(event) - pass - - - #============================================================================ - # INIZIO - funzioni per visualizzare messaggi nella finestra di testo - #============================================================================ - def showTextWindow(self, mode = True): - if mode == True: - if self.__alreadyDockedTextWindow == False: - self.iface.addDockWidget(Qt.BottomDockWidgetArea, self.TextWindow) - self.__alreadyDockedTextWindow = True - - self.TextWindow.setVisible(mode) - if mode == True: - self.TextWindow.setFocus() - - def showMsg(self, msg, displayPromptAfterMsg = False): - self.TextWindow.showMsg(msg, displayPromptAfterMsg) - - def showErr(self, err): - self.TextWindow.showErr(err) - - def showInputMsg(self, inputMsg = None, inputType = QadInputTypeEnum.COMMAND, \ - default = None, keyWords = "", inputMode = QadInputModeEnum.NONE): - # il valore di default del parametro di una funzione non può essere una traduzione - # perché lupdate.exe non lo riesce ad interpretare - if inputMsg is None: - inputMsg = QadMsg.translate("QAD", "Command: ") - - self.TextWindow.showInputMsg(inputMsg, inputType, default, keyWords, inputMode) - - - #============================================================================ - # INIZIO - funzioni per comandi - #============================================================================ - - def clearCurrentObjsSelection(self): - # pulisco le entità selezionate e i grip points correnti - self.tool.clearEntitySet() - self.clearEntityGripPoints() - - def clearEntityGripPoints(self): - # pulisco i grip points correnti - self.tool.clearEntityGripPoints() - - def runCommand(self, command, param = None): - self.QadCommands.run(command, param) - - def runMacro(self, args): - self.QadCommands.runMacro(args) - - def continueCommandFromMapTool(self): - self.QadCommands.continueCommandFromMapTool() - - def continueCommandFromTextWindow(self, msg): - self.QadCommands.continueCommandFromTextWindow(msg) - - def abortCommand(self): - self.QadCommands.abortCommand() - - def isValidCommand(self, command): - return self.QadCommands.isValidCommand(command) - - def getCommandNames(self): - return self.QadCommands.getCommandNames() - - def getCommandObj(self, cmdName): - return self.QadCommands.getCommandObj(cmdName) - - def getMoreUsedCmd(self, filter): - return self.QadCommands.getMoreUsedCmd(filter) - - def isValidEnvVariable(self, variable): - return self.QadCommands.isValidEnvVariable(variable) - - def forceCommandMapToolSnapTypeOnce(self, snapType, snapParams = None): - self.QadCommands.forceCommandMapToolSnapTypeOnce(snapType, snapParams) - - def refreshCommandMapToolSnapType(self): - self.QadCommands.refreshCommandMapToolSnapType() - - def getCurrenPointFromCommandMapTool(self): - return self.QadCommands.getCurrenPointFromCommandMapTool() - - def toggleOsMode(self): - value = QadVariables.get(QadMsg.translate("Environment variables", "OSMODE")) - if value & QadSnapTypeEnum.DISABLE: - value = value - QadSnapTypeEnum.DISABLE - msg = QadMsg.translate("QAD", "") - else: - value = value + QadSnapTypeEnum.DISABLE - msg = QadMsg.translate("QAD", "") - - QadVariables.set(QadMsg.translate("Environment variables", "OSMODE"), value) - QadVariables.save() - self.showMsg(msg, True) - self.QadCommands.refreshCommandMapToolSnapType() - - def toggleOrthoMode(self): - value = QadVariables.get(QadMsg.translate("Environment variables", "ORTHOMODE")) - if value == 0: - value = 1 - autosnap = QadVariables.get(QadMsg.translate("Environment variables", "AUTOSNAP")) - if (autosnap & 8) == True: - QadVariables.set(QadMsg.translate("Environment variables", "AUTOSNAP"), autosnap - 8) # disattivo la modalità polare - msg = QadMsg.translate("QAD", "") - else: - value = 0 - msg = QadMsg.translate("QAD", "") - - QadVariables.set(QadMsg.translate("Environment variables", "ORTHOMODE"), value) - QadVariables.save() - self.showMsg(msg, True) - self.QadCommands.refreshCommandMapToolOrthoMode() - - def togglePolarMode(self): - value = QadVariables.get(QadMsg.translate("Environment variables", "AUTOSNAP")) - if (value & 8) == False: - value = value + 8 - QadVariables.set(QadMsg.translate("Environment variables", "ORTHOMODE"), 0) # disattivo la modalità orto - msg = QadMsg.translate("QAD", "") - else: - value = value - 8 - msg = QadMsg.translate("QAD", "") - - QadVariables.set(QadMsg.translate("Environment variables", "AUTOSNAP"), value) - QadVariables.save() - self.showMsg(msg, True) - self.QadCommands.refreshCommandMapToolAutoSnap() - - def getCurrMsgFromTxtWindow(self): - return self.TextWindow.getCurrMsg() - - def getHistoryfromTxtWindow(self): - return self.TextWindow.getHistory() # list - - def updateHistoryfromTxtWindow(self, command): - return self.TextWindow.updateHistory(command) - - def showEvaluateMsg(self, msg = None): - self.TextWindow.showEvaluateMsg(msg) - - #============================================================================ - # funzioni per l'avvio di un comando - #============================================================================ - def runCommandAbortingTheCurrent(self, cmdName): - self.mainAction.setChecked(True) - self.canvas.setFocus() - self.abortCommand() - self.showEvaluateMsg(cmdName) - - def runMacroAbortingTheCurrent(self, args): - self.mainAction.setChecked(True) - self.canvas.setFocus() - self.abortCommand() - self.runMacro(args) - - def runIDCommand(self): - self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "ID")) - - def runSETVARCommand(self): - self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "SETVAR")) - - def runPLINECommand(self): - self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "PLINE")) - - def runSETCURRLAYERBYGRAPHCommand(self): - self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "SETCURRLAYERBYGRAPH")) - - def runSETCURRUPDATEABLELAYERBYGRAPHCommand(self): - self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "SETCURRUPDATEABLELAYERBYGRAPH")) - - def runARCCommand(self): - self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "ARC")) - def runARCBY3POINTSCommand(self): # MACRO - # nome comando + argomenti - args = [QadMsg.translate("Command_list", "ARC"), None, None, None] - self.runMacroAbortingTheCurrent(args) - def runARC_BY_START_CENTER_END_Command(self): # MACRO - # nome comando + argomenti - args = [QadMsg.translate("Command_list", "ARC"), \ - None, \ - QadMsg.translate("Command_ARC", "Center"), \ - None, - None] - self.runMacroAbortingTheCurrent(args) - def runARC_BY_START_CENTER_ANGLE_Command(self): # MACRO - # nome comando + argomenti - args = [QadMsg.translate("Command_list", "ARC"), \ - None, \ - QadMsg.translate("Command_ARC", "Center"), \ - None, \ - QadMsg.translate("Command_ARC", "Angle"), \ - None] - self.runMacroAbortingTheCurrent(args) - def runARC_BY_START_CENTER_LENGTH_Command(self): # MACRO - # nome comando + argomenti - args = [QadMsg.translate("Command_list", "ARC"), \ - None, \ - QadMsg.translate("Command_ARC", "Center"), \ - None, \ - QadMsg.translate("Command_ARC", "chord Length"), \ - None] - self.runMacroAbortingTheCurrent(args) - def runARC_BY_START_END_ANGLE_Command(self): # MACRO - # nome comando + argomenti - args = [QadMsg.translate("Command_list", "ARC"), \ - None, \ - QadMsg.translate("Command_ARC", "End"), \ - None, \ - QadMsg.translate("Command_ARC", "Angle"), \ - None] - self.runMacroAbortingTheCurrent(args) - def runARC_BY_START_END_TAN_Command(self): # MACRO - # nome comando + argomenti - args = [QadMsg.translate("Command_list", "ARC"), \ - None, \ - QadMsg.translate("Command_ARC", "End"), \ - None, \ - QadMsg.translate("Command_ARC", "Direction"), \ - None] - self.runMacroAbortingTheCurrent(args) - def runARC_BY_START_END_RADIUS_Command(self): # MACRO - # nome comando + argomenti - args = [QadMsg.translate("Command_list", "ARC"), \ - None, \ - QadMsg.translate("Command_ARC", "End"), \ - None, \ - QadMsg.translate("Command_ARC", "Radius"), \ - None] - self.runMacroAbortingTheCurrent(args) - def runARC_BY_CENTER_START_END_Command(self): # MACRO - # nome comando + argomenti - args = [QadMsg.translate("Command_list", "ARC"), \ - QadMsg.translate("Command_ARC", "Center"), \ - None, \ - None, \ - None] - self.runMacroAbortingTheCurrent(args) - def runARC_BY_CENTER_START_ANGLE_Command(self): # MACRO - # nome comando + argomenti - args = [QadMsg.translate("Command_list", "ARC"), \ - QadMsg.translate("Command_ARC", "Center"), \ - None, \ - None, \ - QadMsg.translate("Command_ARC", "Angle"), \ - None] - self.runMacroAbortingTheCurrent(args) - def runARC_BY_CENTER_START_LENGTH_Command(self): # MACRO - # nome comando + argomenti - args = [QadMsg.translate("Command_list", "ARC"), \ - QadMsg.translate("Command_ARC", "Center"), \ - None, \ - None, \ - QadMsg.translate("Command_ARC", "chord Length"), \ - None] - self.runMacroAbortingTheCurrent(args) - - def runCIRCLECommand(self): - self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "CIRCLE")) - def runCIRCLE_BY_CENTER_RADIUS_Command(self): # MACRO - # nome comando + argomenti - args = [QadMsg.translate("Command_list", "CIRCLE"), \ - None, \ - None] - self.runMacroAbortingTheCurrent(args) - def runCIRCLE_BY_CENTER_DIAMETER_Command(self): # MACRO - # nome comando + argomenti - args = [QadMsg.translate("Command_list", "CERCHIO"), \ - None, \ - QadMsg.translate("Command_CIRCLE", "Diameter"), \ - None] - self.runMacroAbortingTheCurrent(args) - def runCIRCLE_BY_2POINTS_Command(self): # MACRO - # nome comando + argomenti - args = [QadMsg.translate("Command_list", "CIRCLE"), \ - QadMsg.translate("Command_CIRCLE", "2POints"), \ - None, \ - None] - self.runMacroAbortingTheCurrent(args) - def runCIRCLE_BY_3POINTS_Command(self): # MACRO - # nome comando + argomenti - args = [QadMsg.translate("Command_list", "CIRCLE"), \ - QadMsg.translate("Command_CIRCLE", "3Points"), \ - None, \ - None, \ - None] - self.runMacroAbortingTheCurrent(args) - def runCIRCLE_BY_2TANS_RADIUS_Command(self): # MACRO - # nome comando + argomenti - args = [QadMsg.translate("Command_list", "CIRCLE"), \ - QadMsg.translate("Command_CIRCLE", "Ttr (tangent tangent radius)"), \ - None, \ - None, \ - None] - self.runMacroAbortingTheCurrent(args) - def runCIRCLE_BY_3TANS_Command(self): # MACRO - # nome comando + argomenti - args = [QadMsg.translate("Command_list", "CIRCLE"), \ - QadMsg.translate("Command_CIRCLE", "3Points"), \ - QadMsg.translate("Snap", "TAN"), \ - QadMsg.translate("Snap", "TAN"), \ - QadMsg.translate("Snap", "TAN")] - self.runMacroAbortingTheCurrent(args) - - def runDSETTINGSCommand(self): - self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "DSETTINGS")) - - def runLINECommand(self): - self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "LINE")) - - def runERASECommand(self): - self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "ERASE")) - - def runMPOLYGONCommand(self): - self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "MPOLYGON")) - - def runMBUFFERCommand(self): - self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "MBUFFER")) - - def runROTATECommand(self): - self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "ROTATE")) - - def runMOVECommand(self): - self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "MOVE")) - - def runSCALECommand(self): - self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "SCALE")) - - def runCOPYCommand(self): - self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "COPY")) - - def runOFFSETCommand(self): - self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "OFFSET")) - - def runEXTENDCommand(self): - self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "EXTEND")) - - def runTRIMCommand(self): - self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "TRIM")) - - def runRECTANGLECommand(self): - self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "RECTANGLE")) - - def runMIRRORCommand(self): - self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "MIRROR")) - - def runUNDOCommand(self): - self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "UNDO")) - def runU_Command(self): # MACRO - # nome comando + argomenti - args = [QadMsg.translate("Command_list", "UNDO"), \ - QadMsg.translate("Command_UNDO", "1")] - self.runMacroAbortingTheCurrent(args) - - def runREDOCommand(self): - self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "REDO")) - - def runINSERTCommand(self): - self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "INSERT")) - - def runTEXTCommand(self): - self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "TEXT")) - - def runSTRETCHCommand(self): - self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "STRETCH")) - - def runBREAKCommand(self): - self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "BREAK")) - - def runPEDITCommand(self): - self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "PEDIT")) - - def runFILLETCommand(self): - self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "FILLET")) - - def runPOLYGONCommand(self): - self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "POLYGON")) - - def runDIMLINEARCommand(self): - self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "DIMLINEAR")) - - def runDIMALIGNEDCommand(self): - self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "DIMALIGNED")) - - def runDIMSTYLECommand(self): - self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "DIMSTYLE")) - - def runHELPCommand(self): - self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "HELP")) - - def runLENGTHENCommand(self): - self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "LENGTHEN")) +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + comandi di editazione geometria stile CAD + + ------------------- + begin : 2014-11-03 + copyright : 2013-2016 + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries + +from qgis.PyQt.QtCore import Qt, QObject, QTranslator, qVersion, QCoreApplication, QSettings +from qgis.PyQt.QtGui import QIcon, QKeySequence +from qgis.PyQt.QtWidgets import QAction, QMenu, QToolButton, QShortcut, QMessageBox, QWIDGETSIZE_MAX +from qgis.core import QgsPointXY, QgsProject, QgsMapLayer, QgsSettings, QgsApplication +from qgis.gui import QgsGui +# Initialize Qt resources from file qad_rc.py +from .qad_rc import * + +import os +import math + + +from .qad_msg import QadMsg +from .qad_shortcuts import QadShortcuts +from .qad_utils import getAngleBy2Pts, normalizeAngle +from .qad_layer import getLayerById, QadLayerStatusEnum, QadLayerStatusListClass +from .qad_maptool import QadMapTool +from .qad_variables import QadVariables, QadAUTOSNAPEnum +from .qad_snapper import QadSnapTypeEnum +from .qad_textwindow import QadTextWindow, QadInputTypeEnum, QadInputModeEnum +from .qad_commands import QadCommandsClass, getMaxDailyCmdCounter +from .qad_entity import QadLayerEntitySet, QadEntity, QadEntitySet +from .qad_dim import QadDimStyles +from .qad_undoredo import QadUndoStack +from .cmd.qad_array_cmd import QadARRAYCommandClassSeriesTypeEnum + + +class Qad(QObject): + """ + Classe plug in di Qad + """ + + # UI + toolBar = None + dimToolBar = None + menu = None + translator = None + + # Map Tool attivo. Quando non ci sono comandi che necessitano di input dalla finestra grafica + # QadMapTool é quello attivo + tool = None + # Finestra grafica + canvas = None + # Finestra testuale + TextWindow = None + # Classe che gestisce i comandi + QadCommands = None + # Azione corrente + currentAction = None + + # ultimo punto selezionato + lastPoint = None + # coeff angolare ultimo segmento + lastSegmentAng = 0.0 + # ultima rotazione + lastRot = 0.0 + # ultima altezza testo + lastHText = 1.0 + # ultimo angolo di riferimento (es. comando ruota) + lastReferenceRot = 0.0 + # ultimo nuovo angolo di riferimento (es. comando ruota) + lastNewReferenceRot = 0.0 + # ultimo raggio + lastRadius = 1.0 + # ultimo punto di offset + lastOffsetPt = QgsPointXY(0, 0) + # ultima lunghezza di riferimento (es. comando scala) + lastReferenceLen = 1.0 + # ultima lunghezza di riferimento (es. comando scala) + lastNewReferenceLen = 1.0 + # ultimo fattore di scala (es. comando scala) + lastScale = 1.0 + # numero di segmenti per l'approssimazione delle curve (es. buffer) + segments = 10 + # ultima entità inserita + lastEntity = None + # ultimo set di entità + lastEntitySet = None + # tipo di unione (es. editpl->unione) + joinMode = 1 # 1=Estendi, 2=Aggiungi, 3=Entrambi + # distanza di approssimazione nell'unione (es. editpl->unione) + joinToleranceDist = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2COINCIDENT")) + # modalità di raccordo in comando raccordo + filletMode = 1 # 1=Taglia-estendi, 2=Non taglia-estendi + # ultimo numero di lati per poligono + lastPolygonSideNumber = 4 + # ultima opzione di costruzione del poligono conoscendo il centro + # "Inscritto nel cerchio", Circoscritto intorno al cerchio", "Area" + lastPolygonConstructionModeByCenter = QadMsg.translate("Command_POLYGON", "Inscribed in circle") + # ultimo delta usato nel comando lengthen + lastDelta_lengthen = 0.0 + # ultimo delta angolo usato nel comando lengthen + lastDeltaAngle_lengthen = 0.0 + # ultima percentuale usata nel comando lengthen + lastPerc_lengthen = 100.0 + # ultima lunghezza totale usato nel comando lengthen + lastTotal_lengthen = 1.0 + # ultimo angolo totale usato nel comando lengthen + lastTotalAngle_lengthen = 0.0 + # ultima modalità operativa del comando lengthen + lastOpMode_lengthen = "DElta" + + # INIZIO PARAMETRI COMANDO ARRRAY + # ultima tipo di serie del comando array + lastArrayType_array = QadARRAYCommandClassSeriesTypeEnum.RECTANGLE + # memorizzo se gli elementi vanno ruotati + lastItemsRotation_array = True + + # ultimo l'angolo di orientamento della serie di tipo rettangolo + lastRectangleAngle_array = 0.0 + # ultimo numero di colonne della serie di tipo rettangolo + lastRectangleCols_array = 4 + # ultimo numero di righe della serie di tipo rettangolo + lastRectangleRows_array = 3 + + # ultima direzione della tangente per la serie di tipo traiettoria + lastPathTangentDirection_array = 0.0 + # ultimo numero di righe della serie di tipo traiettoria + lastPathRows_array = 1 + + # ultimo numero di elementi della serie di tipo polare + lastPolarItemsNumber_array = 6 + # ultimo angolo tra gli elementi della serie di tipo polare + lastPolarAngleBetween_array = math.pi / 3 # 60 gradi (60 * 6 = 360 gradi) + # ultimo numero di righe della serie di tipo polare + lastPolarRows_array = 1 + # FINE PARAMETRI COMANDO ARRRAY + + # flag per identificare se un comando di QAD é attivo oppure no + isQadActive = False + + # Quotatura + dimTextEntitySetRecodeOnSave = QadLayerEntitySet() # entity set dei testi delle quote da riallineare in salvataggio + beforeCommitChangesDimLayer = None # layer da cui é scaturito il salvataggio delle quotature + layerStatusList = QadLayerStatusListClass() + + # comando dsettings - ultimo tab utilizzato + dsettingsLastUsedTabIndex = -1 # -1 = non inizializzato + + # comando options - ultimo tab utilizzato + optionsLastUsedTabIndex = -1 # -1 = non inizializzato + + # comando dimstyle - ultimo tab utilizzato + dimStyleLastUsedTabIndex = -1 # -1 = non inizializzato + + cmdsHistory = [] # lista della storia degli ultimi comandi usati + ptsHistory = [] # lista della storia degli ultimi punti usati + + shortcuts = QadShortcuts() + + maxDailyCmdCounter = -1 + + + # ============================================================================ + # version + # ============================================================================ + def version(self): + return "3.0.8" # allinea con metadata.txt alla sez [general] voce "version" + + + def setLastPointAndSegmentAng(self, point, segmentAng = None): + # memorizzo il coeff angolare ultimo segmento e l'ultimo punto selezionato + if segmentAng is None: + if self.lastPoint is not None: + self.setLastSegmentAng(getAngleBy2Pts(self.lastPoint, point)) + else: + self.setLastSegmentAng(segmentAng) + self.setLastPoint(point) + + def setLastPoint(self, point): + if point is None: return + # memorizzo l'ultimo punto selezionato + self.lastPoint = QgsPointXY(point) + self.updatePtsHistory(self.lastPoint) + + def setLastSegmentAng(self, segmentAng): + # memorizzo il coeff angolare ultimo segmento + self.lastSegmentAng = normalizeAngle(segmentAng) + + def setLastRot(self, rot): + # memorizzo l'ultima rotazione in radianti + self.lastRot = normalizeAngle(rot) + + def setLastHText(self, hText): + # memorizzo l'ultima altezza testo + if hText > 0: + self.lastHText = hText + + def setLastReferenceRot(self, rot): + # memorizzo l'ultimo angolo di riferimento (es. comando ruota) in radianti + self.lastReferenceRot = normalizeAngle(rot) + + def setLastNewReferenceRot(self, rot): + # memorizzo l'ultimo nuovo angolo di riferimento (es. comando ruota) in radianti + self.lastNewReferenceRot = normalizeAngle(rot) + + def setLastRadius(self, radius): + # memorizzo l'ultimo raggio + if radius > 0: + self.lastRadius = radius + + def setLastOffsetPt(self, offsetPt): + # memorizzo l'ultimo punto di offset + # la x del punto rappresenta l'offset X + # la y del punto rappresenta l'offset Y + self.lastOffsetPt.set(offsetPt.x(), offsetPt.y()) + + def setLastReferenceLen(self, length): + # memorizzo l'ultima lunghezza di riferimento (es. comando scale) + self.lastReferenceLen = length + + def setLastNewReferenceRot(self, length): + # memorizzo l'ultima nuova lunghezza di riferimento (es. comando scale) + self.lastNewReferenceLen = length + + def setLastScale(self, scale): + # memorizzo l'ultimo fattore di scala + if scale > 0: + self.lastScale = scale + + def setNSegmentsToApproxCurve(self, segments): + # memorizzo il numero di segmenti per l'approssimazione delle curve (es. buffer) + if segments > 1: + self.segments = int(segments) + + def setLastEntity(self, layer, featureId): + # memorizzo l'ultimo entità creata + if self.lastEntity is None: + self.lastEntity = QadEntity() + self.lastEntity.set(layer, featureId) + + def getLastEntity(self): + if self.lastEntity is None: + return None + else: + if self.lastEntity.exists() == False: # non esiste più + return None + else: + return self.lastEntity + + def setLastEntitySet(self, entitySet): + # memorizzo l'ultimo set di entità + if self.lastEntitySet is None: + self.lastEntitySet = QadEntitySet() + self.lastEntitySet.set(entitySet) + + def setJoinMode(self, joinMode): + # memorizzo tipo di unione (es. editpl->unione); 1=Estendi, 2=Aggiungi, 3=Entrambi + if joinMode == 1 or joinMode == 2 or joinMode == 3: + self.joinMode = int(joinMode) + + def setJoinToleranceDist(self, joinToleranceDist): + # memorizzo la distanza di approssimazione nell'unione (es. editpl->unione) + if joinToleranceDist >= 0: + self.joinToleranceDist = joinToleranceDist + + def setFilletMode(self, filletMode): + # memorizzo modalità di raccordo in comando raccordo; 1=Taglia-estendi, 2=Non taglia-estendi + if filletMode == 1 or filletMode == 2: + self.filletMode = int(filletMode) + + def setLastPolygonSideNumber(self, polygonSideNumber): + # memorizzo l'ultimo numero di lati del poligono + if polygonSideNumber > 2: + self.lastPolygonSideNumber = polygonSideNumber + + def setLastPolygonConstructionModeByCenter(self, mode): + # memorizzo ultima opzione di costruzione del poligono conoscendo il centro + # "Inscritto nel cerchio", Circoscritto intorno al cerchio", "Area" + self.lastPolygonConstructionModeByCenter = mode + + def setLastDelta_lengthen(self, lastDelta_lengthen): + # ultimo delta usato nel comando lengthen + self.lastDelta_lengthen = lastDelta_lengthen + + def setLastDeltaAngle_lengthen(self, lastDeltaAngle_lengthen): + # ultimo delta angolo usato nel comando lengthen + self.lastDeltaAngle_lengthen = normalizeAngle(lastDeltaAngle_lengthen) + + def setLastPerc_lengthen(self, lastPerc_lengthen): + # ultima percentuale usata nel comando lengthen + if lastPerc_lengthen > 0: + self.lastPerc_lengthen = lastPerc_lengthen + + def setLastTotal_lengthen(self, lastTotal_lengthen): + # ultima lunghezza totale usato nel comando lengthen + if lastTotal_lengthen > 0: + self.lastTotal_lengthen = lastTotal_lengthen + + def setLastTotalAngle_lengthen(self, lastTotalAngle_lengthen): + # ultimo angolo totale usato nel comando lengthen + self.lastTotalAngle_lengthen = normalizeAngle(lastTotalAngle_lengthen) + + def setLastOpMode_lengthen(self, opMode): + # memorizzo modalità operativa del comando lengthen: "DElta" o "Percent" o "Total" o "DYnamic" + if opMode == "DElta" or opMode == "Percent" or opMode == "Total" or opMode == "DYnamic": + self.lastOpMode_lengthen = opMode + + # INIZIO PARAMETRI COMANDO ARRRAY + def setLastArrayType_array(self, arrayType): + # memorizzo il tipo di serie del comando array + self.lastArrayType_array = arrayType + def setLastItemsRotation_array(self, itemsRotation): + # memorizzo se gli elementi vanno ruotati + self.lastItemsRotation_array = itemsRotation + + def setLastRectangleAngle_array(self, rectangleAngle): + # memorizzo l'angolo di orientamento della serie di tipo rettangolo + self.lastRectangleAngle_array = rectangleAngle + def setLastRectangleCols_array(self, rectangleCols): + # memorizzo il numero di colonne della serie di tipo rettangolo + self.lastRectangleCols_array = rectangleCols + def setLastRectangleRows_array(self, rectangleRows): + # memorizzo il numero di righe della serie di tipo rettangolo + self.lastRectangleRows_array = rectangleRows + + def setLastPathTangentDirection_array(self, pathTangentDirection): + # memorizzo la direzione della tangente per la serie di tipo traiettoria + self.lastPathTangentDirection_array = pathTangentDirection + def setLastPathRows_array(self, pathRows): + # memorizzo il numero di righe della serie di tipo traiettoria + self.lastPathRows_array = pathRows + + def setLastPolarItemsNumber_array(self, polarItemsNumber): + # memorizzo il numero di elementi della serie di tipo polare + self.lastPolarItemsNumber_array = polarItemsNumber + def setLastPolarAngleBetween_array(self, polarAngleBetween): + # memorizzo l'angolo tra gli elementi della serie di tipo polare + self.lastPolarAngleBetween_array = polarAngleBetween + def setLastPolarRows_array(self, polarRows): + # memorizzo il numero di righe della serie di tipo polare + self.lastPolarRows_array = polarRows + # FINE PARAMETRI COMANDO ARRRAY + + def loadDimStyles(self): + global QadDimStyles + # carico gli stili di quotatura + QadDimStyles.load() + # questa variabile non avrebbe senso perchè si dovrebbe usare la variabile globale QadDimStyles + # per un motivo sconosciuto quando si generano gli eventi tipo beforeCommitChanges + # la variabile globale QadDimStyles risulta essere None anche se QAD non lo hai mai posto a quel valore + # se invece uso una variabile del plugin che punta a QadDimStyles questa non viene messa a None + self.mQadDimStyle = QadDimStyles + + + # ============================================================================ + # __initLocalization + # ============================================================================ + # inizializza la localizzazione delle traduzioni e dell'help in linea + def __initLocalization(self, locale): + # traduzioni proprie di qt + # ad esempio l'italiano fornito con qgis non va bene quindi se lo trovo nella cartella del plugin lo carico + localePath = os.path.join(self.plugin_dir, 'i18n', 'qt_{}.qm'.format(locale)) + if os.path.exists(localePath): + self.qttranslator = QTranslator() + self.qttranslator.load(localePath) + if qVersion() > '4.3.3': + QCoreApplication.installTranslator(self.qttranslator) + + localePath = os.path.join(self.plugin_dir, 'i18n', 'qad_{}.qm'.format(locale)) + + if os.path.exists(localePath): + self.translator = QTranslator() + self.translator.load(localePath) + if qVersion() > '4.3.3': + QCoreApplication.installTranslator(self.translator) + return True + else: + return False + + + # ============================================================================ + # __init__ + # ============================================================================ + def __init__(self, iface): + + QObject.__init__(self) + + # Save reference to the QGIS interface + self.iface = iface + + # initialize plugin directory + self.plugin_dir = os.path.dirname(__file__) + + # initialize locale + userLocaleList = QSettings().value("locale/userLocale").split("_") + language = userLocaleList[0] + region = userLocaleList[1] if len(userLocaleList) > 1 else "" + # provo a caricare la lingua e la regione selezionate + if self.__initLocalization(language + "_" + region) == False: + # provo a caricare la lingua + self.__initLocalization(language) + + self.canvas = self.iface.mapCanvas() + + # Lista dei comandi va creata dopo aver inizializzato self.canvas + self.QadCommands = QadCommandsClass(self) + + self.TextWindow = None + + # inizializzazione sul caricamento del progetto + self.initOnProjectLoaded() + + # QadMapTool va creato dopo aver inizializzato self.QadCommands e self.canvas e self.initOnProjectLoaded() + self.tool = QadMapTool(self) + + self.maxDailyCmdCounter = getMaxDailyCmdCounter() + + + # ============================================================================ + # INIZIO - gestione shortcut perchè certi tasti premuti nel mapcanvas non arrivano ... + # ============================================================================ + + + def enableShortcut(self): + self.shortcuts.registerForPrintableAndQadFKeys() + return + + + def disableShortcut(self): + self.shortcuts.unregisterForPrintableAndQadFKeys() + return + + + def getCurrentMapTool(self): + if self.canvas.mapTool() == self.tool: + return self.tool + elif self.QadCommands.actualCommand is not None: + if self.canvas.mapTool() == self.QadCommands.actualCommand.getPointMapTool(): + return self.QadCommands.actualCommand.getPointMapTool() + return None + + def sendKeyToCurrentMapTool(self, keyEvent): + mt = self.getCurrentMapTool() + if mt is not None: + mt.keyPressEvent(keyEvent) + + def send_a_toTxtWindow(self): + self.keyPressEvent(QKeyEvent(QEvent.KeyPress, Qt.Key_A, Qt.NoModifier, "a")) + def send_A_toTxtWindow(self): + self.keyPressEvent(QKeyEvent(QEvent.KeyPress, Qt.Key_A, Qt.NoModifier, "A")) + def send_c_toTxtWindow(self): + self.keyPressEvent(QKeyEvent(QEvent.KeyPress, Qt.Key_C, Qt.NoModifier, "c")) + def send_C_toTxtWindow(self): + self.keyPressEvent(QKeyEvent(QEvent.KeyPress, Qt.Key_C, Qt.NoModifier, "C")) + def send_d_toTxtWindow(self): + self.keyPressEvent(QKeyEvent(QEvent.KeyPress, Qt.Key_D, Qt.NoModifier, "d")) + def send_D_toTxtWindow(self): + self.keyPressEvent(QKeyEvent(QEvent.KeyPress, Qt.Key_D, Qt.NoModifier, "D")) + def send_p_toTxtWindow(self): + self.keyPressEvent(QKeyEvent(QEvent.KeyPress, Qt.Key_P, Qt.NoModifier, "p")) + def send_P_toTxtWindow(self): + self.keyPressEvent(QKeyEvent(QEvent.KeyPress, Qt.Key_P, Qt.NoModifier, "P")) + def send_x_toTxtWindow(self): + self.keyPressEvent(QKeyEvent(QEvent.KeyPress, Qt.Key_X, Qt.NoModifier, "x")) + def send_X_toTxtWindow(self): + self.keyPressEvent(QKeyEvent(QEvent.KeyPress, Qt.Key_X, Qt.NoModifier, "X")) + def send_y_toTxtWindow(self): + self.keyPressEvent(QKeyEvent(QEvent.KeyPress, Qt.Key_Y, Qt.NoModifier, "y")) + def send_Y_toTxtWindow(self): + self.keyPressEvent(QKeyEvent(QEvent.KeyPress, Qt.Key_Y, Qt.NoModifier, "Y")) + def send_F3_toTxtWindow(self): + self.keyPressEvent(QKeyEvent(QEvent.KeyPress, Qt.Key_F3, Qt.NoModifier)) + def send_Backspace_toTxtWindow(self): + self.keyPressEvent(QKeyEvent(QEvent.KeyPress, Qt.Key_Backspace, Qt.NoModifier)) + def send_Delete_toTxtWindow(self): + self.keyPressEvent(QKeyEvent(QEvent.KeyPress, Qt.Key_Delete, Qt.NoModifier)) + + + # ============================================================================ + # FINE - gestione shortcut perchè certi tasti premuti nel mapcanvas non arrivano ... + # ============================================================================ + + + def initOnProjectLoaded(self): + # carico le variabili d'ambiente + QadVariables.load() + + if self.TextWindow is not None: + self.TextWindow.refreshColors() + + # carico gli stili di quotatura + self.loadDimStyles() + # Gestore di Undo/Redo + self.undoStack = QadUndoStack() + + self.UpdatedVariablesEvent() + + + def initGui(self): + # creo tutte le azioni e le collego ai comandi + self.initActions() + + # Connect to signals + self.canvas.mapToolSet.connect(self.deactivate) + + # Add menu + self.menu = QMenu(QadMsg.getQADTitle()) # titolo (nome applicazione + sponsor) + self.menu.addAction(self.mainAction) + + # crea il menu help + self.helpMenu = self.createHelpMenu() + self.menu.addMenu(self.helpMenu) + + self.menu.addAction(self.u_action) + self.menu.addAction(self.undo_action) + self.menu.addAction(self.redo_action) + + # crea il menu Draw + self.drawMenu = self.createDrawMenu() + self.menu.addMenu(self.drawMenu) + + # menu Edit + self.editMenu = self.createEditMenu() + self.menu.addMenu(self.editMenu) + + # menu Tools + self.toolsMenu = self.createToolsMenu() + self.menu.addMenu(self.toolsMenu) + + # menu Dim + self.dimMenu = self.createDimMenu() + self.menu.addMenu(self.dimMenu) + + # aggiunge il menu al menu vector di QGIS + self.iface.vectorMenu().addMenu(self.menu) + +# menu_bar = self.iface.mainWindow().menuBar() +# actions = menu_bar.actions() +# lastAction = actions[ len( actions ) - 1 ] +# menu_bar.insertMenu(lastAction, self.menu ) + + # aggiunge le toolbar + self.toolBar = self.iface.addToolBar(QadMsg.getQADTitle()) + self.toolBar.setObjectName(QadMsg.getQADTitle()) + self.toolBar.addAction(self.mainAction) + + # help + self.helpToolButton = self.createHelpToolButton() + self.toolBar.addWidget(self.helpToolButton) + + # aggiunge le toolbar per i comandi + self.toolBar.addAction(self.setCurrLayerByGraph_action) + self.toolBar.addAction(self.setCurrUpdateableLayerByGraph_action) + self.toolBar.addAction(self.u_action) + self.toolBar.addAction(self.redo_action) + self.toolBar.addAction(self.line_action) + self.toolBar.addAction(self.pline_action) + # arco + self.arcToolButton = self.createArcToolButton() + self.toolBar.addWidget(self.arcToolButton) + # cerchio + self.circleToolButton = self.createCircleToolButton() + self.toolBar.addWidget(self.circleToolButton) + # ellisse + self.ellipseToolButton = self.createEllipseToolButton() # da vedere + self.toolBar.addWidget(self.ellipseToolButton) + + self.toolBar.addAction(self.rectangle_action) + self.toolBar.addAction(self.polygon_action) + self.toolBar.addAction(self.mpolygon_action) + self.toolBar.addAction(self.mbuffer_action) + self.toolBar.addAction(self.insert_action) + self.toolBar.addAction(self.text_action) + + self.toolBar.addAction(self.erase_action) + self.toolBar.addAction(self.rotate_action) + self.toolBar.addAction(self.move_action) + self.toolBar.addAction(self.scale_action) + self.toolBar.addAction(self.copy_action) + + # array + self.arrayToolButton = self.createArrayToolButton() + self.toolBar.addWidget(self.arrayToolButton) + + self.toolBar.addAction(self.offset_action) + self.toolBar.addAction(self.extend_action) + self.toolBar.addAction(self.trim_action) + self.toolBar.addAction(self.mirror_action) + self.toolBar.addAction(self.stretch_action) + self.toolBar.addAction(self.lengthen_action) + self.toolBar.addAction(self.divide_action) + self.toolBar.addAction(self.measure_action) + + # break + self.breakToolButton = self.createBreakToolButton() + self.toolBar.addWidget(self.breakToolButton) + + self.toolBar.addAction(self.pedit_action) + self.toolBar.addAction(self.mapmpedit_action) + self.toolBar.addAction(self.fillet_action) + self.toolBar.addAction(self.join_action) + self.toolBar.addAction(self.disjoin_action) + self.toolBar.addAction(self.id_action) + self.toolBar.addAction(self.dsettings_action) + self.toolBar.addAction(self.options_action) + self.enableUndoRedoButtons() + + # aggiunge la toolbar per la quotatura + self.dimToolBar = self.createDimToolBar() + + # Inizializzo la finestra di testo + self.TextWindow = QadTextWindow(self) + self.TextWindow.initGui() + + isFloating, dockGeometry, dockWidgetArea = self.TextWindow.readDockWidgetSettings() + self.TextWindow.setFloating(isFloating) + if isFloating == False: + # ugly hack, but only way to set dock size correctly for Qt < 5.6 + self.TextWindow.setFixedSize(dockGeometry.size()) + self.iface.addDockWidget(dockWidgetArea, self.TextWindow) + self.TextWindow.resize(dockGeometry.size()) + QgsApplication.processEvents() # required! + self.TextWindow.setFixedSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); + else: + self.TextWindow.setGeometry(dockGeometry) + self.iface.addDockWidget(dockWidgetArea, self.TextWindow) + + + # aggiungo i segnali di aggiunta e rimozione di layer per collegare ogni layer + # all'evento per sapere se la modifica fatta su quel layer + # é stata fatta da QAD o dall'esterno + # per i layer esistenti + for layer in QgsProject.instance().mapLayers().values(): + self.layerAdded(layer) + self.removeLayer(layer.id()) + # per i layer futuri + QgsProject.instance().layerWasAdded.connect(self.layerAdded) + QgsProject.instance().layerWillBeRemoved[QgsMapLayer].connect(self.layerAdded) + self.iface.projectRead.connect(self.onProjectLoaded) + self.iface.newProjectCreated.connect(self.onProjectLoaded) + + self.showTextWindow(QadVariables.get(QadMsg.translate("Environment variables", "SHOWTEXTWINDOW"), True)) + self.setStandardMapTool() + #QMessageBox.warning(None, "titolo" , 'altezza dopo addDockWidget ' + str(self.TextWindow.height())) + + + def unload(self): + self.abortCommand() + # Disconnect to signals + self.canvas.mapToolSet.disconnect(self.deactivate) + + QgsProject.instance().layerWasAdded.disconnect(self.layerAdded) + QgsProject.instance().layerWillBeRemoved[QgsMapLayer].disconnect(self.layerAdded) + self.iface.projectRead.disconnect(self.onProjectLoaded) + self.iface.newProjectCreated.disconnect(self.onProjectLoaded) + + # Remove the plugin menu item and icon + self.iface.removePluginVectorMenu("&QAD", self.mainAction) + self.iface.removeToolBarIcon(self.mainAction) + + # remove toolbars and menubars + if self.toolBar is not None: + del self.toolBar + if self.dimToolBar is not None: + del self.dimToolBar + if self.menu is not None: + del self.menu + + if self.TextWindow is not None: + self.TextWindow.writeDockWidgetSettings() + self.TextWindow.setVisible(False) + self.iface.removeDockWidget(self.TextWindow) + del self.TextWindow + self.TextWindow = None + + if self.tool: + if self.canvas.mapTool() == self.tool: + self.canvas.unsetMapTool(self.tool) + elif self.QadCommands.actualCommand is not None: + if self.canvas.mapTool() == self.QadCommands.actualCommand.getPointMapTool(): + self.canvas.unsetMapTool(self.QadCommands.actualCommand.getPointMapTool()) + + self.tool.removeItems() + del self.tool + + del self.QadCommands + + + def onProjectLoaded(self): + self.initOnProjectLoaded() + self.showTextWindow(QadVariables.get(QadMsg.translate("Environment variables", "SHOWTEXTWINDOW"), True)) + self.setStandardMapTool() + + + # ============================================================================ + # INIZIO - Gestione ACTION (da chiamare prima di creare MENU e TOOLBAR) + # ============================================================================ + def initActions(self): + # Creo le azioni e le collego ai comandi + + self.mainAction = QAction(QIcon(":/plugins/qad/icons/qad.svg"), \ + QadMsg.getQADTitle(), self.iface.mainWindow()) + self.mainAction.setCheckable(True) + self.mainAction.triggered.connect(self.run) + + # SETCURRLAYERBYGRAPH + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "SETCURRLAYERBYGRAPH")) + self.setCurrLayerByGraph_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) + self.setCurrLayerByGraph_action.setToolTip(cmd.getToolTipText()) + cmd.connectQAction(self.setCurrLayerByGraph_action) + # SETCURRUPDATEABLELAYERBYGRAPH + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "SETCURRUPDATEABLELAYERBYGRAPH")) + self.setCurrUpdateableLayerByGraph_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) + self.setCurrUpdateableLayerByGraph_action.setToolTip(cmd.getToolTipText()) + cmd.connectQAction(self.setCurrUpdateableLayerByGraph_action) + + # ARC BY 3 POINTS (MACRO) + self.arcBy3Points_action = QAction(QIcon(":/plugins/qad/icons/arcBy3Points.svg"), \ + QadMsg.translate("Command_ARC", "Arc passing through 3 points"), \ + self.iface.mainWindow()) + self.arcBy3Points_action.triggered.connect(self.runARCBY3POINTSCommand) + # ARC BY START CENTER END POINTS (MACRO) + self.arcByStartCenterEndPoints_action = QAction(QIcon(":/plugins/qad/icons/arcByStartCenterEndPoints.svg"), \ + QadMsg.translate("Command_ARC", "Arc defined by start, central and final points"), \ + self.iface.mainWindow()) + self.arcByStartCenterEndPoints_action.triggered.connect(self.runARC_BY_START_CENTER_END_Command) + # ARC BY START CENTER ANGLE (MACRO) + self.arcByStartCenterAngle_action = QAction(QIcon(":/plugins/qad/icons/arcByStartCenterAngle.svg"), \ + QadMsg.translate("Command_ARC", "Arc defined by start, central points and angle"), \ + self.iface.mainWindow()) + self.arcByStartCenterAngle_action.triggered.connect(self.runARC_BY_START_CENTER_ANGLE_Command) + # ARC BY START CENTER LENGTH (MACRO) + self.arcByStartCenterLength_action = QAction(QIcon(":/plugins/qad/icons/arcByStartCenterLength.svg"), \ + QadMsg.translate("Command_ARC", "Arc defined by start, central points and cord length"), \ + self.iface.mainWindow()) + self.arcByStartCenterLength_action.triggered.connect(self.runARC_BY_START_CENTER_LENGTH_Command) + # ARC BY START END ANGLE (MACRO) + self.arcByStartEndAngle_action = QAction(QIcon(":/plugins/qad/icons/arcByStartEndAngle.svg"), \ + QadMsg.translate("Command_ARC", "Arc defined by start, final points and angle"), \ + self.iface.mainWindow()) + self.arcByStartEndAngle_action.triggered.connect(self.runARC_BY_START_END_ANGLE_Command) + # ARC BY START END TAN (MACRO) + self.arcByStartEndTan_action = QAction(QIcon(":/plugins/qad/icons/arcByStartEndTan.svg"), \ + QadMsg.translate("Command_ARC", "Arc defined by start, final points and tangent"), \ + self.iface.mainWindow()) + self.arcByStartEndTan_action.triggered.connect(self.runARC_BY_START_END_TAN_Command) + # ARC BY START END RADIUS (MACRO) + self.arcByStartEndRadius_action = QAction(QIcon(":/plugins/qad/icons/arcByStartEndRadius.svg"), \ + QadMsg.translate("Command_ARC", "Arc defined by start, final points and radius"), \ + self.iface.mainWindow()) + self.arcByStartEndRadius_action.triggered.connect(self.runARC_BY_START_END_RADIUS_Command) + # ARC BY CENTER START END (MACRO) + self.arcByCenterStartEnd_action = QAction(QIcon(":/plugins/qad/icons/arcByCenterStartEnd.svg"), \ + QadMsg.translate("Command_ARC", "Arc defined by central, start and final points"), \ + self.iface.mainWindow()) + self.arcByCenterStartEnd_action.triggered.connect(self.runARC_BY_CENTER_START_END_Command) + # ARC BY CENTER START ANGLE (MACRO) + self.arcByCenterStartAngle_action = QAction(QIcon(":/plugins/qad/icons/arcByCenterStartAngle.svg"), \ + QadMsg.translate("Command_ARC", "Arc defined by central, start points and angle"), \ + self.iface.mainWindow()) + self.arcByCenterStartAngle_action.triggered.connect(self.runARC_BY_CENTER_START_ANGLE_Command) + # ARC BY CENTER START LENGTH (MACRO) + self.arcByCenterStartLength_action = QAction(QIcon(":/plugins/qad/icons/arcByCenterStartLength.svg"), \ + QadMsg.translate("Command_ARC", "Arc defined by central, start points and cord length"), \ + self.iface.mainWindow()) + self.arcByCenterStartLength_action.triggered.connect(self.runARC_BY_CENTER_START_LENGTH_Command) + + # ARRAYRECT + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "ARRAYRECT")) + self.arrayRect_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) + self.arrayRect_action.setToolTip(cmd.getToolTipText()) + cmd.connectQAction(self.arrayRect_action) + # ARRAYPATH + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "ARRAYPATH")) + self.arrayPath_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) + self.arrayPath_action.setToolTip(cmd.getToolTipText()) + cmd.connectQAction(self.arrayPath_action) + # ARRAYPOLAR (MACRO) + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "ARRAYPOLAR")) + self.arrayPolar_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) + self.arrayPolar_action.setToolTip(cmd.getToolTipText()) + cmd.connectQAction(self.arrayPolar_action) + + # BREAK + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "BREAK")) + self.break_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) + self.break_action.setToolTip(cmd.getToolTipText()) + cmd.connectQAction(self.break_action) + # BREAK BY 1 POINT (MACRO) + self.breakBy1Point_action = QAction(QIcon(":/plugins/qad/icons/breakBy1Point.svg"), \ + QadMsg.translate("Command_BREAK", "Breaks an object at one point"), \ + self.iface.mainWindow()) + self.breakBy1Point_action.triggered.connect(self.runBREAK_BY_1_POINT_Command) + + # CIRCLE BY CENTER RADIUS (MACRO) + self.circleByCenterRadius_action = QAction(QIcon(":/plugins/qad/icons/circleByCenterRadius.svg"), \ + QadMsg.translate("Command_CIRCLE", "Circle defined by central point and radius"), \ + self.iface.mainWindow()) + self.circleByCenterRadius_action.triggered.connect(self.runCIRCLE_BY_CENTER_RADIUS_Command) + # CIRCLE BY CENTER DIAMETER (MACRO) + self.circleByCenterDiameter_action = QAction(QIcon(":/plugins/qad/icons/circleByCenterDiameter.svg"), \ + QadMsg.translate("Command_CIRCLE", "Circle defined by central point and diameter"), \ + self.iface.mainWindow()) + self.circleByCenterDiameter_action.triggered.connect(self.runCIRCLE_BY_CENTER_DIAMETER_Command) + # CIRCLE BY 2 POINTS (MACRO) + self.circleBy2Points_action = QAction(QIcon(":/plugins/qad/icons/circleBy2Points.svg"), \ + QadMsg.translate("Command_CIRCLE", "Circle defined by 2 points"), \ + self.iface.mainWindow()) + self.circleBy2Points_action.triggered.connect(self.runCIRCLE_BY_2POINTS_Command) + # CIRCLE BY 3 POINTS (MACRO) + self.circleBy3Points_action = QAction(QIcon(":/plugins/qad/icons/circleBy3Points.svg"), \ + QadMsg.translate("Command_CIRCLE", "Circle defined by 3 points"), \ + self.iface.mainWindow()) + self.circleBy3Points_action.triggered.connect(self.runCIRCLE_BY_3POINTS_Command) + # CIRCLE BY TANGEN TANGENT RADIUS (MACRO) + self.circleBy2TansRadius_action = QAction(QIcon(":/plugins/qad/icons/circleBy2TansRadius.svg"), \ + QadMsg.translate("Command_CIRCLE", "Circle defined by 2 tangent points and radius"), \ + self.iface.mainWindow()) + self.circleBy2TansRadius_action.triggered.connect(self.runCIRCLE_BY_2TANS_RADIUS_Command) + # CIRCLE BY TANGEN TANGENT TANGENT (MACRO) + self.circleBy3Tans_action = QAction(QIcon(":/plugins/qad/icons/circleBy3Tans.svg"), \ + QadMsg.translate("Command_CIRCLE", "Circle defined by 3 tangent points"), \ + self.iface.mainWindow()) + self.circleBy3Tans_action.triggered.connect(self.runCIRCLE_BY_3TANS_Command) + + # COPY + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "COPY")) + self.copy_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) + self.copy_action.setToolTip(cmd.getToolTipText()) + cmd.connectQAction(self.copy_action) + + # DIMALIGNED + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "DIMALIGNED")) + self.dimAligned_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) + self.dimAligned_action.setToolTip(cmd.getToolTipText()) + cmd.connectQAction(self.dimAligned_action) + # DIMLINEAR + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "DIMLINEAR")) + self.dimLinear_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) + self.dimLinear_action.setToolTip(cmd.getToolTipText()) + cmd.connectQAction(self.dimLinear_action) + # DIMARC + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "DIMARC")) + self.dimArc_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) + self.dimArc_action.setToolTip(cmd.getToolTipText()) + cmd.connectQAction(self.dimArc_action) + # DIMRADIUS + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "DIMRADIUS")) + self.dimRadius_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) + self.dimRadius_action.setToolTip(cmd.getToolTipText()) + cmd.connectQAction(self.dimRadius_action) + # DIMSTYLE + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "DIMSTYLE")) + self.dimStyle_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) + self.dimStyle_action.setToolTip(cmd.getToolTipText()) + cmd.connectQAction(self.dimStyle_action) + + # DSETTINGS + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "DSETTINGS")) + self.dsettings_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) + self.dsettings_action.setToolTip(cmd.getToolTipText()) + cmd.connectQAction(self.dsettings_action) + + # DISJOIN + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "DISJOIN")) + self.disjoin_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) + self.disjoin_action.setToolTip(cmd.getToolTipText()) + cmd.connectQAction(self.disjoin_action) + + # DIVIDE + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "DIVIDE")) + self.divide_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) + self.divide_action.setToolTip(cmd.getToolTipText()) + cmd.connectQAction(self.divide_action) + + # ELLIPSE BY CENTER 2 POINTS (MACRO) + self.ellipseByCenter2Points_action = QAction(QIcon(":/plugins/qad/icons/ellipseByCenter2Points.svg"), \ + QadMsg.translate("Command_ELLIPSE", "Ellipse defined by central point"), \ + self.iface.mainWindow()) + self.ellipseByCenter2Points_action.triggered.connect(self.runELLIPSE_BY_CENTER_2_POINTS_Command) + # ELLIPSE BY AXIS1 + self.ellipse_action = QAction(QIcon(":/plugins/qad/icons/ellipseByAxis1Point.svg"), \ + QadMsg.translate("Command_ELLIPSE", "Creates an ellipse or an elliptical arc"), \ + self.iface.mainWindow()) + self.ellipse_action.triggered.connect(self.runELLIPSECommand) + # ELLIPTICAL ARC (MACRO) + self.ellipticalArc_action = QAction(QIcon(":/plugins/qad/icons/ellipseArc.svg"), \ + QadMsg.translate("Command_ELLIPSE", "Creates an elliptical arc"), \ + self.iface.mainWindow()) + self.ellipticalArc_action.triggered.connect(self.runELLIPTICAL_ARC_Command) + # ELLIPSE BY FOCI (MACRO) + self.ellipseByFoci_action = QAction(QIcon(":/plugins/qad/icons/ellipseByFociPoint.svg"), \ + QadMsg.translate("Command_ELLIPSE", "Creates an ellipse by foci"), \ + self.iface.mainWindow()) + self.ellipseByFoci_action.triggered.connect(self.runELLIPSE_BY_FOCI_Command) + + # ERASE + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "ERASE")) + self.erase_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) + self.erase_action.setToolTip(cmd.getToolTipText()) + cmd.connectQAction(self.erase_action) + + # EXTEND + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "EXTEND")) + self.extend_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) + self.extend_action.setToolTip(cmd.getToolTipText()) + cmd.connectQAction(self.extend_action) + + # FILLET + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "FILLET")) + self.fillet_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) + self.fillet_action.setToolTip(cmd.getToolTipText()) + cmd.connectQAction(self.fillet_action) + + # HELP + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "HELP")) + self.help_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) + self.help_action.setToolTip(cmd.getToolTipText()) + cmd.connectQAction(self.help_action) + + # ID + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "ID")) + self.id_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) + self.id_action.setToolTip(cmd.getToolTipText()) + cmd.connectQAction(self.id_action) + + # INSERT + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "INSERT")) + self.insert_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) + self.insert_action.setToolTip(cmd.getToolTipText()) + cmd.connectQAction(self.insert_action) + + # JOIN + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "JOIN")) + self.join_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) + self.join_action.setToolTip(cmd.getToolTipText()) + cmd.connectQAction(self.join_action) + + # LENGTHEN + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "LENGTHEN")) + self.lengthen_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) + self.lengthen_action.setToolTip(cmd.getToolTipText()) + cmd.connectQAction(self.lengthen_action) + + # LINE + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "LINE")) + self.line_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) + self.line_action.setToolTip(cmd.getToolTipText()) + cmd.connectQAction(self.line_action) + + # MAPMPEDIT + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "MAPMPEDIT")) + self.mapmpedit_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) + self.mapmpedit_action.setToolTip(cmd.getToolTipText()) + cmd.connectQAction(self.mapmpedit_action) + + # MBUFFER + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "MBUFFER")) + self.mbuffer_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) + self.mbuffer_action.setToolTip(cmd.getToolTipText()) + cmd.connectQAction(self.mbuffer_action) + + # MEASURE + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "MEASURE")) + self.measure_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) + self.measure_action.setToolTip(cmd.getToolTipText()) + cmd.connectQAction(self.measure_action) + + # MIRROR + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "MIRROR")) + self.mirror_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) + self.mirror_action.setToolTip(cmd.getToolTipText()) + cmd.connectQAction(self.mirror_action) + + # MOVE + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "MOVE")) + self.move_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) + self.move_action.setToolTip(cmd.getToolTipText()) + cmd.connectQAction(self.move_action) + + # MPOLYGON + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "MPOLYGON")) + self.mpolygon_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) + self.mpolygon_action.setToolTip(cmd.getToolTipText()) + cmd.connectQAction(self.mpolygon_action) + + # OFFSET + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "OFFSET")) + self.offset_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) + self.offset_action.setToolTip(cmd.getToolTipText()) + cmd.connectQAction(self.offset_action) + + # OPTIONS + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "OPTIONS")) + self.options_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) + self.options_action.setToolTip(cmd.getToolTipText()) + cmd.connectQAction(self.options_action) + + # PEDIT + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "PEDIT")) + self.pedit_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) + self.pedit_action.setToolTip(cmd.getToolTipText()) + cmd.connectQAction(self.pedit_action) + + # PLINE + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "PLINE")) + self.pline_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) + self.pline_action.setToolTip(cmd.getToolTipText()) + cmd.connectQAction(self.pline_action) + + # POLYGON + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "POLYGON")) + self.polygon_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) + self.polygon_action.setToolTip(cmd.getToolTipText()) + cmd.connectQAction(self.polygon_action) + + # RECTANGLE + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "RECTANGLE")) + self.rectangle_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) + self.rectangle_action.setToolTip(cmd.getToolTipText()) + cmd.connectQAction(self.rectangle_action) + + # REDO + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "REDO")) + self.redo_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) + self.redo_action.setToolTip(cmd.getToolTipText()) + cmd.connectQAction(self.redo_action) + + # ROTATE + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "ROTATE")) + self.rotate_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) + self.rotate_action.setToolTip(cmd.getToolTipText()) + cmd.connectQAction(self.rotate_action) + + # SCALE + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "SCALE")) + self.scale_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) + self.scale_action.setToolTip(cmd.getToolTipText()) + cmd.connectQAction(self.scale_action) + + # STRETCH + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "STRETCH")) + self.stretch_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) + self.stretch_action.setToolTip(cmd.getToolTipText()) + cmd.connectQAction(self.stretch_action) + + # SUPPORTERS + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "SUPPORTERS")) + self.supporters_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) + self.supporters_action.setToolTip(cmd.getToolTipText()) + cmd.connectQAction(self.supporters_action) + + # TEXT + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "TEXT")) + self.text_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) + self.text_action.setToolTip(cmd.getToolTipText()) + cmd.connectQAction(self.text_action) + + # TRIM + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "TRIM")) + self.trim_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) + self.trim_action.setToolTip(cmd.getToolTipText()) + cmd.connectQAction(self.trim_action) + + # UNDO + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "UNDO")) + self.undo_action = QAction(cmd.getIcon(), cmd.getName(), self.iface.mainWindow()) + self.undo_action.setToolTip(cmd.getToolTipText()) + cmd.connectQAction(self.undo_action) + # UNDO OF ONLY ONE OPERATION (MACRO) + self.u_action = QAction(QIcon(":/plugins/qad/icons/u.svg"), \ + QadMsg.translate("Command_UNDO", "Undo last operation"), \ + self.iface.mainWindow()) + self.u_action.triggered.connect(self.runU_Command) + + + # ============================================================================ + # FINE - Gestione ACTION + # ============================================================================ + + + def UpdatedVariablesEvent(self): + # aggiorna in base alle nuove impostazioni delle variabili + if self.tool: + self.tool.UpdatedVariablesEvent() + if self.TextWindow: + self.TextWindow.refreshColors() + + + # ============================================================================ + # INIZIO - Gestione MENU (da chiamare prima di creare TOOLBAR) + # ============================================================================ + + + def createHelpMenu(self): + # menu Help + helpMenu = QMenu(QadMsg.translate("Command_list", "HELP")) + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "HELP")) + helpMenu.setIcon(cmd.getIcon()) + helpMenu.addAction(self.help_action) + helpMenu.addAction(self.supporters_action) + return helpMenu + + def createArcMenu(self): + # menu Arco + arcMenu = QMenu(QadMsg.translate("Command_list", "ARC")) + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "ARC")) + arcMenu.setIcon(cmd.getIcon()) + arcMenu.addAction(self.arcBy3Points_action) + arcMenu.addSeparator() + arcMenu.addAction(self.arcByStartCenterEndPoints_action) + arcMenu.addAction(self.arcByStartCenterAngle_action) + arcMenu.addAction(self.arcByStartCenterLength_action) + arcMenu.addSeparator() + arcMenu.addAction(self.arcByStartEndAngle_action) + arcMenu.addAction(self.arcByStartEndTan_action) + arcMenu.addAction(self.arcByStartEndRadius_action) + arcMenu.addSeparator() + arcMenu.addAction(self.arcByCenterStartEnd_action) + arcMenu.addAction(self.arcByCenterStartAngle_action) + arcMenu.addAction(self.arcByCenterStartLength_action) + return arcMenu + + def createBreakMenu(self): + # menu Break + breakMenu = QMenu(QadMsg.translate("Command_list", "BREAK")) + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "BREAK")) + breakMenu.setIcon(cmd.getIcon()) + breakMenu.addAction(self.break_action) + breakMenu.addAction(self.breakBy1Point_action) + return breakMenu + + def createCircleMenu(self): + # menu Circle + circleMenu = QMenu(QadMsg.translate("Command_list", "CIRCLE")) + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "CIRCLE")) + circleMenu.setIcon(cmd.getIcon()) + circleMenu.addAction(self.circleByCenterRadius_action) + circleMenu.addAction(self.circleByCenterDiameter_action) + circleMenu.addSeparator() + circleMenu.addAction(self.circleBy2Points_action) + circleMenu.addAction(self.circleBy3Points_action) + circleMenu.addSeparator() + circleMenu.addAction(self.circleBy2TansRadius_action) + circleMenu.addAction(self.circleBy3Tans_action) + return circleMenu + + def createEllipseMenu(self): + # menu Circle + ellipseMenu = QMenu(QadMsg.translate("Command_list", "ELLIPSE")) + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "ELLIPSE")) + ellipseMenu.setIcon(cmd.getIcon()) + ellipseMenu.addAction(self.ellipseByCenter2Points_action) + ellipseMenu.addAction(self.ellipse_action) + ellipseMenu.addAction(self.ellipticalArc_action) + ellipseMenu.addAction(self.ellipseByFoci_action) + return ellipseMenu + + def createArrayMenu(self): + # menu Circle + arrayMenu = QMenu(QadMsg.translate("Command_list", "ARRAY")) + cmd = self.QadCommands.getCommandObj(QadMsg.translate("Command_list", "ARRAY")) + arrayMenu.setIcon(cmd.getIcon()) + arrayMenu.addAction(self.arrayRect_action) + arrayMenu.addAction(self.arrayPath_action) + arrayMenu.addAction(self.arrayPolar_action) + return arrayMenu + + def createDrawMenu(self): + # menu Draw + drawMenu = QMenu(QadMsg.translate("QAD", "Draw")) + drawMenu.addAction(self.line_action) + drawMenu.addAction(self.pline_action) + + # menu arco + self.arcMenu = self.createArcMenu() + drawMenu.addMenu(self.arcMenu) + + # menu cerchio + self.circleMenu = self.createCircleMenu() + drawMenu.addMenu(self.circleMenu) + + # menu ellisse + self.ellipseMenu = self.createEllipseMenu() + drawMenu.addMenu(self.ellipseMenu) + + drawMenu.addAction(self.rectangle_action) + drawMenu.addAction(self.polygon_action) + drawMenu.addAction(self.mpolygon_action) + drawMenu.addAction(self.mbuffer_action) + drawMenu.addSeparator() + drawMenu.addAction(self.insert_action) + drawMenu.addAction(self.text_action) + return drawMenu + + def createEditMenu(self): + # menu Edit + editMenu = QMenu(QadMsg.translate("QAD", "Edit")) + editMenu.addAction(self.erase_action) + editMenu.addAction(self.rotate_action) + editMenu.addAction(self.move_action) + editMenu.addAction(self.scale_action) + editMenu.addAction(self.copy_action) + + # menu array + self.arrayMenu = self.createArrayMenu() + editMenu.addMenu(self.arrayMenu) + + editMenu.addAction(self.offset_action) + editMenu.addAction(self.extend_action) + editMenu.addAction(self.trim_action) + editMenu.addAction(self.mirror_action) + editMenu.addAction(self.stretch_action) + editMenu.addAction(self.lengthen_action) + editMenu.addAction(self.divide_action) + editMenu.addAction(self.measure_action) + + # menu break + self.breakMenu = self.createBreakMenu() + editMenu.addMenu(self.breakMenu) + + editMenu.addAction(self.pedit_action) + editMenu.addAction(self.mapmpedit_action) + editMenu.addAction(self.fillet_action) + editMenu.addAction(self.join_action) + editMenu.addAction(self.disjoin_action) + return editMenu + + def createToolsMenu(self): + # menu Tools + toolsMenu = QMenu(QadMsg.translate("QAD", "Tools")) + toolsMenu.addAction(self.setCurrLayerByGraph_action) + toolsMenu.addAction(self.setCurrUpdateableLayerByGraph_action) + toolsMenu.addAction(self.id_action) + toolsMenu.addAction(self.dsettings_action) + toolsMenu.addAction(self.options_action) + return toolsMenu + + def createDimMenu(self): + # menu Dim + dimMenu = QMenu(QadMsg.translate("QAD", "Dimensioning")) + dimMenu.addAction(self.dimLinear_action) + dimMenu.addAction(self.dimAligned_action) + dimMenu.addAction(self.dimArc_action) + dimMenu.addAction(self.dimRadius_action) + dimMenu.addAction(self.dimStyle_action) + return dimMenu + + # ============================================================================ + # FINE - Gestione MENU + # ============================================================================ + + + # ============================================================================ + # INIZIO - Gestione TOOLBAR + # ============================================================================ + + def createHelpToolButton(self): + helpToolButton = QToolButton(self.toolBar) + helpToolButton.setPopupMode(QToolButton.MenuButtonPopup) + helpToolButton.setMenu(self.helpMenu) + helpToolButton.setDefaultAction(self.helpMenu.actions()[0]) # prima voce di menu + helpToolButton.triggered.connect(self.helpToolButtonTriggered) + return helpToolButton + def helpToolButtonTriggered(self, action): + self.helpToolButton.setDefaultAction(action) + + + def createArcToolButton(self): + arcToolButton = QToolButton(self.toolBar) + arcToolButton.setPopupMode(QToolButton.MenuButtonPopup) + arcToolButton.setMenu(self.arcMenu) + arcToolButton.setDefaultAction(self.arcMenu.actions()[0]) # prima voce di menu + arcToolButton.triggered.connect(self.arcToolButtonTriggered) + return arcToolButton + def arcToolButtonTriggered(self, action): + self.arcToolButton.setDefaultAction(action) + + + def createArrayToolButton(self): + arrayToolButton = QToolButton(self.toolBar) + arrayToolButton.setPopupMode(QToolButton.MenuButtonPopup) + arrayToolButton.setMenu(self.arrayMenu) + arrayToolButton.setDefaultAction(self.arrayMenu.actions()[0]) # prima voce di menu + arrayToolButton.triggered.connect(self.arrayToolButtonTriggered) + return arrayToolButton + def arrayToolButtonTriggered(self, action): + self.arrayToolButton.setDefaultAction(action) + + + def createBreakToolButton(self): + breakToolButton = QToolButton(self.toolBar) + breakToolButton.setPopupMode(QToolButton.MenuButtonPopup) + breakToolButton.setMenu(self.breakMenu) + breakToolButton.setDefaultAction(self.breakMenu.actions()[0]) # prima voce di menu + breakToolButton.triggered.connect(self.breakToolButtonTriggered) + return breakToolButton + def breakToolButtonTriggered(self, action): + self.breakToolButton.setDefaultAction(action) + + + def createCircleToolButton(self): + circleToolButton = QToolButton(self.toolBar) + circleToolButton.setPopupMode(QToolButton.MenuButtonPopup) + circleToolButton.setMenu(self.circleMenu) + circleToolButton.setDefaultAction(self.circleMenu.actions()[0]) # prima voce di menu + circleToolButton.triggered.connect(self.circleToolButtonTriggered) + return circleToolButton + def circleToolButtonTriggered(self, action): + self.circleToolButton.setDefaultAction(action) + + + def createEllipseToolButton(self): + ellipseToolButton = QToolButton(self.toolBar) + ellipseToolButton.setPopupMode(QToolButton.MenuButtonPopup) + ellipseToolButton.setMenu(self.ellipseMenu) + ellipseToolButton.setDefaultAction(self.ellipseMenu.actions()[0]) # prima voce di menu + ellipseToolButton.triggered.connect(self.ellipseToolButtonTriggered) + return ellipseToolButton + def ellipseToolButtonTriggered(self, action): + self.ellipseToolButton.setDefaultAction(action) + + + def createDimToolBar(self): + # aggiunge la toolbar per la quotatura + toolBar = self.iface.addToolBar(QadMsg.getQADTitle() + " - " + QadMsg.translate("QAD", "Dimensioning")) + toolBar.setObjectName(QadMsg.getQADTitle() + " - " + QadMsg.translate("QAD", "Dimensioning")) + toolBar.addAction(self.dimLinear_action) + toolBar.addAction(self.dimAligned_action) + toolBar.addAction(self.dimArc_action) + toolBar.addAction(self.dimRadius_action) + toolBar.addAction(self.dimStyle_action) + return toolBar + + + # ============================================================================ + # FINE - Gestione TOOLBAR + # ============================================================================ + + + # ============================================================================ + # INIZIO - Gestione Layer + # ============================================================================ + + # se viene salvato per prima un layer di quote testuale: + # 1) beforeCommitChanges su layer testuale + # 2) committedFeaturesAdded su layer testuale + # 3) editingStopped su layer testuale che scatena + # 1) committedFeaturesAdded su layer linee + # 2) committedFeaturesAdded su layer simboli + + # se viene salvato per prima un layer di quote simbolo o linea: + # 1) beforeCommitChanges su layer simbolo o linea + # 2) committedFeaturesAdded su layer testuale + # 3) editingStopped su layer testuale che scatena + # 1) committedFeaturesAdded su layer linea + # 2) committedFeaturesAdded su layer simboli + + def layerAdded(self, layer): + if (layer.type() != QgsMapLayer.VectorLayer): + return + layer.layerModified.connect(self.layerModified) + layer.beforeCommitChanges.connect(self.beforeCommitChanges) + layer.committedFeaturesAdded.connect(self.committedFeaturesAdded) + layer.afterCommitChanges.connect(self.afterCommitChanges) + + # vedi qgsvectorlayer.cpp funzione QgsVectorLayer::commitChanges() + # devo predere l'ultimo segnale vhe viene emesso dal salvataggio QGIS + # questo segnale arriva alla fine del salvataggio di un layer dalla versione 2.3 di QGIS + # layer.repaintRequested.connect(self.repaintRequested) + # questo segnale arriva alla fine del salvataggio di un layer alla versione 2.2 di QGIS + layer.editingStopped.connect(self.editingStopped) + + + def removeLayer(self, layerId): + self.abortCommand() # perchè il comando corrente potrebbe puntare al layer + layer = getLayerById(layerId) + if (layer.type() != QgsMapLayer.VectorLayer): + return + layer.layerModified.disconnect(self.layerModified) + layer.beforeCommitChanges.disconnect(self.beforeCommitChanges) + layer.committedFeaturesAdded.disconnect(self.committedFeaturesAdded) + layer.afterCommitChanges.disconnect(self.afterCommitChanges) + + # vedi qgsvectorlayer.cpp funzione QgsVectorLayer::commitChanges() + # devo predere l'ultimo segnale vhe viene emesso dal salvataggio QGIS + # questo segnale arriva alla fine del salvataggio di un layer dalla versione 2.3 di QGIS + # layer.repaintRequested.disconnect(self.repaintRequested) + # questo segnale arriva alla fine del salvataggio di un layer alla versione 2.2 di QGIS + layer.editingStopped.disconnect(self.editingStopped) + + # viene rimosso un layer quindi lo tolgo dallo stack + self.undoStack.clearByLayer(layerId) + self.enableUndoRedoButtons() + # lo elimino anche dalla lista degli stati dei layer + self.layerStatusList.remove(layer.name()) + + + def editingStopped(self): + return +# # questo segnale arriva alla fine del salvataggio di un layer alla versione 2.2 di QGIS +# # se bisogna fare la ricodifica delle quote +# if self.dimTextEntitySetRecodeOnSave.isEmpty() == False: +# layer = self.dimTextEntitySetRecodeOnSave.layer +# # ricavo gli stili di quotatura +# dimStyleList = self.mQadDimStyle.getDimListByLayer(layer) +# for dimStyle in dimStyleList: +# if dimStyle.isValid(): # stile valido +# # cerco tutte le feature text in self.dimTextEntitySetRecodeOnSave che appartengono allo stile +# # di quotatura dimStyle +# textAddedEntitySet = dimStyle.getFilteredLayerEntitySet(self.dimTextEntitySetRecodeOnSave) +# # salvo gli oggetti di quello stile di quotatura aggiornando i reference +# # ricodifica +# dimStyle.updateTextReferencesOnSave(self, textAddedEntitySet) +# +# self.dimTextEntitySetRecodeOnSave.clear() +# +# for dimStyle in dimStyleList: +# if dimStyle.isValid(): # stile valido +# # salvataggio +# dimStyle.commitChanges(self) # la funzione scarta self.beforeCommitChangesDimLayer +# self.beforeCommitChangesDimLayer = None +# dimStyle.startEditing() + + + def repaintRequested(self): + return +# # questo segnale arriva alla fine del salvataggio di un layer dalla versione 2.3 di QGIS +# # se bisogna fare la ricodifica delle quote +# if self.dimTextEntitySetRecodeOnSave.isEmpty() == False: +# # ricavo gli stili di quotatura +# dimStyleList = self.mQadDimStyle.getDimListByLayer(self.dimTextEntitySetRecodeOnSave.layer) +# for dimStyle in dimStyleList: +# if dimStyle.isValid(): # stile valido +# # cerco tutte le feature in self.dimTextEntitySetRecodeOnSave che appartengono allo stile +# # di quotatura dimStyle +# textAddedEntitySet = dimStyle.getFilteredLayerEntitySet(self.dimTextEntitySetRecodeOnSave) +# # salvo gli oggetti di quello stile di quotatura aggiornando i reference +# # ricodifica +# dimStyle.updateTextReferencesOnSave(self, textAddedEntitySet) +# +# self.dimTextEntitySetRecodeOnSave.clear() +# +# for dimStyle in dimStyleList: +# if dimStyle.isValid(): # stile valido +# # salvataggio +# dimStyle.commitChanges(self) # la funzione scarta self.beforeCommitChangesDimLayer +# self.beforeCommitChangesDimLayer = None +# dimStyle.startEditing() + + + def beforeCommitChanges(self): + layer = self.sender() + # verifico se il salvataggio del layer non è controllato da QAD + if self.layerStatusList.getStatus(layer.id()) != QadLayerStatusEnum.COMMIT_BY_INTERNAL: + # verifico se il layer che si sta per salvare appartiene ad uno o più stili di quotatura + dimStyleList = self.mQadDimStyle.getDimListByLayer(layer) + for dimStyle in dimStyleList: + if dimStyle.isValid(): # stile valido + if dimStyle.getTextualLayer().id() != layer.id(): # se non si tratta del layer dei testi di quota + # memorizzo il layer da cui é scaturito il salvataggio delle quotature per scartarlo + # nella funzione dimStyle.commitChanges + # self.beforeCommitChangesDimLayer = layer non lo uso più perchè non serve con l'evento afterCommitChanges + dimStyle.textCommitChangesOnSave(self) # salvo i testi delle quote per ricodifica ID + #dimStyle.startEditing() + + + def committedFeaturesAdded(self, layerId, addedFeatures): + layer = getLayerById(layerId) + # verifico se il layer che é stato salvato appartiene ad uno o più stili di quotatura + dimStyleList = self.mQadDimStyle.getDimListByLayer(layer) + for dimStyle in dimStyleList: + if dimStyle.isValid(): # stile valido + # se si tratta del layer testuale delle quote + if dimStyle.getTextualLayer().id() == layerId: + # mi memorizzo le features testuali da riallineare + self.dimTextEntitySetRecodeOnSave.set(dimStyle.getTextualLayer(), addedFeatures) + return + + + def afterCommitChanges(self): + layer = self.sender() + # verifico se il salvataggio del layer non è controllato da QAD + if self.layerStatusList.getStatus(layer.id()) != QadLayerStatusEnum.COMMIT_BY_INTERNAL: + # questo segnale arriva alla fine del salvataggio di un layer (versione 3.4 di QGIS) + # verifico se il layer che si sta per salvare appartiene ad uno o più stili di quotatura + dimStyleList = self.mQadDimStyle.getDimListByLayer(layer) + for dimStyle in dimStyleList: + if dimStyle.isValid(): # stile valido + # cerco tutte le feature in self.dimTextEntitySetRecodeOnSave che appartengono allo stile + # di quotatura dimStyle + textAddedEntitySet = dimStyle.getFilteredLayerEntitySet(self.dimTextEntitySetRecodeOnSave) + # salvo gli oggetti di quello stile di quotatura aggiornando i reference + # ricodifica + dimStyle.updateTextReferencesOnSave(self, textAddedEntitySet) + self.dimTextEntitySetRecodeOnSave.subtract(textAddedEntitySet) + + # salvataggio + dimStyle.commitChanges(self) # la funzione scarta self.beforeCommitChangesDimLayer + self.beforeCommitChangesDimLayer = None + #dimStyle.startEditing() + + + def layerModified(self): + if self.isQadActive == False: + # la modifica fatta su quel layer é stata fatta dall'esterno di QAD + # quindi ho perso la sincronizzazione con lo stack di undo di QAD che + # viene svuotato perché ormai inutilizzabile + self.undoStack.clear() + self.enableUndoRedoButtons() + + + # ============================================================================ + # INIZIO - Gestione UNDO e REDO + # ============================================================================ + + + def enableUndoRedoButtons(self): + self.undo_action.setEnabled(self.undoStack.isUndoAble()) + self.u_action.setEnabled(self.undoStack.isUndoAble()) + self.redo_action.setEnabled(self.undoStack.isRedoAble()) + + def beginEditCommand(self, text, layerList): + if type(layerList) == list or type(layerList) == tuple: + # layerList é una lista di layer + self.undoStack.beginEditCommand(text, layerList) + else: + # layerList é un solo layer + self.undoStack.beginEditCommand(text, [layerList]) + + + def destroyEditCommand(self): + # pulisco le entità selezionate e i grip points correnti + self.tool.clearEntitySet() + self.tool.clearEntityGripPoints() + + self.isQadActive = True + self.undoStack.destroyEditCommand() + self.isQadActive = False + self.enableUndoRedoButtons() + + + def endEditCommand(self): + self.isQadActive = True + self.undoStack.endEditCommand(self.canvas) + self.isQadActive = False + self.enableUndoRedoButtons() + + def undoEditCommand(self, nTimes = 1): + # pulisco le entità selezionate e i grip points correnti + self.tool.clearEntitySet() + self.tool.clearEntityGripPoints() + + self.isQadActive = True + self.undoStack.undoEditCommand(self.canvas, nTimes) + self.isQadActive = False + self.enableUndoRedoButtons() + + + def redoEditCommand(self, nTimes = 1): + # pulisco le entità selezionate e i grip points correnti + self.tool.clearEntitySet() + self.tool.clearEntityGripPoints() + + self.isQadActive = True + self.undoStack.redoEditCommand(self.canvas, nTimes) + self.isQadActive = False + self.enableUndoRedoButtons() + + def addLayerListToLastEditCommand(self, text, layerList): + for layer in layerList: + self.undoStack.addLayerToLastEditCommand(text, layer) + + def addLayerToLastEditCommand(self, text, layer): + self.undoStack.addLayerToLastEditCommand(text, layer) + + def insertBeginGroup(self, text = "Group"): + self.undoStack.insertBeginGroup(text) + + def insertEndGroup(self): + return self.undoStack.insertEndGroup() + + def insertBookmark(self, text = "Bookmark"): + return self.undoStack.insertBookmark(text) + + def getPrevBookmarkPos(self): + return self.undoStack.getPrevBookmarkPos(self.undoStack.index) + + def undoUntilBookmark(self): + # pulisco le entità selezionate e i grip points correnti + self.tool.clearEntitySet() + self.tool.clearEntityGripPoints() + + self.isQadActive = True + self.undoStack.undoUntilBookmark(self.canvas) + self.isQadActive = False + self.enableUndoRedoButtons() + + + # ============================================================================ + # FINE - Gestione UNDO e REDO + # ============================================================================ + + def run(self): + self.setStandardMapTool() + self.showTextWindow() + + def deactivate(self): + self.mainAction.setChecked(False) + + def setStandardMapTool(self): + mc = self.canvas + mc.setMapTool(self.tool) + self.mainAction.setChecked(True) + + def keyPressEvent(self, event): + self.TextWindow.keyPressEvent(event) + + + # ============================================================================ + # INIZIO - funzioni per visualizzare messaggi nella finestra di testo + # ============================================================================ + def showTextWindow(self, mode = True): + self.TextWindow.setVisible(mode) + if mode == True: + self.TextWindow.setFocus() + + def showMsg(self, msg, displayPromptAfterMsg = False): + self.TextWindow.showMsg(msg, displayPromptAfterMsg) + + def showErr(self, err): + self.TextWindow.showErr(err) + + def showInputMsg(self, inputMsg = None, inputType = QadInputTypeEnum.COMMAND, \ + default = None, keyWords = "", inputMode = QadInputModeEnum.NONE): + self.TextWindow.showInputMsg(inputMsg, inputType, default, keyWords, inputMode) + + + # ============================================================================ + # INIZIO - funzioni per comandi + # ============================================================================ + + def clearCurrentObjsSelection(self): + # pulisco le entità selezionate e i grip points correnti + self.tool.clearEntitySet() + self.clearEntityGripPoints() + + def clearEntityGripPoints(self): + # pulisco i grip points correnti + self.tool.clearEntityGripPoints() + + def runCommand(self, command, param = None): + self.QadCommands.run(command, param) + + def runMacro(self, args): + self.QadCommands.runMacro(args) + + def continueCommandFromMapTool(self): + self.QadCommands.continueCommandFromMapTool() + + def continueCommandFromTextWindow(self, msg): + self.QadCommands.continueCommandFromTextWindow(msg) + + def abortCommand(self): + self.QadCommands.abortCommand() + + def isValidCommand(self, command): + return self.QadCommands.isValidCommand(command) + + def getCommandNames(self): + return self.QadCommands.getCommandNames() + + def getCommandObj(self, cmdName): + return self.QadCommands.getCommandObj(cmdName) + + def getMoreUsedCmd(self, filter): + return self.QadCommands.getMoreUsedCmd(filter) + + def isValidEnvVariable(self, variable): + return self.QadCommands.isValidEnvVariable(variable) + + def forceCommandMapToolSnapTypeOnce(self, snapType, snapParams = None): + self.QadCommands.forceCommandMapToolSnapTypeOnce(snapType, snapParams) + + def forceCommandMapToolM2P(self): + self.QadCommands.forceCommandMapToolM2P() + + def refreshCommandMapToolSnapType(self): + self.QadCommands.refreshCommandMapToolSnapType() + + def refreshCommandMapToolAutoSnap(self): + self.QadCommands.refreshCommandMapToolAutoSnap() + + def refreshCommandMapToolDynamicInput(self): + self.QadCommands.refreshCommandMapToolDynamicInput() + + def getCurrenPointFromCommandMapTool(self): + return self.QadCommands.getCurrenPointFromCommandMapTool() + + def getCurrenPointFromCommandMapTool(self): + return self.QadCommands.getCurrenPointFromCommandMapTool() + + def toggleOsMode(self): + value = QadVariables.get(QadMsg.translate("Environment variables", "OSMODE")) + if value & QadSnapTypeEnum.DISABLE: + value = value - QadSnapTypeEnum.DISABLE + msg = QadMsg.translate("QAD", "") + else: + value = value + QadSnapTypeEnum.DISABLE + msg = QadMsg.translate("QAD", "") + + QadVariables.set(QadMsg.translate("Environment variables", "OSMODE"), value) + QadVariables.save() + self.showMsg(msg, True) + self.QadCommands.refreshCommandMapToolSnapType() + + def toggleOrthoMode(self): + value = QadVariables.get(QadMsg.translate("Environment variables", "ORTHOMODE")) + if value == 0: + value = 1 + autosnap = QadVariables.get(QadMsg.translate("Environment variables", "AUTOSNAP")) + if (autosnap & QadAUTOSNAPEnum.POLAR_TRACKING) == True: + QadVariables.set(QadMsg.translate("Environment variables", "AUTOSNAP"), autosnap - QadAUTOSNAPEnum.POLAR_TRACKING) # disattivo la modalità polare + msg = QadMsg.translate("QAD", "") + else: + value = 0 + msg = QadMsg.translate("QAD", "") + + QadVariables.set(QadMsg.translate("Environment variables", "ORTHOMODE"), value) + QadVariables.save() + self.showMsg(msg, True) + self.QadCommands.refreshCommandMapToolOrthoMode() + + + def togglePolarMode(self): + value = QadVariables.get(QadMsg.translate("Environment variables", "AUTOSNAP")) + if (value & QadAUTOSNAPEnum.POLAR_TRACKING) == False: + value = value + QadAUTOSNAPEnum.POLAR_TRACKING + QadVariables.set(QadMsg.translate("Environment variables", "ORTHOMODE"), 0) # disattivo la modalità orto + msg = QadMsg.translate("QAD", "") + else: + value = value - QadAUTOSNAPEnum.POLAR_TRACKING + msg = QadMsg.translate("QAD", "") + + QadVariables.set(QadMsg.translate("Environment variables", "AUTOSNAP"), value) + QadVariables.save() + self.showMsg(msg, True) + self.refreshCommandMapToolAutoSnap() + + + def toggleObjectSnapTracking(self): + value = QadVariables.get(QadMsg.translate("Environment variables", "AUTOSNAP")) + if (value & QadAUTOSNAPEnum.OBJ_SNAP_TRACKING) == False: + value = value + QadAUTOSNAPEnum.OBJ_SNAP_TRACKING + msg = QadMsg.translate("QAD", "") + else: + value = value - QadAUTOSNAPEnum.OBJ_SNAP_TRACKING + msg = QadMsg.translate("QAD", "") + + QadVariables.set(QadMsg.translate("Environment variables", "AUTOSNAP"), value) + QadVariables.save() + self.showMsg(msg, True) + self.refreshCommandMapToolAutoSnap() + + + def toggleDynamicInput(self): + value = QadVariables.get(QadMsg.translate("Environment variables", "DYNMODE")) + value = -value + if value > 0: + msg = QadMsg.translate("QAD", "") + else: + msg = QadMsg.translate("QAD", "") + + QadVariables.set(QadMsg.translate("Environment variables", "DYNMODE"), value) + QadVariables.save() + self.showMsg(msg, True) + self.refreshCommandMapToolDynamicInput() + + + def getCurrMsgFromTxtWindow(self): + return self.TextWindow.getCurrMsg() + + def showEvaluateMsg(self, msg = None): + self.TextWindow.showEvaluateMsg(msg) + + + # ============================================================================ + # funzioni per l'avvio di un comando + # ============================================================================ + + + def runCommandAbortingTheCurrent(self, cmdName): + self.mainAction.setChecked(True) + self.canvas.setFocus() + self.abortCommand() + self.showEvaluateMsg(cmdName) + + + def runMacroAbortingTheCurrent(self, args): + self.mainAction.setChecked(True) + self.canvas.setFocus() + self.abortCommand() + self.runMacro(args) + + + def runIDCommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "ID")) + + def runSETVARCommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "SETVAR")) + + def runPLINECommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "PLINE")) + + def runSETCURRLAYERBYGRAPHCommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "SETCURRLAYERBYGRAPH")) + + def runSETCURRUPDATEABLELAYERBYGRAPHCommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "SETCURRUPDATEABLELAYERBYGRAPH")) + + def runARCCommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "ARC")) + def runARCBY3POINTSCommand(self): # MACRO + # nome comando + argomenti + args = [QadMsg.translate("Command_list", "ARC"), None, None, None] + self.runMacroAbortingTheCurrent(args) + def runARC_BY_START_CENTER_END_Command(self): # MACRO + # nome comando + argomenti + args = [QadMsg.translate("Command_list", "ARC"), \ + None, \ + QadMsg.translate("Command_ARC", "Center"), \ + None, + None] + self.runMacroAbortingTheCurrent(args) + def runARC_BY_START_CENTER_ANGLE_Command(self): # MACRO + # nome comando + argomenti + args = [QadMsg.translate("Command_list", "ARC"), \ + None, \ + QadMsg.translate("Command_ARC", "Center"), \ + None, \ + QadMsg.translate("Command_ARC", "Angle"), \ + None] + self.runMacroAbortingTheCurrent(args) + def runARC_BY_START_CENTER_LENGTH_Command(self): # MACRO + # nome comando + argomenti + args = [QadMsg.translate("Command_list", "ARC"), \ + None, \ + QadMsg.translate("Command_ARC", "Center"), \ + None, \ + QadMsg.translate("Command_ARC", "chord Length"), \ + None] + self.runMacroAbortingTheCurrent(args) + def runARC_BY_START_END_ANGLE_Command(self): # MACRO + # nome comando + argomenti + args = [QadMsg.translate("Command_list", "ARC"), \ + None, \ + QadMsg.translate("Command_ARC", "End"), \ + None, \ + QadMsg.translate("Command_ARC", "Angle"), \ + None] + self.runMacroAbortingTheCurrent(args) + def runARC_BY_START_END_TAN_Command(self): # MACRO + # nome comando + argomenti + args = [QadMsg.translate("Command_list", "ARC"), \ + None, \ + QadMsg.translate("Command_ARC", "End"), \ + None, \ + QadMsg.translate("Command_ARC", "Direction"), \ + None] + self.runMacroAbortingTheCurrent(args) + def runARC_BY_START_END_RADIUS_Command(self): # MACRO + # nome comando + argomenti + args = [QadMsg.translate("Command_list", "ARC"), \ + None, \ + QadMsg.translate("Command_ARC", "End"), \ + None, \ + QadMsg.translate("Command_ARC", "Radius"), \ + None] + self.runMacroAbortingTheCurrent(args) + def runARC_BY_CENTER_START_END_Command(self): # MACRO + # nome comando + argomenti + args = [QadMsg.translate("Command_list", "ARC"), \ + QadMsg.translate("Command_ARC", "Center"), \ + None, \ + None, \ + None] + self.runMacroAbortingTheCurrent(args) + def runARC_BY_CENTER_START_ANGLE_Command(self): # MACRO + # nome comando + argomenti + args = [QadMsg.translate("Command_list", "ARC"), \ + QadMsg.translate("Command_ARC", "Center"), \ + None, \ + None, \ + QadMsg.translate("Command_ARC", "Angle"), \ + None] + self.runMacroAbortingTheCurrent(args) + def runARC_BY_CENTER_START_LENGTH_Command(self): # MACRO + # nome comando + argomenti + args = [QadMsg.translate("Command_list", "ARC"), \ + QadMsg.translate("Command_ARC", "Center"), \ + None, \ + None, \ + QadMsg.translate("Command_ARC", "chord Length"), \ + None] + self.runMacroAbortingTheCurrent(args) + + def runARRAYCommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "ARRAY")) + def runARRAYRECTCommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "ARRAYRECT")) + def runARRAYPATHCommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "ARRAYPATH")) + def runARRAYPOLARCommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "ARRAYPOLAR")) + + def runBREAK_BY_1_POINT_Command(self): # MACRO + # nome comando + argomenti + args = [QadMsg.translate("Command_list", "BREAK"), \ + None, \ + QadMsg.translate("Command_BREAK", "First point"), \ + None, \ + "@"] + self.runMacroAbortingTheCurrent(args) + + def runCIRCLECommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "CIRCLE")) + def runCIRCLE_BY_CENTER_RADIUS_Command(self): # MACRO + # nome comando + argomenti + args = [QadMsg.translate("Command_list", "CIRCLE"), \ + None, \ + None] + self.runMacroAbortingTheCurrent(args) + def runCIRCLE_BY_CENTER_DIAMETER_Command(self): # MACRO + # nome comando + argomenti + args = [QadMsg.translate("Command_list", "CIRCLE"), \ + None, \ + QadMsg.translate("Command_CIRCLE", "Diameter"), \ + None] + self.runMacroAbortingTheCurrent(args) + def runCIRCLE_BY_2POINTS_Command(self): # MACRO + # nome comando + argomenti + args = [QadMsg.translate("Command_list", "CIRCLE"), \ + QadMsg.translate("Command_CIRCLE", "2POints"), \ + None, \ + None] + self.runMacroAbortingTheCurrent(args) + def runCIRCLE_BY_3POINTS_Command(self): # MACRO + # nome comando + argomenti + args = [QadMsg.translate("Command_list", "CIRCLE"), \ + QadMsg.translate("Command_CIRCLE", "3Points"), \ + None, \ + None, \ + None] + self.runMacroAbortingTheCurrent(args) + def runCIRCLE_BY_2TANS_RADIUS_Command(self): # MACRO + # nome comando + argomenti + args = [QadMsg.translate("Command_list", "CIRCLE"), \ + QadMsg.translate("Command_CIRCLE", "Ttr (tangent tangent radius)"), \ + None, \ + None, \ + None] + self.runMacroAbortingTheCurrent(args) + def runCIRCLE_BY_3TANS_Command(self): # MACRO + # nome comando + argomenti + args = [QadMsg.translate("Command_list", "CIRCLE"), \ + QadMsg.translate("Command_CIRCLE", "3Points"), \ + QadMsg.translate("Snap", "TAN"), \ + QadMsg.translate("Snap", "TAN"), \ + QadMsg.translate("Snap", "TAN")] + self.runMacroAbortingTheCurrent(args) + + def runDSETTINGSCommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "DSETTINGS")) + + def runELLIPSECommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "ELLIPSE")) + def runELLIPSE_BY_CENTER_2_POINTS_Command(self): # MACRO + # nome comando + argomenti + args = [QadMsg.translate("Command_list", "ELLIPSE"), \ + QadMsg.translate("Command_ELLIPSE", "Center")] + self.runMacroAbortingTheCurrent(args) + def runELLIPTICAL_ARC_Command(self): # MACRO + # nome comando + argomenti + args = [QadMsg.translate("Command_list", "ELLIPSE"), \ + QadMsg.translate("Command_ELLIPSE", "Arc")] + self.runMacroAbortingTheCurrent(args) + def runELLIPSE_BY_FOCI_Command(self): # MACRO + # nome comando + argomenti + args = [QadMsg.translate("Command_list", "ELLIPSE"), \ + QadMsg.translate("Command_ELLIPSE", "Foci")] + self.runMacroAbortingTheCurrent(args) + + def runLINECommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "LINE")) + + def runERASECommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "ERASE")) + + def runMPOLYGONCommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "MPOLYGON")) + + def runMBUFFERCommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "MBUFFER")) + + def runROTATECommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "ROTATE")) + + def runMOVECommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "MOVE")) + + def runSCALECommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "SCALE")) + + def runCOPYCommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "COPY")) + + def runOFFSETCommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "OFFSET")) + + def runEXTENDCommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "EXTEND")) + + def runTRIMCommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "TRIM")) + + def runRECTANGLECommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "RECTANGLE")) + + def runMIRRORCommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "MIRROR")) + + def runUNDOCommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "UNDO")) + def runU_Command(self): # MACRO + # nome comando + argomenti + args = [QadMsg.translate("Command_list", "UNDO"), \ + QadMsg.translate("Command_UNDO", "1")] + self.runMacroAbortingTheCurrent(args) + + def runREDOCommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "REDO")) + + def runINSERTCommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "INSERT")) + + def runTEXTCommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "TEXT")) + + def runSTRETCHCommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "STRETCH")) + + def runBREAKCommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "BREAK")) + + def runPEDITCommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "PEDIT")) + + def runMAPMPEDITCommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "MAPMPEDIT")) + + def runFILLETCommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "FILLET")) + + def runJOINCommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "JOIN")) + + def runDISJOINCommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "DISJOIN")) + + def runPOLYGONCommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "POLYGON")) + + def runDIMLINEARCommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "DIMLINEAR")) + + def runDIMALIGNEDCommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "DIMALIGNED")) + + def runDIMARCCommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "DIMARC")) + + def runDIMRADIUSCommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "DIMRADIUS")) + + def runDIMSTYLECommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "DIMSTYLE")) + + def runDIVIDECommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "DIVIDE")) + + def runMEASURECommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "MEASURE")) + + def runHELPCommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "HELP")) + + def runSUPPORTERSCommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "SUPPORTERS")) + + def runLENGTHENCommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "LENGTHEN")) + + def runOPTIONSCommand(self): + self.runCommandAbortingTheCurrent(QadMsg.translate("Command_list", "OPTIONS")) + + + def updateCmdsHistory(self, command): + # aggiorna la lista della storia degli ultimi comandi usati + # Se command é una lista di comandi + if isinstance(command, list): + for line in command: + self.updateCmdsHistory(line) + elif not command == "": + upperCmd = command.upper() + # cerco se il comando è già presente in lista + # se era in lista lo rimuovo + try: + ndx = self.cmdsHistory.index(upperCmd) + del self.cmdsHistory[ndx] + except ValueError: + pass + + # aggiungo il comando in fondo alla lista + self.cmdsHistory.append(upperCmd) + + cmdInputHistoryMax = QadVariables.get(QadMsg.translate("Environment variables", "CMDINPUTHISTORYMAX")) + if len(self.cmdsHistory) > cmdInputHistoryMax: + del self.cmdsHistory[0] + + + def updatePtsHistory(self, pt): + # aggiorna la lista della storia degli ultimi punti usati + # Se pt é una lista di punti + if isinstance(pt, list): + for line in pt: + self.updatePtsHistory(line) + elif pt is not None: + # cerco se il punto è già presente in lista + # se era in lista lo rimuovo + try: + ndx = self.ptsHistory.index(pt) + del self.ptsHistory[ndx] + except ValueError: + pass + + # aggiungo il punto in fondo alla lista + self.ptsHistory.append(pt) + + cmdInputHistoryMax = QadVariables.get(QadMsg.translate("Environment variables", "CMDINPUTHISTORYMAX")) + if len(self.ptsHistory) > cmdInputHistoryMax: + del self.ptsHistory[0] + + + def shortCutManagement(self, e): + # gestisce i tasti scorciatoia + # ritorna True se la funzione li ha gestiti altrimenti ritorna False e quindi l'evento keyPressed va gestito altrove + + # Se é stato premuto il tasto CTRL (o META) + 9 oppure il tasto F2 + if (((e.modifiers() & Qt.ControlModifier) or (e.modifiers() & Qt.MetaModifier)) and e.key() == Qt.Key_9) or \ + e.key() == Qt.Key_F2: + # Accendo o spengo la finestra di testo + if self.TextWindow is not None: + self.TextWindow.toggleShow() + return True + + # Se é stato premuto il tasto F3 + if e.key() == Qt.Key_F3: + # Attivo o disattivo lo snap + self.toggleOsMode() + return True + + # Se é stato premuto il tasto F8 + if e.key() == Qt.Key_F8: + # Attivo o disattivo la modalità ortogonale + self.toggleOrthoMode() + return True + + # Se é stato premuto il tasto F12 + if e.key() == Qt.Key_F12: + # Attivo o disattivo l'input dinamico + self.toggleDynamicInput() + return True + + # Se é stato premuto il tasto ESC + if e.key() == Qt.Key_Escape: + # interrompo il comando corrente + self.abortCommand() + self.clearCurrentObjsSelection() + self.TextWindow.showCmdSuggestWindow(False) + self.getCurrentMapTool().getDynamicInput().abort() + + return True + + # Se é stato premuto il tasto F10 + if e.key() == Qt.Key_F10: + # Attivo o disattivo il modo polare + self.togglePolarMode() + return True + + # Se é stato premuto il tasto F11 + if e.key() == Qt.Key_F11: + # Attivo o disattivo il puntamento snap ad oggetto + self.toggleObjectSnapTracking() + return True + + return False + + diff --git a/qad.qrc b/qad.qrc index 4916a94d..90390086 100644 --- a/qad.qrc +++ b/qad.qrc @@ -1,72 +1,98 @@ - - - icons/arc.png - icons/arcBy3Points.png - icons/arcByCenterStartAngle.png - icons/arcByCenterStartEnd.png - icons/arcByCenterStartLength.png - icons/arcByStartCenterAngle.png - icons/arcByStartCenterEndPoints.png - icons/arcByStartCenterLength.png - icons/arcByStartEndAngle.png - icons/arcByStartEndRadius.png - icons/arcByStartEndTan.png - icons/break.png - icons/circle.png - icons/circleBy2Points.png - icons/circleBy2TansRadius.png - icons/circleBy3Points.png - icons/circleBy3Tans.png - icons/circleByCenterDiameter.png - icons/circleByCenterRadius.png - icons/copy.png - icons/copyEnt.png - icons/dimAligned.png - icons/dimLinear.png - icons/dsettings.png - icons/erase.png - icons/extend.png - icons/fillet.png - icons/insert.png - icons/line.png - icons/mbuffer.png - icons/mirror.png - icons/move.png - icons/mpolygon.png - icons/offset.png - icons/osnap_cen.png - icons/osnap_disable.png - icons/osnap_end.png - icons/osnap_endLine.png - icons/osnap_ext.png - icons/osnap_extInt.png - icons/osnap_int.png - icons/osnap_mid.png - icons/osnap_nea.png - icons/osnap_nod.png - icons/osnap_par.png - icons/osnap_per.png - icons/osnap_pr.png - icons/osnap_qua.png - icons/osnap_tan.png - icons/pedit.png - icons/pline.png - icons/polygon.png - icons/qad.png - icons/rectangle.png - icons/redo.png - icons/rotate.png - icons/setcurrlayerbygraph.png - icons/setcurrupdateablelayerbygraph.png - icons/scale.png - icons/stretch.png - icons/text.png - icons/trim.png - icons/u.png - icons/undo.png - icons/dimStyle.png - icons/help.png - icons/lengthen.png - icons/variable.png - - + + + icons/arc.svg + icons/arcBy3Points.svg + icons/arcByCenterStartAngle.svg + icons/arcByCenterStartEnd.svg + icons/arcByCenterStartLength.svg + icons/arcByStartCenterAngle.svg + icons/arcByStartCenterEndPoints.svg + icons/arcByStartCenterLength.svg + icons/arcByStartEndAngle.svg + icons/arcByStartEndRadius.svg + icons/arcByStartEndTan.svg + icons/arrayRect.svg + icons/arrayPath.svg + icons/arrayPolar.svg + icons/break.svg + icons/breakBy1Point.svg + icons/circle.svg + icons/circleBy2Points.svg + icons/circleBy2TansRadius.svg + icons/circleBy3Points.svg + icons/circleBy3Tans.svg + icons/circleByCenterDiameter.svg + icons/circleByCenterRadius.svg + icons/copy.svg + icons/copyEnt.svg + icons/dimAligned.svg + icons/dimArc.svg + icons/dimLinear.svg + icons/dimRadius.svg + icons/dimStyle.svg + icons/divide.svg + icons/disjoin.svg + icons/dsettings.svg + icons/erase.svg + icons/extend.svg + icons/fillet.svg + icons/help.svg + icons/id.svg + icons/insert.svg + icons/join.svg + icons/lengthen.svg + icons/line.svg + icons/mapmpedit.svg + icons/mbuffer.svg + icons/measure.svg + icons/mirror.svg + icons/move.svg + icons/mpolygon.svg + icons/offset.svg + icons/options.svg + icons/osnap_cen.svg + icons/osnap_disable.svg + icons/osnap_end.svg + icons/osnap_endLine.svg + icons/osnap_ext.svg + icons/osnap_extInt.svg + icons/osnap_int.svg + icons/osnap_mid.svg + icons/osnap_mid2p.svg + icons/osnap_nea.svg + icons/osnap_nod.svg + icons/osnap_par.svg + icons/osnap_per.svg + icons/osnap_pr.svg + icons/osnap_qua.svg + icons/osnap_tan.svg + icons/pedit.svg + icons/pline.svg + icons/polygon.svg + icons/qad.svg + icons/rectangle.svg + icons/redo.svg + icons/rotate.svg + icons/setcurrlayerbygraph.svg + icons/setcurrupdateablelayerbygraph.svg + icons/scale.svg + icons/stretch.svg + icons/text.svg + icons/trim.svg + icons/u.svg + icons/undo.svg + icons/variable.svg + icons/locked.svg + icons/down_key.svg + icons/ellipse.svg + icons/ellipseArc.svg + icons/ellipseBy4Points.svg + icons/ellipseByAxis1Point.svg + icons/ellipseByCenter2Points.svg + icons/ellipseByCenter3Points.svg + icons/ellipseByExtent.svg + icons/ellipseByFociPoint.svg + icons/ellipseFromCenter.svg + icons/supporters.svg + + diff --git a/qad.ui b/qad.ui index cc0a5b4e..3da48ab8 100644 --- a/qad.ui +++ b/qad.ui @@ -1,67 +1,67 @@ - - QAD - - - - 0 - 0 - 400 - 300 - - - - QAD - - - - - 30 - 240 - 341 - 32 - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - buttonBox - accepted() - QAD - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - QAD - reject() - - - 316 - 260 - - - 286 - 274 - - - - - + + QAD + + + + 0 + 0 + 400 + 300 + + + + QAD + + + + + 30 + 240 + 341 + 32 + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + buttonBox + accepted() + QAD + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + QAD + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/qad_arc.py b/qad_arc.py index 6e5edb19..4e202dc5 100644 --- a/qad_arc.py +++ b/qad_arc.py @@ -1,751 +1,1194 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - classe per la gestione degli archi - - ------------------- - begin : 2013-05-22 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -from qgis.gui import * -import math - - -import qad_utils -from qad_circle import * -from qad_variables import * - - -#=============================================================================== -# QadArc arc class -#=============================================================================== -class QadArc(): - - def __init__(self, arc = None): - if arc is not None: - self.set(arc.center, arc.radius, arc.startAngle, arc.endAngle) - else: - self.center = None - self.radius = None - self.startAngle = None - self.endAngle = None - - def whatIs(self): - return "ARC" - - def set(self, center, radius, startAngle, endAngle): - self.center = QgsPoint(center) - self.radius = radius - self.startAngle = startAngle - self.endAngle = endAngle - - def transform(self, coordTransform): - """Transform this geometry as described by CoordinateTranasform ct.""" - self.center = coordTransform.transform(self.center) - - def transformFromCRSToCRS(self, sourceCRS, destCRS): - """Transform this geometry as described by CRS.""" - if (sourceCRS is not None) and (destCRS is not None) and sourceCRS != destCRS: - coordTransform = QgsCoordinateTransform(sourceCRS, destCRS) # trasformo le coord - self.center = coordTransform.transform(self.center) - - def __eq__(self, arc): - """self == other""" - if self.center != arc.center or self.radius != arc.radius or \ - self.startAngle != arc.startAngle or self.endAngle != arc.endAngle: - return False - else: - return True - - def __ne__(self, arc): - """self != other""" - if self.center != arc.center or self.radius != arc.radius or \ - self.startAngle != arc.startAngle or self.endAngle != arc.endAngle: - return True - else: - return False - - def totalAngle(self): - if self.startAngle < self.endAngle: - return self.endAngle - self.startAngle - else: - return (2 * math.pi - self.startAngle) + self.endAngle - - def length(self): - return self.radius * self.totalAngle() - - def getStartPt(self): - return qad_utils.getPolarPointByPtAngle(self.center, - self.startAngle, - self.radius) - def setStartAngleByPt(self, pt): - # da usare per modificare un arco già efinito - angle = qad_utils.getAngleBy2Pts(self.center, pt) - if angle == self.endAngle: - return False - else: - self.startAngle = angle - return True - - def getEndPt(self): - return qad_utils.getPolarPointByPtAngle(self.center, - self.endAngle, - self.radius) - def setEndAngleByPt(self, pt): - # da usare per modificare un arco già definito - angle = qad_utils.getAngleBy2Pts(self.center, pt) - if angle == self.startAngle: - return False - else: - self.endAngle = angle - return True - - def isPtOnArc(self, point): - dist = qad_utils.getDistance(self.center, point) - if qad_utils.doubleNear(self.radius, dist): - angle = qad_utils.getAngleBy2Pts(self.center, point) - return qad_utils.isAngleBetweenAngles(self.startAngle, self.endAngle, angle) - else: - return False - - def inverse(self): - dummy = self.endAngle - self.endAngle = self.startAngle - self.startAngle = dummy - - def getMiddlePt(self): - halfAngle = self.totalAngle() / 2 - return qad_utils.getPolarPointByPtAngle(self.center, - self.startAngle + halfAngle, - self.radius) - - def getQuadrantPoints(self): - result = [] - - angle = 0 - if qad_utils.isAngleBetweenAngles(self.startAngle, self.endAngle, angle) == True: - result.append(QgsPoint(self.center.x() + self.radius, self.center.y())) - - angle = math.pi / 2 - if qad_utils.isAngleBetweenAngles(self.startAngle, self.endAngle, angle) == True: - result.append(QgsPoint(self.center.x(), self.center.y() + self.radius)) - - angle = math.pi - if qad_utils.isAngleBetweenAngles(self.startAngle, self.endAngle, angle) == True: - result.append(QgsPoint(self.center.x() - self.radius, self.center.y())) - - angle = math.pi * 3 / 2 - if qad_utils.isAngleBetweenAngles(self.startAngle, self.endAngle, angle) == True: - result.append(QgsPoint(self.center.x(), self.center.y() - self.radius)) - - return result - - - #============================================================================ - # getTanPoints - #============================================================================ - def getTanPoints(self, point): - result = [] - - circle = QadCircle() - circle.set(self.center, self.radius) - points = circle.getTanPoints(point) - tot = len(points) - for p in points: - angle = qad_utils.getAngleBy2Pts(self.center, p) - if qad_utils.isAngleBetweenAngles(self.startAngle, self.endAngle, angle) == True: - result.append(p) - - return result - - - #============================================================================ - # getTanDirectionOnPt - #============================================================================ - def getTanDirectionOnPt(self, pt): - angle = qad_utils.getAngleBy2Pts(self.center, pt) - return qad_utils.normalizeAngle(angle + math.pi / 2) - - - #============================================================================ - # getTanDirectionOnStartPt - #============================================================================ - def getTanDirectionOnStartPt(self): - return self.getTanDirectionOnPt(self.getStartPt()) - - - #============================================================================ - # getTanDirectionOnEndPt - #============================================================================ - def getTanDirectionOnEndPt(self): - return self.getTanDirectionOnPt(self.getEndPt()) - - - #============================================================================ - # getPerpendicularPoints - #============================================================================ - def getPerpendicularPoints(self, point): - result = [] - angle = qad_utils.getAngleBy2Pts(self.center, point) - if qad_utils.isAngleBetweenAngles(self.startAngle, self.endAngle, angle) == True: - result.append( qad_utils.getPolarPointByPtAngle(self.center, - angle, - self.radius)) - - angle = angle + math.pi - if qad_utils.isAngleBetweenAngles(self.startAngle, self.endAngle, angle) == True: - result.append( qad_utils.getPolarPointByPtAngle(self.center, - angle, - self.radius)) - - return result - - #============================================================================ - # getIntersectionPointsWithInfinityLine - #============================================================================ - def getIntersectionPointsWithInfinityLine(self, p1, p2): - result = [] - circle = QadCircle() - circle.set(self.center, self.radius) - intPtList = circle.getIntersectionPointsWithInfinityLine(p1, p2) - for intPt in intPtList: - if self.isPtOnArc(intPt): - result.append(intPt) - return result - - - #============================================================================ - # getIntersectionPointsWithSegment - #============================================================================ - def getIntersectionPointsWithSegment(self, p1, p2): - result = [] - intPtList = self.getIntersectionPointsWithInfinityLine(p1, p2) - for intPt in intPtList: - if qad_utils.isPtOnSegment(p1, p2, intPt): - result.append(intPt) - return result - - - #============================================================================ - # getIntersectionPointsWithCircle - #============================================================================ - def getIntersectionPointsWithCircle(self, circle): - result = [] - circle1 = QadCircle() - circle1.set(self.center, self.radius) - intPtList = circle1.getIntersectionPointsWithCircle(circle) - for intPt in intPtList: - if self.isPtOnArc(intPt): - result.append(intPt) - return result - - - #============================================================================ - # getIntersectionPointsWithArc - #============================================================================ - def getIntersectionPointsWithArc(self, arc): - result = [] - circle = QadCircle() - circle.set(arc.center, arc.radius) - intPtList = self.getIntersectionPointsWithCircle(circle) - for intPt in intPtList: - if arc.isPtOnArc(intPt): - result.append(intPt) - return result - - - #============================================================================ - # asPolyline - #============================================================================ - def asPolyline(self, tolerance2ApproxCurve = None, atLeastNSegment = None): - """ - ritorna una lista di punti che definisce l'arco - """ - if tolerance2ApproxCurve is None: - tolerance = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) - else: - tolerance = tolerance2ApproxCurve - - if atLeastNSegment is None: - _atLeastNSegment = QadVariables.get(QadMsg.translate("Environment variables", "ARCMINSEGMENTQTY"), 12) - else: - _atLeastNSegment = atLeastNSegment - - # Calcolo la lunghezza del segmento con pitagora - dummy = self.radius - tolerance - if dummy <= 0: # se la tolleranza é troppo bassa rispetto al raggio - SegmentLen = self.radius - else: - dummy = (self.radius * self.radius) - (dummy * dummy) - SegmentLen = math.sqrt(dummy) # radice quadrata - SegmentLen = SegmentLen * 2 - - if SegmentLen == 0: # se la tolleranza é troppo bassa la lunghezza del segmento diventa zero - return None - - # calcolo quanti segmenti ci vogliono (non meno di _atLeastNSegment) - SegmentTot = math.ceil(self.length() / SegmentLen) - if SegmentTot < _atLeastNSegment: - SegmentTot = _atLeastNSegment - - points = [] - # primo punto - pt = qad_utils.getPolarPointByPtAngle(self.center, self.startAngle, self.radius) - points.append(pt) - - i = 1 - angle = self.startAngle - offSetAngle = self.totalAngle() / SegmentTot - while i < SegmentTot: - angle = angle + offSetAngle - pt = qad_utils.getPolarPointByPtAngle(self.center, angle, self.radius) - points.append(pt) - i = i + 1 - - # ultimo punto - pt = qad_utils.getPolarPointByPtAngle(self.center, self.endAngle, self.radius) - points.append(pt) - return points - - - #============================================================================ - # fromStartSecondEndPts - #============================================================================ - def fromStartSecondEndPts(self, startPt, secondPt, endPt): - """ - setta le caratteristiche dell'arco attraverso: - punto iniziale - secondo punto (intermedio) - punto finale - """ - points = [startPt, secondPt, endPt] - # lista di punti, parte dal punto 0, almeno 2 segmenti - if self.fromPolyline(points, 0, 2) is None: - return False - else: - return True - - - #============================================================================ - # fromStartCenterEndPts - #============================================================================ - def fromStartCenterEndPts(self, startPt, centerPt, endPt): - """ - setta le caratteristiche dell'arco attraverso: - punto iniziale - centro - punto finale - """ - if startPt == centerPt or startPt == endPt or endPt == centerPt: - return False - - self.center = centerPt - self.radius = qad_utils.getDistance(centerPt, startPt) - self.startAngle = qad_utils.getAngleBy2Pts(centerPt, startPt) - self.endAngle = qad_utils.getAngleBy2Pts(centerPt, endPt) - return True - - - #============================================================================ - # fromStartCenterPtsAngle - #============================================================================ - def fromStartCenterPtsAngle(self, startPt, centerPt, angle): - """ - setta le caratteristiche dell'arco attraverso: - punto iniziale - centro - angolo inscritto - """ - if startPt == centerPt or angle == 0: - return False - - self.center = centerPt - self.radius = qad_utils.getDistance(centerPt, startPt) - self.startAngle = qad_utils.getAngleBy2Pts(centerPt, startPt) - self.endAngle = self.startAngle + angle - if self.endAngle > math.pi * 2: - self.endAngle = self.endAngle % (math.pi * 2) # modulo - return True - - - #============================================================================ - # fromStartCenterPtsChord - #============================================================================ - def fromStartCenterPtsChord(self, startPt, centerPt, chord): - """ - setta le caratteristiche dell'arco attraverso: - punto iniziale - centro - lunghezza dela corda tra punto iniziale e finale - """ - if startPt == centerPt or chord == 0: - return False - - self.center = centerPt - self.radius = qad_utils.getDistance(centerPt, startPt) - if chord > 2 * self.radius: - return False - self.startAngle = qad_utils.getAngleBy2Pts(centerPt, startPt) - # Teorema della corda - angle = 2 * math.asin(chord / (2 * self.radius)) - self.endAngle = self.startAngle + angle - return True - - - #============================================================================ - # fromStartEndPtsAngle - #============================================================================ - def fromStartEndPtsAngle(self, startPt, endPt, angle): - """ - setta le caratteristiche dell'arco attraverso: - punto iniziale - punto finale - angolo inscritto - """ - if startPt == endPt or angle == 0: - return False - - chord = qad_utils.getDistance(startPt, endPt) - half_chord = chord / 2 - # Teorema della corda - self.radius = half_chord / math.sin(angle / 2) - - angleSegment = qad_utils.getAngleBy2Pts(startPt, endPt) - ptMiddle = qad_utils.getMiddlePoint(startPt, endPt) - - # Pitagora - distFromCenter = math.sqrt((self.radius * self.radius) - (half_chord * half_chord)) - if angle < math.pi: # se angolo < 180 gradi - # aggiungo 90 gradi per cercare il centro a sinistra del segmento - self.center = qad_utils.getPolarPointByPtAngle(ptMiddle, - angleSegment + (math.pi / 2), - distFromCenter) - else: - # sottraggo 90 gradi per cercare il centro a destra del segmento - self.center = qad_utils.getPolarPointByPtAngle(ptMiddle, - angleSegment - (math.pi / 2), - distFromCenter) - self.startAngle = qad_utils.getAngleBy2Pts(self.center, startPt) - self.endAngle = qad_utils.getAngleBy2Pts(self.center, endPt) - return True - - - #============================================================================ - # fromStartEndPtsTan - #============================================================================ - def fromStartEndPtsTan(self, startPt, endPt, tan): - """ - setta le caratteristiche dell'arco attraverso: - punto iniziale - punto finale - direzione della tangente sul punto iniziale - """ - if startPt == endPt: - return False - - angleSegment = qad_utils.getAngleBy2Pts(startPt, endPt) - if tan == angleSegment or tan == angleSegment - math.pi: - return False - - chord = qad_utils.getDistance(startPt, endPt) - half_chord = chord / 2 - ptMiddle = qad_utils.getMiddlePoint(startPt, endPt) - - angle = tan + (math.pi / 2) - angle = angleSegment - angle - distFromCenter = math.tan(angle) * half_chord - self.center = qad_utils.getPolarPointByPtAngle(ptMiddle, - angleSegment - (math.pi / 2), - distFromCenter) - pt = qad_utils.getPolarPointByPtAngle(startPt, tan, chord) - - if qad_utils.leftOfLine(endPt, startPt, pt) < 0: - # arco si sviluppa a sinistra della tangente - self.startAngle = qad_utils.getAngleBy2Pts(self.center, startPt) - self.endAngle = qad_utils.getAngleBy2Pts(self.center, endPt) - else: - # arco si sviluppa a destra della tangente - self.startAngle = qad_utils.getAngleBy2Pts(self.center, endPt) - self.endAngle = qad_utils.getAngleBy2Pts(self.center, startPt) - - self.radius = qad_utils.getDistance(startPt, self.center) - return True - - - #============================================================================ - # fromStartEndPtsRadius - #============================================================================ - def fromStartEndPtsRadius(self, startPt, endPt, radius): - """ - setta le caratteristiche dell'arco attraverso: - punto iniziale - punto finale - raggio - """ - if startPt == endPt or radius <= 0: - return False - - chord = qad_utils.getDistance(startPt, endPt) - half_chord = chord / 2 - if radius < half_chord: - return False - - self.radius = radius - angleSegment = qad_utils.getAngleBy2Pts(startPt, endPt) - ptMiddle = qad_utils.getMiddlePoint(startPt, endPt) - - # Pitagora - distFromCenter = math.sqrt((self.radius * self.radius) - (half_chord * half_chord)) - # aggiungo 90 gradi - self.center = qad_utils.getPolarPointByPtAngle(ptMiddle, - angleSegment + (math.pi / 2), - distFromCenter) - self.startAngle = qad_utils.getAngleBy2Pts(self.center, startPt) - self.endAngle = qad_utils.getAngleBy2Pts(self.center, endPt) - return True - - - #============================================================================ - # fromStartPtAngleRadiusChordDirection - #============================================================================ - def fromStartPtAngleRadiusChordDirection(self, startPt, angle, radius, chordDirection): - """ - setta le caratteristiche dell'arco attraverso: - punto iniziale - angolo inscritto - raggio - direzione della corda - """ - if angle == 0 or angle == 2 * math.pi or radius <= 0: - return False - - a = chordDirection + (math.pi / 2) - (angle / 2) - self.radius = radius - self.center = qad_utils.getPolarPointByPtAngle(startPt, a, radius) - endPt = qad_utils.getPolarPointByPtAngle(self.center, a + math.pi + angle, radius) - - self.startAngle = qad_utils.getAngleBy2Pts(self.center, startPt) - self.endAngle = qad_utils.getAngleBy2Pts(self.center, endPt) - return True - - - #============================================================================ - # fromPolyline - #============================================================================ - def fromPolyline(self, points, startVertex, atLeastNSegment = None): - """ - setta le caratteristiche del primo arco incontrato nella lista di punti - partendo dalla posizione startVertex (0-indexed) - ritorna la posizione nella lista del punto iniziale e finale se é stato trovato un arco - altrimenti None - N.B. in punti NON devono essere in coordinate geografiche - """ - if atLeastNSegment is None: - _atLeastNSegment = QadVariables.get(QadMsg.translate("Environment variables", "ARCMINSEGMENTQTY"), 12) - else: - _atLeastNSegment = atLeastNSegment - - totPoints = len(points) - # perché sia un arco ci vogliono almeno _atLeastNSegment segmenti - if (totPoints - 1) - startVertex < _atLeastNSegment or _atLeastNSegment < 2: - return None - - # per problemi di approssimazione dei calcoli - epsilon = 1.e-4 # percentuale del raggio per ottenere max diff. di una distanza con il raggio - - InfinityLinePerpOnMiddle1 = None - InfinityLinePerpOnMiddle2 = None - - nSegment = 0 - i = startVertex - while i < totPoints - 1: - if InfinityLinePerpOnMiddle1 is None: - InfinityLinePerpOnMiddle1 = qad_utils.getInfinityLinePerpOnMiddle(points[i], points[i + 1]) - nStartVertex = i - nSegment = 1 - i = i + 1 - continue - elif InfinityLinePerpOnMiddle2 is None: - InfinityLinePerpOnMiddle2 = qad_utils.getInfinityLinePerpOnMiddle(points[i], points[i + 1]) - if InfinityLinePerpOnMiddle2 is None: - InfinityLinePerpOnMiddle1 = None - nSegment = 0 - else: - # calcolo il presunto centro con 2 segmenti - center = qad_utils.getIntersectionPointOn2InfinityLines(InfinityLinePerpOnMiddle1[0], \ - InfinityLinePerpOnMiddle1[1], \ - InfinityLinePerpOnMiddle2[0], \ - InfinityLinePerpOnMiddle2[1]) - if center is None: # linee parallele - InfinityLinePerpOnMiddle1 = InfinityLinePerpOnMiddle2 - InfinityLinePerpOnMiddle2 = None - nStartVertex = i - nSegment = 1 - else: - nSegment = nSegment + 1 - radius = qad_utils.getDistance(center, points[i + 1]) # calcolo il presunto raggio - maxDifference = radius * epsilon - - # calcolo il verso dell'arco e l'angolo dell'arco - # se un punto intermedio dell'arco è a sinistra del - # segmento che unisce i due punti allora il verso è antiorario - startClockWise = True if qad_utils.leftOfLine(points[i], points[i - 1], points[i + 1]) < 0 else False - angle = qad_utils.getAngleBy3Pts(points[i - 1], center, points[i + 1], startClockWise) - else: # e sono già stati valutati almeno 2 segmenti - # calcolo la distanza del punto dal presunto centro - dist = qad_utils.getDistance(center, points[i + 1]) - # calcolo il verso dell'arco e l'angolo - clockWise = True if qad_utils.leftOfLine(points[i], points[i - 1], points[i + 1]) < 0 else False - angle = angle + qad_utils.getAngleBy3Pts(points[i], center, points[i + 1], startClockWise) - - # se la distanza è così vicina a quella del raggio - # il verso dell'arco deve essere quello iniziale - # l'angolo dell'arco non può essere >= 360 gradi - if qad_utils.doubleNear(radius, dist, maxDifference) and \ - startClockWise == clockWise and \ - angle < 2 * math.pi: - nSegment = nSegment + 1 # anche questo segmento fa parte dell'arco - else: # questo segmento non fa parte del cerchio - # se sono stati trovati un numero sufficiente di segmenti successivi - if nSegment >= _atLeastNSegment: - # se é un angolo giro e il primo punto = ultimo punto allora points é un cerchio - if qad_utils.doubleNear(angle, 2 * math.pi) and points[0] == points[-1]: - return None - break - else: - i = i - 2 - InfinityLinePerpOnMiddle1 = None - InfinityLinePerpOnMiddle2 = None - - i = i + 1 - - # se sono stati trovati un numero sufficiente di segmenti successivi - if nSegment >= _atLeastNSegment: - nEndVertex = nStartVertex + nSegment - # se il punto iniziale e quello finale non coincidono é un arco - if points[nStartVertex] != points[nEndVertex]: - self.center = center - self.radius = radius - - # se il verso é orario - if startClockWise: - # inverto l'angolo iniziale con quello finale - self.endAngle = qad_utils.getAngleBy2Pts(center, points[nStartVertex]) - self.startAngle = qad_utils.getAngleBy2Pts(center, points[nEndVertex]) - else: - self.startAngle = qad_utils.getAngleBy2Pts(center, points[nStartVertex]) - self.endAngle = qad_utils.getAngleBy2Pts(center, points[nEndVertex]) - - return nStartVertex, nEndVertex - - return None - - -#=============================================================================== -# QadArcList lista di archi class -#=============================================================================== -class QadArcList(): - def __init__(self): - self.arcList = [] # lista di archi - self.startEndVerticesList = [] # lista degli estremi (posizioni dei vertici iniziali e finali) - - def clear(self): - del self.arcList[:] # svuoto la lista - del self.startEndVerticesList[:] # svuoto la lista - - - #============================================================================ - # fromPoints - #============================================================================ - def fromPoints(self, points, atLeastNSegment = None): - """ - setta la lista degli archi e degli estremi leggendo una sequenza di punti - ritorna il numero di archi trovati - """ - if atLeastNSegment is None: - _atLeastNSegment = QadVariables.get(QadMsg.translate("Environment variables", "ARCMINSEGMENTQTY"), 12) - else: - _atLeastNSegment = atLeastNSegment - - self.clear() - startVertex = 0 - arc = QadArc() - startEndVertices = arc.fromPolyline(points, startVertex, _atLeastNSegment) - while startEndVertices is not None: - _arc = QadArc(arc) # ne faccio una copia - self.arcList.append(_arc) - self.startEndVerticesList.append(startEndVertices) - startVertex = startEndVertices[1] # l'ultimo punto dell'arco - startEndVertices = arc.fromPolyline(points, startVertex, _atLeastNSegment) - - return len(self.arcList) - - - #============================================================================ - # fromGeom - #============================================================================ - def fromGeom(self, geom, atLeastNSegment = None): - """ - setta la lista degli archi e degli estremi leggendo una geometria - ritorna il numero di archi trovati - """ - if atLeastNSegment is None: - _atLeastNSegment = QadVariables.get(QadMsg.translate("Environment variables", "ARCMINSEGMENTQTY"), 12) - else: - _atLeastNSegment = atLeastNSegment - - self.clear() - arc = QadArc() - incremental = 0 - # riduco in polilinee - geoms = qad_utils.asPointOrPolyline(geom) - for g in geoms: - points = g.asPolyline() # vettore di punti - startVertex = 0 - startEndVertices = arc.fromPolyline(points, startVertex, _atLeastNSegment) - while startEndVertices is not None: - _arc = QadArc(arc) # ne faccio una copia - self.arcList.append(_arc) - self.startEndVerticesList.append([startEndVertices[0] + incremental, startEndVertices[1] + incremental]) - startVertex = startEndVertices[1] # l'ultimo punto dell'arco - startEndVertices = arc.fromPolyline(points, startVertex, _atLeastNSegment) - - incremental = len(points) - 1 - - return len(self.arcList) - - #============================================================================ - # fromGeom - #============================================================================ - def arcAt(self, afterVertex): - """ - cerca se esiste un arco al segmento il cui secondo vertice é - restituisce una lista con , - oppure None se arco non trovato - """ - i = 0 - for startEndVertices in self.startEndVerticesList: - if afterVertex > startEndVertices[0] and afterVertex <= startEndVertices[1]: - return self.arcList[i], startEndVertices - i = i + 1 - - return None +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + classe per la gestione degli archi + + ------------------- + begin : 2013-05-22 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from qgis.PyQt.QtCore import * +from qgis.PyQt.QtGui import * +from qgis.core import * +from qgis.gui import * +import math + + +from . import qad_utils +from .qad_circle import QadCircle +from .qad_variables import QadVariables +from .qad_msg import QadMsg + + +# =============================================================================== +# QadArc arc class +# =============================================================================== +class QadArc(QadCircle): + + def __init__(self, arc=None): + if arc is not None: + if arc.radius <= 0: return None + self.set(arc.center, arc.radius, arc.startAngle, arc.endAngle, arc.reversed) + else: + self.center = None + self.radius = None + self.startAngle = None # come l'arco per il cerchio + self.endAngle = None + # if reversed is True the versus of the arc is from endAngle to startAngle + self.reversed = False + + + def whatIs(self): + # obbligatoria + return "ARC" + + + # ============================================================================ + # isClosed + # ============================================================================ + def isClosed(self): + return False + + + def set(self, center, radius=None, startAngle=None, endAngle=None, reversed=False): + if isinstance(center, QadArc): + arc = center + return self.set(arc.center, arc.radius, arc.startAngle, arc.endAngle, arc.reversed) + + if radius <= 0: return None + self.center = QgsPointXY(center) + self.radius = radius + self.reversed = reversed + if self.setArc(startAngle, endAngle) == False: return None + return self + + + def setArc(self, startAngle, endAngle): + # set controllato degli angoli per inizializzare l'arco + _startAngle = qad_utils.normalizeAngle(startAngle) + _endAngle = qad_utils.normalizeAngle(endAngle) + if _startAngle == _endAngle: return False # cerchio completo + self.startAngle = _startAngle + self.endAngle = _endAngle + + + def __eq__(self, arc): + # obbligatoria + """self == other""" + if arc.whatIs() != "ARC": return False + if self.center != arc.center or self.radius != arc.radius or \ + self.startAngle != arc.startAngle or self.endAngle != arc.endAngle or self.reversed != arc.reversed: + return False + else: + return True + + + def __ne__(self, arc): + """self != other""" + return not self.__eq__(arc) + + + def equals(self, arc): + # uguali geometricamente (NON conta il verso) + return self.__eq__(arc) + + + def copy(self): + # obbligatoria + return QadArc(self) + + + def totalAngle(self): + if self.startAngle < self.endAngle: + return self.endAngle - self.startAngle + else: + return (2 * math.pi - self.startAngle) + self.endAngle + + + # =============================================================================== + # length + # =============================================================================== + def length(self): + # obbligatoria + return self.radius * self.totalAngle() + + + # ============================================================================ + # reverse + # ============================================================================ + def reverse(self): + # inverto direzione dell'arco (punto iniziale-finale) + self.reversed = not self.reversed + return self + + + # ============================================================================ + # inverseAngles + # ============================================================================ + def inverseAngles(self): + # inverto angolo iniziale-finale + dummy = self.endAngle + self.endAngle = self.startAngle + self.startAngle = dummy + # per mantenere lo stesso punto iniziale inverto la direzione dei punti iniziale-finale + self.reverse() + + + # ============================================================================ + # getStartPt, setStartPt + # ============================================================================ + def getStartPt(self, usingReversedFlag = True): + # obbligatoria + # usingReversedFlag è usato per sapere il punto iniziale nel caso l'arco abbia una direzione (nella polyline) + if usingReversedFlag: + return qad_utils.getPolarPointByPtAngle(self.center, + self.endAngle if self.reversed else self.startAngle, + self.radius) + else: + return qad_utils.getPolarPointByPtAngle(self.center, + self.startAngle, + self.radius) + + def setStartPt(self, pt): + # obbligatoria + if self.reversed: + return self.setEndAngleByPt(pt) + else: + return self.setStartAngleByPt(pt) + + + def setStartAngleByPt(self, pt): + # da usare per modificare un arco già definito + angle = qad_utils.getAngleBy2Pts(self.center, pt) + if angle == self.endAngle: return False + self.startAngle = angle + return True + + + # ============================================================================ + # getEndPt, setEndPt + # ============================================================================ + def getEndPt(self, usingReversedFlag = True): + # obbligatoria + # usingReversedFlag è usato per sapere il punto iniziale nel caso l'arco abbia una direzione (nella polyline) + if usingReversedFlag: + return qad_utils.getPolarPointByPtAngle(self.center, + self.startAngle if self.reversed else self.endAngle, + self.radius) + else: + return qad_utils.getPolarPointByPtAngle(self.center, + self.endAngle, + self.radius) + + def setEndPt(self, pt): + # obbligatoria + if self.reversed: + return self.setStartAngleByPt(pt) + else: + return self.setEndAngleByPt(pt) + + + def setEndAngleByPt(self, pt): + # da usare per modificare un arco già definito + angle = qad_utils.getAngleBy2Pts(self.center, pt) + if angle == self.startAngle: + return False + self.endAngle = angle + return True + + + # ============================================================================ + # isPtOnArcOnlyByAngle + # ============================================================================ + def isPtOnArcOnlyByAngle(self, point): + # la funzione valuta se un punto è sull'arco considerando solo gli angoli iniziale/finale + return self.isAngleBetweenAngles(qad_utils.getAngleBy2Pts(self.center, point)) + + + # ============================================================================ + # isAngleBetweenAngles + # ============================================================================ + def isAngleBetweenAngles(self, angle): + # la funzione valuta se un angolo è compreso tra gli angoli iniziale/finale + return qad_utils.isAngleBetweenAngles(self.startAngle, self.endAngle, angle) + + + # =============================================================================== + # getBoundingBox + # =============================================================================== + def getBoundingBox(self): + """ + la funzione ritorna il rettangolo che racchiude l'arco. + """ + circleBoundingBox = QadCircle.getBoundingBox(self) + + p1 = qad_utils.getPolarPointByPtAngle(self.center, self.startAngle, self.radius) + p2 = qad_utils.getPolarPointByPtAngle(self.center, self.endAngle, self.radius) + + if p1.x() > p2.x(): + xMax = p1.x() + xMin = p2.x() + else: + xMax = p2.x() + xMin = p1.x() + + if p1.y() > p2.y(): + yMax = p1.y() + yMin = p2.y() + else: + yMax = p2.y() + yMin = p1.y() + + end = self.endAngle + if end < self.startAngle: end = end + 2 * math.pi + + if end > math.pi / 2: + if self.startAngle < math.pi / 2: yMax = circleBoundingBox.yMaximum() + if end > math.pi: + if self.startAngle < math.pi: xMin = circleBoundingBox.xMinimum() + if end > math.pi * 3 / 4: + if self.startAngle < math.pi * 3 / 4: yMin = circleBoundingBox.yMinimum() + if end > math.pi * 2: + xMax = circleBoundingBox.xMaximum() + if end > math.pi * 2 + math.pi / 2: + yMax = circleBoundingBox.yMaximum() + if end > math.pi * 2 + math.pi: + xMin = circleBoundingBox.xMinimum() + if end > math.pi * 2 + math.pi * 3 / 4: + yMin = circleBoundingBox.yMinimum() + + return QgsRectangle(xMin, yMin, xMax, yMax) + + + # =============================================================================== + # containsPt + # =============================================================================== + def containsPt(self, point): + # obbligatoria + """ + la funzione ritorna true se il punto é sull'arco (estremi compresi). + point è di tipo QgsPointXY. + """ + dist = qad_utils.getDistance(self.center, point) + if qad_utils.doubleNear(self.radius, dist): + return self.isPtOnArcOnlyByAngle(point) + else: + return False + + + # ============================================================================ + # getDistanceFromStart + # ============================================================================ + def getDistanceFromStart(self, pt): + # obbligatoria + """ + la funzione restituisce la distanza di (che deve essere sull'oggetto o sua estensione) + dal punto iniziale. + """ + if qad_utils.ptNear(pt, self.getStartPt()): return 0.0 + dummy = QadArc(self) + dummy.setEndPt(pt) + return dummy.length() + + + # ============================================================================ + # getPointFromStart + # ============================================================================ + def getPointFromStart(self, distance): + # obbligatoria + """ + la funzione restituisce un punto (e la direzione della tangente) alla distanza + (che deve essere sull'oggetto) dal punto iniziale. + """ + if distance < 0: + return None, None + l = self.length() + if distance > l: + return None, None + + # (2*pi) : (2*pi*r) = angle : delta + angle = distance / self.radius + + if self.reversed: + angle = self.endAngle - angle + else: + angle = self.startAngle + angle + + pt = qad_utils.getPolarPointByPtAngle(self.center, angle, self.radius) + return pt, self.getTanDirectionOnPt(pt) + + + # ============================================================================ + # getDistanceFromEnd + # ============================================================================ + def getDistanceFromEnd(self, pt): + # obbligatoria + """ + la funzione restituisce la distanza di (che deve essere sull'oggetto o sua estensione) + dal punto finale. + """ + return self.length() - self.getDistanceFromStart() + + + # =============================================================================== + # getPointFromEnd + # =============================================================================== + def getPointFromEnd(self, distance): + """ + la funzione restituisce un punto (e la direzione della tangente) alla distanza + (che deve essere sull'oggetto) dal punto finale. + """ + d = self.length() - distance + return self.getPointFromStart(d) + + + # ============================================================================ + # lengthen_delta + # ============================================================================ + def lengthen_delta(self, move_startPt, delta): + # obbligatoria + """ + la funzione sposta il punto iniziale (se move_startPt = True) o finale (se move_startPt = False) + di una distanza delta + """ + length = self.length() + circle = QadCircle().set(self.center, self.radius) + # lunghezza arco + delta non può essere >= alla circonferenza del cerchio + if length + delta >= circle.length(): + return False + # (2*pi) : (2*pi*r) = angle : delta + angle = delta / self.radius + + if move_startPt == True: + if self.reversed: + self.endAngle = self.endAngle + angle + else: + self.startAngle = self.startAngle - angle + else: + if self.reversed: + self.startAngle = self.startAngle - angle + else: + self.endAngle = self.endAngle + angle + return True + + + # ============================================================================ + # lengthen_deltaAngle + # ============================================================================ + def lengthen_deltaAngle(self, move_startPt, delta): + # obbligatoria + """ + la funzione sposta il punto iniziale (se move_startPt = True) o finale (se move_startPt = False) + dell'arco di un certo numero di gradi delta + """ + totalAngle = self.totalAngle() + # angolo dell'arco + delta non può essere >= 2 * pi + if totalAngle + delta >= 2 * math.pi: + return False + # angolo dell'arco + delta non può essere <= 0 + if totalAngle + delta <= 0: + return False + + if move_startPt == True: + if self.reversed: + self.endAngle = self.endAngle + delta + else: + self.startAngle = self.startAngle - delta + else: + if self.reversed: + self.startAngle = self.startAngle - delta + else: + self.endAngle = self.endAngle + delta + return True + + + # ============================================================================ + # getQuadrantPoints + # ============================================================================ + def getQuadrantPoints(self): + result = [] + + angle = 0 + if self.isAngleBetweenAngles(angle) == True: + result.append(QgsPointXY(self.center.x() + self.radius, self.center.y())) + + angle = math.pi / 2 + if self.isAngleBetweenAngles(angle) == True: + result.append(QgsPointXY(self.center.x(), self.center.y() + self.radius)) + + angle = math.pi + if self.isAngleBetweenAngles(angle) == True: + result.append(QgsPointXY(self.center.x() - self.radius, self.center.y())) + + angle = math.pi * 3 / 2 + if self.isAngleBetweenAngles(angle) == True: + result.append(QgsPointXY(self.center.x(), self.center.y() - self.radius)) + + return result + + + # =============================================================================== + # getMiddlePoint + # =============================================================================== + def getMiddlePt(self): + halfAngle = self.totalAngle() / 2 + return qad_utils.getPolarPointByPtAngle(self.center, + self.startAngle + halfAngle, + self.radius) + + + # ============================================================================ + # getTanDirectionOnPt + # ============================================================================ + def getTanDirectionOnPt(self, pt): + # obbligatoria + """ + la funzione ritorna la direzione della tangente al punto dell'oggetto. + """ + angle = qad_utils.getAngleBy2Pts(self.center, pt) + if self.reversed: # la direzione dell'arco è invertita + return qad_utils.normalizeAngle(angle - math.pi / 2) + else: + return qad_utils.normalizeAngle(angle + math.pi / 2) + + + # ============================================================================ + # getTanDirectionOnStartPt, getTanDirectionOnEndPt, getTanDirectionOnMiddlePt + # ============================================================================ + def getTanDirectionOnStartPt(self): + # obbligatoria + """ + la funzione ritorna la direzione della tangente al punto iniziale dell'oggetto. + """ + return self.getTanDirectionOnPt(self.getStartPt()) + + def getTanDirectionOnEndPt(self): + # obbligatoria + """ + la funzione ritorna la direzione della tangente al punto finale dell'oggetto. + """ + return self.getTanDirectionOnPt(self.getEndPt()) + + + def getTanDirectionOnMiddlePt(self): + # obbligatoria + """ + la funzione ritorna la direzione della tangente al punto medio dell'oggetto. + """ + return self.getTanDirectionOnPt(self.getMiddlePt()) + + + # =============================================================================== + # leftOf + # =============================================================================== + def leftOf(self, pt): + # obbligatoria + """ + la funzione ritorna una numero < 0 se il punto pt é alla sinistra dell'arco ptStart -> ptEnd + """ + if qad_utils.getDistance(self.center, pt) - self.radius > 0: + # esterno all'arco + if self.reversed: # l'arco é in senso inverso + return -1 # a sinistra + else: + return 1 # a destra + else: + # interno all'arco + if self.reversed: # l'arco é in senso inverso + return 1 # a destra + else: + return -1 # a sinistra + + + # ============================================================================ + # asPolyline + # ============================================================================ + def asPolyline(self, tolerance2ApproxCurve=None, atLeastNSegment=None): + # obbligatoria + """ + ritorna una lista di punti che definisce l'arco + """ + if tolerance2ApproxCurve is None: + tolerance = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) + else: + tolerance = tolerance2ApproxCurve + + if atLeastNSegment is None: + _atLeastNSegment = QadVariables.get(QadMsg.translate("Environment variables", "ARCMINSEGMENTQTY"), 12) + else: + _atLeastNSegment = atLeastNSegment + + # Calcolo la lunghezza del segmento con pitagora + dummy = self.radius - tolerance + if dummy <= 0: # se la tolleranza é troppo bassa rispetto al raggio + SegmentLen = self.radius + else: + dummy = (self.radius * self.radius) - (dummy * dummy) + SegmentLen = math.sqrt(dummy) # radice quadrata + SegmentLen = SegmentLen * 2 + + if SegmentLen == 0: # se la tolleranza é troppo bassa la lunghezza del segmento diventa zero + return None + + # calcolo quanti segmenti ci vogliono (non meno di _atLeastNSegment) + SegmentTot = math.ceil(self.length() / SegmentLen) + if SegmentTot < _atLeastNSegment: + SegmentTot = _atLeastNSegment + + if SegmentTot > 99999: # metto un limite al numero di segmenti + SegmentTot = _atLeastNSegment + + points = [] + if self.reversed: # la direzione dell'arco è invertita + pt = qad_utils.getPolarPointByPtAngle(self.center, self.endAngle, self.radius) + points.append(pt) + + i = 1 + angle = self.endAngle + offsetAngle = self.totalAngle() / SegmentTot + while i < SegmentTot: + angle = angle - offsetAngle + pt = qad_utils.getPolarPointByPtAngle(self.center, angle, self.radius) + points.append(pt) + i = i + 1 + + # ultimo punto + pt = qad_utils.getPolarPointByPtAngle(self.center, self.startAngle, self.radius) + else: + pt = qad_utils.getPolarPointByPtAngle(self.center, self.startAngle, self.radius) + points.append(pt) + + i = 1 + angle = self.startAngle + offsetAngle = self.totalAngle() / SegmentTot + while i < SegmentTot: + angle = angle + offsetAngle + pt = qad_utils.getPolarPointByPtAngle(self.center, angle, self.radius) + points.append(pt) + i = i + 1 + + # ultimo punto + pt = qad_utils.getPolarPointByPtAngle(self.center, self.endAngle, self.radius) + points.append(pt) + + return points + + + # =============================================================================== + # asCircularString + # =============================================================================== + def asCircularString(self, forcedStartPt = None): + """ + la funzione ritorna l'arco in forma di circularString. + Quando l'arco fa parte di una polilinea è necessario che il suo punto iniziale coincida con quello finale della parte precedente + percui il punto iniziale viene forzato. + """ + if forcedStartPt is None: + return QgsCircularString(QgsPoint(self.getStartPt()), QgsPoint(self.getMiddlePt()), QgsPoint(self.getEndPt())) + else: + return QgsCircularString(QgsPoint(forcedStartPt), QgsPoint(self.getMiddlePt()), QgsPoint(self.getEndPt())) + + + # =============================================================================== + # asLineString + # =============================================================================== + def asLineString(self, tolerance2ApproxCurve = None, atLeastNSegment = None): + """ + la funzione ritorna l'arco in forma di lineString. + """ + pts = self.asPolyline(tolerance2ApproxCurve, atLeastNSegment) + if pts is None or len(pts) == 0: + return None + return QgsLineString(pts) + + + # =============================================================================== + # asAbstractGeom + # =============================================================================== + def asAbstractGeom(self, wkbType = QgsWkbTypes.LineString, tolerance2ApproxCurve = None, atLeastNSegment = None): + """ + la funzione ritorna l'arco in forma di QgsAbstractGeometry. + """ + flatType = QgsWkbTypes.flatType(wkbType) + + if flatType == QgsWkbTypes.CompoundCurve: + circularString = self.asCircularString() + compoundCurve = QgsCompoundCurve() + compoundCurve.addCurve(circularString) + return compoundCurve + + elif flatType == QgsWkbTypes.MultiCurve: + circularString = self.asCircularString() + multiCurve = QgsMultiCurve() + multiCurve.addGeometry(circularString) + return multiCurve + + elif flatType == QgsWkbTypes.MultiLineString: + lineString = self.asLineString(tolerance2ApproxCurve, atLeastNSegment) + multiLineString = QgsMultiLineString() + multiLineString.addGeometry(lineString) + return multiLineString + + return self.asLineString(tolerance2ApproxCurve, atLeastNSegment) + + + # =============================================================================== + # asGeom + # =============================================================================== + def asGeom(self, wkbType = QgsWkbTypes.LineString, tolerance2ApproxCurve = None, atLeastNSegment = None): + """ + la funzione ritorna l'arco in forma di QgsGeometry. + """ + return QgsGeometry(self.asAbstractGeom(wkbType, tolerance2ApproxCurve, atLeastNSegment)); + + + # ============================================================================ + # fromStartSecondEndPts + # ============================================================================ + def fromStartSecondEndPts(self, startPt, secondPt, endPt): + """ + setta le caratteristiche dell'arco attraverso: + punto iniziale + secondo punto (intermedio) + punto finale + """ + points = [startPt, secondPt, endPt] + # lista di punti, parte dal punto 0, almeno 2 segmenti + if self.fromPolyline(points, 0, 2) == None: return False + return True + + + # ============================================================================ + # fromStartCenterEndPts + # ============================================================================ + def fromStartCenterEndPts(self, startPt, centerPt, endPt): + """ + setta le caratteristiche dell'arco attraverso: + punto iniziale + centro + punto finale + """ + if startPt == centerPt or startPt == endPt or endPt == centerPt: + return False + + self.center = centerPt + self.radius = qad_utils.getDistance(centerPt, startPt) + self.startAngle = qad_utils.getAngleBy2Pts(centerPt, startPt) + self.endAngle = qad_utils.getAngleBy2Pts(centerPt, endPt) + self.reversed = False + return True + + + # ============================================================================ + # fromStartCenterPtsAngle + # ============================================================================ + def fromStartCenterPtsAngle(self, startPt, centerPt, angle): + """ + setta le caratteristiche dell'arco attraverso: + punto iniziale + centro + angolo inscritto + """ + if startPt == centerPt or angle == 0: + return False + + self.center = centerPt + self.radius = qad_utils.getDistance(centerPt, startPt) + self.startAngle = qad_utils.getAngleBy2Pts(centerPt, startPt) + self.endAngle = self.startAngle + angle + if self.endAngle > math.pi * 2: + self.endAngle = self.endAngle % (math.pi * 2) # modulo + self.reversed = False + return True + + + # ============================================================================ + # fromStartCenterPtsChord + # ============================================================================ + def fromStartCenterPtsChord(self, startPt, centerPt, chord): + """ + setta le caratteristiche dell'arco attraverso: + punto iniziale + centro + lunghezza dela corda tra punto iniziale e finale + """ + if startPt == centerPt or chord == 0: + return False + + self.center = centerPt + self.radius = qad_utils.getDistance(centerPt, startPt) + if chord > 2 * self.radius: + return False + self.startAngle = qad_utils.getAngleBy2Pts(centerPt, startPt) + # Teorema della corda + angle = 2 * math.asin(chord / (2 * self.radius)) + self.endAngle = self.startAngle + angle + self.reversed = False + return True + + + # ============================================================================ + # fromStartCenterPtsLength + # ============================================================================ + def fromStartCenterPtsLength(self, startPt, centerPt, length): + """ + setta le caratteristiche dell'arco attraverso: + punto iniziale + centro + lunghezza dela corda tra punto iniziale e finale + """ + if startPt == centerPt or chord == 0: + return False + + self.center = centerPt + self.radius = qad_utils.getDistance(centerPt, startPt) + circumference = 2 * math.pi * self.radius + if length >= circumference: + return False + self.startAngle = qad_utils.getAngleBy2Pts(centerPt, startPt) + + # circumference : math.pi * 2 = length : angle + angle = (math.pi * 2) * length / circumference + self.endAngle = self.startAngle + angle + self.reversed = False + return True + + + # ============================================================================ + # fromStartEndPtsAngle + # ============================================================================ + def fromStartEndPtsAngle(self, startPt, endPt, angle): + """ + setta le caratteristiche dell'arco attraverso: + punto iniziale + punto finale + angolo inscritto + """ + if startPt == endPt or angle == 0: + return False + + chord = qad_utils.getDistance(startPt, endPt) + half_chord = chord / 2 + # Teorema della corda + self.radius = half_chord / math.sin(angle / 2) + + angleSegment = qad_utils.getAngleBy2Pts(startPt, endPt) + ptMiddle = qad_utils.getMiddlePoint(startPt, endPt) + + # Pitagora + distFromCenter = math.sqrt((self.radius * self.radius) - (half_chord * half_chord)) + if angle < math.pi: # se angolo < 180 gradi + # aggiungo 90 gradi per cercare il centro a sinistra del segmento + self.center = qad_utils.getPolarPointByPtAngle(ptMiddle, + angleSegment + (math.pi / 2), + distFromCenter) + else: + # sottraggo 90 gradi per cercare il centro a destra del segmento + self.center = qad_utils.getPolarPointByPtAngle(ptMiddle, + angleSegment - (math.pi / 2), + distFromCenter) + self.startAngle = qad_utils.getAngleBy2Pts(self.center, startPt) + self.endAngle = qad_utils.getAngleBy2Pts(self.center, endPt) + self.reversed = False + return True + + + # ============================================================================ + # fromStartEndPtsTan + # ============================================================================ + def fromStartEndPtsTan(self, startPt, endPt, tan): + """ + setta le caratteristiche dell'arco attraverso: + punto iniziale + punto finale + direzione della tangente sul punto iniziale + """ + if startPt == endPt: + return False + + angleSegment = qad_utils.getAngleBy2Pts(startPt, endPt) + if tan == angleSegment or tan == angleSegment - math.pi: + return False + + chord = qad_utils.getDistance(startPt, endPt) + half_chord = chord / 2 + ptMiddle = qad_utils.getMiddlePoint(startPt, endPt) + + angle = tan + (math.pi / 2) + angle = angleSegment - angle + distFromCenter = math.tan(angle) * half_chord + self.center = qad_utils.getPolarPointByPtAngle(ptMiddle, + angleSegment - (math.pi / 2), + distFromCenter) + pt = qad_utils.getPolarPointByPtAngle(startPt, tan, chord) + + if qad_utils.leftOfLine(endPt, startPt, pt) < 0: + # arco si sviluppa a sinistra della tangente + self.startAngle = qad_utils.getAngleBy2Pts(self.center, startPt) + self.endAngle = qad_utils.getAngleBy2Pts(self.center, endPt) + self.reversed = False + else: + # arco si sviluppa a destra della tangente + self.startAngle = qad_utils.getAngleBy2Pts(self.center, endPt) + self.endAngle = qad_utils.getAngleBy2Pts(self.center, startPt) + self.reversed = True + + self.radius = qad_utils.getDistance(startPt, self.center) + return True + + + # ============================================================================ + # fromStartEndPtsRadius + # ============================================================================ + def fromStartEndPtsRadius(self, startPt, endPt, radius): + """ + setta le caratteristiche dell'arco attraverso: + punto iniziale + punto finale + raggio + """ + if startPt == endPt or radius <= 0: + return False + + chord = qad_utils.getDistance(startPt, endPt) + half_chord = chord / 2 + if radius < half_chord: + return False + + self.radius = radius + angleSegment = qad_utils.getAngleBy2Pts(startPt, endPt) + ptMiddle = qad_utils.getMiddlePoint(startPt, endPt) + + # Pitagora + distFromCenter = math.sqrt((self.radius * self.radius) - (half_chord * half_chord)) + # aggiungo 90 gradi + self.center = qad_utils.getPolarPointByPtAngle(ptMiddle, + angleSegment + (math.pi / 2), + distFromCenter) + self.startAngle = qad_utils.getAngleBy2Pts(self.center, startPt) + self.endAngle = qad_utils.getAngleBy2Pts(self.center, endPt) + self.reversed = False + return True + + + # ============================================================================ + # fromStartPtAngleRadiusChordDirection + # ============================================================================ + def fromStartPtAngleRadiusChordDirection(self, startPt, angle, radius, chordDirection): + """ + setta le caratteristiche dell'arco attraverso: + punto iniziale + angolo inscritto + raggio + direzione della corda + """ + if angle == 0 or angle == 2 * math.pi or radius <= 0: + return False + + a = chordDirection + (math.pi / 2) - (angle / 2) + self.radius = radius + self.center = qad_utils.getPolarPointByPtAngle(startPt, a, radius) + endPt = qad_utils.getPolarPointByPtAngle(self.center, a + math.pi + angle, radius) + + self.startAngle = qad_utils.getAngleBy2Pts(self.center, startPt) + self.endAngle = qad_utils.getAngleBy2Pts(self.center, endPt) + self.reversed = False + return True + + + # ============================================================================ + # fromPolyline + # ============================================================================ + def fromPolyline(self, points, startVertex, atLeastNSegment=None): + """ + setta le caratteristiche del primo arco incontrato nella lista di punti + partendo dalla posizione startVertex (0-indexed). + Ritorna la posizione nella lista del punto finale se é stato trovato un arco + altrimenti None + N.B. i punti NON devono essere in coordinate geografiche + """ + # se il punto iniziale e quello finale coincidono non é un arco + if points[startVertex] == points[-1]: + return None + + i = startVertex + + if atLeastNSegment is None: + _atLeastNSegment = QadVariables.get(QadMsg.translate("Environment variables", "ARCMINSEGMENTQTY"), 12) + else: + _atLeastNSegment = atLeastNSegment + + totPoints = len(points) - startVertex + nSegment = totPoints - 1 + # perché sia un arco ci vogliono almeno _atLeastNSegment segmenti e almeno 3 punti + if nSegment < _atLeastNSegment or totPoints < 3: + return None + + myPoints = [] + # sposto i primi 3 punti vicino a 0,0 per migliorare la precisione dei calcoli + dx = points[startVertex].x() + dy = points[startVertex].y() + myPoints.append(qad_utils.movePoint(points[startVertex], -dx, -dy)) + myPoints.append(qad_utils.movePoint(points[startVertex + 1], -dx, -dy)) + myPoints.append(qad_utils.movePoint(points[startVertex + 2], -dx, -dy)) + +# InfinityLinePerpOnMiddle1 = qd_utils.getInfinityLinePerpOnMiddle(myPoints[0], myPoints[1]) +# if InfinityLinePerpOnMiddle1 is None: return None +# InfinityLinePerpOnMiddle2 = qad_utils.getInfinityLinePerpOnMiddle(myPoints[1], myPoints[2]) +# if InfinityLinePerpOnMiddle2 is None: return None +# +# # calcolo il presunto centro con 2 segmenti +# center = qad_utils.getIntersectionPointOn2InfinityLines(InfinityLinePerpOnMiddle1[0], \ +# InfinityLinePerpOnMiddle1[1], \ +# InfinityLinePerpOnMiddle2[0], \ +# InfinityLinePerpOnMiddle2[1]) +# if center is None: return None # linee parallele + + # se uso QgsCircle ottengo una miglior precisione + circle = QgsCircle.from3Points(QgsPoint(myPoints[0]), QgsPoint(myPoints[1]), QgsPoint(myPoints[2])) + if circle.isEmpty() == True: + return None + + center = circle.center() + center = QgsPointXY(center.x(), center.y()) + radius = qad_utils.getDistance(center, myPoints[0]) # calcolo il presunto raggio + + # calcolo il verso dell'arco e l'angolo dell'arco + # se un punto intermedio dell'arco è a sinistra del + # segmento che unisce i due punti allora il verso è antiorario + startClockWise = True if qad_utils.leftOfLine(myPoints[1], myPoints[0], myPoints[2]) < 0 else False + angle = qad_utils.getAngleBy3Pts(myPoints[0], center, myPoints[2], startClockWise) + + # uso la distanza TOLERANCE2COINCIDENT / 2 perchè in una polilinea se ci sono 2 archi consecutivi + # si vuole essere sicuri che il punto finale del primo arco sia distante dal punto iniziale del + # secondo arco non più di TOLERANCE2COINCIDENT perchè siano considerato 2 punti coincidenti + myTolerance = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2COINCIDENT")) / 2 + # myTolerance = 0 # test + + i = 3 + while i < totPoints: + # sposto i punti vicino a 0,0 per migliorare la precisione dei calcoli + myPoints.append(qad_utils.movePoint(points[i + startVertex], -dx, -dy)) + + # se TOLERANCE2COINCIDENT = 0,001 viene riconosciuto un arco di 1000 m + # se il punto calcolato non è abbastanza vicino al punto reale + # altrimenti trovo problemi con le intersezioni con gli oggetti + if qad_utils.ptNear(qad_utils.getPolarPointByPtAngle(center, qad_utils.getAngleBy2Pts(center, myPoints[i]), radius), \ + myPoints[i], myTolerance) == False: + break + + # calcolo il verso dell'arco e l'angolo + clockWise = True if qad_utils.leftOfLine(myPoints[i - 1], myPoints[i - 2], myPoints[i]) < 0 else False + if startClockWise != clockWise: break # cambiata la direzione + angle = angle + qad_utils.getAngleBy3Pts(myPoints[i - 1], center, myPoints[i], startClockWise) + if angle >= 2 * math.pi: break # l'arco non può avere un angolo interno maggiore o uguale a 2 pi + + i = i + 1 + + # se non sono stati trovati un numero sufficiente di segmenti successivi + i = i - 1 # ultimo punto valido dell'arco + if i < _atLeastNSegment: return None + + self.center = center + self.radius = radius + + # se il verso é orario + if startClockWise: + # inverto l'angolo iniziale con quello finale + self.endAngle = qad_utils.getAngleBy2Pts(center, myPoints[0]) + self.startAngle = qad_utils.getAngleBy2Pts(center, myPoints[i]) + self.reversed = True + else: + self.startAngle = qad_utils.getAngleBy2Pts(center, myPoints[0]) + self.endAngle = qad_utils.getAngleBy2Pts(center, myPoints[i]) + self.reversed = False + + # traslo la geometria per riportarla alla sua posizione originale + self.move(dx, dy) + + return i + startVertex + + + # =============================================================================== + # sqrDist + # =============================================================================== + def sqrDist(self, point): + # obbligatoria + """ + la funzione ritorna una lista con + ( + ) + """ + minDistPoint = QgsPointXY() + angle = qad_utils.getAngleBy2Pts(self.center, point) + if self.isAngleBetweenAngles(angle): + distFromArc = qad_utils.getDistance(self.center, point) - self.radius + return (distFromArc * distFromArc, qad_utils.getPolarPointByPtAngle(self.center, angle, self.radius)) + else: + startPt = self.getStartPt() + endPt = self.getEndPt() + distFromStartPt = qad_utils.getSqrDistance(startPt, point) + distFromEndPt = qad_utils.getSqrDistance(endPt, point) + if distFromStartPt < distFromEndPt: + return (distFromStartPt, startPt) + else: + return (distFromEndPt, endPt) + + + + # ============================================================================ + # move + # ============================================================================ + def move(self, offsetX, offsetY): + # obbligatoria + self.center = qad_utils.movePoint(self.center, offsetX, offsetY) + + + # ============================================================================ + # rotate + # ============================================================================ + def rotate(self, basePt, angle): + self.center = qad_utils.rotatePoint(self.center, basePt, angle) + self.startAngle = qad_utils.normalizeAngle(self.startAngle + angle) + self.endAngle = qad_utils.normalizeAngle(self.endAngle + angle) + + + # ============================================================================ + # scale + # ============================================================================ + def scale(self, basePt, scale): + self.center = qad_utils.scalePoint(self.center, basePt, scale) + self.radius = self.radius * scale + + + # ============================================================================ + # mirror + # ============================================================================ + def mirror(self, mirrorPt, mirrorAngle): + startPt = qad_utils.mirrorPoint(self.getStartPt(), mirrorPt, mirrorAngle) + secondPt = qad_utils.mirrorPoint(self.getMiddlePt(), mirrorPt, mirrorAngle) + endPt = qad_utils.mirrorPoint(self.getEndPt(), mirrorPt, mirrorAngle) + self.fromStartSecondEndPts(startPt, secondPt, endPt) + #self.reversed = not self.reversed # cambio la direzione dell'arco + + + # =============================================================================== + # offset + # =============================================================================== + def offset(self, offsetDist, offsetSide): + """ + la funzione modifica l'arco facendone l'offset + secondo una distanza e un lato di offset ("right" o "left" o "internal" o "external") + """ + side = "" + if offsetSide == "right": + if self.reversed: # direzione oraria + side = "internal" # offset verso l'interno del cerchio + else: + side = "external" # offset verso l'esterno del cerchio + elif offsetSide == "left": + if self.reversed: # direzione oraria + side = "external" # offset verso l'esterno del cerchio + else: + side = "internal" # offset verso l'interno del cerchio + else: + side = offsetSide + + if side == "internal": # offset verso l'interno del cerchio + radius = self.radius - offsetDist + elif side == "external": # offset verso l'esterno del cerchio + radius = self.radius + offsetDist + + if radius <= 0: return False + self.radius = radius + + return True + + + # ============================================================================ + # extend + # ============================================================================ + def extend(self, extend_startPt, limitPt, tolerance2ApproxCurve): + """ + la funzione estende l'arco dalla parte del punto iniziale se extend_startPt = True (altrimenti finale) fino ad + incontrare il punto . + """ + if extend_startPt: + if self.reversed: + return self.setEndAngleByPt(limitPt) + else: + return self.setStartAngleByPt(limitPt) + else: + if self.reversed: + return self.setStartAngleByPt(limitPt) + else: + return self.setEndAngleByPt(limitPt) + + + # =============================================================================== + # breakOnPts + # =============================================================================== + def breakOnPts(self, firstPt, secondPt): + # obbligatoria + """ + la funzione spezza la geometria in un punto (se = None) o in due punti + come fa il trim. Ritorna una o due geometrie risultanti dall'operazione. + = primo punto di divisione + = secondo punto di divisione + """ + # la funzione ritorna una lista con ( ) + dummy = self.sqrDist(firstPt) + myFirstPt = dummy[1] + + mySecondPt = None + if secondPt is not None: + dummy = self.sqrDist(secondPt) + mySecondPt = dummy[1] + + part1 = self.getGeomBetween2Pts(self.getStartPt(), myFirstPt) + if mySecondPt is None: + part2 = self.getGeomBetween2Pts(myFirstPt, self.getEndPt()) + else: + part2 = self.getGeomBetween2Pts(mySecondPt, self.getEndPt()) + + return [part1, part2] + + + # =============================================================================== + # getGeomBetween2Pts + # =============================================================================== + def getGeomBetween2Pts(self, startPt, endPt): + """ + Ritorna una sotto geometria che parte dal punto startPt e finisce al punto endPt seguendo il tracciato della geometria. + """ + if qad_utils.ptNear(startPt, endPt): return None + if self.containsPt(startPt) == False: return None + if self.containsPt(endPt) == False: return None + + result = self.copy() + d1 = self.getDistanceFromStart(startPt) + if d1 < self.getDistanceFromStart(endPt): + result.setStartPt(startPt) + result.setEndPt(endPt) + else: + result.setStartPt(endPt) + result.setEndPt(startPt) + result.reversed = True + + return result diff --git a/qad_arc_maptool.py b/qad_arc_maptool.py deleted file mode 100644 index ed66479b..00000000 --- a/qad_arc_maptool.py +++ /dev/null @@ -1,380 +0,0 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - classe per gestire il map tool di richiesta di un punto in ambito del comando arco - - ------------------- - begin : 2013-05-22 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -from qgis.gui import * -import math - - -import qad_utils -from qad_snapper import * -from qad_snappointsdisplaymanager import * -from qad_variables import * -from qad_getpoint import * -from qad_arc import * -from qad_rubberband import QadRubberBand -from qad_highlight import QadHighlight - - -#=============================================================================== -# Qad_arc_maptool_ModeEnum class. -#=============================================================================== -class Qad_arc_maptool_ModeEnum(): - # noto niente si richiede il primo punto - NONE_KNOWN_ASK_FOR_START_PT = 1 - # noto il punto iniziale dell'arco si richiede il secondo punto - START_PT_KNOWN_ASK_FOR_SECOND_PT = 2 - # noti il punto iniziale e il secondo punto dell'arco si richiede il punto finale - START_SECOND_PT_KNOWN_ASK_FOR_END_PT = 3 - # noto il punto iniziale dell'arco si richiede il centro - START_PT_KNOWN_ASK_FOR_CENTER_PT = 4 - # noti il punto iniziale e il centro dell'arco si richiede il punto finale - START_CENTER_PT_KNOWN_ASK_FOR_END_PT = 5 - # noti il punto iniziale e il centro dell'arco si richiede l'angolo inscritto - START_CENTER_PT_KNOWN_ASK_FOR_ANGLE = 6 - # noti il punto iniziale e il centro dell'arco si richiede la lunghezza della corda - START_CENTER_PT_KNOWN_ASK_FOR_CHORD = 7 - # noto il punto iniziale dell'arco si richiede il punto finale - START_PT_KNOWN_ASK_FOR_END_PT = 8 - # noti il punto iniziale e finale dell'arco si richiede il centro - START_END_PT_KNOWN_ASK_FOR_CENTER = 9 - # noti il punto iniziale e finale dell'arco si richiede l'angolo inscritto - START_END_PT_KNOWN_ASK_FOR_ANGLE = 10 - # noti il punto iniziale e finale dell'arco si richiede la direzione della tangente al punto iniziale - START_END_PT_KNOWN_ASK_FOR_TAN = 11 - # noti il punto iniziale e finale dell'arco si richiede il raggio - START_END_PT_KNOWN_ASK_FOR_RADIUS = 12 - # noto niente si richiede il centro - NONE_KNOWN_ASK_FOR_CENTER_PT = 13 - # noto il centro dell'arco si richiede il punto iniziale - CENTER_PT_KNOWN_ASK_FOR_START_PT = 14 - # noti il punto iniziale e la tangente al punto iniziale si richiede il punto finale - START_PT_TAN_KNOWN_ASK_FOR_END_PT = 15 - # noto il punto iniziale dell'arco si richiede l'angolo inscritto - START_PT_KNOWN_ASK_FOR_ANGLE = 16 - # noti il punto iniziale e l'angolo inscritto dell'arco si richiede il punto finale - START_PT_ANGLE_KNOWN_ASK_FOR_END_PT = 17 - # noti il punto iniziale e l'angolo inscritto dell'arco si richiede il centro - START_PT_ANGLE_KNOWN_ASK_FOR_CENTER_PT = 18 - # noti il punto iniziale e l'angolo inscritto dell'arco si richiede il raggio - START_PT_ANGLE_KNOWN_ASK_FOR_RADIUS = 19 - # noti il punto iniziale e l'angolo inscritto dell'arco si richiede il secondo punto per misurare il raggio - START_PT_ANGLE_KNOWN_ASK_FOR_SECONDPTRADIUS = 20 - # noti il punto iniziale, l'angolo inscritto e il raggio dell'arco si richiede la direzione della corda - START_PT_ANGLE_RADIUS_KNOWN_ASK_FOR_CHORDDIRECTION = 21 - # noti il punto iniziale e il raggio dell'arco si richiede il punto finale - START_PT_RADIUS_KNOWN_ASK_FOR_END_PT = 22 - - -#=============================================================================== -# Qad_arc_maptool class -#=============================================================================== -class Qad_arc_maptool(QadGetPoint): - - def __init__(self, plugIn): - QadGetPoint.__init__(self, plugIn) - - self.arcStartPt = None - self.arcSecondPt = None - self.arcEndPt = None - self.arcCenterPt = None - self.arcTanOnStartPt = None - self.arcAngle = None - self.arcStartPtForRadius = None - self.arcRadius = None - self.__rubberBand = QadRubberBand(self.canvas) - - - def hidePointMapToolMarkers(self): - QadGetPoint.hidePointMapToolMarkers(self) - self.__rubberBand.hide() - - def showPointMapToolMarkers(self): - QadGetPoint.showPointMapToolMarkers(self) - self.__rubberBand.show() - - def clear(self): - QadGetPoint.clear(self) - self.__rubberBand.reset() - self.mode = None - - - def canvasMoveEvent(self, event): - QadGetPoint.canvasMoveEvent(self, event) - - self.__rubberBand.reset() - - result = False - arc = QadArc() - - # noti il primo e il secondo punto dell'arco si richiede il terzo punto - if self.mode == Qad_arc_maptool_ModeEnum.START_SECOND_PT_KNOWN_ASK_FOR_END_PT: - result = arc.fromStartSecondEndPts(self.arcStartPt, self.arcSecondPt, self.tmpPoint) - # noti il primo punto e il centro dell'arco si richiede il punto finale - elif self.mode == Qad_arc_maptool_ModeEnum.START_CENTER_PT_KNOWN_ASK_FOR_END_PT: - result = arc.fromStartCenterEndPts(self.arcStartPt, self.arcCenterPt, self.tmpPoint) - # noti il primo punto e il centro dell'arco si richiede l'angolo inscritto - elif self.mode == Qad_arc_maptool_ModeEnum.START_CENTER_PT_KNOWN_ASK_FOR_ANGLE: - angle = qad_utils.getAngleBy2Pts(self.arcCenterPt, self.tmpPoint) - result = arc.fromStartCenterPtsAngle(self.arcStartPt, self.arcCenterPt, angle) - # noti il primo punto e il centro dell'arco si richiede la lunghezza della corda - elif self.mode == Qad_arc_maptool_ModeEnum.START_CENTER_PT_KNOWN_ASK_FOR_CHORD: - chord = qad_utils.getDistance(self.arcStartPt, self.tmpPoint) - result = arc.fromStartCenterPtsChord(self.arcStartPt, self.arcCenterPt, chord) - # noti il punto iniziale e finale dell'arco si richiede il centro - elif self.mode == Qad_arc_maptool_ModeEnum.START_END_PT_KNOWN_ASK_FOR_CENTER: - result = arc.fromStartCenterEndPts(self.arcStartPt, self.tmpPoint, self.arcEndPt) - # noti il punto iniziale e finale dell'arco si richiede l'angolo inscritto - elif self.mode == Qad_arc_maptool_ModeEnum.START_END_PT_KNOWN_ASK_FOR_ANGLE: - angle = qad_utils.getAngleBy2Pts(self.arcStartPt, self.tmpPoint) - result = arc.fromStartEndPtsAngle(self.arcStartPt, self.arcEndPt, angle) - # noti il punto iniziale e finale dell'arco si richiede la direzione della tangente - elif self.mode == Qad_arc_maptool_ModeEnum.START_END_PT_KNOWN_ASK_FOR_TAN: - tan = qad_utils.getAngleBy2Pts(self.arcStartPt, self.tmpPoint) - result = arc.fromStartEndPtsTan(self.arcStartPt, self.arcEndPt, tan) - # noti il punto iniziale e finale dell'arco si richiede il raggio - elif self.mode == Qad_arc_maptool_ModeEnum.START_END_PT_KNOWN_ASK_FOR_RADIUS: - radius = qad_utils.getDistance(self.arcEndPt, self.tmpPoint) - result = arc.fromStartEndPtsRadius(self.arcStartPt, self.arcEndPt, radius) - # noti il punto iniziale e la tangente al punto iniziale si richiede il punto finale - elif self.mode == Qad_arc_maptool_ModeEnum.START_PT_TAN_KNOWN_ASK_FOR_END_PT: - result = arc.fromStartEndPtsTan(self.arcStartPt, self.tmpPoint, self.arcTanOnStartPt) - # noti il punto iniziale e l'angolo inscritto dell'arco si richiede il punto finale - elif self.mode == Qad_arc_maptool_ModeEnum.START_PT_ANGLE_KNOWN_ASK_FOR_END_PT: - result = arc.fromStartEndPtsAngle(self.arcStartPt, self.tmpPoint, self.arcAngle) - # noti il punto iniziale e l'angolo inscritto dell'arco si richiede il centro - elif self.mode == Qad_arc_maptool_ModeEnum.START_PT_ANGLE_KNOWN_ASK_FOR_CENTER_PT: - result = arc.fromStartCenterPtsAngle(self.arcStartPt, self.tmpPoint, self.arcAngle) - # noti il punto iniziale, l'angolo inscritto e il raggio dell'arco si richiede la direzione della corda - elif self.mode == Qad_arc_maptool_ModeEnum.START_PT_ANGLE_RADIUS_KNOWN_ASK_FOR_CHORDDIRECTION: - chordDirection = qad_utils.getAngleBy2Pts(self.arcStartPt, self.tmpPoint) - result = arc.fromStartPtAngleRadiusChordDirection(self.arcStartPt, self.arcAngle, \ - self.arcRadius, chordDirection) - # noti il punto iniziale e il raggio dell'arco si richiede il punto finale - elif self.mode == Qad_arc_maptool_ModeEnum.START_PT_RADIUS_KNOWN_ASK_FOR_END_PT: - result = arc.fromStartEndPtsRadius(self.arcStartPt, self.tmpPoint, self.arcRadius) - - if result == True: - points = arc.asPolyline() - - if points is not None: - self.__rubberBand.setLine(points) - - - def activate(self): - QadGetPoint.activate(self) - self.__rubberBand.show() - - def deactivate(self): - try: # necessario perché se si chiude QGIS parte questo evento nonostante non ci sia più l'oggetto maptool ! - QadGetPoint.deactivate(self) - self.__rubberBand.hide() - except: - pass - - def setMode(self, mode): - self.mode = mode - # noto niente si richiede il primo punto - if self.mode == Qad_arc_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_START_PT: - self.setDrawMode(QadGetPointDrawModeEnum.NONE) - # noto il primo punto dell'arco si richiede il secondo punto - elif self.mode == Qad_arc_maptool_ModeEnum.START_PT_KNOWN_ASK_FOR_SECOND_PT: - self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.setStartPoint(self.arcStartPt) - # noti il primo e il secondo punto dell'arco si richiede il terzo punto - elif self.mode == Qad_arc_maptool_ModeEnum.START_SECOND_PT_KNOWN_ASK_FOR_END_PT: - self.setDrawMode(QadGetPointDrawModeEnum.NONE) - # noto il primo punto dell'arco si richiede il centro - elif self.mode == Qad_arc_maptool_ModeEnum.START_PT_KNOWN_ASK_FOR_CENTER_PT: - self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.setStartPoint(self.arcStartPt) - # noti il primo punto e il centro dell'arco si richiede il punto finale - elif self.mode == Qad_arc_maptool_ModeEnum.START_CENTER_PT_KNOWN_ASK_FOR_END_PT: - self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.setStartPoint(self.arcCenterPt) - # noti il primo punto e il centro dell'arco si richiede l'angolo inscritto - elif self.mode == Qad_arc_maptool_ModeEnum.START_CENTER_PT_KNOWN_ASK_FOR_ANGLE: - self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.setStartPoint(self.arcCenterPt) - # noti il primo punto e il centro dell'arco si richiede la lunghezza della corda - elif self.mode == Qad_arc_maptool_ModeEnum.START_CENTER_PT_KNOWN_ASK_FOR_CHORD: - self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.setStartPoint(self.arcStartPt) - # noto il punto iniziale dell'arco si richiede il punto finale - elif self.mode == Qad_arc_maptool_ModeEnum.START_PT_KNOWN_ASK_FOR_END_PT: - self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.setStartPoint(self.arcStartPt) - # noti il punto iniziale e finale dell'arco si richiede il centro - elif self.mode == Qad_arc_maptool_ModeEnum.START_END_PT_KNOWN_ASK_FOR_CENTER: - self.setDrawMode(QadGetPointDrawModeEnum.NONE) - # noti il punto iniziale e finale dell'arco si richiede l'angolo inscritto - elif self.mode == Qad_arc_maptool_ModeEnum.START_END_PT_KNOWN_ASK_FOR_ANGLE: - self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.setStartPoint(self.arcStartPt) - # noti il punto iniziale e finale dell'arco si richiede la direzione della tangente - elif self.mode == Qad_arc_maptool_ModeEnum.START_END_PT_KNOWN_ASK_FOR_TAN: - self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.setStartPoint(self.arcStartPt) - # noti il punto iniziale e finale dell'arco si richiede il raggio - elif self.mode == Qad_arc_maptool_ModeEnum.START_END_PT_KNOWN_ASK_FOR_RADIUS: - self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.setStartPoint(self.arcEndPt) - # noto niente si richiede il centro - elif self.mode == Qad_arc_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_CENTER_PT: - self.setDrawMode(QadGetPointDrawModeEnum.NONE) - # noto il centro dell'arco si richiede il punto iniziale - elif self.mode == Qad_arc_maptool_ModeEnum.CENTER_PT_KNOWN_ASK_FOR_START_PT: - self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.setStartPoint(self.arcCenterPt) - # noti il punto iniziale e la tangente al punto iniziale si richiede il punto finale - elif self.mode == Qad_arc_maptool_ModeEnum.START_PT_TAN_KNOWN_ASK_FOR_END_PT: - self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.setStartPoint(self.arcStartPt) - # noto il punto iniziale dell'arco si richiede l'angolo inscritto - elif self.mode == Qad_arc_maptool_ModeEnum.START_PT_KNOWN_ASK_FOR_ANGLE: - self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.setStartPoint(self.arcStartPt) - # noti il punto iniziale e l'angolo inscritto dell'arco si richiede il punto finale - elif self.mode == Qad_arc_maptool_ModeEnum.START_PT_ANGLE_KNOWN_ASK_FOR_END_PT: - self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.setStartPoint(self.arcStartPt) - # noti il punto iniziale e l'angolo inscritto dell'arco si richiede il centro - elif self.mode == Qad_arc_maptool_ModeEnum.START_PT_ANGLE_KNOWN_ASK_FOR_CENTER_PT: - self.setDrawMode(QadGetPointDrawModeEnum.NONE) - # noti il punto iniziale e l'angolo inscritto dell'arco si richiede il raggio - elif self.mode == Qad_arc_maptool_ModeEnum.START_PT_ANGLE_KNOWN_ASK_FOR_RADIUS: - self.setDrawMode(QadGetPointDrawModeEnum.NONE) - # noti il punto iniziale e l'angolo inscritto dell'arco si richiede il secondo punto per misurare il raggio - elif self.mode == Qad_arc_maptool_ModeEnum.START_PT_ANGLE_KNOWN_ASK_FOR_SECONDPTRADIUS: - self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.setStartPoint(self.arcStartPtForRadius) - # noti il punto iniziale, l'angolo inscritto e il raggio dell'arco si richiede la direzione della corda - elif self.mode == Qad_arc_maptool_ModeEnum.START_PT_ANGLE_RADIUS_KNOWN_ASK_FOR_CHORDDIRECTION: - self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.setStartPoint(self.arcStartPt) - # noti il punto iniziale e il raggio dell'arco si richiede il punto finale - elif self.mode == Qad_arc_maptool_ModeEnum.START_PT_RADIUS_KNOWN_ASK_FOR_END_PT: - self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.setStartPoint(self.arcStartPt) - - - -#=============================================================================== -# Qad_scale_maptool_ModeEnum class. -#=============================================================================== -class Qad_gripChangeArcRadius_maptool_ModeEnum(): - # si richiede il punto base - ASK_FOR_BASE_PT = 1 - # noto il punto base si richiede il secondo punto per il raggio - BASE_PT_KNOWN_ASK_FOR_RADIUS_PT = 2 - - -#=============================================================================== -# Qad_gripChangeArcRadius_maptool class -#=============================================================================== -class Qad_gripChangeArcRadius_maptool(QadGetPoint): - - def __init__(self, plugIn): - QadGetPoint.__init__(self, plugIn) - - self.basePt = None - self.entity = None - self.arc = None - self.coordTransform = None - self.__highlight = QadHighlight(self.canvas) - - def hidePointMapToolMarkers(self): - QadGetPoint.hidePointMapToolMarkers(self) - self.__highlight.hide() - - def showPointMapToolMarkers(self): - QadGetPoint.showPointMapToolMarkers(self) - self.__highlight.show() - - def clear(self): - QadGetPoint.clear(self) - self.__highlight.reset() - self.mode = None - - def setEntity(self, entity): - self.entity = QadEntity(entity) - self.arc = self.entity.getQadGeom() # arco in map coordinate - self.basePt = self.arc.center - self.coordTransform = QgsCoordinateTransform(self.canvas.mapRenderer().destinationCrs(), entity.layer.crs()) - - - #============================================================================ - # stretch - #============================================================================ - def changeRadius(self, radius): - self.__highlight.reset() - # radius = nuovo raggio dell'arco - # tolerance2ApproxCurve = tolleranza per ricreare le curve - self.arc.radius = radius - points = self.arc.asPolyline() - if points is None: - return False - - g = QgsGeometry.fromPolyline(points) - # trasformo la geometria nel crs del layer - g.transform(self.coordTransform) - self.__highlight.addGeometry(g, self.entity.layer) - - - def canvasMoveEvent(self, event): - QadGetPoint.canvasMoveEvent(self, event) - - # noto il punto base si richiede il secondo punto per il raggio - if self.mode == Qad_gripChangeArcRadius_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_RADIUS_PT: - radius = qad_utils.getDistance(self.basePt, self.tmpPoint) - self.changeRadius(radius) - - - def activate(self): - QadGetPoint.activate(self) - self.__highlight.show() - - def deactivate(self): - try: # necessario perché se si chiude QGIS parte questo evento nonostante non ci sia più l'oggetto maptool ! - QadGetPoint.deactivate(self) - self.__highlight.hide() - except: - pass - - - def setMode(self, mode): - self.mode = mode - # noto niente si richiede il punto base - if self.mode == Qad_gripChangeArcRadius_maptool_ModeEnum.ASK_FOR_BASE_PT: - self.clear() - self.setDrawMode(QadGetPointDrawModeEnum.NONE) - self.__highlight.reset() - # noto il punto base si richiede il secondo punto per il raggio - elif self.mode == Qad_gripChangeArcRadius_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_RADIUS_PT: - self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.setStartPoint(self.basePt) - \ No newline at end of file diff --git a/qad_array_fun.py b/qad_array_fun.py new file mode 100644 index 00000000..50c99b67 --- /dev/null +++ b/qad_array_fun.py @@ -0,0 +1,216 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin ok + + funzioni per fare serie di oggetti grafici + + ------------------- + begin : 2016-05-26 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.PyQt.QtCore import * +from qgis.PyQt.QtGui import * +from qgis.core import * +from qgis.gui import * +import qgis.utils + +import math + +from . import qad_utils +from . import qad_arc +from . import qad_circle +from .qad_snapper import * +from . import qad_layer +from .qad_highlight import QadHighlight +from .qad_entity import * +from .qad_dim import * +from . import qad_label +from .qad_multi_geom import fromQadGeomToQgsGeom + +# =============================================================================== +# doMoveAndRotateGeom +# =============================================================================== +def doMoveAndRotateGeom(plugIn, entity, offsetX, offsetY, angle, basePt, addToLayer, highlightObj): + # funzione di ausilio + if entity.whatIs() == "ENTITY": + qadGeom = entity.getQadGeom().copy() + qadGeom.move(offsetX, offsetY) + if angle is not None: + qadGeom.rotate(basePt, angle) + + g = fromQadGeomToQgsGeom(qadGeom, entity.layer) + if addToLayer: + newF = QgsFeature(entity.getFeature()) # la copio perchè altrimenti qgis si incarta + newF.setGeometry(g) + + if len(entity.rotFldName) > 0: + rotValue = newF.attribute(entity.rotFldName) + # a volte vale None e a volte null (vai a capire...) + rotValue = 0 if rotValue is None or rotValue == NULL else qad_utils.toRadians(rotValue) # la rotazione é in gradi nel campo della feature + rotValue = rotValue + angle + newF.setAttribute(entity.rotFldName, qad_utils.toDegrees(qad_utils.normalizeAngle(rotValue))) + + # plugIn, layer, feature, coordTransform, refresh, check_validity + if qad_layer.addFeatureToLayer(plugIn, entity.layer, newF, None, False, False, False) == False: + return False + + if highlightObj is not None: + highlightObj.addGeometry(g, entity.layer) + + del qadGeom + del g + + elif ent.whatIs() == "DIMENTITY": # se l'entità è una quotatura + newDimEntity = QadDimEntity(dimEntity) + newDimEntity.move(offsetX, offsetY) + if angle is not None: + newDimEntity.rotate(basePt, angle) + + if addToLayer: + if newDimEntity.addToLayers(plugIn) == False: + return False + + if highlightObj is not None: + highlightObj.addGeometry(newDimEntity.textualFeature.geometry(), newDimEntity.getTextualLayer()) + highlightObj.addGeometries(newDimEntity.getLinearGeometryCollection(), newDimEntity.getLinearLayer()) + highlightObj.addGeometries(newDimEntity.getSymbolGeometryCollection(), newDimEntity.getSymbolLayer()) + + del newDimEntity + + return True + + +# =============================================================================== +# arrayRectangleEntity +# =============================================================================== +def arrayRectangleEntity(plugIn, ent, basePt, rows, cols, distanceBetweenRows, distanceBetweenCols, angle, itemsRotation, + addToLayer, highlightObj): + """ + serie rettangolare + ent = entità QAD di cui fare la serie (QadEntity o QadDimEntity) + basePt = punto base in map coordinate (QgsPointXY) + rows = numero di righe + cols = numero di colonne + distanceBetweenRows = distanza tra le righe in map coordinate + distanceBetweenCols = distanza tra le colonne in map coordinate + angle = angolo della serie (radianti) + itemsRotation = True se si vuole ruotare gli elementi come l'angolo della serie + addToLayer = se è True aggiunge le nuove entità al layer + highlightObj = se è diverso da None vengono aggiunge le geometrie all'oggetto QadHighlight + + la funzione restituisce True in caso di successo e Falso in caso di errore + """ + for row in range(0, rows): + firstBasePt = qad_utils.getPolarPointByPtAngle(basePt, angle + math.pi / 2, distanceBetweenRows * row) + distX = 0 + for col in range(0, cols): + newBasePt = qad_utils.getPolarPointByPtAngle(firstBasePt, angle, distanceBetweenCols * col) + offsetX = newBasePt.x() - basePt.x() + offsetY = newBasePt.y() - basePt.y() + + if doMoveAndRotateGeom(plugIn, ent, offsetX, offsetY, \ + angle if itemsRotation else None, \ + newBasePt, addToLayer, highlightObj) == False: + return False + + distX = distX + distanceBetweenCols + + return True + + +# =============================================================================== +# arrayPathEntity +# =============================================================================== +def arrayPathEntity(plugIn, ent, basePt, rows, cols, distanceBetweenRows, distanceBetweenCols, tangentDirection, itemsRotation, \ + pathPolyline, distanceFromStartPt, addToLayer, highlightObj): + """ + serie traiettoria + ent = entità QAD di cui fare la serie (QadEntity o QadDimEntity) + basePt = punto base in map coordinate (QgsPointXY) + rows = numero di righe + cols = numero di colonne + distanceBetweenRows = distanza tra le righe in map coordinate + distanceBetweenCols = distanza tra le colonne in map coordinate + tangentDirection = specifica il modo in cui gli elementi disposti in serie sono allineati rispetto alla direzione iniziale della traiettoria + itemsRotation = True se si vuole ruotare gli elementi come l'angolo della serie + pathPolyline = traiettoria da seguire (QadPolyline) in map coordinate + distanceFromStartPt = distanza dal punto iniziale della traccia + addToLayer = se è True aggiunge le nuove entità al layer + highlightObj = se è diverso da None vengono aggiunge le geometrie all'oggetto QadHighlight + + la funzione restituisce True in caso di successo e Falso in caso di errore + """ + firstBasePt = basePt + firstTanDirection = pathPolyline.getTanDirectionOnStartPt() + for col in range(0, cols): + distX = (distanceBetweenCols * col) + distanceFromStartPt + firstBasePt, angle = pathPolyline.getPointFromStart(distX) # ritorna il punto e la direzione della tang in quel punto + if firstBasePt is not None: + for row in range(0, rows): + newBasePt = qad_utils.getPolarPointByPtAngle(firstBasePt, angle + math.pi/2, distanceBetweenRows * row) + offsetX = newBasePt.x() - basePt.x() + offsetY = newBasePt.y() - basePt.y() + + if doMoveAndRotateGeom(plugIn, ent, offsetX, offsetY, \ + angle - tangentDirection if itemsRotation else -tangentDirection, \ + newBasePt, addToLayer, highlightObj) == False: + return False + + distX = distX + distanceBetweenCols + + return True + + +# =============================================================================== +# arrayPolarEntity +# =============================================================================== +def arrayPolarEntity(plugIn, ent, basePt, centerPt, itemsNumber, angleBetween, rows, distanceBetweenRows, itemsRotation, \ + addToLayer, highlightObj): + """ + serie polare + ent = entità QAD di cui fare la serie (QadEntity o QadDimEntity) + basePt = punto base in map coordinate (QgsPointXY) + centerPt = punto centrale in map coordinate (QgsPointXY) + itemsNumber = numero di copie da creare + angleBetween = angolo tra un elemento e l'altro (radianti) + rows = numero di righe + distanceBetweenRows = distanza tra le righe in map coordinate + itemsRotation = True se si vuole ruotare gli elementi intorno al cerchio + addToLayer = se è True aggiunge le nuove entità al layer + highlightObj = se è diverso da None vengono aggiunge le geometrie all'oggetto QadHighlight + """ + firstAngle = qad_utils.getAngleBy2Pts(centerPt, basePt) + dist = qad_utils.getDistance(centerPt, basePt) + for row in range(0, rows): + angle = firstAngle + for i in range(0, itemsNumber): + newBasePt = qad_utils.getPolarPointByPtAngle(centerPt, angle, dist) + offsetX = newBasePt.x() - basePt.x() + offsetY = newBasePt.y() - basePt.y() + + if doMoveAndRotateGeom(plugIn, ent, offsetX, offsetY, \ + i * angleBetween if itemsRotation else None, \ + newBasePt, addToLayer, highlightObj) == False: + return False + angle = angle + angleBetween + + dist = dist + distanceBetweenRows + + return True + diff --git a/qad_break_cmd.py b/qad_break_cmd.py deleted file mode 100644 index 7b3b9172..00000000 --- a/qad_break_cmd.py +++ /dev/null @@ -1,297 +0,0 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - comando SPEZZA per tagliare un oggetto - - ------------------- - begin : 2014-01-09 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -from qgis.gui import * - - -from qad_generic_cmd import QadCommandClass -from qad_snapper import * -from qad_getpoint import * -from qad_textwindow import * -from qad_entsel_cmd import QadEntSelClass -from qad_msg import QadMsg -import qad_layer - -# Classe che gestisce il comando BREAK -class QadBREAKCommandClass(QadCommandClass): - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadBREAKCommandClass(self.plugIn) - - def getName(self): - return QadMsg.translate("Command_list", "BREAK") - - def getEnglishName(self): - return "BREAK" - - def connectQAction(self, action): - QObject.connect(action, SIGNAL("triggered()"), self.plugIn.runBREAKCommand) - - def getIcon(self): - return QIcon(":/plugins/qad/icons/break.png") - - def getNote(self): - # impostare le note esplicative del comando - return QadMsg.translate("Command_BREAK", "Breaks an object.") - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - self.EntSelClass = None - self.firstPt = None - self.secondPt = None - - def __del__(self): - QadCommandClass.__del__(self) - if self.EntSelClass is not None: - self.EntSelClass.entity.deselectOnLayer() - del self.EntSelClass - - def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): - if self.step == 1: # quando si é in fase di selezione entità - return self.EntSelClass.getPointMapTool(drawMode) - else: - return QadCommandClass.getPointMapTool(self, drawMode) - - def waitForEntsel(self, msgMapTool, msg): - if self.EntSelClass is not None: - del self.EntSelClass - self.step = 1 - self.EntSelClass = QadEntSelClass(self.plugIn) - self.EntSelClass.msg = QadMsg.translate("Command_BREAK", "Select the object to break: ") - # scarto la selezione di punti e poligoni - self.EntSelClass.checkPointLayer = False - self.EntSelClass.checkLineLayer = True - self.EntSelClass.checkPolygonLayer = False - self.EntSelClass.checkDimLayers = False - self.EntSelClass.onlyEditableLayers = True - - self.EntSelClass.run(msgMapTool, msg) - - - #============================================================================ - # breakFeatures - #============================================================================ - def breakFeatures(self): - f = self.EntSelClass.entity.getFeature() - if f is None: - return - - layer = self.EntSelClass.entity.layer - LineTempLayer = None - self.plugIn.beginEditCommand("Feature broken", layer) - - tolerance2ApproxCurve = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) - - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - g = self.layerToMapCoordinates(layer, f.geometry()) - result = qad_utils.breakQgsGeometry(g, self.firstPt, self.secondPt, \ - tolerance2ApproxCurve) - if result is not None: - line1 = result[0] - line2 = result[1] - atSubGeom = result[2] - if layer.geometryType() == QGis.Line: - if line1 is not None: - updGeom = qad_utils.setSubGeom(g, line1, atSubGeom) - if updGeom is None: - self.plugIn.destroyEditCommand() - return - brokenFeature1 = QgsFeature(f) - # trasformo la geometria nel crs del layer - brokenFeature1.setGeometry(self.mapToLayerCoordinates(layer, updGeom)) - # plugIn, layer, feature, refresh, check_validity - if qad_layer.updateFeatureToLayer(self.plugIn, layer, brokenFeature1, False, False) == False: - self.plugIn.destroyEditCommand() - return - if line2 is not None: - brokenFeature2 = QgsFeature(f) - # trasformo la geometria nel crs del layer - brokenFeature2.setGeometry(self.mapToLayerCoordinates(layer, line2)) - # plugIn, layer, feature, coordTransform, refresh, check_validity - if qad_layer.addFeatureToLayer(self.plugIn, layer, brokenFeature2, None, False, False) == False: - self.plugIn.destroyEditCommand() - return - else: - # aggiungo le linee nei layer temporanei di QAD - if LineTempLayer is None: - LineTempLayer = qad_layer.createQADTempLayer(self.plugIn, QGis.Line) - self.plugIn.addLayerToLastEditCommand("Feature broken", LineTempLayer) - - lineGeoms = [] - if line1 is not None: - lineGeoms.append(line1) - if line2 is not None: - lineGeoms.append(line2) - - # trasformo la geometria in quella dei layer temporanei - # plugIn, pointGeoms, lineGeoms, polygonGeoms, coord, refresh - if qad_layer.addGeometriesToQADTempLayers(self.plugIn, None, lineGeoms, None, None, False) == False: - self.plugIn.destroyEditCommand() - return - - updGeom = qad_utils.delSubGeom(g, atSubGeom) - - if updGeom is None or updGeom.isGeosEmpty(): # da cancellare - # plugIn, layer, feature id, refresh - if qad_layer.deleteFeatureToLayer(self.plugIn, layer, f.id(), False) == False: - self.plugIn.destroyEditCommand() - return - else: - brokenFeature1 = QgsFeature(f) - # trasformo la geometria nel crs del layer - brokenFeature1.setGeometry(self.mapToLayerCoordinates(layer, updGeom)) - # plugIn, layer, feature, refresh, check_validity - if qad_layer.updateFeatureToLayer(self.plugIn, layer, brokenFeature1, False, False) == False: - self.plugIn.destroyEditCommand() - return - - self.plugIn.endEditCommand() - - - def run(self, msgMapTool = False, msg = None): - if self.plugIn.canvas.mapRenderer().destinationCrs().geographicFlag(): - self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) - return True # fine comando - - if self.step == 0: - self.waitForEntsel(msgMapTool, msg) - return False # continua - - #========================================================================= - # RISPOSTA ALLA SELEZIONE DI UN'ENTITA' (da step = 0) - elif self.step == 1: - if self.EntSelClass.run(msgMapTool, msg) == True: - if self.EntSelClass.entity.isInitialized(): - layer = self.EntSelClass.entity.layer - self.firstPt = self.EntSelClass.point - self.plugIn.setLastPoint(self.firstPt) - - keyWords = QadMsg.translate("Command_BREAK", "First point") - prompt = QadMsg.translate("Command_BREAK", "Specify second break point or [{0}]: ").format(keyWords) - - self.step = 2 - self.getPointMapTool().refreshSnapType() # aggiorno lo snapType che può essere variato dal maptool di selezione entità - - englishKeyWords = "First point" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o enter o una parola chiave - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - None, \ - keyWords, QadInputModeEnum.NONE) - return False - else: - self.showMsg(QadMsg.translate("Command_BREAK", "Non ci sono geometrie in questa posizione.")) - self.waitForEntsel(msgMapTool, msg) - return False # continua - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DEL SECONDO PUNTO DI INTERRUZIONE (da step = 1) - elif self.step == 2: # dopo aver atteso un punto o una parola chiave si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - pass # opzione di default "secondo punto" - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if value is None or type(value) == unicode: - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_BREAK", "Specify first break point: ")) - self.step = 3 - elif type(value) == QgsPoint: # se é stato inserito il secondo punto - self.secondPt = value - self.plugIn.setLastPoint(self.secondPt) - self.breakFeatures() - return True - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DEL PRIMO PUNTO DI INTERRUZIONE (da step = 2) - elif self.step == 3: # dopo aver atteso un punto si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - self.firstPt = value - self.plugIn.setLastPoint(self.firstPt) - - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_BREAK", "Specify second break point: ")) - self.step = 4 - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DEL SECONDO PUNTO DI INTERRUZIONE (da step = 3) - elif self.step == 4: # dopo aver atteso un punto si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - self.secondPt = value - self.plugIn.setLastPoint(self.secondPt) - self.breakFeatures() - - return True diff --git a/qad_break_fun.py b/qad_break_fun.py new file mode 100644 index 00000000..f884e12a --- /dev/null +++ b/qad_break_fun.py @@ -0,0 +1,112 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + funzioni per comando SPEZZA per tagliare un oggetto + + ------------------- + begin : 2019-08-08 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from qgis.PyQt.QtCore import * +from qgis.PyQt.QtGui import * +from qgis.core import * +from qgis.gui import * + + +from .qad_multi_geom import getQadGeomAt, isLinearQadGeom +from .qad_geom_relations import * + + +# =============================================================================== +# breakQadGeometry +# =============================================================================== +def breakQadGeometry(qadGeom, firstPt, secondPt): + """ + la funzione spezza la geometria in un punto (se = None) o in due punti + come fa il trim. + = geometria da tagliare + = primo punto di divisione + = secondo punto di divisione + """ + if qadGeom is None: return None + + gType = qadGeom.whatIs() + if gType == "POINT" or gType == "MULTI_POINT": + return None + + # la funzione ritorna una lista con + # ( + # + # + # + # + # <"a sinistra di" se il punto é alla sinista della parte con i seguenti valori: + # - < 0 = sinistra (per linea, arco o arco di ellisse) o interno (per cerchi, ellissi) + # - > 0 = destra (per linea, arco o arco di ellisse) o esterno (per cerchi, ellissi) + # ) + result = getQadGeomClosestPart(qadGeom, firstPt) + myFirstPt = result[1] + atGeom = result[2] + atSubGeom = result[3] + subQadGeom = getQadGeomAt(qadGeom, atGeom, atSubGeom).copy() + + mySecondPt = None + if secondPt is not None: + # la funzione ritorna una lista con + # ( + # + # + # + # + # <"a sinistra di" se il punto é alla sinista della parte con i seguenti valori: + # - < 0 = sinistra (per linea, arco o arco di ellisse) o interno (per cerchi, ellissi) + # - > 0 = destra (per linea, arco o arco di ellisse) o esterno (per cerchi, ellissi) + # ) + result = getQadGeomClosestPart(qadGeom, secondPt) + mySecondPt = result[1] + atGeom = result[2] + atSubGeom = result[3] + # se le sottogeometrie sono diverse + if result[2] != atGeom or result[3] != atSubGeom: return None + + if mySecondPt is None or qad_utils.ptNear(myFirstPt, mySecondPt): + # divido la polilinea in 2 + if isLinearQadGeom(subQadGeom) == False: return None + + dummy = subQadGeom.breakOnPts(myFirstPt, None) + if dummy is None: return None + return [dummy[0], dummy[1], atGeom, atSubGeom] + else: # c'é anche il secondo punto di divisione + gType = subQadGeom.whatIs() + if gType == "CIRCLE": + endAngle = qad_utils.getAngleBy2Pts(subQadGeom.center, myFirstPt) + startAngle = qad_utils.getAngleBy2Pts(subQadGeom.center, mySecondPt) + arc = QadArc().set(subQadGeom.center, subQadGeom.radius, startAngle, endAngle) + return [arc, None, atGeom, atSubGeom] + + elif gType == "ELLIPSE": + endAngle = qad_utils.getAngleBy3Pts(subQadGeom.majorAxisFinalPt, subQadGeom.center, myFirstPt, False) + startAngle = qad_utils.getAngleBy3Pts(subQadGeom.majorAxisFinalPt, subQadGeom.center, mySecondPt, False) + ellipseArc = QadEllipseArc().set(subQadGeom.center, subQadGeom.majorAxisFinalPt, subQadGeom.axisRatio, startAngle, endAngle) + return [ellipseArc, None, atGeom, atSubGeom] + + else: + dummy = subQadGeom.breakOnPts(myFirstPt, mySecondPt) + return [dummy[0], dummy[1], atGeom, atSubGeom] + diff --git a/qad_cacheareas.py b/qad_cacheareas.py new file mode 100644 index 00000000..05d892f9 --- /dev/null +++ b/qad_cacheareas.py @@ -0,0 +1,451 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + classe per gestire in modalità cache le aree di un layer + + ------------------- + begin : 2013-03-08 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from qgis.PyQt.QtCore import * +from qgis.PyQt.QtGui import * +from qgis.core import * +from qgis.gui import * + + +from .qad_layer import createMemoryLayer + + +# =============================================================================== +# QadLayerCacheGeoms class. +# =============================================================================== +class QadLayerCacheGeoms(): + """ + Classe che gestisce la cache delle geometrie di un layer + """ + + + # ============================================================================ + # __init__ + # ============================================================================ + def __init__(self, layer): + self.layer = layer + self.cacheLayer = None + self.IsEmpty = True + # creo un layer temporaneo in memoria + self.__create_internal_layer() + + self.layer.featureAdded.connect(self.onFeatureAdded) + self.layer.featureDeleted.connect(self.onFeatureDeleted) + self.layer.geometryChanged.connect(self.onGeometryChanged) + + + def __create_internal_layer(self): + if self.cacheLayer is not None: + del self.cacheLayer + # creo un layer temporaneo in memoria + self.cacheLayer = createMemoryLayer("QadLayerCacheArea", getStrLayerGeomType(self.layer), self.layer.crs()) + + provider = self.cacheLayer.dataProvider() + provider.addAttributes([QgsField("index", QMetaType.Int, "Int")]) + self.cacheLayer.updateFields() + + if provider.capabilities() & QgsVectorDataProvider.CreateSpatialIndex: + provider.createSpatialIndex() + + + # ============================================================================ + # __del__ + # ============================================================================ + def __del__(self): + del self.cacheLayer + self.layer.featureAdded.disconnect(self.onFeatureAdded) + self.layer.featureDeleted.disconnect(self.onFeatureDeleted) + self.layer.geometryChanged.disconnect(self.onGeometryChanged) + + + # ============================================================================ + # insertFeature + # ============================================================================ + def insertFeature(self, feature): + # inserisce questa feature nella cache + if self.cacheLayer.startEditing() == False: + return False + + geom = feature.geometry() + if geom is None: + return + newFeature = QgsFeature() + newFeature.initAttributes(1) + newFeature.setAttribute(0, feature.id()) + newFeature.setGeometry(geom) + if self.cacheLayer.addFeature(newFeature): + if self.cacheLayer.commitChanges(): + self.IsEmpty = False + return True + else: + return False + else: + self.cacheLayer.rollBack() + return False + + + # ============================================================================ + # insertFeatures + # ============================================================================ + def insertFeatures(self, features): + if len(features) == 0: + return + + # inserisce questa feature nella cache + if self.cacheLayer.startEditing() == False: + return False + + newFeature = QgsFeature() + newFeature.initAttributes(1) + + for feature in features: + geom = feature.geometry() + if geom is None: + continue + newFeature.setAttribute(0, feature.id()) + newFeature.setGeometry(geom) + if self.cacheLayer.addFeature(newFeature) == False: + self.cacheLayer.rollBack() + return False + + + if self.cacheLayer.commitChanges(): + self.IsEmpty = False + return True + else: + # prova a vedere i messaggi di errore con il debug self.cacheLayer.commitErrors() + self.cacheLayer.rollBack() + return False + + + # ============================================================================ + # __deleteFeature + # ============================================================================ + def __deleteFeature(self, fid): + # inserisce questa feature nella cache + if self.cacheLayer.startEditing() == False: + return False + + if self.cacheLayer.deleteFeature(fid): + return self.cacheLayer.commitChanges() + else: + self.cacheLayer.rollBack() + return False + + + # ============================================================================ + # __updateFeature + # ============================================================================ + def __updateFeature(self, fid, geom): + feature = QgsFeature() + if self.cacheLayer.getFeatures(QgsFeatureRequest().setFilterFid(fid)).nextFeature(feature): + # aggiorna la geometria di questa feature nella cache + feature.setGeometry(geom) + + if self.cacheLayer.startEditing() == False: + return False + + if self.cacheLayer.updateFeature(feature): + return self.cacheLayer.commitChanges() + else: + self.cacheLayer.rollBack() + + return False + + + # ============================================================================ + # getFeatures + # ============================================================================ + def getFeatures(self, rect): + if self.IsEmpty: + return [] + feature = QgsFeature() + featureList = [] + featureIterator = self.cacheLayer.getFeatures(getFeatureRequest(rect, True)) + for feature in featureIterator: + featureList.append(QgsFeature(feature)) + + return featureList + + + # ============================================================================ + # extent + # ============================================================================ + def extent(self, rect): + return self.cacheLayer.extent() + + + # ============================================================================ + # onFeatureAdded + # ============================================================================ + def onFeatureAdded(self, fid): + feature = QgsFeature() + if self.layer.getFeatures(QgsFeatureRequest().setFilterFid(fid)).nextFeature(feature): + return self.insertFeature(feature) + return False + + + # ============================================================================ + # onFeatureDeleted + # ============================================================================ + def onFeatureDeleted(self, fid): + feature = QgsFeature() + if self.cacheLayer.getFeatures(QgsFeatureRequest().setFilterExpression("\"index\"=" + str(fid))).nextFeature(feature): + return self.__deleteFeature(feature.id()) + return False + + + # ============================================================================ + # onGeometryChanged + # ============================================================================ + def onGeometryChanged(self, fid, geom): + feature = QgsFeature() + if self.cacheLayer.getFeatures(QgsFeatureRequest().setFilterFid(fid)).nextFeature(feature): + return self.__updateFeature(feature.id(), geom) + + return False + + +# =============================================================================== +# QadLayerCacheGeomsDict class. +# =============================================================================== +class QadLayerCacheGeomsDict(): + """ + Classe che gestisce un dizionario delle chache delle geometrie dei layer + """ + + + # ============================================================================ + # __init__ + # ============================================================================ + def __init__(self, canvas = None): + self.canvas = canvas + + self.layersToCheck = None + self.checkPointLayer = True + self.checkLineLayer = True + self.checkPolygonLayer = True + self.onlyEditableLayers = False + + self.layerCacheAreaDict = dict() # dizionario delle chache delle aree dei layer + if self.canvas is not None: + self.canvas.extentsChanged.connect(self.onExtentsChanged) + self.canvas.layersChanged.connect(self.onLayersChanged) + #self.canvas.layerStyleOverridesChanged.connect(self.onLayerStyleOverridesChanged) da qgis 2.12 + + + # ============================================================================ + # __del__ + # ============================================================================ + def __del__(self): + del self.layerCacheAreaDict + if self.canvas is not None: + self.canvas.extentsChanged.disconnect(self.onExtentsChanged) + self.canvas.layersChanged.disconnect(self.onLayersChanged) + # self.canvas.layerStyleOverridesChanged.disconnect(self.onLayerStyleOverridesChanged) da qgis 2.12 + + + # ============================================================================ + # insertFeature + # ============================================================================ + def insertFeature(self, layer, feature): + # inserisce questa feature nella cache + layerId = layer.id() + # verifica se layer esiste già nel dizionario + if layerId not in self.layerCacheAreaDict: + cacheArea = QadLayerCacheGeoms(layer) + self.layerCacheAreaDict[layerId] = cacheArea + else: + cacheArea = self.layerCacheAreaDict[layerId] + cacheArea.insertFeature(feature) + + + # ============================================================================ + # insertFeatures + # ============================================================================ + def insertFeatures(self, layer, features): + if len(features) == 0: + return True + # inserisce questa feature nella cache + layerId = layer.id() + # verifica se layer esiste già nel dizionario + if layerId not in self.layerCacheAreaDict: + cacheArea = QadLayerCacheGeoms(layer) + self.layerCacheAreaDict[layerId] = cacheArea + else: + cacheArea = self.layerCacheAreaDict[layerId] + return cacheArea.insertFeatures(features) + + + # ============================================================================ + # refreshOnMapCanvasExtent + # ============================================================================ + def refreshOnMapCanvasExtent(self, layersToCheck = None, \ + checkPointLayer = True, checkLineLayer = True, checkPolygonLayer = True, \ + onlyEditableLayers = False): + """ + la funzione aggiorna la cache usando l'estensione dello schermo corrente. + layersToCheck = opzionale, lista dei layer in cui cercare + checkPointLayer = opzionale, considera i layer di tipo punto + checkLineLayer = opzionale, considera i layer di tipo linea + checkPolygonLayer = opzionale, considera i layer di tipo poligono + onlyEditableLayers = per cercare solo nei layer modificabili + """ + if self.canvas is None: + return False + + self.layersToCheck = layersToCheck + self.checkPointLayer = checkPointLayer + self.checkLineLayer = checkLineLayer + self.checkPolygonLayer = checkPolygonLayer + self.onlyEditableLayers = onlyEditableLayers + + if checkPointLayer == False and checkLineLayer == False and checkPolygonLayer == False: + return True + + boundBox = self.canvas.extent() # in map coordinate + + if layersToCheck is None: + # Tutti i layer visibili + _layers = self.canvas.layers() + else: + # solo la lista passata come parametro + _layers = layersToCheck + + for layer in _layers: # ciclo sui layer + # considero solo i layer vettoriali che sono filtrati per tipo + if (layer.type() == QgsMapLayer.VectorLayer) and \ + ((layer.geometryType() == QgsWkbTypes.PointGeometry and checkPointLayer == True) or \ + (layer.geometryType() == QgsWkbTypes.LineGeometry and checkLineLayer == True) or \ + (layer.geometryType() == QgsWkbTypes.PolygonGeometry and checkPolygonLayer == True)) and \ + (onlyEditableLayers == False or layer.isEditable()): + + # se il layer non è visibile a questa scala + if layer.hasScaleBasedVisibility() and \ + (self.canvas.mapSettings().scale() > layer.minimumScale() or self.canvas.mapSettings().scale() < layer.maximumScale()): + continue + + rect = self.canvas.mapSettings().mapToLayerCoordinates(layer, boundBox) # map to layer coordinate + + if self.refreshOnRectOnLayer(layer, rect) == False: + return False + + return True + + + # =============================================================================== + # refreshOnRectOnLayer + # =============================================================================== + def refreshOnRectOnLayer(self, layer, rect): + featureList = [] + featureIterator = layer.getFeatures(getFeatureRequest(rect, False)) + feature = QgsFeature() + for feature in featureIterator: + featureList.append(QgsFeature(feature)) + + # non ho trovato oggetti quindi lo segno in cache + return self.insertFeatures(layer, featureList) + + + # ============================================================================ + # onExtentsChanged + # ============================================================================ + def onExtentsChanged(self): + self.refreshOnMapCanvasExtent(self.layersToCheck, \ + self.checkPointLayer, self.checkLineLayer, self.checkPolygonLayer, \ + self.onlyEditableLayers) + + + # ============================================================================ + # onLayersChanged + # ============================================================================ + def onLayersChanged(self): + self.refreshOnMapCanvasExtent(self.layersToCheck, \ + self.checkPointLayer, self.checkLineLayer, self.checkPolygonLayer, \ + self.onlyEditableLayers) + + + # ============================================================================ + # onLayerStyleOverridesChanged + # ============================================================================ + def onLayerStyleOverridesChanged(self): + self.refreshOnMapCanvasExtent(self.layersToCheck, \ + self.checkPointLayer, self.checkLineLayer, self.checkPolygonLayer, \ + self.onlyEditableLayers) + + + # ============================================================================ + # getFeatures + # ============================================================================ + def getFeatures(self, layer, rect): + layerId = layer.id() + # verifica se layer esiste già nel dizionario + if layerId in self.layerCacheAreaDict: + return self.layerCacheAreaDict[layerId].getFeatures(rect) + else: + return [] + + + +################################ +# funzioni generiche + + +# ============================================================================ +# getFeatureRequest +# ============================================================================ +def getFeatureRequest(rect, SubsetOfAttribute): + request = QgsFeatureRequest() + request.setFilterRect(rect) + if SubsetOfAttribute == False: + request.setSubsetOfAttributes([]) + return request + + +# =============================================================================== +# getStrLayerGeomType +# =============================================================================== +def getStrLayerGeomType(layer): + wkbTypeLayer = layer.wkbType() + if wkbTypeLayer == QgsWkbTypes.NoGeometry: + return "NoGeometry" + + gType = layer.geometryType() + if QgsWkbTypes.isMultiType(wkbTypeLayer) == False: + if gType == QgsWkbTypes.PointGeometry: + return "Point" + elif gType == QgsWkbTypes.LineGeometry: + return "LineString" + elif gType == QgsWkbTypes.PolygonGeometry: + return "Polygon" + else: + if gType == QgsWkbTypes.PointGeometry: + return "MultiPoint" + elif gType == QgsWkbTypes.LineGeometry: + return "MultiLineString" + elif gType == QgsWkbTypes.PolygonGeometry: + return "MultiPolygon" + + return "NoGeometry" \ No newline at end of file diff --git a/qad_circle.py b/qad_circle.py index b071196e..d0c30420 100644 --- a/qad_circle.py +++ b/qad_circle.py @@ -1,1932 +1,516 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - classe per la gestione dei cerchi - - ------------------- - begin : 2013-05-22 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -from qgis.gui import * -import math -import sys - -import qad_utils -from qad_variables import * -from qad_arc import * - - -#=============================================================================== -# QadArc arc class -#=============================================================================== -class QadCircle(): - - def __init__(self, circle = None): - if circle is not None: - self.set(circle.center, circle.radius) - else: - self.center = None - self.radius = None - - def whatIs(self): - return "CIRCLE" - - def set(self, center, radius): - if radius <=0: - return False - self.center = QgsPoint(center) - self.radius = radius - return True - - def transform(self, ct): - """Transform this geometry as described by CoordinateTranasform ct.""" - self.center = coordTransform.transform(self.center) - - def transformFromCRSToCRS(self, sourceCRS, destCRS): - """Transform this geometry as described by CRS.""" - if (sourceCRS is not None) and (destCRS is not None) and sourceCRS != destCRS: - coordTransform = QgsCoordinateTransform(sourceCRS, destCRS) # trasformo le coord - self.center = coordTransform.transform(self.center) - - def __eq__(self, circle): - """self == other""" - if self.center != circle.center or self.radius != circle.radius: - return False - else: - return True - - def __ne__(self, circle): - """self != other""" - if self.center != circle.center or self.radius != circle.radius: - return True - else: - return False - - def length(self): - return 2 * math.pi * self.radius - - def lengthBetween2Points(self, pt1, pt2, leftOfPt1): - """ - Calcola la distanza tra 2 punti sulla circonferenza. L'arco considerato può essere - quello a sinistra o a destra di (vedi ) - se é boolean allora se = True viene considerato l'arco a sin di pt1 - se é float allora significa che si tratta della direzione della tangente su pt1 - e se la direzione è a sin viene considerato l'arco a sin di pt1 - """ - if qad_utils.ptNear(pt1, pt2): # se i punti sono così vicini da essere considerati uguali - return 0 - - if type(leftOfPt1) == float: # direzione della tangente su pt1 - startAngle = qad_utils.getAngleBy2Pts(self.center, pt1) - if qad_utils.doubleNear(qad_utils.normalizeAngle(startAngle + math.pi / 2), - qad_utils.normalizeAngle(leftOfPt1)): - _leftOfPt1 = True - else: - _leftOfPt1 = False - else: # booolean - _leftOfPt1 = leftOfPt1 - - if _leftOfPt1: # arco a sinistra di pt1 - startAngle = qad_utils.getAngleBy2Pts(self.center, pt1) - endAngle = qad_utils.getAngleBy2Pts(self.center, pt2) - else: # arco a destra di pt1 - startAngle = qad_utils.getAngleBy2Pts(self.center, pt2) - endAngle = qad_utils.getAngleBy2Pts(self.center, pt1) - - if startAngle < endAngle: - totalAngle = endAngle - startAngle - else: - totalAngle = (2 * math.pi - startAngle) + endAngle - - return self.radius * totalAngle - - def area(self): - return math.pi * self.radius * self.radius - - def isPtOnCircle(self, point): - dist = qad_utils.getDistance(self.center, point) - return qad_utils.doubleNear(self.radius, dist) - - def getQuadrantPoints(self): - # ritorna i punti quadranti: pt in alto, pt in basso, a destra, a sinistra del centro - pt1 = QgsPoint(self.center.x(), self.center.y() + self.radius) - pt2 = QgsPoint(self.center.x(), self.center.y()- self.radius) - pt3 = QgsPoint(self.center.x() + self.radius, self.center.y()) - pt4 = QgsPoint(self.center.x() - self.radius, self.center.y()) - return [pt1, pt2, pt3, pt4] - - - def getTanPoints(self, point): - dist = qad_utils.getDistance(self.center, point) - if dist < self.radius: - return [] - if dist == self.radius: - return [point] - - angleOffSet = math.asin(self.radius / dist) - angleOffSet = (math.pi / 2) - angleOffSet - angle = qad_utils.getAngleBy2Pts(self.center, point) - - pt1 = qad_utils.getPolarPointByPtAngle(self.center, angle + angleOffSet, self.radius) - pt2 = qad_utils.getPolarPointByPtAngle(self.center, angle - angleOffSet, self.radius) - return [pt1, pt2] - - - def getPerpendicularPoints(self, point): - angle = qad_utils.getAngleBy2Pts(self.center, point) - pt1 = qad_utils.getPolarPointByPtAngle(self.center, angle, self.radius) - pt2 = qad_utils.getPolarPointByPtAngle(self.center, angle + math.pi, self.radius) - return [pt1, pt2] - - - def getIntersectionPointsWithCircle(self, circle): - result = [] - # se i punti sono così vicini da essere considerati uguali - if qad_utils.ptNear(self.center, circle.center): # stesso centro - return result - distFromCenters = qad_utils.getDistance(self.center, circle.center) - distFromCirc = distFromCenters - self.radius - circle.radius - - # se è così vicino allo zero da considerarlo = 0 - if qad_utils.doubleNear(distFromCirc, 0): - angle = qad_utils.getAngleBy2Pts(self.center, circle.center) - result.append(qad_utils.getPolarPointByPtAngle(self.center, angle, self.radius)) - return result - - if distFromCirc > 0: # i cerchi sono troppo distanti - return result - - x2_self = self.center.x() * self.center.x() # X del centro del cerchio al quadrato - x2_circle = circle.center.x() * circle.center.x() # Y del centro del cerchio al quadrato - radius2_self = self.radius * self.radius # raggio del cerchio al quadrato - radius2_circle = circle.radius * circle.radius # raggio del cerchio al quadrato - - if qad_utils.doubleNear(self.center.y(), circle.center.y()): - x1 = x2_circle - x2_self + radius2_self - radius2_circle - x1 = x1 / (2 * (circle.center.x() - self.center.x())) - x2 = x1 - D = radius2_self - ((x1 - self.center.x()) * (x1 - self.center.x())) - # se D è così vicino a zero - if qad_utils.doubleNear(D, 0.0): - D = 0 - elif D < 0: # non si può fare la radice quadrata di un numero negativo - return result - E = math.sqrt(D) - - y1 = self.center.y() + E - y2 = self.center.y() - E - else: - y2_self = self.center.y() * self.center.y() # Y del centro del cerchio al quadrato - y2_circle = circle.center.y() * circle.center.y() # Y del centro del cerchio al quadrato - - a = (self.center.x() - circle.center.x()) / (circle.center.y() - self.center.y()) - b = x2_circle - x2_self + y2_circle - y2_self + radius2_self - radius2_circle - b = b / (2 * (circle.center.y() - self.center.y())) - - A = 1 + (a * a) - B = (2 * a * b) - (2 * self.center.x()) - (2 * a * self.center.y()) - C = (b * b) - (2 * self.center.y() * b) + x2_self + y2_self - radius2_self - D = (B * B) - (4 * A * C) - # se D è così vicino a zero - if qad_utils.doubleNear(D, 0.0): - D = 0 - elif D < 0: # non si può fare la radice quadrata di un numero negativo - return result - E = math.sqrt(D) - - x1 = (-B + E) / (2 * A) - y1 = a * x1 + b - - x2 = (-B - E) / (2 * A) - y2 = a * x2 + b - - result.append(QgsPoint(x1, y1)) - if x1 != x2 or y1 != y2: # i punti non sono coincidenti - result.append(QgsPoint(x2, y2)) - - return result - - - def getIntersectionPointsWithInfinityLine(self, p1, p2): - result = [] - if p1 == p2: - return result - - x2_self = self.center.x() * self.center.x() # X del centro del cerchio al quadrato - y2_self = self.center.y() * self.center.y() # Y del centro del cerchio al quadrato - radius2_self = self.radius * self.radius # raggio del cerchio al quadrato - - diffX = p2.x() - p1.x() - if diffX == 0: # se la retta passante per p1 e p2 é verticale - B = -2 * self.center.y() - C = x2_self + y2_self + (p1.x() * p1.x()) - (2* p1.x() * self.center.x()) - radius2_self - D = (B * B) - (4 * C) - # se D è così vicino a zero - if qad_utils.doubleNear(D, 0.0): - D = 0 - elif D < 0: # non si può fare la radice quadrata di un numero negativo - return result - E = math.sqrt(D) - - y1 = (-B + E) / 2 - x1 = p1.x() - - y2 = (-B - E) / 2 - x2 = p1.x() - else: - m = (p2.y() - p1.y()) / diffX - q = p1.y() - (m * p1.x()) - A = 1 + (m * m) - B = (2 * m * q) - (2 * self.center.x()) - (2 * m * self.center.y()) - C = x2_self + (q * q) + y2_self - (2 * q * self.center.y()) - radius2_self - - D = (B * B) - 4 * A * C - # se D è così vicino a zero - if qad_utils.doubleNear(D, 0.0): - D = 0 - elif D < 0: # non si può fare la radice quadrata di un numero negativo - return result - E = math.sqrt(D) - - x1 = (-B + E) / (2 * A) - y1 = p1.y() + m * x1 - m * p1.x() - - x2 = (-B - E) / (2 * A) - y2 = p1.y() + m * x2 - m * p1.x() - - result.append(QgsPoint(x1, y1)) - if x1 != x2 or y1 != y2: # i punti non sono coincidenti - result.append(QgsPoint(x2, y2)) - - return result - - - #============================================================================ - # getIntersectionPointsWithSegment - #============================================================================ - def getIntersectionPointsWithSegment(self, p1, p2): - result = [] - intPtList = self.getIntersectionPointsWithInfinityLine(p1, p2) - for intPt in intPtList: - if qad_utils.isPtOnSegment(p1, p2, intPt): - result.append(intPt) - return result - - - #============================================================================ - # getTangentsWithCircle - #============================================================================ - def getTangentsWithCircle(self, circle): - """ - la funzione ritorna una lista di linee che sono le tangenti ai due cerchi - """ - tangents = [] - - x1 = self.center[0] - y1 = self.center[1] - r1 = self.radius - x2 = circle.center[0] - y2 = circle.center[1] - r2 = circle.radius - - d_sq = (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) - if (d_sq <= (r1-r2)*(r1-r2)): - return tangents - - d = math.sqrt(d_sq); - vx = (x2 - x1) / d; - vy = (y2 - y1) / d; - - # Let A, B be the centers, and C, D be points at which the tangent - # touches first and second circle, and n be the normal vector to it. - # - # We have the system: - # n * n = 1 (n is a unit vector) - # C = A + r1 * n - # D = B +/- r2 * n - # n * CD = 0 (common orthogonality) - # - # n * CD = n * (AB +/- r2*n - r1*n) = AB*n - (r1 -/+ r2) = 0, <=> - # AB * n = (r1 -/+ r2), <=> - # v * n = (r1 -/+ r2) / d, where v = AB/|AB| = AB/d - # This is a linear equation in unknown vector n. - sign1 = +1 - while sign1 >= -1: - c = (r1 - sign1 * r2) / d; - - # Now we're just intersecting a line with a circle: v*n=c, n*n=1 - - if (c*c > 1.0): - sign1 = sign1 - 2 - continue - - h = math.sqrt(max(0.0, 1.0 - c*c)); - - sign2 = +1 - while sign2 >= -1: - nx = vx * c - sign2 * h * vy; - ny = vy * c + sign2 * h * vx; - - tangent = [] - tangent.append(QgsPoint(x1 + r1 * nx, y1 + r1 * ny)) - tangent.append(QgsPoint(x2 + sign1 * r2 * nx, y2 + sign1 * r2 * ny)) - tangents.append(tangent) - sign2 = sign2 - 2 - - sign1 = sign1 - 2 - - return tangents - - - #============================================================================ - # asPolyline - #============================================================================ - def asPolyline(self, tolerance2ApproxCurve = None, atLeastNSegment = None): - """ - ritorna una lista di punti che definisce il cerchio - """ - - if tolerance2ApproxCurve is None: - tolerance = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) - else: - tolerance = tolerance2ApproxCurve - - if atLeastNSegment is None: - _atLeastNSegment = QadVariables.get(QadMsg.translate("Environment variables", "CIRCLEMINSEGMENTQTY"), 12) - else: - _atLeastNSegment = atLeastNSegment - - # Calcolo la lunghezza del segmento con pitagora - dummy = self.radius - tolerance - if dummy <= 0: # se la tolleranza è troppo bassa rispetto al raggio - SegmentLen = self.radius - else: - dummy = (self.radius * self.radius) - (dummy * dummy) - SegmentLen = math.sqrt(dummy) # radice quadrata - SegmentLen = SegmentLen * 2 - - if SegmentLen == 0: # se la tolleranza è troppo bassa la lunghezza del segmento diventa zero - return None - - # calcolo quanti segmenti ci vogliono (non meno di _atLeastNSegment) - SegmentTot = math.ceil(self.length() / SegmentLen) - if SegmentTot < _atLeastNSegment: - SegmentTot = _atLeastNSegment - - points = [] - # primo punto - firsPt = qad_utils.getPolarPointByPtAngle(self.center, 0, self.radius) - points.append(firsPt) - - i = 1 - angle = 0 - offSetAngle = 2 * math.pi / SegmentTot - while i < SegmentTot: - angle = angle + offSetAngle - pt = qad_utils.getPolarPointByPtAngle(self.center, angle, self.radius) - points.append(pt) - i = i + 1 - - # ultimo punto (come il primo) - points.append(firsPt) - return points - - - #============================================================================ - # fromPolyline - #============================================================================ - def fromPolyline(self, points, startVertex, atLeastNSegment = None): - """ - setta le caratteristiche del primo cerchio incontrato nella lista di punti - partendo dalla posizione startVertex (0-indexed) - ritorna la posizione nella lista del punto iniziale e finale se é stato trovato un cerchio - altrimenti None. - N.B. in punti NON devono essere in coordinate geografiche - """ - if atLeastNSegment is None: - _atLeastNSegment = QadVariables.get(QadMsg.translate("Environment variables", "CIRCLEMINSEGMENTQTY"), 12) - else: - _atLeastNSegment = atLeastNSegment - - totPoints = len(points) - # perchésia un cerchio ci vogliono almeno _atLeastNSegment segmenti - if (totPoints - 1) - startVertex < _atLeastNSegment or _atLeastNSegment < 2: - return None - - # per problemi di approssimazione dei calcoli - epsilon = 1.e-4 # percentuale del raggio per ottenere max diff. di una distanza con il raggio - - InfinityLinePerpOnMiddle1 = None - InfinityLinePerpOnMiddle2 = None - - nSegment = 0 - i = startVertex - while i < totPoints - 1: - if InfinityLinePerpOnMiddle1 is None: - InfinityLinePerpOnMiddle1 = qad_utils.getInfinityLinePerpOnMiddle(points[i], points[i + 1]) - nStartVertex = i - nSegment = 1 - i = i + 1 - continue - elif InfinityLinePerpOnMiddle2 is None: - InfinityLinePerpOnMiddle2 = qad_utils.getInfinityLinePerpOnMiddle(points[i], points[i + 1]) - if InfinityLinePerpOnMiddle2 is None: - InfinityLinePerpOnMiddle1 = None - nSegment = 0 - else: - # calcolo il presunto centro con 2 segmenti - center = qad_utils.getIntersectionPointOn2InfinityLines(InfinityLinePerpOnMiddle1[0], \ - InfinityLinePerpOnMiddle1[1], \ - InfinityLinePerpOnMiddle2[0], \ - InfinityLinePerpOnMiddle2[1]) - if center is None: # linee parallele - InfinityLinePerpOnMiddle1 = InfinityLinePerpOnMiddle2 - InfinityLinePerpOnMiddle2 = None - nStartVertex = i - nSegment = 1 - else: - nSegment = nSegment + 1 - radius = qad_utils.getDistance(center, points[i + 1]) # calcolo il presunto raggio - maxDifference = radius * epsilon - - # calcolo il verso dell'arco e l'angolo dell'arco - # se un punto intermedio dell'arco è a sinistra del - # segmento che unisce i due punti allora il verso è antiorario - startClockWise = True if qad_utils.leftOfLine(points[i], points[i - 1], points[i + 1]) < 0 else False - angle = qad_utils.getAngleBy3Pts(points[i - 1], center, points[i + 1], startClockWise) - else: # e sono già stati valutati almeno 2 segmenti - # calcolo la distanza del punto dal presunto centro - dist = qad_utils.getDistance(center, points[i + 1]) - # calcolo il verso dell'arco e l'angolo - clockWise = True if qad_utils.leftOfLine(points[i], points[i - 1], points[i + 1]) < 0 else False - angle = angle + qad_utils.getAngleBy3Pts(points[i], center, points[i + 1], startClockWise) - - # se la distanza è così vicina a quella del raggio - # il verso dell'arco deve essere quello iniziale - # l'angolo dell'arco non può essere > 360 gradi - if qad_utils.doubleNear(radius, dist, maxDifference) and \ - startClockWise == clockWise and \ - (angle < 2 * math.pi or qad_utils.doubleNear(angle, 2 * math.pi)): - nSegment = nSegment + 1 # anche questo segmento fa parte del cerchio - else: # questo segmento non fa parte del cerchio - nStartVertex = i - nSegment = 1 - InfinityLinePerpOnMiddle1 = qad_utils.getInfinityLinePerpOnMiddle(points[i], points[i + 1]) - InfinityLinePerpOnMiddle2 = None - - i = i + 1 - - # se sono stati trovati un numero sufficiente di segmenti successivi - if nSegment >= _atLeastNSegment: - nEndVertex = nStartVertex + nSegment - # se il punto iniziale e quello finale coincidono é un cerchio - if points[nStartVertex] == points[nEndVertex]: - self.center = center - self.radius = radius - return nStartVertex, nEndVertex - - return None - - - #============================================================================ - # fromCenterPtArea - #============================================================================ - def fromCenterArea(self, centerPt, area): - """ - setta le caratteristiche del cerchio attraverso: - il punto centrale - area - """ - if centerPt is None or area <= 0: - return False - self.center = centerPt - self.radius = math.sqrt(area / math.pi) - return True - - - #============================================================================ - # from3Pts - #============================================================================ - def from3Pts(self, firstPt, secondPt, thirdPt): - """ - setta le caratteristiche del cerchio attraverso: - punto iniziale - secondo punto (intermedio) - punto finale - """ - InfinityLinePerpOnMiddle1 = qad_utils.getInfinityLinePerpOnMiddle(firstPt, secondPt) - InfinityLinePerpOnMiddle2 = qad_utils.getInfinityLinePerpOnMiddle(secondPt, thirdPt) - if InfinityLinePerpOnMiddle1 is None or InfinityLinePerpOnMiddle2 is None: - return False - self.center = qad_utils.getIntersectionPointOn2InfinityLines(InfinityLinePerpOnMiddle1[0], \ - InfinityLinePerpOnMiddle1[1], \ - InfinityLinePerpOnMiddle2[0], \ - InfinityLinePerpOnMiddle2[1]) - if self.center is None: # linee parallele - return False - self.radius = qad_utils.getDistance(self.center, firstPt) - return True - - - #============================================================================ - # from3TanPts - #============================================================================ - def from3TanPts(self, geom1, pt1, geom2, pt2, geom3, pt3): - """ - setta le caratteristiche del cerchio attraverso - tre oggetti di tangenza per le estremità del diametro: - geometria 1 di tangenza (linea, arco o cerchio) - punto di selezione geometria 1 - geometria 2 di tangenza (linea, arco o cerchio) - punto di selezione geometria 2 - """ - obj1 = qad_utils.whatGeomIs(pt1, geom1) - obj2 = qad_utils.whatGeomIs(pt2, geom2) - obj3 = qad_utils.whatGeomIs(pt3, geom3) - - if (obj1 is None) or (obj2 is None) or (obj3 is None): - return False - - if (type(obj1) == list or type(obj1) == tuple): - obj1Type = "LINE" - else: - obj1Type = obj1.whatIs() - if obj1Type == "ARC": # se è arco lo trasformo in cerchio - circle = QadCircle() - circle.set(obj1.center, obj1.radius) - obj1 = circle - obj1Type = "CIRCLE" - - if (type(obj2) == list or type(obj2) == tuple): - obj2Type = "LINE" - else: - obj2Type = obj2.whatIs() - if obj2Type == "ARC": # se è arco lo trasformo in cerchio - circle = QadCircle() - circle.set(obj2.center, obj2.radius) - obj2 = circle - obj2Type = "CIRCLE" - - if (type(obj3) == list or type(obj3) == tuple): - obj3Type = "LINE" - else: - obj3Type = obj3.whatIs() - if obj3Type == "ARC": # se è arco lo trasformo in cerchio - circle = QadCircle() - circle.set(obj3.center, obj3.radius) - obj3 = circle - obj3Type = "CIRCLE" - - if obj1Type == "LINE": - if obj2Type == "LINE": - if obj3Type == "LINE": - return self.fromLineLineLineTanPts(obj1, pt1, obj2, pt2, obj3, pt3) - elif obj3Type == "CIRCLE": - return self.fromLineLineCircleTanPts(obj1, pt1, obj2, pt2, obj3, pt3) - elif obj2Type == "CIRCLE": - if obj3Type == "LINE": - return self.fromLineLineCircleTanPts(obj1, pt1, obj3, pt3, obj2, pt2) - elif obj3Type == "CIRCLE": - return self.fromLineCircleCircleTanPts(obj1, pt1, obj2, pt2, obj3, pt3) - elif obj1Type == "CIRCLE": - if obj2Type == "LINE": - if obj3Type == "LINE": - return self.fromLineLineCircleTanPts(obj2, pt2, obj3, pt3, obj1, pt1) - elif obj3Type == "CIRCLE": - return self.fromLineCircleCircleTanPts(obj2, pt2, obj1, pt1, obj3, pt3) - elif obj2Type == "CIRCLE": - if obj3Type == "LINE": - return self.fromLineCircleCircleTanPts(obj3, pt3, obj1, pt1, obj2, pt2) - elif obj3Type == "CIRCLE": - return self.fromCircleCircleCircleTanPts(obj1, pt1, obj2, pt2, obj3, pt3) - - return False - - - #============================================================================ - # fromLineLineLineTanPts - #============================================================================ - def fromLineLineLineTanPts(self, line1, pt1, line2, pt2, line3, pt3): - """ - setta le caratteristiche del cerchio attraverso tre linee: - linea1 di tangenza (lista di 2 punti) - punto di selezione linea1 - linea2 di tangenza (lista di 2 punti) - punto di selezione linea2 - linea3 di tangenza (lista di 2 punti) - punto di selezione linea3 - """ - circleList = [] - - # Punti di intersezione delle rette (line1, line2, line3) - ptInt1 = qad_utils.getIntersectionPointOn2InfinityLines(line1[0], line1[1], \ - line2[0], line2[1]) - ptInt2 = qad_utils.getIntersectionPointOn2InfinityLines(line2[0], line2[1], \ - line3[0], line3[1]) - ptInt3 = qad_utils.getIntersectionPointOn2InfinityLines(line3[0], line3[1], \ - line1[0], line1[1]) - # tre rette parallele - if (ptInt1 is None) and (ptInt2 is None): - return circleList - - if (ptInt1 is None): # la linea1 e linea2 sono parallele - circleList.extend(self.from2ParLinesLineTanPts(line1, line2, line3)) - elif (ptInt2 is None): # la linea2 e linea3 sono parallele - circleList.extend(self.from2ParLinesLineTanPts(line2, line3, line1)) - elif (ptInt3 is None): # la linea3 e linea1 sono parallele - circleList.extend(self.from2ParLinesLineTanPts(line3, line1, line2)) - else: - # Bisettrici degli angoli interni del triangolo avente come vertici i punti di intersezione delle rette - Bisector123 = qad_utils.getBisectorInfinityLine(ptInt1, ptInt2, ptInt3) - Bisector231 = qad_utils.getBisectorInfinityLine(ptInt2, ptInt3, ptInt1) - Bisector312 = qad_utils.getBisectorInfinityLine(ptInt3, ptInt1, ptInt2) - # Punto di intersezione delle bisettrici = centro delle circonferenza inscritta al triangolo - center = qad_utils.getIntersectionPointOn2InfinityLines(Bisector123[0], Bisector123[1], \ - Bisector231[0], Bisector231[1]) - # Perpendicolari alle rette line1 passanti per il centro della circonferenza inscritta - ptPer = qad_utils.getPerpendicularPointOnInfinityLine(line1[0], line1[1], center) - radius = qad_utils.getDistance(center, ptPer) - circle = QadCircle() - circle.set(center, radius) - circleList.append(circle) - - # Bisettrici degli angoli esterni del triangolo - angle = qad_utils.getAngleBy2Pts(Bisector123[0], Bisector123[1]) + math.pi / 2 - Bisector123 = [ptInt2, qad_utils.getPolarPointByPtAngle(ptInt2, angle, 10)] - - angle = qad_utils.getAngleBy2Pts(Bisector231[0], Bisector231[1]) + math.pi / 2 - Bisector231 = [ptInt3, qad_utils.getPolarPointByPtAngle(ptInt3, angle, 10)] - - angle = qad_utils.getAngleBy2Pts(Bisector312[0], Bisector312[1]) + math.pi / 2 - Bisector312 = [ptInt1, qad_utils.getPolarPointByPtAngle(ptInt1, angle, 10)] - # Punti di intersezione delle bisettrici = centro delle circonferenze ex-inscritte - center = qad_utils.getIntersectionPointOn2InfinityLines(Bisector123[0], Bisector123[1], \ - Bisector231[0], Bisector231[1]) - ptPer = qad_utils.getPerpendicularPointOnInfinityLine(ptInt2, ptInt3, center) - radius = qad_utils.getDistance(center, ptPer) - circle = QadCircle() - circle.set(center, radius) - circleList.append(circle) - - center = qad_utils.getIntersectionPointOn2InfinityLines(Bisector231[0], Bisector231[1], \ - Bisector312[0], Bisector312[1]) - ptPer = qad_utils.getPerpendicularPointOnInfinityLine(ptInt3, ptInt1, center) - radius = qad_utils.getDistance(center, ptPer) - circle = QadCircle() - circle.set(center, radius) - circleList.append(circle) - - center = qad_utils.getIntersectionPointOn2InfinityLines(Bisector312[0], Bisector312[1], \ - Bisector123[0], Bisector123[1]) - ptPer = qad_utils.getPerpendicularPointOnInfinityLine(ptInt1, ptInt2, center) - radius = qad_utils.getDistance(center, ptPer) - circle = QadCircle() - circle.set(center, radius) - circleList.append(circle) - - if len(circleList) == 0: - return False - - AvgList = [] - Avg = sys.float_info.max - for circleTan in circleList: - del AvgList[:] # svuoto la lista - - ptInt = qad_utils.getPerpendicularPointOnInfinityLine(line1[0], line1[1], circleTan.center) - AvgList.append(qad_utils.getDistance(ptInt, pt1)) - - ptInt = qad_utils.getPerpendicularPointOnInfinityLine(line2[0], line2[1], circleTan.center) - AvgList.append(qad_utils.getDistance(ptInt, pt2)) - - ptInt = qad_utils.getPerpendicularPointOnInfinityLine(line3[0], line3[1], circleTan.center) - AvgList.append(qad_utils.getDistance(ptInt, pt3)) - - currAvg = qad_utils.numericListAvg(AvgList) - if currAvg < Avg: # mediamente più vicino - Avg = currAvg - self.center = circleTan.center - self.radius = circleTan.radius - - return True - - - #=========================================================================== - # from2ParLinesLineTanPts - #=========================================================================== - def from2ParLinesLineTanPts(self, parLine1, parLine2, line3): - """ - setta le caratteristiche del cerchio attraverso 2 linee parallele e una terza linea non parallela: - linea1 di tangenza (lista di 2 punti) parallela a linea2 - linea2 di tangenza (lista di 2 punti) parallela a linea1 - linea3 di tangenza (lista di 2 punti) - """ - circleList = [] - - ptInt2 = qad_utils.getIntersectionPointOn2InfinityLines(parLine2[0], parLine2[1], \ - line3[0], line3[1]) - ptInt3 = qad_utils.getIntersectionPointOn2InfinityLines(line3[0], line3[1], \ - parLine1[0], parLine1[1]) - - if parLine1[0] == ptInt3: - pt = parLine1[1] - else: - pt = parLine1[0] - Bisector123 = qad_utils.getBisectorInfinityLine(pt, ptInt2, ptInt3) - - if parLine2[0] == ptInt2: - pt = parLine2[1] - else: - pt = parLine2[0] - Bisector312 = qad_utils.getBisectorInfinityLine(pt, ptInt3, ptInt2) - - # Punto di intersezione delle bisettrici = centro delle circonferenza - center = qad_utils.getIntersectionPointOn2InfinityLines(Bisector123[0], Bisector123[1], \ - Bisector312[0], Bisector312[1]) - ptPer = qad_utils.getPerpendicularPointOnInfinityLine(parLine1[0], parLine1[1], center) - radius = qad_utils.getDistance(center, ptPer) - circle = QadCircle() - circle.set(center, radius) - circleList.append(circle) - - # Bisettrici degli angoli esterni - Bisector123 = Bisector123 + math.pi / 2 - Bisector312 = Bisector312 + math.pi / 2 - # Punto di intersezione delle bisettrici = centro delle circonferenza - center = qad_utils.getIntersectionPointOn2InfinityLines(Bisector123[0], Bisector123[1], \ - Bisector312[0], Bisector312[1]) - ptPer = qad_utils.getPerpendicularPointOnInfinityLine(parLine1[0], parLine1[1], center) - radius = qad_utils.getDistance(center, ptPer) - circle = QadCircle() - circle.set(center, radius) - circleList.append(circle) - - return circleList - - - #============================================================================ - # fromLineLineCircleTanPts - #============================================================================ - def fromLineLineCircleTanPts(self, line1, pt1, line2, pt2, circle, pt3): - """ - setta le caratteristiche del cerchio attraverso tre linee: - linea1 di tangenza (lista di 2 punti) - punto di selezione linea1 - linea2 di tangenza (lista di 2 punti) - punto di selezione linea2 - cerchio di tangenza (oggetto QadCircle) - punto di selezione cerchio - """ - circleList = [] - - circleList.extend(qad_utils.solveCircleTangentTo2LinesAndCircle(line1, line2, circle, -1, -1)) - circleList.extend(qad_utils.solveCircleTangentTo2LinesAndCircle(line1, line2, circle, -1, 1)) - circleList.extend(qad_utils.solveCircleTangentTo2LinesAndCircle(line1, line2, circle, 1, -1)) - circleList.extend(qad_utils.solveCircleTangentTo2LinesAndCircle(line1, line2, circle, 1, 1)) - - if len(circleList) == 0: - return False - - AvgList = [] - Avg = sys.float_info.max - for circleTan in circleList: - del AvgList[:] # svuoto la lista - - ptInt = qad_utils.getPerpendicularPointOnInfinityLine(line1[0], line1[1], circleTan.center) - AvgList.append(qad_utils.getDistance(ptInt, pt1)) - - ptInt = qad_utils.getPerpendicularPointOnInfinityLine(line2[0], line2[1], circleTan.center) - AvgList.append(qad_utils.getDistance(ptInt, pt2)) - - angle = qad_utils.getAngleBy2Pts(circleTan.center, circle.center) - if qad_utils.getDistance(circleTan.center, circle.center) < circle.radius: # cerchio interno - angle = angle + math.pi / 2 - ptInt = qad_utils.getPolarPointByPtAngle(circleTan.center, angle, circleTan.radius) - AvgList.append(qad_utils.getDistance(ptInt, pt3)) - - currAvg = qad_utils.numericListAvg(AvgList) - if currAvg < Avg: # mediamente più vicino - Avg = currAvg - self.center = circleTan.center - self.radius = circleTan.radius - - return True - - - #============================================================================ - # fromLineCircleCircleTanPts - #============================================================================ - def fromLineCircleCircleTanPts(self, line, pt, circle1, pt1, circle2, pt2): - """ - setta le caratteristiche del cerchio attraverso tre linee: - linea di tangenza (lista di 2 punti) - punto di selezione linea - cerchio1 di tangenza (oggetto QadCircle) - punto di selezione cerchio1 - cerchio2 di tangenza (oggetto QadCircle) - punto di selezione cerchio2 - """ - circleList = [] - - circleList.extend(qad_utils.solveCircleTangentToLineAnd2Circles(line, circle1, circle2, -1, -1)) - circleList.extend(qad_utils.solveCircleTangentToLineAnd2Circles(line, circle1, circle2, -1, 1)) - circleList.extend(qad_utils.solveCircleTangentToLineAnd2Circles(line, circle1, circle2, 1, -1)) - circleList.extend(qad_utils.solveCircleTangentToLineAnd2Circles(line, circle1, circle2, 1, 1)) - - if len(circleList) == 0: - return False - - AvgList = [] - Avg = sys.float_info.max - for circleTan in circleList: - del AvgList[:] # svuoto la lista - - ptInt = qad_utils.getPerpendicularPointOnInfinityLine(line[0], line[1], circleTan.center) - AvgList.append(qad_utils.getDistance(ptInt, pt)) - - angle = qad_utils.getAngleBy2Pts(circleTan.center, circle1.center) - if qad_utils.getDistance(circleTan.center, circle1.center) < circle1.radius: # cerchio interno - angle = angle + math.pi / 2 - ptInt = qad_utils.getPolarPointByPtAngle(circleTan.center, angle, circleTan.radius) - AvgList.append(qad_utils.getDistance(ptInt, pt1)) - - angle = qad_utils.getAngleBy2Pts(circleTan.center, circle2.center) - if qad_utils.getDistance(circleTan.center, circle2.center) < circle2.radius: # cerchio interno - angle = angle + math.pi / 2 - ptInt = qad_utils.getPolarPointByPtAngle(circleTan.center, angle, circleTan.radius) - AvgList.append(qad_utils.getDistance(ptInt, pt2)) - - currAvg = qad_utils.numericListAvg(AvgList) - if currAvg < Avg: # mediamente più vicino - Avg = currAvg - self.center = circleTan.center - self.radius = circleTan.radius - - return True - - - #============================================================================ - # fromCircleCircleCircleTanPts - #============================================================================ - def fromCircleCircleCircleTanPts(self, circle1, pt1, circle2, pt2, circle3, pt3): - """ - setta le caratteristiche dei cerchi attraverso tre linee: - cerchio1 di tangenza (oggetto QadCircle) - punto di selezione cerchio1 - cerchio2 di tangenza (oggetto QadCircle) - punto di selezione cerchio2 - cerchio3 di tangenza (oggetto QadCircle) - punto di selezione cerchio3 - """ - circleList = [] - circle = qad_utils.solveApollonius(circle1, circle2, circle3, -1, -1, -1) - if circle is not None: - circleList.append(circle) - circle = qad_utils.solveApollonius(circle1, circle2, circle3, -1, -1, 1) - if circle is not None: - circleList.append(circle) - circle = qad_utils.solveApollonius(circle1, circle2, circle3, -1, 1, -1) - if circle is not None: - circleList.append(circle) - circle = qad_utils.solveApollonius(circle1, circle2, circle3, -1, 1, 1) - if circle is not None: - circleList.append(circle) - circle = qad_utils.solveApollonius(circle1, circle2, circle3, 1, -1, -1) - if circle is not None: - circleList.append(circle) - circle = qad_utils.solveApollonius(circle1, circle2, circle3, 1, -1, 1) - if circle is not None: - circleList.append(circle) - circle = qad_utils.solveApollonius(circle1, circle2, circle3, 1, 1, -1) - if circle is not None: - circleList.append(circle) - circle = qad_utils.solveApollonius(circle1, circle2, circle3, 1, 1, 1) - if circle is not None: - circleList.append(circle) - - if len(circleList) == 0: - return False - - AvgList = [] - Avg = sys.float_info.max - for circleTan in circleList: - del AvgList[:] # svuoto la lista - - angle = qad_utils.getAngleBy2Pts(circleTan.center, circle1.center) - if qad_utils.getDistance(circleTan.center, circle1.center) < circle1.radius: # cerchio interno - angle = angle + math.pi / 2 - ptInt = qad_utils.getPolarPointByPtAngle(circleTan.center, angle, circleTan.radius) - AvgList.append(qad_utils.getDistance(ptInt, pt1)) - - angle = qad_utils.getAngleBy2Pts(circleTan.center, circle2.center) - if qad_utils.getDistance(circleTan.center, circle2.center) < circle2.radius: # cerchio interno - angle = angle + math.pi / 2 - ptInt = qad_utils.getPolarPointByPtAngle(circleTan.center, angle, circleTan.radius) - AvgList.append(qad_utils.getDistance(ptInt, pt2)) - - angle = qad_utils.getAngleBy2Pts(circleTan.center, circle3.center) - if qad_utils.getDistance(circleTan.center, circle3.center) < circle3.radius: # cerchio interno - angle = angle + math.pi / 2 - ptInt = qad_utils.getPolarPointByPtAngle(circleTan.center, angle, circleTan.radius) - AvgList.append(qad_utils.getDistance(ptInt, pt3)) - - currAvg = qad_utils.numericListAvg(AvgList) - if currAvg < Avg: # mediamente più vicino - Avg = currAvg - self.center = circleTan.center - self.radius = circleTan.radius - - return True - - - #============================================================================ - # from2IntPts1TanPt - #============================================================================ - def from2IntPts1TanPt(self, pt1, pt2, geom, pt): - """ - setta le caratteristiche del cerchio attraverso - 2 punti di intersezione ed un oggetto di tangenza: - punto1 di intersezione - punto2 di intersezione - geometria di tangenza (linea, arco o cerchio) - punto di selezione geometria - """ - obj = qad_utils.whatGeomIs(pt, geom) - - if obj is None: - return False - - if (type(obj) == list or type(obj) == tuple): - objType = "LINE" - else: - objType = obj.whatIs() - if objType == "ARC": # se è arco lo trasformo in cerchio - circle = QadCircle() - circle.set(obj.center, obj.radius) - obj = circle - objType = "CIRCLE" - - if objType == "LINE": - return self.from2IntPtsLineTanPts(pt1, pt2, obj, pt) - elif objType == "CIRCLE": - return self.from2IntPtsCircleTanPts(pt1, pt2, obj, pt) - - return False - - - #=========================================================================== - # from2IntPtsLineTanPts - #=========================================================================== - def from2IntPtsLineTanPts(self, pt1, pt2, line, pt, AllCircles = False): - """ - setta le caratteristiche dei cerchi attraverso 2 punti di intersezione e una linea tangente: - punto1 di intersezione - punto2 di intersezione - linea di tangenza (lista di 2 punti) - punto di selezione linea - il parametro AllCircles se = True fa restituire tutti i cerchi e non sono quello più vicino a pt1 e pt2 - """ - circleList = [] - - pt1Line = line[0] - pt2Line = line[1] - - A = (pt1[0] * pt1[0]) + (pt1[1] * pt1[1]) - B = (pt2[0] * pt2[0]) + (pt2[1] * pt2[1]) - - E = - pt1[0] + pt2[0] - F = pt1[1] - pt2[1] - if F == 0: - if AllCircles == True: - return circleList - else: - return False - - G = (-A + B) / F - H = E / F - - if pt1Line[0] - pt2Line[0] == 0: - # la linea é verticale - e = pt1Line[0] - I = H * H - if I == 0: - if AllCircles == True: - return circleList - else: - return False - J = (2 * G * H) - (4 * e) + (4 * pt2[0]) + (4 * H * pt2[1]) - K = (G * G) - (4 * e * e) + (4 * B) + (4 * G * pt2[1]) - else: - # equazione della retta line -> y = dx + e - d = (pt2Line[1] - pt1Line[1]) / (pt2Line[0] - pt1Line[0]) - e = - d * pt1Line[0] + pt1Line[1] - C = 4 * (1 + d * d) - D = 2 * d * e - d2 = d * d - I = 1 + (H * H * d2) + 2 * H * d - if I == 0: - if AllCircles == True: - return circleList - else: - return False - J = (2 * d2 * G * H) + (2 * D) + (2 * D * H * d) + (2 * G * d) - (e * C * H) + (pt2[0] * C) + H * pt2[1] * C - K = (G * G * d2) + (2 * D * G * d) + (D * D) - (C * e * e) - (C * G * e) + (B * C) + (G * pt2[1] * C) - - L = (J * J) - (4 * I * K) - if L < 0: - if AllCircles == True: - return circleList - else: - return False - - a1 = (-J + math.sqrt(L)) / (2 * I) - b1 = (a1 * H) + G - c1 = - B - (a1 * pt2[0]) - (b1 * pt2[1]) - center = QgsPoint() - center.setX(- (a1 / 2)) - center.setY(- (b1 / 2)) - radius = math.sqrt((a1 * a1 / 4) + (b1 * b1 / 4) - c1) - circle = QadCircle() - circle.set(center, radius) - circleList.append(circle) - - a2 = (-J - math.sqrt(L)) / (2 * I) - b2 = (a2 * H) + G - c2 = - B - (a2 * pt2[0]) - (b2 * pt2[1]) - center.setX(- (a2 / 2)) - center.setY(- (b2 / 2)) - radius = math.sqrt((a2 * a2 / 4) + (b2 * b2 / 4) - c2) - circle = QadCircle() - circle.set(center, radius) - circleList.append(circle) - - if AllCircles == True: - return circleList - - if len(circleList) == 0: - return False - - minDist = sys.float_info.max - for circle in circleList: - ptInt = qad_utils.getPerpendicularPointOnInfinityLine(line[0], line[1], circle.center) - dist = qad_utils.getDistance(ptInt, pt) - - if dist < minDist: # mediamente più vicino - minDist = dist - self.center = circle.center - self.radius = circle.radius - - return True - - - #=========================================================================== - # from2IntPtsCircleTanPts - #=========================================================================== - def from2IntPtsCircleTanPts(self, pt1, pt2, circle, pt): - """ - setta le caratteristiche dei cerchi attraverso 2 punti di intersezione e una cerchio tangente: - punto1 di intersezione - punto2 di intersezione - cerchio di tangenza (oggetto QadCircle) - punto di selezione cerchio - """ - # http://www.batmath.it/matematica/a_apollonio/ppc.htm - circleList = [] - - if pt1 == pt2: - return False - - dist1 = qad_utils.getDistance(pt1, circle.center) # distanza del punto 1 dal centro - dist2 = qad_utils.getDistance(pt2, circle.center) # distanza del punto 2 dal centro - - # entrambi i punti devono essere esterni o interni a circle - if (dist1 > circle.radius and dist2 < circle.radius) or \ - (dist1 < circle.radius and dist2 > circle.radius): - return False - - if dist1 == dist2: # l'asse di pt1 e pt2 passa per il centro di circle - if dist1 == circle.radius: # entrambi i punti sono sulla circonferenza di circle - return False - axis = qad_utils.getInfinityLinePerpOnMiddle(pt1, pt2) # asse di pt1 e pt2 - intPts = circle.getIntersectionPointsWithInfinityLine(axis) # punti di intersezione tra l'asse e circle - for intPt in intPts: - circleTan = QadCircle() - if circleTan.from3Pts(pt1, pt2, intPt) == True: - circleList.append(circleTan) - elif dist1 > circle.radius and dist2 > circle.radius : # entrambi i punti sono esterni a circle - # mi ricavo una qualunque circonferenza passante per p1 e p2 ed intersecante circle - circleInt = QadCircle() - if circleInt.from3Pts(pt1, pt2, circle.center) == False: - return False - intPts = circle.getIntersectionPointsWithCircle(circleInt) - intPt = qad_utils.getIntersectionPointOn2InfinityLines(pt1, pt2, intPts[0], intPts[1]) - tanPts = circle.getTanPoints(intPt) - for tanPt in tanPts: - circleTan = QadCircle() - if circleTan.from3Pts(pt1, pt2, tanPt) == True: - circleList.append(circleTan) - elif dist1 < circle.radius and dist2 < circle.radius : # entrambi i punti sono interni a circle - # mi ricavo una qualunque circonferenza passante per p1 e p2 ed intersecante circle - ptMiddle = qad_utils.getMiddlePoint(pt1, pt2) - angle = qad_utils.getAngleBy2Pts(pt1, pt2) + math.pi / 2 - pt3 = qad_utils.getPolarPointByPtAngle(ptMiddle, angle, 2 * circle.radius) - circleInt = QadCircle() - if circleInt.from3Pts(pt1, pt2, pt3) == False: - return False - intPts = circle.getIntersectionPointsWithCircle(circleInt) - intPt = qad_utils.getIntersectionPointOn2InfinityLines(pt1, pt2, intPts[0], intPts[1]) - tanPts = circle.getTanPoints(intPt) - for tanPt in tanPts: - circleTan = QadCircle() - if circleTan.from3Pts(pt1, pt2, tanPt) == True: - circleList.append(circleTan) - elif dist1 == radius: # il punto1 sulla circonferenza di circle - # una sola circonferenza avente come centro l'intersezione tra l'asse pt1 e pt2 e la retta - # passante per il centro di circle e pt1 - axis = qad_utils.getInfinityLinePerpOnMiddle(pt1, pt2) # asse di pt1 e pt2 - intPt = qad_utils.getIntersectionPointOn2InfinityLines(axis[0], axis[1], circle.center, pt1) - circleTan = QadCircle() - circleTan.set(intPt, qad_utils.getDistance(pt1, intPt)) - circleList.append(circleTan) - elif dist2 == radius: # il punto3 é sulla circonferenza di circle - # una sola circonferenza avente come centro l'intersezione tra l'asse pt1 e pt2 e la retta - # passante per il centro di circle e pt2 - axis = qad_utils.getInfinityLinePerpOnMiddle(pt1, pt2) # asse di pt1 e pt2 - intPt = qad_utils.getIntersectionPointOn2InfinityLines(axis[0], axis[1], circle.center, pt2) - circleTan = QadCircle() - circleTan.set(intPt, qad_utils.getDistance(pt2, intPt)) - circleList.append(circleTan) - - if len(circleList) == 0: - return False - - minDist = sys.float_info.max - for circleTan in circleList: - angle = qad_utils.getAngleBy2Pts(circleTan.center, circle.center) - if qad_utils.getDistance(circleTan.center, circle.center) < circle.radius: # cerchio interno - angle = angle + math.pi / 2 - ptInt = qad_utils.getPolarPointByPtAngle(circleTan.center, angle, circleTan.radius) - dist = qad_utils.getDistance(ptInt, pt) - - if dist < minDist: # mediamente più vicino - minDist = dist - self.center = circleTan.center - self.radius = circleTan.radius - - return True - - - #============================================================================ - # from1IntPt2TanPts - #============================================================================ - def from1IntPt2TanPts(self, pt, geom1, pt1, geom2, pt2): - """ - setta le caratteristiche del cerchio attraverso - 1 punti di intersezione e 2 oggetti di tangenza: - punto di intersezione - geometria1 di tangenza (linea, arco o cerchio) - punto di selezione geometria1 - geometria2 di tangenza (linea, arco o cerchio) - punto di selezione geometria2 - """ - obj1 = qad_utils.whatGeomIs(pt1, geom1) - obj2 = qad_utils.whatGeomIs(pt2, geom2) - - if (obj1 is None) or (obj2 is None): - return False - - if (type(obj1) == list or type(obj1) == tuple): - obj1Type = "LINE" - else: - obj1Type = obj1.whatIs() - if obj1Type == "ARC": # se è arco lo trasformo in cerchio - circle = QadCircle() - circle.set(obj1.center, obj1.radius) - obj1 = circle - obj1Type = "CIRCLE" - - if (type(obj2) == list or type(obj2) == tuple): - obj2Type = "LINE" - else: - obj2Type = obj2.whatIs() - if obj2Type == "ARC": # se è arco lo trasformo in cerchio - circle = QadCircle() - circle.set(obj1.center, obj1.radius) - obj2 = circle - obj2Type = "CIRCLE" - - if obj1Type == "LINE": - if obj2Type == "LINE": - return self.from1IntPtLineLineTanPts(pt, obj1, pt1, obj2, pt2) - elif obj2Type == "CIRCLE": - return self.from1IntPtLineCircleTanPts(pt, obj1, pt1, obj2, pt2) - elif obj1Type == "CIRCLE": - if obj2Type == "LINE": - return self.from1IntPtLineCircleTanPts(pt, obj2, pt2, obj1, pt1) - elif obj2Type == "CIRCLE": - return self.from1IntPtCircleCircleTanPts(pt, obj1, pt1, obj2, pt2) - - return False - - - #=========================================================================== - # from1IntPtLineLineTanPts - #=========================================================================== - def from1IntPtLineLineTanPts(self, pt, line1, pt1, line2, pt2, AllCircles = False): - """ - setta le caratteristiche dei cerchi attraverso 1 punti di intersezione e due linee tangenti: - punto di intersezione - linea1 di tangenza (lista di 2 punti) - punto di selezione linea1 - linea2 di tangenza (lista di 2 punti) - punto di selezione linea2 - il parametro AllCircles se = True fa restituire tutti i cerchi e non sono quello più vicino a pt1 e pt2 - """ - # http://www.batmath.it/matematica/a_apollonio/prr.htm - circleList = [] - - # verifico se le rette sono parallele - ptInt = qad_utils.getIntersectionPointOn2InfinityLines(line1[0], line1[1], \ - line2[0], line2[1]) - if ptInt is None: # le rette sono parallele - # Se le rette sono parallele il problema ha soluzioni solo se il punto - # é non esterno alla striscia individuata dalle due rette e basta considerare - # il simmetrico di A rispetto alla bisettrice della striscia. - ptPerp = qad_utils.getPerpendicularPointOnInfinityLine(line1[0], line1[1], line2[0]) - angle = qad_utils.getAngleBy2Pts(line2[0], ptPerp) - dist = qad_utils.getDistance(line2[0], ptPerp) - pt1ParLine = qad_utils.getPolarPointByPtAngle(line2[0], angle, dist / 2) - angle = angle + math.pi / 2 - pt2ParLine = qad_utils.getPolarPointByPtAngle(pt1ParLine, angle, dist) - - ptPerp = qad_utils.getPerpendicularPointOnInfinityLine(pt1ParLine, pt2ParLine, pt) - dist = qad_utils.getDistance(pt, ptPerp) - - # trovo il punto simmetrico - angle = qad_utils.getAngleBy2Pts(pt, ptPerp) - ptSymmetric = qad_utils.getPolarPointByPtAngle(pt, angle, dist * 2) - return self.from2IntPtsLineTanPts(pt, ptSymmetric, line1, pt1, AllCircles) - else: # le rette non sono parallele - if ptInt == pt: - return False - # se il punto é sulla linea1 o sulla linea2 - ptPerp1 = qad_utils.getPerpendicularPointOnInfinityLine(line1[0], line1[1], pt) - ptPerp2 = qad_utils.getPerpendicularPointOnInfinityLine(line2[0], line2[1], pt) - if ptPerp1 == pt or ptPerp2 == pt: - # Se le rette sono incidenti ed il punto appartiene ad una delle due la costruzione - # é quasi immediata: basta tracciare le bisettrici dei due angoli individuati dalle rette - # e la perpendicolare per pt alla retta cui appartiene pt stesso. Si avranno due circonferenze. - - if ptPerp1 == pt: # se il punto é sulla linea1 - angle = qad_utils.getAngleBy2Pts(line2[0], line2[1]) - ptLine = qad_utils.getPolarPointByPtAngle(ptInt, angle, 10) - Bisector1 = qad_utils.getBisectorInfinityLine(pt, ptInt, ptLine) - ptLine = qad_utils.getPolarPointByPtAngle(ptInt, angle + math.pi, 10) - Bisector2 = qad_utils.getBisectorInfinityLine(pt, ptInt, ptLine) - angle = qad_utils.getAngleBy2Pts(line1[0], line1[1]) - ptPerp = qad_utils.getPolarPointByPtAngle(pt, angle + math.pi / 2, 10) - else: # se il punto é sulla linea2 - angle = qad_utils.getAngleBy2Pts(line1[0], line1[1]) - ptLine = qad_utils.getPolarPointByPtAngle(ptInt, angle, 10) - Bisector1 = qad_utils.getBisectorInfinityLine(pt, ptInt, ptLine) - ptLine = qad_utils.getPolarPointByPtAngle(ptInt, angle + math.pi, 10) - Bisector2 = qad_utils.getBisectorInfinityLine(pt, ptInt, ptLine) - angle = qad_utils.getAngleBy2Pts(line2[0], line2[1]) - ptPerp = qad_utils.getPolarPointByPtAngle(pt, angle + math.pi / 2, 10) - - center = qad_utils.getIntersectionPointOn2InfinityLines(Bisector1[0], Bisector1[1], \ - pt, ptPerp) - radius = qad_utils.getDistance(pt, center) - circleTan = QadCircle() - circleTan.set(center, radius) - circleList.append(circleTan) - - center = qad_utils.getIntersectionPointOn2InfinityLines(Bisector2[0], Bisector2[1], \ - pt, ptPerp) - radius = qad_utils.getDistance(pt, center) - circleTan = QadCircle() - circleTan.set(center, radius) - circleList.append(circleTan) - else: - # Bisettrice dell'angolo interno del triangolo avente come vertice i punti di intersezione delle rette - Bisector = qad_utils.getBisectorInfinityLine(ptPerp1, ptInt, ptPerp2) - - ptPerp = qad_utils.getPerpendicularPointOnInfinityLine(Bisector[0], Bisector[1], pt) - dist = qad_utils.getDistance(pt, ptPerp) - - # trovo il punto simmetrico - angle = qad_utils.getAngleBy2Pts(pt, ptPerp) - ptSymmetric = qad_utils.getPolarPointByPtAngle(pt, angle, dist * 2) - return self.from2IntPtsLineTanPts(pt, ptSymmetric, line1, pt1, AllCircles) - - if AllCircles == True: - return circleList - - if len(circleList) == 0: - return False - - AvgList = [] - Avg = sys.float_info.max - for circleTan in circleList: - del AvgList[:] # svuoto la lista - - ptInt = qad_utils.getPerpendicularPointOnInfinityLine(line1[0], line1[1], circleTan.center) - AvgList.append(qad_utils.getDistance(ptInt, pt1)) - - ptInt = qad_utils.getPerpendicularPointOnInfinityLine(line2[0], line2[1], circleTan.center) - AvgList.append(qad_utils.getDistance(ptInt, pt2)) - - currAvg = qad_utils.numericListAvg(AvgList) - if currAvg < Avg: # mediamente più vicino - Avg = currAvg - self.center = circleTan.center - self.radius = circleTan.radius - - return True - - - #============7=============================================================== - # from1IntPtLineCircleTanPts - #=========================================================================== - def from1IntPtLineCircleTanPts(self, pt, line1, pt1, circle2, pt2, AllCircles = False): - """ - setta le caratteristiche dei cerchi attraverso 1 punto di intersezione, 1 linea e 1 cerchio tangenti: - punto di intersezione - linea di tangenza (lista di 2 punti) - punto di selezione linea - cerchio di tangenza (oggetto QadCircle) - punto di selezione cerchio - il parametro AllCircles se = True fa restituire tutti i cerchi e non sono quello più vicino a pt1 e pt2 - """ - # http://www.batmath.it/matematica/a_apollonio/prc.htm - circleList = [] - - # Sono dati un cerchio circle2, un punto pt ed una retta line1 nell'ipotesi che pt - # non stia nè sulla retta line1 nè sul circolo. - # Si vogliono trovare le circonferenze passanti per il punto e tangenti alla retta e al cerchio dato. - # Il problema si può risolvere facilmente utilizzando un'inversione di centro pt e raggio qualunque. - # Trovate le circonferenze inverse della retta data e del circolo dato, se ne trovano le tangenti comuni. - # Le inverse di queste tangenti comuni sono le circonferenze cercate. - - if qad_utils.getYOnInfinityLine(line1[0], line1[1], pt.x()) == pt.y() or \ - qad_utils.getDistance(pt, circle2.center) == circle2.radius: - if AllCircles == True: - return circleList - else: - return False - - c = QadCircle() - c.set(pt, 10) - - circularInvLine = qad_utils.getCircularInversionOfLine(c, line1) - circularInvCircle = qad_utils.getCircularInversionOfCircle(c, circle2) - tangents = circularInvCircle.getTangentsWithCircle(circularInvLine) - for tangent in tangents: - circleList.append(qad_utils.getCircularInversionOfLine(c, tangent)) - - if AllCircles == True: - return circleList - - if len(circleList) == 0: - return False - - AvgList = [] - Avg = sys.float_info.max - for circleTan in circleList: - del AvgList[:] # svuoto la lista - - ptInt = qad_utils.getPerpendicularPointOnInfinityLine(line1[0], line1[1], circleTan.center) - AvgList.append(qad_utils.getDistance(ptInt, pt1)) - - angle = qad_utils.getAngleBy2Pts(circleTan.center, circle2.center) - if qad_utils.getDistance(circleTan.center, circle2.center) < circle2.radius: # cerchio interno - angle = angle + math.pi / 2 - ptInt = qad_utils.getPolarPointByPtAngle(circleTan.center, angle, circleTan.radius) - AvgList.append(qad_utils.getDistance(ptInt, pt2)) - - currAvg = qad_utils.numericListAvg(AvgList) - if currAvg < Avg: # mediamente più vicino - Avg = currAvg - self.center = circleTan.center - self.radius = circleTan.radius - - if AllCircles == True: - return circleList - else: - return True - - - #=========================================================================== - # from1IntPtCircleCircleTanPts - #=========================================================================== - def from1IntPtCircleCircleTanPts(self, pt, circle1, pt1, circle2, pt2): - """ - setta le caratteristiche dei cerchi attraverso 1 punto di intersezione, 2 cerchi tangenti: - punto di intersezione - cerchio1 di tangenza (oggetto QadCircle) - punto di selezione cerchio1 - cerchio2 di tangenza (oggetto QadCircle) - punto di selezione cerchio2 - """ - # http://www.batmath.it/matematica/a_apollonio/prc.htm - circleList = [] - - # Sono dati un punto pt e due circonferenze circle1 e circle2; - # si devono determinare le circonferenze passanti per pt e tangenti alle due circonferenze. - # Proponiamo una costruzione che utilizza l'inversione, in quanto ci pare la più elegante. - # In realtà si potrebbe anche fare una costruzione utilizzando i centri di omotetia dei due cerchi dati - # ma, nella sostanza, é solo un modo per mascherare l'uso dell'inversione. - # Si considera un circolo di inversione di centro pt e raggio qualunque. - # Si determinano i circoli inversi dei due circoli dati e le loro tangenti comuni. - # Le circonferenze inverse di queste tangenti comuni sono quelle che soddisfano il problema. - - c = QadCircle() - c.set(pt, 10) - - circularInvCircle1 = qad_utils.getCircularInversionOfCircle(c, circle1) - circularInvCircle2 = qad_utils.getCircularInversionOfCircle(c, circle2) - tangents = circularInvCircle1.getTangentsWithCircle(circularInvCircle2) - for tangent in tangents: - circleList.append(qad_utils.getCircularInversionOfLine(c, tangent)) - - if len(circleList) == 0: - return False - - AvgList = [] - Avg = sys.float_info.max - for circleTan in circleList: - del AvgList[:] # svuoto la lista - - angle = qad_utils.getAngleBy2Pts(circleTan.center, circle1.center) - if qad_utils.getDistance(circleTan.center, circle1.center) < circle1.radius: # cerchio interno - angle = angle + math.pi / 2 - ptInt = qad_utils.getPolarPointByPtAngle(circleTan.center, angle, circleTan.radius) - AvgList.append(qad_utils.getDistance(ptInt, pt1)) - - angle = qad_utils.getAngleBy2Pts(circleTan.center, circle2.center) - if qad_utils.getDistance(circleTan.center, circle2.center) < circle2.radius: # cerchio interno - angle = angle + math.pi / 2 - ptInt = qad_utils.getPolarPointByPtAngle(circleTan.center, angle, circleTan.radius) - AvgList.append(qad_utils.getDistance(ptInt, pt2)) - - currAvg = qad_utils.numericListAvg(AvgList) - if currAvg < Avg: # mediamente più vicino - Avg = currAvg - self.center = circleTan.center - self.radius = circleTan.radius - - return True - - - #============================================================================ - # fromDiamEnds - #============================================================================ - def fromDiamEnds(self, startPt, endPt): - """ - setta le caratteristiche del cerchio attraverso i punti estremità del diametro: - punto iniziale - punto finale - """ - self.radius = qad_utils.getDistance(startPt, endPt) / 2 - if self.radius == 0: - return False - self.center = qad_utils.getMiddlePoint(startPt, endPt) - return True - - - #============================================================================ - # fromDiamEndsPtTanPt - #============================================================================ - def fromDiamEndsPtTanPt(self, startPt, geom, pt): - """ - setta le caratteristiche del cerchio attraverso un punto di estremità del diametro e - un oggetto di tangenza per l'altra estremità : - punto iniziale - geometria 1 di tangenza (linea, arco o cerchio) - punto di selezione geometria 1 - """ - obj = qad_utils.whatGeomIs(pt, geom) - - if (type(obj) == list or type(obj) == tuple): - objType = "LINE" - else: - objType = obj.whatIs() - if objType == "ARC": # se è arco lo trasformo in cerchio - circle = QadCircle() - circle.set(obj.center, obj.radius) - obj = circle - objType = "CIRCLE" - - if objType == "LINE": - ptPer = qad_utils.getPerpendicularPointOnInfinityLine(obj[0], obj[1], startPt) - return self.fromDiamEnds(startPt, ptPer) - elif objType == "CIRCLE": - ptIntList = obj.getIntersectionPointsWithInfinityLine(startPt, obj.center) - # scelgo il punto più vicino al punto pt - ptTan = qad_utils.getNearestPoints(pt, ptIntList)[0] - return self.fromDiamEnds(startPt, ptTan) - - - #============================================================================ - # fromDiamEnds2TanPts - #============================================================================ - def fromDiamEnds2TanPts(self, geom1, pt1, geom2, pt2): - """ - setta le caratteristiche del cerchio attraverso - due oggetto di tangenza per le estremità del diametro: - geometria1 di tangenza (linea, arco o cerchio) - punto di selezione geometria1 - geometria2 di tangenza (linea, arco o cerchio) - punto di selezione geometria2 - """ - obj1 = qad_utils.whatGeomIs(pt1, geom1) - obj2 = qad_utils.whatGeomIs(pt2, geom2) - - if (obj1 is None) or (obj2 is None): - return False - - if (type(obj1) == list or type(obj1) == tuple): - obj1Type = "LINE" - else: - obj1Type = obj1.whatIs() - if obj1Type == "ARC": # se è arco lo trasformo in cerchio - circle = QadCircle() - circle.set(obj1.center, obj1.radius) - obj1 = circle - obj1Type = "CIRCLE" - - if (type(obj2) == list or type(obj2) == tuple): - obj2Type = "LINE" - else: - obj2Type = obj2.whatIs() - if obj2Type == "ARC": # se è arco lo trasformo in cerchio - circle = QadCircle() - circle.set(obj1.center, obj1.radius) - obj2 = circle - obj2Type = "CIRCLE" - - if obj1Type == "LINE": - if obj2Type == "LINE": - return False # Il diametro non può essere tangente a due linee - elif obj2Type == "CIRCLE": - return self.fromLineCircleTanPts(obj1, pt1, obj2, pt2) - elif obj1Type == "CIRCLE": - if obj2Type == "LINE": - return self.fromLineCircleTanPts(obj2, pt2, obj1, pt1) - elif obj2Type == "CIRCLE": - return self.fromCircleCircleTanPts(obj1, pt1, obj2, pt2) - - return False - - - #============================================================================ - # fromLineCircleTanPts - #============================================================================ - def fromLineCircleTanPts(self, line, ptLine, circle, ptCircle): - """ - setta le caratteristiche del cerchio attraverso una linea, un cerchio di tangenza: - linea di tangenza (lista di 2 punti) - punto di selezione linea - cerchio di tangenza (oggetto QadCircle) - punto di selezione cerchio - """ - - ptPer = qad_utils.getPerpendicularPointOnInfinityLine(line[0], line[1], circle.center) - - ptIntList = obj.getIntersectionPointsWithInfinityLine(ptPer, circle.center) - # scelgo il punto più vicino al punto pt - ptTan = qad_utils.getNearestPoints(pt, ptIntList)[0] - return self.fromDiamEnds(ptPer, ptTan) - - - #============================================================================ - # fromCircleCircleTanPts - #============================================================================ - def fromCircleCircleTanPts(self, circle1, pt1, circle2, pt2): - """ - setta le caratteristiche del cerchio attraverso due cerchi di tangenza: - cerchio1 di tangenza (oggetto QadCircle) - punto di selezione cerchio1 - cerchio2 di tangenza (oggetto QadCircle) - punto di selezione cerchio2 - """ - - ptIntList = circle1.getIntersectionPointsWithInfinityLine(circle1.center, circle2.center) - # scelgo il punto più vicino al punto pt1 - ptTan1 = qad_utils.getNearestPoints(pt1, ptIntList)[0] - - ptIntList = circle2.getIntersectionPointsWithInfinityLine(circle1.center, circle2.center) - # scelgo il punto più vicino al punto pt2 - ptTan2 = qad_utils.getNearestPoints(pt2, ptIntList)[0] - - return self.fromDiamEnds(ptTan1, ptTan2) - - - #============================================================================ - # from2TanPtsRadius - #============================================================================ - def from2TanPtsRadius(self, geom1, pt1, geom2, pt2, radius): - """ - setta le caratteristiche del cerchio attraverso 2 oggetti di tangenza e un raggio: - geometria1 di tangenza (linea, arco o cerchio) - punto di selezione geometria1 - oggetto2 di tangenza (linea, arco o cerchio) - punto di selezione geometria2 - raggio - """ - obj1 = qad_utils.whatGeomIs(pt1, geom1) - obj2 = qad_utils.whatGeomIs(pt2, geom2) - - if (obj1 is None) or (obj2 is None): - return False - - if (type(obj1) == list or type(obj1) == tuple): - obj1Type = "LINE" - else: - obj1Type = obj1.whatIs() - if obj1Type == "ARC": # se è arco lo trasformo in cerchio - circle = QadCircle() - circle.set(obj1.center, obj1.radius) - obj1 = circle - obj1Type = "CIRCLE" - - if (type(obj2) == list or type(obj2) == tuple): - obj2Type = "LINE" - else: - obj2Type = obj2.whatIs() - if obj1Type == "ARC": # se è arco lo trasformo in cerchio - circle = QadCircle() - circle.set(obj2.center, obj2.radius) - obj2 = circle - obj2Type = "CIRCLE" - - if obj1Type == "LINE": - if obj2Type == "LINE": - return self.fromLineLineTanPtsRadius(obj1, pt1, obj2, pt2, radius) - elif obj2Type == "CIRCLE": - return self.fromLineCircleTanPtsRadius(obj1, pt1, obj2, pt2, radius) - elif obj1Type == "CIRCLE": - if obj2Type == "LINE": - return self.fromLineCircleTanPtsRadius(obj2, pt2, obj1, pt1, radius) - elif obj2Type == "CIRCLE": - return self.fromCircleCircleTanPtsRadius(obj1, pt1, obj2, pt2, radius) - - return False - - - #============================================================================ - # fromLineLineTanPtsRadius - #============================================================================ - def fromLineLineTanPtsRadius(self, line1, pt1, line2, pt2, radius): - """ - setta le caratteristiche del cerchio attraverso due linee di tangenza e un raggio: - linea1 di tangenza (lista di 2 punti) - punto di selezione linea1 - linea2 di tangenza (lista di 2 punti) - punto di selezione linea2 - raggio - """ - - # calcolo il punto medio tra i due punti di selezione - ptMiddle = qad_utils.getMiddlePoint(pt1, pt2) - - # verifico se le rette sono parallele - ptInt = qad_utils.getIntersectionPointOn2InfinityLines(line1[0], line1[1], \ - line2[0], line2[1]) - if ptInt is None: # le rette sono parallele - ptPer = qad_utils.getPerpendicularPointOnInfinityLine(line1[0], line1[1], ptMiddle) - if qad_utils.doubleNear(radius, qad_utils.getDistance(ptPer, ptMiddle)): - self.center = ptMiddle - self.radius = radius - return True - else: - return False - - # angolo linea1 - angle = qad_utils.getAngleBy2Pts(line1[0], line1[1]) - # retta parallela da un lato della linea1 distante radius - angle = angle + math.pi / 2 - pt1Par1Line1 = qad_utils.getPolarPointByPtAngle(line1[0], angle, radius) - pt2Par1Line1 = qad_utils.getPolarPointByPtAngle(line1[1], angle, radius) - # retta parallela dall'altro lato della linea1 distante radius - angle = angle - math.pi - pt1Par2Line1 = qad_utils.getPolarPointByPtAngle(line1[0], angle, radius) - pt2Par2Line1 = qad_utils.getPolarPointByPtAngle(line1[1], angle, radius) - - # angolo linea2 - angle = qad_utils.getAngleBy2Pts(line2[0], line2[1]) - # retta parallela da un lato della linea2 distante radius - angle = angle + math.pi / 2 - pt1Par1Line2 = qad_utils.getPolarPointByPtAngle(line2[0], angle, radius) - pt2Par1Line2 = qad_utils.getPolarPointByPtAngle(line2[1], angle, radius) - # retta parallela dall'altro lato della linea2 distante radius - angle = angle - math.pi - pt1Par2Line2 = qad_utils.getPolarPointByPtAngle(line2[0], angle, radius) - pt2Par2Line2 = qad_utils.getPolarPointByPtAngle(line2[1], angle, radius) - - # calcolo le intersezioni - ptIntList = [] - ptInt = qad_utils.getIntersectionPointOn2InfinityLines(pt1Par1Line1, pt2Par1Line1, \ - pt1Par1Line2, pt2Par1Line2) - ptIntList.append(ptInt) - - ptInt = qad_utils.getIntersectionPointOn2InfinityLines(pt1Par1Line1, pt2Par1Line1, \ - pt1Par2Line2, pt2Par2Line2) - ptIntList.append(ptInt) - - ptInt = qad_utils.getIntersectionPointOn2InfinityLines(pt1Par2Line1, pt2Par2Line1, \ - pt1Par1Line2, pt2Par1Line2) - ptIntList.append(ptInt) - - ptInt = qad_utils.getIntersectionPointOn2InfinityLines(pt1Par2Line1, pt2Par2Line1, \ - pt1Par2Line2, pt2Par2Line2) - ptIntList.append(ptInt) - - # scelgo il punto più vicino al punto medio - self.center = qad_utils.getNearestPoints(ptMiddle, ptIntList)[0] - self.radius = radius - return True - - - #============================================================================ - # fromLineCircleTanPtsRadius - #============================================================================ - def fromLineCircleTanPtsRadius(self, line, ptLine, circle, ptCircle, radius): - """ - setta le caratteristiche del cerchio attraverso una linea, un cerchio di tangenza e un raggio: - linea di tangenza (lista di 2 punti) - punto di selezione linea - cerchio di tangenza (oggetto QadCircle) - punto di selezione cerchio - raggio - """ - - # calcolo il punto medio tra i due punti di selezione - ptMiddle = qad_utils.getMiddlePoint(ptLine, ptCircle) - - # angolo linea1 - angle = qad_utils.getAngleBy2Pts(line[0], line[1]) - # retta parallela da un lato della linea1 distante radius - angle = angle + math.pi / 2 - pt1Par1Line = qad_utils.getPolarPointByPtAngle(line[0], angle, radius) - pt2Par1Line = qad_utils.getPolarPointByPtAngle(line[1], angle, radius) - # retta parallela dall'altro lato della linea1 distante radius - angle = angle - math.pi - pt1Par2Line = qad_utils.getPolarPointByPtAngle(line[0], angle, radius) - pt2Par2Line = qad_utils.getPolarPointByPtAngle(line[1], angle, radius) - - # creo un cerchio con un raggio + grande - circleTan = QadCircle() - circleTan.set(circle.center, circle.radius + radius) - ptIntList = circleTan.getIntersectionPointsWithInfinityLine(pt1Par1Line, pt2Par1Line) - ptIntList2 = circleTan.getIntersectionPointsWithInfinityLine(pt1Par2Line, pt2Par2Line) - ptIntList.extend(ptIntList2) - - if len(ptIntList) == 0: # nessuna intersezione - return False - - # scelgo il punto più vicino al punto medio - self.center = qad_utils.getNearestPoints(ptMiddle, ptIntList)[0] - self.radius = radius - return True - - - #============================================================================ - # fromCircleCircleTanPtsRadius - #============================================================================ - def fromCircleCircleTanPtsRadius(self, circle1, pt1, circle2, pt2, radius): - """ - setta le caratteristiche del cerchio attraverso due cerchi di tangenza e un raggio: - cerchio1 di tangenza (oggetto QadCircle) - punto di selezione cerchio1 - cerchio2 di tangenza (oggetto QadCircle) - punto di selezione cerchio2 - raggio - """ - - # calcolo il punto medio tra i due punti di selezione - ptMiddle = qad_utils.getMiddlePoint(pt1, pt2) - - # creo due cerchi con un raggio + grande - circle1Tan = QadCircle() - circle1Tan.set(circle1.center, circle1.radius + radius) - circle2Tan = QadCircle() - circle2Tan.set(circle2.center, circle2.radius + radius) - ptIntList = circle1Tan.getIntersectionPointsWithCircle(circle2Tan) - - if len(ptIntList) == 0: # nessuna intersezione - return False - - # scelgo il punto più vicino al punto medio - self.center = qad_utils.getNearestPoints(ptMiddle, ptIntList)[0] - self.radius = radius - return True - - -#=============================================================================== -# QadCircleList lista di cerchi class -#=============================================================================== -class QadCircleList(): - def __init__(self): - self.circleList = [] # lista dei cerchi - self.startEndVerticesList = [] # lista degli estremi (posizioni dei vertici iniziali e finali) - - def clear(self): - del self.circleList[:] # svuoto la lista - del self.startEndVerticesList[:] # svuoto la lista - - - #============================================================================ - # fromPoints - #============================================================================ - def fromPoints(self, points, atLeastNSegment = None): - """ - setta la lista deg cerchi e degli estremi leggendo una sequenza di punti - ritorna il numero dei cerchi trovati - """ - if atLeastNSegment is None: - _atLeastNSegment = QadVariables.get(QadMsg.translate("Environment variables", "CIRCLEMINSEGMENTQTY"), 12) - else: - _atLeastNSegment = atLeastNSegment - - self.clear() - startVertex = 0 - circle = QadCircle() - startEndVertices = circle.fromPolyline(points, startVertex, _atLeastNSegment) - while startEndVertices is not None: - _circle = QadCircle(circle) # ne faccio una copia - self.circleList.append(_circle) - self.startEndVerticesList.append(startEndVertices) - startVertex = startEndVertices[1] # l'ultimo punto del cerchio - startEndVertices = circle.fromPolyline(points, startVertex, _atLeastNSegment) - - return len(self.circleList) - - - #============================================================================ - # fromGeom - #============================================================================ - def fromGeom(self, geom, atLeastNSegment = None): - """ - setta la lista dei cerchi e degli estremi leggendo una geometria - ritorna il numero di cerchi trovati - """ - if atLeastNSegment is None: - _atLeastNSegment = QadVariables.get(QadMsg.translate("Environment variables", "CIRCLEMINSEGMENTQTY"), 12) - else: - _atLeastNSegment = atLeastNSegment - - self.clear() - circle = QadCircle() - incremental = 0 - # riduco in polilinee - geoms = qad_utils.asPointOrPolyline(geom) - for g in geoms: - points = g.asPolyline() # vettore di punti - startVertex = 0 - startEndVertices = circle.fromPolyline(points, startVertex, _atLeastNSegment) - while startEndVertices is not None: - _circle = QadCircle(circle) # ne faccio una copia - self.circleList.append(_circle) - self.startEndVerticesList.append([startEndVertices[0] + incremental, startEndVertices[1] + incremental]) - startVertex = startEndVertices[1] # l'ultimo punto del cerchio - startEndVertices = circle.fromPolyline(points, startVertex, _atLeastNSegment) - - incremental = len(points) - 1 - - return len(self.circleList) - - #============================================================================ - # fromGeom - #============================================================================ - def circleAt(self, iVertex): - """ - cerca se esiste un cerchio al vertice - restituisce una lista con , - oppure None se cerchio non trovato - """ - i = 0 - for startEndVertices in self.startEndVerticesList: - if iVertex >= startEndVertices[0] and iVertex <= startEndVertices[1]: - return self.circleList[i], startEndVertices - i = i + 1 - - return None +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + classe per la gestione dei cerchi + + ------------------- + begin : 2013-05-22 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from qgis.PyQt.QtCore import * +from qgis.PyQt.QtGui import * +from qgis.core import * +from qgis.gui import * + +import math +import sys + +from . import qad_utils +from .qad_variables import QadVariables +from .qad_msg import QadMsg + + +# =============================================================================== +# QadCircle circle class +# =============================================================================== +class QadCircle(): + + def __init__(self, circle = None): + if circle is not None: + self.set(circle.center, circle.radius) + else: + self.center = None + self.radius = None + + def whatIs(self): + # obbligatoria + return "CIRCLE" + + + # ============================================================================ + # isClosed + # ============================================================================ + def isClosed(self): + return True + + + def set(self, center, radius = None): + if isinstance(center, QadCircle): + circle = center + return self.set(circle.center, circle.radius) + + if radius <= 0: return None + self.center = QgsPointXY(center) + self.radius = radius + return self + + def transform(self, coordTransform): + """Transform this geometry as described by CoordinateTranasform ct.""" + self.center = coordTransform.transform(self.center) + + def transformFromCRSToCRS(self, sourceCRS, destCRS): + """Transform this geometry as described by CRS.""" + if (sourceCRS is not None) and (destCRS is not None) and sourceCRS != destCRS: + coordTransform = QgsCoordinateTransform(sourceCRS, destCR, QgsProject.instance()) # trasformo le coord + self.center = coordTransform.transform(self.center) + + def __eq__(self, circle): + # obbligatoria + """self == other""" + if circle.whatIs() != "CIRCLE": return False + if self.center != circle.center or self.radius != circle.radius: + return False + else: + return True + + def __ne__(self, circle): + """self != other""" + return not self.__eq__(circle) + + + def equals(self, circle): + # uguali geometricamente (NON conta il verso) + return self.__eq__(circle) + + + def copy(self): + # obbligatoria + return QadCircle(self) + + + def length(self): + return 2 * math.pi * self.radius + + + # =============================================================================== + # getBoundingBox + # =============================================================================== + def getBoundingBox(self): + """ + la funzione ritorna il rettangolo che racchiude il cerchio. + """ + return QgsRectangle(self.center.x() - self.radius, + self.center.y() - self.radius, + self.center.x() + self.radius, + self.center.y() + self.radius) + + + # =============================================================================== + # containsPt + # =============================================================================== + def containsPt(self, point): + # obbligatoria + """ + la funzione ritorna true se il punto é sulla circonferenze del cerchio. + """ + # whereIsPt ritorna -1 se il punto è interno, 0 se è sulla circonferenza, 1 se è esterno + return True if self.whereIsPt(point) == 0 else 0 + + + # =============================================================================== + # lengthBetween2Points + # =============================================================================== + def lengthBetween2Points(self, pt1, pt2, leftOfPt1): + """ + Calcola la distanza tra 2 punti sulla circonferenza. L'arco considerato può essere + quello a sinistra o a destra di (vedi ) + se é boolean allora se = True viene considerato l'arco a sin di pt1 + se é float allora significa che si tratta della direzione della tangente su pt1 + e se la direzione è a sin viene considerato l'arco a sin di pt1 + """ + if qad_utils.ptNear(pt1, pt2): # se i punti sono così vicini da essere considerati uguali + return 0 + + if type(leftOfPt1) == float: # direzione della tangente su pt1 + startAngle = qad_utils.getAngleBy2Pts(self.center, pt1) + if qad_utils.doubleNear(qad_utils.normalizeAngle(startAngle + math.pi / 2), + qad_utils.normalizeAngle(leftOfPt1)): + _leftOfPt1 = True + else: + _leftOfPt1 = False + else: # booolean + _leftOfPt1 = leftOfPt1 + + if _leftOfPt1: # arco a sinistra di pt1 + startAngle = qad_utils.getAngleBy2Pts(self.center, pt1) + endAngle = qad_utils.getAngleBy2Pts(self.center, pt2) + else: # arco a destra di pt1 + startAngle = qad_utils.getAngleBy2Pts(self.center, pt2) + endAngle = qad_utils.getAngleBy2Pts(self.center, pt1) + + if startAngle < endAngle: + totalAngle = endAngle - startAngle + else: + totalAngle = (2 * math.pi - startAngle) + endAngle + + return self.radius * totalAngle + + + def area(self): + return math.pi * self.radius * self.radius + + + def isPtOnCircle(self, point): + return True if self.whereIsPt(point) == 0 else False # -1 interno, 0 sulla circonferenza, 1 esterno + + + # ============================================================================ + # whereIsPt + # ============================================================================ + def whereIsPt(self, point): + # ritorna -1 se il punto è interno, 0 se è sulla circonferenza, 1 se è esterno + dist = self.center.distance(point) + if qad_utils.doubleNear(dist, self.radius): return 0 + elif dist < self.radius: return -1 # interno + else: return 1 # esterno + + + def getQuadrantPoints(self): + # ritorna i punti quadranti: pt in alto, pt in basso, a destra, a sinistra del centro + pt1 = QgsPointXY(self.center.x(), self.center.y() + self.radius) + pt2 = QgsPointXY(self.center.x(), self.center.y()- self.radius) + pt3 = QgsPointXY(self.center.x() + self.radius, self.center.y()) + pt4 = QgsPointXY(self.center.x() - self.radius, self.center.y()) + return [pt1, pt2, pt3, pt4] + + + + + # ============================================================================ + # asPolyline + # ============================================================================ + def asPolyline(self, tolerance2ApproxCurve = None, atLeastNSegment = None): + """ + ritorna una lista di punti che definisce il cerchio + """ + + if tolerance2ApproxCurve is None: + tolerance = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) + else: + tolerance = tolerance2ApproxCurve + + if atLeastNSegment is None: + _atLeastNSegment = QadVariables.get(QadMsg.translate("Environment variables", "CIRCLEMINSEGMENTQTY"), 12) + else: + _atLeastNSegment = atLeastNSegment + + # Calcolo la lunghezza del segmento con pitagora + dummy = self.radius - tolerance + if dummy <= 0: # se la tolleranza è troppo bassa rispetto al raggio + SegmentLen = self.radius + else: + dummy = (self.radius * self.radius) - (dummy * dummy) + SegmentLen = math.sqrt(dummy) # radice quadrata + SegmentLen = SegmentLen * 2 + + if SegmentLen == 0: # se la tolleranza è troppo bassa la lunghezza del segmento diventa zero + return None + + # calcolo quanti segmenti ci vogliono (non meno di _atLeastNSegment) + SegmentTot = math.ceil(self.length() / SegmentLen) + if SegmentTot < _atLeastNSegment: + SegmentTot = _atLeastNSegment + + points = [] + # primo punto + firsPt = qad_utils.getPolarPointByPtAngle(self.center, 0, self.radius) + points.append(firsPt) + + i = 1 + angle = 0 + offsetAngle = 2 * math.pi / SegmentTot + while i < SegmentTot: + angle = angle + offsetAngle + pt = qad_utils.getPolarPointByPtAngle(self.center, angle, self.radius) + points.append(pt) + i = i + 1 + + # ultimo punto (come il primo) + points.append(firsPt) + return points + + + # =============================================================================== + # asCircularString + # =============================================================================== + def asCircularString(self): + """ + la funzione ritorna il cerchio in forma di circularString. + """ + circle = QgsCircle(QgsPoint(self.center), self.radius) + return circle.toCircularString() + + + # =============================================================================== + # asLineString + # =============================================================================== + def asLineString(self, tolerance2ApproxCurve = None, atLeastNSegment = None): + """ + la funzione ritorna il cerchio in forma di lineString. + """ + pts = self.asPolyline(tolerance2ApproxCurve, atLeastNSegment) + if pts is None or len(pts) == 0: + return None + return QgsLineString(pts) + + + # =============================================================================== + # asAbstractGeom + # =============================================================================== + def asAbstractGeom(self, wkbType = QgsWkbTypes.LineString, tolerance2ApproxCurve = None, atLeastNSegment = None): + """ + la funzione ritorna il cerchio in forma di QgsAbstractGeometry. + """ + flatType = QgsWkbTypes.flatType(wkbType) + + if flatType == QgsWkbTypes.CompoundCurve: + circularString = self.asCircularString() + compoundCurve = QgsCompoundCurve() + compoundCurve.addCurve(circularString) + return compoundCurve + + elif flatType == QgsWkbTypes.MultiCurve: + circularString = self.asCircularString() + multiCurve = QgsMultiCurve() + multiCurve.addGeometry(circularString) + return multiCurve + + elif flatType == QgsWkbTypes.CurvePolygon: + circularString = self.asCircularString() + curvePolygon = QgsCurvePolygon() + curvePolygon.setExteriorRing(circularString) + return curvePolygon + + elif flatType == QgsWkbTypes.MultiSurface: # Geometry that is combined from several CurvePolygon is called MultiSurface + curvePolygon = self.asAbstractGeom(QgsWkbTypes.CurvePolygon, tolerance2ApproxCurve, atLeastNSegment) + multiSurface = QgsMultiSurface() + multiSurface.addGeometry(curvePolygon) + return multiSurface + + elif flatType == QgsWkbTypes.Polygon: + linestring = self.asLineString(tolerance2ApproxCurve, atLeastNSegment) + polygon = QgsPolygon() + polygon.setExteriorRing(linestring) + return polygon + + elif flatType == QgsWkbTypes.MultiPolygon: + polygon = self.asAbstractGeom(QgsWkbTypes.Polygon, tolerance2ApproxCurve, atLeastNSegment) + multiPolygon = QgsMultiPolygon() + multiPolygon.addGeometry(polygon) + return multiPolygon + + elif flatType == QgsWkbTypes.MultiLineString: + lineString = self.asLineString(tolerance2ApproxCurve, atLeastNSegment) + multiLineString = QgsMultiLineString() + multiLineString.addGeometry(lineString) + return multiLineString + + return self.asLineString(tolerance2ApproxCurve, atLeastNSegment) + + + # =============================================================================== + # asGeom + # =============================================================================== + def asGeom(self, wkbType = QgsWkbTypes.LineString, tolerance2ApproxCurve = None, atLeastNSegment = None): + """ + la funzione ritorna il cerchio in forma di QgsGeometry. + """ + return QgsGeometry(self.asAbstractGeom(wkbType, tolerance2ApproxCurve, atLeastNSegment)) + + + # ============================================================================ + # fromPolyline + # ============================================================================ + def fromPolyline(self, points, atLeastNSegment = None): + """ + setta le caratteristiche del cerchio incontrato nella lista di punti + ritorna True se é stato trovato un cerchio altrimenti False. + N.B. in punti NON devono essere in coordinate geografiche + """ + # se il punto iniziale e quello finale non coincidono non é un cerchio + if points[0] != points[-1]: + return False + + totPoints = len(points) + + if atLeastNSegment is None: + _atLeastNSegment = QadVariables.get(QadMsg.translate("Environment variables", "CIRCLEMINSEGMENTQTY"), 12) + else: + _atLeastNSegment = atLeastNSegment + + # perché sia un cerchio ci vogliono almeno _atLeastNSegment segmenti + if (totPoints - 1) < _atLeastNSegment or _atLeastNSegment < 2: + return False + + # sposto i primi 3 punti vicino a 0,0 per migliorare la precisione dei calcoli + dx = points[0].x() + dy = points[0].y() + myPoints = [] + myPoints.append(qad_utils.movePoint(points[0], -dx, -dy)) + myPoints.append(qad_utils.movePoint(points[1], -dx, -dy)) + myPoints.append(qad_utils.movePoint(points[2], -dx, -dy)) + +# InfinityLinePerpOnMiddle1 = qad_utils.getInfinityLinePerpOnMiddle(myPoints[0], myPoints[1]) +# if InfinityLinePerpOnMiddle1 is None: return False +# InfinityLinePerpOnMiddle2 = qad_utils.getInfinityLinePerpOnMiddle(myPoints[1], myPoints[2]) +# if InfinityLinePerpOnMiddle2 is None: return False +# +# # calcolo il presunto centro con 2 segmenti +# center = qad_utils.getIntersectionPointOn2InfinityLines(InfinityLinePerpOnMiddle1[0], \ +# InfinityLinePerpOnMiddle1[1], \ +# InfinityLinePerpOnMiddle2[0], \ +# InfinityLinePerpOnMiddle2[1]) +# if center is None: return False # linee parallele + + # se uso QgsCircle ottengo una miglior precisione + circle = QgsCircle.from3Points(QgsPoint(myPoints[0]), QgsPoint(myPoints[1]), QgsPoint(myPoints[2])) + if circle.isEmpty() == True: + return False + + center = circle.center() + center = QgsPointXY(center.x(), center.y()) + radius = center.distance(myPoints[0]) # calcolo il presunto raggio + + # se il punto finale dell'arco è a sinistra del + # segmento che unisce i punti iniziale e intermedio allora il verso è antiorario + startClockWise = False if qad_utils.leftOfLine(myPoints[2], myPoints[0], myPoints[1]) < 0 else True + angle = qad_utils.getAngleBy3Pts(myPoints[0], center, myPoints[2], startClockWise) + + i = 3 + while i < totPoints: + # sposto i punti vicino a 0,0 per migliorare la precisione dei calcoli + myPoints.append(qad_utils.movePoint(points[i], -dx, -dy)) + + # se TOLERANCE2COINCIDENT = 0,001 viene riconosciuto un cerchio di 1000 m + # se il punto calcolato non è abbastanza vicino al punto reale + # altrimenti trovo problemi con le intersezioni con gli oggetti + if qad_utils.ptNear(qad_utils.getPolarPointByPtAngle(center, qad_utils.getAngleBy2Pts(center, myPoints[i]), radius), \ + myPoints[i]) == False: + return False + + # calcolo il verso dell'arco e l'angolo + clockWise = True if qad_utils.leftOfLine(myPoints[i], myPoints[i - 1], myPoints[i - 2]) < 0 else False + # il verso deve essere lo stesso di quello originale + if startClockWise != clockWise: return False + angle = angle + qad_utils.getAngleBy3Pts(myPoints[i-1], center, myPoints[i], startClockWise) + # l'angolo incritto non può essere > di 360 + if qad_utils.doubleSmallerOrEquals(angle, 2 * math.pi): + i = i + 1 + else: + return False + + self.center = center + self.radius = radius + # traslo la geometria per riportarla alla sua posizione originale + self.move(dx, dy) + + return True + + + # ============================================================================ + # move + # ============================================================================ + def move(self, offsetX, offsetY): + self.center = qad_utils.movePoint(self.center, offsetX, offsetY) + + + # ============================================================================ + # rotate + # ============================================================================ + def rotate(self, basePt, angle): + self.center = qad_utils.rotatePoint(self.center, basePt, angle) + + + # ============================================================================ + # scale + # ============================================================================ + def scale(self, basePt, scale): + self.center = qad_utils.scalePoint(self.center, basePt, scale) + self.radius = self.radius * scale + + + # ============================================================================ + # mirror + # ============================================================================ + def mirror(self, mirrorPt, mirrorAngle): + self.center = qad_utils.mirrorPoint(self.center, mirrorPt, mirrorAngle) + + + # =============================================================================== + # offset + # =============================================================================== + def offset(self, offsetDist, offsetSide): + """ + la funzione modifica il cerchio facendone l'offset + secondo una distanza e un lato di offset ("internal" o "external") + """ + if offsetSide == "internal": + # offset verso l'interno del cerchio + radius = self.radius - offsetDist + if radius <= 0: + return False + else: + # offset verso l'esterno del cerchio + radius = self.radius + offsetDist + + self.radius = radius + + return True + + + # ============================================================================ + # fromCenterPtArea + # ============================================================================ + def fromCenterArea(self, centerPt, area): + """ + setta le caratteristiche del cerchio attraverso: + il punto centrale + area + """ + if centerPt is None or area <= 0: + return None + self.center = centerPt + self.radius = math.sqrt(area / math.pi) + return self + + + # ============================================================================ + # fromDiamEnds + # ============================================================================ + def fromDiamEnds(self, startPt, endPt): + """ + setta le caratteristiche del cerchio attraverso i punti estremità del diametro: + punto iniziale + punto finale + """ + self.radius = qad_utils.getDistance(startPt, endPt) / 2 + if self.radius == 0: + return None + self.center = qad_utils.getMiddlePoint(startPt, endPt) + return self diff --git a/qad_circle_fun.py b/qad_circle_fun.py new file mode 100644 index 00000000..ad7a5422 --- /dev/null +++ b/qad_circle_fun.py @@ -0,0 +1,1526 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + funzioni per creare cerchi + + ------------------- + begin : 2018-04-08 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.PyQt.QtCore import * +from qgis.PyQt.QtGui import * +from qgis.core import * +from qgis.gui import * +import qgis.utils + +import math + +from . import qad_utils +from .qad_geom_relations import * + + +# ============================================================================ +# circleFrom3Pts +# ============================================================================ +def circleFrom3Pts(firstPt, secondPt, thirdPt): + """ + crea un cerchio attraverso: + punto iniziale + secondo punto (intermedio) + punto finale + """ + l = QadLine() + l.set(firstPt, secondPt) + InfinityLinePerpOnMiddle1 = QadPerpendicularity.getInfinityLinePerpOnMiddleLine(l) + l.set(secondPt, thirdPt) + InfinityLinePerpOnMiddle2 = QadPerpendicularity.getInfinityLinePerpOnMiddleLine(l) + if InfinityLinePerpOnMiddle1 is None or InfinityLinePerpOnMiddle2 is None: + return None + center = QadIntersections.twoInfinityLines(InfinityLinePerpOnMiddle1, InfinityLinePerpOnMiddle2) + if center is None: return None # linee parallele + radius = center.distance(firstPt) + + return QadCircle().set(center, radius) + + +# =========================================================================== +# circleFrom2IntPtsCircleTanPts +# =========================================================================== +def circleFrom2IntPtsCircleTanPts(pt1, pt2, circle, pt): + """ + crea un cerchio attraverso 2 punti di intersezione e un cerchio tangente: + punto1 di intersezione + punto2 di intersezione + cerchio di tangenza (oggetto QadCircle) + punto di selezione cerchio + """ + # http://www.batmath.it/matematica/a_apollonio/ppc.htm + circleList = [] + + if pt1 == pt2: return None + + dist1 = pt1.distance(circle.center) # distanza del punto 1 dal centro + dist2 = pt2.distance(circle.center) # distanza del punto 2 dal centro + + # entrambi i punti devono essere esterni o interni a circle + if (dist1 > circle.radius and dist2 < circle.radius) or \ + (dist1 < circle.radius and dist2 > circle.radius): + return None + + l = QadLine() + l.set(pt1, pt2) + + if dist1 == dist2: # l'asse di pt1 e pt2 passa per il centro di circle + if dist1 == circle.radius: # entrambi i punti sono sulla circonferenza di circle + return None + + axis = QadPerpendicularity.getInfinityLinePerpOnMiddleLine(l) # asse di pt1 e pt2 + intPts = QadIntersections.infinityLineWithCircle(axis, circle) # punti di intersezione tra l'asse e circle + for intPt in intPts: + circleTan = circleFrom3Pts(pt1, pt2, intPt) + if circleTan is not None: + circleList.append(circleTan) + elif dist1 > circle.radius and dist2 > circle.radius : # entrambi i punti sono esterni a circle + # mi ricavo una qualunque circonferenza passante per p1 e p2 ed intersecante circle + circleInt = circleFrom3Pts(pt1, pt2, circle.center) + if circleInt is None: return None + + intPts = QadIntersections.twoCircles(circle, circleInt) + l1 = QadLine().set(pt1, pt2) + l2 = QadLine().set(intPts[0], intPts[1]) + intPt = QadIntersections.twoInfinityLines(l1, l2) + tanPts = QadTangency.fromPointToCircle(intPt, circle) + for tanPt in tanPts: + circleTan = circleFrom3Pts(pt1, pt2, tanPt) + if circleTan is not None: + circleList.append(circleTan) + elif dist1 < circle.radius and dist2 < circle.radius : # entrambi i punti sono interni a circle + # mi ricavo una qualunque circonferenza passante per p1 e p2 ed intersecante circle + ptMiddle = qad_utils.getMiddlePoint(pt1, pt2) + angle = qad_utils.getAngleBy2Pts(pt1, pt2) + math.pi / 2 + pt3 = qad_utils.getPolarPointByPtAngle(ptMiddle, angle, 2 * circle.radius) + circleInt = circleFrom3Pts(pt1, pt2, pt3) + if circleInt is None: + return None + intPts = QadIntersections.twoCircles(circle, circleInt) + l1 = QadLine().set(pt1, pt2) + l2 = QadLine().set(intPts[0], intPts[1]) + intPt = QadIntersections.twoInfinityLines(l1, l2) + tanPts = QadTangency.fromPointToCircle(intPt, circle) + for tanPt in tanPts: + circleTan = circleFrom3Pts(pt1, pt2, tanPt) + if circleTan is not None: + circleList.append(circleTan) + elif dist1 == radius: # il punto1 sulla circonferenza di circle + # una sola circonferenza avente come centro l'intersezione tra l'asse pt1 e pt2 e la retta + # passante per il centro di circle e pt1 + axis = QadPerpendicularity.getInfinityLinePerpOnMiddleLine(l) # asse di pt1 e pt2 + l1 = QadLine().set(circle.center, pt1) + intPt = QadIntersections.twoInfinityLines(axis, l1) + circleTan = QadCircle().set(intPt, qad_utils.getDistance(pt1, intPt)) + circleList.append(circleTan) + elif dist2 == radius: # il punto3 é sulla circonferenza di circle + # una sola circonferenza avente come centro l'intersezione tra l'asse pt1 e pt2 e la retta + # passante per il centro di circle e pt2 + axis = QadPerpendicularity.getInfinityLinePerpOnMiddleLine(l) # asse di pt1 e pt2 + l2 = QadLine().set(circle.center, pt2) + intPt = QadIntersections.twoInfinityLines(axis, l2) + circleTan = QadCircle().set(intPt, qad_utils.getDistance(pt2, intPt)) + circleList.append(circleTan) + + if len(circleList) == 0: + return None + + result = QadCircle() + minDist = sys.float_info.max + for circleTan in circleList: + angle = qad_utils.getAngleBy2Pts(circleTan.center, circle.center) + if qad_utils.getDistance(circleTan.center, circle.center) < circle.radius: # cerchio interno + angle = angle + math.pi / 2 + ptInt = qad_utils.getPolarPointByPtAngle(circleTan.center, angle, circleTan.radius) + dist = qad_utils.getDistance(ptInt, pt) + + if dist < minDist: # mediamente più vicino + minDist = dist + result.center = circleTan.center + result.radius = circleTan.radius + + return result + + +# =========================================================================== +# circleFrom2IntPtsLineTanPts +# =========================================================================== +def circleFrom2IntPtsLineTanPts(pt1, pt2, line, pt, AllCircles = False): + """ + crea uno o più cerchi (vedi allCircles) attraverso 2 punti di intersezione e una linea tangente: + punto1 di intersezione + punto2 di intersezione + linea di tangenza (QadLine) + punto di selezione linea + il parametro AllCircles se = True fa restituire tutti i cerchi altrimenti solo quello più vicino a pt1 e pt2 + """ + circleList = [] + + pt1Line = line.getStartPt() + pt2Line = line.getEndPt() + + A = (pt1.x() * pt1.x()) + (pt1.y() * pt1.y()) + B = (pt2.x() * pt2.x()) + (pt2.y() * pt2.y()) + + E = - pt1.x() + pt2.x() + F = pt1.y() - pt2.y() + if F == 0: + if AllCircles == True: + return circleList + else: + return None + + G = (-A + B) / F + H = E / F + + if pt1Line.x() - pt2Line.x() == 0: + # la linea é verticale + e = pt1Line.x() + I = H * H + if I == 0: + if AllCircles == True: + return circleList + else: + return None + J = (2 * G * H) - (4 * e) + (4 * pt2.x()) + (4 * H * pt2.y()) + K = (G * G) - (4 * e * e) + (4 * B) + (4 * G * pt2.y()) + else: + # equazione della retta line -> y = dx + e + d = (pt2Line.y() - pt1Line.y()) / (pt2Line.x() - pt1Line.x()) + e = - d * pt1Line.x() + pt1Line.y() + C = 4 * (1 + d * d) + D = 2 * d * e + d2 = d * d + I = 1 + (H * H * d2) + 2 * H * d + if I == 0: + if AllCircles == True: + return circleList + else: + return None + J = (2 * d2 * G * H) + (2 * D) + (2 * D * H * d) + (2 * G * d) - (e * C * H) + (pt2.x() * C) + H * pt2.y() * C + K = (G * G * d2) + (2 * D * G * d) + (D * D) - (C * e * e) - (C * G * e) + (B * C) + (G * pt2.y() * C) + + L = (J * J) - (4 * I * K) + if L < 0: + if AllCircles == True: + return circleList + else: + return None + + a1 = (-J + math.sqrt(L)) / (2 * I) + b1 = (a1 * H) + G + c1 = - B - (a1 * pt2.x()) - (b1 * pt2.y()) + center = QgsPointXY() + center.setX(- (a1 / 2)) + center.setY(- (b1 / 2)) + radius = math.sqrt((a1 * a1 / 4) + (b1 * b1 / 4) - c1) + circle = QadCircle() + circle.set(center, radius) + circleList.append(circle) + + a2 = (-J - math.sqrt(L)) / (2 * I) + b2 = (a2 * H) + G + c2 = - B - (a2 * pt2.x()) - (b2 * pt2.y()) + center.setX(- (a2 / 2)) + center.setY(- (b2 / 2)) + radius = math.sqrt((a2 * a2 / 4) + (b2 * b2 / 4) - c2) + circle = QadCircle() + circle.set(center, radius) + circleList.append(circle) + + if AllCircles == True: + return circleList + + if len(circleList) == 0: + return None + + result = QadCircle() + minDist = sys.float_info.max + for circle in circleList: + ptInt = QadPerpendicularity.fromPointToInfinityLine(circle.center, line) + dist = ptInt.distance(pt) + + if dist < minDist: # mediamente più vicino + minDist = dist + result.center = circle.center + result.radius = circle.radius + + return result + + +# ============================================================================ +# circleFrom2IntPts1TanPt +# ============================================================================ +def circleFrom2IntPts1TanPt(pt1, pt2, geom, pt): + """ + crea un cerhcio attraverso 2 punti di intersezione ed un oggetto di tangenza: + punto1 di intersezione + punto2 di intersezione + geometria di tangenza (linea, arco o cerchio) + punto di selezione geometria + """ + objType = geom.whatIs() + + if objType != "LINE" and objType != "ARC" and objType != "CIRCLE": + return None + + if objType == "ARC": # se è arco lo trasformo in cerchio + obj = QadCircle().set(geom.center, geom.radius) + objType = "CIRCLE" + else: + obj = geom + + if objType == "LINE": + return circleFrom2IntPtsLineTanPts(pt1, pt2, obj, pt) + elif objType == "CIRCLE": + return circleFrom2IntPtsCircleTanPts(pt1, pt2, obj, pt) + + return None + + +# ============================================================================ +# circleFrom1IntPt2TanPts +# ============================================================================ +def circleFrom1IntPt2TanPts(pt, geom1, pt1, geom2, pt2): + """ + crea un cerchio attraverso 1 punti di intersezione e 2 oggetti di tangenza: + punto di intersezione + geometria1 di tangenza (linea, arco o cerchio) + punto di selezione geometria1 + geometria2 di tangenza (linea, arco o cerchio) + punto di selezione geometria2 + """ + obj1Type = geom1.whatIs() + obj2Type = geom2.whatIs() + + if (obj1Type != "LINE" and obj1Type != "ARC" and obj1Type != "CIRCLE") or \ + (obj2Type != "LINE" and obj2Type != "ARC" and obj2Type != "CIRCLE"): + return None + + if obj1Type == "ARC": # se è arco lo trasformo in cerchio + obj1 = QadCircle().set(geom1.center, geom1.radius) + obj1Type = "CIRCLE" + else: + obj1 = geom1 + + if obj2Type == "ARC": # se è arco lo trasformo in cerchio + obj2 = QadCircle().set(geom2.center, geom2.radius) + obj2Type = "CIRCLE" + else: + obj2 = geom2 + + if obj1Type == "LINE": + if obj2Type == "LINE": + return circleFrom1IntPtLineLineTanPts(pt, obj1, pt1, obj2, pt2) + elif obj2Type == "CIRCLE": + return circleFrom1IntPtLineCircleTanPts(pt, obj1, pt1, obj2, pt2) + elif obj1Type == "CIRCLE": + if obj2Type == "LINE": + return circleFrom1IntPtLineCircleTanPts(pt, obj2, pt2, obj1, pt1) + elif obj2Type == "CIRCLE": + return circleFrom1IntPtCircleCircleTanPts(pt, obj1, pt1, obj2, pt2) + + return None + + +# =========================================================================== +# circleFrom1IntPtLineLineTanPts +# =========================================================================== +def circleFrom1IntPtLineLineTanPts(pt, line1, pt1, line2, pt2, AllCircles = False): + """ + crea uno o più cerchi (vedi allCircles) attraverso 1 punti di intersezione e due linee tangenti: + punto di intersezione + linea1 di tangenza (QLine) + punto di selezione linea1 + linea2 di tangenza (QLine) + punto di selezione linea2 + il parametro AllCircles se = True fa restituire tutti i cerchi e non sono quello più vicino a pt1 e pt2 + """ + # http://www.batmath.it/matematica/a_apollonio/prr.htm + circleList = [] + + # verifico se le rette sono parallele + ptInt = QadIntersections.twoInfinityLines(line1, line2) + if ptInt is None: # le rette sono parallele + # Se le rette sono parallele il problema ha soluzioni solo se il punto + # é non esterno alla striscia individuata dalle due rette e basta considerare + # il simmetrico di A rispetto alla bisettrice della striscia. + ptPerp = QadPerpendicularity.fromPointToInfinityLine(line2.getStartPt(), line1) + angle = qad_utils.getAngleBy2Pts(line2.getStartPt(), ptPerp) + dist = qad_utils.getDistance(line2.getStartPt(), ptPerp) + pt1ParLine = qad_utils.getPolarPointByPtAngle(line2.getStartPt(), angle, dist / 2) + angle = angle + math.pi / 2 + pt2ParLine = qad_utils.getPolarPointByPtAngle(pt1ParLine, angle, dist) + l = QadLine().set(pt1ParLine, pt2ParLine) + ptPerp = QadPerpendicularity.fromPointToInfinityLine(pt, l) + dist = qad_utils.getDistance(pt, ptPerp) + + # trovo il punto simmetrico + angle = qad_utils.getAngleBy2Pts(pt, ptPerp) + ptSymmetric = qad_utils.getPolarPointByPtAngle(pt, angle, dist * 2) + return circleFrom2IntPtsLineTanPts(pt, ptSymmetric, line1, pt1, AllCircles) + else: # le rette non sono parallele + if ptInt == pt: + return None + # se il punto é sulla linea1 o sulla linea2 + ptPerp1 = QadPerpendicularity.fromPointToInfinityLine(pt, line1) + ptPerp2 = QadPerpendicularity.fromPointToInfinityLine(pt, line2) + if ptPerp1 == pt or ptPerp2 == pt: + # Se le rette sono incidenti ed il punto appartiene ad una delle due la costruzione + # é quasi immediata: basta tracciare le bisettrici dei due angoli individuati dalle rette + # e la perpendicolare per pt alla retta cui appartiene pt stesso. Si avranno due circonferenze. + + if ptPerp1 == pt: # se il punto é sulla linea1 + angle = qad_utils.getAngleBy2Pts(line2.getStartPt(), line2.getEndPt()) + ptLine = qad_utils.getPolarPointByPtAngle(ptInt, angle, 10) + Bisector1 = qad_utils.getBisectorInfinityLine(pt, ptInt, ptLine) + ptLine = qad_utils.getPolarPointByPtAngle(ptInt, angle + math.pi, 10) + Bisector2 = qad_utils.getBisectorInfinityLine(pt, ptInt, ptLine) + angle = qad_utils.getAngleBy2Pts(line1.getStartPt(), line1.getEndPt()) + ptPerp = qad_utils.getPolarPointByPtAngle(pt, angle + math.pi / 2, 10) + else: # se il punto é sulla linea2 + angle = qad_utils.getAngleBy2Pts(line1.getStartPt(), line1.getEndPt()) + ptLine = qad_utils.getPolarPointByPtAngle(ptInt, angle, 10) + Bisector1 = qad_utils.getBisectorInfinityLine(pt, ptInt, ptLine) + ptLine = qad_utils.getPolarPointByPtAngle(ptInt, angle + math.pi, 10) + Bisector2 = qad_utils.getBisectorInfinityLine(pt, ptInt, ptLine) + angle = qad_utils.getAngleBy2Pts(line2.getStartPt(), line2.getEndPt()) + ptPerp = qad_utils.getPolarPointByPtAngle(pt, angle + math.pi / 2, 10) + + l1 = QadLine().set(Bisector1[0], Bisector1[1]) + l2 = QadLine().set(pt, ptPerp) + center = QadIntersections.twoInfinityLines(l1, l2) + + radius = qad_utils.getDistance(pt, center) + circleTan = QadCircle() + circleTan.set(center, radius) + circleList.append(circleTan) + + l1.set(Bisector2[0], Bisector2[1]) + center = QadIntersections.twoInfinityLines(l1, l2) + radius = qad_utils.getDistance(pt, center) + circleTan = QadCircle() + circleTan.set(center, radius) + circleList.append(circleTan) + else: + # Bisettrice dell'angolo interno del triangolo avente come vertice i punti di intersezione delle rette + Bisector = qad_utils.getBisectorInfinityLine(ptPerp1, ptInt, ptPerp2) + l = QadLine().set(Bisector[0], Bisector[1]) + ptPerp = QadPerpendicularity.fromPointToInfinityLine(pt, l) + dist = qad_utils.getDistance(pt, ptPerp) + + # trovo il punto simmetrico + angle = qad_utils.getAngleBy2Pts(pt, ptPerp) + ptSymmetric = qad_utils.getPolarPointByPtAngle(pt, angle, dist * 2) + return circleFrom2IntPtsLineTanPts(pt, ptSymmetric, line1, pt1, AllCircles) + + if AllCircles == True: + return circleList + + if len(circleList) == 0: + return None + + result = QadCircle() + AvgList = [] + Avg = sys.float_info.max + for circleTan in circleList: + del AvgList[:] # svuoto la lista + + ptInt = QadPerpendicularity.fromPointToInfinityLine(circleTan.center, line1) + AvgList.append(qad_utils.getDistance(ptInt, pt1)) + + ptInt = QadPerpendicularity.fromPointToInfinityLine(circleTan.center, line2) + AvgList.append(qad_utils.getDistance(ptInt, pt2)) + + currAvg = qad_utils.numericListAvg(AvgList) + if currAvg < Avg: # mediamente più vicino + Avg = currAvg + result.center = circleTan.center + result.radius = circleTan.radius + + return result + + +# =============================================================================== +# solveCircleTangentTo2LinesAndCircle +# =============================================================================== +def solveCircleTangentTo2LinesAndCircle(line1, line2, circle, s1, s2): + ''' + Trova i due cerchi tangenti a due rette e un cerchio (sarebbero 8 cerchi che si trovano con le + 4 combinazioni di s1, s2 che assumo valore -1 o 1) + e restituisce quello più vicino a pt + ''' + circleList = [] + # http://www.batmath.it/matematica/a_apollonio/rrc.htm + + # Questa costruzione utilizza una particolare trasformazione geometrica, che alcuni chiamano dilatazione parallela: + # si immagina che il raggio r del cerchio dato c si riduca a zero (il cerchio é ridotto al suo centro), + # mentre le rette rimangono parallele con distanze dal centro del cerchio che si é ridotto a zero aumentate o + # diminuite di r. Si é così ricondotti al caso di un punto e due rette e si può applicare una delle tecniche viste + # in quel caso. + + line1Par = [] + angle = qad_utils.getAngleBy2Pts(line1.getStartPt(), line1.getEndPt()) + line1Par.append(qad_utils.getPolarPointByPtAngle(line1[0], angle + math.pi / 2, circle.radius * s1)) + line1Par.append(qad_utils.getPolarPointByPtAngle(line1.getEndPt(), angle + math.pi / 2, circle.radius * s1)) + + line2Par = [] + angle = qad_utils.getAngleBy2Pts(line2.getStartPt(), line2.getEndPt()) + line2Par.append(qad_utils.getPolarPointByPtAngle(line2.getStartPt(), angle + math.pi / 2, circle.radius * s2)) + line2Par.append(qad_utils.getPolarPointByPtAngle(line2.getEndPt(), angle + math.pi / 2, circle.radius * s2)) + + circleList = circleFrom1IntPtLineLineTanPts(circle.center, line1Par, None, line2Par, None, True) + + for circleTan in circleList: + ptPerp = qad_utils.getPerpendicularPointOnInfinityLine(line1.getStartPt(), line1.getEndPt(), circleTan.center) + circleTan.radius = qad_utils.getDistance(ptPerp, circleTan.center) + + return circleList + + +# ============================================================================ +# circleFromLineLineCircleTanPts +# ============================================================================ +def circleFromLineLineCircleTanPts(line1, pt1, line2, pt2, circle, pt3): + """ + crea un cerchio attraverso tre linee: + linea1 di tangenza (QadLine) + punto di selezione linea1 + linea2 di tangenza (QadLine) + punto di selezione linea2 + cerchio di tangenza (oggetto QadCircle) + punto di selezione cerchio + """ + circleList = [] + + circleList.extend(solveCircleTangentTo2LinesAndCircle(line1, line2, circle, -1, -1)) + circleList.extend(solveCircleTangentTo2LinesAndCircle(line1, line2, circle, -1, 1)) + circleList.extend(solveCircleTangentTo2LinesAndCircle(line1, line2, circle, 1, -1)) + circleList.extend(solveCircleTangentTo2LinesAndCircle(line1, line2, circle, 1, 1)) + + if len(circleList) == 0: + return None + + result = QadCircle() + AvgList = [] + Avg = sys.float_info.max + for circleTan in circleList: + del AvgList[:] # svuoto la lista + + ptInt = qad_utils.getPerpendicularPointOnInfinityLine(line1.getStartPt(), line1.getEndPt(), circleTan.center) + AvgList.append(ptInt.distance(pt1)) + + ptInt = qad_utils.getPerpendicularPointOnInfinityLine(line2.getStartPt(), line2.getEndPt(), circleTan.center) + AvgList.append(ptInt.distance(pt2)) + + angle = qad_utils.getAngleBy2Pts(circleTan.center, circle.center) + if circleTan.center.distance(circle.center) < circle.radius: # cerchio interno + angle = angle + math.pi / 2 + ptInt = qad_utils.getPolarPointByPtAngle(circleTan.center, angle, circleTan.radius) + AvgList.append(ptInt.distance(pt3)) + + currAvg = qad_utils.numericListAvg(AvgList) + if currAvg < Avg: # mediamente più vicino + Avg = currAvg + result.center = circleTan.center + result.radius = circleTan.radius + + return True + + +# ============================================================================ +# circleFrom3TanPts +# ============================================================================ +def circleFrom3TanPts(geom1, pt1, geom2, pt2, geom3, pt3): + """ + crea un cerchio attraverso tre oggetti di tangenza per le estremità del diametro: + geometria 1 di tangenza (linea, arco o cerchio) + punto di selezione geometria 1 + geometria 2 di tangenza (linea, arco o cerchio) + punto di selezione geometria 2 + """ + obj1Type = geom1.whatIs() + obj2Type = geom2.whatIs() + obj3Type = geom3.whatIs() + + if (obj1Type != "LINE" and obj1Type != "ARC" and obj1Type != "CIRCLE") or \ + (obj2Type != "LINE" and obj2Type != "ARC" and obj2Type != "CIRCLE") or \ + (obj3Type != "LINE" and obj3Type != "ARC" and obj3Type != "CIRCLE"): + return None + + if obj1Type == "ARC": # se è arco lo trasformo in cerchio + obj1 = QadCircle().set(geom1.center, geom1.radius) + obj1Type = "CIRCLE" + else: + obj1 = geom1 + + if obj2Type == "ARC": # se è arco lo trasformo in cerchio + obj2 = QadCircle().set(geom2.center, geom2.radius) + obj2Type = "CIRCLE" + else: + obj2 = geom2 + + if obj3Type == "ARC": # se è arco lo trasformo in cerchio + obj3 = QadCircle().set(geom3.center, geom3.radius) + obj3Type = "CIRCLE" + else: + obj3 = geom3 + + if obj1Type == "LINE": + if obj2Type == "LINE": + if obj3Type == "LINE": + return circleFromLineLineLineTanPts(obj1, pt1, obj2, pt2, obj3, pt3) + elif obj3Type == "CIRCLE": + return circleFromLineLineCircleTanPts(obj1, pt1, obj2, pt2, obj3, pt3) + elif obj2Type == "CIRCLE": + if obj3Type == "LINE": + return circleFromLineLineCircleTanPts(obj1, pt1, obj3, pt3, obj2, pt2) + elif obj3Type == "CIRCLE": + return circleFromLineCircleCircleTanPts(obj1, pt1, obj2, pt2, obj3, pt3) + elif obj1Type == "CIRCLE": + if obj2Type == "LINE": + if obj3Type == "LINE": + return circleFromLineLineCircleTanPts(obj2, pt2, obj3, pt3, obj1, pt1) + elif obj3Type == "CIRCLE": + return circleFromLineCircleCircleTanPts(obj2, pt2, obj1, pt1, obj3, pt3) + elif obj2Type == "CIRCLE": + if obj3Type == "LINE": + return circleFromLineCircleCircleTanPts(obj3, pt3, obj1, pt1, obj2, pt2) + elif obj3Type == "CIRCLE": + return circleFromCircleCircleCircleTanPts(obj1, pt1, obj2, pt2, obj3, pt3) + + return None + + +# ============================================================================ +# circleFromLineLineLineTanPts +# ============================================================================ +def circleFromLineLineLineTanPts(line1, pt1, line2, pt2, line3, pt3): + """ + Crea un cerchio attraverso tre linee: + linea1 di tangenza (QadLine) + punto di selezione linea1 + linea2 di tangenza (QadLine) + punto di selezione linea2 + linea3 di tangenza (QadLine) + punto di selezione linea3 + """ + circleList = [] + + # Punti di intersezione delle rette (line1, line2, line3) + ptInt1 = QadIntersections.twoInfinityLines(line1, line2) + ptInt2 = QadIntersections.twoInfinityLines(line2, line3) + ptInt3 = QadIntersections.twoInfinityLines(line3, line1) + + # tre rette parallele + if (ptInt1 is None) and (ptInt2 is None): + return circleList + + if (ptInt1 is None): # la linea1 e linea2 sono parallele + circleList.extend(circleFrom2ParLinesLineTanPts(line1, line2, line3)) + elif (ptInt2 is None): # la linea2 e linea3 sono parallele + circleList.extend(circleFrom2ParLinesLineTanPts(line2, line3, line1)) + elif (ptInt3 is None): # la linea3 e linea1 sono parallele + circleList.extend(circleFrom2ParLinesLineTanPts(line3, line1, line2)) + else: + # Bisettrici degli angoli interni del triangolo avente come vertici i punti di intersezione delle rette + Bisector123 = qad_utils.getBisectorInfinityLine(ptInt1, ptInt2, ptInt3) + Bisector231 = qad_utils.getBisectorInfinityLine(ptInt2, ptInt3, ptInt1) + Bisector312 = qad_utils.getBisectorInfinityLine(ptInt3, ptInt1, ptInt2) + # Punto di intersezione delle bisettrici = centro delle circonferenza inscritta al triangolo + l1 = QadLine().set(Bisector123[0], Bisector123[1]) + l2 = QadLine().set(Bisector231[0], Bisector231[1]) + center = QadIntersections.twoInfinityLines(l1, l2) + + # Perpendicolari alle rette line1 passanti per il centro della circonferenza inscritta + ptPer = QadPerpendicularity.fromPointToInfinityLine(center, line1) + radius = center.distance(ptPer) + circle = QadCircle() + circle.set(center, radius) + circleList.append(circle) + + # Bisettrici degli angoli esterni del triangolo + angle = qad_utils.getAngleBy2Pts(Bisector123[0], Bisector123[1]) + math.pi / 2 + Bisector123 = QadLine().set(ptInt2, qad_utils.getPolarPointByPtAngle(ptInt2, angle, 10)) + + angle = qad_utils.getAngleBy2Pts(Bisector231[0], Bisector231[1]) + math.pi / 2 + Bisector231 = QadLine().set(ptInt3, qad_utils.getPolarPointByPtAngle(ptInt3, angle, 10)) + + angle = qad_utils.getAngleBy2Pts(Bisector312[0], Bisector312[1]) + math.pi / 2 + Bisector312 = QadLine().set(ptInt1, qad_utils.getPolarPointByPtAngle(ptInt1, angle, 10)) + + # Punti di intersezione delle bisettrici = centro delle circonferenze ex-inscritte + center = QadIntersections.twoInfinityLines(Bisector123, Bisector231) + l = QadLine().set(ptInt2, ptInt3) + ptPer = QadPerpendicularity.fromPointToInfinityLine(center, l) + radius = center.distance(ptPer) + circle = QadCircle() + circle.set(center, radius) + circleList.append(circle) + + center = QadIntersections.twoInfinityLines(Bisector231, Bisector312) + l.set(ptInt3, ptInt1) + ptPer = QadPerpendicularity.fromPointToInfinityLine(center, l) + radius = center.distance(ptPer) + circle = QadCircle() + circle.set(center, radius) + circleList.append(circle) + + center = QadIntersections.twoInfinityLines(Bisector312, Bisector123) + l.set(ptInt1, ptInt2) + ptPer = QadPerpendicularity.fromPointToInfinityLine(center, l) + radius = center.distance(ptPer) + circle = QadCircle() + circle.set(center, radius) + circleList.append(circle) + + if len(circleList) == 0: + return None + + result = QadCircle() + AvgList = [] + Avg = sys.float_info.max + for circleTan in circleList: + del AvgList[:] # svuoto la lista + + ptInt = QadPerpendicularity.fromPointToInfinityLine(circleTan.center, line1) + AvgList.append(ptInt.distance(pt1)) + + ptInt = QadPerpendicularity.fromPointToInfinityLine(circleTan.center, line2) + AvgList.append(ptInt.distance(pt2)) + + ptInt = QadPerpendicularity.fromPointToInfinityLine(circleTan.center, line3) + AvgList.append(ptInt.distance(pt3)) + + currAvg = qad_utils.numericListAvg(AvgList) + if currAvg < Avg: # mediamente più vicino + Avg = currAvg + result.center = circleTan.center + result.radius = circleTan.radius + + return result + + +# =========================================================================== +# circleFrom2ParLinesLineTanPts +# =========================================================================== +def circleFrom2ParLinesLineTanPts(parLine1, parLine2, line3): + """ + Crea due cerchi attraverso 2 linee parallele e una terza linea non parallela: + linea1 di tangenza (QadLine) parallela a linea2 + linea2 di tangenza (QadLine) parallela a linea1 + linea3 di tangenza (QadLine) + """ + circleList = [] + + ptInt2 = QadIntersections.twoInfinityLines(parLine2, line3) + ptInt3 = QadIntersections.twoInfinityLines(line3, parLine1) + + if parLine1.getStartPt() == ptInt3: + pt = parLine1.getEndPt() + else: + pt = parLine1.getStartPt() + Bisector123 = qad_utils.getBisectorInfinityLine(pt, ptInt2, ptInt3) + + if parLine2.getStartPt() == ptInt2: + pt = parLine2.getEndPt() + else: + pt = parLine2.getStartPt() + Bisector312 = qad_utils.getBisectorInfinityLine(pt, ptInt3, ptInt2) + + # Punto di intersezione delle bisettrici = centro delle circonferenza + center = qad_utils.getIntersectionPointOn2InfinityLines(Bisector123[0], Bisector123[1], \ + Bisector312[0], Bisector312[1]) + ptPer = QadPerpendicularity.fromPointToInfinityLine(center, parLine1) + radius = center.distance(ptPer) + circle = QadCircle() + circle.set(center, radius) + circleList.append(circle) + + # Bisettrici degli angoli esterni + Bisector123 = Bisector123 + math.pi / 2 + Bisector312 = Bisector312 + math.pi / 2 + # Punto di intersezione delle bisettrici = centro delle circonferenza + center = qad_utils.getIntersectionPointOn2InfinityLines(Bisector123[0], Bisector123[1], \ + Bisector312[0], Bisector312[1]) + ptPer = QadPerpendicularity.fromPointToInfinityLine(center, parLine1) + radius = center.distance(ptPer) + circle = QadCircle() + circle.set(center, radius) + circleList.append(circle) + + return circleList + + +# ============================================================================ +# circleFromLineCircleCircleTanPts +# ============================================================================ +def circleFromLineCircleCircleTanPts(line, pt, circle1, pt1, circle2, pt2): + """ + setta le caratteristiche del cerchio attraverso tre linee: + linea di tangenza (QadLine) + punto di selezione linea + cerchio1 di tangenza (oggetto QadCircle) + punto di selezione cerchio1 + cerchio2 di tangenza (oggetto QadCircle) + punto di selezione cerchio2 + """ + circleList = [] + + circleList.extend(solveCircleTangentToLineAnd2Circles(line, circle1, circle2, -1, -1)) + circleList.extend(solveCircleTangentToLineAnd2Circles(line, circle1, circle2, -1, 1)) + circleList.extend(solveCircleTangentToLineAnd2Circles(line, circle1, circle2, 1, -1)) + circleList.extend(solveCircleTangentToLineAnd2Circles(line, circle1, circle2, 1, 1)) + + if len(circleList) == 0: + return None + + result = QadCircle() + AvgList = [] + Avg = sys.float_info.max + for circleTan in circleList: + del AvgList[:] # svuoto la lista + + ptInt = QadPerpendicularity.fromPointToInfinityLine(circleTan.center, line) + AvgList.append(ptInt.distance(t)) + + angle = qad_utils.getAngleBy2Pts(circleTan.center, circle1.center) + if circleTan.center.distance(circle1.center) < circle1.radius: # cerchio interno + angle = angle + math.pi / 2 + ptInt = qad_utils.getPolarPointByPtAngle(circleTan.center, angle, circleTan.radius) + AvgList.append(ptInt.distance(pt1)) + + angle = qad_utils.getAngleBy2Pts(circleTan.center, circle2.center) + if circleTan.center.distance(circle2.center) < circle2.radius: # cerchio interno + angle = angle + math.pi / 2 + ptInt = qad_utils.getPolarPointByPtAngle(circleTan.center, angle, circleTan.radius) + AvgList.append(ptInt.distance(pt2)) + + currAvg = qad_utils.numericListAvg(AvgList) + if currAvg < Avg: # mediamente più vicino + Avg = currAvg + result.center = circleTan.center + result.radius = circleTan.radius + + return result + + +# ============================================================================ +# circleFromCircleCircleCircleTanPts +# ============================================================================ +def circleFromCircleCircleCircleTanPts(circle1, pt1, circle2, pt2, circle3, pt3): + """ + Crea un cerchio attraverso tre cerchi tangenti: + cerchio1 di tangenza (oggetto QadCircle) + punto di selezione cerchio1 + cerchio2 di tangenza (oggetto QadCircle) + punto di selezione cerchio2 + cerchio3 di tangenza (oggetto QadCircle) + punto di selezione cerchio3 + """ + circleList = [] + circle = solveApollonius(circle1, circle2, circle3, -1, -1, -1) + if circle is not None: + circleList.append(circle) + circle = solveApollonius(circle1, circle2, circle3, -1, -1, 1) + if circle is not None: + circleList.append(circle) + circle = solveApollonius(circle1, circle2, circle3, -1, 1, -1) + if circle is not None: + circleList.append(circle) + circle = solveApollonius(circle1, circle2, circle3, -1, 1, 1) + if circle is not None: + circleList.append(circle) + circle = solveApollonius(circle1, circle2, circle3, 1, -1, -1) + if circle is not None: + circleList.append(circle) + circle = solveApollonius(circle1, circle2, circle3, 1, -1, 1) + if circle is not None: + circleList.append(circle) + circle = solveApollonius(circle1, circle2, circle3, 1, 1, -1) + if circle is not None: + circleList.append(circle) + circle = solveApollonius(circle1, circle2, circle3, 1, 1, 1) + if circle is not None: + circleList.append(circle) + + if len(circleList) == 0: + return None + + result = QadCircle() + AvgList = [] + Avg = sys.float_info.max + for circleTan in circleList: + del AvgList[:] # svuoto la lista + + angle = qad_utils.getAngleBy2Pts(circleTan.center, circle1.center) + if circleTan.center.distance(circle1.center) < circle1.radius: # cerchio interno + angle = angle + math.pi / 2 + ptInt = qad_utils.getPolarPointByPtAngle(circleTan.center, angle, circleTan.radius) + AvgList.append(ptInt.distance(pt1)) + + angle = qad_utils.getAngleBy2Pts(circleTan.center, circle2.center) + if circleTan.center.distance(circle2.center) < circle2.radius: # cerchio interno + angle = angle + math.pi / 2 + ptInt = qad_utils.getPolarPointByPtAngle(circleTan.center, angle, circleTan.radius) + AvgList.append(ptInt.distance(pt2)) + + angle = qad_utils.getAngleBy2Pts(circleTan.center, circle3.center) + if circleTan.center.distance(circle3.center) < circle3.radius: # cerchio interno + angle = angle + math.pi / 2 + ptInt = qad_utils.getPolarPointByPtAngle(circleTan.center, angle, circleTan.radius) + AvgList.append(ptInt.distance(pt3)) + + currAvg = qad_utils.numericListAvg(AvgList) + if currAvg < Avg: # mediamente più vicino + Avg = currAvg + result.center = circleTan.center + result.radius = circleTan.radius + + return result + + +# =========================================================================== +# circleFrom1IntPtLineCircleTanPts +# =========================================================================== +def circleFrom1IntPtLineCircleTanPts(pt, line1, pt1, circle2, pt2, AllCircles = False): + """ + crea uno o più cerchi (vedi AllCircles) attraverso 1 punto di intersezione, 1 linea e 1 cerchio tangenti: + punto di intersezione + linea di tangenza (QadLine) + punto di selezione linea + cerchio di tangenza (QadLine) + punto di selezione cerchio + il parametro AllCircles se = True fa restituire tutti i cerchi e non sono quello più vicino a pt1 e pt2 + """ + # http://www.batmath.it/matematica/a_apollonio/prc.htm + circleList = [] + + # Sono dati un cerchio circle2, un punto pt ed una retta line1 nell'ipotesi che pt + # non stia nè sulla retta line1 nè sul circolo. + # Si vogliono trovare le circonferenze passanti per il punto e tangenti alla retta e al cerchio dato. + # Il problema si può risolvere facilmente utilizzando un'inversione di centro pt e raggio qualunque. + # Trovate le circonferenze inverse della retta data e del circolo dato, se ne trovano le tangenti comuni. + # Le inverse di queste tangenti comuni sono le circonferenze cercate. + + if line1.getYOnInfinityLine(pt.x()) == pt.y() or \ + qad_utils.getDistance(pt, circle2.center) == circle2.radius: + if AllCircles == True: + return circleList + else: + return None + + c = QadCircle() + c.set(pt, 10) + + circularInvLine = getCircularInversionOfLine(c, line1) + circularInvCircle = getCircularInversionOfCircle(c, circle2) + tangents = QadTangency.twoCircles(circularInvCircle, circularInvLine) + for tangent in tangents: + circleList.append(getCircularInversionOfLine(c, tangent)) + + if AllCircles == True: + return circleList + + if len(circleList) == 0: + return None + + result = QadCircle() + AvgList = [] + Avg = sys.float_info.max + for circleTan in circleList: + del AvgList[:] # svuoto la lista + + ptInt = QadPerpendicularity.fromPointToInfinityLine(circleTan.center, line1) + AvgList.append(qad_utils.getDistance(ptInt, pt1)) + + angle = qad_utils.getAngleBy2Pts(circleTan.center, circle2.center) + if qad_utils.getDistance(circleTan.center, circle2.center) < circle2.radius: # cerchio interno + angle = angle + math.pi / 2 + ptInt = qad_utils.getPolarPointByPtAngle(circleTan.center, angle, circleTan.radius) + AvgList.append(qad_utils.getDistance(ptInt, pt2)) + + currAvg = qad_utils.numericListAvg(AvgList) + if currAvg < Avg: # mediamente più vicino + Avg = currAvg + result.center = circleTan.center + result.radius = circleTan.radius + + return result + + +# =========================================================================== +# circleFrom1IntPtCircleCircleTanPts +# =========================================================================== +def circleFrom1IntPtCircleCircleTanPts(pt, circle1, pt1, circle2, pt2): + """ + Crea dei cerchi attraverso 1 punto di intersezione, 2 cerchi tangenti: + punto di intersezione + cerchio1 di tangenza (oggetto QadCircle) + punto di selezione cerchio1 + cerchio2 di tangenza (oggetto QadCircle) + punto di selezione cerchio2 + """ + # http://www.batmath.it/matematica/a_apollonio/prc.htm + circleList = [] + + # Sono dati un punto pt e due circonferenze circle1 e circle2; + # si devono determinare le circonferenze passanti per pt e tangenti alle due circonferenze. + # Proponiamo una costruzione che utilizza l'inversione, in quanto ci pare la più elegante. + # In realtà si potrebbe anche fare una costruzione utilizzando i centri di omotetia dei due cerchi dati + # ma, nella sostanza, é solo un modo per mascherare l'uso dell'inversione. + # Si considera un circolo di inversione di centro pt e raggio qualunque. + # Si determinano i circoli inversi dei due circoli dati e le loro tangenti comuni. + # Le circonferenze inverse di queste tangenti comuni sono quelle che soddisfano il problema. + + c = QadCircle() + c.set(pt, 10) + + circularInvCircle1 = getCircularInversionOfCircle(c, circle1) + circularInvCircle2 = getCircularInversionOfCircle(c, circle2) + tangents = QadTangency.twoCircles(circularInvCircle1, circularInvCircle2) + for tangent in tangents: + circleList.append(getCircularInversionOfLine(c, tangent)) + + if len(circleList) == 0: + return None + + result = QadCircle() + AvgList = [] + Avg = sys.float_info.max + for circleTan in circleList: + del AvgList[:] # svuoto la lista + + angle = qad_utils.getAngleBy2Pts(circleTan.center, circle1.center) + if qad_utils.getDistance(circleTan.center, circle1.center) < circle1.radius: # cerchio interno + angle = angle + math.pi / 2 + ptInt = qad_utils.getPolarPointByPtAngle(circleTan.center, angle, circleTan.radius) + AvgList.append(qad_utils.getDistance(ptInt, pt1)) + + angle = qad_utils.getAngleBy2Pts(circleTan.center, circle2.center) + if qad_utils.getDistance(circleTan.center, circle2.center) < circle2.radius: # cerchio interno + angle = angle + math.pi / 2 + ptInt = qad_utils.getPolarPointByPtAngle(circleTan.center, angle, circleTan.radius) + AvgList.append(qad_utils.getDistance(ptInt, pt2)) + + currAvg = qad_utils.numericListAvg(AvgList) + if currAvg < Avg: # mediamente più vicino + Avg = currAvg + result.center = circleTan.center + result.radius = circleTan.radius + + return result + + +# ============================================================================ +# circleFromDiamEndsPtTanPt +# ============================================================================ +def circleFromDiamEndsPtTanPt(startPt, geom, pt): + """ + Crea un cerchio attraverso un punto di estremità del diametro e + un oggetto di tangenza per l'altra estremità : + punto iniziale + geometria 1 di tangenza (linea, arco o cerchio) + punto di selezione geometria 1 + """ + objype = geom.whatIs() + + if (objType != "LINE" and objType != "ARC" and objType != "CIRCLE"): return None + + if objType == "ARC": # se è arco lo trasformo in cerchio + obj = QadCircle().set(geom.center, geom.radius) + objType = "CIRCLE" + else: + obj = geom + + if objType == "LINE": + ptPer = QadPerpendicularity.fromPointToInfinityLine(startPt, obj) + return QadCircle().fromDiamEnds(startPt, ptPer) + elif objType == "CIRCLE": + l = QadLine().set(startPt, obj.center) + intPts = QadIntersections.infinityLineWithCircle(l, obj) + # scelgo il punto più vicino al punto pt + ptTan = qad_utils.getNearestPoints(pt, ptIntList)[0] + return QadCircle().fromDiamEnds(startPt, ptTan) + + +# ============================================================================ +# circleFromDiamEnds2TanPts +# ============================================================================ +def circleFromDiamEnds2TanPts(geom1, pt1, geom2, pt2): + """ + Creo un cerchio attraverso due oggetto di tangenza per le estremità del diametro: + geometria1 di tangenza (linea, arco o cerchio) + punto di selezione geometria1 + geometria2 di tangenza (linea, arco o cerchio) + punto di selezione geometria2 + """ + obj1Type = geom1.whatIs() + obj2Type = geom2.whatIs() + + if (obj1Type != "LINE" and obj1Type != "ARC" and obj1Type != "CIRCLE") or \ + (obj2Type != "LINE" and obj2Type != "ARC" and obj2Type != "CIRCLE"): + return None + + if obj1Type == "ARC": # se è arco lo trasformo in cerchio + obj1 = QadCircle().set(geom1.center, geom1.radius) + obj1Type = "CIRCLE" + else: + obj1 = geom1 + + if obj2Type == "ARC": # se è arco lo trasformo in cerchio + obj2 = QadCircle().set(geom2.center, geom2.radius) + obj2Type = "CIRCLE" + else: + obj2 = geom2 + + if obj1Type == "LINE": + if obj2Type == "LINE": + return None # Il diametro non può essere tangente a due linee + elif obj2Type == "CIRCLE": + return circleFromLineCircleTanPts(obj1, obj2, pt2) + elif obj1Type == "CIRCLE": + if obj2Type == "LINE": + return circleFromLineCircleTanPts(obj2, obj1, pt1) + elif obj2Type == "CIRCLE": + return circleFromCircleCircleTanPts(obj1, pt1, obj2, pt2) + + return None + + +# ============================================================================ +# circleFromLineCircleTanPts +# ============================================================================ +def circleFromLineCircleTanPts(line, circle, ptCircle): + """ + Creo un cerchio attraverso una linea, un cerchio di tangenza: + linea di tangenza (QadLine) + cerchio di tangenza (oggetto QadCircle) + punto di selezione cerchio + """ + ptPer = QadPerpendicularity.fromPointToInfinityLine(circle.center, line) + tanPoints = [] + tanPoints.append(qad_utils.getPolarPointBy2Pts(circle.center, ptPer, circle.radius)) + tanPoints.append(qad_utils.getPolarPointBy2Pts(circle.center, ptPer, -circle.radius)) + # scelgo il punto più vicino al punto pt + ptTan = qad_utils.getNearestPoints(ptCircle, tanPoints)[0] + return QadCircle().fromDiamEnds(ptPer, ptTan) + + +# ============================================================================ +# circleFromCircleCircleTanPts +# ============================================================================ +def circleFromCircleCircleTanPts(circle1, pt1, circle2, pt2): + """ + Crea un cerchio attraverso due cerchi di tangenza: + cerchio1 di tangenza (oggetto QadCircle) + punto di selezione cerchio1 + cerchio2 di tangenza (oggetto QadCircle) + punto di selezione cerchio2 + """ + l = QadLine().set(circle1.center, circle2.center) + ptIntList = QadIntersections.infinityLineWithCircle(l, circle1) + # scelgo il punto più vicino al punto pt1 + ptTan1 = qad_utils.getNearestPoints(pt1, ptIntList)[0] + + ptIntList = QadIntersections.infinityLineWithCircle(l, circle2) + # scelgo il punto più vicino al punto pt2 + ptTan2 = qad_utils.getNearestPoints(pt2, ptIntList)[0] + + return QadCircle().fromDiamEnds(ptTan1, ptTan2) + + +# ============================================================================ +# circleFrom2TanPtsRadius +# ============================================================================ +def circleFrom2TanPtsRadius(geom1, pt1, geom2, pt2, radius): + """ + Crea un cerchio attraverso 2 oggetti di tangenza e un raggio: + geometria1 di tangenza (linea, arco o cerchio) + punto di selezione geometria1 + oggetto2 di tangenza (linea, arco o cerchio) + punto di selezione geometria2 + raggio + """ + obj1Type = geom1.whatIs() + obj2Type = geom2.whatIs() + + if (obj1Type != "LINE" and obj1Type != "ARC" and obj1Type != "CIRCLE") or \ + (obj2Type != "LINE" and obj2Type != "ARC" and obj2Type != "CIRCLE"): + return False + + if obj1Type == "ARC": # se è arco lo trasformo in cerchio + obj1 = QadCircle().set(geom1.center, geom1.radius) + obj1Type = "CIRCLE" + else: + obj1 = geom1 + + if obj2Type == "ARC": # se è arco lo trasformo in cerchio + obj2 = QadCircle().set(geom2.center, geom2.radius) + obj2Type = "CIRCLE" + else: + obj2 = geom2 + + if obj1Type == "LINE": + if obj2Type == "LINE": + return circleFromLineLineTanPtsRadius(obj1, pt1, obj2, pt2, radius) + elif obj2Type == "CIRCLE": + return circleFromLineCircleTanPtsRadius(obj1, pt1, obj2, pt2, radius) + elif obj1Type == "CIRCLE": + if obj2Type == "LINE": + return circleFromLineCircleTanPtsRadius(obj2, pt2, obj1, pt1, radius) + elif obj2Type == "CIRCLE": + return circleFromCircleCircleTanPtsRadius(obj1, pt1, obj2, pt2, radius) + + return None + + +# ============================================================================ +# circleFromLineLineTanPtsRadius +# ============================================================================ +def circleFromLineLineTanPtsRadius(line1, pt1, line2, pt2, radius): + """ + Crea un cerchio attraverso due linee di tangenza e un raggio: + linea1 di tangenza (QadLine) + punto di selezione linea1 + linea2 di tangenza (QadLine) + punto di selezione linea2 + raggio + """ + # calcolo il punto medio tra i due punti di selezione + ptMiddle = qad_utils.getMiddlePoint(pt1, pt2) + + # verifico se le rette sono parallele + ptInt = QadIntersections.twoInfinityLines(line1, line2) + if ptInt is None: # le rette sono parallele + ptPer = QadPerpendicularity.fromPointToInfinityLine(ptMiddle, line1) + if qad_utils.doubleNear(radius, qad_utils.getDistance(ptPer, ptMiddle)): + return QadCircle().set(ptMiddle, radius) + else: + return None + + # angolo linea1 + angle = qad_utils.getAngleBy2Pts(line1.getStartPt(), line1.getEndPt()) + # retta parallela da un lato della linea1 distante radius + angle = angle + math.pi / 2 + pt1Par1Line1 = qad_utils.getPolarPointByPtAngle(line1.getStartPt(), angle, radius) + pt2Par1Line1 = qad_utils.getPolarPointByPtAngle(line1.getEndPt(), angle, radius) + # retta parallela dall'altro lato della linea1 distante radius + angle = angle - math.pi + pt1Par2Line1 = qad_utils.getPolarPointByPtAngle(line1.getStartPt(), angle, radius) + pt2Par2Line1 = qad_utils.getPolarPointByPtAngle(line1.getEndPt(), angle, radius) + + # angolo linea2 + angle = qad_utils.getAngleBy2Pts(line2.getStartPt(), line2.getEndPt()) + # retta parallela da un lato della linea2 distante radius + angle = angle + math.pi / 2 + pt1Par1Line2 = qad_utils.getPolarPointByPtAngle(line2.getStartPt(), angle, radius) + pt2Par1Line2 = qad_utils.getPolarPointByPtAngle(line2.getEndPt(), angle, radius) + # retta parallela dall'altro lato della linea2 distante radius + angle = angle - math.pi + pt1Par2Line2 = qad_utils.getPolarPointByPtAngle(line2.getStartPt(), angle, radius) + pt2Par2Line2 = qad_utils.getPolarPointByPtAngle(line2.getEndPt(), angle, radius) + + # calcolo le intersezioni + ptIntList = [] + ptInt = qad_utils.getIntersectionPointOn2InfinityLines(pt1Par1Line1, pt2Par1Line1, \ + pt1Par1Line2, pt2Par1Line2) + ptIntList.append(ptInt) + + ptInt = qad_utils.getIntersectionPointOn2InfinityLines(pt1Par1Line1, pt2Par1Line1, \ + pt1Par2Line2, pt2Par2Line2) + ptIntList.append(ptInt) + + ptInt = qad_utils.getIntersectionPointOn2InfinityLines(pt1Par2Line1, pt2Par2Line1, \ + pt1Par1Line2, pt2Par1Line2) + ptIntList.append(ptInt) + + ptInt = qad_utils.getIntersectionPointOn2InfinityLines(pt1Par2Line1, pt2Par2Line1, \ + pt1Par2Line2, pt2Par2Line2) + ptIntList.append(ptInt) + + # scelgo il punto più vicino al punto medio + center = qad_utils.getNearestPoints(ptMiddle, ptIntList)[0] + return QadCircle().set(center, radius) + + +# ============================================================================ +# circleFromLineCircleTanPtsRadius +# ============================================================================ +def circleFromLineCircleTanPtsRadius(line, ptLine, circle, ptCircle, radius): + """ + Crea un cerchio attraverso una linea, un cerchio di tangenza e un raggio: + linea di tangenza (QadLine) + punto di selezione linea + cerchio di tangenza (oggetto QadCircle) + punto di selezione cerchio + raggio + """ + # calcolo il punto medio tra i due punti di selezione + ptMiddle = qad_utils.getMiddlePoint(ptLine, ptCircle) + + # angolo linea1 + angle = qad_utils.getAngleBy2Pts(line.getStartPt(), line.getEndPt()) + # retta parallela da un lato della linea1 distante radius + angle = angle + math.pi / 2 + pt1Par1Line = qad_utils.getPolarPointByPtAngle(line.getStartPt(), angle, radius) + pt2Par1Line = qad_utils.getPolarPointByPtAngle(line.getEndPt(), angle, radius) + # retta parallela dall'altro lato della linea1 distante radius + angle = angle - math.pi + pt1Par2Line = qad_utils.getPolarPointByPtAngle(line.getStartPt(), angle, radius) + pt2Par2Line = qad_utils.getPolarPointByPtAngle(line.getEndPt(), angle, radius) + + # creo un cerchio con un raggio + grande + circleTan = QadCircle() + circleTan.set(circle.center, circle.radius + radius) + + l = QadLine().set(pt1Par1Line, pt2Par1Line) + ptIntList = QadIntersections.infinityLineWithCircle(l, circleTan) + + l.set(pt1Par2Line, pt2Par2Line) + ptIntList2 = QadIntersections.infinityLineWithCircle(l, circleTan) + + ptIntList.extend(ptIntList2) + + if len(ptIntList) == 0: # nessuna intersezione + return None + + # scelgo il punto più vicino al punto medio + center = qad_utils.getNearestPoints(ptMiddle, ptIntList)[0] + return QadCircle().set(center, radius) + + +# ============================================================================ +# circleFromCircleCircleTanPtsRadius +# ============================================================================ +def circleFromCircleCircleTanPtsRadius(circle1, pt1, circle2, pt2, radius): + """ + Crea un cerchio attraverso due cerchi di tangenza e un raggio: + cerchio1 di tangenza (oggetto QadCircle) + punto di selezione cerchio1 + cerchio2 di tangenza (oggetto QadCircle) + punto di selezione cerchio2 + raggio + """ + # calcolo il punto medio tra i due punti di selezione + ptMiddle = qad_utils.getMiddlePoint(pt1, pt2) + + # creo due cerchi con un raggio + grande + circle1Tan = QadCircle() + circle1Tan.set(circle1.center, circle1.radius + radius) + circle2Tan = QadCircle() + circle2Tan.set(circle2.center, circle2.radius + radius) + ptIntList = QadIntersections.twoCircles(circle1Tan, circle2Tan) + + if len(ptIntList) == 0: # nessuna intersezione + return None + + # scelgo il punto più vicino al punto medio + center = qad_utils.getNearestPoints(ptMiddle, ptIntList)[0] + return QadCircle().set(center, radius) + + +# =============================================================================== +# solveCircleTangentToLineAnd2Circles +# =============================================================================== +def solveCircleTangentToLineAnd2Circles(line, circle1, circle2, s1, s2): + ''' + Trova i due cerchi tangenti a una retta e due cerchi (sarebbero 8 cerchi che si trovano con le + 4 combinazioni di s1, s2 che assumo valore -1 o 1) + e restituisce quello più vicino a pt + ''' + # http://www.batmath.it/matematica/a_apollonio/rcc.htm + + # Il modo più semplice per risolvere questo problema é quello di utilizzare una particolare + # trasformazione geometrica, che alcuni chiamano dilatazione parallela: si immagina che il raggio r + # del più piccolo dei cerchi in questione si riduca a zero (il cerchio é ridotto al suo centro), + # mentre le rette (risp. gli altri cerchi) rimangono parallele (risp. concentrici) con distanze + # dal centro del cerchio che si é ridotto a zero (rispettivamente con raggi dei cerchi) aumentati o + # diminuiti di r. + # Se applichiamo questa trasformazione al nostro caso, riducendo a zero il raggio del cerchio più piccolo + # (o di uno dei due se hanno lo stesso raggio) ci ritroveremo con un punto, un cerchio e una retta: + # trovate le circonferenze passanti per il punto e tangenti alla retta e al cerchio (nel modo già noto) + # potremo applicare la trasformazione inversa della dilatazione parallela precedente per determinare + # le circonferenze richieste. + if circle1.radius <= circle2.radius: + smallerCircle = circle1 + greaterCircle = circle2 + else: + smallerCircle = circle2 + greaterCircle = circle1 + + linePar = [] + angle = qad_utils.getAngleBy2Pts(line[0], line[1]) + linePar.append(qad_utils.getPolarPointByPtAngle(line[0], angle + math.pi / 2, smallerCircle.radius * s1)) + linePar.append(qad_utils.getPolarPointByPtAngle(line[1], angle + math.pi / 2, smallerCircle.radius * s1)) + + circlePar = QadCircle(greaterCircle) + circlePar.radius = circlePar.radius + smallerCircle.radius * s1 + + circleList = circleFrom1IntPtLineCircleTanPts(smallerCircle.center, linePar, None, circlePar, None, True) + + for circleTan in circleList: + ptPerp = qad_utils.getPerpendicularPointOnInfinityLine(line[0], line[1], circleTan.center) + circleTan.radius = qad_utils.getDistance(ptPerp, circleTan.center) + + return circleList + + +# =============================================================================== +# solveApollonius +# =============================================================================== +def solveApollonius(c1, c2, c3, s1, s2, s3): + ''' + >>> solveApollonius((0, 0, 1), (4, 0, 1), (2, 4, 2), 1,1,1) + Circle(x=2.0, y=2.1, r=3.9) + >>> solveApollonius((0, 0, 1), (4, 0, 1), (2, 4, 2), -1,-1,-1) + Circle(x=2.0, y=0.8333333333333333, r=1.1666666666666667) + Trova il cerchio tangente a tre cerchi (sarebbero 8 cerchi che si trovano con le + 8 combinazioni di s1, s2, s3 che assumo valore -1 o 1) + ''' + x1 = c1.center.x() + y1 = c1.center.y() + r1 = c1.radius + x2 = c2.center.x() + y2 = c2.center.y() + r2 = c2.radius + x3 = c3.center.x() + y3 = c3.center.y() + r3 = c3.radius + + v11 = 2*x2 - 2*x1 + v12 = 2*y2 - 2*y1 + v13 = x1*x1 - x2*x2 + y1*y1 - y2*y2 - r1*r1 + r2*r2 + v14 = 2*s2*r2 - 2*s1*r1 + + v21 = 2*x3 - 2*x2 + v22 = 2*y3 - 2*y2 + v23 = x2*x2 - x3*x3 + y2*y2 - y3*y3 - r2*r2 + r3*r3 + v24 = 2*s3*r3 - 2*s2*r2 + + if v11 == 0: + return None + + w12 = v12/v11 + w13 = v13/v11 + w14 = v14/v11 + + if v21 == 0: + return None + + w22 = v22/v21-w12 + w23 = v23/v21-w13 + w24 = v24/v21-w14 + + if w22 == 0: + return None + + P = -w23/w22 + Q = w24/w22 + M = -w12*P-w13 + N = w14 - w12*Q + + a = N*N + Q*Q - 1 + b = 2*M*N - 2*N*x1 + 2*P*Q - 2*Q*y1 + 2*s1*r1 + c = x1*x1 + M*M - 2*M*x1 + P*P + y1*y1 - 2*P*y1 - r1*r1 + + # Find a root of a quadratic equation. This requires the circle centers not to be e.g. colinear + if a == 0: + return None + D = (b * b) - (4 * a * c) + + # se D é così vicino a zero + if qad_utils.doubleNear(D, 0.0): + D = 0 + elif D < 0: # non si può fare la radice quadrata di un numero negativo + return None + + rs = (-b-math.sqrt(D))/(2*a) + + xs = M+N*rs + ys = P+Q*rs + + center = QgsPointXY(xs, ys) + circle = QadCircle().set(center, rs) + return circle + + +# =============================================================================== +# getCircularInversionOfPoint +# =============================================================================== +def getCircularInversionOfPoint(circleRef, pt): + """ + la funzione ritorna l'inversione circolare di un punto + """ + dist = qad_utils.getDistance(circleRef.center, pt) + angle = qad_utils.getAngleBy2Pts(circleRef.center, pt) + circInvDist = circleRef.radius * circleRef.radius / dist + return qad_utils.getPolarPointByPtAngle(circleRef.center, angle, circInvDist) + + +# =============================================================================== +# getCircularInversionOfLine +# =============================================================================== +def getCircularInversionOfLine(circleRef, line): + """ + la funzione ritorna l'inversione circolare di una linea (che é un cerchio) + """ + angleLine = qad_utils.getAngleBy2Pts(line.getStartPt(), line.getEndPt()) + ptNearestLine = QadPerpendicularity.fromPointToInfinityLine(circleRef.center, line) + dist = qad_utils.getDistance(circleRef.center, ptNearestLine) + + pt1 = getCircularInversionOfPoint(circleRef, ptNearestLine) + + pt = qad_utils.getPolarPointByPtAngle(ptNearestLine, angleLine, dist) + pt2 = getCircularInversionOfPoint(circleRef, pt) + + pt = qad_utils.getPolarPointByPtAngle(ptNearestLine, angleLine + math.pi, dist) + pt3 = getCircularInversionOfPoint(circleRef, pt) + + return circleFrom3Pts(pt1, pt2, pt3) + + +# =============================================================================== +# getCircularInversionOfCircle +# =============================================================================== +def getCircularInversionOfCircle(circleRef, circle): + """ + la funzione ritorna l'inversione circolare di un cerchio (che é un cerchio) + """ + + angleLine = qad_utils.getAngleBy2Pts(circle.center, circleRef.center) + ptNearestLine = qad_utils.getPolarPointByPtAngle(circle.center, angleLine, circle.radius) + dist = qad_utils.getDistance(circleRef.center, circle.center) + + pt1 = getCircularInversionOfPoint(circleRef, ptNearestLine) + + pt = qad_utils.getPolarPointByPtAngle(circle.center, angleLine + math.pi / 2, circle.radius) + pt2 = getCircularInversionOfPoint(circleRef, pt) + + pt = qad_utils.getPolarPointByPtAngle(circle.center, angleLine - math.pi / 2, circle.radius) + pt3 = getCircularInversionOfPoint(circleRef, pt) + + return circleFrom3Pts(pt1, pt2, pt3) diff --git a/qad_cmd_aliases.py b/qad_cmd_aliases.py index bb4a2e34..261a2f4c 100644 --- a/qad_cmd_aliases.py +++ b/qad_cmd_aliases.py @@ -1,130 +1,130 @@ -# -*- coding: utf-8 -*- - -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - classe per gestire gli alias dei comandi - - ------------------- - begin : 2013-05-22 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -import os.path -import codecs -from qgis.core import * - - -import qad_utils - - -# Classe che gestisce gli alias dei comandi di Qad -class QadCommandAliasesClass(): - - def __init__(self): - self.__commandAliases = dict() # dizionario degli alias dei comandi - - def getCommandAliasDict(self,): - """ - Restituisce il dizionario degli alias - """ - return self.__commandAliases - - #============================================================================ - # getCommandName - #============================================================================ - def getCommandName(self, alias): - """ - Dato l'alias restituisce il nome del comando - """ - if type(alias) == str or type(alias) == unicode: - return self.__commandAliases.get(alias.upper()) - else: - return self.__commandAliases.get(alias.toUpper()) - - - #============================================================================ - # load - #============================================================================ - def load(self, Path = "", exceptionList = None): - """ - Carica la lista degli alias dei comandi da file - Ritorna True in caso di successo, false in caso di errore - """ - # svuoto il dizionario e lo reimposto con i valori di default - self.__commandAliases.clear() - - if Path == "": - # Se la path non é indicata uso il file "qad.pgp" in lingua locale - userLocaleList = QSettings().value("locale/userLocale").split("_") - language = userLocaleList[0] - region = userLocaleList[1] if len(userLocaleList) > 1 else "" - - fileName = "qad" + "_" + language + "_" + region + ".pgp "# provo a caricare la lingua e la regione selezionate - Path = qad_utils.findFile(fileName) - if Path == "": # se file non trovato - fileName = "qad" + "_" + language + ".pgp " # provo a caricare la lingua - Path = qad_utils.findFile(fileName) - if Path == "": # se file non trovato - return True - else: - if not os.path.exists(Path): - return True - - file = codecs.open(unicode(Path), "r", encoding='utf-8') # apre il file in lettura in modalità unicode utf-8 - - for line in file: - line = qad_utils.strip(line, [" ", "\t", "\r\n"]) # rimuovo gli spazi e i tab prima e dopo - if len(line) == 0: - continue - # se la riga inizia per ; allora é una riga commentata - if line[0] == ";": - continue - - # leggo il nome dell'alias + il nome del comando (es "alias, *comando") - sep = line.find(",") - if sep <= 0: - continue - alias = line[0:sep] - alias = qad_utils.strip(alias, [" ", "\t", "\r\n"]) # rimuovo gli spazi e i tab prima e dopo - if len(alias) == 0: - continue - - command = line[sep+1:] - command = qad_utils.strip(command, [" ", "\t", "\r\n"]) # rimuovo gli spazi e i tab prima e dopo - if len(command) <= 1: - continue - # se il comando non inizia per * allora non é un alias - if command[0] != "*": - continue - command = command[1:] - # il comando non può contenere spazi - sep = command.find(" ") - if sep > 0: - continue - - if exceptionList is None: - self.__commandAliases[alias.upper()] = command.upper() - else: - if alias.upper() not in exceptionList: - self.__commandAliases[alias.upper()] = command.upper() - - file.close() - - return True +# -*- coding: utf-8 -*- + +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + classe per gestire gli alias dei comandi + + ------------------- + begin : 2013-05-22 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.PyQt.QtCore import * +import os.path +import codecs +from qgis.core import * + + +from . import qad_utils + + +# Classe che gestisce gli alias dei comandi di Qad +class QadCommandAliasesClass(): + + def __init__(self): + self.__commandAliases = dict() # dizionario degli alias dei comandi + + def getCommandAliasDict(self,): + """ + Restituisce il dizionario degli alias + """ + return self.__commandAliases + + # ============================================================================ + # getCommandName + # ============================================================================ + def getCommandName(self, alias): + """ + Dato l'alias restituisce il nome del comando + """ + if type(alias) == str or type(alias) == unicode: + return self.__commandAliases.get(alias.upper()) + else: + return self.__commandAliases.get(alias.toUpper()) + + + # ============================================================================ + # load + # ============================================================================ + def load(self, Path = "", exceptionList = None): + """ + Carica la lista degli alias dei comandi da file + Ritorna True in caso di successo, false in caso di errore + """ + # svuoto il dizionario e lo reimposto con i valori di default + self.__commandAliases.clear() + + if Path == "": + # Se la path non é indicata uso il file "qad.pgp" in lingua locale + userLocaleList = QSettings().value("locale/userLocale").split("_") + language = userLocaleList[0] + region = userLocaleList[1] if len(userLocaleList) > 1 else "" + + fileName = "qad" + "_" + language + "_" + region + ".pgp "# provo a caricare la lingua e la regione selezionate + Path = qad_utils.findFile(fileName) + if Path == "": # se file non trovato + fileName = "qad" + "_" + language + ".pgp " # provo a caricare la lingua + Path = qad_utils.findFile(fileName) + if Path == "": # se file non trovato + return True + else: + if not os.path.exists(Path): + return True + + file = codecs.open(unicode(Path), "r", encoding='utf-8') # apre il file in lettura in modalità unicode utf-8 + + for line in file: + line = qad_utils.strip(line, [" ", "\t", "\r\n"]) # rimuovo gli spazi e i tab prima e dopo + if len(line) == 0: + continue + # se la riga inizia per ; allora é una riga commentata + if line[0] == ";": + continue + + # leggo il nome dell'alias + il nome del comando (es "alias, *comando") + sep = line.find(",") + if sep <= 0: + continue + alias = line[0:sep] + alias = qad_utils.strip(alias, [" ", "\t", "\r\n"]) # rimuovo gli spazi e i tab prima e dopo + if len(alias) == 0: + continue + + command = line[sep+1:] + command = qad_utils.strip(command, [" ", "\t", "\r\n"]) # rimuovo gli spazi e i tab prima e dopo + if len(command) <= 1: + continue + # se il comando non inizia per * allora non é un alias + if command[0] != "*": + continue + command = command[1:] + # il comando non può contenere spazi + sep = command.find(" ") + if sep > 0: + continue + + if exceptionList is None: + self.__commandAliases[alias.upper()] = command.upper() + else: + if alias.upper() not in exceptionList: + self.__commandAliases[alias.upper()] = command.upper() + + file.close() + + return True diff --git a/qad_commands.py b/qad_commands.py index f783dbb4..1eaf85b0 100644 --- a/qad_commands.py +++ b/qad_commands.py @@ -1,569 +1,723 @@ -# -*- coding: utf-8 -*- - -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - classe per gestire i comandi - - ------------------- - begin : 2013-05-22 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -from qgis.gui import * - - -from qad_maptool import QadMapTool, QadVirtualSelCommandClass, QadVirtualGripCommandsClass -from qad_msg import QadMsg -from qad_cmd_aliases import * -from qad_variables import QadVariables - -from qad_getpoint import * -from qad_generic_cmd import QadCommandClass -from qad_id_cmd import QadIDCommandClass -from qad_setcurrlayerbygraph_cmd import QadSETCURRLAYERBYGRAPHCommandClass, QadSETCURRUPDATEABLELAYERBYGRAPHCommandClass -from qad_setvar_cmd import QadSETVARCommandClass -from qad_pline_cmd import QadPLINECommandClass -from qad_arc_cmd import QadARCCommandClass -from qad_circle_cmd import QadCIRCLECommandClass -from qad_dsettings_cmd import QadDSETTINGSCommandClass -from qad_line_cmd import QadLINECommandClass -from qad_erase_cmd import QadERASECommandClass -from qad_mpolygon_cmd import QadMPOLYGONCommandClass -from qad_mbuffer_cmd import QadMBUFFERCommandClass -from qad_rotate_cmd import QadROTATECommandClass -from qad_move_cmd import QadMOVECommandClass -from qad_scale_cmd import QadSCALECommandClass -from qad_copy_cmd import QadCOPYCommandClass -from qad_offset_cmd import QadOFFSETCommandClass -from qad_extend_cmd import QadEXTENDCommandClass -from qad_trim_cmd import QadTRIMCommandClass -from qad_rectangle_cmd import QadRECTANGLECommandClass -from qad_mirror_cmd import QadMIRRORCommandClass -from qad_undoredo_cmd import QadUNDOCommandClass, QadREDOCommandClass -from qad_insert_cmd import QadINSERTCommandClass -from qad_text_cmd import QadTEXTCommandClass -from qad_stretch_cmd import QadSTRETCHCommandClass -from qad_break_cmd import QadBREAKCommandClass -from qad_pedit_cmd import QadPEDITCommandClass -from qad_fillet_cmd import QadFILLETCommandClass -from qad_polygon_cmd import QadPOLYGONCommandClass -from qad_dim_cmd import QadDIMLINEARCommandClass, QadDIMALIGNEDCommandClass -from qad_dimstyle_cmd import QadDIMSTYLECommandClass -from qad_lengthen_cmd import QadLENGTHENCommandClass -from qad_help_cmd import QadHELPCommandClass - - -# Classe che gestisce i comandi di Qad -class QadCommandsClass(): - # quando si aggiunge un nuovo comando bisogna - # 1) aggiungerlo nella lista __cmdObjs nella funzione __init__ - # 2) se il comando può essere richiamato da menu o da toolbar vedere la funzione Qad::initGui (qad.py) - # e ricordarsi di inserire l'icona in resources.qrc e di ricompilare le risorse - # 3) aggiungere funzione per l'avvio del comando "runCommand" - - def __init__(self, plugIn): - self.plugIn = plugIn - - self.__cmdObjs = [] # lista interna degli oggetti comandi - self.__cmdObjs.append(QadIDCommandClass(self.plugIn)) # ID - self.__cmdObjs.append(QadSETVARCommandClass(self.plugIn)) # SETVAR - self.__cmdObjs.append(QadPLINECommandClass(self.plugIn)) # PLINE - self.__cmdObjs.append(QadSETCURRLAYERBYGRAPHCommandClass(self.plugIn))# SETCURRLAYERBYGRAPH - self.__cmdObjs.append(QadSETCURRUPDATEABLELAYERBYGRAPHCommandClass(self.plugIn)) # SETCURRUPDATEABLELAYERBYGRAPH - self.__cmdObjs.append(QadARCCommandClass(self.plugIn)) # ARC - self.__cmdObjs.append(QadCIRCLECommandClass(self.plugIn)) # CIRCLE - self.__cmdObjs.append(QadDSETTINGSCommandClass(self.plugIn)) # DSETTINGS - self.__cmdObjs.append(QadLINECommandClass(self.plugIn)) # LINE - self.__cmdObjs.append(QadERASECommandClass(self.plugIn)) # ERASE - self.__cmdObjs.append(QadMPOLYGONCommandClass(self.plugIn)) # MPOLYGON - self.__cmdObjs.append(QadMBUFFERCommandClass(self.plugIn)) # MBUFFER - self.__cmdObjs.append(QadROTATECommandClass(self.plugIn)) # ROTATE - self.__cmdObjs.append(QadMOVECommandClass(self.plugIn)) # MOVE - self.__cmdObjs.append(QadSCALECommandClass(self.plugIn)) # SCALE - self.__cmdObjs.append(QadCOPYCommandClass(self.plugIn)) # COPY - self.__cmdObjs.append(QadOFFSETCommandClass(self.plugIn)) # OFFSET - self.__cmdObjs.append(QadEXTENDCommandClass(self.plugIn)) # EXTEND - self.__cmdObjs.append(QadTRIMCommandClass(self.plugIn)) # TRIM - self.__cmdObjs.append(QadRECTANGLECommandClass(self.plugIn)) # RECTANGLE - self.__cmdObjs.append(QadMIRRORCommandClass(self.plugIn)) # MIRROR - self.__cmdObjs.append(QadUNDOCommandClass(self.plugIn)) # UNDO - self.__cmdObjs.append(QadREDOCommandClass(self.plugIn)) # REDO - self.__cmdObjs.append(QadINSERTCommandClass(self.plugIn)) # INSERT - self.__cmdObjs.append(QadTEXTCommandClass(self.plugIn)) # TEXT - self.__cmdObjs.append(QadSTRETCHCommandClass(self.plugIn)) # STRETCH - self.__cmdObjs.append(QadBREAKCommandClass(self.plugIn)) # BREAK - self.__cmdObjs.append(QadPEDITCommandClass(self.plugIn)) # PEDIT - self.__cmdObjs.append(QadFILLETCommandClass(self.plugIn)) # FILLET - self.__cmdObjs.append(QadPOLYGONCommandClass(self.plugIn)) # POLYGON - self.__cmdObjs.append(QadDIMLINEARCommandClass(self.plugIn)) # DIMLINEAR - self.__cmdObjs.append(QadDIMALIGNEDCommandClass(self.plugIn)) # DIMALIGNED - self.__cmdObjs.append(QadDIMSTYLECommandClass(self.plugIn)) # DIMSTYLE - self.__cmdObjs.append(QadHELPCommandClass(self.plugIn)) # HELP - self.__cmdObjs.append(QadLENGTHENCommandClass(self.plugIn)) # LENGTHEN - - self.actualCommand = None # Comando in corso di esecuzione - - # scarto gli alias che hanno lo stesso nome dei comandi - exceptionList = [] - for cmdObj in self.__cmdObjs: - exceptionList.append(cmdObj.getName()) - exceptionList.append("_" + cmdObj.getEnglishName()) - - # carico alias dei comandi - self.commandAliases = QadCommandAliasesClass() - self.commandAliases.load("", exceptionList) - - self.usedCmdNames = QadUsedCmdNamesClass() - - - def isValidCommand(self, command): - cmd = self.getCommandObj(command) - if cmd: - del cmd - return True - else: - return False - - - def isValidEnvVariable(self, variable): - # verifico se è una variabile di sistema - if QadVariables.get(variable) is not None: - return True - else: - return False - - - def showCommandPrompt(self): - if self.plugIn is not None: - self.plugIn.showInputMsg() # visualizza prompt standard per richiesta comando - - def showMsg(self, msg, displayPromptAfterMsg = False): - if self.plugIn is not None: - self.plugIn.showMsg(msg, displayPromptAfterMsg) - - def showErr(self, err): - if self.plugIn is not None: - self.plugIn.showErr(err) - - - #============================================================================ - # getCommandObj - #============================================================================ - def getCommandObj(self, cmdName, useAlias = True): - if cmdName is None: - return None - if cmdName == "": - return None - upperCommand = cmdName.upper() - if upperCommand[0] == "_": - englishName = True - upperCommand = upperCommand[1:] # salto il primo carattere di "_" - else: - englishName = False - - for cmd in self.__cmdObjs: - if englishName: - if upperCommand == cmd.getEnglishName(): # in inglese - return cmd.instantiateNewCmd() - else: - if upperCommand == cmd.getName(): # in lingua locale - return cmd.instantiateNewCmd() - - if cmdName == "MACRO_RUNNER": - return QadMacroRunnerCommandClass(self.plugIn) - else: - if useAlias: - command = self.commandAliases.getCommandName(cmdName) - return self.getCommandObj(command, False) - else: - return None - - - #============================================================================ - # getCommandNames - #============================================================================ - def getCommandNames(self): - """ Return a list of pairs : [(, )...]""" - cmdNames = [] - # ricavo la lista dei nomi dei comandi - for cmd in self.__cmdObjs: - cmdNames.append([cmd.getName(), cmd.getEnglishName]) # in lingua locale, in inglese - # aggiungo gli alias - for alias in self.commandAliases.getCommandAliasDict().keys(): - cmdNames.append([alias, alias]) - - return cmdNames - - - #============================================================================ - # run - #============================================================================ - def run(self, command, param = None): - # se c'é un comando attivo - if self.actualCommand is not None: - return - - # eccezione per comando virtuale "QadVirtualSelCommandClass" che in realtà non è un comando - # ma è usato per selezionare oggetti quando nessun comando è attivo - if command == "QadVirtualSelCommandClass": - self.actualCommand = QadVirtualSelCommandClass(self.plugIn) - # param è la posizione corrente del mouse - if self.actualCommand.run(False, param) == True: # comando terminato - self.clearCommand() - return - - # eccezione per comando virtuale "QadVirtualGripCommandsClass" che in realtà non è un comando - # ma è usato per modificare gli oggetti selezionati da grip points - if command == "QadVirtualGripCommandsClass": - self.actualCommand = QadVirtualGripCommandsClass(self.plugIn) - # param è una lista in cui: - # il primo elemento è il codice del comando da eseguire - # il secondo elemento è entitySetGripPoints - # il terzo elemento è il punto del grip corrente - self.actualCommand.entitySetGripPoints = param[1] - self.actualCommand.basePt = param[2] - self.actualCommand.initStartCommand(param[0]) - if self.actualCommand.run(False) == True: # comando terminato - self.clearCommand() - return - - self.actualCommand = self.getCommandObj(command) - if self.actualCommand is None: - # verifico se è una variabile di sistema - if QadVariables.get(command) is not None: - self.showMsg("\n") - # lancio comando SETVAR per settare la variabile - args = [QadMsg.translate("Command_list", "SETVAR"), command] - return self.runMacro(args) - - msg = QadMsg.translate("QAD", "\nInvalid command \"{0}\".") - self.showErr(msg.format(command)) - return - - self.usedCmdNames.setUsed(command) - self.plugIn.clearEntityGripPoints() # pulisco i grip points correnti - if self.actualCommand.run() == True: # comando terminato - self.clearCommand() - - - #============================================================================ - # runMacro - #============================================================================ - def runMacro(self, args): - # se non c'é alcun comando attivo - if self.actualCommand is not None: - return - - self.actualCommand = self.getCommandObj("MACRO_RUNNER") - if self.actualCommand is None: - msg = QadMsg.translate("QAD", "\nInvalid command \"{0}\".") - self.showErr(msg.format(command)) - return - - self.plugIn.clearEntityGripPoints() # pulisco i grip points correnti - self.actualCommand.setCmdAndOptionsToRun(args) - - self.showMsg(args[0]) # visualizzo il nome del comando in macro - if self.actualCommand.run() == True: # comando terminato - self.clearCommand() - - - #============================================================================ - # continueCommandFromMapTool - #============================================================================ - def continueCommandFromMapTool(self): - # se non c'é alcun comando attivo - if self.actualCommand is None: - return - msg = None - # se é stato premuto il tasto destro del mouse valuto cosa é stato inserito nella finestra di testo - if self.actualCommand.getPointMapTool().rightButton == True: - msg = self.actualCommand.getCurrMsgFromTxtWindow() - if (msg is not None) and len(msg) > 0: - self.actualCommand.showEvaluateMsg() - else: - if self.actualCommand.run(True) == True: # comando terminato - self.clearCommand() - else: - if self.actualCommand.run(True) == True: # comando terminato - self.clearCommand() - - - #============================================================================ - # continueCommandFromTextWindow - #============================================================================ - def continueCommandFromTextWindow(self, msg): - # se non c'é alcun comando attivo - if self.actualCommand is None: - return - if self.actualCommand.run(False, msg) == True: # comando terminato - self.clearCommand() - - - #============================================================================ - # abortCommand - #============================================================================ - def abortCommand(self): - # se non c'é alcun comando attivo - if self.actualCommand is None: - self.showCommandPrompt() # visualizza prompt standard per richiesta comando - self.plugIn.setStandardMapTool() - else: - self.showMsg(QadMsg.translate("QAD", "*Canceled*")) - self.clearCommand() - # pulisco le entità selezionate e i grip points correnti - self.plugIn.clearCurrentObjsSelection() - - - #============================================================================ - # clearCommand - #============================================================================ - def clearCommand(self): - if self.actualCommand is None: - return - - # eccezione per comando virtuale "QadVirtualGripCommandsClass" che in realtà non è un comando - # ma è usato per modificare gli oggetti selezionati da grip points - if self.actualCommand.getName() == "QadVirtualGripCommandsClass": - # ridisegno i grip point nelle nuove posizioni resettando quelli selezionati - self.plugIn.tool.clearEntityGripPoints() - self.plugIn.tool.refreshEntityGripPoints() - else: - # eccezione per comando virtuale "QadVirtualSelCommandClass" che in realtà non è un comando - # ma è usato per selezionare oggetti quando nessun comando è attivo - if self.actualCommand.getName() != "QadVirtualSelCommandClass": - qad_utils.deselectAll(self.plugIn.canvas.layers()) - - del self.actualCommand - self.actualCommand = None - self.plugIn.setStandardMapTool() - self.showCommandPrompt() # visualizza prompt standard per richiesta comando - - - #============================================================================ - # forceCommandMapToolSnapTypeOnce - #============================================================================ - def forceCommandMapToolSnapTypeOnce(self, snapType, snapParams = None): - # se non c'é alcun comando attivo - if self.actualCommand is None: - return - # se non c'é un maptool del comando attuale - if self.actualCommand.getPointMapTool() is None: - return - # se il maptool del comando attuale se non é attivo - if self.plugIn.canvas.mapTool() != self.actualCommand.getPointMapTool(): - self.actualCommand.setMapTool(self.actualCommand.getPointMapTool()) - self.actualCommand.getPointMapTool().forceSnapTypeOnce(snapType, snapParams) - - - #============================================================================ - # getCurrenPointFromCommandMapTool - #============================================================================ - def getCurrenPointFromCommandMapTool(self): - # se non c'é alcun comando attivo - if self.actualCommand is None: - return None - # se non c'é un maptool del comando attuale - if self.actualCommand.getPointMapTool() is None: - return None - # se il maptool del comando attuale se non é attivo - if self.plugIn.canvas.mapTool() != self.actualCommand.getPointMapTool(): - self.actualCommand.setMapTool(self.actualCommand.getPointMapTool()) - return self.actualCommand.getPointMapTool().tmpPoint - - - #============================================================================ - # refreshCommandMapToolSnapType - #============================================================================ - def refreshCommandMapToolSnapType(self): - # se non c'é alcun comando attivo - if self.actualCommand is None: - return - # se non c'é un maptool attivo del comando attuale - if self.actualCommand.getPointMapTool() is None: - return - self.actualCommand.getPointMapTool().refreshSnapType() - - - #============================================================================ - # refreshCommandMapToolOrthoMode - #============================================================================ - def refreshCommandMapToolOrthoMode(self): - # se non c'é alcun comando attivo - if self.actualCommand is None: - return - # se non c'é un maptool attivo del comando attuale - if self.actualCommand.getPointMapTool() is None: - return - self.actualCommand.getPointMapTool().refreshOrthoMode() - - - #============================================================================ - # refreshCommandMapToolAutoSnap - #============================================================================ - def refreshCommandMapToolAutoSnap(self): - # se non c'é alcun comando attivo - if self.actualCommand is None: - return - # se non c'é un maptool attivo del comando attuale - if self.actualCommand.getPointMapTool() is None: - return - self.actualCommand.getPointMapTool().refreshAutoSnap() - - - - #============================================================================ - # getMoreUsedCmd - #============================================================================ - def getMoreUsedCmd(self, filter): - upperFilter = filter.upper() - cmdName, qty = self.usedCmdNames.getMoreUsed(upperFilter) - if cmdName == "": # nessun comando - if upperFilter[0] == "_": - englishName = True - upperFilter = upperFilter[1:] # salto il primo carattere di "_" - else: - englishName = False - - for cmd in self.__cmdObjs: - if englishName: - if cmd.getEnglishName().startswith(upperFilter): # in inglese - return cmd.getEnglishName(), 0 - else: - if cmd.getName().startswith(upperFilter): # in lingua locale - return cmd.getName(), 0 - return cmdName, 0 - - -#=============================================================================== -# QadMacroRunnerCommandClass -#=============================================================================== -class QadMacroRunnerCommandClass(QadCommandClass): - # Classe che gestisce l'esecuzione di altri comandi - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadMacroRunnerCommandClass(self.plugIn) - - def getName(self): - if self.command is None: - return "MACRO_RUNNER" - else: - return self.command.getName() - - def getEnglishName(self): - return "MACRO_RUNNER" - - def connectQAction(self, action): - QObject.connect(action, SIGNAL("triggered()"), self.plugIn.runREDOCommand) - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - self.command = None - self.args = [] # lista degli argomenti - self.argsIndex = -1 - - def __del__(self): - QadCommandClass.__del__(self) - if self.command is not None: - del self.command - - def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): - if self.command is not None: - return self.command.getPointMapTool(drawMode) - else: - return QadCommandClass.getPointMapTool(self, drawMode) - - def setCmdAndOptionsToRun(self, CmdAndArglist): - # primo eleemto della lista = nome comando - # gli altri elementi sono gli argomenti del comando None = input dell'utente - cmdName = CmdAndArglist[0] - self.args = CmdAndArglist[1:] # copio la lista saltando il primo elemento - - self.command = self.plugIn.getCommandObj(cmdName) - - if self.command is None: - msg = QadMsg.translate("QAD", "\nInvalid command \"{0}\".") - self.showErr(msg.format(command)) - return False - self.plugIn.updateHistoryfromTxtWindow(cmdName) - return True - - def run(self, msgMapTool = False, msg = None): - - if self.command.run(msgMapTool, msg) == True: - return True - - # se l'input precedente era valido - if self.command.isValidPreviousInput == True: - # al comando passo la prossima opzione - self.argsIndex = self.argsIndex + 1 - if self.argsIndex < len(self.args): - arg = self.args[self.argsIndex] - if arg is not None: - self.showEvaluateMsg(arg) - - return False - - -#=============================================================================== -# QadUsedCmdNamesClass usata per contare quante volte sono stati usati i comandi -#=============================================================================== - - -class QadUsedCmdNamesClass(): - def __init__(self): - self.__nUsedCmdNames = [] # lista interna di item composti da (nome comando o alias, n. di volte che è stato usato) - - def __del__(self): - del self.__nUsedCmdNames[:] - - - def setUsed(self, cmdName): - uName = cmdName.upper() - for _cmdName in self.__nUsedCmdNames: - if _cmdName[0] == uName: - _cmdName[1] = _cmdName[1] + 1 - return _cmdName[1] - - self.__nUsedCmdNames.append([uName, 1]) - return 1 - - - def getUsed(self, cmdName): - uName = cmdName.upper() - for _cmdName in self.__nUsedCmdNames: - if _cmdName[0] == uName: - return _cmdName[1] - - return 0 - - def getMoreUsed(self, filter): - moreUsedCmd = "" - nUsedCmd = 0 - for _cmdName in self.__nUsedCmdNames: - if _cmdName[0].startswith(filter): - if _cmdName[1] > nUsedCmd: - moreUsedCmd = _cmdName[0] - nUsedCmd = _cmdName[1] - - return moreUsedCmd, nUsedCmd \ No newline at end of file +# -*- coding: utf-8 -*- + +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + classe per gestire i comandi + + ------------------- + begin : 2013-05-22 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.PyQt.QtCore import QObject +from qgis.PyQt.QtWidgets import QMessageBox + +import sys, traceback +from datetime import date + +from .qad_maptool import QadMapTool, QadVirtualSelCommandClass, QadVirtualGripCommandsClass +from .qad_msg import QadMsg +from .qad_cmd_aliases import * +from .qad_variables import QadVariables + +from .qad_getpoint import * +from .qad_utils import decriptPlainText, getQADPath, getMacAddress +from .cmd.qad_generic_cmd import QadCommandClass +from .cmd.qad_id_cmd import QadIDCommandClass +from .cmd.qad_setcurrlayerbygraph_cmd import QadSETCURRLAYERBYGRAPHCommandClass, QadSETCURRUPDATEABLELAYERBYGRAPHCommandClass +from .cmd.qad_setvar_cmd import QadSETVARCommandClass +from .cmd.qad_pline_cmd import QadPLINECommandClass +from .cmd.qad_arc_cmd import QadARCCommandClass +from .cmd.qad_circle_cmd import QadCIRCLECommandClass +from .cmd.qad_dsettings_cmd import QadDSETTINGSCommandClass +from .cmd.qad_line_cmd import QadLINECommandClass +from .cmd.qad_erase_cmd import QadERASECommandClass +from .cmd.qad_mpolygon_cmd import QadMPOLYGONCommandClass +from .cmd.qad_mbuffer_cmd import QadMBUFFERCommandClass +from .cmd.qad_rotate_cmd import QadROTATECommandClass +from .cmd.qad_move_cmd import QadMOVECommandClass +from .cmd.qad_scale_cmd import QadSCALECommandClass +from .cmd.qad_copy_cmd import QadCOPYCommandClass +from .cmd.qad_offset_cmd import QadOFFSETCommandClass +from .cmd.qad_extend_cmd import QadEXTENDCommandClass +from .cmd.qad_trim_cmd import QadTRIMCommandClass +from .cmd.qad_rectangle_cmd import QadRECTANGLECommandClass +from .cmd.qad_mirror_cmd import QadMIRRORCommandClass +from .cmd.qad_undoredo_cmd import QadUNDOCommandClass, QadREDOCommandClass +from .cmd.qad_insert_cmd import QadINSERTCommandClass +from .cmd.qad_text_cmd import QadTEXTCommandClass +from .cmd.qad_stretch_cmd import QadSTRETCHCommandClass +from .cmd.qad_break_cmd import QadBREAKCommandClass +from .cmd.qad_pedit_cmd import QadPEDITCommandClass +from .cmd.qad_fillet_cmd import QadFILLETCommandClass +from .cmd.qad_polygon_cmd import QadPOLYGONCommandClass +from .cmd.qad_dim_cmd import QadDIMLINEARCommandClass, QadDIMALIGNEDCommandClass, QadDIMARCCommandClass, QadDIMRADIUSCommandClass +from .cmd.qad_dimstyle_cmd import QadDIMSTYLECommandClass +from .cmd.qad_lengthen_cmd import QadLENGTHENCommandClass +from .cmd.qad_help_cmd import QadHELPCommandClass, QadSUPPORTERSCommandClass +from .cmd.qad_options_cmd import QadOPTIONSCommandClass +from .cmd.qad_mapmpedit_cmd import QadMAPMPEDITCommandClass +from .cmd.qad_joindisjoin_cmd import QadJOINCommandClass, QadDISJOINCommandClass +from .cmd.qad_array_cmd import QadARRAYCommandClass, QadARRAYRECTCommandClass, QadARRAYPATHCommandClass, QadARRAYPOLARCommandClass +from .cmd.qad_divide_cmd import QadDIVIDECommandClass +from .cmd.qad_measure_cmd import QadMEASURECommandClass +from .cmd.qad_ellipse_cmd import QadELLIPSECommandClass + + +# Classe che gestisce i comandi di Qad +class QadCommandsClass(): + # quando si aggiunge un nuovo comando bisogna + # 1) aggiungerlo nella lista __cmdObjs nella funzione __init__ + # 2) se il comando può essere richiamato da menu o da toolbar vedere la funzione Qad::initGui (qad.py) + # e ricordarsi di inserire l'icona in resources.qrc e di ricompilare le risorse + # 3) aggiungere funzione per l'avvio del comando "runCommand" + + def __init__(self, plugIn): + self.plugIn = plugIn + + self.__cmdObjs = [] # lista interna degli oggetti comandi + self.__cmdObjs.append(QadIDCommandClass(self.plugIn)) # ID + self.__cmdObjs.append(QadSETVARCommandClass(self.plugIn)) # SETVAR + self.__cmdObjs.append(QadPLINECommandClass(self.plugIn)) # PLINE + self.__cmdObjs.append(QadSETCURRLAYERBYGRAPHCommandClass(self.plugIn))# SETCURRLAYERBYGRAPH + self.__cmdObjs.append(QadSETCURRUPDATEABLELAYERBYGRAPHCommandClass(self.plugIn)) # SETCURRUPDATEABLELAYERBYGRAPH + self.__cmdObjs.append(QadARCCommandClass(self.plugIn)) # ARC + self.__cmdObjs.append(QadCIRCLECommandClass(self.plugIn)) # CIRCLE + self.__cmdObjs.append(QadDSETTINGSCommandClass(self.plugIn)) # DSETTINGS + self.__cmdObjs.append(QadLINECommandClass(self.plugIn)) # LINE + self.__cmdObjs.append(QadERASECommandClass(self.plugIn)) # ERASE + self.__cmdObjs.append(QadMPOLYGONCommandClass(self.plugIn)) # MPOLYGON + self.__cmdObjs.append(QadMBUFFERCommandClass(self.plugIn)) # MBUFFER + self.__cmdObjs.append(QadROTATECommandClass(self.plugIn)) # ROTATE + self.__cmdObjs.append(QadMOVECommandClass(self.plugIn)) # MOVE + self.__cmdObjs.append(QadSCALECommandClass(self.plugIn)) # SCALE + self.__cmdObjs.append(QadCOPYCommandClass(self.plugIn)) # COPY + self.__cmdObjs.append(QadOFFSETCommandClass(self.plugIn)) # OFFSET + self.__cmdObjs.append(QadEXTENDCommandClass(self.plugIn)) # EXTEND + self.__cmdObjs.append(QadTRIMCommandClass(self.plugIn)) # TRIM + self.__cmdObjs.append(QadRECTANGLECommandClass(self.plugIn)) # RECTANGLE + self.__cmdObjs.append(QadMIRRORCommandClass(self.plugIn)) # MIRROR + self.__cmdObjs.append(QadUNDOCommandClass(self.plugIn)) # UNDO + self.__cmdObjs.append(QadREDOCommandClass(self.plugIn)) # REDO + self.__cmdObjs.append(QadINSERTCommandClass(self.plugIn)) # INSERT + self.__cmdObjs.append(QadTEXTCommandClass(self.plugIn)) # TEXT + self.__cmdObjs.append(QadSTRETCHCommandClass(self.plugIn)) # STRETCH + self.__cmdObjs.append(QadBREAKCommandClass(self.plugIn)) # BREAK + self.__cmdObjs.append(QadPEDITCommandClass(self.plugIn)) # PEDIT + self.__cmdObjs.append(QadFILLETCommandClass(self.plugIn)) # FILLET + self.__cmdObjs.append(QadPOLYGONCommandClass(self.plugIn)) # POLYGON + self.__cmdObjs.append(QadDIMLINEARCommandClass(self.plugIn)) # DIMLINEAR + self.__cmdObjs.append(QadDIMALIGNEDCommandClass(self.plugIn)) # DIMALIGNED + self.__cmdObjs.append(QadDIMARCCommandClass(self.plugIn)) # DIMARC + self.__cmdObjs.append(QadDIMRADIUSCommandClass(self.plugIn)) # DIMRADIUS + self.__cmdObjs.append(QadDIMSTYLECommandClass(self.plugIn)) # DIMSTYLE + self.__cmdObjs.append(QadHELPCommandClass(self.plugIn)) # HELP + self.__cmdObjs.append(QadLENGTHENCommandClass(self.plugIn)) # LENGTHEN + self.__cmdObjs.append(QadOPTIONSCommandClass(self.plugIn)) # OPTIONS + self.__cmdObjs.append(QadMAPMPEDITCommandClass(self.plugIn)) # MAPMPEDIT + self.__cmdObjs.append(QadJOINCommandClass(self.plugIn)) # JOIN + self.__cmdObjs.append(QadDISJOINCommandClass(self.plugIn)) # DISJOIN + self.__cmdObjs.append(QadARRAYCommandClass(self.plugIn)) # ARRAY + self.__cmdObjs.append(QadARRAYRECTCommandClass(self.plugIn)) # ARRAYRECT + self.__cmdObjs.append(QadARRAYPATHCommandClass(self.plugIn)) # ARRAYPATH + self.__cmdObjs.append(QadARRAYPOLARCommandClass(self.plugIn)) # ARRAYPOLAR + self.__cmdObjs.append(QadDIVIDECommandClass(self.plugIn)) # DIVIDE + self.__cmdObjs.append(QadMEASURECommandClass(self.plugIn)) # MEASURE + self.__cmdObjs.append(QadELLIPSECommandClass(self.plugIn)) # ELLIPSE + self.__cmdObjs.append(QadSUPPORTERSCommandClass(self.plugIn)) # SUPPORTERS + + self.actualCommand = None # Comando in corso di esecuzione + + # scarto gli alias che hanno lo stesso nome dei comandi + exceptionList = [] + for cmdObj in self.__cmdObjs: + exceptionList.append(cmdObj.getName()) + exceptionList.append("_" + cmdObj.getEnglishName()) + + # carico alias dei comandi + self.commandAliases = QadCommandAliasesClass() + self.commandAliases.load("", exceptionList) + + self.usedCmdNames = QadUsedCmdNamesClass() + + + def isValidCommand(self, command): + cmd = self.getCommandObj(command) + if cmd: + del cmd + return True + else: + return False + + + def isValidEnvVariable(self, variable): + # verifico se è una variabile di sistema + if QadVariables.get(variable) is not None: + return True + else: + return False + + + def showCommandPrompt(self): + if self.plugIn is not None: + self.plugIn.showInputMsg() # visualizza prompt standard per richiesta comando + + def showMsg(self, msg, displayPromptAfterMsg = False): + if self.plugIn is not None: + self.plugIn.showMsg(msg, displayPromptAfterMsg) + + def showErr(self, err): + if self.plugIn is not None: + self.plugIn.showErr(err) + + + # ============================================================================ + # getCommandObj + # ============================================================================ + def getCommandObj(self, cmdName, useAlias = True): + if cmdName is None: + return None + if cmdName == "": + return None + upperCommand = cmdName.upper() + if upperCommand[0] == "_": + englishName = True + upperCommand = upperCommand[1:] # salto il primo carattere di "_" + else: + englishName = False + + for cmd in self.__cmdObjs: + if englishName: + if upperCommand == cmd.getEnglishName(): # in inglese + return cmd.instantiateNewCmd() + else: + if upperCommand == cmd.getName(): # in lingua locale + return cmd.instantiateNewCmd() + + if cmdName == "MACRO_RUNNER": + return QadMacroRunnerCommandClass(self.plugIn) + else: + if useAlias: + command = self.commandAliases.getCommandName(cmdName) + return self.getCommandObj(command, False) + else: + return None + + + # ============================================================================ + # getCommandNames + # ============================================================================ + def getCommandNames(self): + """ Return a list of pairs : [(, )...]""" + cmdNames = [] + # ricavo la lista dei nomi dei comandi + for cmd in self.__cmdObjs: + cmdNames.append([cmd.getName(), cmd.getEnglishName]) # in lingua locale, in inglese + # aggiungo gli alias + for alias in self.commandAliases.getCommandAliasDict().keys(): + cmdNames.append([alias, alias]) + + return cmdNames + + + # ============================================================================ + # run + # ============================================================================ + def run(self, command, param = None): + try: + # se c'é un comando attivo + if self.actualCommand is not None: + return + + if command != QadMsg.translate("Command_list", "SUPPORTERS"): + if incrementDailyCmdCounter() > self.plugIn.maxDailyCmdCounter: + if QMessageBox.question(None, "QAD", QadMsg.translate("QAD", "QAD lets you run 200 commands per day free of popups. Donations help us to fund software development, documentation, translation and bug-fixing efforts. Do you want to donate ?"), \ + QMessageBox.Yes, QMessageBox.No) == QMessageBox.Yes: + command = "_SUPPORTERS"; +# if QMessageBox.critical(None, "QAD", QadMsg.translate("QAD", "You have run out of daily commands available for this version of QAD, your reasonable donation will allow us to adapt the product to your needs. Do you want to donate ?"), \ +# QMessageBox.Yes, QMessageBox.No) == QMessageBox.Yes: +# command = "_SUPPORTERS"; +# else: +# return + + # eccezione per comando virtuale "QadVirtualSelCommandClass" che in realtà non è un comando + # ma è usato per selezionare oggetti quando nessun comando è attivo + if command == "QadVirtualSelCommandClass": + self.actualCommand = QadVirtualSelCommandClass(self.plugIn) + # param è la posizione corrente del mouse + if self.actualCommand.run(False, param) == True: # comando terminato + self.clearCommand() + return + + # eccezione per comando virtuale "QadVirtualGripCommandsClass" che in realtà non è un comando + # ma è usato per modificare gli oggetti selezionati da grip points + if command == "QadVirtualGripCommandsClass": + self.actualCommand = QadVirtualGripCommandsClass(self.plugIn) + # param è una lista in cui: + # il primo elemento è il codice del comando da eseguire + # il secondo elemento è entitySetGripPoints + # il terzo elemento è il punto del grip corrente + self.actualCommand.entitySetGripPoints = param[1] + self.actualCommand.basePt = param[2] + self.actualCommand.initStartCommand(param[0]) + if self.actualCommand.run(False) == True: # comando terminato + self.clearCommand() + return + + self.actualCommand = self.getCommandObj(command) + if self.actualCommand is None: + # verifico se è una variabile di sistema + if QadVariables.get(command) is not None: + self.showMsg("\n") + # lancio comando SETVAR per settare la variabile + args = [QadMsg.translate("Command_list", "SETVAR"), command] + return self.runMacro(args) + + msg = QadMsg.translate("QAD", "\nInvalid command \"{0}\".") + self.showErr(msg.format(command)) + return + + self.usedCmdNames.setUsed(command) + self.plugIn.clearEntityGripPoints() # pulisco i grip points correnti + if self.actualCommand.run() == True: # comando terminato + self.clearCommand() + + except Exception as e: + self.abortCommand() + displayError(e) + + + # ============================================================================ + # runMacro + # ============================================================================ + def runMacro(self, args): + try: + # se non c'é alcun comando attivo + if self.actualCommand is not None: + return + + if args[0] != QadMsg.translate("Command_list", "SUPPORTERS"): + if incrementDailyCmdCounter() > self.plugIn.maxDailyCmdCounter: + if QMessageBox.question(None, "QAD", QadMsg.translate("QAD", "QAD lets you run 200 commands per day free of popups. Donations help us to fund software development, documentation, translation and bug-fixing efforts. Do you want to donate ?"), \ + QMessageBox.Yes, QMessageBox.No) == QMessageBox.Yes: + args[0] = "_SUPPORTERS"; +# if QMessageBox.critical(None, "QAD", QadMsg.translate("QAD", "You have run out of daily commands available for this version of QAD, your reasonable donation will allow us to adapt the product to your needs. Do you want to donate ?"), \ +# QMessageBox.Yes, QMessageBox.No) == QMessageBox.Yes: +# args[0] = "_SUPPORTERS"; +# else: +# return + + self.actualCommand = self.getCommandObj("MACRO_RUNNER") + if self.actualCommand is None: + msg = QadMsg.translate("QAD", "\nInvalid command \"{0}\".") + self.showErr(msg.format(command)) + return + + self.plugIn.clearEntityGripPoints() # pulisco i grip points correnti + self.actualCommand.setCmdAndOptionsToRun(args) + + self.showMsg(args[0]) # visualizzo il nome del comando in macro + if self.actualCommand.run() == True: # comando terminato + self.clearCommand() + + except Exception as e: + self.abortCommand() + displayError(e) + + + # ============================================================================ + # continueCommandFromMapTool + # ============================================================================ + def continueCommandFromMapTool(self): + try: + # se non c'é alcun comando attivo + if self.actualCommand is None: + return + msg = None + # se é stato premuto il tasto destro del mouse valuto cosa é stato inserito nella finestra di testo + if self.actualCommand.getPointMapTool().rightButton == True: + msg = self.actualCommand.getCurrMsgFromTxtWindow() + if (msg is not None) and len(msg) > 0: + self.actualCommand.showEvaluateMsg() + else: + if self.actualCommand.run(True) == True: # comando terminato + self.clearCommand() + else: + if self.actualCommand.run(True) == True: # comando terminato + self.clearCommand() + + except Exception as e: + self.abortCommand() + displayError(e) + + + # ============================================================================ + # continueCommandFromTextWindow + # ============================================================================ + def continueCommandFromTextWindow(self, msg): + try: + # se non c'é alcun comando attivo + if self.actualCommand is None: + return + if self.actualCommand.run(False, msg) == True: # comando terminato + self.clearCommand() + + except Exception as e: + self.abortCommand() + displayError(e) + + + # ============================================================================ + # abortCommand + # ============================================================================ + def abortCommand(self): + # se non c'é alcun comando attivo + if self.actualCommand is None: + self.showCommandPrompt() # visualizza prompt standard per richiesta comando + self.plugIn.setStandardMapTool() + self.plugIn.getCurrentMapTool() + else: + self.showMsg(QadMsg.translate("QAD", "*Canceled*")) + self.clearCommand() + # pulisco le entità selezionate e i grip points correnti + self.plugIn.clearCurrentObjsSelection() + + + # ============================================================================ + # clearCommand + # ============================================================================ + def clearCommand(self): + if self.actualCommand is None: + return + + # eccezione per comando virtuale "QadVirtualGripCommandsClass" che in realtà non è un comando + # ma è usato per modificare gli oggetti selezionati da grip points + if self.actualCommand.getName() == "QadVirtualGripCommandsClass": + # ridisegno i grip point nelle nuove posizioni resettando quelli selezionati + self.plugIn.tool.clearEntityGripPoints() + self.plugIn.tool.refreshEntityGripPoints() + else: + # eccezione per comando virtuale "QadVirtualSelCommandClass" che in realtà non è un comando + # ma è usato per selezionare oggetti quando nessun comando è attivo + if self.actualCommand.getName() != "QadVirtualSelCommandClass": + qad_utils.deselectAll(self.plugIn.canvas.layers()) + + del self.actualCommand + self.actualCommand = None + self.plugIn.setStandardMapTool() + self.plugIn.tool.getDynamicInput().show(False) + self.showCommandPrompt() # visualizza prompt standard per richiesta comando + + + # ============================================================================ + # getActualCommandPointMapTool + # ============================================================================ + def getActualCommandPointMapTool(self): + # se non c'é alcun comando attivo + if self.actualCommand is None: + return None + # se non c'é un maptool del comando attuale + if self.actualCommand.getPointMapTool() is None: + return None + # se il maptool del comando attuale se non é attivo + if self.plugIn.canvas.mapTool() != self.actualCommand.getPointMapTool(): + self.actualCommand.setMapTool(self.actualCommand.getPointMapTool()) + return self.actualCommand.getPointMapTool() + + + # ============================================================================ + # forceCommandMapToolSnapTypeOnce + # ============================================================================ + def forceCommandMapToolSnapTypeOnce(self, snapType, snapParams = None): + pointMapTool = self.getActualCommandPointMapTool() + if pointMapTool is None: + return + pointMapTool.forceSnapTypeOnce(snapType, snapParams) + + + # ============================================================================ + # forceCommandMapToolM2P + # ============================================================================ + def forceCommandMapToolM2P(self): + pointMapTool = self.getActualCommandPointMapTool() + if pointMapTool is None: + return + pointMapTool.forceM2P() + + + # ============================================================================ + # getCurrenPointFromCommandMapTool + # ============================================================================ + def getCurrenPointFromCommandMapTool(self): + pointMapTool = self.getActualCommandPointMapTool() + if pointMapTool is None: + return None + return pointMapTool.tmpPoint + + + # ============================================================================ + # refreshCommandMapToolSnapType + # ============================================================================ + def refreshCommandMapToolSnapType(self): + pointMapTool = self.getActualCommandPointMapTool() + if pointMapTool is None: + return + pointMapTool.refreshSnapType() + + + # ============================================================================ + # refreshCommandMapToolOrthoMode + # ============================================================================ + def refreshCommandMapToolOrthoMode(self): + pointMapTool = self.getActualCommandPointMapTool() + if pointMapTool is None: + return + pointMapTool.refreshOrthoMode() + + + # ============================================================================ + # refreshCommandMapToolAutoSnap + # ============================================================================ + def refreshCommandMapToolAutoSnap(self): + pointMapTool = self.getActualCommandPointMapTool() + if pointMapTool is None: + return + pointMapTool.refreshAutoSnap() + + + # ============================================================================ + # refreshCommandMapToolDynamicInput + # ============================================================================ + def refreshCommandMapToolDynamicInput(self): + self.plugIn.tool.getDynamicInput().refreshOnEnvVariables() + + pointMapTool = self.getActualCommandPointMapTool() + if pointMapTool is not None: + pointMapTool.getDynamicInput().refreshOnEnvVariables() + if pointMapTool.getDynamicInput().isActive() == False: + pointMapTool.getDynamicInput().show(False) + else: + pointMapTool.getDynamicInput().show(True) + else: + if self.plugIn.tool.getDynamicInput().isActive() == False: + self.plugIn.tool.getDynamicInput().show(False) + else: + if self.plugIn.tool.getDynamicInput().resStr != "": # solo se forniva già un risultato + self.plugIn.tool.getDynamicInput().show(True) + + + # ============================================================================ + # getMoreUsedCmd + # ============================================================================ + def getMoreUsedCmd(self, filter): + upperFilter = filter.upper() + cmdName, qty = self.usedCmdNames.getMoreUsed(upperFilter) + if cmdName == "": # nessun comando + if upperFilter[0] == "_": + englishName = True + upperFilter = upperFilter[1:] # salto il primo carattere di "_" + else: + englishName = False + + for cmd in self.__cmdObjs: + if englishName: + if cmd.getEnglishName().startswith(upperFilter): # in inglese + return cmd.getEnglishName(), 0 + else: + if cmd.getName().startswith(upperFilter): # in lingua locale + return cmd.getName(), 0 + return cmdName, 0 + + +# =============================================================================== +# QadMacroRunnerCommandClass +# =============================================================================== +class QadMacroRunnerCommandClass(QadCommandClass): + # Classe che gestisce l'esecuzione di altri comandi + + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadMacroRunnerCommandClass(self.plugIn) + + def getName(self): + if self.command is None: + return "MACRO_RUNNER" + else: + return self.command.getName() + + def getEnglishName(self): + return "MACRO_RUNNER" + + def connectQAction(self, action): + action.triggered.connect(self.plugIn.runREDOCommand) + + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + self.command = None + self.args = [] # lista degli argomenti + self.argsIndex = -1 + + def __del__(self): + QadCommandClass.__del__(self) + if self.command is not None: + del self.command + + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if self.command is not None: + return self.command.getPointMapTool(drawMode) + else: + return QadCommandClass.getPointMapTool(self, drawMode) + + + def getCurrentContextualMenu(self): + if self.command is None: + return None + else: + return self.command.getCurrentContextualMenu() + + + def setCmdAndOptionsToRun(self, CmdAndArglist): + # primo elemento della lista = nome comando + # gli altri elementi sono gli argomenti del comando None = input dell'utente + cmdName = CmdAndArglist[0] + self.args = CmdAndArglist[1:] # copio la lista saltando il primo elemento + + self.command = self.plugIn.getCommandObj(cmdName) + + if self.command is None: + msg = QadMsg.translate("QAD", "\nInvalid command \"{0}\".") + self.showErr(msg.format(command)) + return False + self.plugIn.updateCmdsHistory(cmdName) + return True + + + def run(self, msgMapTool = False, msg = None): + + if self.command.run(msgMapTool, msg) == True: + return True + + # se l'input precedente era valido + if self.command.isValidPreviousInput == True: + # al comando passo la prossima opzione + self.argsIndex = self.argsIndex + 1 + if self.argsIndex < len(self.args): + arg = self.args[self.argsIndex] + if arg is not None: + self.showEvaluateMsg(arg) + + return False + + +# =============================================================================== +# QadUsedCmdNamesClass usata per contare quante volte sono stati usati i comandi +# =============================================================================== + + +class QadUsedCmdNamesClass(): + def __init__(self): + self.__nUsedCmdNames = [] # lista interna di item composti da (nome comando o alias, n. di volte che è stato usato) + + def __del__(self): + del self.__nUsedCmdNames[:] + + + def setUsed(self, cmdName): + uName = cmdName.upper() + for _cmdName in self.__nUsedCmdNames: + if _cmdName[0] == uName: + _cmdName[1] = _cmdName[1] + 1 + return _cmdName[1] + + self.__nUsedCmdNames.append([uName, 1]) + return 1 + + + def getUsed(self, cmdName): + uName = cmdName.upper() + for _cmdName in self.__nUsedCmdNames: + if _cmdName[0] == uName: + return _cmdName[1] + + return 0 + + def getMoreUsed(self, filter): + moreUsedCmd = "" + nUsedCmd = 0 + for _cmdName in self.__nUsedCmdNames: + if _cmdName[0].startswith(filter): + if _cmdName[1] > nUsedCmd: + moreUsedCmd = _cmdName[0] + nUsedCmd = _cmdName[1] + + return moreUsedCmd, nUsedCmd + + +def displayError(exception = None): + exc_type, exc_value, exc_traceback = sys.exc_info() + format_exception = traceback.format_exception(exc_type, exc_value, exc_traceback) + stk = "" + for s in format_exception: + if s != "Traceback (most recent call last):\n": + stk = s + stk + if exception is not None and exception.__doc__ is not None: + stk = exception.__doc__ + "\n" + stk + stk = QadMsg.translate("QAD", "Well, this is embarrassing !\n\n") + stk + QMessageBox.critical(None, "QAD", stk) + + +def incrementDailyCmdCounter(): + key = '/qgis/digitizing/qad_daily_cmd_counter' + today = date.today().strftime('%Y-%m-%d') + value = QSettings().value(key, today + ";0") + + if type(value) != str: + QSettings().setValue(key, today + ";1") + return 1 + + res = value.split(";") + if len(res) != 2: + QSettings().setValue(key, today + ";1") + return 1 + + if res[0] != today: + QSettings().setValue(key, today + ";1") + return 1 + + n = qad_utils.str2int(res[1]) + if n is None: + n = 0 + + n = n + 1 + QSettings().setValue(key, today + ";" + str(n)) + return n + + +def getMaxDailyCmdCounter(): + try: + path = getQADPath() + "/" + "auth.ini" + f = open(path, "r") + line = f.readline() + f.close() + if decriptPlainText(line) == getMacAddress(): + return 999999 + except: + pass + + return 200 diff --git a/qad_copy_maptool.py b/qad_copy_maptool.py deleted file mode 100644 index 72cd3cca..00000000 --- a/qad_copy_maptool.py +++ /dev/null @@ -1,170 +0,0 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - classe per gestire il map tool in ambito del comando copy - - ------------------- - begin : 2013-10-02 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -from qgis.gui import * -import math - - -import qad_utils -from qad_snapper import * -from qad_snappointsdisplaymanager import * -from qad_variables import * -from qad_getpoint import * -from qad_highlight import QadHighlight -from qad_dim import * -from qad_entity import * - - -#=============================================================================== -# Qad_copy_maptool_ModeEnum class. -#=============================================================================== -class Qad_copy_maptool_ModeEnum(): - # noto niente si richiede il punto base - NONE_KNOWN_ASK_FOR_BASE_PT = 1 - # noto il punto base si richiede il secondo punto per la copia - BASE_PT_KNOWN_ASK_FOR_COPY_PT = 2 - -#=============================================================================== -# Qad_copy_maptool class -#=============================================================================== -class Qad_copy_maptool(QadGetPoint): - - def __init__(self, plugIn): - QadGetPoint.__init__(self, plugIn) - - self.basePt = None - self.entitySet = QadEntitySet() - self.seriesLen = 0 - self.adjust = False - self.__highlight = QadHighlight(self.canvas) - - def hidePointMapToolMarkers(self): - QadGetPoint.hidePointMapToolMarkers(self) - self.__highlight.hide() - - def showPointMapToolMarkers(self): - QadGetPoint.showPointMapToolMarkers(self) - self.__highlight.show() - - def clear(self): - QadGetPoint.clear(self) - self.__highlight.reset() - self.mode = None - - - #============================================================================ - # move - #============================================================================ - def move(self, f, offSetX, offSetY, layerEntitySet, entitySet, dimEntity): - if dimEntity is None: - # sposto la feature - f.setGeometry(qad_utils.moveQgsGeometry(f.geometry(), offSetX, offSetY)) - self.__highlight.addGeometry(f.geometry(), layerEntitySet.layer) - else: - # sposto la quota - dimEntity.move(offSetX, offSetY) - self.__highlight.addGeometry(dimEntity.textualFeature.geometry(), dimEntity.getTextualLayer()) - self.__highlight.addGeometries(dimEntity.getLinearGeometryCollection(), dimEntity.getLinearLayer()) - self.__highlight.addGeometries(dimEntity.getSymbolGeometryCollection(), dimEntity.getSymbolLayer()) - - - def setCopiedGeometries(self, newPt): - self.__highlight.reset() - - # copio entitySet - entitySet = QadEntitySet(self.entitySet) - - for layerEntitySet in entitySet.layerEntitySetList: - layer = layerEntitySet.layer - - transformedBasePt = self.canvas.mapRenderer().mapToLayerCoordinates(layer, self.basePt) - transformedNewPt = self.canvas.mapRenderer().mapToLayerCoordinates(layer, newPt) - offSetX = transformedNewPt.x() - transformedBasePt.x() - offSetY = transformedNewPt.y() - transformedBasePt.y() - entity = QadEntity() - - while len(layerEntitySet.featureIds) > 0: - featureId = layerEntitySet.featureIds[0] - f = layerEntitySet.getFeature(featureId) - if f is None: - del layerEntitySet.featureIds[0] - continue - - # verifico se l'entità appartiene ad uno stile di quotatura - dimEntity = QadDimStyles.getDimEntity(layer, f.id()) - - if self.seriesLen > 0: # devo fare una serie - if self.adjust == True: - offSetX = offSetX / (self.seriesLen - 1) - offSetY = offSetY / (self.seriesLen - 1) - - deltaX = offSetX - deltaY = offSetY - - for i in xrange(1, self.seriesLen, 1): - self.move(f, deltaX, deltaY, layerEntitySet, entitySet, dimEntity) - deltaX = deltaX + offSetX - deltaY = deltaY + offSetY - else: - self.move(f, offSetX, offSetY, layerEntitySet, entitySet, dimEntity) - - # la rimuovo da entitySet - if dimEntity is None: - del layerEntitySet.featureIds[0] - else: - dimEntitySet = dimEntity.getEntitySet() - entitySet.subtract(dimEntitySet) - - def canvasMoveEvent(self, event): - QadGetPoint.canvasMoveEvent(self, event) - - # noto il punto base si richiede il secondo punto - if self.mode == Qad_copy_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_COPY_PT: - self.setCopiedGeometries(self.tmpPoint) - - - def activate(self): - QadGetPoint.activate(self) - self.__highlight.show() - - def deactivate(self): - try: # necessario perché se si chiude QGIS parte questo evento nonostante non ci sia più l'oggetto maptool ! - QadGetPoint.deactivate(self) - self.__highlight.hide() - except: - pass - - def setMode(self, mode): - self.mode = mode - # noto niente si richiede il punto base - if self.mode == Qad_copy_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_BASE_PT: - self.setDrawMode(QadGetPointDrawModeEnum.NONE) - # noto il punto base si richiede il secondo punto per l'angolo di rotazione - elif self.mode == Qad_copy_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_COPY_PT: - self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.setStartPoint(self.basePt) diff --git a/qad_debug.py b/qad_debug.py deleted file mode 100644 index f4323ecd..00000000 --- a/qad_debug.py +++ /dev/null @@ -1,61 +0,0 @@ -# -*- coding: latin1 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - classe per il debug - - ------------------- - begin : 2013-04-17 - copyright : (C) 2013 IREN Acqua Gas SpA - email : geosim.dev@gruppoiren.it - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - -DEBUG = False # False # True - -if DEBUG == True: - import os.path - - import sys - - path = "C:/software/python/eclipse-SDK-4.2.2-win32/eclipse/plugins/org.python.pydev_2.7.3.2013031601/pysrc/" - if os.path.exists(path): - import sys - sys.path.append(path) - from pydevd import * - - -# Import the PyQt library -from PyQt4.QtCore import * -from PyQt4.QtGui import * - - -def isDebug(): - return DEBUG - -def displayMsg(msg): - if isDebug(): - QMessageBox.information(None, "QAD Debug Window", msg) - -def displayList(values): - if isDebug(): - msg = "" - for value in Values: - msg = msg + str(value) + "\n" - displayMsg(msg) - -def breakPoint(): - if isDebug(): - # Premi F5 per continuare il debug... - settrace() \ No newline at end of file diff --git a/qad_dim.py b/qad_dim.py index 2b277944..396159ab 100644 --- a/qad_dim.py +++ b/qad_dim.py @@ -1,3901 +1,5984 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - classe per la gestione delle quote - - ------------------- - begin : 2014-02-20 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -from qgis.gui import * -import codecs -import ConfigParser -import math -import sys - - -import qad_utils -import qad_stretch_fun -import qad_layer -import qad_label -from qad_entity import * -from qad_variables import * - - -""" -La classe quotatura é composta da tre layer: testo, linea, simbolo con lo stesso sistema di coordinate. - -Il layer testo deve avere tutte le caratteristiche del layer testo di QAD ed in più: -- il posizionamento dell'etichetta con modalita "Intorno al punto" con distanza = 0 - (che vuol dire punto di inserimento in basso a sx) -- la dimensione del testo in unità mappa (la dimensione varia a seconda dello zoom). -- dimStyleFieldName = "dim_style"; nome del campo che contiene il nome dello stile di quota (opzionale) -- dimTypeFieldName = "dim_type"; nome del campo che contiene il tipo dello stile di quota (opzionale) -- l'opzione "Mostra etichette capovolte" deve essere su "sempre" nel tab "Etichette"->"Visualizzazione" -- rotFieldName = "rot"; nome del campo che contiene la rotazione del testo -- la rotazione deve essere letta dal campo indicato da rotFieldName -- idFieldName = "id"; nome del campo che contiene il codice della quota (opzionale) -- la rotazione deve essere derivata dal campo rotFieldName -- il font del carattere può essere derivata da un campo -- la dimensione del carattere può essere derivata da un campo -- il colore del testo può essere derivato da un campo (opzionale) - - -Il layer simbolo deve avere tutte le caratteristiche del layer simbolo di QAD ed in più: -- il simbolo freccia con rotazione 0 deve essere orizzontale con la freccia rivolta verso destra - ed il suo punto di inserimento deve essere sulla punta della freccia -- la dimensione del simbolo in unità mappa (la dimensione varia a seconda dello zoom), - impostare la dimensione del simbolo in modo che la larghezza della freccia sia 1 unità di mappa. -- componentFieldName = "type"; nome del campo che contiene il tipo di componente della quota (vedi QadDimComponentEnum) (opzionale) -- symbolFieldName = "block"; nome del campo che contiene il nome del simbolo (opzionale) -- idParentFieldName = "id_parent"; nome del campo che contiene il codice del testo della quota (opzionale) -- scaleFieldName = "scale"; nome del campo che contiene il fattore di scala del simbolo (opzionale) - se usato usare lo stile "singolo simbolo" (unico che consente di impostare la scala come diametro scala) - la scala deve essere impostata su attraverso Stile->avanzato->campo di dimensione della scala-> - la modalità di scala deve essere impostata su attraverso Stile->avanzato->campo di dimensione della scala->diametro scala -- rotFieldName = "rot"; nome del campo che contiene la rotazione del simbolo - la rotazione deve essere letta dal campo indicato da rotFieldName (360-rotFieldName) - -Il layer linea deve avere tutte le caratteristiche del layer linea ed in più: -- componentFieldName = "type"; nome del campo che contiene il tipo di componente della quota (vedi QadDimComponentEnum) (opzionale) -- lineTypeFieldName = "line_type"; nome del campo che contiene il tipolinea (opzionale) -- colorFieldName = "color"; nome del campo che contiene il colore 'r,g,b,alpha'; alpha é opzionale (0=trasparente, 255=opaco) (opzionale) -- idParentFieldName = "id_parent"; nome del campo che contiene il codice del testo della quota (opzionale) - -""" - - -#=============================================================================== -# QadDimTypeEnum class. -#=============================================================================== -class QadDimTypeEnum(): - ALIGNED = "AL" # quota lineare allineata ai punti di origine delle linee di estensione - ANGULAR = "AN" # quota angolare, misura l'angolo tra i 3 punti o tra gli oggetti selezionati - BASE_LINE = "BL" # quota lineare, angolare o coordinata a partire dalla linea di base della quota precedente o di una quota selezionata - DIAMETER = "DI" # quota per il diametro di un cerchio o di un arco - LEADER = "LD" # crea una linea che consente di collegare un'annotazione ad una lavorazione - LINEAR = "LI" # quota lineare con una linea di quota orizzontale o verticale - RADIUS = "RA" # quota radiale, misura il raggio di un cerchio o di un arco selezionato e visualizza il testo di quota con un simbolo di raggio davanti - ARC_LENTGH = "AR" # quota per la lunghezza di un cerchio o di un arco - - -#=============================================================================== -# QadDimComponentEnum class. -#=============================================================================== -class QadDimComponentEnum(): - DIM_LINE1 = "D1" # linea di quota ("Dimension line 1") - DIM_LINE2 = "D2" # linea di quota ("Dimension line 2") - EXT_LINE1 = "E1" # prima linea di estensione ("Extension line 1") - EXT_LINE2 = "E2" # seconda linea di estensione ("Extension line 2") - LEADER_LINE = "L" # linea porta quota usata quando il testo é fuori dalla quota ("Leader") - BLOCK1 = "B1" # primo blocco della freccia ("Block 1") - BLOCK2 = "B2" # secondo blocco della freccia ("Block 2") - LEADER_BLOCK = "LB" # blocco della freccia nel caso leader ("Leader Block") - ARC_BLOCK = "AB" # simbolo dell'arco ("Arc Block") - DIM_PT1 = "D1" # primo punto da quotare ("Dimension point 1") - DIM_PT2 = "D2" # secondo punto da quotare ("Dimension point 2") - TEXT_PT = "T" # punto del testo di quota ("Text") - - -#=============================================================================== -# QadDimStyleAlignmentEnum class. -#=============================================================================== -class QadDimStyleAlignmentEnum(): - HORIZONTAL = 0 # orizzontale - VERTICAL = 1 # verticale - ALIGNED = 2 # allineata - FORCED_ROTATION = 3 # rotazione forzata - - -#=============================================================================== -# QadDimStyleTxtVerticalPosEnum class. -#=============================================================================== -class QadDimStyleTxtVerticalPosEnum(): - CENTERED_LINE = 0 # testo centrato alla linea di quota - ABOVE_LINE = 1 # testo sopra alla linea di quota ma nel caso la linea di quota non sia orizzontale - # e il testo sia dentro le linee di estensione e forzato orizzontale allora il testo diventa centrato - EXTERN_LINE = 2 # testo posizionato nella parte opposta ai punti di quotatura - BELOW_LINE = 4 # testo sotto alla linea di quota ma nel caso la linea di quota non sia orizzontale - # e il testo sia dentro le linee di estensione e forzato orizzontale allora il testo diventa centrato - - -#=============================================================================== -# QadDimStyleTxtHorizontalPosEnum class. -#=============================================================================== -class QadDimStyleTxtHorizontalPosEnum(): - CENTERED_LINE = 0 # testo centrato alla linea di quota - FIRST_EXT_LINE = 1 # testo vicino alla prima linea di estensione - SECOND_EXT_LINE = 2 # testo vicino alla seconda linea di estensione - FIRST_EXT_LINE_UP = 3 # testo sopra e allineato alla prima linea di estensione - SECOND_EXT_LINE_UP = 4 # testo sopra e allineato alla seconda linea di estensione - - -#=============================================================================== -# QadDimStyleTxtRotEnum class. -#=============================================================================== -class QadDimStyleTxtRotModeEnum(): - HORIZONTAL = 0 # testo orizzontale - ALIGNED_LINE = 1 # testo allineato con la linea di quota - ISO = 2 # testo allineato con la linea di quota se tra le linee di estensione, - # altrimenti testo orizzontale - FORCED_ROTATION = 3 # testo con rotazione forzata - - -#=============================================================================== -# QadDimStyleArcSymbolPosEnum class. -#=============================================================================== -class QadDimStyleArcSymbolPosEnum(): - BEFORE_TEXT = 0 # simbolo prima del testo - ABOVE_TEXT = 1 # simbolo sopra il testo - NONE = 2 # niente simbolo - - -#=============================================================================== -# QadDimStyleArcSymbolPosEnum class. -#=============================================================================== -class QadDimStyleTxtDirectionEnum(): - SX_TO_DX = 0 # da sinistra a destra - DX_TO_SX = 1 # da destra a sinistra - - -#=============================================================================== -# QadDimStyleTextBlocksAdjustEnum class. -#=============================================================================== -class QadDimStyleTextBlocksAdjustEnum(): - BOTH_OUTSIDE_EXT_LINES = 0 # sposta testo e frecce fuori dalle linee di estensione - FIRST_BLOCKS_THEN_TEXT = 1 # sposta prima le frecce poi, se non basta, anche il testo - FIRST_TEXT_THEN_BLOCKS = 2 # sposta prima il testo poi, se non basta, anche le frecce - WHICHEVER_FITS_BEST = 3 # Sposta indistintamente il testo o le frecce (l'oggetto che si adatta meglio) - - -#=============================================================================== -# QadDim dimension style class -#=============================================================================== -class QadDimStyle(): - - def __init__(self, dimStyle = None): - self.name = "standard" # nome dello stile - self.description = "" - self.path = "" # percorso e nome del file in cui è stato salvato/caricato - self.dimType = QadDimTypeEnum.ALIGNED # tipo di quotatura - - # testo di quota - self.textPrefix = "" # prefisso per il testo della quota - self.textSuffix = "" # suffisso per il testo della quota - self.textSuppressLeadingZeros = False # per sopprimere o meno gli zero all'inizio del testo - self.textDecimalZerosSuppression = True # per sopprimere gli zero finali nei decimali - self.textHeight = 1.0 # altezza testo (DIMTXT) in unità di mappa - self.textVerticalPos = QadDimStyleTxtVerticalPosEnum.ABOVE_LINE # posizione verticale del testo rispetto la linea di quota (DIMTAD) - self.textHorizontalPos = QadDimStyleTxtHorizontalPosEnum.CENTERED_LINE # posizione orizzontale del testo rispetto la linea di quota (DIMTAD) - self.textOffsetDist = 0.5 # distanza aggiunta intorno al testo quando per inserirlo viene spezzata la linea di quota (DIMGAP) - self.textRotMode = QadDimStyleTxtRotModeEnum.ALIGNED_LINE # modalità di rotazione del testo (DIMTIH e DIMTOH) - self.textForcedRot = 0.0 # rotazione forzata del testo - self.textDecimals = 2 # numero di decimali (DIMDEC) - self.textDecimalSep = "." # Separatore dei decimali (DIMDSEP) - self.textFont = "Arial" # nome del font di testo (DIMTXSTY) - self.textColor = "255,255,255,255" # Colore per i testi della quota (DIMCLRT); bianco con opacità totale - self.textDirection = QadDimStyleTxtDirectionEnum.SX_TO_DX # specifica la direzione del testo di quota (DIMTXTDIRECTION) 0 = da sx a dx, 1 = da dx a sx - self.arcSymbPos = QadDimStyleArcSymbolPosEnum.BEFORE_TEXT # disegna o meno il simbolo dell'arco con DIMARC (DIMARCSYM). - - # linee di quota - self.dimLine1Show = True # Mostra o nasconde la prima linea di quota (DIMSD1) - self.dimLine2Show = True # Mostra o nasconde la seconda linea di quota (DIMSD2) - self.dimLineLineType = "continuous" # Tipo di linea per le linee di quota (DIMLTYPE) - self.dimLineColor = "255,255,255,255" # Colore per le linee di quota (DIMCLRD); bianco con opacità totale - self.dimLineSpaceOffset = 3.75 # Controlla la spaziatura delle linee di quota nelle quote da linea di base (DIMDLI) - - # simboli per linee di quota - # il blocco per la freccia é una freccia verso destra con il punto di inserimento sulla punta della freccia - self.block1Name = "triangle2" # nome del simbolo da usare come punta della freccia sulla prima linea di quota (DIMBLK1) - self.block2Name = "triangle2" # nome del simbolo da usare come punta della freccia sulla seconda linea di quota (DIMBLK2) - self.blockLeaderName = "triangle2" # nome del simbolo da usare come punta della freccia sulla linea della direttrice (DIMLDRBLK) - self.blockWidth = 0.5 # larghezza del simbolo (in orizzontale) quando la dimensione in unità di mappa = 1 (vedi "triangle2") - self.blockScale = 1.0 # scala della dimensione del simbolo (DIMASZ) - self.centerMarkSize = 0.0 # disegna o meno il marcatore di centro o le linee d'asse per le quote create con - # DIMCENTER, DIMDIAMETER, e DIMRADIUS (DIMCEN). - # 0 = niente, > 0 dimensione marcatore di centro, < 0 dimensione linee d'asse - - # adattamento del testo e delle frecce - self.textBlockAdjust = QadDimStyleTextBlocksAdjustEnum.WHICHEVER_FITS_BEST # (DIMATFIT) - self.blockSuppressionForNoSpace = False # Sopprime le punte della frecce se non c'é spazio sufficiente all'interno delle linee di estensione (DIMSOXD) - - # linee di estensione - self.extLine1Show = True # Mostra o nasconde la prima linea di estensione (DIMSE1) - self.extLine2Show = True # Mostra o nasconde la seconda linea di estensione (DIMSE2) - self.extLine1LineType = "continuous" # Tipo di linea per la prima linea di estensione (DIMLTEX1) - self.extLine2LineType = "continuous" # Tipo di linea per la seconda linea di estensione (DIMLTEX2) - self.extLineColor = "255,255,255,255" # Colore per le linee di estensione (DIMCLRE); bianco con opacità totale - self.extLineOffsetDimLine = 0.0 # distanza della linea di estensione oltre la linea di quota (DIMEXE) - self.extLineOffsetOrigPoints = 0.0 # distanza della linea di estensione dai punti da quotare (DIMEXO) - self.extLineIsFixedLen = False # Attiva lunghezza fissa delle line di estensione (DIMFXLON) - self.extLineFixedLen = 1.0 # lunghezza fissa delle line di estensione (DIMFXL) dalla linea di quota - # al punto da quotare spostato di extLineOffsetOrigPoints - # (la linea di estensione non va oltre il punto da quotare) - - # layer e loro caratteristiche - # devo allocare i campi a livello di classe QadDimStyle perché QgsFeature.setFields usa solo il puntatore alla lista fields - # che, se allocata privatamente in qualsiasi funzione, all'uscita della funzione verrebbe distrutta - self.textualLayerName = None # nome layer per memorizzare il testo della quota - self.__textualLayer = None # layer per memorizzare il testo della quota - self.__textFields = None - self.__textualFeaturePrototype = None - - self.linearLayerName = None # nome layer per memorizzare le linee della quota - self.__linearLayer = None # layer per memorizzare le linee della quota - self.__lineFields = None - self.__linearFeaturePrototype = None - - self.symbolLayerName = None # nome layer per memorizzare i blocchi delle frecce della quota - self.__symbolLayer = None # layer per memorizzare i blocchi delle frecce della quota - self.__symbolFields = None - self.__symbolFeaturePrototype = None - - self.componentFieldName = "type" # nome del campo che contiene il tipo di componente della quota (vedi QadDimComponentEnum) - self.lineTypeFieldName = "line_type" # nome del campo che contiene il tipolinea - self.colorFieldName = "color" # nome del campo che contiene il colore 'r,g,b,alpha'; alpha é opzionale (0=trasparente, 255=opaco) - self.idFieldName = "id" # nome del campo che contiene il codice del della quota nel layer di tipo testo - self.idParentFieldName = "id_parent" # nome del campo che contiene il codice della quota nei layer simbolo e linea - self.dimStyleFieldName = "dim_style" # nome del campo che contiene il nome dello stile di quota - self.dimTypeFieldName = "dim_type" # nome del campo che contiene il tipo dello stile di quota - self.symbolFieldName = "block" # nome del campo che contiene il nome del simbolo - self.scaleFieldName = "scale" # nome del campo che contiene la dimensione - self.rotFieldName = "rot" # nome del campo che contiene rotazione in gradi - - if dimStyle is None: - return - self.set(dimStyle) - - - #============================================================================ - # FUNZIONI GENERICHE - INIZIO - #============================================================================ - - def set(self, dimStyle): - self.name = dimStyle.name - self.description = dimStyle.description - self.path = dimStyle.path - self.dimType = dimStyle.dimType - - # testo di quota - self.textPrefix = dimStyle.textPrefix - self.textSuffix = dimStyle.textSuffix - self.textSuppressLeadingZeros = dimStyle.textSuppressLeadingZeros - self.textDecimaZerosSuppression = dimStyle.textDecimalZerosSuppression - self.textHeight = dimStyle.textHeight - self.textVerticalPos = dimStyle.textVerticalPos - self.textHorizontalPos = dimStyle.textHorizontalPos - self.textOffsetDist = dimStyle.textOffsetDist - self.textRotMode = dimStyle.textRotMode - self.textForcedRot = dimStyle.textForcedRot - self.textDecimals = dimStyle.textDecimals - self.textDecimalSep = dimStyle.textDecimalSep - self.textFont = dimStyle.textFont - self.textColor = dimStyle.textColor - self.textDirection = dimStyle.textDirection - self.arcSymbPos = dimStyle.arcSymbPos - - # linee di quota - self.dimLine1Show = dimStyle.dimLine1Show - self.dimLine2Show = dimStyle.dimLine2Show - self.dimLineLineType = dimStyle.dimLineLineType - self.dimLineColor = dimStyle.dimLineColor - self.dimLineSpaceOffset = dimStyle.dimLineSpaceOffset - - # simboli per linee di quota - self.block1Name = dimStyle.block1Name - self.block2Name = dimStyle.block2Name - self.blockLeaderName = dimStyle.blockLeaderName - self.blockWidth = dimStyle.blockWidth - self.blockScale = dimStyle.blockScale - self.blockSuppressionForNoSpace = dimStyle.blockSuppressionForNoSpace - self.centerMarkSize = dimStyle.centerMarkSize - - # adattamento del testo e delle frecce - self.textBlockAdjust = dimStyle.textBlockAdjust - - # linee di estensione - self.extLine1Show = dimStyle.extLine1Show - self.extLine2Show = dimStyle.extLine2Show - self.extLine1LineType = dimStyle.extLine1LineType - self.extLine2LineType = dimStyle.extLine2LineType - self.extLineColor = dimStyle.extLineColor - self.extLineOffsetDimLine = dimStyle.extLineOffsetDimLine - self.extLineOffsetOrigPoints = dimStyle.extLineOffsetOrigPoints - self.extLineIsFixedLen = dimStyle.extLineIsFixedLen - self.extLineFixedLen = dimStyle.extLineFixedLen - - # layer e loro caratteristiche - self.textualLayerName = dimStyle.textualLayerName - self.__textualLayer = dimStyle.__textualLayer - self.__textFields = dimStyle.__textFields - self.__textualFeaturePrototype = dimStyle.__textualFeaturePrototype - self.linearLayerName = dimStyle.linearLayerName - self.__linearLayer = dimStyle.__linearLayer - self.__lineFields = dimStyle.__lineFields - self.__linearFeaturePrototype = dimStyle.__linearFeaturePrototype - self.symbolLayerName = dimStyle.symbolLayerName - self.__symbolLayer = dimStyle.__symbolLayer - self.__symbolFields = dimStyle.__symbolFields - self.__symbolFeaturePrototype = dimStyle.__symbolFeaturePrototype - - self.componentFieldName = dimStyle.componentFieldName - self.symbolFieldName = dimStyle.symbolFieldName - self.lineTypeFieldName = dimStyle.lineTypeFieldName - self.colorFieldName = dimStyle.colorFieldName - self.idFieldName = dimStyle.idFieldName - self.idParentFieldName = dimStyle.idParentFieldName - self.dimStyleFieldName = dimStyle.dimStyleFieldName - self.dimTypeFieldName = dimStyle.dimTypeFieldName - self.scaleFieldName = dimStyle.scaleFieldName - self.rotFieldName = dimStyle.rotFieldName - - - #============================================================================ - # getPropList - #============================================================================ - def getPropList(self): - proplist = dict() # dizionario di nome con lista [descrizione, valore] - propDescr = QadMsg.translate("Dimension", "Name") - proplist["name"] = [propDescr, self.name] - propDescr = QadMsg.translate("Dimension", "Description") - proplist["description"] = [propDescr, self.description] - propDescr = QadMsg.translate("Dimension", "File path") - proplist["path"] = [propDescr, self.path] - - # testo di quota - value = self.textPrefix - if len(self.textPrefix) > 0: - value += "<>" - value += self.textSuffix - propDescr = QadMsg.translate("Dimension", "Text prefix and suffix") - proplist["textPrefix"] = [propDescr, value] - propDescr = QadMsg.translate("Dimension", "Leading zero suppression") - proplist["textSuppressLeadingZeros"] = [propDescr, self.textSuppressLeadingZeros] - propDescr = QadMsg.translate("Dimension", "Trailing zero suppression") - proplist["textDecimalZerosSuppression"] = [propDescr, self.textDecimalZerosSuppression] - propDescr = QadMsg.translate("Dimension", "Text height") - proplist["textHeight"] = [propDescr, self.textHeight] - propDescr = QadMsg.translate("Dimension", "Vertical text position") - proplist["textVerticalPos"] = [propDescr, self.textVerticalPos] - propDescr = QadMsg.translate("Dimension", "Horizontal text position") - proplist["textHorizontalPos"] = [propDescr, self.textHorizontalPos] - propDescr = QadMsg.translate("Dimension", "Text offset") - proplist["textOffsetDist"] = [propDescr, self.textOffsetDist] - propDescr = QadMsg.translate("Dimension", "Text alignment") - proplist["textRotMode"] = [propDescr, self.textRotMode] - propDescr = QadMsg.translate("Dimension", "Fixed text rotation") - proplist["textForcedRot"] = [propDescr, self.textForcedRot] - propDescr = QadMsg.translate("Dimension", "Precision") - proplist["textDecimals"] = [propDescr, self.textDecimals] - propDescr = QadMsg.translate("Dimension", "Decimal separator") - proplist["textDecimalSep"] = [propDescr, self.textDecimalSep] - propDescr = QadMsg.translate("Dimension", "Text font") - proplist["textFont"] = [propDescr, self.textFont] - propDescr = QadMsg.translate("Dimension", "Text color") - proplist["textColor"] = [propDescr, self.textColor] - if self.textDirection == QadDimStyleTxtDirectionEnum.SX_TO_DX: - value = QadMsg.translate("Dimension", "From left to right") - else: - value = QadMsg.translate("Dimension", "From right to left") - propDescr = QadMsg.translate("Dimension", "Text direction") - proplist["textDirection"] = [propDescr, value] - propDescr = QadMsg.translate("Dimension", "Arc len. symbol") - proplist["arcSymbPos"] = [propDescr, self.arcSymbPos] - - # linee di quota - propDescr = QadMsg.translate("Dimension", "Dim line 1 visible") - proplist["dimLine1Show"] = [propDescr, self.dimLine1Show] - propDescr = QadMsg.translate("Dimension", "Dim line 2 visible") - proplist["dimLine2Show"] = [propDescr, self.dimLine2Show] - propDescr = QadMsg.translate("Dimension", "Dim line linetype") - proplist["dimLineLineType"] = [propDescr, self.dimLineLineType] - propDescr = QadMsg.translate("Dimension", "Dim line color") - proplist["dimLineColor"] = [propDescr, self.dimLineColor] - propDescr = QadMsg.translate("Dimension", "Offset from origin") - proplist["dimLineSpaceOffset"] = [propDescr, self.dimLineSpaceOffset] - - # simboli per linee di quota - propDescr = QadMsg.translate("Dimension", "Arrow 1") - proplist["block1Name"] = [propDescr, self.block1Name] - propDescr = QadMsg.translate("Dimension", "Arrow 2") - proplist["block2Name"] = [propDescr, self.block2Name] - propDescr = QadMsg.translate("Dimension", "Leader arrow") - proplist["blockLeaderName"] = [propDescr, self.blockLeaderName] - propDescr = QadMsg.translate("Dimension", "Arrowhead width") - proplist["blockWidth"] = [propDescr, self.blockWidth] - propDescr = QadMsg.translate("Dimension", "Arrowhead scale") - proplist["blockScale"] = [propDescr, self.blockScale] - propDescr = QadMsg.translate("Dimension", "Center mark size") - proplist["centerMarkSize"] = [propDescr, self.centerMarkSize] - - # adattamento del testo e delle frecce - propDescr = QadMsg.translate("Dimension", "Fit: arrows and text") - proplist["textBlockAdjust"] = [propDescr, self.textBlockAdjust] - propDescr = QadMsg.translate("Dimension", "Suppress arrows for lack of space") - proplist["blockSuppressionForNoSpace"] = [propDescr, self.blockSuppressionForNoSpace] - - # linee di estensione - propDescr = QadMsg.translate("Dimension", "Ext. line 1 visible") - proplist["extLine1Show"] = [propDescr, self.extLine1Show] - propDescr = QadMsg.translate("Dimension", "Ext. line 2 visible") - proplist["extLine2Show"] = [propDescr, self.extLine2Show] - propDescr = QadMsg.translate("Dimension", "Ext. line 1 linetype") - proplist["extLine1LineType"] = [propDescr, self.extLine1LineType] - propDescr = QadMsg.translate("Dimension", "Ext. line 2 linetype") - proplist["extLine2LineType"] = [propDescr, self.extLine2LineType] - propDescr = QadMsg.translate("Dimension", "Ext. line color") - proplist["extLineColor"] = [propDescr, self.extLineColor] - propDescr = QadMsg.translate("Dimension", "Ext. line extension") - proplist["extLineOffsetDimLine"] = [propDescr, self.extLineOffsetDimLine] - propDescr = QadMsg.translate("Dimension", "Ext. line offset") - proplist["extLineOffsetOrigPoints"] = [propDescr, self.extLineOffsetOrigPoints] - propDescr = QadMsg.translate("Dimension", "Fixed length ext. line activated") - proplist["extLineIsFixedLen"] = [propDescr, self.extLineIsFixedLen] - propDescr = QadMsg.translate("Dimension", "Fixed length ext. line") - proplist["extLineFixedLen"] = [propDescr, self.extLineFixedLen] - - # layer e loro caratteristiche - propDescr = QadMsg.translate("Dimension", "Layer for dim texts") - proplist["textualLayerName"] = [propDescr, self.textualLayerName] - propDescr = QadMsg.translate("Dimension", "Layer for dim lines") - proplist["linearLayerName"] = [propDescr, self.linearLayerName] - propDescr = QadMsg.translate("Dimension", "Layer for dim arrows") - proplist["symbolLayerName"] = [propDescr, self.symbolLayerName] - - propDescr = QadMsg.translate("Dimension", "Field for component type") - proplist["componentFieldName"] = [propDescr, self.componentFieldName] - propDescr = QadMsg.translate("Dimension", "Field for linetype") - proplist["lineTypeFieldName"] = [propDescr, self.lineTypeFieldName] - propDescr = QadMsg.translate("Dimension", "Field for color") - proplist["colorFieldName"] = [propDescr, self.colorFieldName] - propDescr = QadMsg.translate("Dimension", "Field for dim ID in texts") - proplist["idFieldName"] = [propDescr, self.idFieldName] - propDescr = QadMsg.translate("Dimension", "Field for dim ID in lines and arrows") - proplist["idParentFieldName"] = [propDescr, self.idParentFieldName] - propDescr = QadMsg.translate("Dimension", "Field for dim style name") - proplist["dimStyleFieldName"] = [propDescr, self.dimStyleFieldName] - propDescr = QadMsg.translate("Dimension", "Field for dim type") - proplist["dimTypeFieldName"] = [propDescr, self.dimTypeFieldName] - propDescr = QadMsg.translate("Dimension", "Field for symbol name") - proplist["symbolFieldName"] = [propDescr, self.symbolFieldName] - propDescr = QadMsg.translate("Dimension", "Field for arrows scale") - proplist["scaleFieldName"] = [propDescr, self.scaleFieldName] - propDescr = QadMsg.translate("Dimension", "Field for arrows rotation") - proplist["rotFieldName"] = [propDescr, self.rotFieldName] - - return proplist - - - #============================================================================ - # getLayer - #============================================================================ - def getLayer(self, layerName): - layerList = qad_layer.getLayersByName(qad_utils.wildCard2regularExpr(layerName)) - if len(layerList) == 1: - return layerList[0] - return None - - - #============================================================================ - # layer testuale - def getTextualLayer(self): - if self.__textualLayer is None: - self.__textualLayer = self.getLayer(self.textualLayerName) - return self.__textualLayer - - def getTextualLayerFields(self): - if self.__textFields is None: - self.__textFields = None if self.getTextualLayer() is None else self.getTextualLayer().pendingFields() - return self.__textFields - - def getTextualFeaturePrototype(self): - if self.__textualFeaturePrototype is None: - if self.getTextualLayerFields() is not None: - self.__textualFeaturePrototype = QgsFeature(self.getTextualLayerFields()) - self.initFeatureToDefautlValues(self.getTextualLayer(), self.__textualFeaturePrototype) - return self.__textualFeaturePrototype - - - #============================================================================ - # layer lineare - def getLinearLayer(self): - if self.__linearLayer is None: - self.__linearLayer = self.getLayer(self.linearLayerName) - return self.__linearLayer - - def getLinearLayerFields(self): - if self.__lineFields is None: - self.__lineFields = None if self.getLinearLayer() is None else self.getLinearLayer().pendingFields() - return self.__lineFields - - def getLinearFeaturePrototype(self): - if self.__linearFeaturePrototype is None: - if self.getLinearLayerFields() is not None: - self.__linearFeaturePrototype = QgsFeature(self.getLinearLayerFields()) - self.initFeatureToDefautlValues(self.getLinearLayer(), self.__linearFeaturePrototype) - return self.__linearFeaturePrototype - - - #============================================================================ - # layer simbolo - def getSymbolLayer(self): - if self.__symbolLayer is None: - self.__symbolLayer = self.getLayer(self.symbolLayerName) - return self.__symbolLayer - - def getSymbolLayerFields(self): - if self.__symbolFields is None: - self.__symbolFields = None if self.getSymbolLayer() is None else self.getSymbolLayer().pendingFields() - return self.__symbolFields - - def getSymbolFeaturePrototype(self): - if self.__symbolFeaturePrototype is None: - if self.getSymbolLayerFields() is not None: - self.__symbolFeaturePrototype = QgsFeature(self.getSymbolLayerFields()) - self.initFeatureToDefautlValues(self.getSymbolLayer(), self.__symbolFeaturePrototype) - return self.__symbolFeaturePrototype - - - #============================================================================ - # initFeatureToDefautlValues - #============================================================================ - def initFeatureToDefautlValues(self, layer, f): - # assegno i valori di default - provider = layer.dataProvider() - fields = f.fields() - for field in fields.toList(): - i = fields.indexFromName(field.name()) - f[field.name()] = provider.defaultValue(i) - - - #============================================================================ - # save - #============================================================================ - def save(self, path = "", overwrite = True): - """ - Salva le impostazioni dello stile di quotatura in un file. - """ - if path == "" and self.path != "": - _path = self.path - else: - dir, base = os.path.split(path) - if dir == "": - dir = QDir.cleanPath(QgsApplication.qgisSettingsDirPath() + "python/plugins/qad") + "/" - else: - dir = QDir.cleanPath(dir) + "/" - - name, ext = os.path.splitext(base) - if name == "": - name = self.name - - if ext == "": # se non c'è estensione la aggiungo - ext = ".dim" - - _path = dir + name + ext - - if overwrite == False: # se non si vuole sovrascrivere - if os.path.exists(_path): - return False - - dir = QFileInfo(_path).absoluteDir() - if not dir.exists(): - os.makedirs(dir.absolutePath()) - - config = qad_utils.QadRawConfigParser(allow_no_value=True) - config.add_section("dimension_options") - config.set("dimension_options", "name", str(self.name)) - config.set("dimension_options", "description", self.description) - config.set("dimension_options", "dimType", str(self.dimType)) - - # testo di quota - config.set("dimension_options", "textPrefix", str(self.textPrefix)) - config.set("dimension_options", "textSuffix", str(self.textSuffix)) - config.set("dimension_options", "textSuppressLeadingZeros", str(self.textSuppressLeadingZeros)) - config.set("dimension_options", "textDecimalZerosSuppression", str(self.textDecimalZerosSuppression)) - config.set("dimension_options", "textHeight", str(self.textHeight)) - config.set("dimension_options", "textVerticalPos", str(self.textVerticalPos)) - config.set("dimension_options", "textHorizontalPos", str(self.textHorizontalPos)) - config.set("dimension_options", "textOffsetDist", str(self.textOffsetDist)) - config.set("dimension_options", "textRotMode", str(self.textRotMode)) - config.set("dimension_options", "textForcedRot", str(self.textForcedRot)) - config.set("dimension_options", "textDecimals", str(self.textDecimals)) - config.set("dimension_options", "textDecimalSep", str(self.textDecimalSep)) - config.set("dimension_options", "textFont", str(self.textFont)) - config.set("dimension_options", "textColor", str(self.textColor)) - config.set("dimension_options", "textDirection", str(self.textDirection)) - config.set("dimension_options", "arcSymbPos", str(self.arcSymbPos)) - - # linee di quota - config.set("dimension_options", "dimLine1Show", str(self.dimLine1Show)) - config.set("dimension_options", "dimLine2Show", str(self.dimLine2Show)) - config.set("dimension_options", "dimLineLineType", str(self.dimLineLineType)) - config.set("dimension_options", "dimLineColor", str(self.dimLineColor)) - config.set("dimension_options", "dimLineSpaceOffset", str(self.dimLineSpaceOffset)) - - # simboli per linee di quota - config.set("dimension_options", "block1Name", str(self.block1Name)) - config.set("dimension_options", "block2Name", str(self.block2Name)) - config.set("dimension_options", "blockLeaderName", str(self.blockLeaderName)) - config.set("dimension_options", "blockWidth", str(self.blockWidth)) - config.set("dimension_options", "blockScale", str(self.blockScale)) - config.set("dimension_options", "blockSuppressionForNoSpace", str(self.blockSuppressionForNoSpace)) - config.set("dimension_options", "centerMarkSize", str(self.centerMarkSize)) - - # adattamento del testo e delle frecce - config.set("dimension_options", "textBlockAdjust", str(self.textBlockAdjust)) - - # linee di estensione - config.set("dimension_options", "extLine1Show", str(self.extLine1Show)) - config.set("dimension_options", "extLine2Show", str(self.extLine2Show)) - config.set("dimension_options", "extLine1LineType", str(self.extLine1LineType)) - config.set("dimension_options", "extLine2LineType", str(self.extLine2LineType)) - config.set("dimension_options", "extLineColor", str(self.extLineColor)) - config.set("dimension_options", "extLineOffsetDimLine", str(self.extLineOffsetDimLine)) - config.set("dimension_options", "extLineOffsetOrigPoints", str(self.extLineOffsetOrigPoints)) - config.set("dimension_options", "extLineIsFixedLen", str(self.extLineIsFixedLen)) - config.set("dimension_options", "extLineFixedLen", str(self.extLineFixedLen)) - - # layer e loro caratteristiche - config.set("dimension_options", "textualLayerName", "" if self.textualLayerName is None else self.textualLayerName) - config.set("dimension_options", "linearLayerName", "" if self.linearLayerName is None else self.linearLayerName) - config.set("dimension_options", "symbolLayerName", "" if self.symbolLayerName is None else self.symbolLayerName) - config.set("dimension_options", "componentFieldName", str(self.componentFieldName)) - config.set("dimension_options", "symbolFieldName", str(self.symbolFieldName)) - config.set("dimension_options", "lineTypeFieldName", str(self.lineTypeFieldName)) - config.set("dimension_options", "colorFieldName", str(self.colorFieldName)) - config.set("dimension_options", "idFieldName", str(self.idFieldName)) - config.set("dimension_options", "idParentFieldName", str(self.idParentFieldName)) - config.set("dimension_options", "dimStyleFieldName", str(self.dimStyleFieldName)) - config.set("dimension_options", "dimTypeFieldName", str(self.dimTypeFieldName)) - config.set("dimension_options", "scaleFieldName", str(self.scaleFieldName)) - config.set("dimension_options", "rotFieldName", str(self.rotFieldName)) - - with codecs.open(_path, 'w', 'utf-8') as configFile: - config.write(configFile) - - self.path = _path - - return True - - - #============================================================================ - # getDefaultDimFilePath - #============================================================================ - def getDefaultDimFilePath(self, fileName): - # ottiene il percorso automatico dove salvare/caricare il file della quotatura - # se esiste un progetto caricato il percorso è quello del progetto - prjFileInfo = QFileInfo(QgsProject.instance().fileName()) - path = prjFileInfo.absolutePath() - if len(path) == 0: - # se non esiste un progetto caricato uso il percorso di installazione di qad - path = QDir.cleanPath(QgsApplication.qgisSettingsDirPath() + "python/plugins/qad") - return path + "/" + fileName - - - #============================================================================ - # load - #============================================================================ - def load(self, path): - """ - Carica le impostazioni dello stile di quotatura da un file. - """ - if path is None or path == "": - return False - - if os.path.dirname(path) == "": # path contiene solo il nome del file (senza dir) - _path = self.getDefaultDimFilePath(path) - else: - _path = path - - if not os.path.exists(_path): - return False - - config = qad_utils.QadRawConfigParser(allow_no_value=True) - config.readfp(codecs.open(_path, "r", "utf-8")) - #config.read(_path) - - self.name = config.get("dimension_options", "name") - self.description = config.get("dimension_options", "description") - self.dimType = config.get("dimension_options", "dimType") - - # testo di quota - self.textPrefix = config.get("dimension_options", "textPrefix") - self.textSuffix = config.get("dimension_options", "textSuffix") - self.textSuppressLeadingZeros = config.getboolean("dimension_options", "textSuppressLeadingZeros") - self.textDecimalZerosSuppression = config.getboolean("dimension_options", "textDecimalZerosSuppression") - self.textHeight = config.getfloat("dimension_options", "textHeight") - self.textVerticalPos = config.getint("dimension_options", "textVerticalPos") - self.textHorizontalPos = config.getint("dimension_options", "textHorizontalPos") - self.textOffsetDist = config.getfloat("dimension_options", "textOffsetDist") - self.textRotMode = config.getint("dimension_options", "textRotMode") - self.textForcedRot = config.getfloat("dimension_options", "textForcedRot") - self.textDecimals = config.getint("dimension_options", "textDecimals") - self.textDecimalSep = config.get("dimension_options", "textDecimalSep") - self.textFont = config.get("dimension_options", "textFont") - self.textColor = config.get("dimension_options", "textColor") - self.textDirection = config.getint("dimension_options", "textDirection") - self.arcSymbPos = config.getint("dimension_options", "arcSymbPos") - - # linee di quota - self.dimLine1Show = config.getboolean("dimension_options", "dimLine1Show") - self.dimLine2Show = config.getboolean("dimension_options", "dimLine2Show") - self.dimLineLineType = config.get("dimension_options", "dimLineLineType") - self.dimLineColor = config.get("dimension_options", "dimLineColor") - self.dimLineSpaceOffset = config.getfloat("dimension_options", "dimLineSpaceOffset") - - # simboli per linee di quota - self.block1Name = config.get("dimension_options", "block1Name") - self.block2Name = config.get("dimension_options", "block2Name") - self.blockLeaderName = config.get("dimension_options", "blockLeaderName") - self.blockWidth = config.getfloat("dimension_options", "blockWidth") - self.blockScale = config.getfloat("dimension_options", "blockScale") - self.blockSuppressionForNoSpace = config.getboolean("dimension_options", "blockSuppressionForNoSpace") - self.centerMarkSize = config.getfloat("dimension_options", "centerMarkSize") - - # adattamento del testo e delle frecce - self.textBlockAdjust = config.getint("dimension_options", "textBlockAdjust") - - # linee di estensione - self.extLine1Show = config.getboolean("dimension_options", "extLine1Show") - self.extLine2Show = config.getboolean("dimension_options", "extLine2Show") - self.extLine1LineType = config.get("dimension_options", "extLine1LineType") - self.extLine2LineType = config.get("dimension_options", "extLine2LineType") - self.extLineColor = config.get("dimension_options", "extLineColor") - self.extLineOffsetDimLine = config.getfloat("dimension_options", "extLineOffsetDimLine") - self.extLineOffsetOrigPoints = config.getfloat("dimension_options", "extLineOffsetOrigPoints") - self.extLineIsFixedLen = config.getboolean("dimension_options", "extLineIsFixedLen") - self.extLineFixedLen = config.getfloat("dimension_options", "extLineFixedLen") - - # layer e loro caratteristiche - self.textualLayerName = config.get("dimension_options", "textualLayerName") - self.linearLayerName = config.get("dimension_options", "linearLayerName") - self.symbolLayerName = config.get("dimension_options", "symbolLayerName") - - self.componentFieldName = config.get("dimension_options", "componentFieldName") - self.symbolFieldName = config.get("dimension_options", "symbolFieldName") - self.lineTypeFieldName = config.get("dimension_options", "lineTypeFieldName") - self.colorFieldName = config.get("dimension_options", "colorFieldName") - self.idFieldName = config.get("dimension_options", "idFieldName") - self.idParentFieldName = config.get("dimension_options", "idParentFieldName") - self.dimStyleFieldName = config.get("dimension_options", "dimStyleFieldName") - self.dimTypeFieldName = config.get("dimension_options", "dimTypeFieldName") - self.scaleFieldName = config.get("dimension_options", "scaleFieldName") - self.rotFieldName = config.get("dimension_options", "rotFieldName") - - self.path = _path - - return True - - - #============================================================================ - # remove - #============================================================================ - def remove(self): - """ - Cancella il file delle impostazioni dello stile di quotatura. - """ - currDimStyleName = QadVariables.get(QadMsg.translate("Environment variables", "DIMSTYLE")) - if self.name == currDimStyleName: # lo stile da cancellare è quello corrente - return False - - if self.path is not None and self.path != "": - if os.path.exists(self.path): - try: - os.remove(self.path) - except: - return False - - return True - - #============================================================================ - # rename - #============================================================================ - def rename(self, newName): - """ - Rinomina il nome dello stile e del file delle impostazioni dello stile di quotatura. - """ - if newName == self.name: # nome uguale - return True - oldName = self.name - - if self.path is not None or self.path != "": - if os.path.exists(self.path): - try: - dir, base = os.path.split(self.path) - dir = QDir.cleanPath(dir) + "/" - - name, ext = os.path.splitext(base) - newPath = dir + "/" + newName + ext - - os.rename(self.path, newPath) - self.path = newPath - self.name = newName - self.save() - except: - return False - else: - self.name = newName - - currDimStyleName = QadVariables.get(QadMsg.translate("Environment variables", "DIMSTYLE")) - if oldName == currDimStyleName: # lo stile da rinominare è quello corrente - QadVariables.set(QadMsg.translate("Environment variables", "DIMSTYLE"), newName) - - self.name = newName - return True - - - #============================================================================ - # getInValidErrMsg - #============================================================================ - def getInValidErrMsg(self): - """ - Verifica se lo stile di quotatura é invalido e in caso affermativo ritorna il messaggio di errore. - Se la quotatura é valida ritorna None. - """ - prefix = QadMsg.translate("Dimension", "\nThe dimension style \"{0}\" ").format(self.name) - - if self.getTextualLayer() is None: - return prefix + QadMsg.translate("Dimension", "has not the textual layer for dimension.\n") - if qad_layer.isTextLayer(self.getTextualLayer()) == False: - errMsg = prefix + QadMsg.translate("Dimension", "has the textual layer for dimension which is not a textual layer.") - errMsg = errMsg + QadMsg.translate("QAD", "\nA textual layer is a vectorial punctual layer having a label and the symbol transparency no more than 10%.\n") - return errMsg - - if self.getSymbolLayer() is None: - return prefix + QadMsg.translate("Dimension", "has not the symbol layer for dimension.\n") - if qad_layer.isSymbolLayer(self.getSymbolLayer()) == False: - errMsg = prefix + QadMsg.translate("Dimension", "has the symbol layer for dimension which is not a symbol layer.") - errMsg = errMsg + QadMsg.translate("QAD", "\nA symbol layer is a vectorial punctual layer without label.\n") - return errMsg - - if self.getLinearLayer() is None: - return prefix + QadMsg.translate("Dimension", "has not the linear layer for dimension.\n") - # deve essere un VectorLayer di tipo linea - if (self.getLinearLayer().type() != QgsMapLayer.VectorLayer) or (self.getLinearLayer().geometryType() != QGis.Line): - errMsg = prefix + QadMsg.translate("Dimension", "has the linear layer for dimension which is not a linear layer.") - return errMsg - # i layer devono avere lo stesso sistema di coordinate - if not (self.getTextualLayer().crs() == self.getLinearLayer().crs() and self.getLinearLayer().crs() == self.getSymbolLayer().crs()): - errMsg = prefix + QadMsg.translate("Dimension", "has not the layers with the same coordinate reference system.") - return errMsg - - return None - - - #=============================================================================== - # getNotGraphEditableErrMsg - #=============================================================================== - def getNotGraphEditableErrMsg(self): - """ - Verifica se i layer dello stile di quotatura sono in sola lettura e in caso affermativo ritorna il messaggio di errore. - Se i layer dello stile di quotatura sono modificabili ritorna None. - """ - prefix = QadMsg.translate("Dimension", "\nThe dimension style \"{0}\" ").format(self.name) - - provider = self.getTextualLayer().dataProvider() - if not (provider.capabilities() & QgsVectorDataProvider.EditingCapabilities): - return prefix + QadMsg.translate("Dimension", "has the textual layer not editable.\n") - if not self.getTextualLayer().isEditable(): - return prefix + QadMsg.translate("Dimension", "has the textual layer not editable.\n") - - provider = self.getSymbolLayer().dataProvider() - if not (provider.capabilities() & QgsVectorDataProvider.EditingCapabilities): - return prefix + QadMsg.translate("Dimension", "has the symbol layer not editable.\n") - if not self.getSymbolLayer().isEditable(): - return prefix + QadMsg.translate("Dimension", "has the symbol layer not editable.\n") - - provider = self.getLinearLayer().dataProvider() - if not (provider.capabilities() & QgsVectorDataProvider.EditingCapabilities): - return prefix + QadMsg.translate("Dimension", "has the linear layer not editable.\n") - if not self.getLinearLayer().isEditable(): - return prefix + QadMsg.translate("Dimension", "has the linear layer not editable.\n") - - return None - - - #============================================================================ - # adjustLineAccordingTextRect - #============================================================================ - def adjustLineAccordingTextRect(self, textRect, pt1, pt2, textLinearDimComponentOn): - """ - Data una linea (pt1-pt2), che tipo di componente di quota rappresenta (textLinearDimComponentOn) - e un rettangolo che rappresenta l'occupazione del testo di quota, la funzione restituisce - due linee (possono essere None) in modo che il testo non si sovrapponga alla linea e che le - impostazioni di quota siano rispettate (dimLine1Show, dimLine2Show, extLine1Show, extLine2Show) - """ - line1 = None - line2 = None - intPts = self.getIntersectionPtsBetweenTextRectAndLine(textRect, pt1, pt2) - if textLinearDimComponentOn == QadDimComponentEnum.DIM_LINE1: # linea di quota ("Dimension line") - if len(intPts) == 2: # il rettangolo é sulla linea - if self.dimLine1Show: - line1 = [pt1, intPts[0]] - if self.dimLine2Show: - line2 = [intPts[1], pt2] - else: # il rettangolo non é sulla linea - if self.dimLine1Show and self.dimLine2Show: - line1 = [pt1, pt2] - else: - space1, space2 = self.getSpaceForBlock1AndBlock2(textRect, pt1, pt2) - rot = qad_utils.getAngleBy2Pts(pt1, pt2) # angolo della linea di quota - intPt1 = qad_utils.getPolarPointByPtAngle(pt1, rot, space1) - intPt2 = qad_utils.getPolarPointByPtAngle(pt2, rot - math.pi, space2) - - if self.dimLine1Show: - line1 = [pt1, intPt2] - elif self.dimLine2Show: - line2 = [pt2, intPt1] - elif textLinearDimComponentOn == QadDimComponentEnum.EXT_LINE1: # prima linea di estensione ("Extension line 1") - if self.extLine1Show: - if len(intPts) > 0: - line1 = [pt1, intPts[0]] - else: - line1 = [pt1, pt2] - elif textLinearDimComponentOn == QadDimComponentEnum.EXT_LINE2: # seconda linea di estensione ("Extension line 2") - if self.extLine2Show: - if len(intPts) > 0: - line1 = [pt1, intPts[0]] - else: - line1 = [pt1, pt2] - elif textLinearDimComponentOn == QadDimComponentEnum.LEADER_LINE: # linea porta quota usata quando il testo é fuori dalla quota ("Leader") - if len(intPts) > 0: - line1 = [pt1, intPts[0]] - else: - line1 = [pt1, pt2] - - return line1, line2 - - - #============================================================================ - # setDimId - #============================================================================ - def setDimId(self, dimId, features, parentId = False): - """ - Setta tutte le feature passate nella lista con il codice della quota. - """ - fieldName = self.idParentFieldName if parentId else self.idFieldName - - if len(fieldName) == 0: - return True - - i = 0 - tot = len(features) - while i < tot: - try: - f = features[i] - if f is not None: - # imposto il codice della quota - f.setAttribute(fieldName, dimId) - except: - return False - i = i + 1 - return True - - - #============================================================================ - # recodeDimIdOnFeatures - #============================================================================ - def recodeDimIdOnFeatures(self, oldDimId, newDimId, features, parentId = False): - """ - Cerca tutte le feature passate nella lista con il codice della - quota oldDimId e le ricodifica con newDimId. - """ - fieldName = self.idParentFieldName if parentId else self.idFieldName - - if len(fieldName) == 0: - return True - - i = 0 - tot = len(features) - while i < tot: - try: - f = features[i] - if f is not None: - if f.attribute(fieldName) == oldDimId: - # imposto il codice della quota - f.setAttribute(fieldName, newDimId) - except: - return False - i = i + 1 - return True - - - def textCommitChangesOnSave(self): - """ - Salva i testi delle quote per ottenere i nuovi ID - e richiamare updateTextReferencesOnSave tramite il segnale committedFeaturesAdded. - """ - # salvo i testi per avere la codifica definitiva - if self.getTextualLayer() is not None: - return self.getTextualLayer().commitChanges() - else: - return False - - - #============================================================================ - # updateTextReferencesOnSave - #============================================================================ - def updateTextReferencesOnSave(self, plugIn, textAddedFeatures): - """ - Aggiorna e salva i reference delle features dello stile di quotatura contenuti in textAddedFeatures. - """ - if self.startEditing() == False: - return False - - plugIn.beginEditCommand("Dimension recoded", [self.getSymbolLayer(), self.getLinearLayer(), self.getTextualLayer()]) - - entity = QadEntity() - for f in textAddedFeatures: - entity.set(self.getTextualLayer(), f.id()) - oldDimId = entity.getAttribute(self.idFieldName) - newDimId = f.id() - if oldDimId is None or self.recodeDimId(plugIn, oldDimId, newDimId) == False: - return False - - plugIn.endEditCommand() - - return True - - - #============================================================================ - # startEditing - #============================================================================ - def startEditing(self): - if self.getTextualLayer() is not None and self.getTextualLayer().isEditable() == False: - if self.getTextualLayer().startEditing() == False: - return False - if self.getLinearLayer() is not None and self.getLinearLayer().isEditable() == False: - if self.getLinearLayer().startEditing() == False: - return False - if self.getSymbolLayer() is not None and self.getSymbolLayer().isEditable() == False: - if self.getSymbolLayer().startEditing() == False: - return False - - - #============================================================================ - # commitChanges - #============================================================================ - def commitChanges(self, excludedLayer): - if self.startEditing() == False: - return False - - if (excludedLayer is None) or excludedLayer.id() != self.getTextualLayer().id(): - # salvo le entità testuali - self.getTextualLayer().commitChanges() - if (excludedLayer is None) or excludedLayer.id() != self.getLinearLayer().id(): - # salvo le entità lineari - self.getLinearLayer().commitChanges() - if (excludedLayer is None) or excludedLayer.id() != self.getSymbolLayer().id(): - # salvo le entità puntuali - self.getSymbolLayer().commitChanges() - - - #============================================================================ - # recodeDimId - #============================================================================ - def getEntitySet(self, dimId): - """ - Ricava un QadEntitySet con tutte le feature della quota dimId. - """ - result = QadEntitySet() - if len(self.idFieldName) == 0 or len(self.idParentFieldName) == 0: - return result - - layerEntitySet = QadLayerEntitySet() - - # ricerco l'entità testo - expression = "\"" + self.idFieldName + "\"=" + str(dimId) - featureIter = self.getTextualLayer().getFeatures(QgsFeatureRequest().setFilterExpression(expression)) - layerEntitySet.set(self.getTextualLayer()) - layerEntitySet.addFeatures(featureIter) - result.addLayerEntitySet(layerEntitySet) - - expression = "\"" + self.idParentFieldName + "\"=" + str(dimId) - - # ricerco le entità linea - layerEntitySet.clear() - featureIter = self.getLinearLayer().getFeatures(QgsFeatureRequest().setFilterExpression(expression)) - layerEntitySet.set(self.getLinearLayer()) - layerEntitySet.addFeatures(featureIter) - result.addLayerEntitySet(layerEntitySet) - - # ricerco e setto id_parent per le entità puntuali - layerEntitySet.clear() - featureIter = self.getSymbolLayer().getFeatures(QgsFeatureRequest().setFilterExpression(expression)) - layerEntitySet.set(self.getSymbolLayer()) - layerEntitySet.addFeatures(featureIter) - result.addLayerEntitySet(layerEntitySet) - - return result - - - #============================================================================ - # recodeDimId - #============================================================================ - def recodeDimId(self, plugIn, oldDimId, newDimId): - """ - Ricodifica tutte le feature della quota oldDimId con il nuovo codice newDimId. - """ - if len(self.idFieldName) == 0 or len(self.idParentFieldName) == 0: - return True - - entitySet = self.getEntitySet(oldDimId) - - # setto l'entità testo - layerEntitySet = entitySet.findLayerEntitySet(self.getTextualLayer()) - if layerEntitySet is not None: - features = layerEntitySet.getFeatureCollection() - if self.setDimId(newDimId, features, False) == False: - return False - # plugIn, layer, features, refresh, check_validity - if qad_layer.updateFeaturesToLayer(plugIn, self.getTextualLayer(), features, False, False) == False: - return False - - # setto id_parent per le entità linea - layerEntitySet = entitySet.findLayerEntitySet(self.getLinearLayer()) - if layerEntitySet is not None: - features = layerEntitySet.getFeatureCollection() - if self.setDimId(newDimId, features, True) == False: - return False - # plugIn, layer, features, refresh, check_validity - if qad_layer.updateFeaturesToLayer(plugIn, self.getLinearLayer(), features, False, False) == False: - return False - - # setto id_parent per le entità puntuali - layerEntitySet = entitySet.findLayerEntitySet(self.getSymbolLayer()) - if layerEntitySet is not None: - features = layerEntitySet.getFeatureCollection() - if self.setDimId(newDimId, features, True) == False: - return False - # plugIn, layer, features, refresh, check_validity - if qad_layer.updateFeaturesToLayer(plugIn, self.getSymbolLayer(), features, False, False) == False: - return False - - return True - - - #============================================================================ - # getDimIdByEntity - #============================================================================ - def getDimIdByEntity(self, entity): - """ - La funzione, data un'entità, verifica se fa parte dello stile di quotatura e, - in caso di successo, restituisce il codice della quotatura altrimenti None. - In più, la funzione, setta il tipo di quotatura se é possibile. - """ - if entity.layer.name() == self.textualLayerName: - dimId = entity.getAttribute(self.idFieldName) - if dimId is None: - return None - f = entity.getFeature() - elif entity.layer.name() == self.linearLayerName or \ - entity.layer.name() == self.symbolLayerName: - dimId = entity.getAttribute(self.idParentFieldName) - if dimId is None: - return None - # ricerco l'entità testo - expression = "\"" + self.idFieldName + "\"=" + str(dimId) - f = QgsFeature() - if self.getTextualLayer().getFeatures(QgsFeatureRequest().setFilterExpression(expression)).nextFeature(f) == False: - return None - else: - return None - - try: - # leggo il nome dello stile di quotatura - dimName = f.attribute(self.dimStyleFieldName) - if dimName != self.name: - return None - except: - return None - - try: - # leggo il tipo dello stile di quotatura - self.dimType = f.attribute(self.dimTypeFieldName) - except: - pass - - return dimId - - - #============================================================================ - # isDimLayer - #============================================================================ - def isDimLayer(self, layer): - """ - La funzione, dato un layer, verifica se fa parte dello stile di quotatura. - """ - if layer.name() == self.textualLayerName or \ - layer.name() == self.linearLayerName or \ - layer.name() == self.symbolLayerName: - return True - else: - return False - - - #============================================================================ - # getFilteredFeatureCollection - #============================================================================ - def getFilteredFeatureCollection(self, layerEntitySet): - """ - La funzione, dato un QadLayerEntitySet, filtra e restituisce solo quelle appartenenti allo stile di quotatura. - """ - result = [] - entity = QadEntity() - for f in layerEntitySet.getFeatureCollection(): - entity.set(layerEntitySet.layer, f.id()) - if self.getDimIdByEntity(entity) is not None: - result.append(f) - - return result - - - #============================================================================ - # FUNZIONI PER I BLOCCHI - INIZIO - #============================================================================ - - - #============================================================================ - # getBlock1Size - #============================================================================ - def getBlock1Size(self): - """ - Restituisce la dimensione del blocco 1 delle frecce in unità di mappa. - """ - return 0 if self.block1Name == "" else self.blockWidth * self.blockScale - - - #============================================================================ - # getBlock2Size - #============================================================================ - def getBlock2Size(self): - """ - Restituisce la dimensione del blocco 2 delle frecce in unità di mappa. - """ - # blockWidth = larghezza del simbolo (in orizzontale) quando la dimensione in unità di mappa = 1 (vedi "triangle2") - # blockScale = scala della dimensione del simbolo (DIMASZ) - return 0 if self.block2Name == "" else self.blockWidth * self.blockScale - - - #============================================================================ - # getBlocksRot - #============================================================================ - def getBlocksRot(self, dimLinePt1, dimLinePt2, inside): - """ - Restituisce una lista di 2 elementi che descrivono le rotazioni dei due blocchi: - - il primo elemento é la rotazione del blocco 1 - - il secondo elemento é la rotazione del blocco 2 - - dimLinePt1 = primo punto della linea di quota (QgsPoint) - dimLinePt2 = secondo punto della linea di quota (QgsPoint) - inside = flag di modo, se = true le frecce sono interne altrimenti sono esterne - """ - rot = qad_utils.getAngleBy2Pts(dimLinePt1, dimLinePt2) # angolo della linea di quota - if inside: - rot1 = rot + math.pi - rot2 = rot - else: - rot1 = rot - rot2 = rot + math.pi - - return qad_utils.normalizeAngle(rot1), qad_utils.normalizeAngle(rot2) - - - #============================================================================ - # getSpaceForBlock1AndBlock2 - #============================================================================ - def getSpaceForBlock1AndBlock2Auxiliary(self, dimLinePt1, dimLinePt2, rectCorner): - # calcolo la proiezione di un vertice del rettangolo sulla linea dimLinePt1, dimLinePt2 - perpPt = qad_utils.getPerpendicularPointOnInfinityLine(dimLinePt1, dimLinePt2, rectCorner) - # se la proienzione non é nel segmento - if qad_utils.isPtOnSegment(dimLinePt1, dimLinePt2, perpPt) == False: - # se la proiezione ricade oltre il punto dimLinePt1 - if qad_utils.getDistance(dimLinePt1, perpPt) < qad_utils.getDistance(dimLinePt2, perpPt): - return 0, qad_utils.getDistance(dimLinePt1, dimLinePt2) - else: # se la proiezione ricade oltre il punto dimLinePt2 - return qad_utils.getDistance(dimLinePt1, dimLinePt2), 0 - else: - return qad_utils.getDistance(dimLinePt1, perpPt), qad_utils.getDistance(dimLinePt2, perpPt) - - def getSpaceForBlock1AndBlock2(self, txtRect, dimLinePt1, dimLinePt2): - """ - txtRect = rettangolo di occupazione del testo o None se non c'é il testo - dimLinePt1 = primo punto della linea di quotatura - dimLinePt2 = primo punto della linea di quotatura - Restituisce lo spazio disponibile per i blocchi 1 e 2 considerando il rettangolo (QadLinearObjectList) che rappresenta il testo - e la linea di quota dimLinePt1-dimLinePt2. - """ - if txtRect is None: # se non c'é il testo (é stato spostato fuori dalla linea di quota) - spaceForBlock1 = qad_utils.getDistance(dimLinePt1, dimLinePt2) / 2 - spaceForBlock2 = spaceForBlock1 - else: - # calcolo la proiezione dei quattro vertici del rettangolo sulla linea dimLinePt1, dimLinePt2 - linearObject = txtRect.getLinearObjectAt(0) - partial1SpaceForBlock1, partial1SpaceForBlock2 = self.getSpaceForBlock1AndBlock2Auxiliary(dimLinePt1, dimLinePt2, \ - linearObject.getStartPt()) - linearObject = txtRect.getLinearObjectAt(1) - partial2SpaceForBlock1, partial2SpaceForBlock2 = self.getSpaceForBlock1AndBlock2Auxiliary(dimLinePt1, dimLinePt2, \ - linearObject.getStartPt()) - spaceForBlock1 = partial1SpaceForBlock1 if partial1SpaceForBlock1 < partial2SpaceForBlock1 else partial2SpaceForBlock1 - spaceForBlock2 = partial1SpaceForBlock2 if partial1SpaceForBlock2 < partial2SpaceForBlock2 else partial2SpaceForBlock2 - - linearObject = txtRect.getLinearObjectAt(2) - partial3SpaceForBlock1, partial3SpaceForBlock2 = self.getSpaceForBlock1AndBlock2Auxiliary(dimLinePt1, dimLinePt2, \ - linearObject.getStartPt()) - if partial3SpaceForBlock1 < spaceForBlock1: - spaceForBlock1 = partial3SpaceForBlock1 - if partial3SpaceForBlock2 < spaceForBlock2: - spaceForBlock2 = partial3SpaceForBlock2 - - linearObject = txtRect.getLinearObjectAt(3) - partial4SpaceForBlock1, partial4SpaceForBlock2 = self.getSpaceForBlock1AndBlock2Auxiliary(dimLinePt1, dimLinePt2, \ - linearObject.getStartPt()) - if partial4SpaceForBlock1 < spaceForBlock1: - spaceForBlock1 = partial4SpaceForBlock1 - if partial4SpaceForBlock2 < spaceForBlock2: - spaceForBlock2 = partial4SpaceForBlock2 - - return spaceForBlock1, spaceForBlock2 - - - #============================================================================ - # getSymbolFeature - #============================================================================ - def getSymbolFeature(self, insPt, rot, isBlock1, textLinearDimComponentOn, sourceCrs = None): - """ - Restituisce la feature per il simbolo delle frecce. - insPt = punto di inserimento - rot = rotazione espressa in radianti - isBlock1 = se True si tratta del blocco1 altrimenti del blocco2 - textLinearDimComponentOn = indica il componente della quota dove é situato il testo di quota (QadDimComponentEnum) - sourceCrs = sistema di coordinate di insPt - """ - # se non c'é il simbolo di quota - if insPt is None or rot is None: - return None - # se si tratta del simbolo 1 - if isBlock1 == True: - # se non deve essere mostrata la linea 1 di quota (vale solo se il testo é sulla linea di quota) - if self.dimLine1Show == False and \ - (textLinearDimComponentOn == QadDimComponentEnum.DIM_LINE1 or textLinearDimComponentOn == QadDimComponentEnum.DIM_LINE2): - return None - else: # se si tratta del simbolo 2 - # se non deve essere mostrata la linea 2 di quota (vale solo se il testo é sulla linea di quota) - if self.dimLine2Show == False and \ - (textLinearDimComponentOn == QadDimComponentEnum.DIM_LINE1 or textLinearDimComponentOn == QadDimComponentEnum.DIM_LINE2): - return None - - f = QgsFeature(self.getSymbolFeaturePrototype()) - g = QgsGeometry.fromPoint(insPt) - - if (sourceCrs is not None) and sourceCrs != self.getSymbolLayer().crs(): - coordTransform = QgsCoordinateTransform(sourceCrs, self.getSymbolLayer().crs()) # trasformo la geometria - g.transform(coordTransform) - - f.setGeometry(g) - - # imposto la scala del blocco - try: - if len(self.scaleFieldName) > 0: - f.setAttribute(self.scaleFieldName, self.blockScale) - except: - pass - - # imposto la rotazione - try: - if len(self.rotFieldName) > 0: - f.setAttribute(self.rotFieldName, qad_utils.toDegrees(rot)) # Converte da radianti a gradi - except: - pass - - # imposto il colore - try: - if len(self.colorFieldName) > 0: - f.setAttribute(self.colorFieldName, self.dimLineColor) - except: - pass - - # imposto il tipo di componente della quotatura - if self.dimType == QadDimTypeEnum.RADIUS: # se quotatura tipo raggio - try: - if len(self.componentFieldName) > 0: - f.setAttribute(self.componentFieldName, QadDimComponentEnum.LEADER_BLOCK) - except: - pass - - try: - if len(self.symbolFieldName) > 0: - f.setAttribute(self.symbolFieldName, self.blockLeaderName) - except: - pass - else: - try: - if len(self.componentFieldName) > 0: - f.setAttribute(self.componentFieldName, QadDimComponentEnum.BLOCK1 if isBlock1 else QadDimComponentEnum.BLOCK2) - except: - pass - - try: - if len(self.symbolFieldName) > 0: - f.setAttribute(self.symbolFieldName, self.block1Name if isBlock1 else self.block2Name) - except: - pass - - return f - - - #============================================================================ - # getDimPointFeature - #============================================================================ - def getDimPointFeature(self, insPt, isDimPt1, sourceCrs): - """ - Restituisce la feature per il punto di quotatura. - insPt = punto di inserimento - isDimPt1 = se True si tratta del punto di quotatura 1 altrimenti del punto di quotatura 2 - sourceCrs = sistema di coordinate di insPt - """ - symbolFeaturePrototype = self.getSymbolFeaturePrototype() - if symbolFeaturePrototype is None: - return None - f = QgsFeature(symbolFeaturePrototype) - g = QgsGeometry.fromPoint(insPt) - - if (sourceCrs is not None) and sourceCrs != self.getSymbolLayer().crs(): - coordTransform = QgsCoordinateTransform(sourceCrs, self.getSymbolLayer().crs()) # trasformo la geometria - g.transform(coordTransform) - - f.setGeometry(g) - - # imposto il tipo di componente della quotatura - try: - f.setAttribute(self.componentFieldName, QadDimComponentEnum.DIM_PT1 if isDimPt1 else QadDimComponentEnum.DIM_PT2) - except: - pass - - try: - # imposto il colore - if len(self.colorFieldName) > 0: - f.setAttribute(self.colorFieldName, self.dimLineColor) - except: - pass - - return f - - - #============================================================================ - # FUNZIONI PER I BLOCCHI - FINE - # FUNZIONI PER IL TESTO - INIZIO - #============================================================================ - - - #============================================================================ - # getFormattedText - #============================================================================ - def getFormattedText(self, measure): - """ - Restituisce il testo della misura della quota formattato - """ - if type(measure) == int or type(measure) == float: - strIntPart, strDecPart = qad_utils.getStrIntDecParts(round(measure, self.textDecimals)) # numero di decimali - - if strIntPart == "0" and self.textSuppressLeadingZeros == True: # per sopprimere o meno gli zero all'inizio del testo - strIntPart = "" - - for i in xrange(0, self.textDecimals - len(strDecPart), 1): # aggiunge "0" per arrivare al numero di decimali - strDecPart = strDecPart + "0" - - if self.textDecimalZerosSuppression == True: # per sopprimere gli zero finali nei decimali - strDecPart = strDecPart.rstrip("0") - - formattedText = "-" if measure < 0 else "" # segno - formattedText = formattedText + strIntPart # parte intera - if len(strDecPart) > 0: # parte decimale - formattedText = formattedText + self.textDecimalSep + strDecPart # Separatore dei decimali - # aggiungo prefisso e suffisso per il testo della quota - return self.textPrefix + formattedText + self.textSuffix - elif type(measure) == unicode or type(measure) == str: - return measure - else: - return "" - - - #============================================================================ - # getNumericText - #============================================================================ - def getNumericText(self, text): - """ - Restituisce il valore numerico del testo della misura della quota formattato - """ - textToConvert = text.lstrip(self.textPrefix) - textToConvert = textToConvert.rstrip(self.textSuffix) - textToConvert = textToConvert.replace(self.textDecimalSep, ".") - - return qad_utils.str2float(textToConvert) - - - #============================================================================ - # textRectToQadLinearObjectList - #============================================================================ - def textRectToQadLinearObjectList(self, ptBottomLeft, textWidth, textHeight, rot): - """ - Restituisce il rettangolo che rappresenta il testo sotto forma di una QadLinearObjectList. - <2>----width----<3> - | | - height height - | | - <1>----width----<4> - """ - pt2 = qad_utils.getPolarPointByPtAngle(ptBottomLeft, rot + (math.pi / 2), textHeight) - pt3 = qad_utils.getPolarPointByPtAngle(pt2, rot, textWidth) - pt4 = qad_utils.getPolarPointByPtAngle(ptBottomLeft, rot , textWidth) - res = qad_utils.QadLinearObjectList() - res.fromPolyline([ptBottomLeft, pt2, pt3, pt4, ptBottomLeft]) - return res - - - #============================================================================ - # getBoundingPointsTextRectProjectedToLine - #============================================================================ - def getBoundingPointsTextRectProjectedToLine(self, pt1, pt2, textRect): - """ - Restituisce una lista di 2 punti che sono i punti estremi della proiezione dei 4 angoli del rettangolo - sulla linea pt1-pt2. - """ - rectCorners = textRect.asPolyline() - # calcolo la proiezione degli angoli del rettangolo sulla linea pt1-pt2 - perpPts = [] - - qad_utils.appendUniquePointToList(perpPts, qad_utils.getPerpendicularPointOnInfinityLine(pt1, pt2, rectCorners[0])) - qad_utils.appendUniquePointToList(perpPts, qad_utils.getPerpendicularPointOnInfinityLine(pt1, pt2, rectCorners[1])) - qad_utils.appendUniquePointToList(perpPts, qad_utils.getPerpendicularPointOnInfinityLine(pt1, pt2, rectCorners[2])) - qad_utils.appendUniquePointToList(perpPts, qad_utils.getPerpendicularPointOnInfinityLine(pt1, pt2, rectCorners[3])) - - return qad_utils.getBoundingPtsOnOnInfinityLine(pt1, pt2, perpPts) - - - #============================================================================ - # getIntersectionPtsBetweenTextRectAndLine - #============================================================================ - def getIntersectionPtsBetweenTextRectAndLine(self, rect, pt1, pt2): - """ - Restituisce i punti di intersezione tra il rettangolo (QadLinearObjectList) che rappresenta il testo - e un segmento pt1-pt2. La lista é ordinata per distanza da pt1. - """ - segment = qad_utils.QadLinearObject([pt1, pt2]) - return rect.getIntersectionPtsWithLinearObject(segment, True)[0] # orderByStartPtOfPart = True - - - #============================================================================ - # getTextPositionOnLine - #============================================================================ - def getTextPositionOnLine(self, pt1, pt2, textWidth, textHeight, horizontalPos, verticalPos, rotMode): - """ - pt1 = primo punto della linea - pt2 = secondo punto della linea - textWidth = larghezza testo - textHeight = altezza testo - - Restituisce il punto di inserimento e la rotazione del testo lungo la linea pt1-pt2 con le modalità: - horizontalPos = QadDimStyleTxtHorizontalPosEnum.CENTERED_LINE (centrato alla linea) - QadDimStyleTxtHorizontalPosEnum.FIRST_EXT_LINE (vicino al punto pt1) - QadDimStyleTxtHorizontalPosEnum.SECOND_EXT_LINE (vicino al punto pt2) - verticalPos = QadDimStyleTxtVerticalPosEnum.CENTERED_LINE (centrato alla linea) - QadDimStyleTxtVerticalPosEnum.ABOVE_LINE (sopra alla linea) - QadDimStyleTxtVerticalPosEnum.BELOW_LINE (sotto alla linea) - rotMode = QadDimStyleTxtRotModeEnum.HORIZONTAL (testo orizzontale) - QadDimStyleTxtRotModeEnum.ALIGNED_LINE (testo allineato con la linea) - QadDimStyleTxtRotModeEnum.FORCED_ROTATION (testo con rotazione forzata) - """ - lineRot = qad_utils.getAngleBy2Pts(pt1, pt2) # angolo della linea - - if (lineRot > math.pi * 3 / 2 and lineRot <= math.pi * 2) or \ - (lineRot >= 0 and lineRot <= math.pi / 2): # da sx a dx - textInsPtCloseToPt1 = True - else: # da dx a sx - textInsPtCloseToPt1 = False - - if rotMode == QadDimStyleTxtRotModeEnum.ALIGNED_LINE: # testo allineato alla linea - if lineRot > (math.pi / 2) and lineRot <= math.pi * 3 / 2: # se il testo é capovolto lo giro - textRot = lineRot - math.pi - else: - textRot = lineRot - - # allineamento orizzontale - #========================= - if horizontalPos == QadDimStyleTxtHorizontalPosEnum.CENTERED_LINE: # testo centrato alla linea - middlePt = qad_utils.getMiddlePoint(pt1, pt2) - if textInsPtCloseToPt1: # il punto di inserimento del testo é vicino a pt1 - insPt = qad_utils.getPolarPointByPtAngle(middlePt, lineRot - math.pi, textWidth / 2) - else: # il punto di inserimento del testo é vicino a pt2 - insPt = qad_utils.getPolarPointByPtAngle(middlePt, lineRot, textWidth / 2) - - elif horizontalPos == QadDimStyleTxtHorizontalPosEnum.FIRST_EXT_LINE: # testo vicino a pt1 - # uso 2 volte textOffsetDist perché una volta é la distanza dal punto pt1 + un offset intorno al testo - if textInsPtCloseToPt1: # il punto di inserimento del testo é vicino a pt1 - insPt = qad_utils.getPolarPointByPtAngle(pt1, lineRot, self.textOffsetDist + self.textOffsetDist) - else: # il punto di inserimento del testo é vicino a pt2 - insPt = qad_utils.getPolarPointByPtAngle(pt1, lineRot, textWidth + self.textOffsetDist + self.textOffsetDist) - - elif horizontalPos == QadDimStyleTxtHorizontalPosEnum.SECOND_EXT_LINE: # testo vicino a pt2 - # uso 2 volte textOffsetDist perché una volta é la distanza dal punto pt1 + un offset intorno al testo - lineLen = qad_utils.getDistance(pt1, pt2) - if textInsPtCloseToPt1: # il punto di inserimento del testo é vicino a pt1 - insPt = qad_utils.getPolarPointByPtAngle(pt1, lineRot, lineLen - textWidth - (self.textOffsetDist + self.textOffsetDist)) - else: # il punto di inserimento del testo é vicino a pt2 - insPt = qad_utils.getPolarPointByPtAngle(pt1, lineRot, lineLen - (self.textOffsetDist + self.textOffsetDist)) - - # allineamento verticale - #========================= - if verticalPos == QadDimStyleTxtVerticalPosEnum.CENTERED_LINE: # testo centrato alla linea - if textInsPtCloseToPt1: # il punto di inserimento del testo é vicino a pt1 - insPt = qad_utils.getPolarPointByPtAngle(insPt, lineRot - math.pi / 2, textHeight / 2) - else: # il punto di inserimento del testo é vicino a pt2 - insPt = qad_utils.getPolarPointByPtAngle(insPt, lineRot + math.pi / 2, textHeight / 2) - elif verticalPos == QadDimStyleTxtVerticalPosEnum.ABOVE_LINE: # sopra alla linea - # uso 2 volte textOffsetDist perché una volta é la distanza dalla linea + un offset intorno al testo - if textInsPtCloseToPt1: # il punto di inserimento del testo é vicino a pt1 - insPt = qad_utils.getPolarPointByPtAngle(insPt, lineRot + math.pi / 2, self.textOffsetDist + self.textOffsetDist) - else: # il punto di inserimento del testo é vicino a pt2 - insPt = qad_utils.getPolarPointByPtAngle(insPt, lineRot - math.pi / 2, self.textOffsetDist + self.textOffsetDist) - elif verticalPos == QadDimStyleTxtVerticalPosEnum.BELOW_LINE: # sotto alla linea - # uso 2 volte textOffsetDist perché una volta é la distanza dalla linea + un offset intorno al testo - if textInsPtCloseToPt1: # il punto di inserimento del testo é vicino a pt1 - insPt = qad_utils.getPolarPointByPtAngle(insPt, lineRot - math.pi / 2, textHeight + (self.textOffsetDist + self.textOffsetDist)) - else: # il punto di inserimento del testo é vicino a pt2 - insPt = qad_utils.getPolarPointByPtAngle(insPt, lineRot + math.pi / 2, textHeight + (self.textOffsetDist + self.textOffsetDist)) - - # testo orizzontale o testo con rotazione forzata - elif rotMode == QadDimStyleTxtRotModeEnum.HORIZONTAL or rotMode == QadDimStyleTxtRotModeEnum.FORCED_ROTATION: - - lineLen = qad_utils.getDistance(pt1, pt2) # lunghezza della linea - textRot = 0.0 if rotMode == QadDimStyleTxtRotModeEnum.HORIZONTAL else self.textForcedRot - - # cerco qual'é l'angolo del rettangolo più vicino alla linea - # <2>----width----<3> - # | | - # height height - # | | - # <1>----width----<4> - # ricavo il rettangolo che racchiude il testo e lo posiziono con il suo angolo in basso a sinistra sul punto pt1 - textRect = self.textRectToQadLinearObjectList(pt1, textWidth, textHeight, textRot) - # ottengo i punti estremi della proiezione del rettangolo sulla linea - pts = self.getBoundingPointsTextRectProjectedToLine(pt1, pt2, textRect) - projectedTextWidth = qad_utils.getDistance(pts[0], pts[1]) - - # allineamento orizzontale - #========================= - if horizontalPos == QadDimStyleTxtHorizontalPosEnum.CENTERED_LINE: # testo centrato alla linea - closestPtToPt1 = qad_utils.getPolarPointByPtAngle(pt1, lineRot, (lineLen - projectedTextWidth) / 2) - - elif horizontalPos == QadDimStyleTxtHorizontalPosEnum.FIRST_EXT_LINE: # testo vicino a pt1 - closestPtToPt1 = qad_utils.getPolarPointByPtAngle(pt1, lineRot, self.textOffsetDist) - - elif horizontalPos == QadDimStyleTxtHorizontalPosEnum.SECOND_EXT_LINE: # testo vicino a pt2 - closestPtToPt1 = qad_utils.getPolarPointByPtAngle(pt1, lineRot, lineLen - self.textOffsetDist - projectedTextWidth) - - # se la linea ha una angolo tra (0-90] gradi (primo quadrante) - if lineRot > 0 and lineRot <= math.pi / 2: - # il punto più vicino a pt1 corrisponde all'angolo in basso a sinistra del rettangolo che racchiude il testo - # mi ricavo il punto di inserimento del testo (angolo in basso a sinistra) - insPt = QgsPoint(closestPtToPt1) - textRect = self.textRectToQadLinearObjectList(insPt, textWidth, textHeight, textRot) - rectCorners = textRect.asPolyline() - - # allineamento verticale - #========================= - if verticalPos == QadDimStyleTxtVerticalPosEnum.ABOVE_LINE: # sopra alla linea - # l'angolo 4 deve essere sopra la linea distante self.textOffsetDist dalla stessa - rectPt = rectCorners[3] - elif verticalPos == QadDimStyleTxtVerticalPosEnum.BELOW_LINE: # sotto alla linea - # l'angolo 2 deve essere sotto la linea distante self.textOffsetDist dalla stessa - rectPt = rectCorners[1] - - # se la linea ha una angolo tra (90-180] gradi (secondo quadrante) - elif lineRot > math.pi / 2 and lineRot <= math.pi: - # il punto più vicino a pt1 corrisponde all'angolo in basso a destra del rettangolo che racchiude il testo - # mi ricavo il punto di inserimento del testo (angolo in basso a sinistra) - insPt = QgsPoint(closestPtToPt1.x() - textWidth, closestPtToPt1.y()) - textRect = self.textRectToQadLinearObjectList(insPt, textWidth, textHeight, textRot) - rectCorners = textRect.asPolyline() - - # allineamento verticale - #========================= - if verticalPos == QadDimStyleTxtVerticalPosEnum.ABOVE_LINE: # sopra alla linea - # l'angolo 1 deve essere sopra la linea distante self.textOffsetDist dalla stessa - rectPt = rectCorners[0] - elif verticalPos == QadDimStyleTxtVerticalPosEnum.BELOW_LINE: # sotto alla linea - # l'angolo 3 deve essere sotto la linea distante self.textOffsetDist dalla stessa - rectPt = rectCorners[2] - - # se la linea ha una angolo tra (180-270] gradi (terzo quadrante) - elif lineRot > math.pi and lineRot <= math.pi * 3 / 2: - # il punto più vicino a pt1 corrisponde all'angolo in alto a destra del rettangolo che racchiude il testo - # mi ricavo il punto di inserimento del testo (angolo in basso a sinistra) - insPt = QgsPoint(closestPtToPt1.x() - textWidth, closestPtToPt1.y() - textHeight) - textRect = self.textRectToQadLinearObjectList(insPt, textWidth, textHeight, textRot) - rectCorners = textRect.asPolyline() - - # allineamento verticale - #========================= - if verticalPos == QadDimStyleTxtVerticalPosEnum.ABOVE_LINE: # sopra alla linea - # l'angolo 4 deve essere sopra la linea distante self.textOffsetDist dalla stessa - rectPt = rectCorners[3] - elif verticalPos == QadDimStyleTxtVerticalPosEnum.BELOW_LINE: # sotto alla linea - # l'angolo 2 deve essere sotto la linea distante self.textOffsetDist dalla stessa - rectPt = rectCorners[1] - - # se la linea ha una angolo tra (270-360] gradi (quarto quadrante) - elif (lineRot > math.pi * 3 / 2 and lineRot <= 360) or lineRot == 0: - # il punto più vicino a pt1 corrisponde all'angolo in alto a destra del rettangolo che racchiude il testo - # mi ricavo il punto di inserimento del testo (angolo in alto a sinistra) - insPt = QgsPoint(closestPtToPt1.x(), closestPtToPt1.y() - textHeight) - textRect = self.textRectToQadLinearObjectList(insPt, textWidth, textHeight, textRot) - rectCorners = textRect.asPolyline() - - # allineamento verticale - #========================= - if verticalPos == QadDimStyleTxtVerticalPosEnum.ABOVE_LINE: # sopra alla linea - # l'angolo 1 deve essere sopra la linea distante self.textOffsetDist dalla stessa - rectPt = rectCorners[0] - elif verticalPos == QadDimStyleTxtVerticalPosEnum.BELOW_LINE: # sotto alla linea - # l'angolo 3 deve essere sotto la linea distante self.textOffsetDist dalla stessa - rectPt = rectCorners[2] - - # allineamento verticale - #========================= - if verticalPos == QadDimStyleTxtVerticalPosEnum.CENTERED_LINE: # testo centrato alla linea - # il centro del rettangolo deve essere sulla linea - centerPt = qad_utils.getPolarPointByPtAngle(rectCorners[0], \ - qad_utils.getAngleBy2Pts(rectCorners[0], rectCorners[2]), \ - qad_utils.getDistance(rectCorners[0], rectCorners[2]) / 2) - perpPt = qad_utils.getPerpendicularPointOnInfinityLine(pt1, pt2, centerPt) - offsetAngle = qad_utils.getAngleBy2Pts(centerPt, perpPt) - offsetDist = qad_utils.getDistance(centerPt, perpPt) - elif verticalPos == QadDimStyleTxtVerticalPosEnum.ABOVE_LINE: # sopra alla linea - # l'angolo deve essere sopra la linea distante self.textOffsetDist dalla stessa - perpPt = qad_utils.getPerpendicularPointOnInfinityLine(pt1, pt2, rectPt) - # se la linea ha una angolo tra (90-270] gradi - if lineRot > math.pi / 2 and lineRot <= math.pi * 3 / 2: - offsetAngle = lineRot - math.pi / 2 - else: # se la linea ha una angolo tra (270-90] gradi - offsetAngle = lineRot + math.pi / 2 - offsetDist = qad_utils.getDistance(rectPt, perpPt) + self.textOffsetDist - elif verticalPos == QadDimStyleTxtVerticalPosEnum.BELOW_LINE: # sotto alla linea - # l'angolo deve essere sotto la linea distante self.textOffsetDist dalla stessa - perpPt = qad_utils.getPerpendicularPointOnInfinityLine(pt1, pt2, rectPt) - # se la linea ha una angolo tra (90-270] gradi - if lineRot > math.pi / 2 and lineRot <= math.pi * 3 / 2: - offsetAngle = lineRot + math.pi / 2 - else: # se la linea ha una angolo tra (270-90] gradi - offsetAngle = lineRot - math.pi / 2 - offsetDist = qad_utils.getDistance(rectPt, perpPt) + self.textOffsetDist - - # traslo il rettangolo - insPt = qad_utils.getPolarPointByPtAngle(insPt, offsetAngle, offsetDist) - textRect = self.textRectToQadLinearObjectList(insPt, textWidth, textHeight, textRot) - - return insPt, textRot - - - #============================================================================ - # getTextPosAndLinesOutOfDimLines - #============================================================================ - def getTextPosAndLinesOutOfDimLines(self, dimLinePt1, dimLinePt2, textWidth, textHeight): - """ - Restituisce una lista di 3 elementi nel caso il testo venga spostato fuori dalle linee - di estensione perché era troppo grosso: - - il primo elemento é il punto di inserimento - - il secondo elemento é la rotazione del testo - - il terzo elemento é una lista di linee da usare come porta quota - - La funzione lo posizione a lato della linea di estensione 2. - dimLinePt1 = primo punto della linea di quota (QgsPoint) - dimLinePt2 = secondo punto della linea di quota (QgsPoint) - textWidth = larghezza testo - textHeight = altezza testo - """ - # Ottengo le linee porta quota per il testo esterno - lines = self.getLeaderLines(dimLinePt1, dimLinePt2, textWidth, textHeight) - # considero l'ultima che é quella che si riferisce al testo - line = lines[-1] - - if self.textRotMode == QadDimStyleTxtRotModeEnum.FORCED_ROTATION: - textRotMode = QadDimStyleTxtRotModeEnum.FORCED_ROTATION - else: - textRotMode = QadDimStyleTxtRotModeEnum.ALIGNED_LINE - - textInsPt, textRot = self.getTextPositionOnLine(line[0], line[1], textWidth, textHeight, \ - QadDimStyleTxtHorizontalPosEnum.FIRST_EXT_LINE, \ - self.textVerticalPos, textRotMode) - return textInsPt, textRot, lines - - - #============================================================================ - # getLinearTextAndBlocksPosition - #============================================================================ - def getLinearTextAndBlocksPosition(self, dimPt1, dimPt2, dimLinePt1, dimLinePt2, textWidth, textHeight): - """ - dimPt1 = primo punto da quotare - dimPt2 = secondo punto da quotare - dimLinePt1 = primo punto della linea di quota (QgsPoint) - dimLinePt2 = secondo punto della linea di quota (QgsPoint) - textWidth = larghezza testo - textHeight = altezza testo - - Restituisce una lista di 4 elementi: - - il primo elemento é una lista con il punto di inserimento del testo della quota e la sua rotazione - - il secondo elemento é una lista con flag che indica il tipo della linea sulla quale é stato messo il testo; vedi QadDimComponentEnum - e una lista di linee "leader" nel caso il testo sia all'esterno della quota - - il terzo elemento é la rotazione del primo blocco delle frecce; può essere None se non visibile - - il quarto elemento é la rotazione del secondo blocco delle frecce; può essere None se non visibile - """ - textInsPt = None # punto di inserimento del testo - textRot = None # rotazione del testo - textLinearDimComponentOn = None # codice del componente lineare sul quale é posizionato il testo - txtLeaderLines = None # lista di linee "leader" nel caso il testo sia all'esterno della quota - block1Rot = None # rotazione del primo blocco delle frecce - block2Rot = None # rotazione del secondo blocco delle frecce - - # se il testo é tra le linee di estensione della quota - if self.textHorizontalPos == QadDimStyleTxtHorizontalPosEnum.CENTERED_LINE or \ - self.textHorizontalPos == QadDimStyleTxtHorizontalPosEnum.FIRST_EXT_LINE or \ - self.textHorizontalPos == QadDimStyleTxtHorizontalPosEnum.SECOND_EXT_LINE: - - dimLineRot = qad_utils.getAngleBy2Pts(dimLinePt1, dimLinePt2) # angolo della linea di quota - - # cambio gli estremi della linea di quota per considerare lo spazio occupato dai blocchi - dimLinePt1Offset = qad_utils.getPolarPointByPtAngle(dimLinePt1, dimLineRot, self.getBlock1Size()) - dimLinePt2Offset = qad_utils.getPolarPointByPtAngle(dimLinePt2, dimLineRot + math.pi, self.getBlock2Size()) - - # testo sopra o sotto alla linea di quota nel caso la linea di quota non sia orizzontale - # e il testo sia dentro le linee di estensione e forzato orizzontale allora il testo diventa centrato - if (self.textVerticalPos == QadDimStyleTxtVerticalPosEnum.ABOVE_LINE or self.textVerticalPos == QadDimStyleTxtVerticalPosEnum.BELOW_LINE) and \ - (dimLineRot != 0 and dimLineRot != math.pi) and self.textRotMode == QadDimStyleTxtRotModeEnum.HORIZONTAL: - textVerticalPos = QadDimStyleTxtVerticalPosEnum.CENTERED_LINE - # testo posizionato nella parte opposta ai punti di quotatura - elif self.textVerticalPos == QadDimStyleTxtVerticalPosEnum.EXTERN_LINE: - # angolo dal primo punto di quota al primo punto della linea di quota - dimPtToDimLinePt_rot = qad_utils.getAngleBy2Pts(dimPt1, dimLinePt1) - if dimPtToDimLinePt_rot > 0 and \ - (dimPtToDimLinePt_rot < math.pi or qad_utils.doubleNear(dimPtToDimLinePt_rot, math.pi)): - textVerticalPos = QadDimStyleTxtVerticalPosEnum.ABOVE_LINE - else: - textVerticalPos = QadDimStyleTxtVerticalPosEnum.BELOW_LINE - else: - textVerticalPos = self.textVerticalPos - - if self.textRotMode == QadDimStyleTxtRotModeEnum.ISO: - textInsPt, textRot = self.getTextPositionOnLine(dimLinePt1Offset, dimLinePt2Offset, textWidth, textHeight, \ - self.textHorizontalPos, textVerticalPos, QadDimStyleTxtRotModeEnum.ALIGNED_LINE) - else: - textInsPt, textRot = self.getTextPositionOnLine(dimLinePt1Offset, dimLinePt2Offset, textWidth, textHeight, \ - self.textHorizontalPos, textVerticalPos, self.textRotMode) - - rect = self.textRectToQadLinearObjectList(textInsPt, textWidth, textHeight, textRot) - spaceForBlock1, spaceForBlock2 = self.getSpaceForBlock1AndBlock2(rect, dimLinePt1, dimLinePt2) - - # se lo spazio non é sufficiente per inserire testo e simboli all'interno delle linee di estensione, - # uso qad_utils.doubleSmaller perché a volte i due numeri sono quasi uguali - if spaceForBlock1 == 0 or spaceForBlock2 == 0 or \ - qad_utils.doubleSmaller(spaceForBlock1, self.getBlock1Size() + self.textOffsetDist) or \ - qad_utils.doubleSmaller(spaceForBlock2, self.getBlock2Size() + self.textOffsetDist): - if self.blockSuppressionForNoSpace: # sopprime i simboli se non c'é spazio sufficiente all'interno delle linee di estensione - block1Rot = None - block2Rot = None - - # considero il testo senza frecce - if self.textRotMode == QadDimStyleTxtRotModeEnum.ISO: - textInsPt, textRot = self.getTextPositionOnLine(dimLinePt1, dimLinePt2, textWidth, textHeight, \ - self.textHorizontalPos, textVerticalPos, QadDimStyleTxtRotModeEnum.ALIGNED_LINE) - else: - textInsPt, textRot = self.getTextPositionOnLine(dimLinePt1, dimLinePt2, textWidth, textHeight, \ - self.textHorizontalPos, textVerticalPos, self.textRotMode) - - rect = self.textRectToQadLinearObjectList(textInsPt, textWidth, textHeight, textRot) - spaceForBlock1, spaceForBlock2 = self.getSpaceForBlock1AndBlock2(rect, dimLinePt1, dimLinePt2) - # se non c'é spazio neanche per il testo senza le frecce - if spaceForBlock1 == 0 or spaceForBlock2 == 0 or \ - spaceForBlock1 < self.textOffsetDist or spaceForBlock2 < self.textOffsetDist: - # sposta testo fuori dalle linee di estensione - textInsPt, textRot, txtLeaderLines = self.getTextPosAndLinesOutOfDimLines(dimLinePt1, dimLinePt2, textWidth, textHeight) - textLinearDimComponentOn = QadDimComponentEnum.LEADER_LINE - else: - textLinearDimComponentOn = QadDimComponentEnum.DIM_LINE1 - else: # non devo sopprimere i simboli - # la prima cosa da spostare all'esterno é : - if self.textBlockAdjust == QadDimStyleTextBlocksAdjustEnum.BOTH_OUTSIDE_EXT_LINES: - # sposta testo e frecce fuori dalle linee di estensione - textInsPt, textRot, txtLeaderLines = self.getTextPosAndLinesOutOfDimLines(dimLinePt1, dimLinePt2, textWidth, textHeight) - textLinearDimComponentOn = QadDimComponentEnum.LEADER_LINE - block1Rot, block2Rot = self.getBlocksRot(dimLinePt1, dimLinePt2, False) # frecce esterne - # sposta prima le frecce poi, se non basta, anche il testo - elif self.textBlockAdjust == QadDimStyleTextBlocksAdjustEnum.FIRST_BLOCKS_THEN_TEXT: - block1Rot, block2Rot = self.getBlocksRot(dimLinePt1, dimLinePt2, False) # frecce esterne - # considero il testo senza frecce - if self.textRotMode == QadDimStyleTxtRotModeEnum.ISO: - textInsPt, textRot = self.getTextPositionOnLine(dimLinePt1, dimLinePt2, textWidth, textHeight, \ - self.textHorizontalPos, textVerticalPos, QadDimStyleTxtRotModeEnum.ALIGNED_LINE) - else: - textInsPt, textRot = self.getTextPositionOnLine(dimLinePt1, dimLinePt2, textWidth, textHeight, \ - self.textHorizontalPos, textVerticalPos, self.textRotMode) - - rect = self.textRectToQadLinearObjectList(textInsPt, textWidth, textHeight, textRot) - spaceForBlock1, spaceForBlock2 = self.getSpaceForBlock1AndBlock2(rect, dimLinePt1, dimLinePt2) - # se non c'é spazio neanche per il testo senza le frecce - if spaceForBlock1 == 0 or spaceForBlock2 == 0 or \ - spaceForBlock1 < self.textOffsetDist or spaceForBlock2 < self.textOffsetDist: - # sposta testo fuori dalle linee di estensione - textInsPt, textRot, txtLeaderLines = self.getTextPosAndLinesOutOfDimLines(dimLinePt1, dimLinePt2, textWidth, textHeight) - textLinearDimComponentOn = QadDimComponentEnum.LEADER_LINE - else: - textLinearDimComponentOn = QadDimComponentEnum.DIM_LINE1 - # sposta prima il testo poi, se non basta, anche le frecce - elif self.textBlockAdjust == QadDimStyleTextBlocksAdjustEnum.FIRST_TEXT_THEN_BLOCKS: - # sposto il testo fuori dalle linee di estensione - textInsPt, textRot, txtLeaderLines = self.getTextPosAndLinesOutOfDimLines(dimLinePt1, dimLinePt2, textWidth, textHeight) - textLinearDimComponentOn = QadDimComponentEnum.LEADER_LINE - # se non ci stanno neanche le frecce - if qad_utils.getDistance(dimLinePt1, dimLinePt2) <= self.getBlock1Size() + self.getBlock2Size(): - block1Rot, block2Rot = self.getBlocksRot(dimLinePt1, dimLinePt2, False) # frecce esterne - else: - block1Rot, block2Rot = self.getBlocksRot(dimLinePt1, dimLinePt2, True) # frecce interne - # Sposta indistintamente il testo o le frecce (l'oggetto che si adatta meglio) - elif self.textBlockAdjust == QadDimStyleTextBlocksAdjustEnum.WHICHEVER_FITS_BEST: - # sposto il più ingombrante - if self.getBlock1Size() + self.getBlock2Size() > textWidth: # le frecce sono più ingombranti del testo - textLinearDimComponentOn = QadDimComponentEnum.DIM_LINE1 - block1Rot, block2Rot = self.getBlocksRot(dimLinePt1, dimLinePt2, False) # frecce esterne - - # considero il testo senza frecce - if self.textRotMode == QadDimStyleTxtRotModeEnum.ISO: - textInsPt, textRot = self.getTextPositionOnLine(dimLinePt1, dimLinePt2, textWidth, textHeight, \ - self.textHorizontalPos, textVerticalPos, QadDimStyleTxtRotModeEnum.ALIGNED_LINE) - else: - textInsPt, textRot = self.getTextPositionOnLine(dimLinePt1, dimLinePt2, textWidth, textHeight, \ - self.textHorizontalPos, textVerticalPos, self.textRotMode) - - rect = self.textRectToQadLinearObjectList(textInsPt, textWidth, textHeight, textRot) - spaceForBlock1, spaceForBlock2 = self.getSpaceForBlock1AndBlock2(rect, dimLinePt1, dimLinePt2) - # se non c'é spazio neanche per il testo senza le frecce - if spaceForBlock1 == 0 or spaceForBlock2 == 0 or \ - spaceForBlock1 < self.textOffsetDist or spaceForBlock2 < self.textOffsetDist: - # sposta testo fuori dalle linee di estensione - textInsPt, textRot, txtLeaderLines = self.getTextPosAndLinesOutOfDimLines(dimLinePt1, dimLinePt2, textWidth, textHeight) - textLinearDimComponentOn = QadDimComponentEnum.LEADER_LINE - else: - textLinearDimComponentOn = QadDimComponentEnum.DIM_LINE1 - else: # il testo é più ingombrante dei simboli - # sposto il testo fuori dalle linee di estensione - textInsPt, textRot, txtLeaderLines = self.getTextPosAndLinesOutOfDimLines(dimLinePt1, dimLinePt2, textWidth, textHeight) - textLinearDimComponentOn = QadDimComponentEnum.LEADER_LINE - # se non ci stanno neanche le frecce - if qad_utils.getDistance(dimLinePt1, dimLinePt2) <= self.getBlock1Size() + self.getBlock2Size(): - block1Rot, block2Rot = self.getBlocksRot(dimLinePt1, dimLinePt2, False) # frecce esterne - else: - block1Rot, block2Rot = self.getBlocksRot(dimLinePt1, dimLinePt2, True) # frecce interne - else: # se lo spazio é sufficiente per inserire testo e simboli all'interno delle linee di estensione, - textLinearDimComponentOn = QadDimComponentEnum.DIM_LINE1 - block1Rot, block2Rot = self.getBlocksRot(dimLinePt1, dimLinePt2, True) # frecce interne - - # il testo é sopra e allineato alla prima linea di estensione - elif self.textHorizontalPos == QadDimStyleTxtHorizontalPosEnum.FIRST_EXT_LINE_UP: - # angolo della linea che va dal punto di quota all'inizio della linea di quota - rotLine = qad_utils.getAngleBy2Pts(dimPt1, dimLinePt1) - pt = qad_utils.getPolarPointByPtAngle(dimLinePt1, rotLine, self.textOffsetDist + textWidth) - if self.textVerticalPos == QadDimStyleTxtVerticalPosEnum.EXTERN_LINE: - textVerticalPos = QadDimStyleTxtVerticalPosEnum.ABOVE_LINE - else: - textVerticalPos = self.textVerticalPos - - if self.textRotMode == QadDimStyleTxtRotModeEnum.FORCED_ROTATION: - textRotMode = QadDimStyleTxtRotModeEnum.FORCED_ROTATION - else: - textRotMode = QadDimStyleTxtRotModeEnum.ALIGNED_LINE - - textInsPt, textRot = self.getTextPositionOnLine(dimLinePt1, pt, textWidth, textHeight, \ - QadDimStyleTxtHorizontalPosEnum.FIRST_EXT_LINE, \ - textVerticalPos, textRotMode) - textLinearDimComponentOn = QadDimComponentEnum.EXT_LINE1 - - # calcolo lo spazio dei blocchi in assenza del testo - spaceForBlock1, spaceForBlock2 = self.getSpaceForBlock1AndBlock2(None, dimLinePt1, dimLinePt2) - # se non c'é spazio per i blocchi - if spaceForBlock1 < self.getBlock1Size() or spaceForBlock2 < self.getBlock2Size(): - if self.blockSuppressionForNoSpace: # i blocchi sono soppressi - block1Rot = None - block2Rot = None - else: # sposto le frecce all'esterno - block1Rot, block2Rot = self.getBlocksRot(dimLinePt1, dimLinePt2, False) - else: # c'é spazio per i blocchi - block1Rot, block2Rot = self.getBlocksRot(dimLinePt1, dimLinePt2, True) # frecce interne - - # il testo é sopra e allineato alla seconda linea di estensione - elif self.textHorizontalPos == QadDimStyleTxtHorizontalPosEnum.SECOND_EXT_LINE_UP: - # angolo della linea che va dal punto di quota all'inizio della linea di quota - rotLine = qad_utils.getAngleBy2Pts(dimPt2, dimLinePt2) - pt = qad_utils.getPolarPointByPtAngle(dimLinePt2, rotLine, self.textOffsetDist + textWidth) - if self.textVerticalPos == QadDimStyleTxtVerticalPosEnum.EXTERN_LINE: - textVerticalPos = QadDimStyleTxtVerticalPosEnum.ABOVE_LINE - else: - textVerticalPos = self.textVerticalPos - - if self.textRotMode == QadDimStyleTxtRotModeEnum.FORCED_ROTATION: - textRotMode = QadDimStyleTxtRotModeEnum.FORCED_ROTATION - else: - textRotMode = QadDimStyleTxtRotModeEnum.ALIGNED_LINE - - textInsPt, textRot = self.getTextPositionOnLine(dimLinePt2, pt, textWidth, textHeight, \ - QadDimStyleTxtHorizontalPosEnum.FIRST_EXT_LINE, \ - textVerticalPos, textRotMode) - textLinearDimComponentOn = QadDimComponentEnum.EXT_LINE2 - - # calcolo lo spazio dei blocchi in assenza del testo - spaceForBlock1, spaceForBlock2 = self.getSpaceForBlock1AndBlock2(None, dimLinePt1, dimLinePt2) - # se non c'é spazio per i blocchi - if spaceForBlock1 < self.getBlock1Size() or spaceForBlock2 < self.getBlock2Size(): - if self.blockSuppressionForNoSpace: # i blocchi sono soppressi - block1Rot = None - block2Rot = None - else: # sposto le frecce all'esterno - block1Rot, block2Rot = self.getBlocksRot(dimLinePt1, dimLinePt2, False) - else: # c'é spazio per i blocchi - block1Rot, block2Rot = self.getBlocksRot(dimLinePt1, dimLinePt2, True) # frecce interne - - if self.textDirection == QadDimStyleTxtDirectionEnum.DX_TO_SX: - # il punto di inserimento diventa l'angolo in alto a destra del rettangolo - textInsPt = qad_utils.getPolarPointByPtAngle(textInsPt, textRot, textWidth) - textInsPt = qad_utils.getPolarPointByPtAngle(textInsPt, textRot + math.pi / 2, textHeight) - # la rotazione viene capovolta - textRot = qad_utils.normalizeAngle(textRot + math.pi) - - return [[textInsPt, textRot], [textLinearDimComponentOn, txtLeaderLines], block1Rot, block2Rot] - - - #============================================================================ - # getTextFeature - #============================================================================ - def getTextFeature(self, measure, pt = None, rot = None, sourceCrs = None): - """ - Restituisce la feature per il testo della quota. - La rotazione é espressa in radianti. - sourceCrs = sistema di coordinate di pt - """ - _pt = QgsPoint(0,0) if pt is None else pt - _rot = 0 if rot is None else rot - - textualFeaturePrototype = self.getTextualFeaturePrototype() - if textualFeaturePrototype is None: - return None - f = QgsFeature(textualFeaturePrototype) - g = QgsGeometry.fromPoint(_pt) - - if (sourceCrs is not None) and sourceCrs != self.getTextualLayer().crs(): - coordTransform = QgsCoordinateTransform(sourceCrs, self.getTextualLayer().crs()) # trasformo la geometria - g.transform(coordTransform) - - f.setGeometry(g) - - # se il testo dipende da un solo campo - labelFieldNames = qad_label.get_labelFieldNames(self.getTextualLayer()) - if len(labelFieldNames) == 1 and len(labelFieldNames[0]) > 0: - f.setAttribute(labelFieldNames[0], self.getFormattedText(measure)) - - # se l'altezza testo dipende da un solo campo - sizeFldNames = qad_label.get_labelSizeFieldNames(self.getTextualLayer()) - if len(sizeFldNames) == 1 and len(sizeFldNames[0]) > 0: - f.setAttribute(sizeFldNames[0], self.textHeight) # altezza testo - - # se la rotazione dipende da un solo campo - rotFldNames = qad_label.get_labelRotationFieldNames(self.getTextualLayer()) - if len(rotFldNames) == 1 and len(rotFldNames[0]) > 0: - f.setAttribute(rotFldNames[0], qad_utils.toDegrees(_rot)) # Converte da radianti a gradi - - # se il font dipende da un solo campo - fontFamilyFldNames = qad_label.get_labelFontFamilyFieldNames(self.getTextualLayer()) - if len(fontFamilyFldNames) == 1 and len(fontFamilyFldNames[0]) > 0: - f.setAttribute(fontFamilyFldNames[0], self.textFont) # nome del font di testo - - # imposto il colore - try: - if len(self.colorFieldName) > 0: - f.setAttribute(self.colorFieldName, self.textColor) - except: - pass - - # imposto lo stile di quotatura - try: - if len(self.dimStyleFieldName) > 0: - f.setAttribute(self.dimStyleFieldName, self.name) - if len(self.dimTypeFieldName) > 0: - f.setAttribute(self.dimTypeFieldName, self.dimType) - except: - pass - - return f - - - #============================================================================ - # FUNZIONI PER IL TESTO - FINE - # FUNZIONI PER LA LINEA DI LEADER - INIZIO - #============================================================================ - - - #============================================================================ - # getLeaderLines - #============================================================================ - def getLeaderLines(self, dimLinePt1, dimLinePt2, textWidth, textHeight): - """ - Restituisce una lista di linee che formano il porta quota nel caso il testo venga spostato - fuori dalle linee di estensione perché era troppo grosso. - dimLinePt1 = primo punto della linea di quota (QgsPoint) - dimLinePt2 = secondo punto della linea di quota (QgsPoint) - textWidth = larghezza testo - textHeight = altezza testo - """ - # le linee sono a lato della linea di estensione 1 - if self.textHorizontalPos == QadDimStyleTxtHorizontalPosEnum.FIRST_EXT_LINE: - rotLine = qad_utils.getAngleBy2Pts(dimLinePt2, dimLinePt1) # angolo della linea porta quota - pt1 = qad_utils.getPolarPointByPtAngle(dimLinePt1, rotLine, self.getBlock1Size()) - line1 = [dimLinePt1, pt1] - # le linee sono a lato della linea di estensione 2 - else: - rotLine = qad_utils.getAngleBy2Pts(dimLinePt1, dimLinePt2) # angolo della linea porta quota - pt1 = qad_utils.getPolarPointByPtAngle(dimLinePt2, rotLine, self.getBlock1Size()) - line1 = [dimLinePt2, pt1] - - # modalità di rotazione del testo orizzontale o - # testo allineato con la linea di quota se tra le linee di estensione, altrimenti testo orizzontale - if self.textRotMode == QadDimStyleTxtRotModeEnum.HORIZONTAL or \ - self.textRotMode == QadDimStyleTxtRotModeEnum.ISO: - if qad_utils.doubleNear(rotLine, math.pi / 2): # verticale dal basso verso l'alto - pt2 = qad_utils.getPolarPointByPtAngle(pt1, 0, self.textOffsetDist + textWidth) - elif qad_utils.doubleNear(rotLine, math.pi * 3 / 2): # verticale dall'alto verso il basso - pt2 = qad_utils.getPolarPointByPtAngle(pt1, math.pi, self.textOffsetDist + textWidth) - elif (rotLine > math.pi * 3 / 2 and rotLine <= math.pi * 2) or \ - (rotLine >= 0 and rotLine < math.pi / 2): # da sx a dx - pt2 = qad_utils.getPolarPointByPtAngle(pt1, 0, self.textOffsetDist + textWidth) - else: # da dx a sx - pt2 = qad_utils.getPolarPointByPtAngle(pt1, math.pi, self.textOffsetDist + textWidth) - elif self.textRotMode == QadDimStyleTxtRotModeEnum.ALIGNED_LINE: # testo allineato con la linea di quota - pt2 = qad_utils.getPolarPointByPtAngle(pt1, rotLine, self.textOffsetDist + textWidth) - elif self.textRotMode == QadDimStyleTxtRotModeEnum.FORCED_ROTATION: # testo con rotazione forzata - pt2 = qad_utils.getPolarPointByPtAngle(pt1, self.textForcedRot, self.textOffsetDist + textWidth) - - line2 = [pt1, pt2] - return [line1, line2] - - - #============================================================================ - # getExtLineFeature - #============================================================================ - def getLeaderFeature(self, leaderLines, sourceCrs = None): - """ - Restituisce la feature per la linea di estensione. - leaderLines = lista di linee di leader [line1, line2 ...] - sourceCrs = sistema di coordinate di leaderLines - """ - if leaderLines is None: - return None - - linearFeaturePrototype = self.getLinearFeaturePrototype() - if linearFeaturePrototype is None: - return None - f = QgsFeature(linearFeaturePrototype) - - pts = [] - first = True - for line in leaderLines: - if first: - pts.append(line[0]) - first = False - pts.append(line[1]) - - g = QgsGeometry.fromPolyline(pts) - - if (sourceCrs is not None) and sourceCrs != self.getLinearLayer().crs(): - coordTransform = QgsCoordinateTransform(sourceCrs, self.getLinearLayer().crs()) # trasformo la geometria - g.transform(coordTransform) - - f.setGeometry(g) - - try: - # imposto il tipo di componente della quotatura - if len(self.componentFieldName) > 0: - f.setAttribute(self.componentFieldName, QadDimComponentEnum.LEADER_LINE) - except: - pass - - try: - # imposto il tipo di linea - if len(self.lineTypeFieldName) > 0: - f.setAttribute(self.lineTypeFieldName, self.dimLineLineType) - except: - pass - - try: - # imposto il colore - if len(self.colorFieldName) > 0: - f.setAttribute(self.colorFieldName, self.dimLineColor) - except: - pass - - return f - - - #============================================================================ - # FUNZIONI PER LA LINEA DI LEADER - FINE - # FUNZIONI PER LE LINEE DI ESTENSIONE - INIZIO - #============================================================================ - - - #============================================================================ - # getExtLine - #============================================================================ - def getExtLine(self, dimPt, dimLinePt): - """ - dimPt = punto da quotare - dimLinePt = corrispondente punto della linea di quotatura - - ritorna una linea di estensione modificata secondo lo stile di quotatura - il primo punto é vicino alla linea di quota, il secondo al punto da quotare - """ - - angle = qad_utils.getAngleBy2Pts(dimPt, dimLinePt) - # distanza della linea di estensione oltre la linea di quota - pt1 = qad_utils.getPolarPointByPtAngle(dimLinePt, angle, self.extLineOffsetDimLine) - # distanza della linea di estensione dai punti da quotare - pt2 = qad_utils.getPolarPointByPtAngle(dimPt, angle, self.extLineOffsetOrigPoints) - - if self.extLineIsFixedLen == True: # attivata lunghezza fissa delle line di estensione - if qad_utils.getDistance(pt1, pt2) > self.extLineFixedLen: - # lunghezza fissa delle line di estensione (DIMFXL) dalla linea di quota - # al punto da quotare spostato di extLineOffsetOrigPoints - # (la linea di estensione non va oltre il punto da quotare) - d = qad_utils.getDistance(dimLinePt, dimPt) - if d > self.extLineFixedLen: - d = self.extLineFixedLen - pt2 = qad_utils.getPolarPointByPtAngle(dimLinePt, angle + math.pi, d) - - return [pt1, pt2] - - - #============================================================================ - # getExtLineFeature - #============================================================================ - def getExtLineFeature(self, extLine, isExtLine1, sourceCrs = None): - """ - Restituisce la feature per la linea di estensione. - extLine = linea di estensione [pt1, pt2] - isExtLine1 = se True si tratta della linea di estensione 1 altrimenti della linea di estensione 2 - sourceCrs = sistema di coordinate di extLine - """ - if (isExtLine1 == True and self.extLine1Show == False) or \ - (isExtLine1 == False and self.extLine2Show == False): - return None - - f = QgsFeature(self.getLinearFeaturePrototype()) - g = QgsGeometry.fromPolyline(extLine) - - if (sourceCrs is not None) and sourceCrs != self.getLinearLayer().crs(): - coordTransform = QgsCoordinateTransform(sourceCrs, self.getLinearLayer().crs()) # trasformo la geometria - g.transform(coordTransform) - - f.setGeometry(g) - - try: - # imposto il tipo di componente della quotatura - if len(self.componentFieldName) > 0: - f.setAttribute(self.componentFieldName, QadDimComponentEnum.EXT_LINE1 if isExtLine1 else QadDimComponentEnum.EXT_LINE2) - except: - pass - - try: - # imposto il tipo di linea - if len(self.lineTypeFieldName) > 0: - f.setAttribute(self.lineTypeFieldName, self.extLine1LineType if isExtLine1 else self.extLine2LineType) - except: - pass - - try: - # imposto il colore - if len(self.colorFieldName) > 0: - f.setAttribute(self.colorFieldName, self.extLineColor) - except: - pass - - return f - - - #============================================================================ - # FUNZIONI PER LE LINEE DI ESTENSIONE - FINE - # FUNZIONI PER LA LINEA DI QUOTA - INIZIO - #============================================================================ - - - #============================================================================ - # getDimLine - #============================================================================ - def getDimLine(self, dimPt1, dimPt2, linePosPt, preferredAlignment = QadDimStyleAlignmentEnum.HORIZONTAL, - dimLineRotation = 0.0): - """ - Restituisce la linea di quotatura: - - dimPt1 = primo punto da quotare - dimPt2 = secondo punto da quotare - linePosPt = punto per indicare dove deve essere posizionata la linea di quota - preferredAlignment = indica se ci si deve allineare ai punti di quota in modo orizzontale o verticale - (se i punti di quota formano una linea obliqua). Usato solo per le quotature lineari - dimLineRotation = angolo della linea di quotatura (default = 0). Usato solo per le quotature lineari - """ - if self.dimType == QadDimTypeEnum.ALIGNED: - # calcolo la proiezione perpendicolare del punto sulla linea che congiunge a - ptPerp = qad_utils.getPerpendicularPointOnInfinityLine(dimPt1, dimPt2, linePosPt) - d = qad_utils.getDistance(linePosPt, ptPerp) - - angle = qad_utils.getAngleBy2Pts(dimPt1, dimPt2) - if qad_utils.leftOfLine(linePosPt, dimPt1, dimPt2) < 0: # a sinistra della linea che congiunge a - angle = angle + (math.pi / 2) - else: - angle = angle - (math.pi / 2) - - return [qad_utils.getPolarPointByPtAngle(dimPt1, angle, d), \ - qad_utils.getPolarPointByPtAngle(dimPt2, angle, d)] - elif self.dimType == QadDimTypeEnum.LINEAR: - if preferredAlignment == QadDimStyleAlignmentEnum.HORIZONTAL: - ptDummy = qad_utils.getPolarPointByPtAngle(dimPt1, dimLineRotation + math.pi / 2, 1) - pt1 = qad_utils.getPerpendicularPointOnInfinityLine(dimPt1, ptDummy, linePosPt) - ptDummy = qad_utils.getPolarPointByPtAngle(dimPt2, dimLineRotation + math.pi / 2, 1) - pt2 = qad_utils.getPerpendicularPointOnInfinityLine(dimPt2, ptDummy, linePosPt) - - return [pt1, pt2] - elif preferredAlignment == QadDimStyleAlignmentEnum.VERTICAL: - ptDummy = qad_utils.getPolarPointByPtAngle(dimPt1, dimLineRotation, 1) - pt1 = qad_utils.getPerpendicularPointOnInfinityLine(dimPt1, ptDummy, linePosPt) - ptDummy = qad_utils.getPolarPointByPtAngle(dimPt2, dimLineRotation, 1) - pt2 = qad_utils.getPerpendicularPointOnInfinityLine(dimPt2, ptDummy, linePosPt) - - return [pt1, pt2] - - - #============================================================================ - # getDimLineFeature - #============================================================================ - def getDimLineFeature(self, dimLine, isDimLine1, textLinearDimComponentOn, sourceCrs = None): - """ - Restituisce la feature per la linea di quota. - dimLine = linea di quota [pt1, pt2] - isDimLine1 = se True si tratta della linea di quota 1 altrimenti della linea di quota 2 - textLinearDimComponentOn = indica il componente della quota dove é situato il testo di quota (QadDimComponentEnum) - sourceCrs = sistema di coordinate di dimLine - """ - - # se non c'é la linea di quota - if dimLine is None: - return None - if isDimLine1 == True: # se si tratta della linea di quota 1 - # se la linea di quota 1 deve essere invisibile (vale solo se il testo é sulla linea di quota) - if self.dimLine1Show == False and \ - (textLinearDimComponentOn == QadDimComponentEnum.DIM_LINE1 or textLinearDimComponentOn == QadDimComponentEnum.DIM_LINE2): - return None - else: # se si tratta della linea di quota 2 - # se la linea di quota 2 deve essere invisibile (vale solo se il testo é sulla linea di quota) - if self.dimLine2Show == False and \ - (textLinearDimComponentOn == QadDimComponentEnum.DIM_LINE1 or textLinearDimComponentOn == QadDimComponentEnum.DIM_LINE2): - return None - - f = QgsFeature(self.getLinearFeaturePrototype()) - g = QgsGeometry.fromPolyline(dimLine) - - if (sourceCrs is not None) and sourceCrs != self.getLinearLayer().crs(): - coordTransform = QgsCoordinateTransform(sourceCrs, self.getLinearLayer().crs()) # trasformo la geometria - g.transform(coordTransform) - - f.setGeometry(g) - - try: - # imposto il tipo di componente della quotatura - if len(self.componentFieldName) > 0: - f.setAttribute(self.componentFieldName, QadDimComponentEnum.DIM_LINE1 if isDimLine1 else QadDimComponentEnum.DIM_LINE2) - except: - pass - - try: - # imposto il tipo di linea - if len(self.lineTypeFieldName) > 0: - f.setAttribute(self.lineTypeFieldName, self.dimLineLineType) - except: - pass - - try: - # imposto il colore - if len(self.colorFieldName) > 0: - f.setAttribute(self.colorFieldName, self.dimLineColor) - except: - pass - - return f - - - #============================================================================ - # FUNZIONI PER LA LINEA DI QUOTA - FINE - # FUNZIONI PER LA QUOTATURA LINEARE - INIZIO - #============================================================================ - - - #============================================================================ - # getLinearDimFeatures - #============================================================================ - def getLinearDimFeatures(self, canvas, dimPt1, dimPt2, linePosPt, measure = None, \ - preferredAlignment = QadDimStyleAlignmentEnum.HORIZONTAL, \ - dimLineRotation = 0.0): - """ - dimPt1 = primo punto da quotare (in unita di mappa) - dimPt2 = secondo punto da quotare (in unita di mappa) - linePosPt = punto per indicare dove deve essere posizionata la linea di quota (in unita di mappa) - measure = indica se la misura é predeterminata oppure (se = None) deve essere calcolata - preferredAlignment = se lo stile di quota é lineare, indica se ci si deve allienare ai punti di quota - in modo orizzontale o verticale (se i punti di quota formano una linea obliqua) - dimLineRotation = angolo della linea di quotatura (default = 0) - - # quota lineare con una linea di quota orizzontale o verticale - # ritorna una lista di elementi che descrivono la geometria della quota: - # 1 lista = feature del primo e del secondo punto di quota; QgsFeature 1, QgsFeature 2 - # 2 lista = feature della prima e della seconda linea di quota (quest'ultima può essere None); QgsFeature 1, QgsFeature 2 - # 3 lista = feature del punto del testo di quota e geometria del rettangolo di occupazione; QgsFeature, QgsGeometry - # 4 lista = feature del primo e del secondo simbolo per la linea di quota (possono essere None); QgsFeature 1, QgsFeature 2 - # 5 lista = feature della prima e della seconda linea di estensione (possono essere None); QgsFeature 1, QgsFeature 2 - # 6 elemento = feature della linea di leader (può essere None); QgsFeature - """ - self.dimType = QadDimTypeEnum.LINEAR - - # punti di quotatura - dimPt1Feature = self.getDimPointFeature(dimPt1, True, \ - canvas.mapRenderer().destinationCrs()) # True = primo punto di quotatura - dimPt2Feature = self.getDimPointFeature(dimPt2, False, \ - canvas.mapRenderer().destinationCrs()) # False = secondo punto di quotatura - - # linea di quota - dimLine1 = self.getDimLine(dimPt1, dimPt2, linePosPt, preferredAlignment, dimLineRotation) - dimLine2 = None - - # testo e blocchi - if measure is None: - textValue = qad_utils.getDistance(dimLine1[0], dimLine1[1]) - else: - textValue = unicode(measure) - - textFeature = self.getTextFeature(textValue) - textWidth, textHeight = qad_label.calculateLabelSize(self.getTextualLayer(), textFeature, canvas) - - # creo un rettangolo intorno al testo con un buffer = self.textOffsetDist - textWidthOffset = textWidth + self.textOffsetDist * 2 - textHeightOffset = textHeight + self.textOffsetDist * 2 - - # Restituisce una lista di 4 elementi: - # - il primo elemento é una lista con il punto di inserimento del testo della quota e la sua rotazione - # - il secondo elemento é una lista con flag che indica il tipo della linea sulla quale é stato messo il testo; vedi QadDimComponentEnum - # e una lista di linee "leader" nel caso il testo sia all'esterno della quota - # - il terzo elemento é la rotazione del primo blocco delle frecce; può essere None se non visibile - # - il quarto elemento é la rotazione del secondo blocco delle frecce; può essere None se non visibile - dummy1, dummy2, block1Rot, block2Rot = self.getLinearTextAndBlocksPosition(dimPt1, dimPt2, \ - dimLine1[0], dimLine1[1], \ - textWidthOffset, textHeightOffset) - - textOffsetRectInsPt = dummy1[0] - textRot = dummy1[1] - textLinearDimComponentOn = dummy2[0] - txtLeaderLines = dummy2[1] - - # trovo il vero punto di inserimento del testo tenendo conto del buffer intorno - textInsPt = qad_utils.getPolarPointByPtAngle(textOffsetRectInsPt, textRot, self.textOffsetDist) - textInsPt = qad_utils.getPolarPointByPtAngle(textInsPt, textRot + math.pi / 2, self.textOffsetDist) - - # testo - textGeom = QgsGeometry.fromPoint(textInsPt) - textFeature = self.getTextFeature(textValue, textInsPt, textRot, canvas.mapRenderer().destinationCrs()) - - # blocchi frecce - block1Feature = self.getSymbolFeature(dimLine1[0], block1Rot, True, textLinearDimComponentOn, canvas.mapRenderer().destinationCrs()) # True = primo punto di quotatura - block2Feature = self.getSymbolFeature(dimLine1[1], block2Rot, False, textLinearDimComponentOn, canvas.mapRenderer().destinationCrs()) # False = secondo punto di quotatura - - extLine1 = self.getExtLine(dimPt1, dimLine1[0]) - extLine2 = self.getExtLine(dimPt2, dimLine1[1]) - - # creo un rettangolo intorno al testo con un offset - textOffsetRect = self.textRectToQadLinearObjectList(textOffsetRectInsPt, textWidthOffset, textHeightOffset, textRot) - - if textLinearDimComponentOn == QadDimComponentEnum.DIM_LINE1: # linea di quota ("Dimension line") - dimLine1, dimLine2 = self.adjustLineAccordingTextRect(textOffsetRect, dimLine1[0], dimLine1[1], QadDimComponentEnum.DIM_LINE1) - elif textLinearDimComponentOn == QadDimComponentEnum.EXT_LINE1: # prima linea di estensione ("Extension line 1") - if extLine1 is not None: - extLineRot = qad_utils.getAngleBy2Pts(dimPt1, dimLine1[0]) - extLine1 = self.getExtLine(dimPt1, qad_utils.getPolarPointByPtAngle(dimLine1[0], extLineRot, textWidth + self.textOffsetDist)) - # passo prima il secondo punto e poi il primo perché getExtLine restituisce una linea dalla linea di quota verso il punto di quotatura - extLine1, dummy = self.adjustLineAccordingTextRect(textOffsetRect, extLine1[1], extLine1[0], QadDimComponentEnum.EXT_LINE1) - elif textLinearDimComponentOn == QadDimComponentEnum.EXT_LINE2: # seconda linea di estensione ("Extension line 2") - if extLine2 is not None: - extLineRot = qad_utils.getAngleBy2Pts(dimPt2, dimLine1[1]) - extLine2 = self.getExtLine(dimPt2, qad_utils.getPolarPointByPtAngle(dimLine1[1], extLineRot, textWidth + self.textOffsetDist)) - # passo prima il secondo punto e poi il primo perché getExtLine restituisce una linea dalla linea di quota verso il punto di quotatura - extLine2, dummy = self.adjustLineAccordingTextRect(textOffsetRect, extLine2[1], extLine2[0], QadDimComponentEnum.EXT_LINE2) - elif textLinearDimComponentOn == QadDimComponentEnum.LEADER_LINE: # linea porta quota usata quando il testo é fuori dalla quota ("Leader") - lastLine = txtLeaderLines[-1] - lastLine, dummy = self.adjustLineAccordingTextRect(textOffsetRect, lastLine[0], lastLine[1], QadDimComponentEnum.LEADER_LINE) - del txtLeaderLines[-1] # sostituisco l'ultimo elemento - txtLeaderLines.append(lastLine) - - # linee di quota - dimLine1Feature = self.getDimLineFeature(dimLine1, True, textLinearDimComponentOn, canvas.mapRenderer().destinationCrs()) # True = prima linea di quota - dimLine2Feature = self.getDimLineFeature(dimLine2, False, textLinearDimComponentOn, canvas.mapRenderer().destinationCrs()) # False = seconda linea di quota - - # linee di estensione - extLine1Feature = self.getExtLineFeature(extLine1, True, canvas.mapRenderer().destinationCrs()) # True = prima linea di estensione - extLine2Feature = self.getExtLineFeature(extLine2, False, canvas.mapRenderer().destinationCrs()) # False = seconda linea di estensione - - # linea di leader - txtLeaderLineFeature = self.getLeaderFeature(txtLeaderLines, canvas.mapRenderer().destinationCrs()) - - dimEntity = QadDimEntity() - dimEntity.dimStyle = self - # features testuali - dimEntity.textualFeature = textFeature - # features lineari - if dimLine1Feature is not None: - dimEntity.linearFeatures.append(dimLine1Feature) - if dimLine2Feature is not None: - dimEntity.linearFeatures.append(dimLine2Feature) - if extLine1Feature is not None: - dimEntity.linearFeatures.append(extLine1Feature) - if extLine2Feature is not None: - dimEntity.linearFeatures.append(extLine2Feature) - if txtLeaderLineFeature is not None: - dimEntity.linearFeatures.append(txtLeaderLineFeature) - # features puntuali - dimEntity.symbolFeatures.extend([dimPt1Feature, dimPt2Feature]) - if block1Feature is not None: - dimEntity.symbolFeatures.append(block1Feature) - if block2Feature is not None: - dimEntity.symbolFeatures.append(block2Feature) - - return dimEntity, QgsGeometry.fromPolygon([textOffsetRect.asPolyline()]) - - - #============================================================================ - # addLinearDimToLayers - #============================================================================ - def addLinearDimToLayers(self, plugIn, dimPt1, dimPt2, linePosPt, measure = None, \ - preferredAlignment = QadDimStyleAlignmentEnum.HORIZONTAL, \ - dimLineRotation = 0.0): - """ - Aggiunge ai layers le features che compongono una quota lineare. - """ - self.dimType = QadDimTypeEnum.LINEAR - - dimEntity, textOffsetRect = self.getLinearDimFeatures(plugIn.canvas, \ - dimPt1, \ - dimPt2, \ - linePosPt, \ - measure, \ - preferredAlignment, \ - dimLineRotation) - - plugIn.beginEditCommand("Linear dimension added", [self.getSymbolLayer(), self.getLinearLayer(), self.getTextualLayer()]) - - # prima di tutto inserisco il testo di quota - # plugIn, layer, feature, coordTransform, refresh, check_validity - if qad_layer.addFeatureToLayer(plugIn, self.getTextualLayer(), dimEntity.textualFeature, None, False, False) == False: - plugIn.destroyEditCommand() - return False - dimId = dimEntity.textualFeature.id() - if self.setDimId(dimId, [dimEntity.textualFeature], False) == True: # setto id - # plugIn, layer, feature, refresh, check_validity - if qad_layer.updateFeatureToLayer(plugIn, self.getTextualLayer(), dimEntity.textualFeature, False, False) == False: - plugIn.destroyEditCommand() - return False - - # features puntuali - self.setDimId(dimId, dimEntity.symbolFeatures, True) # setto id_parent - # plugIn, layer, features, coordTransform, refresh, check_validity - if qad_layer.addFeaturesToLayer(plugIn, self.getSymbolLayer(), dimEntity.symbolFeatures, None, False, False) == False: - plugIn.destroyEditCommand() - return False - - # features lineari - self.setDimId(dimId, dimEntity.linearFeatures, True) # setto id_parent - # plugIn, layer, features, coordTransform, refresh, check_validity - if qad_layer.addFeaturesToLayer(plugIn, self.getLinearLayer(), dimEntity.linearFeatures, None, False, False) == False: - plugIn.destroyEditCommand() - return False - - plugIn.endEditCommand() - - return True - - - #============================================================================ - # FUNZIONI PER LA QUOTATURA LINEARE - FINE - # FUNZIONI PER LA QUOTATURA ALLINEATA - INIZIO - #============================================================================ - - - #============================================================================ - # getAlignedDimFeatures - #============================================================================ - def getAlignedDimFeatures(self, canvas, dimPt1, dimPt2, linePosPt, measure = None): - """ - dimPt1 = primo punto da quotare (in unita di mappa) - dimPt2 = secondo punto da quotare (in unita di mappa) - linePosPt = punto per indicare dove deve essere posizionata la linea di quota (in unita di mappa) - measure = indica se la misura é predeterminata oppure (se = None) deve essere calcolata - - # quota lineare con una linea di quota orizzontale o verticale - # ritorna una lista di elementi che descrivono la geometria della quota: - # 1 lista = feature del primo e del secondo punto di quota; QgsFeature 1, QgsFeature 2 - # 2 lista = feature della prima e della seconda linea di quota (quest'ultima può essere None); QgsFeature 1, QgsFeature 2 - # 3 lista = feature del punto del testo di quota e geometria del rettangolo di occupazione; QgsFeature, QgsGeometry - # 4 lista = feature del primo e del secondo simbolo per la linea di quota (possono essere None); QgsFeature 1, QgsFeature 2 - # 5 lista = feature della prima e della seconda linea di estensione (possono essere None); QgsFeature 1, QgsFeature 2 - # 6 elemento = feature della linea di leader (può essere None); QgsFeature - """ - self.dimType = QadDimTypeEnum.ALIGNED - - # punti di quotatura - dimPt1Feature = self.getDimPointFeature(dimPt1, True, \ - canvas.mapRenderer().destinationCrs()) # True = primo punto di quotatura - dimPt2Feature = self.getDimPointFeature(dimPt2, False, \ - canvas.mapRenderer().destinationCrs()) # False = secondo punto di quotatura - - # linea di quota - dimLine1 = self.getDimLine(dimPt1, dimPt2, linePosPt) - dimLine2 = None - - # testo e blocchi - if measure is None: - textValue = qad_utils.getDistance(dimLine1[0], dimLine1[1]) - else: - textValue = unicode(measure) - - textFeature = self.getTextFeature(textValue) - textWidth, textHeight = qad_label.calculateLabelSize(self.getTextualLayer(), textFeature, canvas) - - # creo un rettangolo intorno al testo con un buffer = self.textOffsetDist - textWidthOffset = textWidth + self.textOffsetDist * 2 - textHeightOffset = textHeight + self.textOffsetDist * 2 - - # Restituisce una lista di 4 elementi: - # - il primo elemento é una lista con il punto di inserimento del testo della quota e la sua rotazione - # - il secondo elemento é una lista con flag che indica il tipo della linea sulla quale é stato messo il testo; vedi QadDimComponentEnum - # e una lista di linee "leader" nel caso il testo sia all'esterno della quota - # - il terzo elemento é la rotazione del primo blocco delle frecce; può essere None se non visibile - # - il quarto elemento é la rotazione del secondo blocco delle frecce; può essere None se non visibile - dummy1, dummy2, block1Rot, block2Rot = self.getLinearTextAndBlocksPosition(dimPt1, dimPt2, \ - dimLine1[0], dimLine1[1], \ - textWidthOffset, textHeightOffset) - textOffsetRectInsPt = dummy1[0] - textRot = dummy1[1] - textLinearDimComponentOn = dummy2[0] - txtLeaderLines = dummy2[1] - - # trovo il vero punto di inserimento del testo tenendo conto del buffer intorno - textInsPt = qad_utils.getPolarPointByPtAngle(textOffsetRectInsPt, textRot, self.textOffsetDist) - textInsPt = qad_utils.getPolarPointByPtAngle(textInsPt, textRot + math.pi / 2, self.textOffsetDist) - - # testo - textGeom = QgsGeometry.fromPoint(textInsPt) - textFeature = self.getTextFeature(textValue, textInsPt, textRot, canvas.mapRenderer().destinationCrs()) - - # blocchi frecce - block1Feature = self.getSymbolFeature(dimLine1[0], block1Rot, True, textLinearDimComponentOn, canvas.mapRenderer().destinationCrs()) # True = primo punto di quotatura - block2Feature = self.getSymbolFeature(dimLine1[1], block2Rot, False, textLinearDimComponentOn, canvas.mapRenderer().destinationCrs()) # False = secondo punto di quotatura - - extLine1 = self.getExtLine(dimPt1, dimLine1[0]) - extLine2 = self.getExtLine(dimPt2, dimLine1[1]) - - # creo un rettangolo intorno al testo con un offset - textOffsetRect = self.textRectToQadLinearObjectList(textOffsetRectInsPt, textWidthOffset, textHeightOffset, textRot) - - if textLinearDimComponentOn == QadDimComponentEnum.DIM_LINE1: # linea di quota ("Dimension line") - dimLine1, dimLine2 = self.adjustLineAccordingTextRect(textOffsetRect, dimLine1[0], dimLine1[1], QadDimComponentEnum.DIM_LINE1) - elif textLinearDimComponentOn == QadDimComponentEnum.EXT_LINE1: # prima linea di estensione ("Extension line 1") - if extLine1 is not None: - extLineRot = qad_utils.getAngleBy2Pts(dimPt1, dimLine1[0]) - extLine1 = self.getExtLine(dimPt1, qad_utils.getPolarPointByPtAngle(dimLine1[0], extLineRot, textWidth + self.textOffsetDist)) - # passo prima il secondo punto e poi il primo perché getExtLine restituisce una linea dalla linea di quota verso il punto di quotatura - extLine1, dummy = self.adjustLineAccordingTextRect(textOffsetRect, extLine1[1], extLine1[0], QadDimComponentEnum.EXT_LINE1) - elif textLinearDimComponentOn == QadDimComponentEnum.EXT_LINE2: # seconda linea di estensione ("Extension line 2") - if extLine2 is not None: - extLineRot = qad_utils.getAngleBy2Pts(dimPt2, dimLine1[1]) - extLine2 = self.getExtLine(dimPt2, qad_utils.getPolarPointByPtAngle(dimLine1[1], extLineRot, textWidth + self.textOffsetDist)) - # passo prima il secondo punto e poi il primo perché getExtLine restituisce una linea dalla linea di quota verso il punto di quotatura - extLine2, dummy = self.adjustLineAccordingTextRect(textOffsetRect, extLine2[1], extLine2[0], QadDimComponentEnum.EXT_LINE2) - elif textLinearDimComponentOn == QadDimComponentEnum.LEADER_LINE: # linea porta quota usata quando il testo é fuori dalla quota ("Leader") - lastLine = txtLeaderLines[-1] - lastLine, dummy = self.adjustLineAccordingTextRect(textOffsetRect, lastLine[0], lastLine[1], QadDimComponentEnum.LEADER_LINE) - del txtLeaderLines[-1] # sostituisco l'ultimo elemento - txtLeaderLines.append(lastLine) - - # linee di quota - dimLine1Feature = self.getDimLineFeature(dimLine1, True, textLinearDimComponentOn, canvas.mapRenderer().destinationCrs()) # True = prima linea di quota - dimLine2Feature = self.getDimLineFeature(dimLine2, False, textLinearDimComponentOn, canvas.mapRenderer().destinationCrs()) # False = seconda linea di quota - - # linee di estensione - extLine1Feature = self.getExtLineFeature(extLine1, True, canvas.mapRenderer().destinationCrs()) # True = prima linea di estensione - extLine2Feature = self.getExtLineFeature(extLine2, False, canvas.mapRenderer().destinationCrs()) # False = seconda linea di estensione - - # linea di leader - txtLeaderLineFeature = self.getLeaderFeature(txtLeaderLines, canvas.mapRenderer().destinationCrs()) - - dimEntity = QadDimEntity() - dimEntity.dimStyle = self - # features testuali - dimEntity.textualFeature = textFeature - # features lineari - if dimLine1Feature is not None: - dimEntity.linearFeatures.append(dimLine1Feature) - if dimLine2Feature is not None: - dimEntity.linearFeatures.append(dimLine2Feature) - if extLine1Feature is not None: - dimEntity.linearFeatures.append(extLine1Feature) - if extLine2Feature is not None: - dimEntity.linearFeatures.append(extLine2Feature) - if txtLeaderLineFeature is not None: - dimEntity.linearFeatures.append(txtLeaderLineFeature) - # features puntuali - dimEntity.symbolFeatures.extend([dimPt1Feature, dimPt2Feature]) - if block1Feature is not None: - dimEntity.symbolFeatures.append(block1Feature) - if block2Feature is not None: - dimEntity.symbolFeatures.append(block2Feature) - - return dimEntity, QgsGeometry.fromPolygon([textOffsetRect.asPolyline()]) - - - #============================================================================ - # addAlignedDimToLayers - #============================================================================ - def addAlignedDimToLayers(self, plugIn, dimPt1, dimPt2, linePosPt, measure = None, \ - preferredAlignment = QadDimStyleAlignmentEnum.HORIZONTAL, \ - dimLineRotation = 0.0): - """ - dimPt1 = primo punto da quotare (in unita di mappa) - dimPt2 = secondo punto da quotare (in unita di mappa) - linePosPt = punto per indicare dove deve essere posizionata la linea di quota (in unita di mappa) - measure = indica se la misura é predeterminata oppure (se = None) deve essere calcolata - preferredAlignment = se lo stile di quota é lineare, indica se ci si deve allienare ai punti di quota - in modo orizzontale o verticale (se i punti di quota formano una linea obliqua) - dimLineRotation = angolo della linea di quotatura (default = 0) - - Aggiunge ai layers le features che compongono una quota allineata. - """ - self.dimType = QadDimTypeEnum.ALIGNED - - dimEntity, textOffsetRect = self.getAlignedDimFeatures(plugIn.canvas, \ - dimPt1, \ - dimPt2, \ - linePosPt, \ - measure) - - plugIn.beginEditCommand("Aligned dimension added", [self.getSymbolLayer(), self.getLinearLayer(), self.getTextualLayer()]) - - # prima di tutto inserisco il testo di quota - # plugIn, layer, feature, coordTransform, refresh, check_validity - if qad_layer.addFeatureToLayer(plugIn, self.getTextualLayer(), dimEntity.textualFeature, None, False, False) == False: - plugIn.destroyEditCommand() - return False - dimId = dimEntity.textualFeature.id() - if self.setDimId(dimId, [dimEntity.textualFeature], False) == True: # setto id - # plugIn, layer, feature, refresh, check_validity - if qad_layer.updateFeatureToLayer(plugIn, self.getTextualLayer(), dimEntity.textualFeature, False, False) == False: - plugIn.destroyEditCommand() - return False - - # features puntuali - self.setDimId(dimId, dimEntity.symbolFeatures, True) # setto id_parent - # plugIn, layer, features, coordTransform, refresh, check_validity - if qad_layer.addFeaturesToLayer(plugIn, self.getSymbolLayer(), dimEntity.symbolFeatures, None, False, False) == False: - plugIn.destroyEditCommand() - return False - - # features lineari - self.setDimId(dimId, dimEntity.linearFeatures, True) # setto id_parent - # plugIn, layer, features, coordTransform, refresh, check_validity - if qad_layer.addFeaturesToLayer(plugIn, self.getLinearLayer(), dimEntity.linearFeatures, None, False, False) == False: - plugIn.destroyEditCommand() - return False - - plugIn.endEditCommand() - - return True - - -#=============================================================================== -# QadDimStylesClass list of dimension styles -#=============================================================================== -class QadDimStylesClass(): - - def __init__(self, dimStyleList = None): - if dimStyleList is None: - self.dimStyleList = [] - else: - self.set(dimStyleList) - - - def __del__(self): - if dimStyleList is None: - del self.dimStyleList[:] - - - def isEmpty(self): - return True if self.count() == 0 else False - - - def count(self): - return len(self.dimStyleList) - - - def clear(self): - del self.dimStyleList[:] - - - def findDimStyle(self, dimStyleName): - """ - La funzione, dato un nome di stile di quotatura, lo cerca nella lista e, - in caso di successo, restituisce lo stile di quotatura. - """ - for dimStyle in self.dimStyleList: - if dimStyle.name == dimStyleName: - return dimStyle - return None - - - def addDimStyle(self, dimStyle, toFile = False, filePath = ""): - d = self.findDimStyle(dimStyle) - if d is None: - self.dimStyleList.append(QadDimStyle(dimStyle)) - if toFile: - if dimStyle.save(filePath, False) == False: # senza sovrascrivere file - return False - return True - - return False - - - #============================================================================ - # removeDimStyle - #============================================================================ - def removeDimStyle(self, dimStyleName, toFile = False): - i = 0 - for dimStyle in self.dimStyleList: - if dimStyle.name == dimStyleName: - del self.dimStyleList[i] - if toFile: - dimStyle.remove() - return True - else: - i = i + 1 - - return False - - - #============================================================================ - # renameDimStyle - #============================================================================ - def renameDimStyle(self, dimStyleName, newDimStyleName): - if dimStyleName == newDimStyleName: # nome uguale - return True - - if self.findDimStyle(newDimStyleName) is not None: - return False - dimStyle = self.findDimStyle(dimStyleName) - if dimStyle is None: - return False - return dimStyle.rename(newDimStyleName) - - - #============================================================================ - # load - #============================================================================ - def load(self, dir = None, append = False): - """ - Carica le impostazioni di tutti gli stili di quotatura presenti nella directory indicata. - se dir = None se esiste un progetto caricato il percorso è quello del progetto altrimenti + il percorso locale di qad - """ - if dir is None: - if append == False: - self.clear() - - # se esiste un progetto caricato il percorso è quello del progetto - prjFileInfo = QFileInfo(QgsProject.instance().fileName()) - path = prjFileInfo.absolutePath() - if len(path) > 0: - path += "/;" - path += QgsApplication.qgisSettingsDirPath() + "python/plugins/qad/" - - # lista di directory separate da ";" - dirList = path.strip().split(";") - for _dir in dirList: - self.load(_dir, True) # in append - else: - _dir = QDir.cleanPath(dir) - if _dir == "": - return False - - if _dir.endswith("/") == False: - _dir = _dir + "/" - - if not os.path.exists(_dir): - return False - - if append == False: - self.clear() - dimStyle = QadDimStyle() - - fileNames = os.listdir(_dir) - for fileName in fileNames: - if fileName.endswith(".dim"): - path = _dir + fileName - if dimStyle.load(path) == True: - if self.findDimStyle(dimStyle.name) is None: - self.addDimStyle(dimStyle) - - return True - - - #============================================================================ - # getDimIdByEntity - #============================================================================ - def getDimIdByEntity(self, entity): - """ - La funzione, data un'entità, verifica se fa parte di uno stile di quotatura della lista e, - in caso di successo, restituisce lo stile di quotatura e il codice della quotatura altrimenti None, None. - """ - for dimStyle in self.dimStyleList: - dimId = dimStyle.getDimIdByEntity(entity) - if dimId is not None: - return dimStyle, dimId - return None, None - - - #============================================================================ - # getDimEntity - #============================================================================ - def getDimEntity(self, layer, fid = None): - """ - la funzione può essere richiamata in 2 modi: - con un solo parametro di tipo QadEntity - con due parametri, il primo QgsVectorLayer e il secondo l'id della feature - """ - # verifico se l'entità appartiene ad uno stile di quotatura - if type(layer) == QgsVectorLayer: - entity = QadEntity() - entity.set(layer, fid) - dimStyle, dimId = self.getDimIdByEntity(entity) - else: # il parametro layer puo essere un oggetto QadEntity - dimStyle, dimId = self.getDimIdByEntity(layer) - - if (dimStyle is None) or (dimId is None): - return None - - dimEntity = QadDimEntity() - if dimEntity.initByDimId(dimStyle, dimId) == False: - return None - - return dimEntity - - - #============================================================================ - # getDimListByLayer - #============================================================================ - def getDimListByLayer(self, layer): - """ - La funzione, dato un layer, verifica se fa parte di uno o più stili di quotatura della lista e, - in caso di successo, restituisce la lista degli stili di quotatura di appartenenza. - """ - result = [] - for dimStyle in self.dimStyleList: - if dimStyle.isDimLayer(layer): - if dimStyle not in result: - result.append(dimStyle) - - return result - - - #============================================================================ - # addAllDimComponentsToEntitySet - #============================================================================ - def addAllDimComponentsToEntitySet(self, entitySet, onlyEditableLayers): - """ - La funzione verifica se le entità che fanno parte di un entitySet sono anche parte di quotatura e, - in caso affermativo, aggiunge tutti i componenti della quotatura all'entitySet. - """ - elaboratedDimEntitySet = QadEntitySet() # lista delle entità di quota elaborate - entity = QadEntity() - for layerEntitySet in entitySet.layerEntitySetList: - # verifico se il layer appartiene ad uno o più stili di quotatura - dimStyleList = self.getDimListByLayer(layerEntitySet.layer) - for dimStyle in dimStyleList: # per tutti gli stili di quotatura - if dimStyle is not None: - remove = False - if onlyEditableLayers == True: - # se anche un solo layer non é modificabile - if dimStyle.getTextualLayer().isEditable() == False or \ - dimStyle.getSymbolLayer().isEditable() == False or \ - dimStyle.getLinearLayer().isEditable() == False: - remove = True - features = layerEntitySet.getFeatureCollection() - for feature in features: - entity.set(layerEntitySet.layer, feature.id()) - if not elaboratedDimEntitySet.containsEntity(entity): - dimId = dimStyle.getDimIdByEntity(entity) - if dimId is not None: - dimEntitySet = dimStyle.getEntitySet(dimId) - if remove == False: - entitySet.unite(dimEntitySet) - else: - entitySet.subtract(dimEntitySet) - - elaboratedDimEntitySet.unite(entitySet) - - - #============================================================================ - # removeAllDimLayersFromEntitySet - #============================================================================ - def removeAllDimLayersFromEntitySet(self, entitySet): - """ - La funzione rimuove tutte le entità che fanno parte di quotature dall'entitySet. - """ - for dimStyle in self.dimStyleList: - entitySet.removeLayerEntitySet(dimStyle.getTextualLayer()) - entitySet.removeLayerEntitySet(dimStyle.getSymbolLayer()) - entitySet.removeLayerEntitySet(dimStyle.getLinearLayer()) - - -#=============================================================================== -# QadDimEntity dimension entity class -#=============================================================================== -class QadDimEntity(): - - #============================================================================ - # __init__ - #============================================================================ - def __init__(self, dimEntity = None): - if dimEntity is not None: - self.set(dimEntity) - else: - self.dimStyle = None - self.textualFeature = None - self.linearFeatures = [] - self.symbolFeatures = [] - - - def whatIs(self): - return "DIMENTITY" - - - def isInitialized(self): - if (self.dimStyle is None) or (self.textualFeature is None): - return False - else: - return True - - - def __eq__(self, dimEntity): - """self == other""" - if self.isInitialized() == False or dimEntity.isInitialized() == False : - return False - - if self.getTextualLayer() == dimEntity.getTextualLayer() and self.getDimId() == dimEntity.getDimId(): - return True - else: - return False - - - #============================================================================ - # getTextualLayer - #============================================================================ - def getTextualLayer(self): - if self.dimStyle is None: - return None - return self.dimStyle.getTextualLayer() - - - #============================================================================ - # getLinearLayer - #============================================================================ - def getLinearLayer(self): - if self.dimStyle is None: - return None - return self.dimStyle.getLinearLayer() - - - #============================================================================ - # getSymbolLayer - #============================================================================ - def getSymbolLayer(self): - if self.dimStyle is None: - return None - return self.dimStyle.getSymbolLayer() - - - #============================================================================ - # set - #============================================================================ - def set(self, dimEntity): - self.dimStyle = QadDimStyle(dimEntity.dimStyle) - - self.textualFeature = QgsFeature(dimEntity.textualFeature) - - del self.linearFeatures[:] - for f in dimEntity.linearFeatures: - self.linearFeatures.append(QgsFeature(f)) - - del self.symbolFeatures[:] - for f in dimEntity.symbolFeatures: - self.symbolFeatures.append(QgsFeature(f)) - - - #============================================================================ - # getLinearGeometryCollection - #============================================================================ - def getLinearGeometryCollection(self): - result = [] - for f in self.linearFeatures: - result.append(f.geometry()) - return result - - - #============================================================================ - # getSymbolGeometryCollection - #============================================================================ - def getSymbolGeometryCollection(self): - result = [] - for f in self.symbolFeatures: - result.append(f.geometry()) - return result - - - #============================================================================ - # getDimId - #============================================================================ - def getDimId(self): - """ - La funzione restituisce il codice della quotatura altrimenti None. - """ - try: - return self.textualFeature.attribute(self.idFieldName) - except: - return None - - - def recodeDimIdToFeature(self, newDimId): - try: - # imposto il codice della quota - self.textualFeature.setAttribute(self.dimStyle.idFieldName, newDimId) - for f in self.linearFeatures: - f.setAttribute(self.dimStyle.idParentFieldName, newDimId) - for f in self.symbolFeatures: - f.setAttribute(self.dimStyle.idParentFieldName, newDimId) - except: - return False - - return True - - - #============================================================================ - # addToLayers - #============================================================================ - def addToLayers(self, plugIn): - # prima di tutto inserisco il testo di quota per ricodificare la quotatura - # plugIn, layer, feature, coordTransform, refresh, check_validity - if qad_layer.addFeatureToLayer(plugIn, self.getTextualLayer(), self.textualFeature, None, False, False) == False: - return False - newDimId = self.textualFeature.id() - - if self.recodeDimIdToFeature(newDimId) == False: - return False - - # plugIn, layer, feature, refresh, check_validity - if qad_layer.updateFeatureToLayer(plugIn, self.getTextualLayer(), self.textualFeature, False, False) == False: - return False - # plugIn, layer, features, coordTransform, refresh, check_validity - if qad_layer.addFeaturesToLayer(plugIn, self.getLinearLayer(), self.linearFeatures, None, False, False) == False: - return False - # plugIn, layer, features, coordTransform, refresh, check_validity - if qad_layer.addFeaturesToLayer(plugIn, self.getSymbolLayer(), self.symbolFeatures, None, False, False) == False: - return False - - return True - - - #============================================================================ - # deleteToLayers - #============================================================================ - def deleteToLayers(self, plugIn): - ids =[] - - # plugIn, layer, featureId, refresh - if qad_layer.deleteFeatureToLayer(plugIn, self.getTextualLayer(), self.textualFeature.id(), False) == False: - return False - - for f in self.linearFeatures: - ids.append(f.id()) - # plugIn, layer, featureIds, refresh - if qad_layer.deleteFeaturesToLayer(plugIn, self.getLinearLayer(), ids, False) == False: - return False - - del ids[:] - for f in self.symbolFeatures: - ids.append(f.id()) - # plugIn, layer, featureIds, refresh - if qad_layer.deleteFeaturesToLayer(plugIn, self.getSymbolLayer(), ids, False) == False: - return False - - return True - - - #============================================================================ - # initByEntity - #============================================================================ - def initByEntity(self, dimStyle, entity): - dimId = dimStyle.getDimIdByEntity(entity) - if dimId is None: - return False - return self.initByDimId(dimStyle, dimId) - - - #============================================================================ - # initByDimId - #============================================================================ - def initByDimId(self, dimStyle, dimId): - self.dimStyle = QadDimStyle(dimStyle) - entitySet = self.dimStyle.getEntitySet(dimId) - - self.textualFeature = None - layerEntitySet = entitySet.findLayerEntitySet(self.getTextualLayer()) - features = layerEntitySet.getFeatureCollection() - self.textualFeature = features[0] - - # entità lineari - layerEntitySet = entitySet.findLayerEntitySet(self.getLinearLayer()) - del self.linearFeatures[:] # svuoto la lista - if layerEntitySet is not None: - self.linearFeatures = layerEntitySet.getFeatureCollection() - - # entità puntuali - layerEntitySet = entitySet.findLayerEntitySet(self.getSymbolLayer()) - del self.symbolFeatures[:] # svuoto la lista - if layerEntitySet is not None: - self.symbolFeatures = layerEntitySet.getFeatureCollection() - - return True - - - #============================================================================ - # getEntitySet - #============================================================================ - def getEntitySet(self): - result = QadEntitySet() - - layerEntitySet = QadLayerEntitySet() - layerEntitySet.set(self.getTextualLayer(), [self.textualFeature]) - result.addLayerEntitySet(layerEntitySet) - - layerEntitySet = QadLayerEntitySet() - layerEntitySet.set(self.getLinearLayer(), self.linearFeatures) - result.addLayerEntitySet(layerEntitySet) - - layerEntitySet = QadLayerEntitySet() - layerEntitySet.set(self.getSymbolLayer(), self.symbolFeatures) - result.addLayerEntitySet(layerEntitySet) - - return result - - - #============================================================================ - # selectOnLayer - #============================================================================ - def selectOnLayer(self, incremental = True): - self.getEntitySet().selectOnLayer(incremental) - - - #============================================================================ - # deselectOnLayer - #============================================================================ - def deselectOnLayer(self): - self.getEntitySet().deselectOnLayer() - - - #============================================================================ - # getDimPts - #============================================================================ - def getDimPts(self, destinationCrs = None): - """ - destinationCrs = sistema di coordinate in cui verrà restituito il risultato - """ - - dimPt1 = None - dimPt2 = None - - if len(self.dimStyle.componentFieldName) > 0: - # cerco tra gli elementi puntuali - for f in self.symbolFeatures: - try: - value = f.attribute(self.dimStyle.componentFieldName) - if value == QadDimComponentEnum.DIM_PT1: # primo punto da quotare ("Dimension point 1") - g = f.geometry() - if (destinationCrs is not None) and destinationCrs != self.getSymbolLayer().crs(): - g.transform(QgsCoordinateTransform(self.getSymbolLayer().crs(), destinationCrs)) # trasformo la geometria in map coordinate - - dimPt1 = g.asPoint() - elif value == QadDimComponentEnum.DIM_PT2: # secondo punto da quotare ("Dimension point 2") - g = f.geometry() - if (destinationCrs is not None) and destinationCrs != self.getSymbolLayer().crs(): - g.transform(QgsCoordinateTransform(self.getSymbolLayer().crs(), destinationCrs)) # trasformo la geometria in map coordinate - - dimPt2 = g.asPoint() - except: - return None, None - - return dimPt1, dimPt2 - - - #============================================================================ - # getDimLinePosPt - #============================================================================ - def getDimLinePosPt(self, containerGeom = None, destinationCrs = None): - """ - Trova fra i vari punti possibili un punto che indichi dove si trova la linea di quota (in destinationCrs tipicamente = map coordinate) - se containerGeom <> None il punto deve essere contenuto in containerGeom - containerGeom = può essere una QgsGeometry rappresentante un poligono (in destinationCrs tipicamente = map coordinate) contenente i punti di geom da stirare - oppure una lista dei punti da stirare (in destinationCrs tipicamente = map coordinate) - destinationCrs = sistema di coordinate in cui è espresso containerGeom e in cui verrà restituito il risultato - """ - - if len(self.dimStyle.componentFieldName) > 0: - # prima cerco tra gli elementi lineari - for f in self.linearFeatures: - try: - value = f.attribute(self.dimStyle.componentFieldName) - # primo punto da quotare ("Dimension point 1") o secondo punto da quotare ("Dimension point 2") - if value == QadDimComponentEnum.DIM_LINE1 or value == QadDimComponentEnum.DIM_LINE2: - g = f.geometry() - - if (destinationCrs is not None) and destinationCrs != self.getSymbolLayer().crs(): - g.transform(QgsCoordinateTransform(self.getLinearLayer().crs(), destinationCrs)) # trasformo la geometria in map coordinate - - pts = g.asPolyline() - if containerGeom is not None: # verifico che il punto iniziale sia interno a containerGeom - if type(containerGeom) == QgsGeometry: # geometria - if containerGeom.contains(pts[0]) == True: - return pts[0] - else: - # verifico che il punto finale sia interno a containerGeom - if containerGeom.contains(pts[-1]) == True: - return pts[-1] - elif type(containerGeom) == list: # lista di punti - for containerPt in containerGeom: - if ptNear(containerPt, pts[0]): # se i punti sono sufficientemente vicini - return pts[0] - else: - # verifico il punto finale - if ptNear(containerPt,pts[-1]): - return pts[-1] - else: - return pts[0] # punto iniziale - except: - return None - - # poi cerco tra gli elementi puntuali - for f in self.symbolFeatures: - try: - value = f.attribute(self.dimStyle.componentFieldName) - # primo blocco della freccia ("Block 1") o secondo blocco della freccia ("Block 2") - if value == QadDimComponentEnum.BLOCK1 or value == QadDimComponentEnum.BLOCK2: - g = f.geometry() - - if (destinationCrs is not None) and destinationCrs != self.getSymbolLayer().crs(): - g.transform(QgsCoordinateTransform(self.getSymbolLayer().crs(), destinationCrs)) # trasformo la geometria in map coordinate - - dimLinePosPt = g.asPoint() - if containerGeom is not None: # verifico che il punto sia interno a containerGeom - if type(containerGeom) == QgsGeometry: # geometria - if containerGeom.contains(dimLinePosPt) == True: - return dimLinePosPt - elif type(containerGeom) == list: # lista di punti - for containerPt in containerGeom: - if ptNear(containerPt, dimLinePosPt): # se i punti sono sufficientemente vicini - return dimLinePosPt - else: - return dimLinePosPt - except: - return None - - return None - - - #============================================================================ - # getDimLinearAlignment - #============================================================================ - def getDimLinearAlignment(self): - dimLinearAlignment = None - dimLineRotation = None - Pts = [] - - if len(self.dimStyle.componentFieldName) > 0: - # prima cerco tra gli elementi lineari - for f in self.linearFeatures: - try: - value = f.attribute(self.dimStyle.componentFieldName) - if value == QadDimComponentEnum.DIM_LINE1: # primo punto da quotare ("Dimension point 1") - Pts = f.geometry().asPolyline() - break - elif value == QadDimComponentEnum.DIM_LINE2: # secondo punto da quotare ("Dimension point 2") - Pts = f.geometry().asPolyline() - break - except: - return None, None - - if Pts is None: - # poi cerco tra gli elementi puntuali - for f in self.symbolFeatures: - try: - value = f.attribute(self.dimStyle.componentFieldName) - if value == QadDimComponentEnum.BLOCK1: # primo blocco della freccia ("Block 1") - Pts.append(f.geometry().asPoint()) - elif value == QadDimComponentEnum.BLOCK2: # secondo blocco della freccia ("Block 1") - Pts.append(f.geometry().asPoint()) - except: - return None, None - - if len(Pts) > 1: # almeno 2 punti - if qad_utils.doubleNear(Pts[0].x(), Pts[-1].x()): # linea verticale (stessa x) - dimLinearAlignment = QadDimStyleAlignmentEnum.VERTICAL - dimLineRotation = 0 - elif qad_utils.doubleNear(Pts[0].y(), Pts[-1].y()): # linea orizzontale (stessa y) - dimLinearAlignment = QadDimStyleAlignmentEnum.HORIZONTAL - dimLineRotation = 0 - else: - dimLinearAlignment = QadDimStyleAlignmentEnum.HORIZONTAL - dimLineRotation = qad_utils.getAngleBy2Pts(Pts[0], Pts[-1]) - - - return dimLinearAlignment, dimLineRotation - - - #============================================================================ - # getTextRot - #============================================================================ - def getTextRot(self): - textRot = None - - if len(self.dimStyle.rotFieldName) > 0: - try: - textRot = self.textualFeature.attribute(self.dimStyle.rotFieldName) - except: - return None - - return qad_utils.toRadians(textRot) - - - #============================================================================ - # getTextValue - #============================================================================ - def getTextValue(self): - textValue = None - - # se il testo dipende da un solo campo - labelFieldNames = qad_label.get_labelFieldNames(self.dimStyle.getTextualLayer()) - if len(labelFieldNames) == 1 and len(labelFieldNames[0]) > 0: - try: - textValue = self.textualFeature.attribute(labelFieldNames[0]) - except: - return None - - return textValue - - - #============================================================================ - # getTextPt - #============================================================================ - def getTextPt(self, destinationCrs = None): - # destinationCrs = sistema di coordinate in cui verrà restituito il risultato - g = self.textualFeature.geometry() - if (destinationCrs is not None) and destinationCrs != self.getTextualLayer().crs(): - g.transform(QgsCoordinateTransform(self.getTextualLayer().crs(), destinationCrs)) # trasformo la geometria in map coordinate - - return g.asPoint() - - - #============================================================================ - # isCalculatedText - #============================================================================ - def isCalculatedText(self): - measure = self.getTextValue() - - if self.dimStyle.dimType == QadDimTypeEnum.ALIGNED: # quota lineare allineata ai punti di origine delle linee di estensione - dimPt1, dimPt2 = self.getDimPts() - return measure == self.dimStyle.getFormattedText(qad_utils.getDistance(dimPt1, dimPt2)) - elif self.dimStyle.dimType == QadDimTypeEnum.LINEAR: # quota lineare con una linea di quota orizzontale o verticale - dimPt1, dimPt2 = self.getDimPts() - linePosPt = self.getDimLinePosPt() - preferredAlignment, dimLineRotation = self.getDimLinearAlignment() - - dimLine = self.dimStyle.getDimLine(dimPt1, dimPt2, linePosPt, preferredAlignment, dimLineRotation) - return measure == self.dimStyle.getFormattedText(qad_utils.getDistance(dimLine[0], dimLine[1])) - - return True - - - #============================================================================ - # move - #============================================================================ - def move(self, offSetX, offSetY): - # offSetX = spostamento X in map coordinate - # offSetY = spostamento Y in map coordinate - destinationCrs = plugIn.canvas.mapRenderer().destinationCrs() - - g = self.textualFeature.geometry() - - if (destinationCrs is not None) and destinationCrs != self.getTextualLayer().crs(): - g.transform(QgsCoordinateTransform(self.getTextualLayer().crs(), destinationCrs)) # trasformo la geometria in map coordinate - - g = qad_utils.moveQgsGeometry(g, offSetX, offSetY) - - if (destinationCrs is not None) and destinationCrs != self.getTextualLayer().crs(): - g.transform(QgsCoordinateTransform(destinationCrs, self.getTextualLayer().crs())) # trasformo la geometria in layer coordinate - - self.textualFeature.setGeometry(g) - - - for f in self.linearFeatures: - g = f.geometry() - - if (destinationCrs is not None) and destinationCrs != self.getLinearLayer().crs(): - g.transform(QgsCoordinateTransform(self.getLinearLayer().crs(), destinationCrs)) # trasformo la geometria in map coordinate - - g = qad_utils.moveQgsGeometry(g, offSetX, offSetY) - - if (destinationCrs is not None) and destinationCrs != self.getLinearLayer().crs(): - g.transform(QgsCoordinateTransform(destinationCrs, self.getLinearLayer().crs())) # trasformo la geometria in layer coordinate - - f.setGeometry(g) - - for f in self.symbolFeatures: - g = f.geometry() - - if (destinationCrs is not None) and destinationCrs != self.getSymbolLayer().crs(): - g.transform(QgsCoordinateTransform(self.getSymbolLayer().crs(), destinationCrs)) # trasformo la geometria in map coordinate - - g = qad_utils.moveQgsGeometry(g, offSetX, offSetY) - - if (destinationCrs is not None) and destinationCrs != self.getSymbolLayer().crs(): - g.transform(QgsCoordinateTransform(destinationCrs, self.getSymbolLayer().crs())) # trasformo la geometria in layer coordinate - - f.setGeometry(g) - - - #============================================================================ - # rotate - #============================================================================ - def rotate(self, plugIn, basePt, angle): - # basePt = punto base espresso in map coordinate - destinationCrs = plugIn.canvas.mapRenderer().destinationCrs() - - measure = self.getTextValue() - - if self.dimStyle.dimType == QadDimTypeEnum.ALIGNED: # quota lineare allineata ai punti di origine delle linee di estensione - dimPt1, dimPt2 = self.getDimPts(destinationCrs) - linePosPt = self.getDimLinePosPt(None, destinationCrs) - if (dimPt1 is not None) and (dimPt2 is not None) and \ - (linePosPt is not None): - dimPt1 = qad_utils.rotatePoint(dimPt1, basePt, angle) - dimPt2 = qad_utils.rotatePoint(dimPt2, basePt, angle) - linePosPt = qad_utils.rotatePoint(linePosPt, basePt, angle) - - dimEntity, textOffsetRect = self.dimStyle.getAlignedDimFeatures(plugIn.canvas, \ - dimPt1, \ - dimPt2, \ - linePosPt, \ - measure) - self.set(dimEntity) - elif self.dimStyle.dimType == QadDimTypeEnum.LINEAR: # quota lineare con una linea di quota orizzontale o verticale - dimPt1, dimPt2 = self.getDimPts(destinationCrs) - linePosPt = self.getDimLinePosPt(None, destinationCrs) - preferredAlignment, dimLineRotation = self.getDimLinearAlignment() - if (dimPt1 is not None) and (dimPt2 is not None) and \ - (linePosPt is not None) and \ - (preferredAlignment is not None) and (dimLineRotation is not None): - textForcedRot = self.getTextRot() - if textForcedRot is not None: - self.dimStyle.textForcedRot = textForcedRot - - dimPt1 = qad_utils.rotatePoint(dimPt1, basePt, angle) - dimPt2 = qad_utils.rotatePoint(dimPt2, basePt, angle) - linePosPt = qad_utils.rotatePoint(linePosPt, basePt, angle) - dimLinearAlignment, dimLineRotation = self.getDimLinearAlignment() - - if dimLinearAlignment == QadDimStyleAlignmentEnum.VERTICAL: - dimLineRotation = math.pi / 2 - dimLinearAlignment = QadDimStyleAlignmentEnum.HORIZONTAL - dimLineRotation = dimLineRotation + angle - - dimEntity, textOffsetRect = self.dimStyle.getLinearDimFeatures(plugIn.canvas, \ - dimPt1, \ - dimPt2, \ - linePosPt, \ - measure, \ - dimLinearAlignment, \ - dimLineRotation) - self.set(dimEntity) - - - #============================================================================ - # scale - #============================================================================ - def scale(self, plugIn, basePt, scale): - # basePt = punto base espresso in map coordinate - destinationCrs = plugIn.canvas.mapRenderer().destinationCrs() - - measure = None if self.isCalculatedText() else self.getTextValue() - - if self.dimStyle.dimType == QadDimTypeEnum.ALIGNED: # quota lineare allineata ai punti di origine delle linee di estensione - dimPt1, dimPt2 = self.getDimPts(destinationCrs) - linePosPt = self.getDimLinePosPt(None, destinationCrs) - if (dimPt1 is not None) and (dimPt2 is not None) and \ - (linePosPt is not None): - dimPt1 = qad_utils.scalePoint(dimPt1, basePt, scale) - dimPt2 = qad_utils.scalePoint(dimPt2, basePt, scale) - linePosPt = qad_utils.scalePoint(linePosPt, basePt, scale) - - dimEntity, textOffsetRect = self.dimStyle.getAlignedDimFeatures(plugIn.canvas, \ - dimPt1, \ - dimPt2, \ - linePosPt, \ - measure) - self.set(dimEntity) - elif self.dimStyle.dimType == QadDimTypeEnum.LINEAR: # quota lineare con una linea di quota orizzontale o verticale - dimPt1, dimPt2 = self.getDimPts(destinationCrs) - linePosPt = self.getDimLinePosPt(None, destinationCrs) - preferredAlignment, dimLineRotation = self.getDimLinearAlignment() - if (dimPt1 is not None) and (dimPt2 is not None) and \ - (linePosPt is not None) and \ - (preferredAlignment is not None) and (dimLineRotation is not None): - textForcedRot = self.getTextRot() - if textForcedRot is not None: - self.dimStyle.textForcedRot = textForcedRot - - dimPt1 = qad_utils.scalePoint(dimPt1, basePt, scale) - dimPt2 = qad_utils.scalePoint(dimPt2, basePt, scale) - linePosPt = qad_utils.scalePoint(linePosPt, basePt, scale) - dimLinearAlignment, dimLineRotation = self.getDimLinearAlignment() - - if dimLinearAlignment == QadDimStyleAlignmentEnum.VERTICAL: - dimLineRotation = math.pi / 2 - dimLinearAlignment = QadDimStyleAlignmentEnum.HORIZONTAL - - dimEntity, textOffsetRect = self.dimStyle.getLinearDimFeatures(plugIn.canvas, \ - dimPt1, \ - dimPt2, \ - linePosPt, \ - measure, \ - dimLinearAlignment, \ - dimLineRotation) - self.set(dimEntity) - - - #============================================================================ - # mirror - #============================================================================ - def mirror(self, plugIn, mirrorPt, mirrorAngle): - # mirrorPt = punto base espresso in map coordinate - destinationCrs = plugIn.canvas.mapRenderer().destinationCrs() - - measure = None if self.isCalculatedText() else self.getTextValue() - - if self.dimStyle.dimType == QadDimTypeEnum.ALIGNED: # quota lineare allineata ai punti di origine delle linee di estensione - dimPt1, dimPt2 = self.getDimPts(destinationCrs) - linePosPt = self.getDimLinePosPt(None, destinationCrs) - if (dimPt1 is not None) and (dimPt2 is not None) and \ - (linePosPt is not None): - dimPt1 = qad_utils.mirrorPoint(dimPt1, mirrorPt, mirrorAngle) - dimPt2 = qad_utils.mirrorPoint(dimPt2, mirrorPt, mirrorAngle) - linePosPt = qad_utils.mirrorPoint(linePosPt, mirrorPt, mirrorAngle) - - dimEntity, textOffsetRect = self.dimStyle.getAlignedDimFeatures(plugIn.canvas, \ - dimPt1, \ - dimPt2, \ - linePosPt, \ - measure) - self.set(dimEntity) - elif self.dimStyle.dimType == QadDimTypeEnum.LINEAR: # quota lineare con una linea di quota orizzontale o verticale - dimPt1, dimPt2 = self.getDimPts(destinationCrs) - linePosPt = self.getDimLinePosPt(None, destinationCrs) - preferredAlignment, dimLineRotation = self.getDimLinearAlignment() - if (dimPt1 is not None) and (dimPt2 is not None) and \ - (linePosPt is not None) and \ - (preferredAlignment is not None) and (dimLineRotation is not None): - textForcedRot = self.getTextRot() - if textForcedRot is not None: - self.dimStyle.textForcedRot = textForcedRot - - dimPt1 = qad_utils.mirrorPoint(dimPt1, mirrorPt, mirrorAngle) - dimPt2 = qad_utils.mirrorPoint(dimPt2, mirrorPt, mirrorAngle) - linePosPt = qad_utils.mirrorPoint(linePosPt, mirrorPt, mirrorAngle) - dimLinearAlignment, dimLineRotation = self.getDimLinearAlignment() - - if dimLinearAlignment == QadDimStyleAlignmentEnum.VERTICAL: - dimLineRotation = math.pi / 2 - dimLinearAlignment = QadDimStyleAlignmentEnum.HORIZONTAL - - ptDummy = qad_utils.getPolarPointByPtAngle(mirrorPt, dimLineRotation, 1) - ptDummy = qad_utils.mirrorPoint(ptDummy, mirrorPt, mirrorAngle) - dimLineRotation = qad_utils.getAngleBy2Pts(mirrorPt, ptDummy) - - dimEntity, textOffsetRect = self.dimStyle.getLinearDimFeatures(plugIn.canvas, \ - dimPt1, \ - dimPt2, \ - linePosPt, \ - measure, \ - dimLinearAlignment, \ - dimLineRotation) - self.set(dimEntity) - - - #============================================================================ - # stretch - #============================================================================ - def stretch(self, plugIn, containerGeom, offSetX, offSetY): - """ - containerGeom = può essere una QgsGeometry rappresentante un poligono contenente i punti di geom da stirare - oppure una lista dei punti da stirare espressi in map coordinate - offSetX = spostamento X in map coordinate - offSetY = spostamento Y in map coordinate - """ - destinationCrs = plugIn.canvas.mapRenderer().destinationCrs() - - measure = None if self.isCalculatedText() else self.getTextValue() - - if self.dimStyle.dimType == QadDimTypeEnum.ALIGNED: # quota lineare allineata ai punti di origine delle linee di estensione - dimPt1, dimPt2 = self.getDimPts(destinationCrs) - linePosPt = self.getDimLinePosPt(containerGeom, destinationCrs) - - if dimPt1 is not None: - newPt = qad_stretch_fun.stretchPoint(dimPt1, containerGeom, offSetX, offSetY) - if newPt is not None: - dimPt1 = newPt - - if dimPt2 is not None: - newPt = qad_stretch_fun.stretchPoint(dimPt2, containerGeom, offSetX, offSetY) - if newPt is not None: - dimPt2 = newPt - - if linePosPt is not None: - newPt = qad_stretch_fun.stretchPoint(linePosPt, containerGeom, offSetX, offSetY) - if newPt is not None: - linePosPt = newPt - else: - linePosPt = self.getDimLinePosPt() - # verifico se è stato coinvolto il testo della quota - if qad_stretch_fun.isPtContainedForStretch(self.getTextPt(destinationCrs), containerGeom): - if linePosPt is not None: - linePosPt = qad_utils.movePoint(linePosPt, offSetX, offSetY) - - if (dimPt1 is not None) and (dimPt2 is not None) and \ - (linePosPt is not None): - dimEntity, textOffsetRect = self.dimStyle.getAlignedDimFeatures(plugIn.canvas, \ - dimPt1, \ - dimPt2, \ - linePosPt, \ - measure) - self.set(dimEntity) - elif self.dimStyle.dimType == QadDimTypeEnum.LINEAR: # quota lineare con una linea di quota orizzontale o verticale - dimPt1, dimPt2 = self.getDimPts(destinationCrs) - linePosPt = self.getDimLinePosPt(containerGeom, destinationCrs) - - dimLinearAlignment, dimLineRotation = self.getDimLinearAlignment() - - if dimPt1 is not None: - newPt = qad_stretch_fun.stretchPoint(dimPt1, containerGeom, offSetX, offSetY) - if newPt is not None: - dimPt1 = newPt - - if dimPt2 is not None: - newPt = qad_stretch_fun.stretchPoint(dimPt2, containerGeom, offSetX, offSetY) - if newPt is not None: - dimPt2 = newPt - - if linePosPt is not None: - newPt = qad_stretch_fun.stretchPoint(linePosPt, containerGeom, offSetX, offSetY) - if newPt is not None: - linePosPt = newPt - else: - linePosPt = self.getDimLinePosPt() - # verifico se è stato coinvolto il testo della quota - if qad_stretch_fun.isPtContainedForStretch(self.getTextPt(destinationCrs), containerGeom): - if linePosPt is not None: - linePosPt = qad_utils.movePoint(linePosPt, offSetX, offSetY) - - if (dimPt1 is not None) and (dimPt2 is not None) and \ - (linePosPt is not None) and \ - (dimLinearAlignment is not None) and (dimLineRotation is not None): - textForcedRot = self.getTextRot() - if textForcedRot is not None: - self.dimStyle.textForcedRot = textForcedRot - - if dimLinearAlignment == QadDimStyleAlignmentEnum.VERTICAL: - dimLineRotation = math.pi / 2 - dimLinearAlignment = QadDimStyleAlignmentEnum.HORIZONTAL - - dimEntity, textOffsetRect = self.dimStyle.getLinearDimFeatures(plugIn.canvas, \ - dimPt1, \ - dimPt2, \ - linePosPt, \ - measure, \ - dimLinearAlignment, \ - dimLineRotation) - self.set(dimEntity) - - - #============================================================================ - # getDimComponentByEntity - #============================================================================ - def getDimComponentByEntity(self, entity): - """ - La funzione, data un'entità, restituisce il componente della quotatura. - """ - if entity.layer == self.getTextualLayer(): - return QadDimComponentEnum.TEXT_PT - elif entity.layer == self.getLinearLayer() or \ - entity.layer == self.getSymbolLayer(): - try: - return entity.getFeature().attribute(self.dimStyle.componentFieldName) - except: - return None - - return None - - -#=============================================================================== -# = variabile globale -#=============================================================================== - +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin ok + + classe per la gestione delle quote + + ------------------- + begin : 2014-02-20 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from qgis.PyQt.QtCore import * +from qgis.PyQt.QtGui import * +from qgis.core import * +from qgis.gui import * +import qgis.utils + +import os +import codecs +import math +import sys + + +from .qad_msg import QadMsg +from . import qad_utils +from .qad_line import getBoundingPtsOnOnInfinityLine, QadLine +from .qad_arc import QadArc +from .qad_geom_relations import * +from . import qad_stretch_fun +from . import qad_layer +from . import qad_label +from .qad_entity import * +from .qad_variables import QadVariables +from .qad_multi_geom import fromQgsGeomToQadGeom, fromQadGeomToQgsGeom + + +""" +La classe quotatura é composta da tre layer: testo, linea, simbolo con lo stesso sistema di coordinate. + +Il layer testo deve avere tutte le caratteristiche del layer testo di QAD ed in più: +- il posizionamento dell'etichetta con modalita "Intorno al punto" con distanza = 0 + (che vuol dire punto di inserimento in basso a sx) +- la dimensione del testo in unità mappa (la dimensione varia a seconda dello zoom). +- dimStyleFieldName = "dim_style"; nome del campo che contiene il nome dello stile di quota (opzionale) +- dimTypeFieldName = "dim_type"; nome del campo che contiene il tipo dello stile di quota (opzionale) +- l'opzione "Mostra etichette capovolte" deve essere su "sempre" nel tab "Etichette"->"Visualizzazione" +- rotFieldName = "rot"; nome del campo che contiene la rotazione del testo +- la rotazione deve essere letta dal campo indicato da rotFieldName +- idFieldName = "id"; nome del campo che contiene il codice della quota (opzionale) +- la rotazione deve essere derivata dal campo rotFieldName +- il font del carattere può essere derivata da un campo +- la dimensione del carattere può essere derivata da un campo +- il colore del testo può essere derivato da un campo (opzionale) + + +Il layer simbolo deve avere tutte le caratteristiche del layer simbolo di QAD ed in più: +- il simbolo freccia con rotazione 0 deve essere orizzontale con la freccia rivolta verso destra + ed il suo punto di inserimento deve essere sulla punta della freccia +- la dimensione del simbolo in unità mappa (la dimensione varia a seconda dello zoom), + impostare la dimensione del simbolo in modo che la larghezza della freccia sia 1 unità di mappa. +- componentFieldName = "type"; nome del campo che contiene il tipo di componente della quota (vedi QadDimComponentEnum) (opzionale) +- symbolFieldName = "block"; nome del campo che contiene il nome del simbolo (opzionale) +- idParentFieldName = "id_parent"; nome del campo che contiene il codice del testo della quota (opzionale) +- scaleFieldName = "scale"; nome del campo che contiene il fattore di scala del simbolo (opzionale) + se usato usare lo stile "singolo simbolo" (unico che consente di impostare la scala come diametro scala) + la scala deve essere impostata su attraverso Stile->avanzato->campo di dimensione della scala-> + la modalità di scala deve essere impostata su attraverso Stile->avanzato->campo di dimensione della scala->diametro scala +- rotFieldName = "rot"; nome del campo che contiene la rotazione del simbolo + la rotazione deve essere letta dal campo indicato da rotFieldName (360-rotFieldName) + +Il layer linea deve avere tutte le caratteristiche del layer linea ed in più: +- componentFieldName = "type"; nome del campo che contiene il tipo di componente della quota (vedi QadDimComponentEnum) (opzionale) +- lineTypeFieldName = "line_type"; nome del campo che contiene il tipolinea (opzionale) +- colorFieldName = "color"; nome del campo che contiene il colore 'r,g,b,alpha'; alpha é opzionale (0=trasparente, 255=opaco) (opzionale) +- idParentFieldName = "id_parent"; nome del campo che contiene il codice del testo della quota (opzionale) + +""" + + +# =============================================================================== +# QadDimTypeEnum class. +# =============================================================================== +class QadDimTypeEnum(): + ALIGNED = "AL" # quota lineare allineata ai punti di origine delle linee di estensione + ANGULAR = "AN" # quota angolare, misura l'angolo tra i 3 punti o tra gli oggetti selezionati + BASE_LINE = "BL" # quota lineare, angolare o coordinata a partire dalla linea di base della quota precedente o di una quota selezionata + DIAMETER = "DI" # quota per il diametro di un cerchio o di un arco + LEADER = "LD" # crea una linea che consente di collegare un'annotazione ad una lavorazione + LINEAR = "LI" # quota lineare con una linea di quota orizzontale o verticale + RADIUS = "RA" # quota radiale, misura il raggio di un cerchio o di un arco selezionato e visualizza il testo di quota con un simbolo di raggio davanti + ARC_LENTGH = "AR" # quota per la lunghezza di un arco + + +# =============================================================================== +# QadDimComponentEnum class. +# =============================================================================== +class QadDimComponentEnum(): + DIM_LINE1 = "D1" # linea di quota ("Dimension line 1") + DIM_LINE2 = "D2" # linea di quota ("Dimension line 2") + DIM_LINE_EXT1 = "X1" # estensione della linea di quota ("Dimension line eXtension 1") + DIM_LINE_EXT2 = "X2" # estensione della linea di quota ("Dimension line eXtension 2") + EXT_LINE1 = "E1" # prima linea di estensione ("Extension line 1") + EXT_LINE2 = "E2" # seconda linea di estensione ("Extension line 2") + LEADER_LINE = "L" # linea porta quota usata quando il testo é fuori dalla quota ("Leader") + ARC_LEADER_LINE = "AL" # linea porta quota usata per collegare il testo di quota con l'arco da quotare (vedi "dimarc" opzione "leader") + BLOCK1 = "B1" # primo blocco della freccia ("Block 1") + BLOCK2 = "B2" # secondo blocco della freccia ("Block 2") + LEADER_BLOCK = "LB" # blocco della freccia nel caso leader ("Leader Block") + ARC_BLOCK = "AB" # simbolo dell'arco ("Arc Block") + DIM_PT1 = "D1" # primo punto da quotare ("Dimension point 1") + DIM_PT2 = "D2" # secondo punto da quotare ("Dimension point 2") + TEXT_PT = "T" # punto del testo di quota ("Text") + CENTER_MARKER_LINE = "CL" # linea che definisce il marcatore del centro di un arco o di un cerchio + + +# =============================================================================== +# QadDimStyleAlignmentEnum class. +# =============================================================================== +class QadDimStyleAlignmentEnum(): + HORIZONTAL = 0 # orizzontale + VERTICAL = 1 # verticale + ALIGNED = 2 # allineata + FORCED_ROTATION = 3 # rotazione forzata + + +# =============================================================================== +# QadDimStyleTxtVerticalPosEnum class. +# =============================================================================== +class QadDimStyleTxtVerticalPosEnum(): + CENTERED_LINE = 0 # testo centrato alla linea di quota + ABOVE_LINE = 1 # testo sopra alla linea di quota ma nel caso la linea di quota non sia orizzontale + # e il testo sia dentro le linee di estensione e forzato orizzontale allora il testo diventa centrato + EXTERN_LINE = 2 # testo posizionato nella parte opposta ai punti di quotatura + BELOW_LINE = 4 # testo sotto alla linea di quota ma nel caso la linea di quota non sia orizzontale + # e il testo sia dentro le linee di estensione e forzato orizzontale allora il testo diventa centrato + + +# =============================================================================== +# QadDimStyleTxtHorizontalPosEnum class. +# =============================================================================== +class QadDimStyleTxtHorizontalPosEnum(): + CENTERED_LINE = 0 # testo centrato alla linea di quota + FIRST_EXT_LINE = 1 # testo vicino alla prima linea di estensione + SECOND_EXT_LINE = 2 # testo vicino alla seconda linea di estensione + FIRST_EXT_LINE_UP = 3 # testo sopra e allineato alla prima linea di estensione + SECOND_EXT_LINE_UP = 4 # testo sopra e allineato alla seconda linea di estensione + + +# =============================================================================== +# QadDimStyleTxtRotEnum class. +# =============================================================================== +class QadDimStyleTxtRotModeEnum(): + HORIZONTAL = 0 # testo orizzontale + ALIGNED_LINE = 1 # testo allineato con la linea di quota + ISO = 2 # testo allineato con la linea di quota se tra le linee di estensione, + # altrimenti testo orizzontale + FORCED_ROTATION = 3 # testo con rotazione forzata + + +# =============================================================================== +# QadDimStyleArcSymbolPosEnum class. +# =============================================================================== +class QadDimStyleArcSymbolPosEnum(): + BEFORE_TEXT = 0 # simbolo prima del testo + ABOVE_TEXT = 1 # simbolo sopra il testo + NONE = 2 # niente simbolo + + +# =============================================================================== +# QadDimStyleArcSymbolPosEnum class. +# =============================================================================== +class QadDimStyleTxtDirectionEnum(): + SX_TO_DX = 0 # da sinistra a destra + DX_TO_SX = 1 # da destra a sinistra + + +# =============================================================================== +# QadDimStyleTextBlocksAdjustEnum class. +# =============================================================================== +class QadDimStyleTextBlocksAdjustEnum(): + BOTH_OUTSIDE_EXT_LINES = 0 # sposta testo e frecce fuori dalle linee di estensione + FIRST_BLOCKS_THEN_TEXT = 1 # sposta prima le frecce poi, se non basta, anche il testo + FIRST_TEXT_THEN_BLOCKS = 2 # sposta prima il testo poi, se non basta, anche le frecce + WHICHEVER_FITS_BEST = 3 # Sposta indistintamente il testo o le frecce (l'oggetto che si adatta meglio) + + +# =============================================================================== +# QadDim dimension style class +# =============================================================================== +class QadDimStyle(): + + def __init__(self, dimStyle = None): + self.name = "standard" # nome dello stile + self.description = "" + self.path = "" # percorso e nome del file in cui è stato salvato/caricato + self.dimType = QadDimTypeEnum.ALIGNED # tipo di quotatura + + # testo di quota + self.textPrefix = "" # prefisso per il testo della quota + self.textSuffix = "" # suffisso per il testo della quota + self.textSuppressLeadingZeros = False # per sopprimere o meno gli zero all'inizio del testo + self.textDecimalZerosSuppression = True # per sopprimere gli zero finali nei decimali + self.textHeight = 1.0 # altezza testo (DIMTXT) in unità di mappa + self.textVerticalPos = QadDimStyleTxtVerticalPosEnum.ABOVE_LINE # posizione verticale del testo rispetto la linea di quota (DIMTAD) + self.textHorizontalPos = QadDimStyleTxtHorizontalPosEnum.CENTERED_LINE # posizione orizzontale del testo rispetto la linea di quota (DIMTAD) + self.textOffsetDist = 0.5 # distanza aggiunta intorno al testo quando per inserirlo viene spezzata la linea di quota (DIMGAP) + self.textRotMode = QadDimStyleTxtRotModeEnum.ALIGNED_LINE # modalità di rotazione del testo (DIMTIH e DIMTOH) + self.textForcedRot = 0.0 # rotazione forzata del testo + self.textDecimals = 2 # numero di decimali (DIMDEC) + self.textDecimalSep = "." # Separatore dei decimali (DIMDSEP) + self.textFont = "Arial" # nome del font di testo (DIMTXSTY) + self.textColor = "255,255,255,255" # Colore per i testi della quota (DIMCLRT); bianco con opacità totale + self.textDirection = QadDimStyleTxtDirectionEnum.SX_TO_DX # specifica la direzione del testo di quota (DIMTXTDIRECTION) 0 = da sx a dx, 1 = da dx a sx + self.arcSymbPos = QadDimStyleArcSymbolPosEnum.BEFORE_TEXT # disegna o meno il simbolo dell'arco con DIMARC (DIMARCSYM). + + # linee di quota + self.dimLine1Show = True # Mostra o nasconde la prima linea di quota (DIMSD1) + self.dimLine2Show = True # Mostra o nasconde la seconda linea di quota (DIMSD2) + self.dimLineLineType = "continuous" # Tipo di linea per le linee di quota (DIMLTYPE) + self.dimLineColor = "255,255,255,255" # Colore per le linee di quota (DIMCLRD); bianco con opacità totale + self.dimLineSpaceOffset = 3.75 # Controlla la spaziatura delle linee di quota nelle quote da linea di base (DIMDLI) + self.dimLineOffsetExtLine = 0.0 # distanza della linea di quota oltre la linea di estensione (DIMDLE) + + + # simboli per linee di quota + # il blocco per la freccia é una freccia verso destra con il punto di inserimento sulla punta della freccia + self.block1Name = "triangle2" # nome del simbolo da usare come punta della freccia sulla prima linea di quota (DIMBLK1) + self.block2Name = "triangle2" # nome del simbolo da usare come punta della freccia sulla seconda linea di quota (DIMBLK2) + self.blockLeaderName = "triangle2" # nome del simbolo da usare come punta della freccia sulla linea della direttrice (DIMLDRBLK) + self.blockWidth = 0.5 # larghezza del simbolo (in orizzontale) quando la dimensione in unità di mappa = 1 (vedi "triangle2") + self.blockScale = 1.0 # scala della dimensione del simbolo (DIMASZ) + self.centerMarkSize = 0.0 # disegna o meno il marcatore di centro o le linee d'asse per le quote create con + # DIMCENTER, DIMDIAMETER, e DIMRADIUS (DIMCEN). + # 0 = niente, > 0 dimensione marcatore di centro, < 0 dimensione linee d'asse + + # adattamento del testo e delle frecce + self.textBlockAdjust = QadDimStyleTextBlocksAdjustEnum.WHICHEVER_FITS_BEST # (DIMATFIT) + self.blockSuppressionForNoSpace = False # Sopprime le punte della frecce se non c'é spazio sufficiente all'interno delle linee di estensione (DIMSOXD) + + # linee di estensione + self.extLine1Show = True # Mostra o nasconde la prima linea di estensione (DIMSE1) + self.extLine2Show = True # Mostra o nasconde la seconda linea di estensione (DIMSE2) + self.extLine1LineType = "continuous" # Tipo di linea per la prima linea di estensione (DIMLTEX1) + self.extLine2LineType = "continuous" # Tipo di linea per la seconda linea di estensione (DIMLTEX2) + self.extLineColor = "255,255,255,255" # Colore per le linee di estensione (DIMCLRE); bianco con opacità totale + self.extLineOffsetDimLine = 0.0 # distanza della linea di estensione oltre la linea di quota (DIMEXE) + self.extLineOffsetOrigPoints = 0.0 # distanza della linea di estensione dai punti da quotare (DIMEXO) + self.extLineIsFixedLen = False # Attiva lunghezza fissa delle line di estensione (DIMFXLON) + self.extLineFixedLen = 1.0 # lunghezza fissa delle line di estensione (DIMFXL) dalla linea di quota + # al punto da quotare spostato di extLineOffsetOrigPoints + # (la linea di estensione non va oltre il punto da quotare) + + # layer e loro caratteristiche + # devo allocare i campi a livello di classe QadDimStyle perché QgsFeature.setFields usa solo il puntatore alla lista fields + # che, se allocata privatamente in qualsiasi funzione, all'uscita della funzione verrebbe distrutta + self.textualLayerName = None # nome layer per memorizzare il testo della quota + self.__textualLayer = None # layer per memorizzare il testo della quota + self.__textFields = None + self.__textualFeaturePrototype = None + + self.linearLayerName = None # nome layer per memorizzare le linee della quota + self.__linearLayer = None # layer per memorizzare le linee della quota + self.__lineFields = None + self.__linearFeaturePrototype = None + + self.symbolLayerName = None # nome layer per memorizzare i blocchi delle frecce della quota + self.__symbolLayer = None # layer per memorizzare i blocchi delle frecce della quota + self.__symbolFields = None + self.__symbolFeaturePrototype = None + + self.componentFieldName = "type" # nome del campo che contiene il tipo di componente della quota (vedi QadDimComponentEnum) + self.lineTypeFieldName = "line_type" # nome del campo che contiene il tipolinea + self.colorFieldName = "color" # nome del campo che contiene il colore 'r,g,b,alpha'; alpha é opzionale (0=trasparente, 255=opaco) + self.idFieldName = "id" # nome del campo che contiene il codice del della quota nel layer di tipo testo + self.idParentFieldName = "id_parent" # nome del campo che contiene il codice della quota nei layer simbolo e linea + self.dimStyleFieldName = "dim_style" # nome del campo che contiene il nome dello stile di quota + self.dimTypeFieldName = "dim_type" # nome del campo che contiene il tipo dello stile di quota + self.symbolFieldName = "block" # nome del campo che contiene il nome del simbolo + self.scaleFieldName = "scale" # nome del campo che contiene la dimensione + self.rotFieldName = "rot" # nome del campo che contiene rotazione in gradi + + if dimStyle is None: + return + self.set(dimStyle) + + + # ============================================================================ + # FUNZIONI GENERICHE - INIZIO + # ============================================================================ + + def set(self, dimStyle): + self.name = dimStyle.name + self.description = dimStyle.description + self.path = dimStyle.path + self.dimType = dimStyle.dimType + + # testo di quota + self.textPrefix = dimStyle.textPrefix + self.textSuffix = dimStyle.textSuffix + self.textSuppressLeadingZeros = dimStyle.textSuppressLeadingZeros + self.textDecimalZerosSuppression = dimStyle.textDecimalZerosSuppression + self.textHeight = dimStyle.textHeight + self.textVerticalPos = dimStyle.textVerticalPos + self.textHorizontalPos = dimStyle.textHorizontalPos + self.textOffsetDist = dimStyle.textOffsetDist + self.textRotMode = dimStyle.textRotMode + self.textForcedRot = dimStyle.textForcedRot + self.textDecimals = dimStyle.textDecimals + self.textDecimalSep = dimStyle.textDecimalSep + self.textFont = dimStyle.textFont + self.textColor = dimStyle.textColor + self.textDirection = dimStyle.textDirection + self.arcSymbPos = dimStyle.arcSymbPos + + # linee di quota + self.dimLine1Show = dimStyle.dimLine1Show + self.dimLine2Show = dimStyle.dimLine2Show + self.dimLineLineType = dimStyle.dimLineLineType + self.dimLineColor = dimStyle.dimLineColor + self.dimLineSpaceOffset = dimStyle.dimLineSpaceOffset + self.dimLineOffsetExtLine = dimStyle.dimLineOffsetExtLine + + # simboli per linee di quota + self.block1Name = dimStyle.block1Name + self.block2Name = dimStyle.block2Name + self.blockLeaderName = dimStyle.blockLeaderName + self.blockWidth = dimStyle.blockWidth + self.blockScale = dimStyle.blockScale + self.blockSuppressionForNoSpace = dimStyle.blockSuppressionForNoSpace + self.centerMarkSize = dimStyle.centerMarkSize + + # adattamento del testo e delle frecce + self.textBlockAdjust = dimStyle.textBlockAdjust + + # linee di estensione + self.extLine1Show = dimStyle.extLine1Show + self.extLine2Show = dimStyle.extLine2Show + self.extLine1LineType = dimStyle.extLine1LineType + self.extLine2LineType = dimStyle.extLine2LineType + self.extLineColor = dimStyle.extLineColor + self.extLineOffsetDimLine = dimStyle.extLineOffsetDimLine + self.extLineOffsetOrigPoints = dimStyle.extLineOffsetOrigPoints + self.extLineIsFixedLen = dimStyle.extLineIsFixedLen + self.extLineFixedLen = dimStyle.extLineFixedLen + + # layer e loro caratteristiche + self.textualLayerName = dimStyle.textualLayerName + self.__textualLayer = dimStyle.__textualLayer + self.__textFields = dimStyle.__textFields + self.__textualFeaturePrototype = dimStyle.__textualFeaturePrototype + self.linearLayerName = dimStyle.linearLayerName + self.__linearLayer = dimStyle.__linearLayer + self.__lineFields = dimStyle.__lineFields + self.__linearFeaturePrototype = dimStyle.__linearFeaturePrototype + self.symbolLayerName = dimStyle.symbolLayerName + self.__symbolLayer = dimStyle.__symbolLayer + self.__symbolFields = dimStyle.__symbolFields + self.__symbolFeaturePrototype = dimStyle.__symbolFeaturePrototype + + self.componentFieldName = dimStyle.componentFieldName + self.symbolFieldName = dimStyle.symbolFieldName + self.lineTypeFieldName = dimStyle.lineTypeFieldName + self.colorFieldName = dimStyle.colorFieldName + self.idFieldName = dimStyle.idFieldName + self.idParentFieldName = dimStyle.idParentFieldName + self.dimStyleFieldName = dimStyle.dimStyleFieldName + self.dimTypeFieldName = dimStyle.dimTypeFieldName + self.scaleFieldName = dimStyle.scaleFieldName + self.rotFieldName = dimStyle.rotFieldName + + + # ============================================================================ + # getPropList + # ============================================================================ + def getPropList(self): + proplist = dict() # dizionario di nome con lista [descrizione, valore] + propDescr = QadMsg.translate("Dimension", "Name") + proplist["name"] = [propDescr, self.name] + propDescr = QadMsg.translate("Dimension", "Description") + proplist["description"] = [propDescr, self.description] + propDescr = QadMsg.translate("Dimension", "File path") + proplist["path"] = [propDescr, self.path] + + # testo di quota + value = self.textPrefix + if len(self.textPrefix) > 0: + value += "<>" + value += self.textSuffix + propDescr = QadMsg.translate("Dimension", "Text prefix and suffix") + proplist["textPrefix"] = [propDescr, value] + propDescr = QadMsg.translate("Dimension", "Leading zero suppression") + proplist["textSuppressLeadingZeros"] = [propDescr, self.textSuppressLeadingZeros] + propDescr = QadMsg.translate("Dimension", "Trailing zero suppression") + proplist["textDecimalZerosSuppression"] = [propDescr, self.textDecimalZerosSuppression] + propDescr = QadMsg.translate("Dimension", "Text height") + proplist["textHeight"] = [propDescr, self.textHeight] + propDescr = QadMsg.translate("Dimension", "Vertical text position") + proplist["textVerticalPos"] = [propDescr, self.textVerticalPos] + propDescr = QadMsg.translate("Dimension", "Horizontal text position") + proplist["textHorizontalPos"] = [propDescr, self.textHorizontalPos] + propDescr = QadMsg.translate("Dimension", "Text offset") + proplist["textOffsetDist"] = [propDescr, self.textOffsetDist] + propDescr = QadMsg.translate("Dimension", "Text alignment") + proplist["textRotMode"] = [propDescr, self.textRotMode] + propDescr = QadMsg.translate("Dimension", "Fixed text rotation") + proplist["textForcedRot"] = [propDescr, self.textForcedRot] + propDescr = QadMsg.translate("Dimension", "Precision") + proplist["textDecimals"] = [propDescr, self.textDecimals] + propDescr = QadMsg.translate("Dimension", "Decimal separator") + proplist["textDecimalSep"] = [propDescr, self.textDecimalSep] + propDescr = QadMsg.translate("Dimension", "Text font") + proplist["textFont"] = [propDescr, self.textFont] + propDescr = QadMsg.translate("Dimension", "Text color") + proplist["textColor"] = [propDescr, self.textColor] + if self.textDirection == QadDimStyleTxtDirectionEnum.SX_TO_DX: + value = QadMsg.translate("Dimension", "From left to right") + else: + value = QadMsg.translate("Dimension", "From right to left") + propDescr = QadMsg.translate("Dimension", "Text direction") + proplist["textDirection"] = [propDescr, value] + propDescr = QadMsg.translate("Dimension", "Arc len. symbol") + proplist["arcSymbPos"] = [propDescr, self.arcSymbPos] + + # linee di quota + propDescr = QadMsg.translate("Dimension", "Dim line 1 visible") + proplist["dimLine1Show"] = [propDescr, self.dimLine1Show] + propDescr = QadMsg.translate("Dimension", "Dim line 2 visible") + proplist["dimLine2Show"] = [propDescr, self.dimLine2Show] + propDescr = QadMsg.translate("Dimension", "Dim line linetype") + proplist["dimLineLineType"] = [propDescr, self.dimLineLineType] + propDescr = QadMsg.translate("Dimension", "Dim line color") + proplist["dimLineColor"] = [propDescr, self.dimLineColor] + propDescr = QadMsg.translate("Dimension", "Offset from origin") + proplist["dimLineSpaceOffset"] = [propDescr, self.dimLineSpaceOffset] + propDescr = QadMsg.translate("Dimension", "Dim line extension") + proplist["dimLineOffsetExtLine"] = [propDescr, self.dimLineOffsetExtLine] + + # simboli per linee di quota + propDescr = QadMsg.translate("Dimension", "Arrow 1") + proplist["block1Name"] = [propDescr, self.block1Name] + propDescr = QadMsg.translate("Dimension", "Arrow 2") + proplist["block2Name"] = [propDescr, self.block2Name] + propDescr = QadMsg.translate("Dimension", "Leader arrow") + proplist["blockLeaderName"] = [propDescr, self.blockLeaderName] + propDescr = QadMsg.translate("Dimension", "Arrowhead width") + proplist["blockWidth"] = [propDescr, self.blockWidth] + propDescr = QadMsg.translate("Dimension", "Arrowhead scale") + proplist["blockScale"] = [propDescr, self.blockScale] + propDescr = QadMsg.translate("Dimension", "Center mark size") + proplist["centerMarkSize"] = [propDescr, self.centerMarkSize] + + # adattamento del testo e delle frecce + propDescr = QadMsg.translate("Dimension", "Fit: arrows and text") + proplist["textBlockAdjust"] = [propDescr, self.textBlockAdjust] + propDescr = QadMsg.translate("Dimension", "Suppress arrows for lack of space") + proplist["blockSuppressionForNoSpace"] = [propDescr, self.blockSuppressionForNoSpace] + + # linee di estensione + propDescr = QadMsg.translate("Dimension", "Ext. line 1 visible") + proplist["extLine1Show"] = [propDescr, self.extLine1Show] + propDescr = QadMsg.translate("Dimension", "Ext. line 2 visible") + proplist["extLine2Show"] = [propDescr, self.extLine2Show] + propDescr = QadMsg.translate("Dimension", "Ext. line 1 linetype") + proplist["extLine1LineType"] = [propDescr, self.extLine1LineType] + propDescr = QadMsg.translate("Dimension", "Ext. line 2 linetype") + proplist["extLine2LineType"] = [propDescr, self.extLine2LineType] + propDescr = QadMsg.translate("Dimension", "Ext. line color") + proplist["extLineColor"] = [propDescr, self.extLineColor] + propDescr = QadMsg.translate("Dimension", "Ext. line extension") + proplist["extLineOffsetDimLine"] = [propDescr, self.extLineOffsetDimLine] + propDescr = QadMsg.translate("Dimension", "Ext. line offset") + proplist["extLineOffsetOrigPoints"] = [propDescr, self.extLineOffsetOrigPoints] + propDescr = QadMsg.translate("Dimension", "Fixed length ext. line activated") + proplist["extLineIsFixedLen"] = [propDescr, self.extLineIsFixedLen] + propDescr = QadMsg.translate("Dimension", "Fixed length ext. line") + proplist["extLineFixedLen"] = [propDescr, self.extLineFixedLen] + + # layer e loro caratteristiche + propDescr = QadMsg.translate("Dimension", "Layer for dim texts") + proplist["textualLayerName"] = [propDescr, self.textualLayerName] + propDescr = QadMsg.translate("Dimension", "Layer for dim lines") + proplist["linearLayerName"] = [propDescr, self.linearLayerName] + propDescr = QadMsg.translate("Dimension", "Layer for dim arrows") + proplist["symbolLayerName"] = [propDescr, self.symbolLayerName] + + propDescr = QadMsg.translate("Dimension", "Field for component type") + proplist["componentFieldName"] = [propDescr, self.componentFieldName] + propDescr = QadMsg.translate("Dimension", "Field for linetype") + proplist["lineTypeFieldName"] = [propDescr, self.lineTypeFieldName] + propDescr = QadMsg.translate("Dimension", "Field for color") + proplist["colorFieldName"] = [propDescr, self.colorFieldName] + propDescr = QadMsg.translate("Dimension", "Field for dim ID in texts") + proplist["idFieldName"] = [propDescr, self.idFieldName] + propDescr = QadMsg.translate("Dimension", "Field for dim ID in lines and arrows") + proplist["idParentFieldName"] = [propDescr, self.idParentFieldName] + propDescr = QadMsg.translate("Dimension", "Field for dim style name") + proplist["dimStyleFieldName"] = [propDescr, self.dimStyleFieldName] + propDescr = QadMsg.translate("Dimension", "Field for dim type") + proplist["dimTypeFieldName"] = [propDescr, self.dimTypeFieldName] + propDescr = QadMsg.translate("Dimension", "Field for symbol name") + proplist["symbolFieldName"] = [propDescr, self.symbolFieldName] + propDescr = QadMsg.translate("Dimension", "Field for arrows scale") + proplist["scaleFieldName"] = [propDescr, self.scaleFieldName] + propDescr = QadMsg.translate("Dimension", "Field for arrows rotation") + proplist["rotFieldName"] = [propDescr, self.rotFieldName] + + return proplist + + + # ============================================================================ + # getLayer + # ============================================================================ + def getLayer(self, layerName): + if layerName is not None: + layerList = QgsProject.instance().mapLayersByName(layerName) + if len(layerList) == 1: + return layerList[0] + return None + + + # ============================================================================ + # layer testuale + def getTextualLayer(self): + if self.__textualLayer is None: + self.__textualLayer = self.getLayer(self.textualLayerName) + return self.__textualLayer + + def getTextualLayerFields(self): + if self.__textFields is None: + self.__textFields = None if self.getTextualLayer() is None else self.getTextualLayer().fields() + return self.__textFields + + def getTextualFeaturePrototype(self): + if self.__textualFeaturePrototype is None: + if self.getTextualLayerFields() is not None: + self.__textualFeaturePrototype = QgsFeature(self.getTextualLayerFields()) + self.initFeatureToDefautlValues(self.getTextualLayer(), self.__textualFeaturePrototype) + return self.__textualFeaturePrototype + + + # ============================================================================ + # layer lineare + def getLinearLayer(self): + if self.__linearLayer is None: + self.__linearLayer = self.getLayer(self.linearLayerName) + return self.__linearLayer + + def getLinearLayerFields(self): + if self.__lineFields is None: + self.__lineFields = None if self.getLinearLayer() is None else self.getLinearLayer().fields() + return self.__lineFields + + def getLinearFeaturePrototype(self): + if self.__linearFeaturePrototype is None: + if self.getLinearLayerFields() is not None: + self.__linearFeaturePrototype = QgsFeature(self.getLinearLayerFields()) + self.initFeatureToDefautlValues(self.getLinearLayer(), self.__linearFeaturePrototype) + return self.__linearFeaturePrototype + + + # ============================================================================ + # layer simbolo + def getSymbolLayer(self): + if self.__symbolLayer is None: + self.__symbolLayer = self.getLayer(self.symbolLayerName) + return self.__symbolLayer + + def getSymbolLayerFields(self): + if self.__symbolFields is None: + self.__symbolFields = None if self.getSymbolLayer() is None else self.getSymbolLayer().fields() + return self.__symbolFields + + def getSymbolFeaturePrototype(self): + if self.__symbolFeaturePrototype is None: + if self.getSymbolLayerFields() is not None: + self.__symbolFeaturePrototype = QgsFeature(self.getSymbolLayerFields()) + self.initFeatureToDefautlValues(self.getSymbolLayer(), self.__symbolFeaturePrototype) + return self.__symbolFeaturePrototype + + + # ============================================================================ + # initFeatureToDefautlValues + # ============================================================================ + def initFeatureToDefautlValues(self, layer, f): + # assegno i valori di default + provider = layer.dataProvider() + fields = f.fields() + for field in fields.toList(): + i = fields.indexFromName(field.name()) + f[field.name()] = provider.defaultValue(i) + + + # ============================================================================ + # getDefaultDimFilePath + # ============================================================================ + def getDefaultDimFilePath(self): + # ottiene il percorso automatico dove salvare/caricare il file della quotatura + # se esiste un progetto caricato il percorso è quello del progetto + prjFileInfo = QFileInfo(QgsProject.instance().fileName()) + path = prjFileInfo.absolutePath() + if len(path) == 0: + # se non esiste un progetto caricato uso il percorso di installazione di qad + path = QDir.cleanPath(QgsApplication.qgisSettingsDirPath() + "python/plugins/qad") + return path + "/" + + + # ============================================================================ + # save + # ============================================================================ + def save(self, path = "", overwrite = True): + """ + Salva le impostazioni dello stile di quotatura in un file. + """ + if path == "" and self.path != "": + _path = self.path + else: + dir, base = os.path.split(path) # ritorna percorso e nome file con estensione + if dir == "": + dir = self.getDefaultDimFilePath() + else: + dir = QDir.cleanPath(dir) + "/" + + name, ext = os.path.splitext(base) + if name == "": + name = self.name + + if ext == "": # se non c'è estensione la aggiungo + ext = ".dim" + + _path = dir + name + ext + + if overwrite == False: # se non si vuole sovrascrivere + if os.path.exists(_path): + return False + + dir = QFileInfo(_path).absoluteDir() + if not dir.exists(): + os.makedirs(dir.absolutePath()) + + config = qad_utils.QadRawConfigParser(allow_no_value=True) + config.add_section("dimension_options") + config.set("dimension_options", "name", str(self.name)) + config.set("dimension_options", "description", self.description) + config.set("dimension_options", "dimType", str(self.dimType)) + + # testo di quota + config.set("dimension_options", "textPrefix", str(self.textPrefix)) + config.set("dimension_options", "textSuffix", str(self.textSuffix)) + config.set("dimension_options", "textSuppressLeadingZeros", str(self.textSuppressLeadingZeros)) + config.set("dimension_options", "textDecimalZerosSuppression", str(self.textDecimalZerosSuppression)) + config.set("dimension_options", "textHeight", str(self.textHeight)) + config.set("dimension_options", "textVerticalPos", str(self.textVerticalPos)) + config.set("dimension_options", "textHorizontalPos", str(self.textHorizontalPos)) + config.set("dimension_options", "textOffsetDist", str(self.textOffsetDist)) + config.set("dimension_options", "textRotMode", str(self.textRotMode)) + config.set("dimension_options", "textForcedRot", str(self.textForcedRot)) + config.set("dimension_options", "textDecimals", str(self.textDecimals)) + config.set("dimension_options", "textDecimalSep", str(self.textDecimalSep)) + config.set("dimension_options", "textFont", str(self.textFont)) + config.set("dimension_options", "textColor", str(self.textColor)) + config.set("dimension_options", "textDirection", str(self.textDirection)) + config.set("dimension_options", "arcSymbPos", str(self.arcSymbPos)) + + # linee di quota + config.set("dimension_options", "dimLine1Show", str(self.dimLine1Show)) + config.set("dimension_options", "dimLine2Show", str(self.dimLine2Show)) + config.set("dimension_options", "dimLineLineType", str(self.dimLineLineType)) + config.set("dimension_options", "dimLineColor", str(self.dimLineColor)) + config.set("dimension_options", "dimLineSpaceOffset", str(self.dimLineSpaceOffset)) + config.set("dimension_options", "dimLineOffsetExtLine", str(self.dimLineOffsetExtLine)) + + # simboli per linee di quota + config.set("dimension_options", "block1Name", str(self.block1Name)) + config.set("dimension_options", "block2Name", str(self.block2Name)) + config.set("dimension_options", "blockLeaderName", str(self.blockLeaderName)) + config.set("dimension_options", "blockWidth", str(self.blockWidth)) + config.set("dimension_options", "blockScale", str(self.blockScale)) + config.set("dimension_options", "blockSuppressionForNoSpace", str(self.blockSuppressionForNoSpace)) + config.set("dimension_options", "centerMarkSize", str(self.centerMarkSize)) + + # adattamento del testo e delle frecce + config.set("dimension_options", "textBlockAdjust", str(self.textBlockAdjust)) + + # linee di estensione + config.set("dimension_options", "extLine1Show", str(self.extLine1Show)) + config.set("dimension_options", "extLine2Show", str(self.extLine2Show)) + config.set("dimension_options", "extLine1LineType", str(self.extLine1LineType)) + config.set("dimension_options", "extLine2LineType", str(self.extLine2LineType)) + config.set("dimension_options", "extLineColor", str(self.extLineColor)) + config.set("dimension_options", "extLineOffsetDimLine", str(self.extLineOffsetDimLine)) + config.set("dimension_options", "extLineOffsetOrigPoints", str(self.extLineOffsetOrigPoints)) + config.set("dimension_options", "extLineIsFixedLen", str(self.extLineIsFixedLen)) + config.set("dimension_options", "extLineFixedLen", str(self.extLineFixedLen)) + + # layer e loro caratteristiche + config.set("dimension_options", "textualLayerName", "" if self.textualLayerName is None else self.textualLayerName) + config.set("dimension_options", "linearLayerName", "" if self.linearLayerName is None else self.linearLayerName) + config.set("dimension_options", "symbolLayerName", "" if self.symbolLayerName is None else self.symbolLayerName) + config.set("dimension_options", "componentFieldName", str(self.componentFieldName)) + config.set("dimension_options", "symbolFieldName", str(self.symbolFieldName)) + config.set("dimension_options", "lineTypeFieldName", str(self.lineTypeFieldName)) + config.set("dimension_options", "colorFieldName", str(self.colorFieldName)) + config.set("dimension_options", "idFieldName", str(self.idFieldName)) + config.set("dimension_options", "idParentFieldName", str(self.idParentFieldName)) + config.set("dimension_options", "dimStyleFieldName", str(self.dimStyleFieldName)) + config.set("dimension_options", "dimTypeFieldName", str(self.dimTypeFieldName)) + config.set("dimension_options", "scaleFieldName", str(self.scaleFieldName)) + config.set("dimension_options", "rotFieldName", str(self.rotFieldName)) + + with codecs.open(_path, 'w', 'utf-8') as configFile: + config.write(configFile) + + self.path = _path + + return True + + + # ============================================================================ + # load + # ============================================================================ + def load(self, path): + """ + Carica le impostazioni dello stile di quotatura da un file. + """ + if path is None or path == "": + return False + + if os.path.dirname(path) == "": # path contiene solo il nome del file (senza dir) + _path = self.getDefaultDimFilePath() + _path = _path + path + else: + _path = path + + if not os.path.exists(_path): + return False + + config = qad_utils.QadRawConfigParser(allow_no_value=True) + # file = codecs.open(_path, "r", "utf-8") + # config.read_file(file) + # file.close() + config.read(_path) + + value = config.get("dimension_options", "name", fallback = None) + if value is not None: + self.name = value + value = config.get("dimension_options", "description", fallback = None) + if value is not None: + self.description = value + value = config.get("dimension_options", "dimType", fallback = None) + if value is not None: + self.dimType = value + + # testo di quota + value = config.get("dimension_options", "textPrefix", fallback = None) + if value is not None: + self.textPrefix = value + value = config.get("dimension_options", "textSuffix", fallback = None) + if value is not None: + self.textSuffix = value + value = config.getboolean("dimension_options", "textSuppressLeadingZeros", fallback = None) + if value is not None: + self.textSuppressLeadingZeros = value + value = config.getboolean("dimension_options", "textDecimalZerosSuppression", fallback = None) + if value is not None: + self.textDecimalZerosSuppression = value + value = config.getfloat("dimension_options", "textHeight", fallback = None) + if value is not None: + self.textHeight = value + value = config.getint("dimension_options", "textVerticalPos", fallback = None) + if value is not None: + self.textVerticalPos = value + value = config.getint("dimension_options", "textHorizontalPos", fallback = None) + if value is not None: + self.textHorizontalPos = value + value = config.getfloat("dimension_options", "textOffsetDist", fallback = None) + if value is not None: + self.textOffsetDist = value + value = config.getint("dimension_options", "textRotMode", fallback = None) + if value is not None: + self.textRotMode = value + value = config.getfloat("dimension_options", "textForcedRot", fallback = None) + if value is not None: + self.textForcedRot = value + value = config.getint("dimension_options", "textDecimals", fallback = None) + if value is not None: + self.textDecimals = value + value = config.get("dimension_options", "textDecimalSep", fallback = None) + if value is not None: + self.textDecimalSep = value + value = config.get("dimension_options", "textFont", fallback = None) + if value is not None: + self.textFont = value + value = config.get("dimension_options", "textColor", fallback = None) + if value is not None: + self.textColor = value + value = config.getint("dimension_options", "textDirection", fallback = None) + if value is not None: + self.textDirection = value + value = config.getint("dimension_options", "arcSymbPos", fallback = None) + if value is not None: + self.arcSymbPos = value + + # linee di quota + value = config.getboolean("dimension_options", "dimLine1Show", fallback = None) + if value is not None: + self.dimLine1Show = value + value = config.getboolean("dimension_options", "dimLine2Show", fallback = None) + if value is not None: + self.dimLine2Show = value + value = config.get("dimension_options", "dimLineLineType", fallback = None) + if value is not None: + self.dimLineLineType = value + value = config.get("dimension_options", "dimLineColor", fallback = None) + if value is not None: + self.dimLineColor = value + value = config.getfloat("dimension_options", "dimLineSpaceOffset", fallback = None) + if value is not None: + self.dimLineSpaceOffset = value + value = config.getfloat("dimension_options", "dimLineOffsetExtLine", fallback = None) + if value is not None: + self.dimLineOffsetExtLine = value + + # simboli per linee di quota + value = config.get("dimension_options", "block1Name", fallback = None) + if value is not None: + self.block1Name = value + value = config.get("dimension_options", "block2Name", fallback = None) + if value is not None: + self.block2Name = value + value = config.get("dimension_options", "blockLeaderName", fallback = None) + if value is not None: + self.blockLeaderName = value + value = config.getfloat("dimension_options", "blockWidth", fallback = None) + if value is not None: + self.blockWidth = value + value = config.getfloat("dimension_options", "blockScale", fallback = None) + if value is not None: + self.blockScale = value + value = config.getboolean("dimension_options", "blockSuppressionForNoSpace", fallback = None) + if value is not None: + self.blockSuppressionForNoSpace = value + value = config.getfloat("dimension_options", "centerMarkSize", fallback = None) + if value is not None: + self.centerMarkSize = value + + # adattamento del testo e delle frecce + value = config.getint("dimension_options", "textBlockAdjust", fallback = None) + if value is not None: + self.textBlockAdjust = value + + # linee di estensione + value = config.getboolean("dimension_options", "extLine1Show", fallback = None) + if value is not None: + self.extLine1Show = value + value = config.getboolean("dimension_options", "extLine2Show", fallback = None) + if value is not None: + self.extLine2Show = value + value = config.get("dimension_options", "extLine1LineType", fallback = None) + if value is not None: + self.extLine1LineType = value + value = config.get("dimension_options", "extLine2LineType", fallback = None) + if value is not None: + self.extLine2LineType = value + value = config.get("dimension_options", "extLineColor", fallback = None) + if value is not None: + self.extLineColor = value + value = config.getfloat("dimension_options", "extLineOffsetDimLine", fallback = None) + if value is not None: + self.extLineOffsetDimLine = value + value = config.getfloat("dimension_options", "extLineOffsetOrigPoints", fallback = None) + if value is not None: + self.extLineOffsetOrigPoints = value + value = config.getboolean("dimension_options", "extLineIsFixedLen", fallback = None) + if value is not None: + self.extLineIsFixedLen = value + value = config.getfloat("dimension_options", "extLineFixedLen", fallback = None) + if value is not None: + self.extLineFixedLen = value + + # layer e loro caratteristiche + value = config.get("dimension_options", "textualLayerName", fallback = None) + if value is not None: + self.textualLayerName = value + value = config.get("dimension_options", "linearLayerName", fallback = None) + if value is not None: + self.linearLayerName = value + value = config.get("dimension_options", "symbolLayerName", fallback = None) + if value is not None: + self.symbolLayerName = value + + value = config.get("dimension_options", "componentFieldName", fallback = None) + if value is not None: + self.componentFieldName = value + value = config.get("dimension_options", "symbolFieldName", fallback = None) + if value is not None: + self.symbolFieldName = value + value = config.get("dimension_options", "lineTypeFieldName", fallback = None) + if value is not None: + self.lineTypeFieldName = value + value = config.get("dimension_options", "colorFieldName", fallback = None) + if value is not None: + self.colorFieldName = value + value = config.get("dimension_options", "idFieldName", fallback = None) + if value is not None: + self.idFieldName = value + value = config.get("dimension_options", "idParentFieldName", fallback = None) + if value is not None: + self.idParentFieldName = value + value = config.get("dimension_options", "dimStyleFieldName", fallback = None) + if value is not None: + self.dimStyleFieldName = value + value = config.get("dimension_options", "dimTypeFieldName", fallback = None) + if value is not None: + self.dimTypeFieldName = value + value = config.get("dimension_options", "scaleFieldName", fallback = None) + if value is not None: + self.scaleFieldName = value + value = config.get("dimension_options", "rotFieldName", fallback = None) + if value is not None: + self.rotFieldName = value + + self.path = _path + + return True + + + # ============================================================================ + # remove + # ============================================================================ + def remove(self): + """ + Cancella il file delle impostazioni dello stile di quotatura. + """ + currDimStyleName = QadVariables.get(QadMsg.translate("Environment variables", "DIMSTYLE")) + if self.name == currDimStyleName: # lo stile da cancellare è quello corrente + return False + + if self.path is not None and self.path != "": + if os.path.exists(self.path): + try: + os.remove(self.path) + except: + return False + + return True + + # ============================================================================ + # rename + # ============================================================================ + def rename(self, newName): + """ + Rinomina il nome dello stile e del file delle impostazioni dello stile di quotatura. + """ + if newName == self.name: # nome uguale + return True + oldName = self.name + + if self.path is not None or self.path != "": + if os.path.exists(self.path): + try: + dir, base = os.path.split(self.path) + dir = QDir.cleanPath(dir) + "/" + + name, ext = os.path.splitext(base) + newPath = dir + "/" + newName + ext + + os.rename(self.path, newPath) + self.path = newPath + self.name = newName + self.save() + except: + return False + else: + self.name = newName + + currDimStyleName = QadVariables.get(QadMsg.translate("Environment variables", "DIMSTYLE")) + if oldName == currDimStyleName: # lo stile da rinominare è quello corrente + QadVariables.set(QadMsg.translate("Environment variables", "DIMSTYLE"), newName) + + self.name = newName + return True + + + # ============================================================================ + # getInValidErrMsg + # ============================================================================ + def getInValidErrMsg(self): + """ + Verifica se lo stile di quotatura é invalido e in caso affermativo ritorna il messaggio di errore. + Se la quotatura é valida ritorna None. + """ + prefix = QadMsg.translate("Dimension", "\nThe dimension style \"{0}\" ").format(self.name) + + if self.getTextualLayer() is None: + return prefix + QadMsg.translate("Dimension", "has not the textual layer for dimension.\n") + if qad_layer.isTextLayer(self.getTextualLayer()) == False: + errPartial = QadMsg.translate("Dimension", "has the textual layer for dimension ({0}) which is not a textual layer.") + errMsg = prefix + errPartial.format(self.getTextualLayer().name()) + errMsg = errMsg + QadMsg.translate("QAD", "\nA textual layer is a vector punctual layer having a label and the symbol transparency no more than 10%.\n") + return errMsg + + if self.getSymbolLayer() is None: + return prefix + QadMsg.translate("Dimension", "has not the symbol layer for dimension.\n") + if qad_layer.isSymbolLayer(self.getSymbolLayer()) == False: + errPartial = QadMsg.translate("Dimension", "has the symbol layer for dimension ({0}) which is not a symbol layer.") + errMsg = prefix + errPartial.format(self.getSymbolLayer().name()) + errMsg = errMsg + QadMsg.translate("QAD", "\nA symbol layer is a vector punctual layer without label.\n") + return errMsg + + if self.getLinearLayer() is None: + return prefix + QadMsg.translate("Dimension", "has not the linear layer for dimension.\n") + # deve essere un VectorLayer di tipo linea + if (self.getLinearLayer().type() != QgsMapLayer.VectorLayer) or (self.getLinearLayer().geometryType() != QgsWkbTypes.LineGeometry): + errPartial = QadMsg.translate("Dimension", "has the linear layer for dimension ({0}) which is not a linear layer.") + errMsg = prefix + errPartial.format(self.getSymbolLayer().name()) + return errMsg + # i layer devono avere lo stesso sistema di coordinate + if not (self.getTextualLayer().crs() == self.getLinearLayer().crs() and self.getLinearLayer().crs() == self.getSymbolLayer().crs()): + errMsg = prefix + QadMsg.translate("Dimension", "has not the layers with the same coordinate reference system.") + return errMsg + + return None + + + # ============================================================================ + # isValid + # ============================================================================ + def isValid(self): + """ + Verifica se lo stile di quotatura é valido e in caso affermativo ritorna True. + Se la quotatura non é valida ritorna False. + """ + return True if self.getInValidErrMsg() is None else False + + + # =============================================================================== + # getNotGraphEditableErrMsg + # =============================================================================== + def getNotGraphEditableErrMsg(self): + """ + Verifica se i layer dello stile di quotatura sono in sola lettura e in caso affermativo ritorna il messaggio di errore. + Se i layer dello stile di quotatura sono modificabili ritorna None. + """ + prefix = QadMsg.translate("Dimension", "\nThe dimension style \"{0}\" ").format(self.name) + + # layer dei testi + textualLayer = self.getTextualLayer() + if textualLayer is None: + errPartial = QadMsg.translate("Dimension", "hasn't the textual layer ({0}).") + return prefix + errPartial.format(self.textualLayerName) + + provider = textualLayer.dataProvider() + if not (provider.capabilities() & QgsVectorDataProvider.EditingCapabilities): + errPartial = QadMsg.translate("Dimension", "has the textual layer ({0}) not editable.") + return prefix + errPartial.format(self.textualLayerName) + if not textualLayer.isEditable(): + errPartial = QadMsg.translate("Dimension", "has the textual layer ({0}) not editable.") + return prefix + errPartial.format(self.textualLayerName) + + # layer dei simboli + symbolLayer = self.getSymbolLayer() + if symbolLayer is None: + errPartial = QadMsg.translate("Dimension", "hasn't the symbol layer ({0}).") + return prefix + errPartial.format(self.symbolLayerName) + + provider = symbolLayer.dataProvider() + if not (provider.capabilities() & QgsVectorDataProvider.EditingCapabilities): + errPartial = QadMsg.translate("Dimension", "has the symbol layer ({0}) not editable.") + return prefix + errPartial.format(self.symbolLayerName) + if not symbolLayer.isEditable(): + errPartial = QadMsg.translate("Dimension", "has the symbol layer ({0}) not editable.") + return prefix + errPartial.format(self.symbolLayerName) + + # layer delle linee + linearLayer = self.getLinearLayer() + if linearLayer is None: + errPartial = QadMsg.translate("Dimension", "hasn't the symbol layer ({0}).") + return prefix + errPartial.format(self.linearLayerName) + + provider = linearLayer.dataProvider() + if not (provider.capabilities() & QgsVectorDataProvider.EditingCapabilities): + errPartial = QadMsg.translate("Dimension", "has the linear layer ({0}) not editable.") + return prefix + errPartial.format(self.linearLayerName) + if not linearLayer.isEditable(): + errPartial = QadMsg.translate("Dimension", "has the linear layer ({0}) not editable.") + return prefix + errPartial.format(self.linearLayerName) + + return None + + + # ============================================================================ + # adjustLineAccordingTextRect + # ============================================================================ + def adjustLineAccordingTextRect(self, textRect, line, textLinearDimComponentOn): + """ + Data una linea, che tipo di componente di quota rappresenta (textLinearDimComponentOn) + e un rettangolo che rappresenta l'occupazione del testo di quota (sottoforma di una QadPolyline), + la funzione restituisce 2 linee (possono essere None) in modo che il testo non si sovrapponga alla linea e che le + impostazioni di quota siano rispettate (dimLine1Show, dimLine2Show, extLine1Show, extLine2Show) + """ + line1 = None + line2 = None + # Restituisce i punti di intersezione tra il rettangolo (QadPolyline) che rappresenta il testo + # e un segmento . La lista é ordinata per distanza dal punto iniziale di line. + intPts = QadIntersections.getOrderedPolylineIntersectionPtsWithBasicGeom(textRect, line, True)[0] # orderByStartPtOfLinearObject = True + if textLinearDimComponentOn == QadDimComponentEnum.DIM_LINE1: # linea di quota ("Dimension line") + if len(intPts) == 2: # il rettangolo é sulla linea + if self.dimLine1Show: + line1 = QadLine().set(line.getStartPt(), intPts[0]) + if self.dimLine2Show: + line2 = QadLine().set(intPts[1], line.getEndPt()) + else: # il rettangolo non é sulla linea + if self.dimLine1Show and self.dimLine2Show: + line1 = line.copy() + else: + space1, space2 = self.getSpaceForBlock1AndBlock2OnLine(textRect, line) + rot = qad_utils.getAngleBy2Pts(line.getStartPt(), line.getEndPt()) # angolo della linea di quota + intPt1 = qad_utils.getPolarPointByPtAngle(line.getStartPt(), rot, space1) + intPt2 = qad_utils.getPolarPointByPtAngle(line.getEndPt(), rot - math.pi, space2) + + if self.dimLine1Show: + line1 = QadLine().set(line.getStartPt(), intPt2) + elif self.dimLine2Show: + line2 = QadLine().set(line.getEndPt(), intPt1) + elif textLinearDimComponentOn == QadDimComponentEnum.EXT_LINE1: # prima linea di estensione ("Extension line 1") + if self.extLine1Show: + if len(intPts) > 0: + line1 = QadLine().set(line.getStartPt(), intPts[0]) + else: + line1 = line.copy() + elif textLinearDimComponentOn == QadDimComponentEnum.EXT_LINE2: # seconda linea di estensione ("Extension line 2") + if self.extLine2Show: + if len(intPts) > 0: + line1 = QadLine().set(line.getStartPt(), intPts[0]) + else: + line1 = line.copy() + elif textLinearDimComponentOn == QadDimComponentEnum.LEADER_LINE: # linea porta quota usata quando il testo é fuori dalla quota ("Leader") + if len(intPts) > 0: + line1 = QadLine().set(line.getEndPt(), intPts[0]) + else: + line1 = line.copy() + + return line1, line2 + + + # ============================================================================ + # adjustArcAccordingTextRect + # ============================================================================ + def adjustArcAccordingTextRect(self, textRect, arc, textLinearDimComponentOn): + """ + Data un arco (), che tipo di componente di quota rappresenta (textLinearDimComponentOn) + e un rettangolo che rappresenta l'occupazione del testo di quota, la funzione restituisce + due archi (possono essere None) in modo che il testo non si sovrapponga all'arco e che le + impostazioni di quota siano rispettate (dimLine1Show, dimLine2Show, extLine1Show, extLine2Show) + """ + intPts = QadIntersections.getOrderedPolylineIntersectionPtsWithBasicGeom(textRect, arc, True)[0] # orderByStartPtOfPart = True + arc1 = None + arc2 = None + + if textLinearDimComponentOn == QadDimComponentEnum.DIM_LINE1: # linea di quota ("Dimension line") + if len(intPts) >= 2: # il rettangolo é sulla linea + if self.dimLine1Show: + arc1 = QadArc(arc) + arc1.setEndAngleByPt(intPts[0]) + if self.dimLine2Show: + arc2 = QadArc(arc) + arc2.setStartAngleByPt(intPts[-1])# ultimo punto + else: # il rettangolo non é sulla linea + if self.dimLine1Show and self.dimLine2Show: + arc1 = QadArc(arc) + else: + space1, space2 = self.getSpaceForBlock1AndBlock2OnArc(textRect, arc) + + if self.dimLine1Show: + arc1 = QadArc(arc) + pt, dummyTg = arc1.getPointFromStart(space1) + arc1.setEndAngleByPt(pt) + elif self.dimLine2Show: + arc2 = QadArc(arc) + pt, dummyTg = arc2.getPointFromStart(arc2.length() - space2) + arc2.setStartAngleByPt(pt) + elif textLinearDimComponentOn == QadDimComponentEnum.EXT_LINE1: # prima linea di estensione ("Extension line 1") + if self.extLine1Show: + if len(intPts) > 0: + arc1 = QadArc(arc) + arc1.setEndAngleByPt(intPts[0]) + elif textLinearDimComponentOn == QadDimComponentEnum.EXT_LINE2: # seconda linea di estensione ("Extension line 2") + if self.extLine2Show: + if len(intPts) > 0: + arc1 = QadArc(arc) + arc1.setEndAngleByPt(intPts[0]) + elif textLinearDimComponentOn == QadDimComponentEnum.LEADER_LINE: # linea porta quota usata quando il testo é fuori dalla quota ("Leader") + if len(intPts) > 0: + arc1 = QadArc(arc) + arc1.setEndAngleByPt(intPts[0]) + + return arc1, arc2 + + + # ============================================================================ + # setDimId + # ============================================================================ + def setDimId(self, dimId, features, parentId = False): + """ + Setta tutte le feature passate nella lista con il codice della quota. + """ + fieldName = self.idParentFieldName if parentId else self.idFieldName + + if len(fieldName) == 0: + return True + + i = 0 + tot = len(features) + while i < tot: + try: + f = features[i] + if f is not None: + # imposto il codice della quota + f.setAttribute(fieldName, dimId) + except: + return False + i = i + 1 + return True + + + # ============================================================================ + # recodeDimIdOnFeatures + # ============================================================================ + def recodeDimIdOnFeatures(self, oldDimId, newDimId, features, parentId = False): + """ + Cerca tutte le feature passate nella lista con il codice della + quota oldDimId e le ricodifica con newDimId. + """ + fieldName = self.idParentFieldName if parentId else self.idFieldName + + if len(fieldName) == 0: + return True + + i = 0 + tot = len(features) + while i < tot: + try: + f = features[i] + if f is not None: + if f.attribute(fieldName) == oldDimId: + # imposto il codice della quota + f.setAttribute(fieldName, newDimId) + except: + return False + i = i + 1 + return True + + + def textCommitChangesOnSave(self, plugIn): + """ + Salva i testi delle quote per ottenere i nuovi ID + e richiamare updateTextReferencesOnSave tramite il segnale committedFeaturesAdded. + """ + # salvo i testi per avere la codifica definitiva + if self.getTextualLayer() is not None: + # segno che questo layer è salvato da QAD + plugIn.layerStatusList.setStatus(self.getTextualLayer().id(), qad_layer.QadLayerStatusEnum.COMMIT_BY_INTERNAL) + res = self.getTextualLayer().commitChanges() + if res == False: + errors = self.getTextualLayer().commitErrors() + plugIn.layerStatusList.remove(self.getTextualLayer().id()) + return res + else: + return False + + + # ============================================================================ + # updateTextReferencesOnSave + # ============================================================================ + def updateTextReferencesOnSave(self, plugIn, textAddedEntitySet): + """ + Aggiorna e salva i reference delle entità dello stile di quotatura contenuti in textAddedEntitySet. + """ + if textAddedEntitySet.isEmpty() == True: + return True + if self.startEditing() == False: + return False + + plugIn.beginEditCommand("Dimension recoded", [self.getSymbolLayer(), self.getLinearLayer(), self.getTextualLayer()]) + + entity = QadEntity() + entityIterator = textAddedEntitySet.getEntities() + + for entity in entityIterator: + oldDimId = entity.getAttribute(self.idFieldName) + newDimId = entity.getFeature().id() + if oldDimId is None or self.recodeDimId(plugIn, oldDimId, newDimId) == False: + return False + + plugIn.endEditCommand() + + return True + + + # ============================================================================ + # startEditing + # ============================================================================ + def startEditing(self): + if self.getTextualLayer() is not None and self.getTextualLayer().isEditable() == False: + if self.getTextualLayer().startEditing() == False: + return False + if self.getLinearLayer() is not None and self.getLinearLayer().isEditable() == False: + if self.getLinearLayer().startEditing() == False: + return False + if self.getSymbolLayer() is not None and self.getSymbolLayer().isEditable() == False: + if self.getSymbolLayer().startEditing() == False: + return False + + + # ============================================================================ + # commitChanges + # ============================================================================ + def commitChanges(self, plugIn): + if self.startEditing() == False: + return False + + excludedLayer = plugIn.beforeCommitChangesDimLayer + + if (excludedLayer is None) or excludedLayer.id() != self.getTextualLayer().id(): + # segno che questo layer è salvato da QAD + plugIn.layerStatusList.setStatus(self.getTextualLayer().id(), qad_layer.QadLayerStatusEnum.COMMIT_BY_INTERNAL) + # salvo le entità testuali + if self.getTextualLayer().commitChanges(False) == False: # By setting stopEditing to false, the layer will stay in editing mode. + errors = self.getTextualLayer().commitErrors() + plugIn.layerStatusList.remove(self.getTextualLayer().id()) + + if (excludedLayer is None) or excludedLayer.id() != self.getLinearLayer().id(): + # segno che questo layer è salvato da QAD + plugIn.layerStatusList.setStatus(self.getLinearLayer().id(), qad_layer.QadLayerStatusEnum.COMMIT_BY_INTERNAL) + # salvo le entità lineari + if self.getLinearLayer().commitChanges(False) == False: # By setting stopEditing to false, the layer will stay in editing mode. + errors = self.getTextualLayer().commitErrors() + plugIn.layerStatusList.remove(self.getLinearLayer().id()) + + if (excludedLayer is None) or excludedLayer.id() != self.getSymbolLayer().id(): + # segno che questo layer è salvato da QAD + plugIn.layerStatusList.setStatus(self.getSymbolLayer().id(), qad_layer.QadLayerStatusEnum.COMMIT_BY_INTERNAL) + # salvo le entità puntuali + if self.getSymbolLayer().commitChanges(False) == False: # By setting stopEditing to false, the layer will stay in editing mode. + errors = self.getTextualLayer().commitErrors() + plugIn.layerStatusList.remove(self.getSymbolLayer().id()) + + + # ============================================================================ + # recodeDimId + # ============================================================================ + def getEntitySet(self, dimId): + """ + Ricava un QadEntitySet con tutte le feature della quota dimId. + """ + result = QadEntitySet() + if len(self.idFieldName) == 0 or len(self.idParentFieldName) == 0: + return result + + if self.isValid() == False: return result; + + layerEntitySet = QadLayerEntitySet() + + # ricerco l'entità testo + expression = "\"" + self.idFieldName + "\"=" + str(dimId) + featureIter = self.getTextualLayer().getFeatures(QgsFeatureRequest().setFilterExpression(expression)) + layerEntitySet.set(self.getTextualLayer()) + layerEntitySet.addFeatures(featureIter) + result.addLayerEntitySet(layerEntitySet) + + expression = "\"" + self.idParentFieldName + "\"=" + str(dimId) + + # ricerco le entità linea + layerEntitySet.clear() + featureIter = self.getLinearLayer().getFeatures(QgsFeatureRequest().setFilterExpression(expression)) + layerEntitySet.set(self.getLinearLayer()) + layerEntitySet.addFeatures(featureIter) + result.addLayerEntitySet(layerEntitySet) + + # ricerco e setto id_parent per le entità puntuali + layerEntitySet.clear() + featureIter = self.getSymbolLayer().getFeatures(QgsFeatureRequest().setFilterExpression(expression)) + layerEntitySet.set(self.getSymbolLayer()) + layerEntitySet.addFeatures(featureIter) + result.addLayerEntitySet(layerEntitySet) + + return result + + + # ============================================================================ + # recodeDimId + # ============================================================================ + def recodeDimId(self, plugIn, oldDimId, newDimId): + """ + Ricodifica tutte le feature della quota oldDimId con il nuovo codice newDimId. + """ + if len(self.idFieldName) == 0 or len(self.idParentFieldName) == 0: + return True + + entitySet = self.getEntitySet(oldDimId) + + # setto l'entità testo + layerEntitySet = entitySet.findLayerEntitySet(self.getTextualLayer()) + if layerEntitySet is not None: + features = layerEntitySet.getFeatureCollection() + if self.setDimId(newDimId, features, False) == False: + return False + # plugIn, layer, features, refresh, check_validity + if qad_layer.updateFeaturesToLayer(plugIn, self.getTextualLayer(), features, False, False) == False: + return False + + # setto id_parent per le entità linea + layerEntitySet = entitySet.findLayerEntitySet(self.getLinearLayer()) + if layerEntitySet is not None: + features = layerEntitySet.getFeatureCollection() + if self.setDimId(newDimId, features, True) == False: + return False + # plugIn, layer, features, refresh, check_validity + if qad_layer.updateFeaturesToLayer(plugIn, self.getLinearLayer(), features, False, False) == False: + return False + + # setto id_parent per le entità puntuali + layerEntitySet = entitySet.findLayerEntitySet(self.getSymbolLayer()) + if layerEntitySet is not None: + features = layerEntitySet.getFeatureCollection() + if self.setDimId(newDimId, features, True) == False: + return False + # plugIn, layer, features, refresh, check_validity + if qad_layer.updateFeaturesToLayer(plugIn, self.getSymbolLayer(), features, False, False) == False: + return False + + return True + + + # ============================================================================ + # addDimEntityToLayers + # ============================================================================ + def addDimEntityToLayers(self, plugIn, dimEntity): + """ + Aggiunge un'entità quota ai layer di pertinenza ricodificando i componenti. + """ + if dimEntity is None: + return False + + plugIn.beginEditCommand("Dimension added", [self.getSymbolLayer(), self.getLinearLayer(), self.getTextualLayer()]) + + # prima di tutto inserisco il testo di quota + # plugIn, layer, feature, coordTransform, refresh, check_validity + if qad_layer.addFeatureToLayer(plugIn, self.getTextualLayer(), dimEntity.textualFeature, None, False, False, False) == False: + plugIn.destroyEditCommand() + return False + + dimId = dimEntity.textualFeature.id() + + if self.setDimId(dimId, [dimEntity.textualFeature], False) == True: # setto id + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(plugIn, self.getTextualLayer(), dimEntity.textualFeature, False, False) == False: + plugIn.destroyEditCommand() + return False + + # features puntuali + self.setDimId(dimId, dimEntity.symbolFeatures, True) # setto id_parent + # plugIn, layer, features, coordTransform, refresh, check_validity + if qad_layer.addFeaturesToLayer(plugIn, self.getSymbolLayer(), dimEntity.symbolFeatures, None, False, False) == False: + plugIn.destroyEditCommand() + return False + + # features lineari + self.setDimId(dimId, dimEntity.linearFeatures, True) # setto id_parent + # plugIn, layer, features, coordTransform, refresh, check_validity + if qad_layer.addFeaturesToLayer(plugIn, self.getLinearLayer(), dimEntity.linearFeatures, None, False, False) == False: + plugIn.destroyEditCommand() + return False + + plugIn.endEditCommand() + + return True + + + # ============================================================================ + # getDimIdByEntity + # ============================================================================ + def getDimIdByEntity(self, entity): + """ + La funzione, data un'entità, verifica se fa parte dello stile di quotatura e, + in caso di successo, restituisce il codice della quotatura altrimenti None. + In più, la funzione, setta il tipo di quotatura se é possibile. + """ + if entity.layer.name() == self.textualLayerName: + dimId = entity.getAttribute(self.idFieldName) + if dimId is None: + return None + f = entity.getFeature() + elif entity.layer.name() == self.linearLayerName or \ + entity.layer.name() == self.symbolLayerName: + textualLayer = self.getTextualLayer() + if textualLayer is None: return None + + dimId = entity.getAttribute(self.idParentFieldName) + if dimId is None: + return None + # ricerco l'entità testo + expression = "\"" + self.idFieldName + "\"=" + str(dimId) + f = QgsFeature() + if textualLayer.getFeatures(QgsFeatureRequest().setFilterExpression(expression)).nextFeature(f) == False: + return None + else: + return None + + try: + # leggo il nome dello stile di quotatura + dimName = f.attribute(self.dimStyleFieldName) + if dimName != self.name: + return None + except: + return None + + try: + # leggo il tipo dello stile di quotatura + self.dimType = f.attribute(self.dimTypeFieldName) + except: + pass + + return dimId + + + # ============================================================================ + # isDimLayer + # ============================================================================ + def isDimLayer(self, layer): + """ + La funzione, dato un layer, verifica se fa parte dello stile di quotatura. + """ + if layer.name() == self.textualLayerName or \ + layer.name() == self.linearLayerName or \ + layer.name() == self.symbolLayerName: + return True + else: + return False + + + # ============================================================================ + # getFilteredLayerEntitySet + # ============================================================================ + def getFilteredLayerEntitySet(self, layerEntitySet): + """ + La funzione, dato un QadLayerEntitySet, filtra e restituisce solo quelle appartenenti allo stile di quotatura. + """ + result = QadLayerEntitySet() + entity = QadEntity() + entityIterator = layerEntitySet.getEntities() + + for entity in entityIterator: + if self.getDimIdByEntity(entity) is not None: + result.addEntity(entity) + + return result + + + + + # ============================================================================ + # FUNZIONI PER I BLOCCHI - INIZIO + # ============================================================================ + + + # ============================================================================ + # getBlock1Size + # ============================================================================ + def getBlock1Size(self): + """ + Restituisce la dimensione del blocco 1 delle frecce in unità di mappa. + """ + return 0 if self.block1Name == "" else self.blockWidth * self.blockScale + + + # ============================================================================ + # getBlock2Size + # ============================================================================ + def getBlock2Size(self): + """ + Restituisce la dimensione del blocco 2 delle frecce in unità di mappa. + """ + # blockWidth = larghezza del simbolo (in orizzontale) quando la dimensione in unità di mappa = 1 (vedi "triangle2") + # blockScale = scala della dimensione del simbolo (DIMASZ) + return 0 if self.block2Name == "" else self.blockWidth * self.blockScale + + + # ============================================================================ + # getBlocksRotOnLine + # ============================================================================ + def getBlocksRotOnLine(self, dimLine, inside): + """ + Restituisce una lista di 2 elementi che descrivono le rotazioni dei due blocchi: + - il primo elemento é la rotazione del blocco 1 + - il secondo elemento é la rotazione del blocco 2 + + dimLine = linea di quota + inside = flag di modo, se = true le frecce sono interne altrimenti sono esterne + """ + rot = dimLine.getTanDirectionOnPt() # angolo della linea di quota + if inside: + rot1 = rot + math.pi + rot2 = rot + else: + rot1 = rot + rot2 = rot + math.pi + + return qad_utils.normalizeAngle(rot1), qad_utils.normalizeAngle(rot2) + + + # ============================================================================ + # getBlocksRotOnArc + # ============================================================================ + def getBlocksRotOnArc(self, dimLineArc, inside): + """ + Restituisce una lista di 2 elementi che descrivono le rotazioni dei due blocchi: + - il primo elemento é la rotazione del blocco 1 + - il secondo elemento é la rotazione del blocco 2 + + dimLineArc = arco rappresentante la linea di quota (QadArc) + inside = flag di modo, se = true le frecce sono interne altrimenti sono esterne + """ + rot1 = dimLineArc.getTanDirectionOnPt(dimLineArc.getStartPt()) # angolo della linea di quota all'inizio dell'arco + rot2 = dimLineArc.getTanDirectionOnPt(dimLineArc.getEndPt()) # angolo della linea di quota alla fine dell'arco + if inside: + rot1 = rot1 + math.pi + else: + rot2 = rot2 + math.pi + + return qad_utils.normalizeAngle(rot1), qad_utils.normalizeAngle(rot2) + + + # ============================================================================ + # getSpaceForBlock1AndBlock2OnLine + # ============================================================================ + def getSpaceForBlock1AndBlock2OnLineAuxiliary(self, dimLine, rectCorner): + # calcolo la proiezione di un vertice del rettangolo sulla linea dimLine + perpPt = QadPerpendicularity.fromPointToInfinityLine(rectCorner, dimLine) + # se la proiezione non é nel segmento + if dimLine.containsPt(perpPt) == False: + # se la proiezione ricade oltre il punto iniziale di dimLine + if qad_utils.getDistance(dimLine.getStartPt(), perpPt) < qad_utils.getDistance(dimLine.getEndPt(), perpPt): + return 0, dimLine.length() + else: # se la proiezione ricade oltre il punto finale di dimLine + return dimLine.length(), 0 + else: + return qad_utils.getDistance(dimLine.getStartPt(), perpPt), qad_utils.getDistance(dimLine.getEndPt(), perpPt) + + def getSpaceForBlock1AndBlock2OnLine(self, txtRect, dimLine): + """ + txtRect = rettangolo di occupazione del testo (QadPolyline) o None se non c'é il testo + dimLine = linea di quotatura + Restituisce lo spazio disponibile per i blocchi 1 e 2 considerando il rettangolo (QadPolyline) che rappresenta il testo + e la linea di quota dimLine. + """ + if txtRect is None: # se non c'é il testo (é stato spostato fuori dalla linea di quota) + spaceForBlock1 = dimLine.length() / 2 + spaceForBlock2 = spaceForBlock1 + else: + # calcolo la proiezione dei quattro vertici del rettangolo sulla linea dimLine + linearObject = txtRect.getLinearObjectAt(0) + partial1SpaceForBlock1, partial1SpaceForBlock2 = self.getSpaceForBlock1AndBlock2OnLineAuxiliary(dimLine, \ + linearObject.getStartPt()) + linearObject = txtRect.getLinearObjectAt(1) + partial2SpaceForBlock1, partial2SpaceForBlock2 = self.getSpaceForBlock1AndBlock2OnLineAuxiliary(dimLine, \ + linearObject.getStartPt()) + spaceForBlock1 = partial1SpaceForBlock1 if partial1SpaceForBlock1 < partial2SpaceForBlock1 else partial2SpaceForBlock1 + spaceForBlock2 = partial1SpaceForBlock2 if partial1SpaceForBlock2 < partial2SpaceForBlock2 else partial2SpaceForBlock2 + + linearObject = txtRect.getLinearObjectAt(2) + partial3SpaceForBlock1, partial3SpaceForBlock2 = self.getSpaceForBlock1AndBlock2OnLineAuxiliary(dimLine, \ + linearObject.getStartPt()) + if partial3SpaceForBlock1 < spaceForBlock1: + spaceForBlock1 = partial3SpaceForBlock1 + if partial3SpaceForBlock2 < spaceForBlock2: + spaceForBlock2 = partial3SpaceForBlock2 + + linearObject = txtRect.getLinearObjectAt(3) + partial4SpaceForBlock1, partial4SpaceForBlock2 = self.getSpaceForBlock1AndBlock2OnLineAuxiliary(dimLine, \ + linearObject.getStartPt()) + if partial4SpaceForBlock1 < spaceForBlock1: + spaceForBlock1 = partial4SpaceForBlock1 + if partial4SpaceForBlock2 < spaceForBlock2: + spaceForBlock2 = partial4SpaceForBlock2 + + return spaceForBlock1, spaceForBlock2 + + + # ============================================================================ + # getSpaceForBlock1AndBlock2OnArc + # ============================================================================ + def getSpaceForBlock1AndBlock2OnArcAuxiliary(self, dimLineArc, rectCorner): + # calcolo la proiezione di un vertice del rettangolo sull'arco dimLineArc + angle = qad_utils.getAngleBy2Pts(dimLineArc.center, rectCorner) + perpPt = qad_utils.getPolarPointByPtAngle(dimLineArc.center, angle, dimLineArc.radius) + startPt = dimLineArc.getStartPt() + endPt = dimLineArc.getEndPt() + # se la proiezione non é nell'arco + if dimLineArc.containsPt(perpPt) == False: + # se la proiezione ricade oltre il punto startPt (uso le corde) + if qad_utils.getDistance(startPt, perpPt) < qad_utils.getDistance(endPt, perpPt): + return 0, dimLineArc.length() + else: # se la proiezione ricade oltre il punto endPt + return dimLineArc.length(), 0 + else: + arc1 = QadArc(dimLineArc) + arc1.setEndAngleByPt(perpPt) + arc2 = QadArc(dimLineArc) + arc2.setStartAngleByPt(perpPt) + return arc1.length(), arc2.length() + + def getSpaceForBlock1AndBlock2OnArc(self, txtRect, dimLineArc): + """ + txtRect = rettangolo di occupazione del testo o None se non c'é il testo + dimLineArc = arco rappresentante la linea di quotatura + Restituisce lo spazio disponibile per i blocchi 1 e 2 considerando il rettangolo (QadPolyline) che rappresenta il testo + e la linea di quota dimLineArc. + """ + if txtRect is None: # se non c'é il testo (é stato spostato fuori dalla linea di quota) + spaceForBlock1 = dimLineArc.length() / 2 + spaceForBlock2 = spaceForBlock1 + else: + # rettangolo del testo + p1 = txtRect.getLinearObjectAt(0).getStartPt() + p2 = txtRect.getLinearObjectAt(1).getStartPt() + p3 = txtRect.getLinearObjectAt(2).getStartPt() + p4 = txtRect.getLinearObjectAt(3).getStartPt() + rect1 = QgsGeometry.fromPolygonXY([[p1, p2, p3, p4, p1]]) + # quadrato del primo blocco + pt = dimLineArc.getStartPt() + lineRot = dimLineArc.getTanDirectionOnPt(pt) + p1 = qad_utils.getPolarPointByPtAngle(pt, lineRot + math.pi / 2, self.getBlock1Size() / 2) + p2 = qad_utils.getPolarPointByPtAngle(p1, lineRot, self.getBlock1Size()) + p3 = qad_utils.getPolarPointByPtAngle(p2, lineRot - math.pi / 2, self.getBlock1Size()) + p4 = qad_utils.getPolarPointByPtAngle(p3, lineRot, - self.getBlock1Size()) + rect2 = QgsGeometry.fromPolygonXY([[p1, p2, p3, p4, p1]]) + + if rect1.intersects(rect2): + spaceForBlock1 = 0 + else: + spaceForBlock1 = dimLineArc.length() / 2 + + # quadrato del primo blocco + pt = dimLineArc.getEndPt() + lineRot = dimLineArc.getTanDirectionOnPt(pt) - 2 * math.pi + p1 = qad_utils.getPolarPointByPtAngle(pt, lineRot + math.pi / 2, self.getBlock2Size() / 2) + p2 = qad_utils.getPolarPointByPtAngle(p1, lineRot, self.getBlock2Size()) + p3 = qad_utils.getPolarPointByPtAngle(p2, lineRot - math.pi / 2, self.getBlock2Size()) + p4 = qad_utils.getPolarPointByPtAngle(p3, lineRot - 2 * math.pi, self.getBlock2Size()) + rect2 = QgsGeometry.fromPolygonXY([[p1, p2, p3, p4, p1]]) + + if rect1.intersects(rect2): + spaceForBlock2 = 0 + else: + spaceForBlock2 = dimLineArc.length() / 2 + + +# # calcolo la proiezione dei quattro vertici del rettangolo sulla linea dimLinePt1, dimLinePt2 +# linearObject = txtRect.getLinearObjectAt(0) +# partial1SpaceForBlock1, partial1SpaceForBlock2 = self.getSpaceForBlock1AndBlock2OnArcAuxiliary(dimLineArc, \ +# linearObject.getStartPt()) +# linearObject = txtRect.getLinearObjectAt(1) +# partial2SpaceForBlock1, partial2SpaceForBlock2 = self.getSpaceForBlock1AndBlock2OnArcAuxiliary(dimLineArc, \ +# linearObject.getStartPt()) +# spaceForBlock1 = partial1SpaceForBlock1 if partial1SpaceForBlock1 < partial2SpaceForBlock1 else partial2SpaceForBlock1 +# spaceForBlock2 = partial1SpaceForBlock2 if partial1SpaceForBlock2 < partial2SpaceForBlock2 else partial2SpaceForBlock2 +# +# linearObject = txtRect.getLinearObjectAt(2) +# partial3SpaceForBlock1, partial3SpaceForBlock2 = self.getSpaceForBlock1AndBlock2OnArcAuxiliary(dimLineArc, \ +# linearObject.getStartPt()) +# if partial3SpaceForBlock1 < spaceForBlock1: +# spaceForBlock1 = partial3SpaceForBlock1 +# if partial3SpaceForBlock2 < spaceForBlock2: +# spaceForBlock2 = partial3SpaceForBlock2 +# +# linearObject = txtRect.getLinearObjectAt(3) +# partial4SpaceForBlock1, partial4SpaceForBlock2 = self.getSpaceForBlock1AndBlock2OnArcAuxiliary(dimLineArc, \ +# linearObject.getStartPt()) +# if partial4SpaceForBlock1 < spaceForBlock1: +# spaceForBlock1 = partial4SpaceForBlock1 +# if partial4SpaceForBlock2 < spaceForBlock2: +# spaceForBlock2 = partial4SpaceForBlock2 + + return spaceForBlock1, spaceForBlock2 + + + # ============================================================================ + # getSymbolFeature + # ============================================================================ + def getSymbolFeature(self, insPt, rot, isBlock1, textLinearDimComponentOn): + """ + Restituisce la feature per il simbolo delle frecce. + insPt = punto di inserimento + rot = rotazione espressa in radianti + isBlock1 = se True si tratta del blocco1 altrimenti del blocco2 + textLinearDimComponentOn = indica il componente della quota dove é situato il testo di quota (QadDimComponentEnum) + """ + # se non c'é il simbolo di quota + if insPt is None or rot is None: + return None + # se si tratta del simbolo 1 + if isBlock1 == True: + # se non deve essere mostrata la linea 1 di quota (vale solo se il testo é sulla linea di quota) + if self.dimLine1Show == False and \ + (textLinearDimComponentOn == QadDimComponentEnum.DIM_LINE1 or textLinearDimComponentOn == QadDimComponentEnum.DIM_LINE2): + return None + else: # se si tratta del simbolo 2 + # se non deve essere mostrata la linea 2 di quota (vale solo se il testo é sulla linea di quota) + if self.dimLine2Show == False and \ + (textLinearDimComponentOn == QadDimComponentEnum.DIM_LINE1 or textLinearDimComponentOn == QadDimComponentEnum.DIM_LINE2): + return None + + f = QgsFeature(self.getSymbolFeaturePrototype()) + g = fromQadGeomToQgsGeom(QadPoint().set(insPt), self.getSymbolLayer()) + f.setGeometry(g) + + # imposto la scala del blocco + try: + if len(self.scaleFieldName) > 0: + f.setAttribute(self.scaleFieldName, self.blockScale) + except: + pass + + # imposto la rotazione + try: + if len(self.rotFieldName) > 0: + f.setAttribute(self.rotFieldName, qad_utils.toDegrees(rot)) # Converte da radianti a gradi + except: + pass + + # imposto il colore + try: + if len(self.colorFieldName) > 0: + f.setAttribute(self.colorFieldName, self.dimLineColor) + except: + pass + + # imposto il tipo di componente della quotatura + if self.dimType == QadDimTypeEnum.RADIUS: # se quotatura tipo raggio + try: + if len(self.componentFieldName) > 0: + f.setAttribute(self.componentFieldName, QadDimComponentEnum.LEADER_BLOCK) + except: + pass + + try: + if len(self.symbolFieldName) > 0: + f.setAttribute(self.symbolFieldName, self.blockLeaderName) + except: + pass + else: + try: + if len(self.componentFieldName) > 0: + f.setAttribute(self.componentFieldName, QadDimComponentEnum.BLOCK1 if isBlock1 else QadDimComponentEnum.BLOCK2) + except: + pass + + try: + if len(self.symbolFieldName) > 0: + f.setAttribute(self.symbolFieldName, self.block1Name if isBlock1 else self.block2Name) + except: + pass + + return f + + + # ============================================================================ + # getDimPointFeature + # ============================================================================ + def getDimPointFeature(self, insPt, isDimPt1): + """ + Restituisce la feature per il punto di quotatura. + insPt = punto di inserimento + isDimPt1 = se True si tratta del punto di quotatura 1 altrimenti del punto di quotatura 2 + """ + symbolFeaturePrototype = self.getSymbolFeaturePrototype() + if symbolFeaturePrototype is None: + return None + f = QgsFeature(symbolFeaturePrototype) + g = fromQadGeomToQgsGeom(QadPoint().set(insPt), self.getSymbolLayer()) # trasformo la geometria + f.setGeometry(g) + + # imposto il tipo di componente della quotatura + try: + f.setAttribute(self.componentFieldName, QadDimComponentEnum.DIM_PT1 if isDimPt1 else QadDimComponentEnum.DIM_PT2) + except: + pass + + try: + # imposto il colore + if len(self.colorFieldName) > 0: + f.setAttribute(self.colorFieldName, self.dimLineColor) + except: + pass + + return f + + + # ============================================================================ + # getLeaderSymbolFeature + # ============================================================================ + def getLeaderSymbolFeature(self, insPt, rot): + """ + Restituisce la feature per il simbolo delle frecce per la linea direttrice. + insPt = punto di inserimento + rot = rotazione espressa in radianti + """ + # se non c'é il simbolo di quota + if insPt is None or rot is None: + return None + + f = QgsFeature(self.getSymbolFeaturePrototype()) + g = fromQadGeomToQgsGeom(QadPoint().set(insPt), self.getSymbolLayer()) # trasformo la geometria + f.setGeometry(g) + + # imposto la scala del blocco + try: + if len(self.scaleFieldName) > 0: + f.setAttribute(self.scaleFieldName, self.blockScale) + except: + pass + + # imposto la rotazione + try: + if len(self.rotFieldName) > 0: + f.setAttribute(self.rotFieldName, qad_utils.toDegrees(rot)) # Converte da radianti a gradi + except: + pass + + # imposto il colore + try: + if len(self.colorFieldName) > 0: + f.setAttribute(self.colorFieldName, self.dimLineColor) + except: + pass + + # imposto il tipo di componente della quotatura + try: + if len(self.componentFieldName) > 0: + f.setAttribute(self.componentFieldName, QadDimComponentEnum.LEADER_BLOCK) + except: + pass + + try: + if len(self.symbolFieldName) > 0: + f.setAttribute(self.symbolFieldName, self.blockLeaderName) + except: + pass + + return f + + + # ============================================================================ + # getArcSymbolLineFeature + # ============================================================================ + def getArcSymbolLineFeature(self, arc): + """ + Restituisce la feature per il simbolo dell'arco. + arc = arco + """ + # se non c'é l'arco + if arc is None: + return None + + f = QgsFeature(self.getLinearFeaturePrototype()) + g = fromQadGeomToQgsGeom(arc, self.getLinearLayer()) # trasformo la geometria + f.setGeometry(g) + + try: + # imposto il tipo di componente della quotatura + if len(self.componentFieldName) > 0: + f.setAttribute(self.componentFieldName, QadDimComponentEnum.ARC_BLOCK) + except: + pass + + try: + # imposto il tipo di linea + if len(self.lineTypeFieldName) > 0: + f.setAttribute(self.lineTypeFieldName, self.dimLineLineType) + except: + pass + + try: + # imposto il colore + if len(self.colorFieldName) > 0: + f.setAttribute(self.colorFieldName, self.dimLineColor) + except: + pass + + return f + + + # ============================================================================ + # FUNZIONI PER I BLOCCHI - FINE + # FUNZIONI PER IL TESTO - INIZIO + # ============================================================================ + + + # ============================================================================ + # getFormattedText + # ============================================================================ + def getFormattedText(self, measure): + """ + Restituisce il testo della misura della quota formattato + """ + if type(measure) == int or type(measure) == float: + return qad_utils.numToStringFmt(measure, self.textDecimals, self.textDecimalSep, \ + self.textSuppressLeadingZeros, self.textDecimalZerosSuppression, \ + self.textPrefix, self.textSuffix) + elif type(measure) == unicode or type(measure) == str: + return measure + else: + return "" + + + # ============================================================================ + # getNumericText + # ============================================================================ + def getNumericText(self, text): + """ + Restituisce il valore numerico del testo della misura della quota formattato + """ + textToConvert = text.lstrip(self.textPrefix) + textToConvert = textToConvert.rstrip(self.textSuffix) + textToConvert = textToConvert.replace(self.textDecimalSep, ".") + + return qad_utils.str2float(textToConvert) + + + # ============================================================================ + # textRectToQadPolyline + # ============================================================================ + def textRectToQadPolyline(self, ptBottomLeft, textWidth, textHeight, rot): + """ + Restituisce il rettangolo che rappresenta il testo sotto forma di una QadPolyline. + <2>----width----<3> + | | + height height + | | + <1>----width----<4> + """ + pt2 = qad_utils.getPolarPointByPtAngle(ptBottomLeft, rot + (math.pi / 2), textHeight) + pt3 = qad_utils.getPolarPointByPtAngle(pt2, rot, textWidth) + pt4 = qad_utils.getPolarPointByPtAngle(ptBottomLeft, rot , textWidth) + res = QadPolyline() + res.fromPolyline([ptBottomLeft, pt2, pt3, pt4, ptBottomLeft]) + return res + + + # ============================================================================ + # getBoundingPointsTextRectProjectedToLine + # ============================================================================ + def getBoundingPointsTextRectProjectedToLine(self, line, textRect): + """ + Restituisce una lista di 2 punti che sono i punti estremi della proiezione dei 4 angoli del rettangolo + sulla linea . + """ + rectCorners = textRect.asPolyline() + # calcolo la proiezione degli angoli del rettangolo sulla linea pt1-pt2 + perpPts = [] + + p = QadPerpendicularity.fromPointToInfinityLine(rectCorners[0], line) + qad_utils.appendUniquePointToList(perpPts, p) + p = QadPerpendicularity.fromPointToInfinityLine(rectCorners[1], line) + qad_utils.appendUniquePointToList(perpPts, p) + p = QadPerpendicularity.fromPointToInfinityLine(rectCorners[2], line) + qad_utils.appendUniquePointToList(perpPts, p) + p = QadPerpendicularity.fromPointToInfinityLine(rectCorners[3], line) + qad_utils.appendUniquePointToList(perpPts, p) + + return getBoundingPtsOnOnInfinityLine(perpPts) + + + # ============================================================================ + # getTextPositionOnLine + # ============================================================================ + def getTextPositionOnLine(self, pt1, pt2, textWidth, textHeight, horizontalPos, verticalPos, rotMode): + """ + pt1 = primo punto della linea + pt2 = secondo punto della linea + textWidth = larghezza testo + textHeight = altezza testo + + Restituisce il punto di inserimento e la rotazione del testo lungo la linea pt1-pt2 con le modalità: + horizontalPos = QadDimStyleTxtHorizontalPosEnum.CENTERED_LINE (centrato alla linea) + QadDimStyleTxtHorizontalPosEnum.FIRST_EXT_LINE (vicino al punto pt1) + QadDimStyleTxtHorizontalPosEnum.SECOND_EXT_LINE (vicino al punto pt2) + verticalPos = QadDimStyleTxtVerticalPosEnum.CENTERED_LINE (centrato alla linea) + QadDimStyleTxtVerticalPosEnum.ABOVE_LINE (sopra alla linea) + QadDimStyleTxtVerticalPosEnum.BELOW_LINE (sotto alla linea) + rotMode = QadDimStyleTxtRotModeEnum.HORIZONTAL (testo orizzontale) + QadDimStyleTxtRotModeEnum.ALIGNED_LINE (testo allineato con la linea) + QadDimStyleTxtRotModeEnum.FORCED_ROTATION (testo con rotazione forzata) + """ + lineRot = qad_utils.getAngleBy2Pts(pt1, pt2) # angolo della linea + + if (lineRot > math.pi * 3 / 2 and lineRot <= math.pi * 2) or \ + (lineRot >= 0 and lineRot <= math.pi / 2): # da sx a dx + textInsPtCloseToPt1 = True + else: # da dx a sx + textInsPtCloseToPt1 = False + + if rotMode == QadDimStyleTxtRotModeEnum.ALIGNED_LINE: # testo allineato alla linea + if lineRot > (math.pi / 2) and lineRot <= math.pi * 3 / 2: # se il testo é capovolto lo giro + textRot = lineRot - math.pi + else: + textRot = lineRot + + # allineamento orizzontale + # ========================= + if horizontalPos == QadDimStyleTxtHorizontalPosEnum.CENTERED_LINE: # testo centrato alla linea + middlePt = qad_utils.getMiddlePoint(pt1, pt2) + if textInsPtCloseToPt1: # il punto di inserimento del testo é vicino a pt1 + insPt = qad_utils.getPolarPointByPtAngle(middlePt, lineRot - math.pi, textWidth / 2) + else: # il punto di inserimento del testo é vicino a pt2 + insPt = qad_utils.getPolarPointByPtAngle(middlePt, lineRot, textWidth / 2) + + elif horizontalPos == QadDimStyleTxtHorizontalPosEnum.FIRST_EXT_LINE: # testo vicino a pt1 + # uso 2 volte textOffsetDist perché una volta é la distanza dal punto pt1 + un offset intorno al testo + if textInsPtCloseToPt1: # il punto di inserimento del testo é vicino a pt1 + insPt = qad_utils.getPolarPointByPtAngle(pt1, lineRot, self.textOffsetDist + self.textOffsetDist) + else: # il punto di inserimento del testo é vicino a pt2 + insPt = qad_utils.getPolarPointByPtAngle(pt1, lineRot, textWidth + self.textOffsetDist + self.textOffsetDist) + + elif horizontalPos == QadDimStyleTxtHorizontalPosEnum.SECOND_EXT_LINE: # testo vicino a pt2 + # uso 2 volte textOffsetDist perché una volta é la distanza dal punto pt1 + un offset intorno al testo + lineLen = qad_utils.getDistance(pt1, pt2) + if textInsPtCloseToPt1: # il punto di inserimento del testo é vicino a pt1 + insPt = qad_utils.getPolarPointByPtAngle(pt1, lineRot, lineLen - textWidth - (self.textOffsetDist + self.textOffsetDist)) + else: # il punto di inserimento del testo é vicino a pt2 + insPt = qad_utils.getPolarPointByPtAngle(pt1, lineRot, lineLen - (self.textOffsetDist + self.textOffsetDist)) + + # allineamento verticale + # ========================= + if verticalPos == QadDimStyleTxtVerticalPosEnum.CENTERED_LINE: # testo centrato alla linea + if textInsPtCloseToPt1: # il punto di inserimento del testo é vicino a pt1 + insPt = qad_utils.getPolarPointByPtAngle(insPt, lineRot - math.pi / 2, textHeight / 2) + else: # il punto di inserimento del testo é vicino a pt2 + insPt = qad_utils.getPolarPointByPtAngle(insPt, lineRot + math.pi / 2, textHeight / 2) + elif verticalPos == QadDimStyleTxtVerticalPosEnum.ABOVE_LINE: # sopra alla linea + # uso 2 volte textOffsetDist perché una volta é la distanza dalla linea + un offset intorno al testo + if textInsPtCloseToPt1: # il punto di inserimento del testo é vicino a pt1 + insPt = qad_utils.getPolarPointByPtAngle(insPt, lineRot + math.pi / 2, self.textOffsetDist + self.textOffsetDist) + else: # il punto di inserimento del testo é vicino a pt2 + insPt = qad_utils.getPolarPointByPtAngle(insPt, lineRot - math.pi / 2, self.textOffsetDist + self.textOffsetDist) + elif verticalPos == QadDimStyleTxtVerticalPosEnum.BELOW_LINE: # sotto alla linea + # uso 2 volte textOffsetDist perché una volta é la distanza dalla linea + un offset intorno al testo + if textInsPtCloseToPt1: # il punto di inserimento del testo é vicino a pt1 + insPt = qad_utils.getPolarPointByPtAngle(insPt, lineRot - math.pi / 2, textHeight + (self.textOffsetDist + self.textOffsetDist)) + else: # il punto di inserimento del testo é vicino a pt2 + insPt = qad_utils.getPolarPointByPtAngle(insPt, lineRot + math.pi / 2, textHeight + (self.textOffsetDist + self.textOffsetDist)) + + # testo orizzontale o testo con rotazione forzata + elif rotMode == QadDimStyleTxtRotModeEnum.HORIZONTAL or rotMode == QadDimStyleTxtRotModeEnum.FORCED_ROTATION: + + lineLen = qad_utils.getDistance(pt1, pt2) # lunghezza della linea + textRot = 0.0 if rotMode == QadDimStyleTxtRotModeEnum.HORIZONTAL else self.textForcedRot + + # cerco qual'é l'angolo del rettangolo più vicino alla linea + # <2>----width----<3> + # | | + # height height + # | | + # <1>----width----<4> + # ricavo il rettangolo che racchiude il testo e lo posiziono con il suo angolo in basso a sinistra sul punto pt1 + textRect = self.textRectToQadPolyline(pt1, textWidth, textHeight, textRot) + # ottengo i punti estremi della proiezione del rettangolo sulla linea + pts = self.getBoundingPointsTextRectProjectedToLine(QadLine().set(pt1, pt2), textRect) + projectedTextWidth = qad_utils.getDistance(pts[0], pts[1]) + + # allineamento orizzontale + # ========================= + if horizontalPos == QadDimStyleTxtHorizontalPosEnum.CENTERED_LINE: # testo centrato alla linea + closestPtToPt1 = qad_utils.getPolarPointByPtAngle(pt1, lineRot, (lineLen - projectedTextWidth) / 2) + + elif horizontalPos == QadDimStyleTxtHorizontalPosEnum.FIRST_EXT_LINE: # testo vicino a pt1 + closestPtToPt1 = qad_utils.getPolarPointByPtAngle(pt1, lineRot, self.textOffsetDist) + + elif horizontalPos == QadDimStyleTxtHorizontalPosEnum.SECOND_EXT_LINE: # testo vicino a pt2 + closestPtToPt1 = qad_utils.getPolarPointByPtAngle(pt1, lineRot, lineLen - self.textOffsetDist - projectedTextWidth) + + # se la linea ha una angolo tra (0-90] gradi (primo quadrante) + if lineRot > 0 and lineRot <= math.pi / 2: + # il punto più vicino a pt1 corrisponde all'angolo in basso a sinistra del rettangolo che racchiude il testo + # mi ricavo il punto di inserimento del testo (angolo in basso a sinistra) + insPt = QgsPointXY(closestPtToPt1) + textRect = self.textRectToQadPolyline(insPt, textWidth, textHeight, textRot) + rectCorners = textRect.asPolyline() + + # allineamento verticale + # ========================= + if verticalPos == QadDimStyleTxtVerticalPosEnum.ABOVE_LINE: # sopra alla linea + # l'angolo 4 deve essere sopra la linea distante self.textOffsetDist dalla stessa + rectPt = rectCorners[3] + elif verticalPos == QadDimStyleTxtVerticalPosEnum.BELOW_LINE: # sotto alla linea + # l'angolo 2 deve essere sotto la linea distante self.textOffsetDist dalla stessa + rectPt = rectCorners[1] + + # se la linea ha una angolo tra (90-180] gradi (secondo quadrante) + elif lineRot > math.pi / 2 and lineRot <= math.pi: + # il punto più vicino a pt1 corrisponde all'angolo in basso a destra del rettangolo che racchiude il testo + # mi ricavo il punto di inserimento del testo (angolo in basso a sinistra) + insPt = QgsPointXY(closestPtToPt1.x() - textWidth, closestPtToPt1.y()) + textRect = self.textRectToQadPolyline(insPt, textWidth, textHeight, textRot) + rectCorners = textRect.asPolyline() + + # allineamento verticale + # ========================= + if verticalPos == QadDimStyleTxtVerticalPosEnum.ABOVE_LINE: # sopra alla linea + # l'angolo 1 deve essere sopra la linea distante self.textOffsetDist dalla stessa + rectPt = rectCorners[0] + elif verticalPos == QadDimStyleTxtVerticalPosEnum.BELOW_LINE: # sotto alla linea + # l'angolo 3 deve essere sotto la linea distante self.textOffsetDist dalla stessa + rectPt = rectCorners[2] + + # se la linea ha una angolo tra (180-270] gradi (terzo quadrante) + elif lineRot > math.pi and lineRot <= math.pi * 3 / 2: + # il punto più vicino a pt1 corrisponde all'angolo in alto a destra del rettangolo che racchiude il testo + # mi ricavo il punto di inserimento del testo (angolo in basso a sinistra) + insPt = QgsPointXY(closestPtToPt1.x() - textWidth, closestPtToPt1.y() - textHeight) + textRect = self.textRectToQadPolyline(insPt, textWidth, textHeight, textRot) + rectCorners = textRect.asPolyline() + + # allineamento verticale + # ========================= + if verticalPos == QadDimStyleTxtVerticalPosEnum.ABOVE_LINE: # sopra alla linea + # l'angolo 4 deve essere sopra la linea distante self.textOffsetDist dalla stessa + rectPt = rectCorners[3] + elif verticalPos == QadDimStyleTxtVerticalPosEnum.BELOW_LINE: # sotto alla linea + # l'angolo 2 deve essere sotto la linea distante self.textOffsetDist dalla stessa + rectPt = rectCorners[1] + + # se la linea ha una angolo tra (270-360] gradi (quarto quadrante) + elif (lineRot > math.pi * 3 / 2 and lineRot <= 360) or lineRot == 0: + # il punto più vicino a pt1 corrisponde all'angolo in alto a destra del rettangolo che racchiude il testo + # mi ricavo il punto di inserimento del testo (angolo in alto a sinistra) + insPt = QgsPointXY(closestPtToPt1.x(), closestPtToPt1.y() - textHeight) + textRect = self.textRectToQadPolyline(insPt, textWidth, textHeight, textRot) + rectCorners = textRect.asPolyline() + + # allineamento verticale + # ========================= + if verticalPos == QadDimStyleTxtVerticalPosEnum.ABOVE_LINE: # sopra alla linea + # l'angolo 1 deve essere sopra la linea distante self.textOffsetDist dalla stessa + rectPt = rectCorners[0] + elif verticalPos == QadDimStyleTxtVerticalPosEnum.BELOW_LINE: # sotto alla linea + # l'angolo 3 deve essere sotto la linea distante self.textOffsetDist dalla stessa + rectPt = rectCorners[2] + + # allineamento verticale + # ========================= + if verticalPos == QadDimStyleTxtVerticalPosEnum.CENTERED_LINE: # testo centrato alla linea + # il centro del rettangolo deve essere sulla linea + centerPt = qad_utils.getPolarPointByPtAngle(rectCorners[0], \ + qad_utils.getAngleBy2Pts(rectCorners[0], rectCorners[2]), \ + qad_utils.getDistance(rectCorners[0], rectCorners[2]) / 2) + perpPt = qad_utils.getPerpendicularPointOnInfinityLine(pt1, pt2, centerPt) + offsetAngle = qad_utils.getAngleBy2Pts(centerPt, perpPt) + offsetDist = qad_utils.getDistance(centerPt, perpPt) + elif verticalPos == QadDimStyleTxtVerticalPosEnum.ABOVE_LINE: # sopra alla linea + # l'angolo deve essere sopra la linea distante self.textOffsetDist dalla stessa + perpPt = qad_utils.getPerpendicularPointOnInfinityLine(pt1, pt2, rectPt) + # se la linea ha una angolo tra (90-270] gradi + if lineRot > math.pi / 2 and lineRot <= math.pi * 3 / 2: + offsetAngle = lineRot - math.pi / 2 + else: # se la linea ha una angolo tra (270-90] gradi + offsetAngle = lineRot + math.pi / 2 + offsetDist = qad_utils.getDistance(rectPt, perpPt) + self.textOffsetDist + elif verticalPos == QadDimStyleTxtVerticalPosEnum.BELOW_LINE: # sotto alla linea + # l'angolo deve essere sotto la linea distante self.textOffsetDist dalla stessa + perpPt = qad_utils.getPerpendicularPointOnInfinityLine(pt1, pt2, rectPt) + # se la linea ha una angolo tra (90-270] gradi + if lineRot > math.pi / 2 and lineRot <= math.pi * 3 / 2: + offsetAngle = lineRot + math.pi / 2 + else: # se la linea ha una angolo tra (270-90] gradi + offsetAngle = lineRot - math.pi / 2 + offsetDist = qad_utils.getDistance(rectPt, perpPt) + self.textOffsetDist + + # traslo il rettangolo + insPt = qad_utils.getPolarPointByPtAngle(insPt, offsetAngle, offsetDist) + textRect = self.textRectToQadPolyline(insPt, textWidth, textHeight, textRot) + + return insPt, textRot + + + # ============================================================================ + # getTextPositionOnArc + # ============================================================================ + def getTextPositionOnArc(self, arc, textWidth, textHeight, horizontalPos, verticalPos, rotMode): + """ + arc = oggetto QadArc + textWidth = larghezza testo compreso l'offset (2 volte offset, davanti e dietro il testo) + textHeight = altezza testo compreso l'offset (2 volte offset, sopra e sotto il testo) + + Restituisce il punto di inserimento e la rotazione del testo lungo l'arco con le modalità: + horizontalPos = QadDimStyleTxtHorizontalPosEnum.CENTERED_LINE (centrato alla linea) + QadDimStyleTxtHorizontalPosEnum.FIRST_EXT_LINE (vicino al punto pt1) + QadDimStyleTxtHorizontalPosEnum.SECOND_EXT_LINE (vicino al punto pt2) + verticalPos = QadDimStyleTxtVerticalPosEnum.CENTERED_LINE (centrato alla linea) + QadDimStyleTxtVerticalPosEnum.ABOVE_LINE (sopra alla linea) + QadDimStyleTxtVerticalPosEnum.BELOW_LINE (sotto alla linea) + rotMode = QadDimStyleTxtRotModeEnum.HORIZONTAL (testo orizzontale) + QadDimStyleTxtRotModeEnum.ALIGNED_LINE (testo allineato con la linea) + QadDimStyleTxtRotModeEnum.FORCED_ROTATION (testo con rotazione forzata) + """ + arcLength = arc.length() + + # calcolo lo sviluppo della lunghezza del testo (con gli offset) sull'arco (il testo è una linea retta) + myArc = QadArc() + if myArc.fromStartCenterPtsChord(arc.getStartPt(), arc.center, textWidth): + TextWidthOnArc = myArc.length() + else: + TextWidthOnArc = textWidth + # calcolo lo sviluppo della lunghezza dell'offset sull'arco (il testo è una linea retta) + if myArc.fromStartCenterPtsChord(arc.getStartPt(), arc.center, self.textOffsetDist): + textOffsetDistOnArc = myArc.length() + else: + textOffsetDistOnArc = self.textOffsetDist + + if rotMode == QadDimStyleTxtRotModeEnum.HORIZONTAL: # testo orizzontale + textRot = 0.0 + elif rotMode == QadDimStyleTxtRotModeEnum.FORCED_ROTATION: # testo con rotazione forzata + textRot = self.textForcedRot + + + # allineamento orizzontale + # ========================= + if horizontalPos == QadDimStyleTxtHorizontalPosEnum.CENTERED_LINE: # testo centrato alla linea + insPtCenterTxt = arc.getMiddlePt() + lineRot = arc.getTanDirectionOnPt(insPtCenterTxt) + + if rotMode == QadDimStyleTxtRotModeEnum.ALIGNED_LINE: # testo allineato alla linea + textRot = lineRot + if textRot > (math.pi / 2) and textRot <= math.pi * 3 / 2: # se il testo é capovolto lo giro + textRot = textRot - math.pi + + + elif horizontalPos == QadDimStyleTxtHorizontalPosEnum.FIRST_EXT_LINE: # testo vicino a pt1 + # uso 2 volte textOffsetDist perché una volta é la distanza dal punto pt1 + un offset intorno al testo + insPtCenterTxt, dummyTg = arc.getPointFromStart(textOffsetDistOnArc + textOffsetDistOnArc + TextWidthOnArc / 2) + + lineRot = arc.getTanDirectionOnPt(insPtCenterTxt) + + if rotMode == QadDimStyleTxtRotModeEnum.ALIGNED_LINE: # testo allineato alla linea + textRot = lineRot + if textRot > (math.pi / 2) and textRot <= math.pi * 3 / 2: # se il testo é capovolto lo giro + textRot = textRot - math.pi + + + elif horizontalPos == QadDimStyleTxtHorizontalPosEnum.SECOND_EXT_LINE: # testo vicino a pt2 + # uso 2 volte textOffsetDist perché una volta é la distanza dal punto pt1 + un offset intorno al testo + insPtCenterTxt, dummyTg = arc.getPointFromStart(arcLength - TextWidthOnArc / 2 - textOffsetDistOnArc - textOffsetDistOnArc) + lineRot = arc.getTanDirectionOnPt(insPtCenterTxt) + + if rotMode == QadDimStyleTxtRotModeEnum.ALIGNED_LINE: # testo allineato alla linea + textRot = lineRot + if textRot > (math.pi / 2) and textRot <= math.pi * 3 / 2: # se il testo é capovolto lo giro + textRot = textRot - math.pi + + # angolo della linea che congiunge il centro dell'arco con il centro del testo + angleOnCenterTxt = qad_utils.getAngleBy2Pts(arc.center, insPtCenterTxt) + # normalizzo l'angolo + textRot = qad_utils.normalizeAngle(textRot) + if (textRot > math.pi * 3 / 2 and textRot <= math.pi * 2) or \ + (textRot >= 0 and textRot < math.pi / 2): # da sx a dx + insPt = qad_utils.getPolarPointByPtAngle(insPtCenterTxt, textRot, -textWidth / 2) + else: + insPt = qad_utils.getPolarPointByPtAngle(insPtCenterTxt, textRot, textWidth / 2) + + + # allineamento verticale + # ========================= + angleOnCenterTxt = qad_utils.getAngleBy2Pts(arc.center, insPtCenterTxt) + + if verticalPos == QadDimStyleTxtVerticalPosEnum.CENTERED_LINE: # testo centrato alla linea + if textRot > (math.pi / 2) and textRot <= math.pi * 3 / 2: # se il testo é capovolto + if (angleOnCenterTxt > 0 and angleOnCenterTxt <= math.pi): # il testo va verso il punto finale dell'arco + insPt = qad_utils.getPolarPointByPtAngle(insPt, angleOnCenterTxt, textHeight / 2) + else: # il testo va verso il punto iniziale dell'arco + insPt = qad_utils.getPolarPointByPtAngle(insPt, angleOnCenterTxt, -textHeight / 2) + else: # il testo è dritto + if (angleOnCenterTxt > 0 and angleOnCenterTxt <= math.pi): # il testo va verso il punto iniziale dell'arco + insPt = qad_utils.getPolarPointByPtAngle(insPt, angleOnCenterTxt, -textHeight / 2) + else: # il testo va verso il punto finale dell'arco + insPt = qad_utils.getPolarPointByPtAngle(insPt, angleOnCenterTxt, textHeight / 2) + + + elif verticalPos == QadDimStyleTxtVerticalPosEnum.ABOVE_LINE: # sopra alla linea + if textRot > (math.pi / 2) and textRot <= math.pi * 3 / 2: # se il testo é capovolto + if (angleOnCenterTxt > 0 and angleOnCenterTxt <= math.pi): # il testo va verso il punto finale dell'arco + insPt = qad_utils.getPolarPointByPtAngle(insPt, angleOnCenterTxt, -self.textOffsetDist) + else: # il testo va verso il punto iniziale dell'arco + insPt = qad_utils.getPolarPointByPtAngle(insPt, angleOnCenterTxt, self.textOffsetDist) + else: # il testo è dritto + if (angleOnCenterTxt > 0 and angleOnCenterTxt <= math.pi): # il testo va verso il punto iniziale dell'arco + insPt = qad_utils.getPolarPointByPtAngle(insPt, angleOnCenterTxt, self.textOffsetDist) + else: # il testo va verso il punto finale dell'arco + insPt = qad_utils.getPolarPointByPtAngle(insPt, angleOnCenterTxt, -self.textOffsetDist) + + + elif verticalPos == QadDimStyleTxtVerticalPosEnum.BELOW_LINE: # sotto alla linea + if textRot > (math.pi / 2) and textRot <= math.pi * 3 / 2: # se il testo é capovolto + if (angleOnCenterTxt > 0 and angleOnCenterTxt <= math.pi): # il testo va verso il punto finale dell'arco + insPt = qad_utils.getPolarPointByPtAngle(insPt, angleOnCenterTxt, (textHeight + self.textOffsetDist)) + else: # il testo va verso il punto iniziale dell'arco + insPt = qad_utils.getPolarPointByPtAngle(insPt, angleOnCenterTxt, -(textHeight + self.textOffsetDist)) + else: # il testo è dritto + if (angleOnCenterTxt > 0 and angleOnCenterTxt <= math.pi): # il testo va verso il punto iniziale dell'arco + insPt = qad_utils.getPolarPointByPtAngle(insPt, angleOnCenterTxt, -(textHeight + self.textOffsetDist)) + else: # il testo va verso il punto finale dell'arco + insPt = qad_utils.getPolarPointByPtAngle(insPt, angleOnCenterTxt, (textHeight + self.textOffsetDist)) + + + return insPt, textRot + + + # ============================================================================ + # getTextPosAndLinesOutOfDimLines + # ============================================================================ + def getTextPosAndLinesOutOfDimLines(self, dimLinePt1, dimLinePt2, textWidth, textHeight): + """ + Restituisce una lista di 3 elementi nel caso il testo venga spostato fuori dalle linee + di estensione perché era troppo grosso: + - il primo elemento é il punto di inserimento + - il secondo elemento é la rotazione del testo + - il terzo elemento é una lista di linee da usare come porta quota + + La funzione lo posizione a lato della linea di estensione 2. + dimLinePt1 = primo punto della linea di quota (QgsPointXY) + dimLinePt2 = secondo punto della linea di quota (QgsPointXY) + textWidth = larghezza testo + textHeight = altezza testo + """ + # Ottengo le linee porta quota per il testo esterno + lines = self.getLeaderLinesOnLine(dimLinePt1, dimLinePt2, textWidth, textHeight) + # considero l'ultima che é quella che si riferisce al testo + line = lines.getLinearObjectAt(-1) + + if self.textRotMode == QadDimStyleTxtRotModeEnum.FORCED_ROTATION: + textRotMode = QadDimStyleTxtRotModeEnum.FORCED_ROTATION + else: + textRotMode = QadDimStyleTxtRotModeEnum.ALIGNED_LINE + + textInsPt, textRot = self.getTextPositionOnLine(line.getStartPt(), line.getEndPt(), textWidth, textHeight, \ + QadDimStyleTxtHorizontalPosEnum.FIRST_EXT_LINE, \ + self.textVerticalPos, textRotMode) + return textInsPt, textRot, lines + + + # ============================================================================ + # getTextPosAndLinesOutOfDimArc + # ============================================================================ + def getTextPosAndLinesOutOfDimArc(self, dimLineArc, textWidth, textHeight): + """ + Restituisce una lista di 3 elementi nel caso il testo venga spostato fuori dalle linee + di estensione perché era troppo grosso: + - il primo elemento é il punto di inserimento del testo + - il secondo elemento é la rotazione del testo + - il terzo elemento é una lista di linee da usare come porta quota + + La funzione lo posizione a lato della linea di estensione 2. + getTextPosAndLinesOutOfDimArc = arco rappresentante la linea di quota (QadArc) + textWidth = larghezza testo + textHeight = altezza testo + """ + # Ottengo le linee porta quota per il testo esterno + lines = self.getLeaderLinesOnArc(dimLineArc, textWidth, textHeight) + # considero l'ultima che é quella che si riferisce al testo + line = lines.getLinearObjectAt(-1) + + if self.textRotMode == QadDimStyleTxtRotModeEnum.FORCED_ROTATION: + textRotMode = QadDimStyleTxtRotModeEnum.FORCED_ROTATION + else: + textRotMode = QadDimStyleTxtRotModeEnum.ALIGNED_LINE + + textInsPt, textRot = self.getTextPositionOnLine(line.getStartPt(), line.getEndPt(), textWidth, textHeight, \ + QadDimStyleTxtHorizontalPosEnum.FIRST_EXT_LINE, \ + self.textVerticalPos, textRotMode) + return textInsPt, textRot, lines + + + # ============================================================================ + # getLinearTextAndBlocksPosition + # ============================================================================ + def getLinearTextAndBlocksPosition(self, dimPt1, dimPt2, dimLine, textWidth, textHeight): + """ + dimPt1 = primo punto da quotare + dimPt2 = secondo punto da quotare + dimLine = linea di quota (QadLine) + textWidth = larghezza testo + textHeight = altezza testo + + Restituisce una lista di 4 elementi: + - il primo elemento é una lista con il punto di inserimento del testo della quota e la sua rotazione + - il secondo elemento é una lista con flag che indica il tipo della linea sulla quale é stato messo il testo; vedi QadDimComponentEnum + e una lista di linee "leader" nel caso il testo sia all'esterno della quota + - il terzo elemento é la rotazione del primo blocco delle frecce; può essere None se non visibile + - il quarto elemento é la rotazione del secondo blocco delle frecce; può essere None se non visibile + """ + textInsPt = None # punto di inserimento del testo + textRot = None # rotazione del testo + textLinearDimComponentOn = None # codice del componente lineare sul quale é posizionato il testo + txtLeaderLines = None # lista di linee "leader" nel caso il testo sia all'esterno della quota + block1Rot = None # rotazione del primo blocco delle frecce + block2Rot = None # rotazione del secondo blocco delle frecce + + # se il testo é tra le linee di estensione della quota + if self.textHorizontalPos == QadDimStyleTxtHorizontalPosEnum.CENTERED_LINE or \ + self.textHorizontalPos == QadDimStyleTxtHorizontalPosEnum.FIRST_EXT_LINE or \ + self.textHorizontalPos == QadDimStyleTxtHorizontalPosEnum.SECOND_EXT_LINE: + + dimLineRot = qad_utils.getAngleBy2Pts(dimLine.getStartPt(), dimLine.getEndPt()) # angolo della linea di quota + + # cambio gli estremi della linea di quota per considerare lo spazio occupato dai blocchi + dimLinePt1Offset = qad_utils.getPolarPointByPtAngle(dimLine.getStartPt(), dimLineRot, self.getBlock1Size()) + dimLinePt2Offset = qad_utils.getPolarPointByPtAngle(dimLine.getEndPt(), dimLineRot + math.pi, self.getBlock2Size()) + + # testo sopra o sotto alla linea di quota nel caso la linea di quota non sia orizzontale + # e il testo sia dentro le linee di estensione e forzato orizzontale allora il testo diventa centrato + if (self.textVerticalPos == QadDimStyleTxtVerticalPosEnum.ABOVE_LINE or self.textVerticalPos == QadDimStyleTxtVerticalPosEnum.BELOW_LINE) and \ + (dimLineRot != 0 and dimLineRot != math.pi) and self.textRotMode == QadDimStyleTxtRotModeEnum.HORIZONTAL: + textVerticalPos = QadDimStyleTxtVerticalPosEnum.CENTERED_LINE + # testo posizionato nella parte opposta ai punti di quotatura + elif self.textVerticalPos == QadDimStyleTxtVerticalPosEnum.EXTERN_LINE: + # angolo dal primo punto di quota al primo punto della linea di quota + dimPtToDimLinePt_rot = qad_utils.getAngleBy2Pts(dimPt1, dimLine.getStartPt()) + if dimPtToDimLinePt_rot > 0 and \ + (dimPtToDimLinePt_rot < math.pi or qad_utils.doubleNear(dimPtToDimLinePt_rot, math.pi)): + textVerticalPos = QadDimStyleTxtVerticalPosEnum.ABOVE_LINE + else: + textVerticalPos = QadDimStyleTxtVerticalPosEnum.BELOW_LINE + else: + textVerticalPos = self.textVerticalPos + + if self.textRotMode == QadDimStyleTxtRotModeEnum.ISO: + textInsPt, textRot = self.getTextPositionOnLine(dimLinePt1Offset, dimLinePt2Offset, textWidth, textHeight, \ + self.textHorizontalPos, textVerticalPos, QadDimStyleTxtRotModeEnum.ALIGNED_LINE) + else: + textInsPt, textRot = self.getTextPositionOnLine(dimLinePt1Offset, dimLinePt2Offset, textWidth, textHeight, \ + self.textHorizontalPos, textVerticalPos, self.textRotMode) + + rect = self.textRectToQadPolyline(textInsPt, textWidth, textHeight, textRot) + spaceForBlock1, spaceForBlock2 = self.getSpaceForBlock1AndBlock2OnLine(rect, dimLine) + + # se lo spazio non é sufficiente per inserire testo e simboli all'interno delle linee di estensione, + # uso qad_utils.doubleSmaller perché a volte i due numeri sono quasi uguali + if spaceForBlock1 == 0 or spaceForBlock2 == 0 or \ + qad_utils.doubleSmaller(spaceForBlock1, self.getBlock1Size() + self.textOffsetDist) or \ + qad_utils.doubleSmaller(spaceForBlock2, self.getBlock2Size() + self.textOffsetDist): + if self.blockSuppressionForNoSpace: # sopprime i simboli se non c'é spazio sufficiente all'interno delle linee di estensione + block1Rot = None + block2Rot = None + + # considero il testo senza frecce + if self.textRotMode == QadDimStyleTxtRotModeEnum.ISO: + textInsPt, textRot = self.getTextPositionOnLine(dimLine.getStartPt(), dimLine.getEndPt(), textWidth, textHeight, \ + self.textHorizontalPos, textVerticalPos, QadDimStyleTxtRotModeEnum.ALIGNED_LINE) + else: + textInsPt, textRot = self.getTextPositionOnLine(dimLine.getStartPt(), dimLine.getEndPt(), textWidth, textHeight, \ + self.textHorizontalPos, textVerticalPos, self.textRotMode) + + rect = self.textRectToQadPolyline(textInsPt, textWidth, textHeight, textRot) + spaceForBlock1, spaceForBlock2 = self.getSpaceForBlock1AndBlock2OnLine(rect, dimLine) + # se non c'é spazio neanche per il testo senza le frecce + if spaceForBlock1 == 0 or spaceForBlock2 == 0 or \ + spaceForBlock1 < self.textOffsetDist or spaceForBlock2 < self.textOffsetDist: + # sposta testo fuori dalle linee di estensione + textInsPt, textRot, txtLeaderLines = self.getTextPosAndLinesOutOfDimLines(dimLine.getStartPt(), dimLine.getEndPt(), \ + textWidth, textHeight) + textLinearDimComponentOn = QadDimComponentEnum.LEADER_LINE + else: + textLinearDimComponentOn = QadDimComponentEnum.DIM_LINE1 + else: # non devo sopprimere i simboli + # la prima cosa da spostare all'esterno é : + if self.textBlockAdjust == QadDimStyleTextBlocksAdjustEnum.BOTH_OUTSIDE_EXT_LINES: + # sposta testo e frecce fuori dalle linee di estensione + textInsPt, textRot, txtLeaderLines = self.getTextPosAndLinesOutOfDimLines(dimLine.getStartPt(), dimLine.getEndPt(), \ + textWidth, textHeight) + textLinearDimComponentOn = QadDimComponentEnum.LEADER_LINE + block1Rot, block2Rot = self.getBlocksRotOnLine(dimLine, False) # frecce esterne + # sposta prima le frecce poi, se non basta, anche il testo + elif self.textBlockAdjust == QadDimStyleTextBlocksAdjustEnum.FIRST_BLOCKS_THEN_TEXT: + block1Rot, block2Rot = self.getBlocksRotOnLine(dimLine, False) # frecce esterne + # considero il testo senza frecce + if self.textRotMode == QadDimStyleTxtRotModeEnum.ISO: + textInsPt, textRot = self.getTextPositionOnLine(dimLine.getStartPt(), dimLine.getEndPt(), \ + textWidth, textHeight, \ + self.textHorizontalPos, textVerticalPos, QadDimStyleTxtRotModeEnum.ALIGNED_LINE) + else: + textInsPt, textRot = self.getTextPositionOnLine(dimLine.getStartPt(), dimLine.getEndPt(), \ + textWidth, textHeight, \ + self.textHorizontalPos, textVerticalPos, self.textRotMode) + + rect = self.textRectToQadPolyline(textInsPt, textWidth, textHeight, textRot) + spaceForBlock1, spaceForBlock2 = self.getSpaceForBlock1AndBlock2OnLine(rect, dimLine) + # se non c'é spazio neanche per il testo senza le frecce + if spaceForBlock1 == 0 or spaceForBlock2 == 0 or \ + spaceForBlock1 < self.textOffsetDist or spaceForBlock2 < self.textOffsetDist: + # sposta testo fuori dalle linee di estensione + textInsPt, textRot, txtLeaderLines = self.getTextPosAndLinesOutOfDimLines(dimLine.getStartPt(), dimLine.getEndPt(), \ + textWidth, textHeight) + textLinearDimComponentOn = QadDimComponentEnum.LEADER_LINE + else: + textLinearDimComponentOn = QadDimComponentEnum.DIM_LINE1 + # sposta prima il testo poi, se non basta, anche le frecce + elif self.textBlockAdjust == QadDimStyleTextBlocksAdjustEnum.FIRST_TEXT_THEN_BLOCKS: + # sposto il testo fuori dalle linee di estensione + textInsPt, textRot, txtLeaderLines = self.getTextPosAndLinesOutOfDimLines(dimLine.getStartPt(), dimLine.getEndPt(), \ + textWidth, textHeight) + textLinearDimComponentOn = QadDimComponentEnum.LEADER_LINE + # se non ci stanno neanche le frecce + if dimLine.length() <= self.getBlock1Size() + self.getBlock2Size(): + block1Rot, block2Rot = self.getBlocksRotOnLine(dimLine, False) # frecce esterne + else: + block1Rot, block2Rot = self.getBlocksRotOnLine(dimLine, True) # frecce interne + # Sposta indistintamente il testo o le frecce (l'oggetto che si adatta meglio) + elif self.textBlockAdjust == QadDimStyleTextBlocksAdjustEnum.WHICHEVER_FITS_BEST: + # sposto il più ingombrante + if self.getBlock1Size() + self.getBlock2Size() > textWidth: # le frecce sono più ingombranti del testo + textLinearDimComponentOn = QadDimComponentEnum.DIM_LINE1 + block1Rot, block2Rot = self.getBlocksRotOnLine(dimLine, False) # frecce esterne + + # considero il testo senza frecce + if self.textRotMode == QadDimStyleTxtRotModeEnum.ISO: + textInsPt, textRot = self.getTextPositionOnLine(dimLine.getStartPt(), dimLine.getEndPt(), \ + textWidth, textHeight, \ + self.textHorizontalPos, textVerticalPos, QadDimStyleTxtRotModeEnum.ALIGNED_LINE) + else: + textInsPt, textRot = self.getTextPositionOnLine(dimLine.getStartPt(), dimLine.getEndPt(), \ + textWidth, textHeight, \ + self.textHorizontalPos, textVerticalPos, self.textRotMode) + + rect = self.textRectToQadPolyline(textInsPt, textWidth, textHeight, textRot) + spaceForBlock1, spaceForBlock2 = self.getSpaceForBlock1AndBlock2OnLine(rect, dimLine) + # se non c'é spazio neanche per il testo senza le frecce + if spaceForBlock1 == 0 or spaceForBlock2 == 0 or \ + spaceForBlock1 < self.textOffsetDist or spaceForBlock2 < self.textOffsetDist: + # sposta testo fuori dalle linee di estensione + textInsPt, textRot, txtLeaderLines = self.getTextPosAndLinesOutOfDimLines(dimLine.getStartPt(), dimLine.getEndPt(), \ + textWidth, textHeight) + textLinearDimComponentOn = QadDimComponentEnum.LEADER_LINE + else: + textLinearDimComponentOn = QadDimComponentEnum.DIM_LINE1 + else: # il testo é più ingombrante dei simboli + # sposto il testo fuori dalle linee di estensione + textInsPt, textRot, txtLeaderLines = self.getTextPosAndLinesOutOfDimLines(dimLine.getStartPt(), dimLine.getEndPt(), \ + textWidth, textHeight) + textLinearDimComponentOn = QadDimComponentEnum.LEADER_LINE + # se non ci stanno neanche le frecce + if dimLine.length() <= self.getBlock1Size() + self.getBlock2Size(): + block1Rot, block2Rot = self.getBlocksRotOnLine(dimLine, False) # frecce esterne + else: + block1Rot, block2Rot = self.getBlocksRotOnLine(dimLine, True) # frecce interne + else: # se lo spazio é sufficiente per inserire testo e simboli all'interno delle linee di estensione, + textLinearDimComponentOn = QadDimComponentEnum.DIM_LINE1 + block1Rot, block2Rot = self.getBlocksRotOnLine(dimLine, True) # frecce interne + + # il testo é sopra e allineato alla prima linea di estensione + elif self.textHorizontalPos == QadDimStyleTxtHorizontalPosEnum.FIRST_EXT_LINE_UP: + # angolo della linea che va dal punto di quota all'inizio della linea di quota + rotLine = qad_utils.getAngleBy2Pts(dimPt1, dimLine.getStartPt()) + pt = qad_utils.getPolarPointByPtAngle(dimLine.getStartPt(), rotLine, self.textOffsetDist + textWidth) + if self.textVerticalPos == QadDimStyleTxtVerticalPosEnum.EXTERN_LINE: + textVerticalPos = QadDimStyleTxtVerticalPosEnum.ABOVE_LINE + else: + textVerticalPos = self.textVerticalPos + + if self.textRotMode == QadDimStyleTxtRotModeEnum.FORCED_ROTATION: + textRotMode = QadDimStyleTxtRotModeEnum.FORCED_ROTATION + else: + textRotMode = QadDimStyleTxtRotModeEnum.ALIGNED_LINE + + textInsPt, textRot = self.getTextPositionOnLine(dimLine.getStartPt(), pt, textWidth, textHeight, \ + QadDimStyleTxtHorizontalPosEnum.FIRST_EXT_LINE, \ + textVerticalPos, textRotMode) + textLinearDimComponentOn = QadDimComponentEnum.EXT_LINE1 + + # calcolo lo spazio dei blocchi in assenza del testo + spaceForBlock1, spaceForBlock2 = self.getSpaceForBlock1AndBlock2OnLine(None, dimLine) + # se non c'é spazio per i blocchi + if spaceForBlock1 < self.getBlock1Size() or spaceForBlock2 < self.getBlock2Size(): + if self.blockSuppressionForNoSpace: # i blocchi sono soppressi + block1Rot = None + block2Rot = None + else: # sposto le frecce all'esterno + block1Rot, block2Rot = self.getBlocksRotOnLine(dimLine, False) + else: # c'é spazio per i blocchi + block1Rot, block2Rot = self.getBlocksRotOnLine(dimLine, True) # frecce interne + + # il testo é sopra e allineato alla seconda linea di estensione + elif self.textHorizontalPos == QadDimStyleTxtHorizontalPosEnum.SECOND_EXT_LINE_UP: + # angolo della linea che va dal punto di quota all'inizio della linea di quota + rotLine = qad_utils.getAngleBy2Pts(dimPt2, dimLine.getEndPt()) + pt = qad_utils.getPolarPointByPtAngle(dimLine.getEndPt(), rotLine, self.textOffsetDist + textWidth) + if self.textVerticalPos == QadDimStyleTxtVerticalPosEnum.EXTERN_LINE: + textVerticalPos = QadDimStyleTxtVerticalPosEnum.ABOVE_LINE + else: + textVerticalPos = self.textVerticalPos + + if self.textRotMode == QadDimStyleTxtRotModeEnum.FORCED_ROTATION: + textRotMode = QadDimStyleTxtRotModeEnum.FORCED_ROTATION + else: + textRotMode = QadDimStyleTxtRotModeEnum.ALIGNED_LINE + + textInsPt, textRot = self.getTextPositionOnLine(dimLine.getEndPt(), pt, textWidth, textHeight, \ + QadDimStyleTxtHorizontalPosEnum.FIRST_EXT_LINE, \ + textVerticalPos, textRotMode) + textLinearDimComponentOn = QadDimComponentEnum.EXT_LINE2 + + # calcolo lo spazio dei blocchi in assenza del testo + spaceForBlock1, spaceForBlock2 = self.getSpaceForBlock1AndBlock2OnLine(None, dimLine) + # se non c'é spazio per i blocchi + if spaceForBlock1 < self.getBlock1Size() or spaceForBlock2 < self.getBlock2Size(): + if self.blockSuppressionForNoSpace: # i blocchi sono soppressi + block1Rot = None + block2Rot = None + else: # sposto le frecce all'esterno + block1Rot, block2Rot = self.getBlocksRotOnLine(dimLine, False) + else: # c'é spazio per i blocchi + block1Rot, block2Rot = self.getBlocksRotOnLine(dimLine, True) # frecce interne + + if self.textDirection == QadDimStyleTxtDirectionEnum.DX_TO_SX: + # il punto di inserimento diventa l'angolo in alto a destra del rettangolo + textInsPt = qad_utils.getPolarPointByPtAngle(textInsPt, textRot, textWidth) + textInsPt = qad_utils.getPolarPointByPtAngle(textInsPt, textRot + math.pi / 2, textHeight) + # la rotazione viene capovolta + textRot = qad_utils.normalizeAngle(textRot + math.pi) + + return [[textInsPt, textRot], [textLinearDimComponentOn, txtLeaderLines], block1Rot, block2Rot] + + + # ============================================================================ + # getArcTextAndBlocksPosition + # ============================================================================ + def getArcTextAndBlocksPosition(self, dimArc, dimLineArc, textWidth, textHeight): + """ + dimArc = arco da quotare + dimLineArc = linea di quota in forma di arco + textWidth = larghezza testo + textHeight = altezza testo + + Restituisce una lista di 4 elementi: + - il primo elemento é una lista con il punto di inserimento del testo della quota e la sua rotazione + - il secondo elemento é una lista con flag che indica il tipo della linea sulla quale é stato messo il testo; vedi QadDimComponentEnum + e una lista di linee "leader" nel caso il testo sia all'esterno della quota + - il terzo elemento é la rotazione del primo blocco delle frecce; può essere None se non visibile + - il quarto elemento é la rotazione del secondo blocco delle frecce; può essere None se non visibile + """ + textInsPt = None # punto di inserimento del testo + textRot = None # rotazione del testo + textLinearDimComponentOn = None # codice del componente lineare sul quale é posizionato il testo + txtLeaderLines = None # lista di linee "leader" nel caso il testo sia all'esterno della quota + block1Rot = None # rotazione del primo blocco delle frecce + block2Rot = None # rotazione del secondo blocco delle frecce + + dimLineArcPt1 = dimLineArc.getStartPt() + dimLineArcPt2 = dimLineArc.getEndPt() + dimLineArcLen = dimLineArc.length() + # se il testo é tra le linee di estensione della quota + if self.textHorizontalPos == QadDimStyleTxtHorizontalPosEnum.CENTERED_LINE or \ + self.textHorizontalPos == QadDimStyleTxtHorizontalPosEnum.FIRST_EXT_LINE or \ + self.textHorizontalPos == QadDimStyleTxtHorizontalPosEnum.SECOND_EXT_LINE: + + dimLineArcMiddlePt = dimLineArc.getMiddlePt() + dimLineRot = dimLineArc.getTanDirectionOnPt(dimLineArcMiddlePt) # angolo nel punto medio dell'arco + + dimLineArcPt1Offset, dummyTg = dimLineArc.getPointFromStart(self.getBlock1Size()) + dimLineArcPt2Offset, dummyTg = dimLineArc.getPointFromStart(dimLineArcLen - self.getBlock2Size()) + + # testo sopra o sotto alla linea di quota nel caso la linea di quota non sia orizzontale + # e il testo sia dentro le linee di estensione e forzato orizzontale allora il testo diventa centrato + if (self.textVerticalPos == QadDimStyleTxtVerticalPosEnum.ABOVE_LINE or self.textVerticalPos == QadDimStyleTxtVerticalPosEnum.BELOW_LINE) and \ + (dimLineRot != 0 and dimLineRot != math.pi) and self.textRotMode == QadDimStyleTxtRotModeEnum.HORIZONTAL: + textVerticalPos = QadDimStyleTxtVerticalPosEnum.CENTERED_LINE + # testo posizionato nella parte opposta ai punti di quotatura + elif self.textVerticalPos == QadDimStyleTxtVerticalPosEnum.EXTERN_LINE: + # temporaneamento lo imposto centrato solo per averela posizione del testo + textVerticalPos = QadDimStyleTxtVerticalPosEnum.CENTERED_LINE + else: + textVerticalPos = self.textVerticalPos + + if self.textRotMode == QadDimStyleTxtRotModeEnum.ISO: + textInsPt, textRot = self.getTextPositionOnArc(dimLineArc, textWidth, textHeight, \ + self.textHorizontalPos, textVerticalPos, QadDimStyleTxtRotModeEnum.ALIGNED_LINE) + else: + textInsPt, textRot = self.getTextPositionOnArc(dimLineArc, textWidth, textHeight, \ + self.textHorizontalPos, textVerticalPos, self.textRotMode) + + # testo posizionato nella parte opposta ai punti di quotatura + if self.textVerticalPos == QadDimStyleTxtVerticalPosEnum.EXTERN_LINE: + # punto centrale del testo di quota + insPtCenterTxt = qad_utils.getPolarPointByPtAngle(textInsPt, textRot, textWidth / 2) + # angolo dal centro dell'arco al punto centrale del testo di quota + dimCenterToTextInsPt_rot = qad_utils.getAngleBy2Pts(dimArc.center, insPtCenterTxt) + if dimCenterToTextInsPt_rot > 0 and \ + (dimCenterToTextInsPt_rot <= math.pi or qad_utils.doubleNear(dimCenterToTextInsPt_rot, math.pi)): + if dimLineArc.radius >= dimArc.radius: + textVerticalPos = QadDimStyleTxtVerticalPosEnum.ABOVE_LINE + else: + textVerticalPos = QadDimStyleTxtVerticalPosEnum.BELOW_LINE + else: + if dimLineArc.radius >= dimArc.radius: + textVerticalPos = QadDimStyleTxtVerticalPosEnum.BELOW_LINE + else: + textVerticalPos = QadDimStyleTxtVerticalPosEnum.ABOVE_LINE + + if self.textRotMode == QadDimStyleTxtRotModeEnum.ISO: + textInsPt, textRot = self.getTextPositionOnArc(dimLineArc, textWidth, textHeight, \ + self.textHorizontalPos, textVerticalPos, QadDimStyleTxtRotModeEnum.ALIGNED_LINE) + else: + textInsPt, textRot = self.getTextPositionOnArc(dimLineArc, textWidth, textHeight, \ + self.textHorizontalPos, textVerticalPos, self.textRotMode) + + rect = self.textRectToQadPolyline(textInsPt, textWidth, textHeight, textRot) + spaceForBlock1, spaceForBlock2 = self.getSpaceForBlock1AndBlock2OnArc(rect, dimLineArc) + + # se lo spazio non é sufficiente per inserire testo e simboli all'interno delle linee di estensione, + # uso qad_utils.doubleSmaller perché a volte i due numeri sono quasi uguali + if spaceForBlock1 == 0 or spaceForBlock2 == 0 or \ + qad_utils.doubleSmaller(spaceForBlock1, self.getBlock1Size() + self.textOffsetDist) or \ + qad_utils.doubleSmaller(spaceForBlock2, self.getBlock2Size() + self.textOffsetDist): + if self.blockSuppressionForNoSpace: # sopprime i simboli se non c'é spazio sufficiente all'interno delle linee di estensione + block1Rot = None + block2Rot = None + + # considero il testo senza frecce + if self.textRotMode == QadDimStyleTxtRotModeEnum.ISO: + textInsPt, textRot = self.getTextPositionOnArc(dimLineArc, textWidth, textHeight, \ + self.textHorizontalPos, textVerticalPos, QadDimStyleTxtRotModeEnum.ALIGNED_LINE) + else: + textInsPt, textRot = self.getTextPositionOnArc(dimLineArc, textWidth, textHeight, \ + self.textHorizontalPos, textVerticalPos, self.textRotMode) + + rect = self.textRectToQadPolyline(textInsPt, textWidth, textHeight, textRot) + spaceForBlock1, spaceForBlock2 = self.getSpaceForBlock1AndBlock2OnArc(rect, dimLineArc) + # se non c'é spazio neanche per il testo senza le frecce + if spaceForBlock1 == 0 or spaceForBlock2 == 0 or \ + spaceForBlock1 < self.textOffsetDist or spaceForBlock2 < self.textOffsetDist: + # sposta testo fuori dalle linee di estensione + textInsPt, textRot, txtLeaderLines = self.getTextPosAndLinesOutOfDimArc(dimLineArc, textWidth, textHeight) + textLinearDimComponentOn = QadDimComponentEnum.LEADER_LINE + else: + textLinearDimComponentOn = QadDimComponentEnum.DIM_LINE1 + else: # non devo sopprimere i simboli + # la prima cosa da spostare all'esterno é : + if self.textBlockAdjust == QadDimStyleTextBlocksAdjustEnum.BOTH_OUTSIDE_EXT_LINES: + # sposta testo e frecce fuori dalle linee di estensione + textInsPt, textRot, txtLeaderLines = self.getTextPosAndLinesOutOfDimArc(dimLineArc, textWidth, textHeight) + textLinearDimComponentOn = QadDimComponentEnum.LEADER_LINE + block1Rot, block2Rot = self.getBlocksRotOnArc(dimLineArc, False) # frecce esterne + # sposta prima le frecce poi, se non basta, anche il testo + elif self.textBlockAdjust == QadDimStyleTextBlocksAdjustEnum.FIRST_BLOCKS_THEN_TEXT: + block1Rot, block2Rot = self.getBlocksRotOnArc(dimLineArc, False) # frecce esterne + # considero il testo senza frecce + if self.textRotMode == QadDimStyleTxtRotModeEnum.ISO: + textInsPt, textRot = self.getTextPositionOnArc(dimLineArc, textWidth, textHeight, \ + self.textHorizontalPos, textVerticalPos, QadDimStyleTxtRotModeEnum.ALIGNED_LINE) + else: + textInsPt, textRot = self.getTextPositionOnArc(dimLineArc, textWidth, textHeight, \ + self.textHorizontalPos, textVerticalPos, self.textRotMode) + + rect = self.textRectToQadPolyline(textInsPt, textWidth, textHeight, textRot) + spaceForBlock1, spaceForBlock2 = self.getSpaceForBlock1AndBlock2OnArc(rect, dimLineArc) + # se non c'é spazio neanche per il testo senza le frecce + if spaceForBlock1 == 0 or spaceForBlock2 == 0 or \ + spaceForBlock1 < self.textOffsetDist or spaceForBlock2 < self.textOffsetDist: + # sposta testo fuori dalle linee di estensione + textInsPt, textRot, txtLeaderLines = self.getTextPosAndLinesOutOfDimArc(dimLineArc, textWidth, textHeight) + textLinearDimComponentOn = QadDimComponentEnum.LEADER_LINE + else: + textLinearDimComponentOn = QadDimComponentEnum.DIM_LINE1 + # sposta prima il testo poi, se non basta, anche le frecce + elif self.textBlockAdjust == QadDimStyleTextBlocksAdjustEnum.FIRST_TEXT_THEN_BLOCKS: + # sposto il testo fuori dalle linee di estensione + textInsPt, textRot, txtLeaderLines = self.getTextPosAndLinesOutOfDimArc(dimLineArc, textWidth, textHeight) + textLinearDimComponentOn = QadDimComponentEnum.LEADER_LINE + # se non ci stanno neanche le frecce + if dimLineArcLen <= self.getBlock1Size() + self.getBlock2Size(): + block1Rot, block2Rot = self.getBlocksRotOnArc(dimLineArc, False) # frecce esterne + else: + block1Rot, block2Rot = self.getBlocksRotOnArc(dimLineArc, True) # frecce interne + # Sposta indistintamente il testo o le frecce (l'oggetto che si adatta meglio) + elif self.textBlockAdjust == QadDimStyleTextBlocksAdjustEnum.WHICHEVER_FITS_BEST: + # sposto il più ingombrante + if self.getBlock1Size() + self.getBlock2Size() > textWidth: # le frecce sono più ingombranti del testo + textLinearDimComponentOn = QadDimComponentEnum.DIM_LINE1 + block1Rot, block2Rot = self.getBlocksRotOnArc(dimLineArc, False) # frecce esterne + + # considero il testo senza frecce + if self.textRotMode == QadDimStyleTxtRotModeEnum.ISO: + textInsPt, textRot = self.getTextPositionOnArc(dimLineArc, textWidth, textHeight, \ + self.textHorizontalPos, textVerticalPos, QadDimStyleTxtRotModeEnum.ALIGNED_LINE) + else: + textInsPt, textRot = self.getTextPositionOnArc(dimLineArc, textWidth, textHeight, \ + self.textHorizontalPos, textVerticalPos, self.textRotMode) + + rect = self.textRectToQadPolyline(textInsPt, textWidth, textHeight, textRot) + spaceForBlock1, spaceForBlock2 = self.getSpaceForBlock1AndBlock2OnArc(rect, dimLineArc) + # se non c'é spazio neanche per il testo senza le frecce + if spaceForBlock1 == 0 or spaceForBlock2 == 0 or \ + spaceForBlock1 < self.textOffsetDist or spaceForBlock2 < self.textOffsetDist: + # sposta testo fuori dalle linee di estensione + textInsPt, textRot, txtLeaderLines = self.getTextPosAndLinesOutOfDimArc(dimLineArc, textWidth, textHeight) + textLinearDimComponentOn = QadDimComponentEnum.LEADER_LINE + else: + textLinearDimComponentOn = QadDimComponentEnum.DIM_LINE1 + else: # il testo é più ingombrante dei simboli + # sposto il testo fuori dalle linee di estensione + textInsPt, textRot, txtLeaderLines = self.getTextPosAndLinesOutOfDimArc(dimLineArc, textWidth, textHeight) + textLinearDimComponentOn = QadDimComponentEnum.LEADER_LINE + # se non ci stanno neanche le frecce + if dimLineArcLen <= self.getBlock1Size() + self.getBlock2Size(): + block1Rot, block2Rot = self.getBlocksRotOnArc(dimLineArc, False) # frecce esterne + else: + block1Rot, block2Rot = self.getBlocksRotOnArc(dimLineArc, True) # frecce interne + else: # se lo spazio é sufficiente per inserire testo e simboli all'interno delle linee di estensione, + textLinearDimComponentOn = QadDimComponentEnum.DIM_LINE1 + block1Rot, block2Rot = self.getBlocksRotOnArc(dimLineArc, True) # frecce interne + + # il testo é sopra e allineato alla prima linea di estensione + elif self.textHorizontalPos == QadDimStyleTxtHorizontalPosEnum.FIRST_EXT_LINE_UP: + # angolo della linea che va dal punto di quota all'inizio della linea di quota + if dimArc.startAngle == dimLineArc.startAngle: + rotLine = qad_utils.getAngleBy2Pts(dimArc.getStartPt(), dimLineArcPt1) + else: + rotLine = qad_utils.getAngleBy2Pts(dimArc.getEndPt(), dimLineArcPt1) + + pt = qad_utils.getPolarPointByPtAngle(dimLineArcPt1, rotLine, self.textOffsetDist + textWidth) + if self.textVerticalPos == QadDimStyleTxtVerticalPosEnum.EXTERN_LINE: + textVerticalPos = QadDimStyleTxtVerticalPosEnum.ABOVE_LINE + else: + textVerticalPos = self.textVerticalPos + + if self.textRotMode == QadDimStyleTxtRotModeEnum.FORCED_ROTATION: + textRotMode = QadDimStyleTxtRotModeEnum.FORCED_ROTATION + else: + textRotMode = QadDimStyleTxtRotModeEnum.ALIGNED_LINE + + textInsPt, textRot = self.getTextPositionOnLine(dimLineArcPt1, pt, textWidth, textHeight, \ + QadDimStyleTxtHorizontalPosEnum.FIRST_EXT_LINE, \ + textVerticalPos, textRotMode) + textLinearDimComponentOn = QadDimComponentEnum.EXT_LINE1 + + # calcolo lo spazio dei blocchi in assenza del testo + spaceForBlock1, spaceForBlock2 = self.getSpaceForBlock1AndBlock2OnArc(None, dimLineArc) + # se non c'é spazio per i blocchi + if spaceForBlock1 < self.getBlock1Size() or spaceForBlock2 < self.getBlock2Size(): + if self.blockSuppressionForNoSpace: # i blocchi sono soppressi + block1Rot = None + block2Rot = None + else: # sposto le frecce all'esterno + block1Rot, block2Rot = self.getBlocksRotOnArc(dimLineArc, False) + else: # c'é spazio per i blocchi + block1Rot, block2Rot = self.getBlocksRotOnArc(dimLineArc, True) # frecce interne + + # il testo é sopra e allineato alla seconda linea di estensione + elif self.textHorizontalPos == QadDimStyleTxtHorizontalPosEnum.SECOND_EXT_LINE_UP: + # angolo della linea che va dal punto di quota all'inizio della linea di quota + # angolo della linea che va dal punto di quota all'inizio della linea di quota + if dimArc.startAngle == dimLineArc.startAngle: + rotLine = qad_utils.getAngleBy2Pts(dimArc.getEndPt(), dimLineArcPt2) + else: + rotLine = qad_utils.getAngleBy2Pts(dimArc.getStartPt(), dimLineArcPt2) + + pt = qad_utils.getPolarPointByPtAngle(dimLineArcPt2, rotLine, self.textOffsetDist + textWidth) + if self.textVerticalPos == QadDimStyleTxtVerticalPosEnum.EXTERN_LINE: + textVerticalPos = QadDimStyleTxtVerticalPosEnum.ABOVE_LINE + else: + textVerticalPos = self.textVerticalPos + + if self.textRotMode == QadDimStyleTxtRotModeEnum.FORCED_ROTATION: + textRotMode = QadDimStyleTxtRotModeEnum.FORCED_ROTATION + else: + textRotMode = QadDimStyleTxtRotModeEnum.ALIGNED_LINE + + textInsPt, textRot = self.getTextPositionOnLine(dimLineArcPt2, pt, textWidth, textHeight, \ + QadDimStyleTxtHorizontalPosEnum.FIRST_EXT_LINE, \ + textVerticalPos, textRotMode) + textLinearDimComponentOn = QadDimComponentEnum.EXT_LINE2 + + # calcolo lo spazio dei blocchi in assenza del testo + spaceForBlock1, spaceForBlock2 = self.getSpaceForBlock1AndBlock2OnArc(None, dimLineArc) + # se non c'é spazio per i blocchi + if spaceForBlock1 < self.getBlock1Size() or spaceForBlock2 < self.getBlock2Size(): + if self.blockSuppressionForNoSpace: # i blocchi sono soppressi + block1Rot = None + block2Rot = None + else: # sposto le frecce all'esterno + block1Rot, block2Rot = self.getBlocksRotOnArc(dimLineArc, False) + else: # c'é spazio per i blocchi + block1Rot, block2Rot = self.getBlocksRotOnArc(dimLineArc, True) # frecce interne + + if self.textDirection == QadDimStyleTxtDirectionEnum.DX_TO_SX: + # il punto di inserimento diventa l'angolo in alto a destra del rettangolo + textInsPt = qad_utils.getPolarPointByPtAngle(textInsPt, textRot, textWidth) + textInsPt = qad_utils.getPolarPointByPtAngle(textInsPt, textRot + math.pi / 2, textHeight) + # la rotazione viene capovolta + textRot = qad_utils.normalizeAngle(textRot + math.pi) + + return [[textInsPt, textRot], [textLinearDimComponentOn, txtLeaderLines], block1Rot, block2Rot] + + + # ============================================================================ + # getRadiusTextAndBlocksPosition + # ============================================================================ + def getRadiusTextAndBlocksPosition(self, dimLine, textWidth, textHeight): + """ + dimLine = linea di quota (QadLine) + textWidth = larghezza testo + textHeight = altezza testo + + Restituisce una lista di 4 elementi: + - il primo elemento é una lista con il punto di inserimento del testo della quota e la sua rotazione + - il secondo elemento é una lista con flag che indica il tipo della linea sulla quale é stato messo il testo; vedi QadDimComponentEnum + e una lista di linee "leader" nel caso il testo sia all'esterno della quota + - il terzo elemento é la rotazione del primo blocco delle frecce; può essere None se non visibile + - il quarto elemento é la rotazione del secondo blocco delle frecce; può essere None se non visibile + """ + textInsPt = None # punto di inserimento del testo + textRot = None # rotazione del testo + textLinearDimComponentOn = None # codice del componente lineare sul quale é posizionato il testo + txtLeaderLines = None # lista di linee "leader" nel caso il testo sia all'esterno della quota + + # cambio alcui parametri di quotatura + block1Name = self.block1Name + self.block1Name = "" # nessuna freccia al punto 1 di quotatura + block2Name = self.block2Name + self.block2Name = "" # nessuna freccia al punto 2 di quotatura + textBlockAdjust = self.textBlockAdjust + self.textBlockAdjust = QadDimStyleTextBlocksAdjustEnum.FIRST_TEXT_THEN_BLOCKS # se il testo non ci sta va fuori dalla linea di quotatura + textHorizontalPos = self.textHorizontalPos + self.textHorizontalPos = QadDimStyleTxtHorizontalPosEnum.FIRST_EXT_LINE + + res = self.getLinearTextAndBlocksPosition(dimLine.getStartPt(), dimLine.getEndPt(), dimLine, textWidth, textHeight) + + # ripristino i valori originali + self.block1Name = block1Name + self.block2Name = block2Name + self.textBlockAdjust = textBlockAdjust + self.textHorizontalPos = textHorizontalPos + + return res + + + # ============================================================================ + # getTextFeature + # ============================================================================ + def getTextFeature(self, measure, pt = None, rot = None): + """ + Restituisce la feature per il testo della quota. + La rotazione é espressa in radianti. + """ + _pt = QgsPointXY(0,0) if pt is None else pt + _rot = 0 if rot is None else rot + + textualFeaturePrototype = self.getTextualFeaturePrototype() + if textualFeaturePrototype is None: + return None + f = QgsFeature(textualFeaturePrototype) + g = fromQadGeomToQgsGeom(QadPoint().set(_pt), self.getTextualLayer()) # trasformo la geometria + f.setGeometry(g) + + # se il testo dipende da un solo campo + labelFieldNames = qad_label.get_labelFieldNames(self.getTextualLayer()) + if len(labelFieldNames) == 1 and len(labelFieldNames[0]) > 0: + f.setAttribute(labelFieldNames[0], self.getFormattedText(measure)) + + # se l'altezza testo dipende da un solo campo + sizeFldNames = qad_label.get_labelSizeFieldNames(self.getTextualLayer()) + if len(sizeFldNames) == 1 and len(sizeFldNames[0]) > 0: + f.setAttribute(sizeFldNames[0], self.textHeight) # altezza testo + + # se la rotazione dipende da un solo campo + rotFldNames = qad_label.get_labelRotationFieldNames(self.getTextualLayer()) + if len(rotFldNames) == 1 and len(rotFldNames[0]) > 0: + f.setAttribute(rotFldNames[0], qad_utils.toDegrees(_rot)) # Converte da radianti a gradi + + # se il font dipende da un solo campo + fontFamilyFldNames = qad_label.get_labelFontFamilyFieldNames(self.getTextualLayer()) + if len(fontFamilyFldNames) == 1 and len(fontFamilyFldNames[0]) > 0: + f.setAttribute(fontFamilyFldNames[0], self.textFont) # nome del font di testo + + # imposto il colore + try: + if len(self.colorFieldName) > 0: + f.setAttribute(self.colorFieldName, self.textColor) + except: + pass + + # imposto lo stile di quotatura + try: + if len(self.dimStyleFieldName) > 0: + f.setAttribute(self.dimStyleFieldName, self.name) + if len(self.dimTypeFieldName) > 0: + f.setAttribute(self.dimTypeFieldName, self.dimType) + except: + pass + + return f + + + # ============================================================================ + # FUNZIONI PER IL TESTO - FINE + # FUNZIONI PER LA LINEA DI LEADER - INIZIO + # ============================================================================ + + + # ============================================================================ + # getAuxiliarySecondLeaderLine + # ============================================================================ + def getAuxiliarySecondLeaderLine(self, pt1, rotLine, textWidth, textHeight): + """ + Funzione interna di ausilio per le successive che si occupano di leader line. + Restituisce la seconda linea porta quota (quella più vicina al testo). + pt1 = punto da cui iniziare la linea (QgsPointXY) + rotLine = angolo della prima linea porta quota (QgsPointXY) + textWidth = larghezza testo + textHeight = altezza testo + """ + # modalità di rotazione del testo orizzontale o + # testo allineato con la linea di quota se tra le linee di estensione, altrimenti testo orizzontale + if self.textRotMode == QadDimStyleTxtRotModeEnum.HORIZONTAL or \ + self.textRotMode == QadDimStyleTxtRotModeEnum.ISO: + if qad_utils.doubleNear(rotLine, math.pi / 2): # verticale dal basso verso l'alto + pt2 = qad_utils.getPolarPointByPtAngle(pt1, 0, self.textOffsetDist + textWidth) + elif qad_utils.doubleNear(rotLine, math.pi * 3 / 2): # verticale dall'alto verso il basso + pt2 = qad_utils.getPolarPointByPtAngle(pt1, math.pi, self.textOffsetDist + textWidth) + elif (rotLine > math.pi * 3 / 2 and rotLine <= math.pi * 2) or \ + (rotLine >= 0 and rotLine < math.pi / 2): # da sx a dx + pt2 = qad_utils.getPolarPointByPtAngle(pt1, 0, self.textOffsetDist + textWidth) + else: # da dx a sx + pt2 = qad_utils.getPolarPointByPtAngle(pt1, math.pi, self.textOffsetDist + textWidth) + elif self.textRotMode == QadDimStyleTxtRotModeEnum.ALIGNED_LINE: # testo allineato con la linea di quota + pt2 = qad_utils.getPolarPointByPtAngle(pt1, rotLine, self.textOffsetDist + textWidth) + elif self.textRotMode == QadDimStyleTxtRotModeEnum.FORCED_ROTATION: # testo con rotazione forzata + pt2 = qad_utils.getPolarPointByPtAngle(pt1, self.textForcedRot, self.textOffsetDist + textWidth) + + return QadLine().set(pt1, pt2) + + + # ============================================================================ + # getLeaderLinesOnLine + # ============================================================================ + def getLeaderLinesOnLine(self, dimLinePt1, dimLinePt2, textWidth, textHeight): + """ + Restituisce una polilinea (QadPolyline) che forma il porta quota nel caso il testo venga spostato + fuori dalle linee di estensione perché era troppo grosso. + dimLinePt1 = primo punto della linea di quota (QgsPointXY) + dimLinePt2 = secondo punto della linea di quota (QgsPointXY) + textWidth = larghezza testo + textHeight = altezza testo + """ + res = QadPolyline() + # le linee sono a lato della linea di estensione 1 + if self.textHorizontalPos == QadDimStyleTxtHorizontalPosEnum.FIRST_EXT_LINE: + rotLine = qad_utils.getAngleBy2Pts(dimLinePt2, dimLinePt1) # angolo della linea porta quota + pt1 = qad_utils.getPolarPointByPtAngle(dimLinePt1, rotLine, self.getBlock1Size()) + res.append(QadLine().set(dimLinePt1, pt1)) + # le linee sono a lato della linea di estensione 2 + else: + rotLine = qad_utils.getAngleBy2Pts(dimLinePt1, dimLinePt2) # angolo della linea porta quota + pt1 = qad_utils.getPolarPointByPtAngle(dimLinePt2, rotLine, self.getBlock2Size()) + res.append(QadLine().set(dimLinePt2, pt1)) + + # ricavo la seconda linea di porta quota + line2 = self.getAuxiliarySecondLeaderLine(pt1, rotLine, textWidth, textHeight) + res.append(line2) + + return res + + + # ============================================================================ + # getLeaderLinesOnArc + # ============================================================================ + def getLeaderLinesOnArc(self, dimLineArc, textWidth, textHeight): + """ + Restituisce una polilinea (QadPolyline) che forma il porta quota nel caso il testo venga spostato + fuori dalle linee di estensione perché era troppo grosso. + dimLineArc = arco rappresentante l'arco di quota (QadArc) + textWidth = larghezza testo + textHeight = altezza testo + """ + res = QadPolyline() + # le linee sono a lato della linea di estensione 1 + if self.textHorizontalPos == QadDimStyleTxtHorizontalPosEnum.FIRST_EXT_LINE: + startPt = dimLineArc.getStartPt() + rotLine = dimLineArc.getTanDirectionOnPt(startPt) + math.pi # angolo della linea porta quota sul punto iniziale + pt1 = qad_utils.getPolarPointByPtAngle(startPt, rotLine, self.getBlock1Size()) + res.append(QadLine().set(startPt, pt1)) + # le linee sono a lato della linea di estensione 2 + else: + endPt = dimLineArc.getEndPt() + rotLine = dimLineArc.getTanDirectionOnPt(endPt) # angolo della linea porta quota sul punto finale + pt1 = qad_utils.getPolarPointByPtAngle(endPt, rotLine, self.getBlock2Size()) + res.append(QadLine().set(endPt, pt1)) + + # ricavo la seconda linea di porta quota + line2 = self.getAuxiliarySecondLeaderLine(pt1, rotLine, textWidth, textHeight) + res.append(line2) + + return res + + + # ============================================================================ + # getLeaderFeature + # ============================================================================ + def getLeaderFeature(self, leaderLines, leaderLineType = QadDimComponentEnum.LEADER_LINE): + """ + Restituisce la feature per la linea di estensione. + leaderLines = polilinea leader (QadPolyline) + leaderLineType = tipo di linea porta quota (LEADER_LINE, ARC_LEADER_LINE, ...) + """ + if leaderLines is None: + return None + + linearFeaturePrototype = self.getLinearFeaturePrototype() + if linearFeaturePrototype is None: + return None + f = QgsFeature(linearFeaturePrototype) + g = fromQadGeomToQgsGeom(leaderLines, self.getLinearLayer()) + f.setGeometry(g) + + try: + # imposto il tipo di componente della quotatura + if len(self.componentFieldName) > 0: + f.setAttribute(self.componentFieldName, leaderLineType) + except: + pass + + try: + # imposto il tipo di linea + if len(self.lineTypeFieldName) > 0: + f.setAttribute(self.lineTypeFieldName, self.dimLineLineType) + except: + pass + + try: + # imposto il colore + if len(self.colorFieldName) > 0: + f.setAttribute(self.colorFieldName, self.dimLineColor) + except: + pass + + return f + + + # ============================================================================ + # getArcLeaderLine + # ============================================================================ + def getArcLeaderLine(self, pt, arc): + """ + Restituisce la linea che congiunge il testo all'arco da quotare. + """ + intPts = QadIntersections.infinityLineWithArc(QadLine().set(pt, arc.center), arc) + if len(intPts) == 1: + return QadLine().set(pt, intPts[0]) + elif len(intPts) == 2: + # scelgo il più vicino + if qad_utils.getDistance(pt, intPts[0]) < qad_utils.getDistance(pt, intPts[1]): + return QadLine().set(pt, intPts[0]) + else: + return QadLine().set(pt, intPts[1]) + else: + return None + + + # ============================================================================ + # FUNZIONI PER LA LINEA DI LEADER - FINE + # FUNZIONI PER LE LINEE DI ESTENSIONE - INIZIO + # ============================================================================ + + + # ============================================================================ + # getExtLine + # ============================================================================ + def getExtLine(self, dimPt, dimLinePt): + """ + dimPt = punto da quotare + dimLinePt = corrispondente punto della linea di quotatura + + ritorna una linea di estensione modificata secondo lo stile di quotatura + il primo punto é vicino alla linea di quota, il secondo al punto da quotare + """ + + angle = qad_utils.getAngleBy2Pts(dimPt, dimLinePt) + # distanza della linea di estensione oltre la linea di quota + pt1 = qad_utils.getPolarPointByPtAngle(dimLinePt, angle, self.extLineOffsetDimLine) + # distanza della linea di estensione dai punti da quotare + pt2 = qad_utils.getPolarPointByPtAngle(dimPt, angle, self.extLineOffsetOrigPoints) + + if self.extLineIsFixedLen == True: # attivata lunghezza fissa delle line di estensione + if qad_utils.getDistance(pt1, pt2) > self.extLineFixedLen: + # lunghezza fissa delle line di estensione (DIMFXL) dalla linea di quota + # al punto da quotare spostato di extLineOffsetOrigPoints + # (la linea di estensione non va oltre il punto da quotare) + d = qad_utils.getDistance(dimLinePt, dimPt) + if d > self.extLineFixedLen: + d = self.extLineFixedLen + pt2 = qad_utils.getPolarPointByPtAngle(dimLinePt, angle + math.pi, d) + + return QadLine().set(pt1, pt2) + + + # ============================================================================ + # getExtArc + # ============================================================================ + def getExtArc(self, arc, linePosPt): + """ + arc = arco da quotare + linePosPt = punto corrispondente a dove posizionare la quotatura + + Ritorna un arco di estensione per la quotatura DIMRADIUS + """ + # se il punto è all'interno dell'arco + angle = qad_utils.getAngleBy2Pts(arc.center, linePosPt) + if qad_utils.isAngleBetweenAngles(arc.startAngle, arc.endAngle, angle) == True: + return None + + myArc = QadArc() + pt = qad_utils.getPolarPointByPtAngle(arc.center, angle, arc.radius) # punto sulla curva + # dalla parte del punto iniziale dell'arco + if qad_utils.getDistance(pt, arc.getStartPt()) < qad_utils.getDistance(pt, arc.getEndPt()): + myArc.set(arc.center, arc.radius, angle, arc.startAngle) + if myArc.length() <= self.extLineOffsetOrigPoints: + return None + + myArc.setStartAngleByPt(pt) + dummyPt, dummyTg = myArc.getPointFromStart(-self.extLineOffsetDimLine) + myArc.setStartAngleByPt(dummyPt) + dummyPt, dummyTg = arc.getPointFromStart(-self.extLineOffsetOrigPoints) + myArc.setEndAngleByPt(dummyPt) # cambio punto finale + else: # dalla parte del punto finale dell'arco + myArc.set(arc.center, arc.radius, arc.endAngle, angle) + if myArc.length() <= self.extLineOffsetOrigPoints: + return None + dummyPt, dummyTg = arc.getPointFromEnd(self.extLineOffsetOrigPoints) + myArc.setStartAngleByPt(dummyPt) # cambio punto iniziale + myArc.setEndAngleByPt(pt) + dummyPt, dummyTg = myArc.getPointFromEnd(self.extLineOffsetDimLine) + myArc.setEndAngleByPt(dummyPt) + + return myArc + + + # ============================================================================ + # getExtLineFeature + # ============================================================================ + def getExtLineFeature(self, extLine, isExtLine1): + """ + Restituisce la feature per la linea di estensione. + extLine = linea di estensione QadLine o QadArc + isExtLine1 = se True si tratta della linea di estensione 1 altrimenti della linea di estensione 2 + """ + if (isExtLine1 == True and self.extLine1Show == False) or \ + (isExtLine1 == False and self.extLine2Show == False): + return None + + f = QgsFeature(self.getLinearFeaturePrototype()) + g = fromQadGeomToQgsGeom(extLine, self.getLinearLayer()) # trasformo la geometria + f.setGeometry(g) + + try: + # imposto il tipo di componente della quotatura + if len(self.componentFieldName) > 0: + f.setAttribute(self.componentFieldName, QadDimComponentEnum.EXT_LINE1 if isExtLine1 else QadDimComponentEnum.EXT_LINE2) + except: + pass + + try: + # imposto il tipo di linea + if len(self.lineTypeFieldName) > 0: + f.setAttribute(self.lineTypeFieldName, self.extLine1LineType if isExtLine1 else self.extLine2LineType) + except: + pass + + try: + # imposto il colore + if len(self.colorFieldName) > 0: + f.setAttribute(self.colorFieldName, self.extLineColor) + except: + pass + + return f + + + # ============================================================================ + # FUNZIONI PER LE LINEE DI ESTENSIONE - FINE + # FUNZIONI PER LA LINEA DI QUOTA - INIZIO + # ============================================================================ + + + # ============================================================================ + # getDimLine + # ============================================================================ + def getDimLine(self, dimPt1, dimPt2, linePosPt, preferredAlignment = QadDimStyleAlignmentEnum.HORIZONTAL, + dimLineRotation = 0.0): + """ + Restituisce la linea di quotatura entro le linee di estensione (eventuali estensioni saranno calcolate + dalla funzione: getDimLineExtensions) + + dimPt1 = primo punto da quotare + dimPt2 = secondo punto da quotare + linePosPt = punto per indicare dove deve essere posizionata la linea di quota + preferredAlignment = indica se ci si deve allineare ai punti di quota in modo orizzontale o verticale + (se i punti di quota formano una linea obliqua). Usato solo per le quotature lineari + dimLineRotation = angolo della linea di quotatura (default = 0). Usato solo per le quotature lineari + """ + if self.dimType == QadDimTypeEnum.ALIGNED: + # calcolo la proiezione perpendicolare del punto sulla linea che congiunge a + ptPerp = qad_utils.getPerpendicularPointOnInfinityLine(dimPt1, dimPt2, linePosPt) + d = qad_utils.getDistance(linePosPt, ptPerp) + + angle = qad_utils.getAngleBy2Pts(dimPt1, dimPt2) + if qad_utils.leftOfLine(linePosPt, dimPt1, dimPt2) < 0: # a sinistra della linea che congiunge a + angle = angle + (math.pi / 2) + else: + angle = angle - (math.pi / 2) + + return QadLine().set(qad_utils.getPolarPointByPtAngle(dimPt1, angle, d), \ + qad_utils.getPolarPointByPtAngle(dimPt2, angle, d)) + elif self.dimType == QadDimTypeEnum.LINEAR: + if preferredAlignment == QadDimStyleAlignmentEnum.HORIZONTAL: + ptDummy = qad_utils.getPolarPointByPtAngle(dimPt1, dimLineRotation + math.pi / 2, 1) + pt1 = qad_utils.getPerpendicularPointOnInfinityLine(dimPt1, ptDummy, linePosPt) + ptDummy = qad_utils.getPolarPointByPtAngle(dimPt2, dimLineRotation + math.pi / 2, 1) + pt2 = qad_utils.getPerpendicularPointOnInfinityLine(dimPt2, ptDummy, linePosPt) + + return QadLine().set(pt1, pt2) + elif preferredAlignment == QadDimStyleAlignmentEnum.VERTICAL: + ptDummy = qad_utils.getPolarPointByPtAngle(dimPt1, dimLineRotation, 1) + pt1 = qad_utils.getPerpendicularPointOnInfinityLine(dimPt1, ptDummy, linePosPt) + ptDummy = qad_utils.getPolarPointByPtAngle(dimPt2, dimLineRotation, 1) + pt2 = qad_utils.getPerpendicularPointOnInfinityLine(dimPt2, ptDummy, linePosPt) + + return QadLine().set(pt1, pt2) + + + # ============================================================================ + # getDimLineForArc + # ============================================================================ + def getDimLineForArc(self, arc, linePosPt): + """ + Restituisce la linea di quotatura (sottoforma di un arco) per la l'ampiezza di un arco + + un flag per avvisare se l'arco è stato invertito + Restituisce la linea di quotatura entro le linee di estensione (eventuali estensioni saranno calcolate + dalla funzione: getDimArcExtensions) + + arc = oggetto arco QadArc (in unita di mappa) + linePosPt = punto per indicare dove deve essere posizionata la linea di quota + """ + if self.dimType == QadDimTypeEnum.ARC_LENTGH: + myArc = QadArc(arc) + # calcolo la distanza tra e il centro dell'arco + d = qad_utils.getDistance(linePosPt, myArc.center) + myArc.radius = d # cambio il raggio + + # se il punto non è all'interno dell'arco considero l'inverso dell'arco + angle = qad_utils.getAngleBy2Pts(myArc.center, linePosPt) + if qad_utils.isAngleBetweenAngles(myArc.startAngle, myArc.endAngle, angle) == False: + myArc.inverseAngles() + return myArc + + return None + + + # ============================================================================ + # getDimLineFeature + # ============================================================================ + def getDimLineFeature(self, dimLine, isDimLine1, textLinearDimComponentOn): + """ + Restituisce la feature per la linea di quota. + dimLine = linea di quota (QadLine o QadArc) + isDimLine1 = se True si tratta della linea di quota 1 altrimenti della linea di quota 2 + textLinearDimComponentOn = indica il componente della quota dove é situato il testo di quota (QadDimComponentEnum) + """ + + # se non c'é la linea di quota + if dimLine is None: + return None + if isDimLine1 == True: # se si tratta della linea di quota 1 + # se la linea di quota 1 deve essere invisibile (vale solo se il testo é sulla linea di quota) + if self.dimLine1Show == False and \ + (textLinearDimComponentOn == QadDimComponentEnum.DIM_LINE1 or textLinearDimComponentOn == QadDimComponentEnum.DIM_LINE2): + return None + else: # se si tratta della linea di quota 2 + # se la linea di quota 2 deve essere invisibile (vale solo se il testo é sulla linea di quota) + if self.dimLine2Show == False and \ + (textLinearDimComponentOn == QadDimComponentEnum.DIM_LINE1 or textLinearDimComponentOn == QadDimComponentEnum.DIM_LINE2): + return None + + f = QgsFeature(self.getLinearFeaturePrototype()) + g = fromQadGeomToQgsGeom(dimLine, self.getLinearLayer()) # trasformo la geometria + f.setGeometry(g) + + try: + # imposto il tipo di componente della quotatura + if len(self.componentFieldName) > 0: + f.setAttribute(self.componentFieldName, QadDimComponentEnum.DIM_LINE1 if isDimLine1 else QadDimComponentEnum.DIM_LINE2) + except: + pass + + try: + # imposto il tipo di linea + if len(self.lineTypeFieldName) > 0: + f.setAttribute(self.lineTypeFieldName, self.dimLineLineType) + except: + pass + + try: + # imposto il colore + if len(self.colorFieldName) > 0: + f.setAttribute(self.colorFieldName, self.dimLineColor) + except: + pass + + return f + + + # ============================================================================ + # FUNZIONI PER LA LINEA DI QUOTA - FINE + # FUNZIONI PER LE ESTENSIONI DELLA LINEA DI QUOTATURA - INIZIO + # ============================================================================ + + + # ============================================================================ + # getDimLineExtensions + # ============================================================================ + def getDimLineExtensions(self, dimLine1, dimLine2): + """ + Restituisce le estensioni delle linee di quotatura a inizio e fine (vedi variabile dimLineOffsetExtLine) + """ + # se non è maggiore di 0 oppure se non ci sono linee di dimensione + if self.dimLineOffsetExtLine <= 0 or (dimLine1 is None and dimLine2 is None): + return None, None + + extDimLine1 = None + extDimLine2 = None + # imposto le linee nello stesso verso della linea di dimensione + rot = qad_utils.getAngleBy2Pts(dimLine1.getStartPt(), dimLine1.getEndPt()) + if dimLine1 is not None: + # cambio punto iniziale + extDimLine1 = QadLine().set(qad_utils.getPolarPointByPtAngle(dimLine1.getStartPt(), rot + math.pi, self.dimLineOffsetExtLine), \ + dimLine1.getStartPt()) + if dimLine2 is None: # se la linea di quotatura è composta solo di una linea + # cambio punto finale + extDimLine2 = QadLine().set(dimLine1.getEndPt(), \ + qad_utils.getPolarPointByPtAngle(dimLine1.getEndPt(), rot, self.dimLineOffsetExtLine)) + + if dimLine2 is not None: + rot = qad_utils.getAngleBy2Pts(dimLine2.getStartPt(), dimLine2.getEndPt()) + # cambio punto finale + extDimLine2 = QadLine().set(dimLine2.getEndPt(), \ + qad_utils.getPolarPointByPtAngle(dimLine2.getEndPt(), rot, self.dimLineOffsetExtLine)) + + return extDimLine1, extDimLine2 + + + # ============================================================================ + # getDimArcExtension + # ============================================================================ + def getDimArcExtensions(self, dimLineArc1, dimLineArc2): + """ + Restituisce le estensioni degli archi di quotatura applicando a inizio e fine (vedi variabile dimLineOffsetExtLine) + """ + # se non è maggiore di 0 oppure se non ci sono linee di dimensione + if self.dimLineOffsetExtLine <= 0 or (dimLineArc1 is None and dimLineArc2 is None): + return None, None + + extDimArc1 = None + extDimArc2 = None + if dimLineArc1 is not None: + extDimArc1 = QadArc(dimLineArc1) + extDimArc1.endAngle = dimLineArc1.startAngle + dummyPt, dummyTg = dimLineArc1.getPointFromStart(-self.dimLineOffsetExtLine) + extDimArc1.setStartAngleByPt(dummyPt) # cambio punto iniziale + if dimLineArc2 is None: # se la linea di quotatura è composta solo di un arco + extDimArc2 = QadArc(dimLineArc1) + extDimArc2.startAngle = dimLineArc1.endAngle + dummyPt, dummtTg = dimLineArc1.getPointFromEnd(self.dimLineOffsetExtLine) + extDimArc2.setEndAngleByPt(dummyPt) # cambio punto finale + + if dimLineArc2 is not None: + extDimArc2 = QadArc(dimLineArc2) + extDimArc2.startAngle = dimLineArc1.endAngle + dummyPt, dummtTg = dimLineArc2.getPointFromEnd(self.dimLineOffsetExtLine) + dimLineArc2.setEndAngleByPt(dummyPt) # cambio punto finale + + return extDimArc1, extDimArc2 + + + # ============================================================================ + # getDimLineExtFeature + # ============================================================================ + def getDimLineExtFeature(self, extLine, isExtLine1): + """ + Restituisce la feature per l'estensione della linea di quotatura. + extLine = linea di estensione (QadLine o QadArc) + isExtLine1 = se True si tratta della estensione della linea di quotatura 1 altrimenti della linea di quotatura 2 + """ + if extLine is None: + return None + + f = QgsFeature(self.getLinearFeaturePrototype()) + g = fromQadGeomToQgsGeom(extLine, self.getLinearLayer()) # trasformo la geometria + f.setGeometry(g) + + try: + # imposto il tipo di componente della quotatura + if len(self.componentFieldName) > 0: + f.setAttribute(self.componentFieldName, QadDimComponentEnum.DIM_LINE_EXT1 if isExtLine1 else QadDimComponentEnum.DIM_LINE_EXT2) + except: + pass + + try: + # imposto il tipo di linea + if len(self.lineTypeFieldName) > 0: + f.setAttribute(self.lineTypeFieldName, self.extLine1LineType if isExtLine1 else self.extLine2LineType) + except: + pass + + try: + # imposto il colore + if len(self.colorFieldName) > 0: + f.setAttribute(self.colorFieldName, self.extLineColor) + except: + pass + + return f + + + # ============================================================================ + # FUNZIONI PER LE ESTENSIONI DELLA LINEA DI QUOTATURA - FINE + # FUNZIONI PER LA QUOTATURA LINEARE - INIZIO + # ============================================================================ + + + # ============================================================================ + # getLinearDimFeatures + # ============================================================================ + def getLinearDimFeatures(self, canvas, dimPt1, dimPt2, linePosPt, measure = None, \ + preferredAlignment = QadDimStyleAlignmentEnum.HORIZONTAL, \ + dimLineRotation = 0.0): + """ + dimPt1 = primo punto da quotare (in unita di mappa) + dimPt2 = secondo punto da quotare (in unita di mappa) + linePosPt = punto per indicare dove deve essere posizionata la linea di quota (in unita di mappa) + measure = indica se la misura é predeterminata oppure (se = None) deve essere calcolata + preferredAlignment = se lo stile di quota é lineare, indica se ci si deve allienare ai punti di quota + in modo orizzontale o verticale (se i punti di quota formano una linea obliqua) + dimLineRotation = angolo della linea di quotatura (default = 0) + + # quota lineare con una linea di quota orizzontale o verticale + # ritorna una lista di elementi che descrivono la geometria della quota: + # 1 lista = feature del primo e del secondo punto di quota; QgsFeature 1, QgsFeature 2 + # 2 lista = feature della prima e della seconda linea di quota (quest'ultima può essere None); QgsFeature 1, QgsFeature 2 + # 3 lista = feature del punto del testo di quota e geometria del rettangolo di occupazione; QgsFeature, QgsGeometry + # 4 lista = feature del primo e del secondo simbolo per la linea di quota (possono essere None); QgsFeature 1, QgsFeature 2 + # 5 lista = feature della prima e della seconda linea di estensione (possono essere None); QgsFeature 1, QgsFeature 2 + # 6 elemento = feature della linea di leader (può essere None); QgsFeature + """ + self.dimType = QadDimTypeEnum.LINEAR + + # punti di quotatura + dimPt1Feature = self.getDimPointFeature(dimPt1, True) # True = primo punto di quotatura + dimPt2Feature = self.getDimPointFeature(dimPt2, False) # False = secondo punto di quotatura + + # linea di quota entro le linee di estensione + dimLine1 = self.getDimLine(dimPt1, dimPt2, linePosPt, preferredAlignment, dimLineRotation) + dimLine2 = None + + # testo e blocchi + if measure is None: + textValue = dimLine1.length() + else: + textValue = unicode(measure) + + textFeature = self.getTextFeature(textValue) + textWidth, textHeight = qad_label.calculateLabelSize(self.getTextualLayer(), textFeature, canvas) + + # creo un rettangolo intorno al testo con un buffer = self.textOffsetDist + textWidthOffset = textWidth + self.textOffsetDist * 2 + textHeightOffset = textHeight + self.textOffsetDist * 2 + + # Restituisce una lista di 4 elementi: + # - il primo elemento é una lista con il punto di inserimento del testo della quota e la sua rotazione + # - il secondo elemento é una lista con flag che indica il tipo della linea sulla quale é stato messo il testo; vedi QadDimComponentEnum + # e una lista di linee "leader" nel caso il testo sia all'esterno della quota + # - il terzo elemento é la rotazione del primo blocco delle frecce; può essere None se non visibile + # - il quarto elemento é la rotazione del secondo blocco delle frecce; può essere None se non visibile + dummy1, dummy2, block1Rot, block2Rot = self.getLinearTextAndBlocksPosition(dimPt1, dimPt2, \ + dimLine1, \ + textWidthOffset, textHeightOffset) + + textOffsetRectInsPt = dummy1[0] + textRot = dummy1[1] + textLinearDimComponentOn = dummy2[0] + txtLeaderLines = dummy2[1] + + # trovo il vero punto di inserimento del testo tenendo conto del buffer intorno + textInsPt = qad_utils.getPolarPointByPtAngle(textOffsetRectInsPt, textRot, self.textOffsetDist) + textInsPt = qad_utils.getPolarPointByPtAngle(textInsPt, textRot + math.pi / 2, self.textOffsetDist) + + # testo + textGeom = QgsGeometry.fromPointXY(textInsPt) + textFeature = self.getTextFeature(textValue, textInsPt, textRot) + + # blocchi frecce + block1Feature = self.getSymbolFeature(dimLine1.getStartPt(), block1Rot, True, textLinearDimComponentOn) # True = primo punto di quotatura + block2Feature = self.getSymbolFeature(dimLine1.getEndPt(), block2Rot, False, textLinearDimComponentOn) # False = secondo punto di quotatura + + extLine1 = self.getExtLine(dimPt1, dimLine1.getStartPt()) + extLine2 = self.getExtLine(dimPt2, dimLine1.getEndPt()) + + # creo un rettangolo intorno al testo con un offset + textOffsetRect = self.textRectToQadPolyline(textOffsetRectInsPt, textWidthOffset, textHeightOffset, textRot) + + if textLinearDimComponentOn == QadDimComponentEnum.DIM_LINE1: # linea di quota ("Dimension line") + dimLine1, dimLine2 = self.adjustLineAccordingTextRect(textOffsetRect, dimLine1, QadDimComponentEnum.DIM_LINE1) + elif textLinearDimComponentOn == QadDimComponentEnum.EXT_LINE1: # prima linea di estensione ("Extension line 1") + if extLine1 is not None: + extLineRot = qad_utils.getAngleBy2Pts(dimPt1, dimLine1.getStartPt()) + extLine1 = self.getExtLine(dimPt1, qad_utils.getPolarPointByPtAngle(dimLine1.getStartPt(), extLineRot, textWidth + self.textOffsetDist)) + # cambio il verso della linea perché getExtLine restituisce una linea dalla linea di quota verso il punto di quotatura + reverseExtLine1 = extLine1.copy().reverse() + extLine1, dummy = self.adjustLineAccordingTextRect(textOffsetRect, reverseExtLine1, QadDimComponentEnum.EXT_LINE1) + elif textLinearDimComponentOn == QadDimComponentEnum.EXT_LINE2: # seconda linea di estensione ("Extension line 2") + if extLine2 is not None: + extLineRot = qad_utils.getAngleBy2Pts(dimPt2, dimLine1.getEndPt()) + extLine2 = self.getExtLine(dimPt2, qad_utils.getPolarPointByPtAngle(dimLine1.getEndPt(), extLineRot, textWidth + self.textOffsetDist)) + # cambio il verso della linea perché getExtLine restituisce una linea dalla linea di quota verso il punto di quotatura + reverseExtLine2 = extLine2.copy().reverse() + extLine2, dummy = self.adjustLineAccordingTextRect(textOffsetRect, reverseExtLine2, QadDimComponentEnum.EXT_LINE2) + elif textLinearDimComponentOn == QadDimComponentEnum.LEADER_LINE: # linea porta quota usata quando il testo é fuori dalla quota ("Leader") + lastLine = txtLeaderLines.getLinearObjectAt(-1) + lastLine, dummy = self.adjustLineAccordingTextRect(textOffsetRect, lastLine, QadDimComponentEnum.LEADER_LINE) + txtLeaderLines.remove(-1) # sostituisco l'ultimo elemento + txtLeaderLines.append(lastLine) + + # linee di quota + dimLine1Feature = self.getDimLineFeature(dimLine1, True, textLinearDimComponentOn) # True = prima linea di quota + dimLine2Feature = self.getDimLineFeature(dimLine2, False, textLinearDimComponentOn) # False = seconda linea di quota + + # estensioni delle linee di quota + dimLineExt1, dimLineExt2 = self.getDimLineExtensions(dimLine1, dimLine2) + dimLineExt1Feature = self.getDimLineExtFeature(dimLineExt1, True) + dimLineExt2Feature = self.getDimLineExtFeature(dimLineExt2, False) + + # linee di estensione + extLine1Feature = self.getExtLineFeature(extLine1, True) # True = prima linea di estensione + extLine2Feature = self.getExtLineFeature(extLine2, False) # False = seconda linea di estensione + + # linea di leader + txtLeaderLineFeature = self.getLeaderFeature(txtLeaderLines) + + dimEntity = QadDimEntity() + dimEntity.dimStyle = self + # features testuali + dimEntity.textualFeature = textFeature + # features lineari + if dimLine1Feature is not None: + dimEntity.linearFeatures.append(dimLine1Feature) + if dimLine2Feature is not None: + dimEntity.linearFeatures.append(dimLine2Feature) + + if dimLineExt1Feature is not None: + dimEntity.linearFeatures.append(dimLineExt1Feature) + if dimLineExt2Feature is not None: + dimEntity.linearFeatures.append(dimLineExt2Feature) + + if extLine1Feature is not None: + dimEntity.linearFeatures.append(extLine1Feature) + if extLine2Feature is not None: + dimEntity.linearFeatures.append(extLine2Feature) + + if txtLeaderLineFeature is not None: + dimEntity.linearFeatures.append(txtLeaderLineFeature) + # features puntuali + dimEntity.symbolFeatures.extend([dimPt1Feature, dimPt2Feature]) + if block1Feature is not None: + dimEntity.symbolFeatures.append(block1Feature) + if block2Feature is not None: + dimEntity.symbolFeatures.append(block2Feature) + + return dimEntity, QgsGeometry.fromPolygonXY([textOffsetRect.asPolyline()]) + + + # ============================================================================ + # addLinearDimToLayers + # ============================================================================ + def addLinearDimToLayers(self, plugIn, dimPt1, dimPt2, linePosPt, measure = None, \ + preferredAlignment = QadDimStyleAlignmentEnum.HORIZONTAL, \ + dimLineRotation = 0.0): + """ + Aggiunge ai layers le features che compongono una quota lineare. + """ + dimEntity, textOffsetRect = self.getLinearDimFeatures(plugIn.canvas, \ + dimPt1, \ + dimPt2, \ + linePosPt, \ + measure, \ + preferredAlignment, \ + dimLineRotation) + + return self.addDimEntityToLayers(plugIn, dimEntity) + + + # ============================================================================ + # FUNZIONI PER LA QUOTATURA LINEARE - FINE + # FUNZIONI PER LA QUOTATURA ALLINEATA - INIZIO + # ============================================================================ + + + # ============================================================================ + # getAlignedDimFeatures + # ============================================================================ + def getAlignedDimFeatures(self, canvas, dimPt1, dimPt2, linePosPt, measure = None): + """ + dimPt1 = primo punto da quotare (in unita di mappa) + dimPt2 = secondo punto da quotare (in unita di mappa) + linePosPt = punto per indicare dove deve essere posizionata la linea di quota (in unita di mappa) + measure = indica se la misura é predeterminata oppure (se = None) deve essere calcolata + + # quota lineare con una linea di quota orizzontale o verticale + # ritorna una lista di elementi che descrivono la geometria della quota: + # 1 lista = feature del primo e del secondo punto di quota; QgsFeature 1, QgsFeature 2 + # 2 lista = feature della prima e della seconda linea di quota (quest'ultima può essere None); QgsFeature 1, QgsFeature 2 + # 3 lista = feature del punto del testo di quota e geometria del rettangolo di occupazione; QgsFeature, QgsGeometry + # 4 lista = feature del primo e del secondo simbolo per la linea di quota (possono essere None); QgsFeature 1, QgsFeature 2 + # 5 lista = feature della prima e della seconda linea di estensione (possono essere None); QgsFeature 1, QgsFeature 2 + # 6 elemento = feature della linea di leader (può essere None); QgsFeature + """ + self.dimType = QadDimTypeEnum.ALIGNED + + # punti di quotatura + dimPt1Feature = self.getDimPointFeature(dimPt1, True) # True = primo punto di quotatura + dimPt2Feature = self.getDimPointFeature(dimPt2, False) # False = secondo punto di quotatura + + # linea di quota entro le linee di estensione + dimLine1 = self.getDimLine(dimPt1, dimPt2, linePosPt) + dimLine2 = None + + # testo e blocchi + if measure is None: + textValue = dimLine1.length() + else: + textValue = unicode(measure) + + textFeature = self.getTextFeature(textValue) + textWidth, textHeight = qad_label.calculateLabelSize(self.getTextualLayer(), textFeature, canvas) + + # creo un rettangolo intorno al testo con un buffer = self.textOffsetDist + textWidthOffset = textWidth + self.textOffsetDist * 2 + textHeightOffset = textHeight + self.textOffsetDist * 2 + + # Restituisce una lista di 4 elementi: + # - il primo elemento é una lista con il punto di inserimento del testo della quota e la sua rotazione + # - il secondo elemento é una lista con flag che indica il tipo della linea sulla quale é stato messo il testo; vedi QadDimComponentEnum + # e una lista di linee "leader" nel caso il testo sia all'esterno della quota + # - il terzo elemento é la rotazione del primo blocco delle frecce; può essere None se non visibile + # - il quarto elemento é la rotazione del secondo blocco delle frecce; può essere None se non visibile + dummy1, dummy2, block1Rot, block2Rot = self.getLinearTextAndBlocksPosition(dimPt1, dimPt2, \ + dimLine1, \ + textWidthOffset, textHeightOffset) + textOffsetRectInsPt = dummy1[0] + textRot = dummy1[1] + textLinearDimComponentOn = dummy2[0] + txtLeaderLines = dummy2[1] + + # trovo il vero punto di inserimento del testo tenendo conto del buffer intorno + textInsPt = qad_utils.getPolarPointByPtAngle(textOffsetRectInsPt, textRot, self.textOffsetDist) + textInsPt = qad_utils.getPolarPointByPtAngle(textInsPt, textRot + math.pi / 2, self.textOffsetDist) + + # testo + textGeom = QgsGeometry.fromPointXY(textInsPt) + textFeature = self.getTextFeature(textValue, textInsPt, textRot) + + # blocchi frecce + block1Feature = self.getSymbolFeature(dimLine1.getStartPt(), block1Rot, True, textLinearDimComponentOn) # True = primo punto di quotatura + block2Feature = self.getSymbolFeature(dimLine1.getEndPt(), block2Rot, False, textLinearDimComponentOn) # False = secondo punto di quotatura + + extLine1 = self.getExtLine(dimPt1, dimLine1.getStartPt()) + extLine2 = self.getExtLine(dimPt2, dimLine1.getEndPt()) + + # creo un rettangolo intorno al testo con un offset + textOffsetRect = self.textRectToQadPolyline(textOffsetRectInsPt, textWidthOffset, textHeightOffset, textRot) + + if textLinearDimComponentOn == QadDimComponentEnum.DIM_LINE1: # linea di quota ("Dimension line") + dimLine1, dimLine2 = self.adjustLineAccordingTextRect(textOffsetRect, dimLine1, QadDimComponentEnum.DIM_LINE1) + elif textLinearDimComponentOn == QadDimComponentEnum.EXT_LINE1: # prima linea di estensione ("Extension line 1") + if extLine1 is not None: + extLineRot = qad_utils.getAngleBy2Pts(dimPt1, dimLine1.getStartPt()) + extLine1 = self.getExtLine(dimPt1, qad_utils.getPolarPointByPtAngle(dimLine1.getStartPt(), extLineRot, textWidth + self.textOffsetDist)) + # cambio il verso della linea perché getExtLine restituisce una linea dalla linea di quota verso il punto di quotatura + reverseExtLine1 = extLine1.copy().reverse() + extLine1, dummy = self.adjustLineAccordingTextRect(textOffsetRect, reverseExtLine1, QadDimComponentEnum.EXT_LINE1) + elif textLinearDimComponentOn == QadDimComponentEnum.EXT_LINE2: # seconda linea di estensione ("Extension line 2") + if extLine2 is not None: + extLineRot = qad_utils.getAngleBy2Pts(dimPt2, dimLine1.getEndPt()) + extLine2 = self.getExtLine(dimPt2, qad_utils.getPolarPointByPtAngle(dimLine1.getEndPt(), extLineRot, textWidth + self.textOffsetDist)) + # cambio il verso della linea perché getExtLine restituisce una linea dalla linea di quota verso il punto di quotatura + reverseExtLine2 = extLine2.copy().reverse() + extLine2, dummy = self.adjustLineAccordingTextRect(textOffsetRect, reverseExtLine2, QadDimComponentEnum.EXT_LINE2) + elif textLinearDimComponentOn == QadDimComponentEnum.LEADER_LINE: # linea porta quota usata quando il testo é fuori dalla quota ("Leader") + lastLine = txtLeaderLines.getLinearObjectAt(-1) + lastLine, dummy = self.adjustLineAccordingTextRect(textOffsetRect, lastLine, QadDimComponentEnum.LEADER_LINE) + txtLeaderLines.remove(-1) # sostituisco l'ultimo elemento + txtLeaderLines.append(lastLine) + + # linee di quota + dimLine1Feature = self.getDimLineFeature(dimLine1, True, textLinearDimComponentOn) # True = prima linea di quota + dimLine2Feature = self.getDimLineFeature(dimLine2, False, textLinearDimComponentOn) # False = seconda linea di quota + + # estensioni delle linee di quota + dimLineExt1, dimLineExt2 = self.getDimLineExtensions(dimLine1, dimLine2) + dimLineExt1Feature = self.getDimLineExtFeature(dimLineExt1, True) + dimLineExt2Feature = self.getDimLineExtFeature(dimLineExt2, False) + + # linee di estensione + extLine1Feature = self.getExtLineFeature(extLine1, True) # True = prima linea di estensione + extLine2Feature = self.getExtLineFeature(extLine2, False) # False = seconda linea di estensione + + # linea di leader + txtLeaderLineFeature = self.getLeaderFeature(txtLeaderLines) + + dimEntity = QadDimEntity() + dimEntity.dimStyle = self + # features testuali + dimEntity.textualFeature = textFeature + # features lineari + if dimLine1Feature is not None: + dimEntity.linearFeatures.append(dimLine1Feature) + if dimLine2Feature is not None: + dimEntity.linearFeatures.append(dimLine2Feature) + + if dimLineExt1Feature is not None: + dimEntity.linearFeatures.append(dimLineExt1Feature) + if dimLineExt2Feature is not None: + dimEntity.linearFeatures.append(dimLineExt2Feature) + + if extLine1Feature is not None: + dimEntity.linearFeatures.append(extLine1Feature) + if extLine2Feature is not None: + dimEntity.linearFeatures.append(extLine2Feature) + + if txtLeaderLineFeature is not None: + dimEntity.linearFeatures.append(txtLeaderLineFeature) + # features puntuali + dimEntity.symbolFeatures.extend([dimPt1Feature, dimPt2Feature]) + if block1Feature is not None: + dimEntity.symbolFeatures.append(block1Feature) + if block2Feature is not None: + dimEntity.symbolFeatures.append(block2Feature) + + return dimEntity, QgsGeometry.fromPolygonXY([textOffsetRect.asPolyline()]) + + + # ============================================================================ + # addAlignedDimToLayers + # ============================================================================ + def addAlignedDimToLayers(self, plugIn, dimPt1, dimPt2, linePosPt, measure = None, \ + preferredAlignment = QadDimStyleAlignmentEnum.HORIZONTAL, \ + dimLineRotation = 0.0): + """ + dimPt1 = primo punto da quotare (in unita di mappa) + dimPt2 = secondo punto da quotare (in unita di mappa) + linePosPt = punto per indicare dove deve essere posizionata la linea di quota (in unita di mappa) + measure = indica se la misura é predeterminata oppure (se = None) deve essere calcolata + preferredAlignment = se lo stile di quota é lineare, indica se ci si deve allienare ai punti di quota + in modo orizzontale o verticale (se i punti di quota formano una linea obliqua) + dimLineRotation = angolo della linea di quotatura (default = 0) + + Aggiunge ai layers le features che compongono una quota allineata. + """ + dimEntity, textOffsetRect = self.getAlignedDimFeatures(plugIn.canvas, \ + dimPt1, \ + dimPt2, \ + linePosPt, \ + measure) + + return self.addDimEntityToLayers(plugIn, dimEntity) + + + # ============================================================================ + # FUNZIONI PER LA QUOTATURA ALLINEATA - FINE + # FUNZIONI PER LA QUOTATURA ARCO - INIZIO + # ============================================================================ + + + # ============================================================================ + # getArcDimFeatures + # ============================================================================ + def getArcDimFeatures(self, canvas, dimArc, linePosPt, measure = None, arcLeader = None): + """ + dimArc = oggetto arco QadArc da quotare (in unita di mappa) + linePosPt = punto per indicare dove deve essere posizionata la linea di quota (in unita di mappa) + measure = indica se la misura é predeterminata oppure (se = None) deve essere calcolata + arcLeader = indica se si deve disegnare la linea direttrice dalla quota all'arco + + # quota arco per misurare la lunghezza di un arco o parte di esso + # ritorna una lista di elementi che descrivono la geometria della quota: + # 1 lista = feature del primo e del secondo punto di quota; QgsFeature 1, QgsFeature 2 + # 2 lista = feature della prima e della seconda linea di quota (quest'ultima può essere None); QgsFeature 1, QgsFeature 2 + # 3 lista = feature del punto del testo di quota e geometria del rettangolo di occupazione; QgsFeature, QgsGeometry + # 4 lista = feature del primo e del secondo simbolo per la linea di quota (possono essere None); QgsFeature 1, QgsFeature 2 + # 5 lista = feature della prima e della seconda linea di estensione (possono essere None); QgsFeature 1, QgsFeature 2 + # 6 elemento = feature della linea di leader (può essere None); QgsFeature + """ + self.dimType = QadDimTypeEnum.ARC_LENTGH + + # linea di quota sottoforma di arco + dimLineArc1 = self.getDimLineForArc(dimArc, linePosPt) + dimLineArc1StartPt = dimLineArc1.getStartPt() + dimLineArc1EndPt = dimLineArc1.getEndPt() + dimLineArc2 = None + + dimPt1 = dimArc.getStartPt() + dimPt2 = dimArc.getEndPt() + + # punti di quotatura + dimPt1Feature = self.getDimPointFeature(dimPt1, True) # True = primo punto di quotatura + dimPt2Feature = self.getDimPointFeature(dimPt2, False) # False = secondo punto di quotatura + + # testo e blocchi + if measure is None: + textValue = dimArc.length() + else: + textValue = unicode(measure) + + textFeature = self.getTextFeature(textValue) + textWidth, textHeight = qad_label.calculateLabelSize(self.getTextualLayer(), textFeature, canvas) + + # creo un rettangolo intorno al testo con un buffer = self.textOffsetDist + textWidthOffset = textWidth + self.textOffsetDist * 2 + textHeightOffset = textHeight + self.textOffsetDist * 2 + + arcSymbRadius = textHeight * 2 / 4 + if self.arcSymbPos == QadDimStyleArcSymbolPosEnum.BEFORE_TEXT: + textWidthOffset = textWidthOffset + self.textOffsetDist + 2 * arcSymbRadius + elif self.arcSymbPos == QadDimStyleArcSymbolPosEnum.ABOVE_TEXT: + textHeightOffset = textHeightOffset + self.textOffsetDist + arcSymbRadius + + # Restituisce una lista di 4 elementi: + # - il primo elemento é una lista con il punto di inserimento del testo della quota e la sua rotazione + # - il secondo elemento é una lista con flag che indica il tipo della linea sulla quale é stato messo il testo; vedi QadDimComponentEnum + # e una lista di linee "leader" nel caso il testo sia all'esterno della quota + # - il terzo elemento é la rotazione del primo blocco delle frecce; può essere None se non visibile + # - il quarto elemento é la rotazione del secondo blocco delle frecce; può essere None se non visibile + dummy1, dummy2, block1Rot, block2Rot = self.getArcTextAndBlocksPosition(dimArc, dimLineArc1, \ + textWidthOffset, textHeightOffset) + textOffsetRectInsPt = dummy1[0] + textRot = dummy1[1] + textLinearDimComponentOn = dummy2[0] + txtLeaderLines = dummy2[1] + + # trovo il vero punto di inserimento del testo tenendo conto del buffer intorno + if self.arcSymbPos == QadDimStyleArcSymbolPosEnum.BEFORE_TEXT: + textInsPt = qad_utils.getPolarPointByPtAngle(textOffsetRectInsPt, textRot, self.textOffsetDist + self.textOffsetDist + 2 * arcSymbRadius) + textInsPt = qad_utils.getPolarPointByPtAngle(textInsPt, textRot + math.pi / 2, self.textOffsetDist) + else: + textInsPt = qad_utils.getPolarPointByPtAngle(textOffsetRectInsPt, textRot, self.textOffsetDist) + textInsPt = qad_utils.getPolarPointByPtAngle(textInsPt, textRot + math.pi / 2, self.textOffsetDist) + + # testo + textGeom = QgsGeometry.fromPointXY(textInsPt) + textFeature = self.getTextFeature(textValue, textInsPt, textRot) + + # blocchi frecce + block1Feature = self.getSymbolFeature(dimLineArc1StartPt, block1Rot, True, textLinearDimComponentOn) # True = primo punto di quotatura + block2Feature = self.getSymbolFeature(dimLineArc1EndPt, block2Rot, False, textLinearDimComponentOn) # False = secondo punto di quotatura + + extLine1 = self.getExtLine(dimPt1, dimLineArc1StartPt) + extLine2 = self.getExtLine(dimPt2, dimLineArc1EndPt) + + # creo un rettangolo intorno al testo con un offset + textOffsetRect = self.textRectToQadPolyline(textOffsetRectInsPt, textWidthOffset, textHeightOffset, textRot) + + if textLinearDimComponentOn == QadDimComponentEnum.DIM_LINE1: # linea di quota ("Dimension line") + dimLineArc1, dimLineArc2 = self.adjustArcAccordingTextRect(textOffsetRect, dimLineArc1, QadDimComponentEnum.DIM_LINE1) + elif textLinearDimComponentOn == QadDimComponentEnum.EXT_LINE1: # prima linea di estensione ("Extension line 1") + if extLine1 is not None: + extLineRot = qad_utils.getAngleBy2Pts(dimPt1, dimLineArc1StartPt) + extLine1 = self.getExtLine(dimPt1, qad_utils.getPolarPointByPtAngle(dimLineArc1StartPt, extLineRot, textWidth + self.textOffsetDist)) + # cambio il verso della linea perché getExtLine restituisce una linea dalla linea di quota verso il punto di quotatura + reverseExtLine1 = extLine1.copy().reverse() + extLine1, dummy = self.adjustLineAccordingTextRect(textOffsetRect, reverseExtLine1, QadDimComponentEnum.EXT_LINE1) + elif textLinearDimComponentOn == QadDimComponentEnum.EXT_LINE2: # seconda linea di estensione ("Extension line 2") + if extLine2 is not None: + extLineRot = qad_utils.getAngleBy2Pts(dimPt2, dimLineArc1EndPt) + extLine2 = self.getExtLine(dimPt2, qad_utils.getPolarPointByPtAngle(dimLineArc1EndPt, extLineRot, textWidth + self.textOffsetDist)) + # cambio il verso della linea perché getExtLine restituisce una linea dalla linea di quota verso il punto di quotatura + reverseExtLine2 = extLine2.copy().reverse() + extLine2, dummy = self.adjustLineAccordingTextRect(textOffsetRect, reverseExtLine2, QadDimComponentEnum.EXT_LINE2) + elif textLinearDimComponentOn == QadDimComponentEnum.LEADER_LINE: # linea porta quota usata quando il testo é fuori dalla quota ("Leader") + lastLine = txtLeaderLines.getLinearObjectAt(-1) + lastLine, dummy = self.adjustLineAccordingTextRect(textOffsetRect, lastLine, QadDimComponentEnum.LEADER_LINE) + txtLeaderLines.remove(-1) # sostituisco l'ultimo elemento + txtLeaderLines.append(lastLine) + + # linee di quota + if dimLineArc1 is None: + dimLine1Feature = None + else: + dimLine1Feature = self.getDimLineFeature(dimLineArc1, True, textLinearDimComponentOn) # True = prima linea di quota + + if dimLineArc2 is None: + dimLine2Feature = None + else: + dimLine2Feature = self.getDimLineFeature(dimLineArc2, False, textLinearDimComponentOn) # False = seconda linea di quota + + # estensioni delle linee di quota + dimArcExt1, dimArcExt2 = self.getDimArcExtensions(dimLineArc1, dimLineArc2) + if dimArcExt1 is None: + dimLineExt1Feature = None + else: + dimLineExt1Feature = self.getDimLineExtFeature(dimArcExt1, True) + + if dimArcExt2 is None: + dimLineExt2Feature = None + else: + dimLineExt2Feature = self.getDimLineExtFeature(dimArcExt2, False) + + # linee di estensione + extLine1Feature = self.getExtLineFeature(extLine1, True) # True = prima linea di estensione + extLine2Feature = self.getExtLineFeature(extLine2, False) # False = seconda linea di estensione + + # linea di leader + txtLeaderLineFeature = self.getLeaderFeature(txtLeaderLines, QadDimComponentEnum.ARC_LEADER_LINE) + + + # linea di arc leader + arcLeaderLineFeature = None + arcLeaderBlockFeature = None + if arcLeader: # se si vuole la linea che congiunge il testo all'arco da quotare + arcLeaderLine = self.getArcLeaderLine(textOffsetRectInsPt, dimArc) + if arcLeaderLine is not None: + arcLeaderLines = QadPolyline() + arcLeaderLines.append(arcLeaderLine) + arcLeaderLineFeature = self.getLeaderFeature(arcLeaderLines) + arcLeaderBlockFeature = self.getLeaderSymbolFeature(arcLeaderLine.getEndPt(), \ + arcLeaderLine.getTanDirectionOnPt()) + # simbolo dell'arco + arcSymbolLineFeature = None + if self.arcSymbPos == QadDimStyleArcSymbolPosEnum.BEFORE_TEXT: + arc = QadArc() + arcPt1 = qad_utils.getPolarPointByPtAngle(textInsPt, textRot, - self.textOffsetDist) + arcCenter = qad_utils.getPolarPointByPtAngle(arcPt1, textRot, - arcSymbRadius) + arcPt2 = qad_utils.getPolarPointByPtAngle(arcCenter, textRot, - arcSymbRadius) + arc.fromStartCenterEndPts(arcPt1, arcCenter, arcPt2) + arcSymbolLineFeature = self.getArcSymbolLineFeature(arc) + elif self.arcSymbPos == QadDimStyleArcSymbolPosEnum.ABOVE_TEXT: + arc = QadArc() + arcCenter = qad_utils.getPolarPointByPtAngle(textInsPt, textRot, textWidth / 2) + arcCenter = qad_utils.getPolarPointByPtAngle(arcCenter, textRot + math.pi / 2, arcSymbRadius + self.textOffsetDist) + arcPt1 = qad_utils.getPolarPointByPtAngle(arcCenter, textRot, arcSymbRadius) + arcPt2 = qad_utils.getPolarPointByPtAngle(arcCenter, textRot, - arcSymbRadius) + arc.fromStartCenterEndPts(arcPt1, arcCenter, arcPt2) + arcSymbolLineFeature = self.getArcSymbolLineFeature(arc) + + dimEntity = QadDimEntity() + dimEntity.dimStyle = self + # features testuali + dimEntity.textualFeature = textFeature + # features lineari + if dimLine1Feature is not None: + dimEntity.linearFeatures.append(dimLine1Feature) + if dimLine2Feature is not None: + dimEntity.linearFeatures.append(dimLine2Feature) + + if dimLineExt1Feature is not None: + dimEntity.linearFeatures.append(dimLineExt1Feature) + if dimLineExt2Feature is not None: + dimEntity.linearFeatures.append(dimLineExt2Feature) + + if extLine1Feature is not None: + dimEntity.linearFeatures.append(extLine1Feature) + if extLine2Feature is not None: + dimEntity.linearFeatures.append(extLine2Feature) + + if txtLeaderLineFeature is not None: + dimEntity.linearFeatures.append(txtLeaderLineFeature) + if arcLeaderLineFeature is not None: + dimEntity.linearFeatures.append(arcLeaderLineFeature) + if arcSymbolLineFeature is not None: + dimEntity.linearFeatures.append(arcSymbolLineFeature) + # features puntuali + dimEntity.symbolFeatures.extend([dimPt1Feature, dimPt2Feature]) + if block1Feature is not None: + dimEntity.symbolFeatures.append(block1Feature) + if block2Feature is not None: + dimEntity.symbolFeatures.append(block2Feature) + if arcLeaderBlockFeature is not None: + dimEntity.symbolFeatures.append(arcLeaderBlockFeature) + + return dimEntity, QgsGeometry.fromPolygonXY([textOffsetRect.asPolyline()]) + + + # ============================================================================ + # addArcDimToLayers + # ============================================================================ + def addArcDimToLayers(self, plugIn, dimArc, linePosPt, measure = None, arcLeader = False): + """ + dimArc = arco da quotare (in unita di mappa) + linePosPt = punto per indicare dove deve essere posizionata la linea di quota (in unita di mappa) + measure = indica se la misura é predeterminata oppure (se = None) deve essere calcolata + arcLeader = indica se si deve disegnare la linea direttrice dalla quota all'arco + + Aggiunge ai layers le features che compongono una quota allineata. + """ + dimEntity, textOffsetRect = self.getArcDimFeatures(plugIn.canvas, \ + dimArc, \ + linePosPt, \ + measure, \ + arcLeader) + + return self.addDimEntityToLayers(plugIn, dimEntity) + + + # ============================================================================ + # FUNZIONI PER LA QUOTATURA ARCO - FINE + # FUNZIONI PER LA QUOTATURA RAGGIO - INIZIO + # ============================================================================ + + + # ============================================================================ + # getCenterMarkerLinesFeature + # ============================================================================ + def getCenterMarkerLinesFeature(self, canvas, dimObj, linePosPt): + """ + center = punto del centro dell'arco o del cerchio da quotare (in unita di mappa) + linePosPt = punto per indicare dove deve essere posizionata la linea di quota (in unita di mappa) + Restituisce una lista di feature che rappresentano del linee di marker del centro + """ + if self.centerMarkSize == 0.0: # 0 = niente + return [] + # se linePosPos è < del raggio non si deve inserire il marker del centro + if qad_utils.getDistance(dimObj.center , linePosPt) < dimObj.radius: + return [] + + geoms = [] + if self.centerMarkSize > 0.0: # dimensione marcatore di centro + horizLine = QadLine().set(QgsPointXY(dimObj.center.x() - self.centerMarkSize, dimObj.center.y()), \ + QgsPointXY(dimObj.center.x() + self.centerMarkSize, dimObj.center.y())) + geoms.append(horizLine) + + vertLine = QadLine().set(QgsPointXY(dimObj.center.x(), dimObj.center.y() - self.centerMarkSize), \ + QgsPointXY(dimObj.center.x(), dimObj.center.y() + self.centerMarkSize)) + geoms.append(vertLine) + else: # dimensione linee d'asse + centerMarkSize = -self.centerMarkSize + + horizLine = QadLine().set(QgsPointXY(dimObj.center.x() - centerMarkSize, dimObj.center.y()), \ + QgsPointXY(dimObj.center.x() + centerMarkSize, dimObj.center.y())) + geoms.append(horizLine) + + vertLine = QadLine().set(QgsPointXY(dimObj.center.x(), dimObj.center.y() - centerMarkSize), \ + QgsPointXY(dimObj.center.x(), dimObj.center.y() + centerMarkSize)) + geoms.append(vertLine) + + if (2 * centerMarkSize) < dimObj.radius: + horizLine = QadLine().set(QgsPointXY(dimObj.center.x() - (2 * centerMarkSize), dimObj.center.y()), \ + QgsPointXY(dimObj.center.x() - dimObj.radius - centerMarkSize, dimObj.center.y())) + geoms.append(horizLine) + + horizLine = QadLine().set(QgsPointXY(dimObj.center.x() + (2 * centerMarkSize), dimObj.center.y()), \ + QgsPointXY(dimObj.center.x() + dimObj.radius + centerMarkSize, dimObj.center.y())) + geoms.append(horizLine) + + vertLine = QadLine().set(QgsPointXY(dimObj.center.x(), dimObj.center.y() - (2 * centerMarkSize)), \ + QgsPointXY(dimObj.center.x(), dimObj.center.y() - dimObj.radius - centerMarkSize)) + geoms.append(vertLine) + + vertLine = QadLine().set(QgsPointXY(dimObj.center.x(), dimObj.center.y() + (2 * centerMarkSize)), \ + QgsPointXY(dimObj.center.x(), dimObj.center.y() + dimObj.radius + centerMarkSize)) + geoms.append(vertLine) + + features = [] + for g in geoms: + f = QgsFeature(self.getLinearFeaturePrototype()) + f.setGeometry(fromQadGeomToQgsGeom(g, self.getLinearLayer())) # trasformo la geometria + + try: + # imposto il tipo di componente della quotatura + if len(self.componentFieldName) > 0: + f.setAttribute(self.componentFieldName, QadDimComponentEnum.CENTER_MARKER_LINE) + except: + pass + + try: + # imposto il tipo di linea + if len(self.lineTypeFieldName) > 0: + f.setAttribute(self.lineTypeFieldName, self.dimLineLineType) + except: + pass + + try: + # imposto il colore + if len(self.colorFieldName) > 0: + f.setAttribute(self.colorFieldName, self.dimLineColor) + except: + pass + + features.append(f) + + return features + + + # ============================================================================ + # getRadiusDimFeatures + # ============================================================================ + def getRadiusDimFeatures(self, canvas, dimObj, linePosPt, measure = None): + """ + dimObj = oggetto arco circle da quotare (in unita di mappa) + linePosPt = punto per indicare dove deve essere posizionata la linea di quota (in unita di mappa) + measure = indica se la misura é predeterminata oppure (se = None) deve essere calcolata + + # quota raggio per misurare la lunghezza di un raggio di arco o di cerchio + # ritorna una lista di elementi che descrivono la geometria della quota: + # 1 lista = feature del primo e del secondo punto di quota; QgsFeature 1, QgsFeature 2 + # 2 lista = feature della prima e della seconda linea di quota (quest'ultima può essere None); QgsFeature 1, QgsFeature 2 + # 3 lista = feature del punto del testo di quota e geometria del rettangolo di occupazione; QgsFeature, QgsGeometry + # 4 lista = feature del primo e del secondo simbolo per la linea di quota (possono essere None); QgsFeature 1, QgsFeature 2 + # 5 lista = feature della prima e della seconda linea di estensione (possono essere None); QgsFeature 1, QgsFeature 2 + # 6 elemento = feature della linea di leader (può essere None); QgsFeature + """ + self.dimType = QadDimTypeEnum.RADIUS + + # marker del centro + dimCenterMarkers = self.getCenterMarkerLinesFeature(canvas, dimObj, linePosPt) + + # punti di quotatura + dimPt1 = dimObj.center + angle = qad_utils.getAngleBy2Pts(dimPt1, linePosPt) + dimPt2 = qad_utils.getPolarPointByPtAngle(dimPt1, angle, dimObj.radius) # punto sulla curva + + dimPt1Feature = self.getDimPointFeature(dimPt1, True) # True = primo punto di quotatura + dimPt2Feature = self.getDimPointFeature(dimPt2, False) # False = secondo punto di quotatura + + # se il blocco di quota 1 e il blocco di quota 2 sono visibili + if self.block1Name != "" and self.block1Name != "": + blockRot = qad_utils.getAngleBy2Pts(linePosPt, dimPt2) + if qad_utils.getDistance(linePosPt, dimPt2) <= 2 * self.getBlock2Size(): + linePosPt = qad_utils.getPolarPointByPtAngle(dimPt2, blockRot + math.pi, 2 * self.getBlock2Size()) + # blocco freccia + blockFeature = self.getSymbolFeature(dimPt2, blockRot, \ + True if self.block1Name != "" else False, \ + QadDimComponentEnum.LEADER_LINE) + else: + blockFeature = None + + # linea di quota + dimLine = QadLine().set(linePosPt, dimPt2) + # la linea di quota 1 o la linea di quota 2 devono essere visibile + if self.dimLine1Show == True or self.dimLine2Show == True: + dimLineFeature = self.getDimLineFeature(dimLine, self.dimLine1Show, QadDimComponentEnum.LEADER_LINE) + else: # la linea di quota è invisibile + dimLineFeature = None + + # linea di estensione + extLineFeature = None + if dimObj.whatIs() == "ARC": + extArc = self.getExtArc(dimObj, linePosPt) + # linee di estensione + if extArc is not None: + extLineFeature = self.getExtLineFeature(extArc, True) # True = prima linea di estensione + + # testo e blocchi + if measure is None: + textValue = QadMsg.translate("Command_DIM", "R") + self.getFormattedText(dimObj.radius) # antepongo la R di Radius + else: + textValue = unicode(measure) + + textFeature = self.getTextFeature(textValue) + textWidth, textHeight = qad_label.calculateLabelSize(self.getTextualLayer(), textFeature, canvas) + + # creo un rettangolo intorno al testo con un buffer = self.textOffsetDist + textWidthOffset = textWidth + self.textOffsetDist * 2 + textHeightOffset = textHeight + self.textOffsetDist * 2 + + # creo una linea fittizia per il posizionamento del testo + # la creo lunga metà della lunghezza del testo per forzare il testo fuori dalla linea fittizia + pt = qad_utils.getPolarPointByPtAngle(linePosPt, angle + math.pi, textWidthOffset / 2) + + # Restituisce una lista di 4 elementi: + # - il primo elemento é una lista con il punto di inserimento del testo della quota e la sua rotazione + # - il secondo elemento é una lista con flag che indica il tipo della linea sulla quale é stato messo il testo; vedi QadDimComponentEnum + # e una lista di linee "leader" nel caso il testo sia all'esterno della quota + dummy1, dummy2, block1Rot, block2Rot = self.getRadiusTextAndBlocksPosition(QadLine().set(linePosPt, pt), \ + textWidthOffset, textHeightOffset) + textOffsetRectInsPt = dummy1[0] + textRot = dummy1[1] + textLinearDimComponentOn = dummy2[0] + txtLeaderLines = dummy2[1] + + # trovo il vero punto di inserimento del testo tenendo conto del buffer intorno + textInsPt = qad_utils.getPolarPointByPtAngle(textOffsetRectInsPt, textRot, self.textOffsetDist) + textInsPt = qad_utils.getPolarPointByPtAngle(textInsPt, textRot + math.pi / 2, self.textOffsetDist) + + # testo + textGeom = QgsGeometry.fromPointXY(textInsPt) + textFeature = self.getTextFeature(textValue, textInsPt, textRot) + + # creo un rettangolo intorno al testo con un offset + textOffsetRect = self.textRectToQadPolyline(textOffsetRectInsPt, textWidthOffset, textHeightOffset, textRot) + + lastLine = txtLeaderLines.getLinearObjectAt(-1) + lastLine, dummy = self.adjustLineAccordingTextRect(textOffsetRect, lastLine, QadDimComponentEnum.LEADER_LINE) + txtLeaderLines.remove(-1) # sostituisco l'ultimo elemento + txtLeaderLines.append(lastLine) + + # linea di leader + txtLeaderLineFeature = self.getLeaderFeature(txtLeaderLines) + + dimEntity = QadDimEntity() + dimEntity.dimStyle = self + # features testuali + dimEntity.textualFeature = textFeature + # features lineari + if dimLineFeature is not None: + dimEntity.linearFeatures.append(dimLineFeature) + if extLineFeature is not None: + dimEntity.linearFeatures.append(extLineFeature) + if txtLeaderLineFeature is not None: + dimEntity.linearFeatures.append(txtLeaderLineFeature) + for dimCenterMarker in dimCenterMarkers: + dimEntity.linearFeatures.append(dimCenterMarker) + # features puntuali + dimEntity.symbolFeatures.extend([dimPt1Feature, dimPt2Feature]) + if blockFeature is not None: + dimEntity.symbolFeatures.append(blockFeature) + + return dimEntity, QgsGeometry.fromPolygonXY([textOffsetRect.asPolyline()]) + + + # ============================================================================ + # addRadiusDimToLayers + # ============================================================================ + def addRadiusDimToLayers(self, plugIn, dimObj, linePosPt, measure = None): + """ + dimObj = oggetto arco circle da quotare (in unita di mappa) + linePosPt = punto per indicare dove deve essere posizionata la linea di quota (in unita di mappa) + measure = indica se la misura é predeterminata oppure (se = None) deve essere calcolata + + Aggiunge ai layers le features che compongono una quota allineata. + """ + dimEntity, textOffsetRect = self.getRadiusDimFeatures(plugIn.canvas, \ + dimObj, \ + linePosPt, \ + measure) + + return self.addDimEntityToLayers(plugIn, dimEntity) + + + # ============================================================================ + # FUNZIONI PER LA QUOTATURA RAGGIO - FINE + # ============================================================================ + + +# =============================================================================== +# QadDimStylesClass list of dimension styles +# =============================================================================== +class QadDimStylesClass(): + + def __init__(self, dimStyleList = None): + if dimStyleList is None: + self.dimStyleList = [] + else: + self.set(dimStyleList) + + + def __del__(self): + if dimStyleList is None: + del self.dimStyleList[:] + + + def isEmpty(self): + return True if self.count() == 0 else False + + + def count(self): + return len(self.dimStyleList) + + + def clear(self): + del self.dimStyleList[:] + + + def findDimStyle(self, dimStyleName): + """ + La funzione, dato un nome di stile di quotatura, lo cerca nella lista e, + in caso di successo, restituisce lo stile di quotatura. + """ + for dimStyle in self.dimStyleList: + if dimStyle.name == dimStyleName: + return dimStyle + return None + + + def addDimStyle(self, dimStyle, toFile = False, filePath = ""): + d = self.findDimStyle(dimStyle) + if d is None: + self.dimStyleList.append(QadDimStyle(dimStyle)) + if toFile: + if dimStyle.save(filePath, False) == False: # senza sovrascrivere file + return False + return True + + return False + + + # ============================================================================ + # removeDimStyle + # ============================================================================ + def removeDimStyle(self, dimStyleName, toFile = False): + i = 0 + for dimStyle in self.dimStyleList: + if dimStyle.name == dimStyleName: + del self.dimStyleList[i] + if toFile: + dimStyle.remove() + return True + else: + i = i + 1 + + return False + + + # ============================================================================ + # renameDimStyle + # ============================================================================ + def renameDimStyle(self, dimStyleName, newDimStyleName): + if dimStyleName == newDimStyleName: # nome uguale + return True + + if self.findDimStyle(newDimStyleName) is not None: + return False + dimStyle = self.findDimStyle(dimStyleName) + if dimStyle is None: + return False + return dimStyle.rename(newDimStyleName) + + + # ============================================================================ + # load + # ============================================================================ + def load(self, dir = None, append = False): + """ + Carica le impostazioni di tutti gli stili di quotatura presenti nella directory indicata. + se dir = None se esiste un progetto caricato il percorso è quello del progetto altrimenti + il percorso locale di qad + """ + if dir is None: + if append == False: + self.clear() + + # se esiste un progetto caricato il percorso è quello del progetto + prjFileInfo = QFileInfo(QgsProject.instance().fileName()) + path = prjFileInfo.absolutePath() + if len(path) > 0: + path += "/;" + path += QgsApplication.qgisSettingsDirPath() + "python/plugins/qad/" + + # lista di directory separate da ";" + dirList = path.strip().split(";") + for _dir in dirList: + self.load(_dir, True) # in append + else: + _dir = QDir.cleanPath(dir) + if _dir == "": + return False + + if _dir.endswith("/") == False: + _dir = _dir + "/" + + if not os.path.exists(_dir): + return False + + if append == False: + self.clear() + dimStyle = QadDimStyle() + + fileNames = os.listdir(_dir) + for fileName in fileNames: + if fileName.endswith(".dim"): + path = _dir + fileName + if dimStyle.load(path) == True: + if self.findDimStyle(dimStyle.name) is None: + self.addDimStyle(dimStyle) + + return True + + + # ============================================================================ + # getDimIdByEntity + # ============================================================================ + def getDimIdByEntity(self, entity): + """ + La funzione, data un'entità, verifica se fa parte di uno stile di quotatura della lista e, + in caso di successo, restituisce lo stile di quotatura e il codice della quotatura altrimenti None, None. + """ + for dimStyle in self.dimStyleList: + dimId = dimStyle.getDimIdByEntity(entity) + if dimId is not None: + return dimStyle, dimId + return None, None + + + # ============================================================================ + # isDimEntity + # ============================================================================ + def isDimEntity(self, entity): + """ + La funzione, data un'entità, verifica se fa parte di uno stile di quotatura della lista e, + in caso di successo, restituisce true altrimenti False. + """ + dimStyle, dimId = self.getDimIdByEntity(entity) + if dimStyle is None or dimId is None: + return False + else: + return True + + + # ============================================================================ + # getDimEntity + # ============================================================================ + def getDimEntity(self, layer, fid = None): + """ + la funzione può essere richiamata in 2 modi: + con un solo parametro di tipo QadEntity + con due parametri, il primo QgsVectorLayer e il secondo l'id della feature + """ + # verifico se l'entità appartiene ad uno stile di quotatura + if isinstance(layer, QgsVectorLayer): + entity = QadEntity() + entity.set(layer, fid) + dimStyle, dimId = self.getDimIdByEntity(entity) + else: # il parametro layer puo essere un oggetto QadEntity + dimStyle, dimId = self.getDimIdByEntity(layer) + + if (dimStyle is None) or (dimId is None): + return None + + dimEntity = QadDimEntity() + if dimEntity.initByDimId(dimStyle, dimId) == False: + return None + + return dimEntity + + + # ============================================================================ + # getDimListByLayer + # ============================================================================ + def getDimListByLayer(self, layer): + """ + La funzione, dato un layer, verifica se fa parte di uno o più stili di quotatura della lista e, + in caso di successo, restituisce la lista degli stili di quotatura di appartenenza. + """ + result = [] + for dimStyle in self.dimStyleList: + if dimStyle.isDimLayer(layer): + if dimStyle not in result: + result.append(dimStyle) + + return result + + + # ============================================================================ + # addAllDimComponentsToEntitySet + # ============================================================================ + def addAllDimComponentsToEntitySet(self, entitySet, onlyEditableLayers): + """ + La funzione verifica se le entità che fanno parte di un entitySet sono anche parte di quotatura e, + in caso affermativo, aggiunge tutti i componenti della quotatura all'entitySet. + """ + elaboratedDimEntitySet = QadEntitySet() # lista delle entità di quota elaborate + entity = QadEntity() + for layerEntitySet in entitySet.layerEntitySetList: + # verifico se il layer appartiene ad uno o più stili di quotatura + dimStyleList = self.getDimListByLayer(layerEntitySet.layer) + for dimStyle in dimStyleList: # per tutti gli stili di quotatura + if dimStyle is not None: + remove = False + if onlyEditableLayers == True: + # se anche un solo layer non é modificabile + if dimStyle.getTextualLayer().isEditable() == False or \ + dimStyle.getSymbolLayer().isEditable() == False or \ + dimStyle.getLinearLayer().isEditable() == False: + remove = True + features = layerEntitySet.getFeatureCollection() + for feature in features: + entity.set(layerEntitySet.layer, feature.id()) + if not elaboratedDimEntitySet.containsEntity(entity): + dimId = dimStyle.getDimIdByEntity(entity) + if dimId is not None: + dimEntitySet = dimStyle.getEntitySet(dimId) + if remove == False: + entitySet.unite(dimEntitySet) + else: + entitySet.subtract(dimEntitySet) + + elaboratedDimEntitySet.unite(dimEntitySet) + + + # ============================================================================ + # removeAllDimLayersFromEntitySet + # ============================================================================ + def removeAllDimLayersFromEntitySet(self, entitySet): + """ + La funzione rimuove tutte le entità che fanno parte di quotature dall'entitySet. + """ + for dimStyle in self.dimStyleList: + entitySet.removeLayerEntitySet(dimStyle.getTextualLayer()) + entitySet.removeLayerEntitySet(dimStyle.getSymbolLayer()) + entitySet.removeLayerEntitySet(dimStyle.getLinearLayer()) + + +# =============================================================================== +# QadDimEntity dimension entity class +# =============================================================================== +class QadDimEntity(): + + # ============================================================================ + # __init__ + # ============================================================================ + def __init__(self, dimEntity = None): + self.dimStyle = None + self.textualFeature = None + self.linearFeatures = [] + self.symbolFeatures = [] + + if dimEntity is not None: + self.set(dimEntity) + + + def whatIs(self): + return "DIMENTITY" + + + def isInitialized(self): + if (self.dimStyle is None) or (self.textualFeature is None): + return False + else: + return True + + + def __eq__(self, dimEntity): + """self == other""" + if self.isInitialized() == False or dimEntity.isInitialized() == False : + return False + + if self.getTextualLayer() == dimEntity.getTextualLayer() and self.getDimId() == dimEntity.getDimId(): + return True + else: + return False + + + # ============================================================================ + # isValid + # ============================================================================ + def isValid(self): + """ + Verifica se lo stile di quotatura é valido e in caso affermativo ritorna True. + Se la quotatura non é valida ritorna False. + """ + if self.dimStyle is None: + return False + return self.dimStyle.isValid() + + + # ============================================================================ + # getTextualLayer + # ============================================================================ + def getTextualLayer(self): + if self.dimStyle is None: + return None + return self.dimStyle.getTextualLayer() + + + # ============================================================================ + # getLinearLayer + # ============================================================================ + def getLinearLayer(self): + if self.dimStyle is None: + return None + return self.dimStyle.getLinearLayer() + + + # ============================================================================ + # getSymbolLayer + # ============================================================================ + def getSymbolLayer(self): + if self.dimStyle is None: + return None + return self.dimStyle.getSymbolLayer() + + + # ============================================================================ + # set + # ============================================================================ + def set(self, dimEntity): + self.dimStyle = QadDimStyle(dimEntity.dimStyle) + + self.textualFeature = QgsFeature(dimEntity.textualFeature) + + del self.linearFeatures[:] + for f in dimEntity.linearFeatures: + self.linearFeatures.append(QgsFeature(f)) + + del self.symbolFeatures[:] + for f in dimEntity.symbolFeatures: + self.symbolFeatures.append(QgsFeature(f)) + + + # ============================================================================ + # getLinearGeometryCollection + # ============================================================================ + def getLinearGeometryCollection(self): + result = [] + for f in self.linearFeatures: + result.append(f.geometry()) + return result + + + # ============================================================================ + # getSymbolGeometryCollection + # ============================================================================ + def getSymbolGeometryCollection(self): + result = [] + for f in self.symbolFeatures: + result.append(f.geometry()) + return result + + + # ============================================================================ + # getDimId + # ============================================================================ + def getDimId(self): + """ + La funzione restituisce il codice della quotatura altrimenti None. + """ + try: + return self.textualFeature.attribute(self.idFieldName) + except: + return None + + + def recodeDimIdToFeature(self, newDimId): + try: + # imposto il codice della quota + self.textualFeature.setAttribute(self.dimStyle.idFieldName, newDimId) + for f in self.linearFeatures: + f.setAttribute(self.dimStyle.idParentFieldName, newDimId) + for f in self.symbolFeatures: + f.setAttribute(self.dimStyle.idParentFieldName, newDimId) + except: + return False + + return True + + + # ============================================================================ + # addToLayers + # ============================================================================ + def addToLayers(self, plugIn): + # prima di tutto inserisco il testo di quota per ricodificare la quotatura + # plugIn, layer, feature, coordTransform, refresh, check_validity + if qad_layer.addFeatureToLayer(plugIn, self.getTextualLayer(), self.textualFeature, None, False, False, False) == False: + return False + newDimId = self.textualFeature.id() + + if self.recodeDimIdToFeature(newDimId) == False: + return False + + # plugIn, layer, feature, refresh, check_validity + if qad_layer.updateFeatureToLayer(plugIn, self.getTextualLayer(), self.textualFeature, False, False) == False: + return False + # plugIn, layer, features, coordTransform, refresh, check_validity + if qad_layer.addFeaturesToLayer(plugIn, self.getLinearLayer(), self.linearFeatures, None, False, False) == False: + return False + # plugIn, layer, features, coordTransform, refresh, check_validity + if qad_layer.addFeaturesToLayer(plugIn, self.getSymbolLayer(), self.symbolFeatures, None, False, False) == False: + return False + + return True + + + # ============================================================================ + # deleteToLayers + # ============================================================================ + def deleteToLayers(self, plugIn): + ids =[] + + # plugIn, layer, featureId, refresh + if qad_layer.deleteFeatureToLayer(plugIn, self.getTextualLayer(), self.textualFeature.id(), False) == False: + return False + + for f in self.linearFeatures: + ids.append(f.id()) + # plugIn, layer, featureIds, refresh + if qad_layer.deleteFeaturesToLayer(plugIn, self.getLinearLayer(), ids, False) == False: + return False + + del ids[:] + for f in self.symbolFeatures: + ids.append(f.id()) + # plugIn, layer, featureIds, refresh + if qad_layer.deleteFeaturesToLayer(plugIn, self.getSymbolLayer(), ids, False) == False: + return False + + return True + + + # ============================================================================ + # initByEntity + # ============================================================================ + def initByEntity(self, dimStyle, entity): + dimId = dimStyle.getDimIdByEntity(entity) + if dimId is None: + return False + return self.initByDimId(dimStyle, dimId) + + + # ============================================================================ + # initByDimId + # ============================================================================ + def initByDimId(self, dimStyle, dimId): + self.dimStyle = QadDimStyle(dimStyle) + entitySet = self.dimStyle.getEntitySet(dimId) + if entitySet.count() == 0: return False + + self.textualFeature = None + layerEntitySet = entitySet.findLayerEntitySet(self.getTextualLayer()) + if layerEntitySet is not None: + features = layerEntitySet.getFeatureCollection() + self.textualFeature = features[0] + + # entità lineari + layerEntitySet = entitySet.findLayerEntitySet(self.getLinearLayer()) + del self.linearFeatures[:] # svuoto la lista + if layerEntitySet is not None: + self.linearFeatures = layerEntitySet.getFeatureCollection() + + # entità puntuali + layerEntitySet = entitySet.findLayerEntitySet(self.getSymbolLayer()) + del self.symbolFeatures[:] # svuoto la lista + if layerEntitySet is not None: + self.symbolFeatures = layerEntitySet.getFeatureCollection() + + return True + + + # ============================================================================ + # getEntitySet + # ============================================================================ + def getEntitySet(self): + result = QadEntitySet() + + if self.isValid() == False: return result; + + layerEntitySet = QadLayerEntitySet() + layerEntitySet.set(self.getTextualLayer(), [self.textualFeature]) + result.addLayerEntitySet(layerEntitySet) + + layerEntitySet = QadLayerEntitySet() + layerEntitySet.set(self.getLinearLayer(), self.linearFeatures) + result.addLayerEntitySet(layerEntitySet) + + layerEntitySet = QadLayerEntitySet() + layerEntitySet.set(self.getSymbolLayer(), self.symbolFeatures) + result.addLayerEntitySet(layerEntitySet) + + return result + + + # ============================================================================ + # selectOnLayer + # ============================================================================ + def selectOnLayer(self, incremental = True): + self.getEntitySet().selectOnLayer(incremental) + + + # ============================================================================ + # deselectOnLayer + # ============================================================================ + def deselectOnLayer(self): + self.getEntitySet().deselectOnLayer() + + + # ============================================================================ + # getDimPts + # ============================================================================ + def getDimPts(self, destinationCrs = None): + """ + destinationCrs = sistema di coordinate in cui verrà restituito il risultato + """ + + dimPt1 = None + dimPt2 = None + + if len(self.dimStyle.componentFieldName) > 0: + # cerco tra gli elementi puntuali + for f in self.symbolFeatures: + try: + value = f.attribute(self.dimStyle.componentFieldName) + if value == QadDimComponentEnum.DIM_PT1: # primo punto da quotare ("Dimension point 1") + g = f.geometry() + if (destinationCrs is not None) and destinationCrs != self.getSymbolLayer().crs(): + g.transform(QgsCoordinateTransform(self.getSymbolLayer().crs(), \ + destinationCrs, + QgsProject.instance())) # trasformo la geometria in map coordinate + + dimPt1 = g.asPoint() + elif value == QadDimComponentEnum.DIM_PT2: # secondo punto da quotare ("Dimension point 2") + g = f.geometry() + if (destinationCrs is not None) and destinationCrs != self.getSymbolLayer().crs(): + g.transform(QgsCoordinateTransform(self.getSymbolLayer().crs(), \ + destinationCrs, + QgsProject.instance())) # trasformo la geometria in map coordinate + + dimPt2 = g.asPoint() + except: + return None, None + + return QadPoint(dimPt1), QadPoint(dimPt2) + + + # ============================================================================ + # getDimLinePts + # ============================================================================ + def getDimLinePts(self, destinationCrs = None): + """ + destinationCrs = sistema di coordinate in cui verrà restituito il risultato + """ + dimLinePt1 = None + dimLinePt2 = None + # cerco i punti iniziale-finale della linea di quota + if len(self.dimStyle.componentFieldName) > 0: + # prima cerco tra gli elementi lineari + for f in self.linearFeatures: + try: + value = f.attribute(self.dimStyle.componentFieldName) + # primo punto da quotare ("Dimension point 1") o secondo punto da quotare ("Dimension point 2") + if value == QadDimComponentEnum.DIM_LINE1 or value == QadDimComponentEnum.DIM_LINE2: + g = f.geometry() + + if (destinationCrs is not None) and destinationCrs != self.getSymbolLayer().crs(): + g.transform(QgsCoordinateTransform(self.getLinearLayer().crs(), \ + destinationCrs, \ + QgsProject.instance())) # trasformo la geometria in map coordinate + + pts = qad_utils.asPointOrPolyline(g)[0].asPolyline() + if value == QadDimComponentEnum.DIM_LINE1: + dimLinePt1 = pts[0] + else: + dimLinePt2 = pts[-1] + + except: + return None, None + + if dimLinePt1 is None or dimLinePt2 is None: + # poi cerco tra gli elementi puntuali + for f in self.symbolFeatures: + try: + value = f.attribute(self.dimStyle.componentFieldName) + # primo blocco della freccia ("Block 1") + if dimLinePt1 is None and value == QadDimComponentEnum.BLOCK1: + g = f.geometry() + + if (destinationCrs is not None) and destinationCrs != self.getSymbolLayer().crs(): + g.transform(QgsCoordinateTransform(self.getSymbolLayer().crs(), \ + destinationCrs, \ + QgsProject.instance())) # trasformo la geometria in map coordinate + + dimLinePt1 = g.asPoint() + + # secondo blocco della freccia ("Block 2") + if dimLinePt2 is None and value == QadDimComponentEnum.BLOCK2: + g = f.geometry() + + if (destinationCrs is not None) and destinationCrs != self.getSymbolLayer().crs(): + g.transform(QgsCoordinateTransform(self.getSymbolLayer().crs(), \ + destinationCrs, \ + QgsProject.instance())) # trasformo la geometria in map coordinate + + dimLinePt2 = g.asPoint() + + except: + return None, None + + return dimLinePt1, dimLinePt2 + + + # ============================================================================ + # getDimArc + # ============================================================================ + def getDimArc(self, destinationCrs = None): + """ + destinationCrs = sistema di coordinate in cui verrà restituito il risultato + """ + # cerco i punti di quotatura + dimPt1, dimPt2 = self.getDimPts(destinationCrs) + if dimPt1 is None or dimPt2 is None: return None + + # cerco il punto iniziale e finale della linea di quota + dimLinePt1, dimLinePt2 = self.getDimLinePts(destinationCrs) + if dimLinePt1 is None or dimLinePt2 is None: return None + + ang1 = qad_utils.normalizeAngle(qad_utils.getAngleBy2Pts(dimPt1, dimLinePt1)) + ang2 = qad_utils.normalizeAngle(qad_utils.getAngleBy2Pts(dimLinePt2, dimPt2)) + if qad_utils.TanDirectionNear(ang1, ang2) == True: # arco di 180 gradi + ptCenter = qad_utils.getMiddlePoint(dimPt1, dimPt2) + else: + ptCenter = qad_utils.getIntersectionPointOn2InfinityLines(dimPt1, dimLinePt1, dimPt2, dimLinePt2) + + arc = QadArc() + if arc.fromStartCenterEndPts(dimPt1, ptCenter, dimPt2) == False: + return None + + return arc + + + # ============================================================================ + # getDimLeaderLine + # ============================================================================ + def getDimLeaderLine(self, leaderLineType = None, destinationCrs = None): + """ + Trova la linea porta quota del tipo indicato (in destinationCrs tipicamente = map coordinate) + destinationCrs = sistema di coordinate in cui è espresso containerGeom e in cui verrà restituito il risultato + """ + if len(self.dimStyle.componentFieldName) > 0: + # prima cerco tra gli elementi lineari + for f in self.linearFeatures: + try: + value = f.attribute(self.dimStyle.componentFieldName) + if value == leaderLineType: + g = f.geometry() + + if (destinationCrs is not None) and destinationCrs != self.getSymbolLayer().crs(): + g.transform(QgsCoordinateTransform(self.getLinearLayer().crs(), \ + destinationCrs, \ + QgsProject.instance())) # trasformo la geometria in map coordinate + + return qad_utils.asPointOrPolyline(g)[0].asPolyline() + except: + return None + + return None + + + # ============================================================================ + # getDimLinePosPt + # ============================================================================ + def getDimLinePosPt(self, containerGeom = None, destinationCrs = None): + """ + Trova fra i vari punti possibili un punto che indichi dove si trova la linea di quota (in destinationCrs tipicamente = map coordinate) + se containerGeom <> None il punto deve essere contenuto in containerGeom + containerGeom = può essere una QgsGeometry rappresentante un poligono (in destinationCrs tipicamente = map coordinate) contenente i punti di geom da stirare + oppure una lista dei punti da stirare (in destinationCrs tipicamente = map coordinate) + destinationCrs = sistema di coordinate in cui è espresso containerGeom e in cui verrà restituito il risultato + """ + + if len(self.dimStyle.componentFieldName) > 0: + # prima cerco tra gli elementi lineari + for f in self.linearFeatures: + try: + value = f.attribute(self.dimStyle.componentFieldName) + # primo punto da quotare ("Dimension point 1") o secondo punto da quotare ("Dimension point 2") + if value == QadDimComponentEnum.DIM_LINE1 or value == QadDimComponentEnum.DIM_LINE2: + g = f.geometry() + + if (destinationCrs is not None) and destinationCrs != self.getSymbolLayer().crs(): + g.transform(QgsCoordinateTransform(self.getLinearLayer().crs(), \ + destinationCrs, \ + QgsProject.instance())) # trasformo la geometria in map coordinate + + pts = qad_utils.asPointOrPolyline(g)[0].asPolyline() + if containerGeom is not None: # verifico che il punto iniziale sia interno a containerGeom + if type(containerGeom) == QgsGeometry: # geometria + if containerGeom.contains(pts[0]) == True: + return QadPoint(pts[0]) + else: + # verifico che il punto finale sia interno a containerGeom + if containerGeom.contains(pts[-1]) == True: + return QadPoint(pts[-1]) + elif type(containerGeom) == list: # lista di punti + for containerPt in containerGeom: + if qad_utils.ptNear(containerPt, pts[0]): # se i punti sono sufficientemente vicini + return QadPoint(pts[0]) + else: + # verifico il punto finale + if qad_utils.ptNear(containerPt,pts[-1]): + return QadPoint(pts[-1]) + else: + return QadPoint(pts[0]) # punto iniziale + except: + return None + + # poi cerco tra gli elementi puntuali + for f in self.symbolFeatures: + try: + value = f.attribute(self.dimStyle.componentFieldName) + # primo blocco della freccia ("Block 1") o secondo blocco della freccia ("Block 2") + if value == QadDimComponentEnum.BLOCK1 or value == QadDimComponentEnum.BLOCK2: + g = f.geometry() + + if (destinationCrs is not None) and destinationCrs != self.getSymbolLayer().crs(): + g.transform(QgsCoordinateTransform(self.getSymbolLayer().crs(), \ + destinationCrs, \ + QgsProject.instance())) # trasformo la geometria in map coordinate + + dimLinePosPt = g.asPoint() + if containerGeom is not None: # verifico che il punto sia interno a containerGeom + if type(containerGeom) == QgsGeometry: # geometria + if containerGeom.contains(dimLinePosPt) == True: + return QadPoint(dimLinePosPt) + elif type(containerGeom) == list: # lista di punti + for containerPt in containerGeom: + if ptNear(containerPt, dimLinePosPt): # se i punti sono sufficientemente vicini + return QadPoint(dimLinePosPt) + else: + return QadPoint(dimLinePosPt) + except: + return None + + return None + + + # ============================================================================ + # getDimLinearAlignment + # ============================================================================ + def getDimLinearAlignment(self): + dimLinearAlignment = None + dimLineRotation = None + Pts = [] + + if len(self.dimStyle.componentFieldName) > 0: + # prima cerco tra gli elementi lineari + for f in self.linearFeatures: + try: + value = f.attribute(self.dimStyle.componentFieldName) + if value == QadDimComponentEnum.DIM_LINE1: # primo punto da quotare ("Dimension point 1") + Pts = qad_utils.asPointOrPolyline(f.geometry())[0].asPolyline() + break + elif value == QadDimComponentEnum.DIM_LINE2: # secondo punto da quotare ("Dimension point 2") + Pts = qad_utils.asPointOrPolyline(f.geometry())[0].asPolyline() + break + except: + return None, None + + if Pts is None: + # poi cerco tra gli elementi puntuali + for f in self.symbolFeatures: + try: + value = f.attribute(self.dimStyle.componentFieldName) + if value == QadDimComponentEnum.BLOCK1: # primo blocco della freccia ("Block 1") + Pts.append(f.geometry().asPoint()) + elif value == QadDimComponentEnum.BLOCK2: # secondo blocco della freccia ("Block 1") + Pts.append(f.geometry().asPoint()) + except: + return None, None + + if len(Pts) > 1: # almeno 2 punti + if qad_utils.doubleNear(Pts[0].x(), Pts[-1].x()): # linea verticale (stessa x) + dimLinearAlignment = QadDimStyleAlignmentEnum.VERTICAL + dimLineRotation = 0 + elif qad_utils.doubleNear(Pts[0].y(), Pts[-1].y()): # linea orizzontale (stessa y) + dimLinearAlignment = QadDimStyleAlignmentEnum.HORIZONTAL + dimLineRotation = 0 + else: + dimLinearAlignment = QadDimStyleAlignmentEnum.HORIZONTAL + dimLineRotation = qad_utils.getAngleBy2Pts(Pts[0], Pts[-1]) + + + return dimLinearAlignment, dimLineRotation + + + # ============================================================================ + # getDimCircle + # ============================================================================ + def getDimCircle(self, destinationCrs = None): + """ + destinationCrs = sistema di coordinate in cui verrà restituito il risultato + Ritorna un cerchio a cui si riferisce la quotatura DIMRADIUS + """ + # cerco i punti di quotatura + dimPt1, dimPt2 = self.getDimPts(destinationCrs) + if dimPt1 is None or dimPt2 is None: return None + + circle = QadCircle() + circle.center = dimPt1 + circle.radius = qad_utils.getDistance(dimPt1, dimPt2) + + return circle + + + # ============================================================================ + # getTextRot + # ============================================================================ + def getTextRot(self): + textRot = None + + if len(self.dimStyle.rotFieldName) > 0: + try: + textRot = self.textualFeature.attribute(self.dimStyle.rotFieldName) + except: + return None + + return qad_utils.toRadians(textRot) + + + # ============================================================================ + # getTextValue + # ============================================================================ + def getTextValue(self): + textValue = None + + if self.dimStyle.getTextualLayer() is None: + return None; + + # se il testo dipende da un solo campo + labelFieldNames = qad_label.get_labelFieldNames(self.dimStyle.getTextualLayer()) + if len(labelFieldNames) == 1 and len(labelFieldNames[0]) > 0: + try: + textValue = self.textualFeature.attribute(labelFieldNames[0]) + except: + return None + + return textValue + + + # ============================================================================ + # getTextPt + # ============================================================================ + def getTextPt(self, destinationCrs = None): + # destinationCrs = sistema di coordinate in cui verrà restituito il risultato + g = self.textualFeature.geometry() + if (destinationCrs is not None) and destinationCrs != self.getTextualLayer().crs(): + g.transform(QgsCoordinateTransform(self.getTextualLayer().crs(), \ + destinationCrs, + QgsProject.instance())) # trasformo la geometria in map coordinate + + return g.asPoint() + + + # ============================================================================ + # isCalculatedText + # ============================================================================ + def isCalculatedText(self): + # la funzione verifica se il testo della quota è calcolato dalla grafica o se è stato forzato un testo diverso + measure = self.getTextValue() + + if self.dimStyle.dimType == QadDimTypeEnum.ALIGNED: # quota lineare allineata ai punti di origine delle linee di estensione + dimPt1, dimPt2 = self.getDimPts() + return measure == self.dimStyle.getFormattedText(qad_utils.getDistance(dimPt1, dimPt2)) + elif self.dimStyle.dimType == QadDimTypeEnum.LINEAR: # quota lineare con una linea di quota orizzontale o verticale + dimPt1, dimPt2 = self.getDimPts() + linePosPt = self.getDimLinePosPt() + preferredAlignment, dimLineRotation = self.getDimLinearAlignment() + + # linea di quota entro le linee di estensione + dimLine = self.dimStyle.getDimLine(dimPt1, dimPt2, linePosPt, preferredAlignment, dimLineRotation) + if dimLine is None: return False + return measure == self.dimStyle.getFormattedText(dimLine.length()) + elif self.dimStyle.dimType == QadDimTypeEnum.ARC_LENTGH: # quota per la lunghezza di un arco + dimArc = self.getDimArc() + if dimArc is None: return False + return measure == self.dimStyle.getFormattedText(dimArc.length()) + elif self.dimStyle.dimType == QadDimTypeEnum.RADIUS: # quota radiale, misura il raggio di un cerchio o di un arco + dimPt1, dimPt2 = self.getDimPts() + return measure == self.dimStyle.getFormattedText(qad_utils.getDistance(dimPt1, dimPt2)) + + return True + + + # ============================================================================ + # isCalculatedTextRot + # ============================================================================ + def isCalculatedTextRot(self): + # la funzione verifica se la rotazione del testo della quota è calcolato dalla grafica o se è stato forzato una rotazione diversa + measure = self.getTextValue() + txtRot = self.getTextRot() + canvas = qgis.utils.iface.mapCanvas() + destinationCrs = canvas.mapSettings().destinationCrs() + + if self.dimStyle.dimType == QadDimTypeEnum.ALIGNED: # quota lineare allineata ai punti di origine delle linee di estensione + dimPt1, dimPt2 = self.getDimPts(destinationCrs) + linePosPt = self.getDimLinePosPt(None, destinationCrs) + if (dimPt1 is not None) and (dimPt2 is not None) and \ + (linePosPt is not None): + dimEntity, textOffsetRect = self.dimStyle.getAlignedDimFeatures(canvas, \ + dimPt1, \ + dimPt2, \ + linePosPt, \ + measure) + return txtRot == dimEntity.getTextRot() + + elif self.dimStyle.dimType == QadDimTypeEnum.LINEAR: # quota lineare con una linea di quota orizzontale o verticale + dimPt1, dimPt2 = self.getDimPts(destinationCrs) + linePosPt = self.getDimLinePosPt(None, destinationCrs) + dimLinearAlignment, dimLineRotation = self.getDimLinearAlignment() + + if (dimPt1 is not None) and (dimPt2 is not None) and \ + (linePosPt is not None) and \ + (dimLinearAlignment is not None) and (dimLineRotation is not None): + + if dimLinearAlignment == QadDimStyleAlignmentEnum.VERTICAL: + dimLineRotation = math.pi / 2 + dimLinearAlignment = QadDimStyleAlignmentEnum.HORIZONTAL + + dimEntity, textOffsetRect = self.dimStyle.getLinearDimFeatures(canvas, \ + dimPt1, \ + dimPt2, \ + linePosPt, \ + measure, \ + dimLinearAlignment, \ + dimLineRotation) + return txtRot == dimEntity.getTextRot() + + elif self.dimStyle.dimType == QadDimTypeEnum.ARC_LENTGH: # quota per la lunghezza di un arco + dimArc = self.getDimArc() + linePosPt = self.getDimLinePosPt(None, destinationCrs) + + if (dimArc is not None) and (linePosPt is not None): + dimEntity, textOffsetRect = self.dimStyle.getArcDimFeatures(canvas, dimArc, linePosPt, measure) + return txtRot == dimEntity.getTextRot() + + elif self.dimStyle.dimType == QadDimTypeEnum.RADIUS: # quota radiale, misura il raggio di un cerchio o di un arco + dimCircle = self.getDimCircle() + linePosPt = self.getDimLinePosPt(None, destinationCrs) + + if (dimCircle is not None) and (linePosPt is not None): + dimEntity, textOffsetRect = self.dimStyle.getRadiusDimFeatures(canvas, dimCircle, linePosPt, measure) + return txtRot == dimEntity.getTextRot() + + return True + + + # ============================================================================ + # move + # ============================================================================ + def move(self, offsetX, offsetY): + # offsetX = spostamento X in map coordinate + # offsetY = spostamento Y in map coordinate + if self.isValid() == False: return False; + + canvas = qgis.utils.iface.mapCanvas() + destinationCrs = canvas.mapSettings().destinationCrs() + + g = self.textualFeature.geometry() + qadGeom = fromQgsGeomToQadGeom(g, self.getTextualLayer().crs()) + qadGeom.move(offsetX, offsetY) + g = fromQadGeomToQgsGeom(qadGeom, self.getTextualLayer()) + self.textualFeature.setGeometry(g) + + for f in self.linearFeatures: + g = f.geometry() + qadGeom = fromQgsGeomToQadGeom(g, self.getLinearLayer().crs()) + qadGeom.move(offsetX, offsetY) + g = fromQadGeomToQgsGeom(qadGeom, self.getLinearLayer()) + f.setGeometry(g) + + for f in self.symbolFeatures: + g = f.geometry() + qadGeom = fromQgsGeomToQadGeom(g, self.getSymbolLayer().crs()) + qadGeom.move(offsetX, offsetY) + g = fromQadGeomToQgsGeom(qadGeom, self.getSymbolLayer()) + f.setGeometry(g) + + return False + + + # ============================================================================ + # rotate + # ============================================================================ + def rotate(self, basePt, angle): + # basePt = punto base espresso in map coordinate + if self.isValid() == False: return False; + + canvas = qgis.utils.iface.mapCanvas() + destinationCrs = canvas.mapSettings().destinationCrs() + + measure = None if self.isCalculatedText() else self.getTextValue() + textRot = None if self.isCalculatedTextRot() else self.getTextRot() + + if textRot is not None: # se la rotazione era forzata allora la imposto + prevTextRotMode = self.dimStyle.textRotMode + self.dimStyle.textRotMode = QadDimStyleTxtRotModeEnum.FORCED_ROTATION + self.dimStyle.textForcedRot = textRot + + if self.dimStyle.dimType == QadDimTypeEnum.ALIGNED: # quota lineare allineata ai punti di origine delle linee di estensione + dimPt1, dimPt2 = self.getDimPts(destinationCrs) + linePosPt = self.getDimLinePosPt(None, destinationCrs) + if (dimPt1 is not None) and (dimPt2 is not None) and \ + (linePosPt is not None): + dimPt1 = qad_utils.rotatePoint(dimPt1, basePt, angle) + dimPt2 = qad_utils.rotatePoint(dimPt2, basePt, angle) + linePosPt = qad_utils.rotatePoint(linePosPt, basePt, angle) + + dimEntity, textOffsetRect = self.dimStyle.getAlignedDimFeatures(canvas, \ + dimPt1, \ + dimPt2, \ + linePosPt, \ + measure) + self.set(dimEntity) + + elif self.dimStyle.dimType == QadDimTypeEnum.LINEAR: # quota lineare con una linea di quota orizzontale o verticale + dimPt1, dimPt2 = self.getDimPts(destinationCrs) + linePosPt = self.getDimLinePosPt(None, destinationCrs) + preferredAlignment, dimLineRotation = self.getDimLinearAlignment() + if (dimPt1 is not None) and (dimPt2 is not None) and \ + (linePosPt is not None) and \ + (preferredAlignment is not None) and (dimLineRotation is not None): + dimPt1 = qad_utils.rotatePoint(dimPt1, basePt, angle) + dimPt2 = qad_utils.rotatePoint(dimPt2, basePt, angle) + linePosPt = qad_utils.rotatePoint(linePosPt, basePt, angle) + dimLinearAlignment, dimLineRotation = self.getDimLinearAlignment() + + if dimLinearAlignment == QadDimStyleAlignmentEnum.VERTICAL: + dimLineRotation = math.pi / 2 + dimLinearAlignment = QadDimStyleAlignmentEnum.HORIZONTAL + dimLineRotation = dimLineRotation + angle + + dimEntity, textOffsetRect = self.dimStyle.getLinearDimFeatures(canvas, \ + dimPt1, \ + dimPt2, \ + linePosPt, \ + measure, \ + dimLinearAlignment, \ + dimLineRotation) + self.set(dimEntity) + + elif self.dimStyle.dimType == QadDimTypeEnum.ARC_LENTGH: # quota per la lunghezza di un arco + dimArc = self.getDimArc(destinationCrs) + linePosPt = self.getDimLinePosPt(None, destinationCrs) + if (dimArc is not None) and (linePosPt is not None): + dimArc.rotate(basePt, angle) + linePosPt = qad_utils.rotatePoint(linePosPt, basePt, angle) + arcLeader = True if self.getDimLeaderLine(QadDimComponentEnum.ARC_LEADER_LINE) is not None else False + + dimEntity, textOffsetRect = self.dimStyle.getArcDimFeatures(canvas, \ + dimArc, \ + linePosPt, \ + measure, \ + arcLeader) + self.set(dimEntity) + + elif self.dimStyle.dimType == QadDimTypeEnum.RADIUS: # quota radiale, misura il raggio di un cerchio o di un arco + # non si può fare perchè non si può sapere se la quota si riferiva ad un cerchio o ad un arco + # al momento ipotizzo si riferisca sempre ad un cerchio + dimCircle = self.getDimCircle() + linePosPt = self.getDimLinePosPt(None, destinationCrs) + + if (dimCircle is not None) and (linePosPt is not None): + dimCircle.rotate(basePt, angle) + linePosPt = qad_utils.rotatePoint(linePosPt, basePt, angle) + dimEntity, textOffsetRect = self.dimStyle.getRadiusDimFeatures(canvas, dimCircle, linePosPt, measure) + self.set(dimEntity) + + if textRot is not None: + self.dimStyle.textRotMode = prevTextRotMode # ripristino la situazione precedente + + return True + + + # ============================================================================ + # scale + # ============================================================================ + def scale(self, basePt, scale): + # basePt = punto base espresso in map coordinate + if self.isValid() == False: return False; + + canvas = qgis.utils.iface.mapCanvas() + destinationCrs = canvas.mapSettings().destinationCrs() + + measure = None if self.isCalculatedText() else self.getTextValue() + textRot = None if self.isCalculatedTextRot() else self.getTextRot() + + if textRot is not None: # se la rotazione era forzata allora la imposto + prevTextRotMode = self.dimStyle.textRotMode + self.dimStyle.textRotMode = QadDimStyleTxtRotModeEnum.FORCED_ROTATION + self.dimStyle.textForcedRot = textRot + + if self.dimStyle.dimType == QadDimTypeEnum.ALIGNED: # quota lineare allineata ai punti di origine delle linee di estensione + dimPt1, dimPt2 = self.getDimPts(destinationCrs) + linePosPt = self.getDimLinePosPt(None, destinationCrs) + if (dimPt1 is not None) and (dimPt2 is not None) and \ + (linePosPt is not None): + dimPt1 = qad_utils.scalePoint(dimPt1, basePt, scale) + dimPt2 = qad_utils.scalePoint(dimPt2, basePt, scale) + linePosPt = qad_utils.scalePoint(linePosPt, basePt, scale) + + dimEntity, textOffsetRect = self.dimStyle.getAlignedDimFeatures(canvas, \ + dimPt1, \ + dimPt2, \ + linePosPt, \ + measure) + self.set(dimEntity) + + elif self.dimStyle.dimType == QadDimTypeEnum.LINEAR: # quota lineare con una linea di quota orizzontale o verticale + dimPt1, dimPt2 = self.getDimPts(destinationCrs) + linePosPt = self.getDimLinePosPt(None, destinationCrs) + preferredAlignment, dimLineRotation = self.getDimLinearAlignment() + if (dimPt1 is not None) and (dimPt2 is not None) and \ + (linePosPt is not None) and \ + (preferredAlignment is not None) and (dimLineRotation is not None): + textForcedRot = self.getTextRot() + if textForcedRot is not None: + self.dimStyle.textForcedRot = textForcedRot + + dimPt1 = qad_utils.scalePoint(dimPt1, basePt, scale) + dimPt2 = qad_utils.scalePoint(dimPt2, basePt, scale) + linePosPt = qad_utils.scalePoint(linePosPt, basePt, scale) + dimLinearAlignment, dimLineRotation = self.getDimLinearAlignment() + + if dimLinearAlignment == QadDimStyleAlignmentEnum.VERTICAL: + dimLineRotation = math.pi / 2 + dimLinearAlignment = QadDimStyleAlignmentEnum.HORIZONTAL + + dimEntity, textOffsetRect = self.dimStyle.getLinearDimFeatures(canvas, \ + dimPt1, \ + dimPt2, \ + linePosPt, \ + measure, \ + dimLinearAlignment, \ + dimLineRotation) + self.set(dimEntity) + + elif self.dimStyle.dimType == QadDimTypeEnum.ARC_LENTGH: # quota per la lunghezza di un arco + dimArc = self.getDimArc(destinationCrs) + linePosPt = self.getDimLinePosPt(None, destinationCrs) + if (dimArc is not None) and \ + (linePosPt is not None): + dimArc.scale(basePt, scale) + linePosPt = qad_utils.scalePoint(linePosPt, basePt, scale) + arcLeader = True if self.getDimLeaderLine(QadDimComponentEnum.ARC_LEADER_LINE) is not None else False + + dimEntity, textOffsetRect = self.dimStyle.getArcDimFeatures(canvas, \ + dimArc, \ + linePosPt, \ + measure, \ + arcLeader) + self.set(dimEntity) + + elif self.dimStyle.dimType == QadDimTypeEnum.RADIUS: # quota radiale, misura il raggio di un cerchio o di un arco + # non si può fare perchè non si può sapere se la quota si riferiva ad un cerchio o ad un arco + # al momento ipotizzo si riferisca sempre ad un cerchio + dimCircle = self.getDimCircle() + linePosPt = self.getDimLinePosPt(None, destinationCrs) + + if (dimCircle is not None) and (linePosPt is not None): + dimCircle.scale(basePt, scale) + linePosPt = qad_utils.scalePoint(linePosPt, basePt, scale) + dimEntity, textOffsetRect = self.dimStyle.getRadiusDimFeatures(canvas, dimCircle, linePosPt, measure) + self.set(dimEntity) + + if textRot is not None: + self.dimStyle.textRotMode = prevTextRotMode # ripristino la situazione precedente + + return True + + + # ============================================================================ + # mirror + # ============================================================================ + def mirror(self, mirrorPt, mirrorAngle): + # mirrorPt = punto base espresso in map coordinate + if self.isValid() == False: return False; + + canvas = qgis.utils.iface.mapCanvas() + destinationCrs = canvas.mapSettings().destinationCrs() + + measure = None if self.isCalculatedText() else self.getTextValue() + textRot = None if self.isCalculatedTextRot() else self.getTextRot() + + if textRot is not None: # se la rotazione era forzata allora la imposto + prevTextRotMode = self.dimStyle.textRotMode + self.dimStyle.textRotMode = QadDimStyleTxtRotModeEnum.FORCED_ROTATION + self.dimStyle.textForcedRot = textRot + + if self.dimStyle.dimType == QadDimTypeEnum.ALIGNED: # quota lineare allineata ai punti di origine delle linee di estensione + dimPt1, dimPt2 = self.getDimPts(destinationCrs) + linePosPt = self.getDimLinePosPt(None, destinationCrs) + if (dimPt1 is not None) and (dimPt2 is not None) and \ + (linePosPt is not None): + dimPt1 = qad_utils.mirrorPoint(dimPt1, mirrorPt, mirrorAngle) + dimPt2 = qad_utils.mirrorPoint(dimPt2, mirrorPt, mirrorAngle) + linePosPt = qad_utils.mirrorPoint(linePosPt, mirrorPt, mirrorAngle) + + dimEntity, textOffsetRect = self.dimStyle.getAlignedDimFeatures(canvas, \ + dimPt1, \ + dimPt2, \ + linePosPt, \ + measure) + self.set(dimEntity) + + elif self.dimStyle.dimType == QadDimTypeEnum.LINEAR: # quota lineare con una linea di quota orizzontale o verticale + dimPt1, dimPt2 = self.getDimPts(destinationCrs) + linePosPt = self.getDimLinePosPt(None, destinationCrs) + preferredAlignment, dimLineRotation = self.getDimLinearAlignment() + if (dimPt1 is not None) and (dimPt2 is not None) and \ + (linePosPt is not None) and \ + (preferredAlignment is not None) and (dimLineRotation is not None): + textForcedRot = self.getTextRot() + if textForcedRot is not None: + self.dimStyle.textForcedRot = textForcedRot + + dimPt1 = qad_utils.mirrorPoint(dimPt1, mirrorPt, mirrorAngle) + dimPt2 = qad_utils.mirrorPoint(dimPt2, mirrorPt, mirrorAngle) + linePosPt = qad_utils.mirrorPoint(linePosPt, mirrorPt, mirrorAngle) + dimLinearAlignment, dimLineRotation = self.getDimLinearAlignment() + + if dimLinearAlignment == QadDimStyleAlignmentEnum.VERTICAL: + dimLineRotation = math.pi / 2 + dimLinearAlignment = QadDimStyleAlignmentEnum.HORIZONTAL + + ptDummy = qad_utils.getPolarPointByPtAngle(mirrorPt, dimLineRotation, 1) + ptDummy = qad_utils.mirrorPoint(ptDummy, mirrorPt, mirrorAngle) + dimLineRotation = qad_utils.getAngleBy2Pts(mirrorPt, ptDummy) + + dimEntity, textOffsetRect = self.dimStyle.getLinearDimFeatures(canvas, \ + dimPt1, \ + dimPt2, \ + linePosPt, \ + measure, \ + dimLinearAlignment, \ + dimLineRotation) + self.set(dimEntity) + + elif self.dimStyle.dimType == QadDimTypeEnum.ARC_LENTGH: # quota per la lunghezza di un arco + dimArc = self.getDimArc(destinationCrs) + linePosPt = self.getDimLinePosPt(None, destinationCrs) + if (dimArc is not None) and \ + (linePosPt is not None): + dimArc.mirror(mirrorPt, mirrorAngle) + linePosPt = qad_utils.mirrorPoint(linePosPt, mirrorPt, mirrorAngle) + arcLeader = True if self.getDimLeaderLine(QadDimComponentEnum.ARC_LEADER_LINE) is not None else False + + dimEntity, textOffsetRect = self.dimStyle.getArcDimFeatures(canvas, \ + dimArc, \ + linePosPt, \ + measure, \ + arcLeader) + self.set(dimEntity) + + elif self.dimStyle.dimType == QadDimTypeEnum.RADIUS: # quota radiale, misura il raggio di un cerchio o di un arco + # non si può fare perchè non si può sapere se la quota si riferiva ad un cerchio o ad un arco + # al momento ipotizzo si riferisca sempre ad un cerchio + dimCircle = self.getDimCircle() + linePosPt = self.getDimLinePosPt(None, destinationCrs) + + if (dimCircle is not None) and (linePosPt is not None): + dimCircle.mirror(mirrorPt, mirrorAngle) + linePosPt = qad_utils.mirrorPoint(linePosPt, mirrorPt, mirrorAngle) + dimEntity, textOffsetRect = self.dimStyle.getRadiusDimFeatures(canvas, dimCircle, linePosPt, measure) + self.set(dimEntity) + + if textRot is not None: + self.dimStyle.textRotMode = prevTextRotMode # ripristino la situazione precedente + + return True + + + # ============================================================================ + # stretch + # ============================================================================ + def stretch(self, containerGeom, offsetX, offsetY): + """ + containerGeom = può essere una QgsGeometry rappresentante un poligono contenente i punti di geom da stirare + oppure una lista dei punti da stirare espressi in map coordinate + offsetX = spostamento X in map coordinate + offsetY = spostamento Y in map coordinate + """ + if self.isValid() == False: return False; + + canvas = qgis.utils.iface.mapCanvas() + destinationCrs = canvas.mapSettings().destinationCrs() + + measure = None if self.isCalculatedText() else self.getTextValue() + textRot = None if self.isCalculatedTextRot() else self.getTextRot() + + if textRot is not None: # se la rotazione era forzata allora la imposto + prevTextRotMode = self.dimStyle.textRotMode + self.dimStyle.textRotMode = QadDimStyleTxtRotModeEnum.FORCED_ROTATION + self.dimStyle.textForcedRot = textRot + + if self.dimStyle.dimType == QadDimTypeEnum.ALIGNED: # quota lineare allineata ai punti di origine delle linee di estensione + dimPt1, dimPt2 = self.getDimPts(destinationCrs) + linePosPt = self.getDimLinePosPt(containerGeom, destinationCrs) + + if dimPt1 is not None: + newPt = qad_stretch_fun.stretchPoint(dimPt1, containerGeom, offsetX, offsetY) + if newPt is not None: + dimPt1 = newPt + + if dimPt2 is not None: + newPt = qad_stretch_fun.stretchPoint(dimPt2, containerGeom, offsetX, offsetY) + if newPt is not None: + dimPt2 = newPt + + if linePosPt is not None: + newPt = qad_stretch_fun.stretchPoint(linePosPt, containerGeom, offsetX, offsetY) + if newPt is not None: + linePosPt = newPt + else: + linePosPt = self.getDimLinePosPt(None, destinationCrs) + # verifico se è stato coinvolto il testo della quota + if qad_stretch_fun.isPtContainedForStretch(self.getTextPt(destinationCrs), containerGeom): + if linePosPt is not None: + linePosPt = qad_utils.movePoint(linePosPt, offsetX, offsetY) + + if (dimPt1 is not None) and (dimPt2 is not None) and \ + (linePosPt is not None): + dimEntity, textOffsetRect = self.dimStyle.getAlignedDimFeatures(canvas, \ + dimPt1, \ + dimPt2, \ + linePosPt, \ + measure) + self.set(dimEntity) + + elif self.dimStyle.dimType == QadDimTypeEnum.LINEAR: # quota lineare con una linea di quota orizzontale o verticale + dimPt1, dimPt2 = self.getDimPts(destinationCrs) + linePosPt = self.getDimLinePosPt(containerGeom, destinationCrs) + + dimLinearAlignment, dimLineRotation = self.getDimLinearAlignment() + + if dimPt1 is not None: + newPt = qad_stretch_fun.stretchPoint(dimPt1, containerGeom, offsetX, offsetY) + if newPt is not None: + dimPt1 = newPt + + if dimPt2 is not None: + newPt = qad_stretch_fun.stretchPoint(dimPt2, containerGeom, offsetX, offsetY) + if newPt is not None: + dimPt2 = newPt + + if linePosPt is not None: + newPt = qad_stretch_fun.stretchPoint(linePosPt, containerGeom, offsetX, offsetY) + if newPt is not None: + linePosPt = newPt + else: + linePosPt = self.getDimLinePosPt(None, destinationCrs) + # verifico se è stato coinvolto il testo della quota + if qad_stretch_fun.isPtContainedForStretch(self.getTextPt(destinationCrs), containerGeom): + if linePosPt is not None: + linePosPt = qad_utils.movePoint(linePosPt, offsetX, offsetY) + + if (dimPt1 is not None) and (dimPt2 is not None) and \ + (linePosPt is not None) and \ + (dimLinearAlignment is not None) and (dimLineRotation is not None): + if dimLinearAlignment == QadDimStyleAlignmentEnum.VERTICAL: + dimLineRotation = math.pi / 2 + dimLinearAlignment = QadDimStyleAlignmentEnum.HORIZONTAL + + dimEntity, textOffsetRect = self.dimStyle.getLinearDimFeatures(canvas, \ + dimPt1, \ + dimPt2, \ + linePosPt, \ + measure, \ + dimLinearAlignment, \ + dimLineRotation) + self.set(dimEntity) + + elif self.dimStyle.dimType == QadDimTypeEnum.ARC_LENTGH: # quota per la lunghezza di un arco + dimArc = self.getDimArc(destinationCrs) + linePosPt = self.getDimLinePosPt(containerGeom, destinationCrs) + + if dimArc is not None: + dimArc = qad_stretch_fun.stretchQadGeometry(dimArc, containerGeom, \ + offsetX, offsetY) + + if linePosPt is not None: + newPt = qad_utils.movePoint(linePosPt, offsetX, offsetY) + linePosPt = qad_utils.getPolarPointBy2Pts(dimArc.center, linePosPt, qad_utils.getDistance(dimArc.center, newPt)) + else: + linePosPt = self.getDimLinePosPt(None, destinationCrs) + # verifico se è stato coinvolto il testo della quota + textPt = self.getTextPt(destinationCrs) + if qad_stretch_fun.isPtContainedForStretch(textPt, containerGeom): + if linePosPt is not None: + newPt = qad_utils.movePoint(textPt, offsetX, offsetY) + linePosPt = qad_utils.getPolarPointBy2Pts(dimArc.center, linePosPt, qad_utils.getDistance(dimArc.center, newPt)) + + if (dimArc is not None) and \ + (linePosPt is not None): + arcLeader = True if self.getDimLeaderLine(QadDimComponentEnum.ARC_LEADER_LINE) is not None else False + + dimEntity, textOffsetRect = self.dimStyle.getArcDimFeatures(canvas, \ + dimArc, \ + linePosPt, \ + measure, \ + arcLeader) + self.set(dimEntity) + + elif self.dimStyle.dimType == QadDimTypeEnum.RADIUS: # quota radiale, misura il raggio di un cerchio o di un arco + # non si può fare perchè non si può sapere se la quota si riferiva ad un cerchio o ad un arco + # al momento ipotizzo si riferisca sempre ad un cerchio + dimCircle = self.getDimCircle() + linePosPt = self.getDimLinePosPt(containerGeom, destinationCrs) + + if type(containerGeom) == list: # lista di punti + for containerPt in containerGeom: + # whereIsPt ritorna -1 se il punto è interno, 0 se è sulla circonferenza, 1 se è esterno + if dimCircle.whereIsPt(containerPt) == 0: + linePosPt = None # sposto il punto che era sulla circonferenza + + if dimCircle is not None: + dimCircle = qad_stretch_fun.stretchQadGeometry(dimCircle, containerGeom, \ + offsetX, offsetY) + + if linePosPt is not None: + newPt = qad_utils.movePoint(linePosPt, offsetX, offsetY) + linePosPt = qad_utils.getPolarPointBy2Pts(dimCircle.center, linePosPt, qad_utils.getDistance(dimCircle.center, newPt)) + else: + linePosPt = self.getDimLinePosPt(None, destinationCrs) + # verifico se è stato coinvolto il testo della quota + textPt = self.getTextPt(destinationCrs) + if qad_stretch_fun.isPtContainedForStretch(textPt, containerGeom): + if linePosPt is not None: + newPt = qad_utils.movePoint(textPt, offsetX, offsetY) + linePosPt = qad_utils.getPolarPointBy2Pts(dimCircle.center, linePosPt, qad_utils.getDistance(dimCircle.center, newPt)) + + if (dimCircle is not None) and (linePosPt is not None): + dimEntity, textOffsetRect = self.dimStyle.getRadiusDimFeatures(canvas, dimCircle, linePosPt, measure) + self.set(dimEntity) + + if textRot is not None: + self.dimStyle.textRotMode = prevTextRotMode # ripristino la situazione precedente + + return True; + + + # ============================================================================ + # getDimComponentByEntity + # ============================================================================ + def getDimComponentByEntity(self, entity): + """ + La funzione, data un'entità, restituisce il componente della quotatura. + """ + if entity.layer == self.getTextualLayer(): + return QadDimComponentEnum.TEXT_PT + elif entity.layer == self.getLinearLayer() or \ + entity.layer == self.getSymbolLayer(): + try: + return entity.getFeature().attribute(self.dimStyle.componentFieldName) + except: + return None + + return None + + +# ============================================================================ +# appendDimEntityIfNotExisting +# ============================================================================ +def appendDimEntityIfNotExisting(dimEntityList, dimEntity): + """ + La funzione è di utilità nei comandi per evitare di elaborare più volte oggetti appartenenti a quotatura + dimEntityList è da dichiarare come una lista semplice (es. dimElaboratedList = []) + La funzione cerca in dimEntityList se esiste dimEntity, in caso affermativo ritorna False + altrimenti aggiunge alla lista dimEntity e ritorna True + """ + for item in dimEntityList: + if item == dimEntity: return False + dimEntityList.append(dimEntity) + return True + + +# =============================================================================== +# = variabile globale +# =============================================================================== + QadDimStyles = QadDimStylesClass() # lista degli stili di quotatura caricati \ No newline at end of file diff --git a/qad_dim_cmd.py b/qad_dim_cmd.py deleted file mode 100644 index 260cd484..00000000 --- a/qad_dim_cmd.py +++ /dev/null @@ -1,1033 +0,0 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - comando COPY per copiare oggetti - - ------------------- - begin : 2014-02-19 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -from qad_dim import * -from qad_dim_maptool import * -from qad_generic_cmd import QadCommandClass -from qad_msg import QadMsg -from qad_getpoint import * -from qad_textwindow import * -from qad_entsel_cmd import QadEntSelClass -from qad_getangle_cmd import QadGetAngleClass -from qad_variables import * -import qad_utils -import qad_layer - - -#============================================================================ -# FUNZIONI GENERICHE - INIZIO -#============================================================================ - - -#============================================================================ -# getStartEndPointClosestPartWithContext -#============================================================================ -def getStartEndPointClosestPartWithContext(entity, point, destCrs): - # legge il punto iniziale e finale della parte più vicina al punto di selezione - # se non si tratta di cerchio altrimenti ritorna l'oggetto QadCircle - geom = entity.getGeometry() - - # trasformo la geometria in screen coordinate - coordTransform = QgsCoordinateTransform(entity.layer.crs(), destCrs) # trasformo la geometria - geom.transform(coordTransform) - - return qad_utils.whatGeomIs(point, geom) - - -#============================================================================ -# FUNZIONI GENERICHE - FINE -#============================================================================ - - -# Classe che gestisce il comando DIMLINEAR -class QadDIMLINEARCommandClass(QadCommandClass): - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadDIMLINEARCommandClass(self.plugIn) - - def getName(self): - return QadMsg.translate("Command_list", "DIMLINEAR") - - def getEnglishName(self): - return "DIMLINEAR" - - def connectQAction(self, action): - QObject.connect(action, SIGNAL("triggered()"), self.plugIn.runDIMLINEARCommand) - - def getIcon(self): - return QIcon(":/plugins/qad/icons/dimLinear.png") - - def getNote(self): - # impostare le note esplicative del comando - return QadMsg.translate("Command_DIM", "Creates an horizontal or vertical linear dimension.") - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - self.EntSelClass = None - self.GetAngleClass = None - - self.dimPt1 = QgsPoint() # primo punto di quotatura esplicito - self.dimPt2 = QgsPoint() # secondo punto di quotatura esplicito - self.dimCircle = None # oggetto cerchio da quotare - - self.measure = None # misura della quota (se None viene calcolato) - self.preferredAlignment = QadDimStyleAlignmentEnum.HORIZONTAL # allineamento della linea di quota - # leggo lo stile di quotatura corrente - dimStyleName = QadVariables.get(QadMsg.translate("Environment variables", "DIMSTYLE")) - self.forcedDimLineAlignment = None # allineamento della linea di quota forzato - self.forcedDimLineRot = 0.0 # rotazione della linea di quota forzato - - _dimStyle = QadDimStyles.findDimStyle(dimStyleName) - if _dimStyle is not None: - self.dimStyle = QadDimStyle(_dimStyle) # ne faccio una copia perché può venire modificato dal comando - self.dimStyle.dimType = QadDimTypeEnum.LINEAR - else: - self.dimStyle = None - - - def __del__(self): - QadCommandClass.__del__(self) - if self.EntSelClass is not None: - self.EntSelClass.entity.deselectOnLayer() - del self.EntSelClass - if self.GetAngleClass is not None: - del self.GetAngleClass - - def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): - if self.step == 2: # quando si é in fase di selezione entità - return self.EntSelClass.getPointMapTool(drawMode) - # quando si é in fase di richiesta rotazione - elif self.step == 6 or self.step == 7: - return self.GetAngleClass.getPointMapTool() - else: - if (self.plugIn is not None): - if self.PointMapTool is None: - self.PointMapTool = Qad_dim_maptool(self.plugIn) - return self.PointMapTool - else: - return None - - - #============================================================================ - # addDimToLayers - #============================================================================ - def addDimToLayers(self, linePosPt): - return self.dimStyle.addLinearDimToLayers(self.plugIn, self.dimPt1, self.dimPt2, \ - linePosPt, self.measure, self.preferredAlignment, \ - self.forcedDimLineRot) - - - #============================================================================ - # waitForFirstPt - #============================================================================ - def waitForFirstPt(self): - self.step = 1 - # imposto il map tool - self.getPointMapTool().setMode(Qad_dim_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_FIRST_PT) - - msg = QadMsg.translate("Command_DIM", "Specify first extension line origin or : ") - - # si appresta ad attendere un punto o enter - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(msg, \ - QadInputTypeEnum.POINT2D, \ - None, \ - "", QadInputModeEnum.NONE) - - - #============================================================================ - # waitForSecondPt - #============================================================================ - def waitForSecondPt(self): - self.step = 3 - # imposto il map tool - self.getPointMapTool().dimPt1 = self.dimPt1 - self.getPointMapTool().setMode(Qad_dim_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT) - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_DIM", "Specify second extension line origin: ")) - - - #============================================================================ - # waitForEntsel - #============================================================================ - def waitForEntsel(self, msgMapTool, msg): - if self.EntSelClass is not None: - del self.EntSelClass - self.step = 2 - self.EntSelClass = QadEntSelClass(self.plugIn) - self.EntSelClass.msg = QadMsg.translate("Command_DIM", "Select the object to dimension: ") - # scarto la selezione di punti - self.EntSelClass.checkPointLayer = False - self.EntSelClass.checkLineLayer = True - self.EntSelClass.checkPolygonLayer = True - self.EntSelClass.getPointMapTool().setSnapType(QadSnapTypeEnum.DISABLE) - self.EntSelClass.run(msgMapTool, msg) - - - #============================================================================ - # waitForDimensionLinePos - #============================================================================ - def waitForDimensionLinePos(self): - self.step = 4 - # imposto il map tool - self.getPointMapTool().dimPt2 = self.dimPt2 - if self.getPointMapTool().dimPt1 is None: # in caso di selezione oggetto dimPt1 non era stato inizializzato - self.getPointMapTool().dimPt1 = self.dimPt1 - self.getPointMapTool().dimCircle = self.dimCircle - self.getPointMapTool().dimStyle = self.dimStyle - self.getPointMapTool().setMode(Qad_dim_maptool_ModeEnum.FIRST_SECOND_PT_KNOWN_ASK_FOR_ALIGNED_DIM_LINE_POS) - - # si appresta ad attendere un punto o una parola chiave - keyWords = QadMsg.translate("Command_DIM", "Text") + "/" + \ - QadMsg.translate("Command_DIM", "Angle") - prompt = QadMsg.translate("Command_DIM", "Specify dimension line location or [{0}]: ").format(keyWords) - englishKeyWords = "Text" + "/" + "Angle" - keyWords += "_" + englishKeyWords - - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - None, \ - keyWords, \ - QadInputModeEnum.NONE) - - - #============================================================================ - # run - #============================================================================ - def run(self, msgMapTool = False, msg = None): - if self.plugIn.canvas.mapRenderer().destinationCrs().geographicFlag(): - self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) - return True # fine comando - - if self.dimStyle is None: - self.showMsg(QadMsg.translate("QAD", "\nDimension style not valid.\nVerify the value of DIMSTYLE variable.\n")) - return True # fine comando - - errMsg = self.dimStyle.getInValidErrMsg() - if errMsg is not None: - self.showMsg(errMsg) - return True # fine comando - - errMsg = self.dimStyle.getNotGraphEditableErrMsg() - if errMsg is not None: - self.showMsg(errMsg) - return True # fine comando - - - #========================================================================= - # RICHIESTA SELEZIONE ORIGINE PRIMA LINEA DI ESTENSIONE - if self.step == 0: # inizio del comando - self.waitForFirstPt() - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA ORIGINE PRIMA LINEA DI ESTENSIONE (da step = 0) - elif self.step == 1: - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - value = None # opzione di default None - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - else: - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if value is None: - self.waitForEntsel(msgMapTool, msg) - else: - self.dimPt1.set(value.x(), value.y()) - self.waitForSecondPt() - - return False - - #========================================================================= - # RISPOSTA ALLA SELEZIONE DI UN'ENTITA' (da step = 1) - elif self.step == 2: - if self.EntSelClass.run(msgMapTool, msg) == True: - if self.EntSelClass.entity.isInitialized(): - result = getStartEndPointClosestPartWithContext(self.EntSelClass.entity, \ - self.EntSelClass.point, \ - self.plugIn.canvas.mapRenderer().destinationCrs()) - if result is not None: - if (type(result) == list or type(result) == tuple): # se é una lista di 2 punti - self.dimPt1 = result[0] - self.dimPt2 = result[1] - else: - objType = result.whatIs() - if objType == "ARC": # se é arco - self.dimPt1 = result.getStartPt() - self.dimPt2 = result.getEndPt() - elif objType == "CIRCLE": # se é cerchio - self.dimCircle = result - intPts = self.dimCircle.getIntersectionPointsWithInfinityLine(self.dimCircle.center, self.EntSelClass.point) - if len(intPts) == 2: - self.dimPt1 = intPts[0] - self.dimPt2 = intPts[1] - - self.waitForDimensionLinePos() - return False - else: - self.showMsg(QadMsg.translate("Command_DIM", "No geometries in this position.")) - self.waitForEntsel(msgMapTool, msg) - return False # continua - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA ORIGINE SECONDA LINEA DI ESTENSIONE (da step = 1) - elif self.step == 3: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if value is None: - return True - - if type(value) == QgsPoint: # se é stato inserito il secondo punto - self.dimPt2.set(value.x(), value.y()) - self.waitForDimensionLinePos() - - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DELLA POSIZIONE DELLA LINEA DI QUOTA (da step = 2 e 3) - elif self.step == 4: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("Command_DIM", "Text") or value == "Text": - prompt = QadMsg.translate("Command_DIM", "Enter dimension text <{0}>: ") - dist = qad_utils.getDistance(self.dimPt1, self.dimPt2) - self.waitForString(prompt.format(str(dist)), dist) - self.getPointMapTool().setMode(Qad_dim_maptool_ModeEnum.ASK_FOR_TEXT) - self.step = 5 - elif value == QadMsg.translate("Command_DIM", "Angle") or value == "Angle": - # si appresta ad attendere l'angolo di rotazione del testo - if self.GetAngleClass is not None: - del self.GetAngleClass - self.GetAngleClass = QadGetAngleClass(self.plugIn) - prompt = QadMsg.translate("Command_DIM", "Specify angle of dimension text <{0}>: ") - self.GetAngleClass.msg = prompt.format(str(qad_utils.toDegrees(self.dimStyle.textForcedRot))) - self.GetAngleClass.angle = self.dimStyle.textForcedRot - self.step = 6 - self.GetAngleClass.run(msgMapTool, msg) - elif type(value) == QgsPoint: # se é stato inserito il punto di posizionamento linea quota - self.dimPt1 = self.getPointMapTool().dimPt1 - self.dimPt2 = self.getPointMapTool().dimPt2 - self.addDimToLayers(value) - return True # fine comando - - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DEL TESTO (da step = 4) - elif self.step == 5: # dopo aver atteso una stringa si riavvia il comando - if type(msg) == unicode: - text = msg.strip() - if len(text) > 0: - self.measure = text - self.getPointMapTool().measure = self.measure - self.waitForDimensionLinePos() - - return False - - -# Classe che gestisce il comando DIMARC da finire -class QadDIMARCCommandClass(QadCommandClass): - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadDIMARCCommandClass(self.plugIn) - - def getName(self): - return QadMsg.translate("Command_list", "DIMARC") - - def getEnglishName(self): - return "DIMARC" - - def connectQAction(self, action): - QObject.connect(action, SIGNAL("triggered()"), self.plugIn.runDIMARCCommand) - - def getIcon(self): - return QIcon(":/plugins/qad/icons/dimArc.png") - - def getNote(self): - # impostare le note esplicative del comando - return QadMsg.translate("Command_DIM", "Creates an arc length dimension.") - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - self.EntSelClass = None - self.GetAngleClass = None - - self.dimPt1 = QgsPoint() - self.dimPt2 = QgsPoint() - self.dimArc = None # oggetto arco da quotare - - self.measure = None # misura della quota (se None viene calcolato) - self.leader = False - # leggo lo stile di quotatura corrente - dimStyleName = QadVariables.get(QadMsg.translate("Environment variables", "DIMSTYLE")) - self.dimStyle = QadDimStyles.findDimStyle(dimStyleName) - if self.dimStyle is not None: - self.dimStyle.dimType = QadDimTypeEnum.ALIGNED - - - def __del__(self): - QadCommandClass.__del__(self) - if self.EntSelClass is not None: - self.EntSelClass.entity.deselectOnLayer() - del self.EntSelClass - if self.GetAngleClass is not None: - del self.GetAngleClass - - def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): - if self.step == 1: # quando si é in fase di selezione entità - return self.EntSelClass.getPointMapTool(drawMode) - # quando si é in fase di richiesta rotazione - elif self.step == 6: - return self.GetAngleClass.getPointMapTool() - else: - if (self.plugIn is not None): - if self.PointMapTool is None: - self.PointMapTool = Qad_dim_maptool(self.plugIn) - return self.PointMapTool - else: - return None - - - #============================================================================ - # addDimToLayers - #============================================================================ - def addDimToLayers(self, linePosPt): - return self.dimStyle.addAlignedDimToLayers(self.plugIn, self.dimPt1, self.dimPt2, \ - linePosPt, self.measure) - - - #============================================================================ - # waitForEntsel - #============================================================================ - def waitForEntsel(self, msgMapTool, msg): - if self.EntSelClass is not None: - del self.EntSelClass - self.step = 1 - self.EntSelClass = QadEntSelClass(self.plugIn) - self.EntSelClass.msg = QadMsg.translate("Command_DIM", "Select arc or polyline arc segment: ") - # scarto la selezione di punti - self.EntSelClass.checkPointLayer = False - self.EntSelClass.checkLineLayer = True - self.EntSelClass.checkPolygonLayer = True - self.EntSelClass.getPointMapTool().setSnapType(QadSnapTypeEnum.DISABLE) - self.EntSelClass.run(msgMapTool, msg) - - - #============================================================================ - # waitForDimensionLinePos - #============================================================================ - def waitForDimensionLinePos(self): - self.step = 4 - # imposto il map tool - self.getPointMapTool().dimPt2 = self.dimPt2 - if self.getPointMapTool().dimPt1 is None: # in caso di selezione oggetto dimPt1 non era stato inizializzato - self.getPointMapTool().dimPt1 = self.dimPt1 - self.getPointMapTool().dimCircle = self.dimCircle - self.getPointMapTool().dimStyle = self.dimStyle - self.getPointMapTool().setMode(Qad_dim_maptool_ModeEnum.FIRST_SECOND_PT_KNOWN_ASK_FOR_ALIGNED_DIM_LINE_POS) - - # si appresta ad attendere un punto o una parola chiave - # si appresta ad attendere un punto o una parola chiave - keyWords = QadMsg.translate("Command_DIM", "Text") + "/" + \ - QadMsg.translate("Command_DIM", "Angle") + "/" + \ - QadMsg.translate("Command_DIM", "Partial") + "/" - englishKeyWords = "Text" + "/" + "2POints" + "/" + "Partial" + "/" - if self.leader: - keyWords = keyWords + QadMsg.translate("Command_DIM", "Leader") - englishKeyWords = englishKeyWords + "Leader" - else: - keyWords = keyWords + QadMsg.translate("Command_DIM", "No leader") - englishKeyWords = englishKeyWords + "No leader" - keyWords += "_" + englishKeyWordsAngle - - prompt = QadMsg.translate("Command_DIM", "Specify dimension location or [{0}]: ").format(keyWords) - - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - None, \ - keyWords, \ - QadInputModeEnum.NONE) - - - #============================================================================ - # run - #============================================================================ - def run(self, msgMapTool = False, msg = None): - if self.plugIn.canvas.mapRenderer().destinationCrs().geographicFlag(): - self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) - return True # fine comando - - if self.dimStyle is None: - self.showMsg(QadMsg.translate("QAD", "\nDimension style not valid.\nVerify the value of DIMSTYLE variable.\n")) - return True # fine comando - - errMsg = self.dimStyle.getInValidErrMsg() - if errMsg is not None: - self.showMsg(errMsg) - return True # fine comando - - errMsg = self.dimStyle.getNotGraphEditableErrMsg() - if errMsg is not None: - self.showMsg(errMsg) - return True # fine comando - - - #========================================================================= - # RICHIESTA SELEZIONE ARCO DA QUOTARE - if self.step == 0: # inizio del comando - self.waitForEntsel(msgMapTool, msg) - return False - - - #========================================================================= - # RISPOSTA ALLA SELEZIONE DI UN'ENTITA' (da step = 0) - elif self.step == 1: - if self.EntSelClass.run(msgMapTool, msg) == True: - if self.EntSelClass.entity.isInitialized(): - result = getStartEndPointClosestPartWithContext(self.EntSelClass.entity, \ - self.EntSelClass.point, \ - self.plugIn.canvas.mapRenderer().destinationCrs()) - if result is not None: - if (type(result) != list and type(result) != tuple): # se non é una lista di 2 punti - objType = result.whatIs() - if objType == "ARC": # se é arco - self.dimArc = result - return False - - self.showMsg(QadMsg.translate("Command_DIM", "Select an arc.")) - self.waitForEntsel(msgMapTool, msg) - else: - self.showMsg(QadMsg.translate("Command_DIM", "No geometries in this position.")) - self.waitForEntsel(msgMapTool, msg) - return False # continua - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA ORIGINE SECONDA LINEA DI ESTENSIONE (da step = 1) - elif self.step == 3: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if value is None: - return True - - if type(value) == QgsPoint: # se é stato inserito il secondo punto - self.dimPt2.set(value.x(), value.y()) - self.waitForDimensionLinePos() - - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DELLA POSIZIONE DELLA LINEA DI QUOTA (da step = 2 e 3) - elif self.step == 4: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("Command_DIM", "Text") or value == "Text": - prompt = QadMsg.translate("Command_DIM", "Enter dimension text <{0}>: ") - dist = qad_utils.getDistance(self.dimPt1, self.dimPt2) - self.waitForString(prompt.format(str(dist)), dist) - self.getPointMapTool().setMode(Qad_dim_maptool_ModeEnum.ASK_FOR_TEXT) - self.step = 5 - elif value == QadMsg.translate("Command_DIM", "Angle") or value == "Angle": - # si appresta ad attendere l'angolo di rotazione del testo - if self.GetAngleClass is not None: - del self.GetAngleClass - self.GetAngleClass = QadGetAngleClass(self.plugIn) - prompt = QadMsg.translate("Command_DIM", "Specify angle of dimension text <{0}>: ") - self.GetAngleClass.msg = prompt.format(str(qad_utils.toDegrees(self.dimStyle.textForcedRot))) - self.GetAngleClass.angle = self.dimStyle.textForcedRot - self.step = 6 - self.GetAngleClass.run(msgMapTool, msg) - elif type(value) == QgsPoint: # se é stato inserito il punto di posizionamento linea quota - self.dimPt1 = self.getPointMapTool().dimPt1 - self.dimPt2 = self.getPointMapTool().dimPt2 - self.addDimToLayers(value) - return True # fine comando - - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DEL TESTO (da step = 4) - elif self.step == 5: # dopo aver atteso una stringa si riavvia il comando - if type(msg) == unicode: - text = msg.strip() - if len(text) > 0: - self.measure = text - self.getPointMapTool().measure = self.measure - self.waitForDimensionLinePos() - - return False diff --git a/qad_dim_maptool.py b/qad_dim_maptool.py deleted file mode 100644 index 34145014..00000000 --- a/qad_dim_maptool.py +++ /dev/null @@ -1,262 +0,0 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - classe per gestire il map tool in ambito dei comandi di quotatura - - ------------------- - begin : 2013-05-22 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -from qgis.gui import * -import math - - -import qad_utils -from qad_snapper import * -from qad_snappointsdisplaymanager import * -from qad_variables import * -from qad_getpoint import * -from qad_dim import * -from qad_rubberband import QadRubberBand - - -#=============================================================================== -# Qad_dim_maptool_ModeEnum class. -#=============================================================================== -class Qad_dim_maptool_ModeEnum(): - # noto niente si richiede il primo punto di quotatura - NONE_KNOWN_ASK_FOR_FIRST_PT = 1 - # noto il primo punto si richiede il secondo punto di quotatura - FIRST_PT_KNOWN_ASK_FOR_SECOND_PT = 2 - # noto i punti di quotatura si richiede la posizione della linea di quota lineare - FIRST_SECOND_PT_KNOWN_ASK_FOR_LINEAR_DIM_LINE_POS = 3 - # si richiede il testo di quota - ASK_FOR_TEXT = 6 - # noto i punti di quotatura si richiede la posizione della linea di quota allineata - FIRST_SECOND_PT_KNOWN_ASK_FOR_ALIGNED_DIM_LINE_POS = 7 - - # noto il primo punto di estremità diam si richiede il secondo punto di estremità diam - FIRST_DIAM_PT_KNOWN_ASK_FOR_SECOND_DIAM_PT = 8 - # noto niente si richiede l'entita del primo punto di tangenza - NONE_KNOWN_ASK_FOR_FIRST_TAN = 9 - # nota l'entita del primo punto di tangenza si richiede quella del secondo punto di tangenza - FIRST_TAN_KNOWN_ASK_FOR_SECOND_TAN = 10 - # note la prima e la seconda entita dei punti di tangenza si richiede il raggio - FIRST_SECOND_TAN_KNOWN_ASK_FOR_RADIUS = 11 - # noto note la prima, la seconda entita dei punti di tangenza e il primo punto per misurare il raggio - # si richiede il secondo punto per misurare il raggio - FIRST_SECOND_TAN_FIRSTPTRADIUS_KNOWN_ASK_FOR_SECONDPTRADIUS = 12 - -#=============================================================================== -# Qad_dim_maptool class -#=============================================================================== -class Qad_dim_maptool(QadGetPoint): - - def __init__(self, plugIn): - QadGetPoint.__init__(self, plugIn) - - dimStyle = None - self.dimPt1 = None - self.dimPt2 = None - self.dimCircle = None - - self.forcedTextRot = None # rotazione del testo di quota - self.measure = None # misura della quota (se None viene calcolato) - self.preferredAlignment = QadDimStyleAlignmentEnum.HORIZONTAL # allineamento della linea di quota - self.forcedDimLineAlignment = None # allineamento della linea di quota forzato - self.forcedDimLineRot = 0.0 # rotazione della linea di quota forzato - - self.__rubberBand = QadRubberBand(self.canvas) - - - self.centerPt = None - self.radius = None - self.dimPt1 = None - self.dimPt2 = None - self.firstDiamPt = None - self.tan1 = None - self.tan2 = None - self.startPtForRadius = None - self.geomType = QGis.Polygon - - def hidePointMapToolMarkers(self): - QadGetPoint.hidePointMapToolMarkers(self) - self.__rubberBand.hide() - - def showPointMapToolMarkers(self): - QadGetPoint.showPointMapToolMarkers(self) - self.__rubberBand.show() - - def clear(self): - QadGetPoint.clear(self) - self.__rubberBand.reset() - self.mode = None - - - def seDimLineAlignment(self, LinePosPt, horizLine1, horizLine2, verticalLine1, verticalLine2): - # < 0 se a sinistra della linea - sxOfHorizLine1 = True if qad_utils.leftOfLine(LinePosPt, horizLine1[0], horizLine1[1]) < 0 else False - sxOfHorizLine2 = True if qad_utils.leftOfLine(LinePosPt, horizLine2[0], horizLine2[1]) < 0 else False - - sxOfVerticalLine1 = True if qad_utils.leftOfLine(LinePosPt, verticalLine1[0], verticalLine1[1]) < 0 else False - sxOfVerticalLine2 = True if qad_utils.leftOfLine(LinePosPt, verticalLine2[0], verticalLine2[1]) < 0 else False - - # se LinePosPt é tra le linee di limite orizzontale e non é tra le linee di limite verticale - if sxOfHorizLine1 != sxOfHorizLine2 and sxOfVerticalLine1 == sxOfVerticalLine2: - self.preferredAlignment = QadDimStyleAlignmentEnum.HORIZONTAL - # se LinePosPt non é tra le linee di limite orizzontale ed é tra le linee di limite verticale - elif sxOfHorizLine1 == sxOfHorizLine2 and sxOfVerticalLine1 != sxOfVerticalLine2: - self.preferredAlignment = QadDimStyleAlignmentEnum.VERTICAL - - return - - - #============================================================================ - # setLinearDimPtsAndDimLineAlignmentOnCircle - #============================================================================ - def setLinearDimPtsAndDimLineAlignmentOnCircle(self, LinePosPt, circle): - pt1 = qad_utils.getPolarPointByPtAngle(circle.center, self.forcedDimLineRot, circle.radius) - pt2 = qad_utils.getPolarPointByPtAngle(pt1, self.forcedDimLineRot + math.pi / 2, circle.radius) - horizLine1 = [pt1, pt2] - - pt1 = qad_utils.getPolarPointByPtAngle(circle.center, self.forcedDimLineRot, -1 * circle.radius) - pt2 = qad_utils.getPolarPointByPtAngle(pt1, self.forcedDimLineRot + math.pi / 2, circle.radius) - horizLine2 = [pt1, pt2] - - pt1 = qad_utils.getPolarPointByPtAngle(circle.center, self.forcedDimLineRot + math.pi / 2, circle.radius) - pt2 = qad_utils.getPolarPointByPtAngle(pt1, self.forcedDimLineRot, circle.radius) - verticalLine1 = [pt1, pt2] - - pt1 = qad_utils.getPolarPointByPtAngle(circle.center, self.forcedDimLineRot + math.pi / 2, -1 * circle.radius) - pt2 = qad_utils.getPolarPointByPtAngle(pt1, self.forcedDimLineRot, circle.radius) - verticalLine2 = [pt1, pt2] - - # se non é stato impostato un allineamento forzato, lo calcolo in automatico - if self.forcedDimLineAlignment is None: - self.seDimLineAlignment(LinePosPt, horizLine1, horizLine2, verticalLine1, verticalLine2) - else: - self.preferredAlignment = self.forcedDimLineAlignment - - if self.preferredAlignment == QadDimStyleAlignmentEnum.HORIZONTAL: - self.dimPt1 = horizLine1[0] - self.dimPt2 = horizLine2[0] - else: - self.dimPt1 = verticalLine1[0] - self.dimPt2 = verticalLine2[0] - - - #============================================================================ - # setLinearDimLineAlignmentOnDimPts - #============================================================================ - def setLinearDimLineAlignmentOnDimPts(self, LinePosPt): - # se non é stato impostato un allineamento forzato, lo calcolo in automatico - if self.forcedDimLineAlignment is None: - pt2 = qad_utils.getPolarPointByPtAngle(self.dimPt1, self.forcedDimLineRot + math.pi / 2, 1) - horizLine1 = [self.dimPt1, pt2] - - pt2 = qad_utils.getPolarPointByPtAngle(self.dimPt2, self.forcedDimLineRot + math.pi / 2, 1) - horizLine2 = [self.dimPt2, pt2] - - pt2 = qad_utils.getPolarPointByPtAngle(self.dimPt1, self.forcedDimLineRot, 1) - verticalLine1 = [self.dimPt1, pt2] - - pt2 = qad_utils.getPolarPointByPtAngle(self.dimPt2, self.forcedDimLineRot, 1) - verticalLine2 = [self.dimPt2, pt2] - - self.seDimLineAlignment(LinePosPt, horizLine1, horizLine2, verticalLine1, verticalLine2) - else: - self.preferredAlignment = self.forcedDimLineAlignment - - - #============================================================================ - # canvasMoveEvent - #============================================================================ - def canvasMoveEvent(self, event): - QadGetPoint.canvasMoveEvent(self, event) - - self.__rubberBand.reset() - - dimEntity = None - - # noti i punti di quotatura si richiede la posizione della linea di quota lineare - if self.mode == Qad_dim_maptool_ModeEnum.FIRST_SECOND_PT_KNOWN_ASK_FOR_LINEAR_DIM_LINE_POS: - if self.dimCircle is not None: - self.setLinearDimPtsAndDimLineAlignmentOnCircle(self.tmpPoint, self.dimCircle) - else: - self.setLinearDimLineAlignmentOnDimPts(self.tmpPoint) - - dimEntity, textOffsetRect = self.dimStyle.getLinearDimFeatures(self.canvas, \ - self.dimPt1, \ - self.dimPt2, \ - self.tmpPoint, \ - self.measure, \ - self.preferredAlignment, \ - self.forcedDimLineRot) - # noti i punti di quotatura si richiede la posizione della linea di quota allineata - elif self.mode == Qad_dim_maptool_ModeEnum.FIRST_SECOND_PT_KNOWN_ASK_FOR_ALIGNED_DIM_LINE_POS: - dimEntity, textOffsetRect = self.dimStyle.getAlignedDimFeatures(self.canvas, \ - self.dimPt1, \ - self.dimPt2, \ - self.tmpPoint, \ - self.measure) - - if dimEntity is not None: - # testo di quota - self.__rubberBand.addGeometry(dimEntity.textualFeature.geometry(), self.dimStyle.getTextualLayer()) # geom e layer - self.__rubberBand.addGeometry(textOffsetRect, self.dimStyle.getTextualLayer()) # geom e layer - for g in dimEntity.getLinearGeometryCollection(): - self.__rubberBand.addGeometry(g, self.dimStyle.getLinearLayer()) # geom e layer - for g in dimEntity.getSymbolGeometryCollection(): - self.__rubberBand.addGeometry(g, self.dimStyle.getSymbolLayer()) # geom e layer - - - def activate(self): - QadGetPoint.activate(self) - if self.__rubberBand is not None: - self.__rubberBand.show() - - def deactivate(self): - try: # necessario perché se si chiude QGIS parte questo evento nonostante non ci sia più l'oggetto maptool ! - QadGetPoint.deactivate(self) - if self.__rubberBand is not None: - self.__rubberBand.hide() - except: - pass - - def setMode(self, mode): - self.mode = mode - # noto niente si richiede il primo punto di quotatura - if self.mode == Qad_dim_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_FIRST_PT: - self.setDrawMode(QadGetPointDrawModeEnum.NONE) - # noto il primo punto si richiede il secondo punto di quotatura - elif self.mode == Qad_dim_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT: - self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.setStartPoint(self.dimPt1) - # noto i punti di quotatura si richiede la posizione della linea di quota - elif self.mode == Qad_dim_maptool_ModeEnum.FIRST_SECOND_PT_KNOWN_ASK_FOR_LINEAR_DIM_LINE_POS: - self.setDrawMode(QadGetPointDrawModeEnum.NONE) - # si richiede il testo di quota - elif self.mode == Qad_dim_maptool_ModeEnum.ASK_FOR_TEXT: - self.setDrawMode(QadGetPointDrawModeEnum.NONE) - # noti i punti di quotatura si richiede la posizione della linea di quota allineata - elif self.mode == Qad_dim_maptool_ModeEnum.FIRST_SECOND_PT_KNOWN_ASK_FOR_ALIGNED_DIM_LINE_POS: - self.setDrawMode(QadGetPointDrawModeEnum.NONE) \ No newline at end of file diff --git a/qad_dimensioninput_settings.ui b/qad_dimensioninput_settings.ui new file mode 100644 index 00000000..c40a51ea --- /dev/null +++ b/qad_dimensioninput_settings.ui @@ -0,0 +1,344 @@ + + + DimInput_Settings_Dialog + + + + 0 + 0 + 400 + 346 + + + + + 0 + 0 + + + + + 400 + 346 + + + + + 400 + 346 + + + + Dimension Input Settings + + + + + 100 + 310 + 295 + 30 + + + + + + + OK + + + + + + + Cancel + + + + + + + ? + + + + + + + + + 10 + 10 + 381 + 291 + + + + Visibility + + + + + 10 + 20 + 231 + 16 + + + + When grip-stretching: + + + + + + 10 + 50 + 361 + 17 + + + + Displays only the length change dimensional input tooltip when you are using grip editing to stretch an object. (DYNDIVIS system variable) + + + Show only 1 dimension input field at a time + + + + + + 10 + 80 + 361 + 17 + + + + Displays the length change and resulting dimensional input tooltips when you are using grip editing to stretch an object. (DYNDIVIS system variable) + + + Show 2 dimension input fields at a time + + + + + + 10 + 110 + 361 + 17 + + + + When you are using grip editing to stretch an object, displays the dimensional input tooltips that are selected below. (DYNDIVIS and DYNDIGRIP system variables) + + + Show the following dimension input fields simultaneously: + + + + + + 30 + 200 + 151 + 17 + + + + Displays an angle dimensional tooltip that is updated as you move the grip. + + + Absolute angle + + + + + + 30 + 140 + 151 + 17 + + + + Displays a length dimensional tooltip that is updated as you move the grip. + + + Resulting dimension + + + + + + 30 + 170 + 151 + 17 + + + + Displays the change in length as you move the grip. + + + Length change + + + + + + 190 + 140 + 141 + 17 + + + + Displays the change in the angle as you move the grip. + + + Angle change + + + + + + 50 + 240 + 321 + 41 + + + + Press TAB to switch to the next dimension input field + + + true + + + + + + 10 + 240 + 31 + 31 + + + + + + + :/plugins/qad/icons/lamp_on.png + + + + + + + + radioShowMoreDim + clicked() + DimInput_Settings_Dialog + radioShowMoreDimChecked() + + + 171 + 126 + + + 359 + 232 + + + + + cancelButton + clicked() + DimInput_Settings_Dialog + reject() + + + 235 + 319 + + + 48 + 307 + + + + + okButton + clicked() + DimInput_Settings_Dialog + ButtonBOX_Accepted() + + + 143 + 325 + + + 18 + 323 + + + + + helpButton + clicked() + DimInput_Settings_Dialog + ButtonHELP_Pressed() + + + 309 + 322 + + + 356 + 326 + + + + + radioShow2Dim + clicked() + DimInput_Settings_Dialog + radioShow2DimChecked() + + + 101 + 99 + + + 360 + 95 + + + + + radioShow1Dim + clicked() + DimInput_Settings_Dialog + radioShow1DimChecked() + + + 176 + 62 + + + 356 + 68 + + + + + + radioShowMoreDimChecked() + ButtonBOX_Accepted() + ButtonHELP_Pressed() + radioShow2DimChecked() + radioShow1DimChecked() + + diff --git a/qad_dimensioninput_settings_ui.py b/qad_dimensioninput_settings_ui.py new file mode 100644 index 00000000..417d0e85 --- /dev/null +++ b/qad_dimensioninput_settings_ui.py @@ -0,0 +1,108 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file '.\qad_dimensioninput_settings.ui' +# +# Created by: PyQt5 UI code generator 5.15.10 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_DimInput_Settings_Dialog(object): + def setupUi(self, DimInput_Settings_Dialog): + DimInput_Settings_Dialog.setObjectName("DimInput_Settings_Dialog") + DimInput_Settings_Dialog.resize(400, 346) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(DimInput_Settings_Dialog.sizePolicy().hasHeightForWidth()) + DimInput_Settings_Dialog.setSizePolicy(sizePolicy) + DimInput_Settings_Dialog.setMinimumSize(QtCore.QSize(400, 346)) + DimInput_Settings_Dialog.setMaximumSize(QtCore.QSize(400, 346)) + self.layoutWidget = QtWidgets.QWidget(DimInput_Settings_Dialog) + self.layoutWidget.setGeometry(QtCore.QRect(100, 310, 295, 30)) + self.layoutWidget.setObjectName("layoutWidget") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.layoutWidget) + self.horizontalLayout.setContentsMargins(0, 0, 0, 0) + self.horizontalLayout.setObjectName("horizontalLayout") + self.okButton = QtWidgets.QPushButton(self.layoutWidget) + self.okButton.setObjectName("okButton") + self.horizontalLayout.addWidget(self.okButton) + self.cancelButton = QtWidgets.QPushButton(self.layoutWidget) + self.cancelButton.setObjectName("cancelButton") + self.horizontalLayout.addWidget(self.cancelButton) + self.helpButton = QtWidgets.QPushButton(self.layoutWidget) + self.helpButton.setObjectName("helpButton") + self.horizontalLayout.addWidget(self.helpButton) + self.groupBox = QtWidgets.QGroupBox(DimInput_Settings_Dialog) + self.groupBox.setGeometry(QtCore.QRect(10, 10, 381, 291)) + self.groupBox.setObjectName("groupBox") + self.label = QtWidgets.QLabel(self.groupBox) + self.label.setGeometry(QtCore.QRect(10, 20, 231, 16)) + self.label.setObjectName("label") + self.radioShow1Dim = QtWidgets.QRadioButton(self.groupBox) + self.radioShow1Dim.setGeometry(QtCore.QRect(10, 50, 361, 17)) + self.radioShow1Dim.setObjectName("radioShow1Dim") + self.radioShow2Dim = QtWidgets.QRadioButton(self.groupBox) + self.radioShow2Dim.setGeometry(QtCore.QRect(10, 80, 361, 17)) + self.radioShow2Dim.setObjectName("radioShow2Dim") + self.radioShowMoreDim = QtWidgets.QRadioButton(self.groupBox) + self.radioShowMoreDim.setGeometry(QtCore.QRect(10, 110, 361, 17)) + self.radioShowMoreDim.setObjectName("radioShowMoreDim") + self.checkAbsoluteAngle = QtWidgets.QCheckBox(self.groupBox) + self.checkAbsoluteAngle.setGeometry(QtCore.QRect(30, 200, 151, 17)) + self.checkAbsoluteAngle.setObjectName("checkAbsoluteAngle") + self.checkResultingDim = QtWidgets.QCheckBox(self.groupBox) + self.checkResultingDim.setGeometry(QtCore.QRect(30, 140, 151, 17)) + self.checkResultingDim.setObjectName("checkResultingDim") + self.checkLengthChange = QtWidgets.QCheckBox(self.groupBox) + self.checkLengthChange.setGeometry(QtCore.QRect(30, 170, 151, 17)) + self.checkLengthChange.setObjectName("checkLengthChange") + self.checkAngleChange = QtWidgets.QCheckBox(self.groupBox) + self.checkAngleChange.setGeometry(QtCore.QRect(190, 140, 141, 17)) + self.checkAngleChange.setObjectName("checkAngleChange") + self.label_2 = QtWidgets.QLabel(self.groupBox) + self.label_2.setGeometry(QtCore.QRect(50, 240, 321, 41)) + self.label_2.setWordWrap(True) + self.label_2.setObjectName("label_2") + self.label_3 = QtWidgets.QLabel(self.groupBox) + self.label_3.setGeometry(QtCore.QRect(10, 240, 31, 31)) + self.label_3.setText("") + self.label_3.setPixmap(QtGui.QPixmap(":/plugins/qad/icons/lamp_on.png")) + self.label_3.setObjectName("label_3") + + self.retranslateUi(DimInput_Settings_Dialog) + self.radioShowMoreDim.clicked.connect(DimInput_Settings_Dialog.radioShowMoreDimChecked) # type: ignore + self.cancelButton.clicked.connect(DimInput_Settings_Dialog.reject) # type: ignore + self.okButton.clicked.connect(DimInput_Settings_Dialog.ButtonBOX_Accepted) # type: ignore + self.helpButton.clicked.connect(DimInput_Settings_Dialog.ButtonHELP_Pressed) # type: ignore + self.radioShow2Dim.clicked.connect(DimInput_Settings_Dialog.radioShow2DimChecked) # type: ignore + self.radioShow1Dim.clicked.connect(DimInput_Settings_Dialog.radioShow1DimChecked) # type: ignore + QtCore.QMetaObject.connectSlotsByName(DimInput_Settings_Dialog) + + def retranslateUi(self, DimInput_Settings_Dialog): + _translate = QtCore.QCoreApplication.translate + DimInput_Settings_Dialog.setWindowTitle(_translate("DimInput_Settings_Dialog", "Dimension Input Settings")) + self.okButton.setText(_translate("DimInput_Settings_Dialog", "OK")) + self.cancelButton.setText(_translate("DimInput_Settings_Dialog", "Cancel")) + self.helpButton.setText(_translate("DimInput_Settings_Dialog", "?")) + self.groupBox.setTitle(_translate("DimInput_Settings_Dialog", "Visibility")) + self.label.setText(_translate("DimInput_Settings_Dialog", "When grip-stretching:")) + self.radioShow1Dim.setToolTip(_translate("DimInput_Settings_Dialog", "Displays only the length change dimensional input tooltip when you are using grip editing to stretch an object. (DYNDIVIS system variable)")) + self.radioShow1Dim.setText(_translate("DimInput_Settings_Dialog", "Show only 1 dimension input field at a time")) + self.radioShow2Dim.setToolTip(_translate("DimInput_Settings_Dialog", "Displays the length change and resulting dimensional input tooltips when you are using grip editing to stretch an object. (DYNDIVIS system variable)")) + self.radioShow2Dim.setText(_translate("DimInput_Settings_Dialog", "Show 2 dimension input fields at a time")) + self.radioShowMoreDim.setToolTip(_translate("DimInput_Settings_Dialog", "When you are using grip editing to stretch an object, displays the dimensional input tooltips that are selected below. (DYNDIVIS and DYNDIGRIP system variables)")) + self.radioShowMoreDim.setText(_translate("DimInput_Settings_Dialog", "Show the following dimension input fields simultaneously:")) + self.checkAbsoluteAngle.setToolTip(_translate("DimInput_Settings_Dialog", "Displays an angle dimensional tooltip that is updated as you move the grip.")) + self.checkAbsoluteAngle.setText(_translate("DimInput_Settings_Dialog", "Absolute angle")) + self.checkResultingDim.setToolTip(_translate("DimInput_Settings_Dialog", "Displays a length dimensional tooltip that is updated as you move the grip.")) + self.checkResultingDim.setText(_translate("DimInput_Settings_Dialog", "Resulting dimension")) + self.checkLengthChange.setToolTip(_translate("DimInput_Settings_Dialog", "Displays the change in length as you move the grip.")) + self.checkLengthChange.setText(_translate("DimInput_Settings_Dialog", "Length change")) + self.checkAngleChange.setToolTip(_translate("DimInput_Settings_Dialog", "Displays the change in the angle as you move the grip.")) + self.checkAngleChange.setText(_translate("DimInput_Settings_Dialog", "Angle change")) + self.label_2.setText(_translate("DimInput_Settings_Dialog", "Press TAB to switch to the next dimension input field")) diff --git a/qad_dimstyle.ui b/qad_dimstyle.ui index 873af253..d4c9eb1f 100644 --- a/qad_dimstyle.ui +++ b/qad_dimstyle.ui @@ -1,380 +1,392 @@ - - - DimStyle_Dialog - - - Qt::WindowModal - - - - 0 - 0 - 523 - 341 - - - - QAD - Dimension style manager - - - - - 20 - 10 - 121 - 16 - - - - Current dimension style: - - - - - - 140 - 10 - 331 - 16 - - - - none - - - - - - 20 - 40 - 47 - 16 - - - - Styles - - - - - - 10 - 60 - 171 - 171 - - - - - - - 410 - 60 - 101 - 23 - - - - Sets the style selected under Styles to current. The current style is applied to dimensions you create. - - - Set current - - - - - - 410 - 90 - 101 - 23 - - - - Define a new dimension style. - - - New... - - - - - - 410 - 120 - 101 - 23 - - - - Modify the selected dimension style. - - - Modify... - - - - - - 410 - 150 - 101 - 23 - - - - Set temporary modifications for the selected style. The temporary modifications will not saved. - - - Override... - - - - - - 410 - 180 - 101 - 23 - - - - Compare two dimension styles or list all the properties of one dimension style. - - - Compare... - - - - - - 10 - 240 - 501 - 61 - - - - Description - - - - - 10 - 10 - 481 - 41 - - - - none - - - - - - - 190 - 40 - 71 - 16 - - - - Preview of: - - - - - - 270 - 40 - 241 - 16 - - - - none - - - - - - 350 - 310 - 158 - 25 - - - - - - - Close - - - - - - - ? - - - - - - - - - 190 - 60 - 211 - 171 - - - - - - - - - - - SetCurrent - clicked() - DimStyle_Dialog - setCurrentStyle() - - - 404 - 71 - - - 403 - 34 - - - - - new_2 - clicked() - DimStyle_Dialog - createNewStyle() - - - 438 - 102 - - - 467 - 33 - - - - - Mod - clicked() - DimStyle_Dialog - modStyle() - - - 429 - 132 - - - 480 - 129 - - - - - dimStyleList - customContextMenuRequested(QPoint) - DimStyle_Dialog - displayPopupMenu() - - - 97 - 138 - - - 70 - 292 - - - - - TempMod - clicked() - DimStyle_Dialog - temporaryModStyle() - - - 422 - 161 - - - 475 - 160 - - - - - helpButton - clicked() - DimStyle_Dialog - ButtonHELP_Pressed() - - - 423 - 291 - - - 475 - 297 - - - - - Diff - clicked() - DimStyle_Dialog - showDiffBetweenStyles() - - - 430 - 191 - - - 477 - 194 - - - - - closeButton - clicked() - DimStyle_Dialog - accept() - - - 237 - 291 - - - 185 - 291 - - - - - - setCurrentStyle() - createNewStyle() - modStyle() - displayPopupMenu() - temporaryModStyle() - ButtonHELP_Pressed() - showDiffBetweenStyles() - - + + + DimStyle_Dialog + + + Qt::WindowModal + + + + 0 + 0 + 539 + 341 + + + + + 539 + 341 + + + + + 539 + 341 + + + + Dimension style manager + + + + + 20 + 10 + 151 + 16 + + + + Current dimension style: + + + + + + 180 + 10 + 341 + 16 + + + + none + + + + + + 10 + 40 + 171 + 20 + + + + Styles + + + + + + 10 + 60 + 171 + 171 + + + + + + + 410 + 60 + 121 + 23 + + + + Sets the style selected under Styles to current. The current style is applied to dimensions you create. + + + Set current + + + + + + 410 + 90 + 121 + 23 + + + + Define a new dimension style. + + + New... + + + + + + 410 + 120 + 121 + 23 + + + + Modify the selected dimension style. + + + Modify... + + + + + + 410 + 150 + 121 + 23 + + + + Set temporary modifications for the selected style. The temporary modifications will not saved. + + + Override... + + + + + + 410 + 180 + 121 + 23 + + + + Compare two dimension styles or list all the properties of one dimension style. + + + Compare... + + + + + + 10 + 240 + 521 + 61 + + + + Description + + + + + 10 + 10 + 481 + 41 + + + + none + + + + + + + 190 + 40 + 101 + 16 + + + + Preview of: + + + + + + 300 + 40 + 221 + 20 + + + + none + + + + + + 370 + 310 + 158 + 25 + + + + + + + Close + + + + + + + ? + + + + + + + + + 190 + 60 + 211 + 171 + + + + + + + + + + + SetCurrent + clicked() + DimStyle_Dialog + setCurrentStyle() + + + 404 + 71 + + + 403 + 34 + + + + + new_2 + clicked() + DimStyle_Dialog + createNewStyle() + + + 438 + 102 + + + 467 + 33 + + + + + Mod + clicked() + DimStyle_Dialog + modStyle() + + + 429 + 132 + + + 480 + 129 + + + + + dimStyleList + customContextMenuRequested(QPoint) + DimStyle_Dialog + displayPopupMenu() + + + 97 + 138 + + + 70 + 292 + + + + + TempMod + clicked() + DimStyle_Dialog + temporaryModStyle() + + + 422 + 161 + + + 475 + 160 + + + + + helpButton + clicked() + DimStyle_Dialog + ButtonHELP_Pressed() + + + 423 + 291 + + + 475 + 297 + + + + + Diff + clicked() + DimStyle_Dialog + showDiffBetweenStyles() + + + 430 + 191 + + + 477 + 194 + + + + + closeButton + clicked() + DimStyle_Dialog + accept() + + + 237 + 291 + + + 185 + 291 + + + + + + setCurrentStyle() + createNewStyle() + modStyle() + displayPopupMenu() + temporaryModStyle() + ButtonHELP_Pressed() + showDiffBetweenStyles() + + diff --git a/qad_dimstyle_details.ui b/qad_dimstyle_details.ui index 08078fa7..1aa5cdc7 100644 --- a/qad_dimstyle_details.ui +++ b/qad_dimstyle_details.ui @@ -1,2560 +1,2890 @@ - - - DimStyle_Details_Dialog - - - - 0 - 0 - 568 - 446 - - - - QAD - Dimension style details - - - - - 10 - 10 - 551 - 391 - - - - 1 - - - - DB - - - - - 10 - 10 - 261 - 81 - - - - Lines - - - - - 12 - 20 - 51 - 21 - - - - Layer: - - - - - - 70 - 20 - 181 - 22 - - - - Name of the layer for dimension lines. - - - - - - 110 - 50 - 141 - 22 - - - - Field storing the linetype name. - - - - - - 10 - 50 - 81 - 21 - - - - Linetype field: - - - - - - - 9 - 100 - 261 - 111 - - - - Symbols and arrows - - - - - 70 - 20 - 181 - 22 - - - - Name of the layer for dimension symbols and arrows. - - - - - - 10 - 20 - 51 - 21 - - - - Layer: - - - - - - 10 - 50 - 81 - 21 - - - - Symbol field: - - - - - - 110 - 50 - 141 - 22 - - - - Field storing the symbol name. - - - - - - 110 - 80 - 141 - 22 - - - - Field storing the symbol size. - - - - - - 10 - 80 - 81 - 21 - - - - Scale field: - - - - - - - 280 - 220 - 261 - 141 - - - - Generics fields - - - - - 10 - 50 - 81 - 21 - - - - Rotation: - - - - - - 110 - 50 - 141 - 22 - - - - Field storing the rotation of the punctual elements od dimension (symbols, arrows, text). - - - - - - 10 - 20 - 81 - 21 - - - - Color: - - - - - - 110 - 20 - 141 - 22 - - - - Field storing the RGB colorfor all elements of dimension (e.g. "255,255,255,255" = white with total opacity). - - - - - - 10 - 110 - 91 - 21 - - - - Linking ID: - - - - - - 110 - 110 - 141 - 22 - - - - Field storing the ID of the dimension to group all elements (except the text which is the root element). - - - - - - 10 - 80 - 91 - 21 - - - - Component type: - - - - - - 110 - 80 - 141 - 22 - - - - <html><head/><body><p>Field storing the component type of dimension:</p><p>&quot;D1&quot; = Dimension line 1.</p><p>&quot;D2&quot; = Dimension line 2.</p><p>&quot;E1&quot; = Extension line 1.</p><p>&quot;E2&quot; = Extension line 2.</p><p>&quot;L&quot; = Leader.</p><p>&quot;B1&quot; = Block 1.</p><p>&quot;B2&quot; = Block 2.</p><p>&quot;LB&quot; = Leader Block.</p><p>&quot;AB&quot; = Arc Block.</p><p>&quot;D1&quot; = Dimension point 1.</p><p>&quot;D2&quot; = Dimension point 2.</p></body></html> - - - - - - - 10 - 220 - 261 - 141 - - - - Text - - - - - 10 - 50 - 81 - 21 - - - - ID Field: - - - - - - 110 - 50 - 141 - 22 - - - - Field storing the unique code for each dimension. - - - - - - 110 - 80 - 141 - 22 - - - - Field storing the name of the dimension style. - - - - - - 10 - 80 - 91 - 21 - - - - Dim. style field: - - - - - - 10 - 110 - 91 - 21 - - - - Dim. type field: - - - - - - 110 - 110 - 141 - 22 - - - - <html><head/><body><p>Field storing the dimension type:</p><p>&quot;AL&quot; = linear dimension that is aligned with the origin points of the extension lines.</p><p>&quot;AN&quot; = angular dimension, it measures the angle between selected objects or 3 points.</p><p>&quot;BL&quot; = linear, angular, or ordinate dimension from the baseline of the previous or selected dimension.</p><p>&quot;CE&quot; = creates the center mark or the centerlines of circles and arcs.</p><p>&quot;DI&quot; = creates a diameter dimension for a circle or an arc.</p><p>&quot;LD&quot; = creates a line that connects annotation to a feature..</p><p>&quot;LI&quot; = linear dimension with a horizontal, vertical, or rotated dimension line.</p><p>&quot;RA&quot; = radial dimension, measures the radius of a selected circle or arc and displays the dimension text with a radius symbol in front of it.</p><p>&quot;AR&quot; = arc length dimensions measure the distance along an arc or polyline arc segment.</p></body></html> - - - - - - 10 - 20 - 51 - 21 - - - - Layer: - - - - - - 70 - 20 - 181 - 22 - - - - Name of the layer storing dimension texts. - - - - - - - 290 - 20 - 251 - 181 - - - - - - - - - - Lines - - - - - 10 - 10 - 271 - 141 - - - - Dimension lines - - - - - 10 - 20 - 51 - 21 - - - - Color: - - - - - - 80 - 50 - 181 - 20 - - - - Linetype of dimension line. - - - - - - 10 - 50 - 61 - 21 - - - - Linetype: - - - - - - 10 - 110 - 61 - 21 - - - - Suppress: - - - - - - 70 - 110 - 91 - 21 - - - - Suppresses display of dimension line 1. - - - Dim. line 1 - - - - - - 170 - 110 - 91 - 21 - - - - Suppresses display of dimension line 2. - - - Dim. line 2 - - - - - - 80 - 20 - 181 - 23 - - - - - - - - - - - 10 - 190 - 531 - 141 - - - - Extension lines - - - - - 11 - 20 - 51 - 21 - - - - Color: - - - - - - 100 - 50 - 161 - 20 - - - - Linetype for extension line 1. - - - - - - 10 - 50 - 81 - 21 - - - - Linetype ext. 1: - - - - - - 10 - 80 - 81 - 21 - - - - Linetype ext. 2: - - - - - - 100 - 80 - 161 - 20 - - - - Linetype for extension line 2. - - - - - - 10 - 110 - 61 - 21 - - - - Suppress: - - - - - - 70 - 110 - 91 - 21 - - - - Suppresses display of extension line 1. - - - Ext. line 1 - - - - - - 170 - 110 - 91 - 21 - - - - Suppresses display of extension line 2. - - - Ext. line 2 - - - - - - 290 - 20 - 141 - 21 - - - - Extend beyond dim lines: - - - - - - 440 - 20 - 81 - 22 - - - - Specifies a distance to extend the extension lines above the dimension line. - - - 6 - - - 1000000.000000000000000 - - - 0.000500000000000 - - - - - - 440 - 50 - 81 - 22 - - - - Sets the distance to offset the extension lines from the points on the drawing that define the dimension. - - - 6 - - - 1000000.000000000000000 - - - 0.000500000000000 - - - - - - 290 - 50 - 141 - 21 - - - - Offset from origin: - - - - - - 290 - 80 - 231 - 21 - - - - Enables fixed length extension lines. - - - Fixed length extension lines - - - - - - 290 - 110 - 141 - 21 - - - - Length: - - - - - - 440 - 110 - 81 - 22 - - - - Total length of the extension lines starting from the dimension line toward the dimension origin. - - - 6 - - - 1000000.000000000000000 - - - 0.000500000000000 - - - - - - 80 - 20 - 181 - 23 - - - - - - - - - - - Symbols and arrows - - - - - 10 - 10 - 271 - 171 - - - - Arrowheads - - - - - 110 - 20 - 151 - 20 - - - - Arrowhead for the first dimension line. When you change the first arrowhead type, the second arrowhead automatically changes to match it. - - - - - - 10 - 20 - 91 - 21 - - - - Arrowhead 1: - - - - - - 110 - 50 - 151 - 20 - - - - Arrowhead for the second dimension line. - - - - - - 10 - 50 - 91 - 21 - - - - Arrowhead 2: - - - - - - 110 - 80 - 151 - 20 - - - - Arrowhead for the leader line. - - - - - - 10 - 80 - 91 - 21 - - - - Leader: - - - - - - 180 - 110 - 81 - 22 - - - - Arrowhead horizontal size in map units using the symbol scale factor = 1. - - - 6 - - - 1000000.000000000000000 - - - 0.000500000000000 - - - - - - 10 - 110 - 111 - 21 - - - - Arrowhead size: - - - - - - 180 - 140 - 81 - 22 - - - - Arrowhead scale. - - - 6 - - - 1000000.000000000000000 - - - 0.000500000000000 - - - - - - 10 - 140 - 111 - 21 - - - - Arrowhead scale: - - - - - - - Text - - - - - 10 - 10 - 271 - 111 - - - - Text appearance - - - - - 10 - 80 - 111 - 21 - - - - Text height: - - - - - - 180 - 80 - 81 - 22 - - - - Text height in map units. - - - 6 - - - 1000000.000000000000000 - - - 0.000500000000000 - - - - - - 10 - 20 - 91 - 21 - - - - Character type: - - - - - - 100 - 20 - 161 - 22 - - - - Dimension text character type. - - - - - - 80 - 50 - 181 - 23 - - - - - - - - - - 10 - 50 - 51 - 21 - - - - Color: - - - - - - - 10 - 220 - 271 - 141 - - - - Text placement - - - - - 100 - 20 - 161 - 22 - - - - Controls the vertical placement of dimension text in relation to the dimension line. - - - - - - 10 - 20 - 81 - 21 - - - - Vertical: - - - - - - 10 - 50 - 81 - 21 - - - - Horizontal: - - - - - - 100 - 50 - 161 - 22 - - - - Controls the horizontal placement of dimension text along the dimension line, in relation to the extension lines. - - - - - - 10 - 80 - 81 - 21 - - - - View direction: - - - - - - 100 - 80 - 161 - 22 - - - - Controls the dimension text viewing direction. - - - - - - 180 - 110 - 81 - 22 - - - - Sets the current text gap, which is the distance around the dimension text when the dimension line is broken to accommodate the dimension text. - - - 6 - - - 1000000.000000000000000 - - - 0.000500000000000 - - - - - - 10 - 110 - 141 - 21 - - - - Offset from dim line: - - - - - - - 290 - 250 - 241 - 111 - - - - Text alignment - - - - - 10 - 20 - 251 - 17 - - - - Places text in a horizontal position. - - - Horizontal - - - - - - 10 - 40 - 251 - 17 - - - - Text aligned with Dimension Line. - - - Aligned with dimension line - - - - - - 10 - 60 - 251 - 17 - - - - Aligns text with the dimension line when text is inside the extension lines, but aligns it horizontally when text is outside the extension lines. - - - ISO Standard - - - - - - 10 - 80 - 101 - 17 - - - - Place text with fixed angle. - - - Fixed rotation - - - - - - 150 - 80 - 81 - 22 - - - - Text angle when fixed rotation mode is on. - - - 6 - - - 360.000000000000000 - - - 0.000500000000000 - - - - - - - Fit - - - - - 10 - 10 - 271 - 211 - - - - Fit options - - - - - 10 - 20 - 251 - 51 - - - - If there isn't enaugh room to place both text and arrows inside extension lines, the first thing to move outside the extension lines is: - - - true - - - - - - 10 - 80 - 251 - 17 - - - - Moves either the text or the arrowheads outside the extension lines based on the best fit. - - - Either text or arrows (best fit) - - - - - - 10 - 100 - 251 - 17 - - - - Moves arrowheads outside the extension lines first, then text. - - - Arrows - - - - - - 10 - 120 - 251 - 17 - - - - Moves text outside the extension lines first, then arrowheads. - - - Text - - - - - - 10 - 140 - 251 - 17 - - - - When not enough space is available for text and arrowheads, moves both outside the extension lines. - - - Both text and arrows - - - - - - 10 - 160 - 251 - 51 - - - - Suppresses arrowheads if not enough space is available inside the extension lines. - - - Suppress arrows if they don't fit inside ext. lines - - - - - - - Primary units - - - - - 10 - 10 - 271 - 141 - - - - Linear dimensions - - - - - 10 - 20 - 81 - 21 - - - - Precision: - - - - - - 100 - 20 - 161 - 22 - - - - Displays and sets the number of decimal places in the dimension text. - - - - - - 170 - 50 - 91 - 22 - - - - Sets the separator for decimal formats. - - - - - - 10 - 50 - 111 - 21 - - - - Decimal separator: - - - - - - 110 - 80 - 151 - 20 - - - - Includes a prefix that you specify in the dimension text. - - - - - - 10 - 80 - 91 - 21 - - - - Prefix: - - - - - - 10 - 110 - 91 - 21 - - - - Suffix: - - - - - - 110 - 110 - 151 - 20 - - - - Includes a suffix that you specify in the dimension text. - - - - - - - 10 - 160 - 271 - 51 - - - - Zero suppression - - - - - 20 - 20 - 70 - 17 - - - - Suppresses leading zeros in all decimal dimensions (0.5 becomes .5). - - - Leading - - - - - - 170 - 20 - 70 - 17 - - - - Suppresses trailing zeros in all decimal dimensions (5.50 becomes 5.5 and 5.0 becomes 5). - - - Trailing - - - - - - - - - 320 - 410 - 239 - 25 - - - - - - - OK - - - - - - - Cancel - - - - - - - ? - - - - - - - - - - linearLayerName - currentIndexChanged(int) - DimStyle_Details_Dialog - linearLayerNameChanged() - - - 181 - 71 - - - 563 - 119 - - - - - symbolLayerName - currentIndexChanged(int) - DimStyle_Details_Dialog - symbolLayerNameChanged() - - - 169 - 159 - - - 566 - 167 - - - - - textualLayerName - currentIndexChanged(int) - DimStyle_Details_Dialog - textualLayerNameChanged() - - - 194 - 286 - - - 569 - 264 - - - - - textRotModeFixedRot - toggled(bool) - DimStyle_Details_Dialog - textRotModeFixedRotToggled() - - - 360 - 378 - - - 82 - 421 - - - - - extLineIsFixedLen - toggled(bool) - DimStyle_Details_Dialog - extLineIsFixedLenToggled() - - - 329 - 311 - - - 567 - 318 - - - - - helpButton - clicked() - DimStyle_Details_Dialog - ButtonHELP_Pressed() - - - 520 - 422 - - - 564 - 402 - - - - - okButton - clicked() - DimStyle_Details_Dialog - accept() - - - 339 - 426 - - - 56 - 421 - - - - - cancelButton - clicked() - DimStyle_Details_Dialog - reject() - - - 420 - 429 - - - 179 - 442 - - - - - tabWidget - currentChanged(int) - DimStyle_Details_Dialog - currentTabChanged() - - - 369 - 18 - - - 369 - 3 - - - - - lineTypeFieldName - currentIndexChanged(int) - DimStyle_Details_Dialog - linetypeFieldNameChanged() - - - 202 - 102 - - - 291 - 5 - - - - - symbolFieldName - currentIndexChanged(int) - DimStyle_Details_Dialog - symbolFieldNameChanged() - - - 220 - 190 - - - 225 - -2 - - - - - scaleFieldName - currentIndexChanged(int) - DimStyle_Details_Dialog - scaleFieldNameChanged() - - - 155 - 221 - - - 0 - 223 - - - - - idFieldName - currentIndexChanged(int) - DimStyle_Details_Dialog - idFieldNameChanged() - - - 151 - 311 - - - 5 - 313 - - - - - dimStyleFieldName - currentIndexChanged(int) - DimStyle_Details_Dialog - dimStyleFieldNameChanged() - - - 161 - 343 - - - 2 - 344 - - - - - dimTypeFieldName - currentIndexChanged(int) - DimStyle_Details_Dialog - dimTypeFieldNameChanged() - - - 142 - 374 - - - 2 - 378 - - - - - colorFieldName - currentIndexChanged(int) - DimStyle_Details_Dialog - colorFieldNameChanged() - - - 443 - 285 - - - 564 - 290 - - - - - rotFieldName - currentIndexChanged(int) - DimStyle_Details_Dialog - rotFieldNameChanged() - - - 463 - 316 - - - 564 - 320 - - - - - componentFieldName - currentIndexChanged(int) - DimStyle_Details_Dialog - componentFieldNameChanged() - - - 466 - 347 - - - 563 - 348 - - - - - idParentFieldName - currentIndexChanged(int) - DimStyle_Details_Dialog - idParentFieldNameChanged() - - - 486 - 372 - - - 566 - 376 - - - - - dimLineLineType - textChanged(QString) - DimStyle_Details_Dialog - dimLineLineTypeChanged() - - - 123 - 100 - - - 5 - 102 - - - - - dimLine1Hide - toggled(bool) - DimStyle_Details_Dialog - dimLine1HideToggled() - - - 99 - 163 - - - 5 - 155 - - - - - dimLine2Hide - toggled(bool) - DimStyle_Details_Dialog - dimLine2HideToggled() - - - 217 - 164 - - - 2 - 185 - - - - - extLine1LineType - textChanged(QString) - DimStyle_Details_Dialog - extLine1LineTypeChanged() - - - 143 - 284 - - - 1 - 272 - - - - - extLine2LineType - textChanged(QString) - DimStyle_Details_Dialog - extLine2LineTypeChanged() - - - 154 - 314 - - - 0 - 309 - - - - - extLine1Hide - toggled(bool) - DimStyle_Details_Dialog - extLine1HideToggled() - - - 98 - 343 - - - 4 - 349 - - - - - extLine2Hide - toggled(bool) - DimStyle_Details_Dialog - extLine2HideToggled() - - - 238 - 342 - - - 3 - 386 - - - - - extLineOffsetDimLine - valueChanged(double) - DimStyle_Details_Dialog - extLineOffsetDimLineChanged() - - - 496 - 254 - - - 567 - 209 - - - - - extLineOffsetOrigPoints - valueChanged(double) - DimStyle_Details_Dialog - extLineOffsetOrigPointsChanged() - - - 524 - 281 - - - 567 - 279 - - - - - extLineFixedLen - valueChanged(double) - DimStyle_Details_Dialog - extLineFixedLenChanged() - - - 476 - 346 - - - 565 - 406 - - - - - block1Name - textChanged(QString) - DimStyle_Details_Dialog - block1NameChanged() - - - 157 - 77 - - - 5 - 72 - - - - - block2Name - textChanged(QString) - DimStyle_Details_Dialog - block2NameChanged() - - - 195 - 103 - - - 4 - 104 - - - - - blockLeaderName - textChanged(QString) - DimStyle_Details_Dialog - blockLeaderNameChanged() - - - 151 - 133 - - - -8 - 140 - - - - - blockWidth - valueChanged(double) - DimStyle_Details_Dialog - blockWidthChanged() - - - 244 - 166 - - - 5 - 164 - - - - - blockScale - valueChanged(double) - DimStyle_Details_Dialog - blockScaleChanged() - - - 239 - 193 - - - 5 - 194 - - - - - textFont - currentIndexChanged(int) - DimStyle_Details_Dialog - textFontChanged() - - - 186 - 73 - - - 2 - 68 - - - - - textHeight - valueChanged(double) - DimStyle_Details_Dialog - textHeightChanged() - - - 237 - 143 - - - -9 - 112 - - - - - textVerticalPos - currentIndexChanged(int) - DimStyle_Details_Dialog - textVerticalPosChanged() - - - 232 - 293 - - - 6 - 158 - - - - - textHorizontalPos - currentIndexChanged(int) - DimStyle_Details_Dialog - textHorizontalPosChanged() - - - 189 - 323 - - - 5 - 189 - - - - - textDirection - currentIndexChanged(int) - DimStyle_Details_Dialog - textDirectionChanged() - - - 210 - 353 - - - 2 - 222 - - - - - textOffsetDist - valueChanged(double) - DimStyle_Details_Dialog - textOffsetDistChanged() - - - 246 - 383 - - - 5 - 255 - - - - - textForcedRot - valueChanged(double) - DimStyle_Details_Dialog - textForcedRotChanged() - - - 505 - 383 - - - 4 - 299 - - - - - blockSuppressionForNoSpace - toggled(bool) - DimStyle_Details_Dialog - blockSuppressionForNoSpaceToggled() - - - 39 - 227 - - - 4 - 229 - - - - - textDecimals - currentIndexChanged(int) - DimStyle_Details_Dialog - textDecimalsChanged() - - - 144 - 71 - - - 5 - 59 - - - - - textDecimalSep - currentIndexChanged(int) - DimStyle_Details_Dialog - textDecimalSepChanged() - - - 242 - 102 - - - 0 - 102 - - - - - textPrefix - textChanged(QString) - DimStyle_Details_Dialog - textPrefixChanged() - - - 234 - 134 - - - 5 - 133 - - - - - textSuffix - textChanged(QString) - DimStyle_Details_Dialog - textSuffixChanged() - - - 210 - 161 - - - 0 - 164 - - - - - textSuppressLeadingZeros - toggled(bool) - DimStyle_Details_Dialog - textSuppressLeadingZerosToggled() - - - 63 - 218 - - - 0 - 216 - - - - - textDecimalZerosSuppression - toggled(bool) - DimStyle_Details_Dialog - textDecimalZerosSuppressionToggled() - - - 210 - 220 - - - 4 - 33 - - - - - textRotModeHorizontal - toggled(bool) - DimStyle_Details_Dialog - textRotModeHorizontalToggled() - - - 338 - 308 - - - 567 - 146 - - - - - textRotModeAligned - toggled(bool) - DimStyle_Details_Dialog - textRotModeAlignedToggled() - - - 427 - 330 - - - 566 - 268 - - - - - textRotModeISO - toggled(bool) - DimStyle_Details_Dialog - textRotModeISOToggled() - - - 382 - 350 - - - 566 - 293 - - - - - textBlockAdjustWhicheverFitsBestOutside - toggled(bool) - DimStyle_Details_Dialog - textBlockAdjustWhicheverFitsBestOutsideToggled() - - - 118 - 127 - - - 4 - 82 - - - - - textBlockAdjustFirstSymbolOutside - toggled(bool) - DimStyle_Details_Dialog - textBlockAdjustFirstSymbolOutsideToggled() - - - 53 - 156 - - - 1 - 140 - - - - - textBlockAdjustFirstTextOutside - toggled(bool) - DimStyle_Details_Dialog - textBlockAdjustFirstTextOutsideToggled() - - - 41 - 169 - - - 1 - 173 - - - - - textBlockAdjustBothOutside - toggled(bool) - DimStyle_Details_Dialog - textBlockAdjustBothOutsideToggled() - - - 98 - 187 - - - 8 - 208 - - - - - - linearLayerNameChanged() - symbolLayerNameChanged() - textualLayerNameChanged() - textRotModeFixedRotToggled() - extLineIsFixedLenToggled() - ButtonHELP_Pressed() - currentTabChanged() - redrawDimOnDBTabChanged() - linetypeFieldNameChanged() - symbolFieldNameChanged() - scaleFieldNameChanged() - idFieldNameChanged() - dimStyleFieldNameChanged() - dimTypeFieldNameChanged() - colorFieldNameChanged() - rotFieldNameChanged() - componentFieldNameChanged() - idParentFieldNameChanged() - dimLineLineTypeChanged() - dimLine1HideToggled() - dimLine2HideToggled() - extLine1LineTypeChanged() - extLine2LineTypeChanged() - extLine1HideToggled() - extLine2HideToggled() - extLineOffsetDimLineChanged() - extLineOffsetOrigPointsChanged() - extLineIsFixedLengthChanged() - extLineFixedLenChanged() - block1NameChanged() - block2NameChanged() - blockLeaderNameChanged() - blockWidthChanged() - blockScaleChanged() - textFontChanged() - textHeightChanged() - textVerticalPosChanged() - textHorizontalPosChanged() - textDirectionChanged() - textOffsetDistChanged() - textForcedRotChanged() - blockSuppressionForNoSpaceToggled() - textDecimalsChanged() - textDecimalSepChanged() - textPrefixChanged() - textSuffixChanged() - textSuppressLeadingZerosToggled() - textDecimalZerosSuppressionToggled() - textRotModeHorizontalToggled() - textRotModeAlignedToggled() - textRotModeISOToggled() - textBlockAdjustWhicheverFitsBestOutsideToggled() - textBlockAdjustFirstSymbolOutsideToggled() - textBlockAdjustFirstTextOutsideToggled() - textBlockAdjustBothOutsideToggled() - - + + + DimStyle_Details_Dialog + + + + 0 + 0 + 637 + 446 + + + + + 600 + 446 + + + + + 700 + 446 + + + + Dimension style details + + + + + 10 + 10 + 621 + 391 + + + + 1 + + + + DB + + + + + 10 + 10 + 291 + 81 + + + + Lines + + + + + 12 + 20 + 81 + 21 + + + + Layer: + + + + + + 100 + 20 + 181 + 22 + + + + Name of the layer for dimension lines. + + + + + + 140 + 50 + 141 + 22 + + + + Field storing the linetype name. + + + + + + 10 + 50 + 121 + 21 + + + + Linetype field: + + + + + + + 9 + 100 + 291 + 111 + + + + Symbols and arrows + + + + + 100 + 20 + 181 + 22 + + + + Name of the layer for dimension symbols and arrows. + + + + + + 10 + 20 + 81 + 21 + + + + Layer: + + + + + + 10 + 50 + 121 + 21 + + + + Symbol field: + + + + + + 140 + 50 + 141 + 22 + + + + Field storing the symbol name. + + + + + + 140 + 80 + 141 + 22 + + + + Field storing the symbol size. + + + + + + 10 + 80 + 121 + 21 + + + + Scale field: + + + + + + + 330 + 220 + 271 + 141 + + + + Generics fields + + + + + 10 + 50 + 101 + 21 + + + + Rotation: + + + + + + 120 + 50 + 141 + 22 + + + + Field storing the rotation of the punctual elements od dimension (symbols, arrows, text). + + + + + + 10 + 20 + 101 + 21 + + + + Color: + + + + + + 120 + 20 + 141 + 22 + + + + Field storing the RGB colorfor all elements of dimension (e.g. "255,255,255,255" = white with total opacity). + + + + + + 10 + 110 + 101 + 21 + + + + Linking ID: + + + + + + 120 + 110 + 141 + 22 + + + + Field storing the ID of the dimension to group all elements (except the text which is the root element). + + + + + + 10 + 80 + 101 + 21 + + + + Component type: + + + + + + 120 + 80 + 141 + 22 + + + + <html><head/><body><p>Field storing the component type of dimension:</p><p>&quot;D1&quot; = Dimension line 1.</p><p>&quot;D2&quot; = Dimension line 2.</p><p>&quot;E1&quot; = Extension line 1.</p><p>&quot;E2&quot; = Extension line 2.</p><p>&quot;L&quot; = Leader.</p><p>&quot;B1&quot; = Block 1.</p><p>&quot;B2&quot; = Block 2.</p><p>&quot;LB&quot; = Leader Block.</p><p>&quot;AB&quot; = Arc Block.</p><p>&quot;D1&quot; = Dimension point 1.</p><p>&quot;D2&quot; = Dimension point 2.</p></body></html> + + + + + + + 10 + 220 + 291 + 141 + + + + Text + + + + + 10 + 50 + 121 + 21 + + + + ID Field: + + + + + + 140 + 50 + 141 + 22 + + + + Field storing the unique code for each dimension. + + + + + + 140 + 80 + 141 + 22 + + + + Field storing the name of the dimension style. + + + + + + 10 + 80 + 121 + 21 + + + + Dim. style field: + + + + + + 10 + 110 + 121 + 21 + + + + Dim. type field: + + + + + + 140 + 110 + 141 + 22 + + + + <html><head/><body><p>Field storing the dimension type:</p><p>&quot;AL&quot; = linear dimension that is aligned with the origin points of the extension lines.</p><p>&quot;AN&quot; = angular dimension, it measures the angle between selected objects or 3 points.</p><p>&quot;BL&quot; = linear, angular, or ordinate dimension from the baseline of the previous or selected dimension.</p><p>&quot;CE&quot; = creates the center mark or the centerlines of circles and arcs.</p><p>&quot;DI&quot; = creates a diameter dimension for a circle or an arc.</p><p>&quot;LD&quot; = creates a line that connects annotation to a feature..</p><p>&quot;LI&quot; = linear dimension with a horizontal, vertical, or rotated dimension line.</p><p>&quot;RA&quot; = radial dimension, measures the radius of a selected circle or arc and displays the dimension text with a radius symbol in front of it.</p><p>&quot;AR&quot; = arc length dimensions measure the distance along an arc or polyline arc segment.</p></body></html> + + + + + + 10 + 20 + 81 + 21 + + + + Layer: + + + + + + 100 + 20 + 181 + 22 + + + + Name of the layer storing dimension texts. + + + + + + + 340 + 20 + 251 + 181 + + + + + + + + + + Lines + + + + + 10 + 10 + 271 + 141 + + + + Dimension lines + + + + + 10 + 20 + 71 + 21 + + + + Color: + + + + + + 90 + 50 + 171 + 20 + + + + Linetype of dimension line. + + + + + + 10 + 50 + 71 + 21 + + + + Linetype: + + + + + + 10 + 110 + 61 + 21 + + + + Suppress: + + + + + + 70 + 110 + 91 + 21 + + + + Suppresses display of dimension line 1. + + + Dim. line 1 + + + + + + 170 + 110 + 91 + 21 + + + + Suppresses display of dimension line 2. + + + Dim. line 2 + + + + + + 90 + 20 + 171 + 23 + + + + + + + + + + 10 + 80 + 161 + 21 + + + + Extend beyond ticks: + + + + + + 180 + 80 + 81 + 22 + + + + Specifies a distance to extend the dimension line above the extension lines. + + + 6 + + + 1000000.000000000000000 + + + 0.000500000000000 + + + + + + + 10 + 210 + 591 + 141 + + + + Extension lines + + + + + 11 + 20 + 81 + 21 + + + + Color: + + + + + + 130 + 50 + 161 + 20 + + + + Linetype for extension line 1. + + + + + + 10 + 50 + 111 + 21 + + + + Linetype ext. 1: + + + + + + 10 + 80 + 111 + 21 + + + + Linetype ext. 2: + + + + + + 130 + 80 + 161 + 20 + + + + Linetype for extension line 2. + + + + + + 10 + 110 + 61 + 21 + + + + Suppress: + + + + + + 80 + 110 + 101 + 21 + + + + Suppresses display of extension line 1. + + + Ext. line 1 + + + + + + 190 + 110 + 101 + 21 + + + + Suppresses display of extension line 2. + + + Ext. line 2 + + + + + + 310 + 20 + 181 + 21 + + + + Extend beyond dim lines: + + + + + + 500 + 20 + 81 + 22 + + + + Specifies a distance to extend the extension lines above the dimension line. + + + 6 + + + 1000000.000000000000000 + + + 0.000500000000000 + + + + + + 500 + 50 + 81 + 22 + + + + Sets the distance to offset the extension lines from the points on the drawing that define the dimension. + + + 6 + + + 1000000.000000000000000 + + + 0.000500000000000 + + + + + + 310 + 50 + 181 + 21 + + + + Offset from origin: + + + + + + 310 + 80 + 271 + 21 + + + + Enables fixed length extension lines. + + + Fixed length extension lines + + + + + + 310 + 110 + 181 + 21 + + + + Length: + + + + + + 500 + 110 + 81 + 22 + + + + Total length of the extension lines starting from the dimension line toward the dimension origin. + + + 6 + + + 1000000.000000000000000 + + + 0.000500000000000 + + + + + + 110 + 20 + 181 + 23 + + + + + + + + + + + Symbols and arrows + + + + + 10 + 10 + 271 + 171 + + + + Arrowheads + + + + + 110 + 20 + 151 + 20 + + + + Arrowhead for the first dimension line. When you change the first arrowhead type, the second arrowhead automatically changes to match it. + + + + + + 10 + 20 + 91 + 21 + + + + Arrowhead 1: + + + + + + 110 + 50 + 151 + 20 + + + + Arrowhead for the second dimension line. + + + + + + 10 + 50 + 91 + 21 + + + + Arrowhead 2: + + + + + + 110 + 80 + 151 + 20 + + + + Arrowhead for the leader line. + + + + + + 10 + 80 + 91 + 21 + + + + Leader: + + + + + + 180 + 110 + 81 + 22 + + + + Arrowhead horizontal size in map units using the symbol scale factor = 1. + + + 6 + + + 1000000.000000000000000 + + + 0.000500000000000 + + + + + + 10 + 110 + 111 + 21 + + + + Arrowhead size: + + + + + + 180 + 140 + 81 + 22 + + + + Arrowhead scale. + + + 6 + + + 1000000.000000000000000 + + + 0.000500000000000 + + + + + + 10 + 140 + 111 + 21 + + + + Arrowhead scale: + + + + + + + 350 + 220 + 231 + 81 + + + + Arc length symbol + + + + + 10 + 20 + 211 + 17 + + + + Places arc length symbols before the dimension text. + + + Preceding dimension text + + + + + + 10 + 40 + 211 + 17 + + + + Places arc length symbols above the dimension text. + + + Above dimension text + + + + + + 10 + 60 + 211 + 17 + + + + Suppresses the display of arc length symbols. + + + None + + + + + + + 10 + 190 + 271 + 81 + + + + Center marks + + + + + 10 + 20 + 82 + 17 + + + + Creates no center mark or centerline. + + + None + + + + + + 10 + 40 + 82 + 17 + + + + Creates a center mark. + + + Mark + + + + + + 10 + 60 + 82 + 17 + + + + Creates a centerline. + + + Line + + + + + + 180 + 30 + 81 + 22 + + + + Size of the center mark or centerline. + + + 6 + + + 1000000.000000000000000 + + + 0.000500000000000 + + + + + + + Text + + + + + 10 + 10 + 271 + 111 + + + + Text appearance + + + + + 10 + 80 + 111 + 21 + + + + Text height: + + + + + + 180 + 80 + 81 + 22 + + + + Text height in map units. + + + 6 + + + 1000000.000000000000000 + + + 0.000500000000000 + + + + + + 10 + 20 + 91 + 21 + + + + Character type: + + + + + + 100 + 20 + 161 + 22 + + + + Dimension text character type. + + + + + + 80 + 50 + 181 + 23 + + + + + + + + + + 10 + 50 + 51 + 21 + + + + Color: + + + + + + + 10 + 220 + 291 + 141 + + + + Text placement + + + + + 120 + 20 + 161 + 22 + + + + Controls the vertical placement of dimension text in relation to the dimension line. + + + + + + 10 + 20 + 101 + 21 + + + + Vertical: + + + + + + 10 + 50 + 101 + 21 + + + + Horizontal: + + + + + + 120 + 50 + 161 + 22 + + + + Controls the horizontal placement of dimension text along the dimension line, in relation to the extension lines. + + + + + + 10 + 80 + 101 + 21 + + + + View direction: + + + + + + 120 + 80 + 161 + 22 + + + + Controls the dimension text viewing direction. + + + + + + 200 + 110 + 81 + 22 + + + + Sets the current text gap, which is the distance around the dimension text when the dimension line is broken to accommodate the dimension text. + + + 6 + + + 1000000.000000000000000 + + + 0.000500000000000 + + + + + + 10 + 110 + 181 + 21 + + + + Offset from dim line: + + + + + + + 340 + 250 + 251 + 111 + + + + Text alignment + + + + + 10 + 20 + 251 + 17 + + + + Places text in a horizontal position. + + + Horizontal + + + + + + 10 + 40 + 251 + 17 + + + + Text aligned with Dimension Line. + + + Aligned with dimension line + + + + + + 10 + 60 + 251 + 17 + + + + Aligns text with the dimension line when text is inside the extension lines, but aligns it horizontally when text is outside the extension lines. + + + ISO Standard + + + + + + 10 + 80 + 101 + 17 + + + + Place text with fixed angle. + + + Fixed rotation + + + + + + 160 + 80 + 81 + 22 + + + + Text angle when fixed rotation mode is on. + + + 6 + + + 360.000000000000000 + + + 0.000500000000000 + + + + + + + Fit + + + + + 10 + 10 + 301 + 211 + + + + Fit options + + + + + 10 + 20 + 251 + 51 + + + + If there isn't enaugh room to place both text and arrows inside extension lines, the first thing to move outside the extension lines is: + + + true + + + + + + 10 + 80 + 251 + 17 + + + + Moves either the text or the arrowheads outside the extension lines based on the best fit. + + + Either text or arrows (best fit) + + + + + + 10 + 100 + 251 + 17 + + + + Moves arrowheads outside the extension lines first, then text. + + + Arrows + + + + + + 10 + 120 + 251 + 17 + + + + Moves text outside the extension lines first, then arrowheads. + + + Text + + + + + + 10 + 140 + 251 + 17 + + + + When not enough space is available for text and arrowheads, moves both outside the extension lines. + + + Both text and arrows + + + + + + 10 + 160 + 281 + 51 + + + + Suppresses arrowheads if not enough space is available inside the extension lines. + + + false + + + Suppress arrows if they don't fit inside ext. lines + + + + + + + Primary units + + + + + 10 + 10 + 271 + 141 + + + + Linear dimensions + + + + + 10 + 20 + 81 + 21 + + + + Precision: + + + + + + 100 + 20 + 161 + 22 + + + + Displays and sets the number of decimal places in the dimension text. + + + + + + 170 + 50 + 91 + 22 + + + + Sets the separator for decimal formats. + + + + + + 10 + 50 + 111 + 21 + + + + Decimal separator: + + + + + + 110 + 80 + 151 + 20 + + + + Includes a prefix that you specify in the dimension text. + + + + + + 10 + 80 + 91 + 21 + + + + Prefix: + + + + + + 10 + 110 + 91 + 21 + + + + Suffix: + + + + + + 110 + 110 + 151 + 20 + + + + Includes a suffix that you specify in the dimension text. + + + + + + + 10 + 160 + 271 + 51 + + + + Zero suppression + + + + + 20 + 20 + 131 + 17 + + + + Suppresses leading zeros in all decimal dimensions (0.5 becomes .5). + + + Leading + + + + + + 170 + 20 + 91 + 17 + + + + Suppresses trailing zeros in all decimal dimensions (5.50 becomes 5.5 and 5.0 becomes 5). + + + Trailing + + + + + + + + + 390 + 410 + 239 + 25 + + + + + + + OK + + + + + + + Cancel + + + + + + + ? + + + + + + + + + + linearLayerName + currentIndexChanged(int) + DimStyle_Details_Dialog + linearLayerNameChanged() + + + 181 + 71 + + + 563 + 119 + + + + + symbolLayerName + currentIndexChanged(int) + DimStyle_Details_Dialog + symbolLayerNameChanged() + + + 169 + 159 + + + 566 + 167 + + + + + textualLayerName + currentIndexChanged(int) + DimStyle_Details_Dialog + textualLayerNameChanged() + + + 194 + 286 + + + 569 + 264 + + + + + textRotModeFixedRot + toggled(bool) + DimStyle_Details_Dialog + textRotModeFixedRotToggled() + + + 360 + 378 + + + 82 + 421 + + + + + extLineIsFixedLen + toggled(bool) + DimStyle_Details_Dialog + extLineIsFixedLenToggled() + + + 331 + 333 + + + 567 + 318 + + + + + helpButton + clicked() + DimStyle_Details_Dialog + ButtonHELP_Pressed() + + + 520 + 422 + + + 564 + 402 + + + + + okButton + clicked() + DimStyle_Details_Dialog + accept() + + + 351 + 427 + + + 56 + 421 + + + + + cancelButton + clicked() + DimStyle_Details_Dialog + reject() + + + 502 + 430 + + + 179 + 442 + + + + + tabWidget + currentChanged(int) + DimStyle_Details_Dialog + currentTabChanged() + + + 369 + 18 + + + 369 + 3 + + + + + lineTypeFieldName + currentIndexChanged(int) + DimStyle_Details_Dialog + linetypeFieldNameChanged() + + + 202 + 102 + + + 291 + 5 + + + + + symbolFieldName + currentIndexChanged(int) + DimStyle_Details_Dialog + symbolFieldNameChanged() + + + 220 + 190 + + + 225 + -2 + + + + + scaleFieldName + currentIndexChanged(int) + DimStyle_Details_Dialog + scaleFieldNameChanged() + + + 155 + 221 + + + 0 + 223 + + + + + idFieldName + currentIndexChanged(int) + DimStyle_Details_Dialog + idFieldNameChanged() + + + 151 + 311 + + + 5 + 313 + + + + + dimStyleFieldName + currentIndexChanged(int) + DimStyle_Details_Dialog + dimStyleFieldNameChanged() + + + 161 + 343 + + + 2 + 344 + + + + + dimTypeFieldName + currentIndexChanged(int) + DimStyle_Details_Dialog + dimTypeFieldNameChanged() + + + 142 + 374 + + + 2 + 378 + + + + + colorFieldName + currentIndexChanged(int) + DimStyle_Details_Dialog + colorFieldNameChanged() + + + 443 + 285 + + + 564 + 290 + + + + + rotFieldName + currentIndexChanged(int) + DimStyle_Details_Dialog + rotFieldNameChanged() + + + 463 + 316 + + + 564 + 320 + + + + + componentFieldName + currentIndexChanged(int) + DimStyle_Details_Dialog + componentFieldNameChanged() + + + 466 + 347 + + + 563 + 348 + + + + + idParentFieldName + currentIndexChanged(int) + DimStyle_Details_Dialog + idParentFieldNameChanged() + + + 486 + 372 + + + 566 + 376 + + + + + dimLineLineType + textChanged(QString) + DimStyle_Details_Dialog + dimLineLineTypeChanged() + + + 123 + 100 + + + 5 + 102 + + + + + dimLine1Hide + toggled(bool) + DimStyle_Details_Dialog + dimLine1HideToggled() + + + 99 + 163 + + + 5 + 155 + + + + + dimLine2Hide + toggled(bool) + DimStyle_Details_Dialog + dimLine2HideToggled() + + + 217 + 164 + + + 2 + 185 + + + + + extLine1LineType + textChanged(QString) + DimStyle_Details_Dialog + extLine1LineTypeChanged() + + + 145 + 306 + + + 1 + 272 + + + + + extLine2LineType + textChanged(QString) + DimStyle_Details_Dialog + extLine2LineTypeChanged() + + + 156 + 336 + + + 0 + 309 + + + + + extLine1Hide + toggled(bool) + DimStyle_Details_Dialog + extLine1HideToggled() + + + 102 + 365 + + + 4 + 349 + + + + + extLine2Hide + toggled(bool) + DimStyle_Details_Dialog + extLine2HideToggled() + + + 240 + 364 + + + 3 + 386 + + + + + extLineOffsetDimLine + valueChanged(double) + DimStyle_Details_Dialog + extLineOffsetDimLineChanged() + + + 498 + 276 + + + 567 + 209 + + + + + extLineOffsetOrigPoints + valueChanged(double) + DimStyle_Details_Dialog + extLineOffsetOrigPointsChanged() + + + 526 + 303 + + + 567 + 279 + + + + + extLineFixedLen + valueChanged(double) + DimStyle_Details_Dialog + extLineFixedLenChanged() + + + 482 + 368 + + + 565 + 406 + + + + + block1Name + textChanged(QString) + DimStyle_Details_Dialog + block1NameChanged() + + + 157 + 77 + + + 5 + 72 + + + + + block2Name + textChanged(QString) + DimStyle_Details_Dialog + block2NameChanged() + + + 195 + 103 + + + 4 + 104 + + + + + blockLeaderName + textChanged(QString) + DimStyle_Details_Dialog + blockLeaderNameChanged() + + + 151 + 133 + + + -8 + 140 + + + + + blockWidth + valueChanged(double) + DimStyle_Details_Dialog + blockWidthChanged() + + + 244 + 166 + + + 5 + 164 + + + + + blockScale + valueChanged(double) + DimStyle_Details_Dialog + blockScaleChanged() + + + 239 + 193 + + + 5 + 194 + + + + + textFont + currentIndexChanged(int) + DimStyle_Details_Dialog + textFontChanged() + + + 186 + 73 + + + 2 + 68 + + + + + textHeight + valueChanged(double) + DimStyle_Details_Dialog + textHeightChanged() + + + 237 + 143 + + + -9 + 112 + + + + + textVerticalPos + currentIndexChanged(int) + DimStyle_Details_Dialog + textVerticalPosChanged() + + + 232 + 293 + + + 6 + 158 + + + + + textHorizontalPos + currentIndexChanged(int) + DimStyle_Details_Dialog + textHorizontalPosChanged() + + + 189 + 323 + + + 5 + 189 + + + + + textDirection + currentIndexChanged(int) + DimStyle_Details_Dialog + textDirectionChanged() + + + 210 + 353 + + + 2 + 222 + + + + + textOffsetDist + valueChanged(double) + DimStyle_Details_Dialog + textOffsetDistChanged() + + + 246 + 383 + + + 5 + 255 + + + + + textForcedRot + valueChanged(double) + DimStyle_Details_Dialog + textForcedRotChanged() + + + 505 + 383 + + + 4 + 299 + + + + + blockSuppressionForNoSpace + toggled(bool) + DimStyle_Details_Dialog + blockSuppressionForNoSpaceToggled() + + + 39 + 227 + + + 4 + 229 + + + + + textDecimals + currentIndexChanged(int) + DimStyle_Details_Dialog + textDecimalsChanged() + + + 144 + 71 + + + 5 + 59 + + + + + textDecimalSep + currentIndexChanged(int) + DimStyle_Details_Dialog + textDecimalSepChanged() + + + 242 + 102 + + + 0 + 102 + + + + + textPrefix + textChanged(QString) + DimStyle_Details_Dialog + textPrefixChanged() + + + 234 + 134 + + + 5 + 133 + + + + + textSuffix + textChanged(QString) + DimStyle_Details_Dialog + textSuffixChanged() + + + 210 + 161 + + + 0 + 164 + + + + + textSuppressLeadingZeros + toggled(bool) + DimStyle_Details_Dialog + textSuppressLeadingZerosToggled() + + + 63 + 218 + + + 0 + 216 + + + + + textDecimalZerosSuppression + toggled(bool) + DimStyle_Details_Dialog + textDecimalZerosSuppressionToggled() + + + 210 + 220 + + + 4 + 33 + + + + + textRotModeHorizontal + toggled(bool) + DimStyle_Details_Dialog + textRotModeHorizontalToggled() + + + 338 + 308 + + + 567 + 146 + + + + + textRotModeAligned + toggled(bool) + DimStyle_Details_Dialog + textRotModeAlignedToggled() + + + 427 + 330 + + + 566 + 268 + + + + + textRotModeISO + toggled(bool) + DimStyle_Details_Dialog + textRotModeISOToggled() + + + 382 + 350 + + + 566 + 293 + + + + + textBlockAdjustWhicheverFitsBestOutside + toggled(bool) + DimStyle_Details_Dialog + textBlockAdjustWhicheverFitsBestOutsideToggled() + + + 118 + 127 + + + 4 + 82 + + + + + textBlockAdjustFirstSymbolOutside + toggled(bool) + DimStyle_Details_Dialog + textBlockAdjustFirstSymbolOutsideToggled() + + + 53 + 156 + + + 1 + 140 + + + + + textBlockAdjustFirstTextOutside + toggled(bool) + DimStyle_Details_Dialog + textBlockAdjustFirstTextOutsideToggled() + + + 41 + 169 + + + 1 + 173 + + + + + textBlockAdjustBothOutside + toggled(bool) + DimStyle_Details_Dialog + textBlockAdjustBothOutsideToggled() + + + 98 + 187 + + + 8 + 208 + + + + + arcSymbolPreceding + toggled(bool) + DimStyle_Details_Dialog + arcSymbolPrecedingToggled() + + + 384 + 288 + + + 295 + 405 + + + + + arcSymbolAbove + toggled(bool) + DimStyle_Details_Dialog + arcSymbolAboveToggled() + + + 410 + 308 + + + 264 + 426 + + + + + arcSymbolNone + toggled(bool) + DimStyle_Details_Dialog + arcSymbolNoneToggled() + + + 346 + 328 + + + 480 + 402 + + + + + dimLineOffsetExtLine + valueChanged(double) + DimStyle_Details_Dialog + dimLineOffsetExtLineChanged() + + + 243 + 135 + + + 93 + 434 + + + + + centerMarkNone + toggled(bool) + DimStyle_Details_Dialog + centerMarkNoneChanged() + + + 53 + 253 + + + 0 + 243 + + + + + centerMarkMark + toggled(bool) + DimStyle_Details_Dialog + centerMarkMarkChanged() + + + 41 + 264 + + + 0 + 272 + + + + + centerMarkLine + toggled(bool) + DimStyle_Details_Dialog + centerMarkLineChanged() + + + 62 + 295 + + + 0 + 300 + + + + + centerMarkLength + valueChanged(double) + DimStyle_Details_Dialog + centerMarkLengthChanged() + + + 219 + 266 + + + 0 + 232 + + + + + + linearLayerNameChanged() + symbolLayerNameChanged() + textualLayerNameChanged() + textRotModeFixedRotToggled() + extLineIsFixedLenToggled() + ButtonHELP_Pressed() + currentTabChanged() + redrawDimOnDBTabChanged() + linetypeFieldNameChanged() + symbolFieldNameChanged() + scaleFieldNameChanged() + idFieldNameChanged() + dimStyleFieldNameChanged() + dimTypeFieldNameChanged() + colorFieldNameChanged() + rotFieldNameChanged() + componentFieldNameChanged() + idParentFieldNameChanged() + dimLineLineTypeChanged() + dimLine1HideToggled() + dimLine2HideToggled() + extLine1LineTypeChanged() + extLine2LineTypeChanged() + extLine1HideToggled() + extLine2HideToggled() + extLineOffsetDimLineChanged() + extLineOffsetOrigPointsChanged() + extLineIsFixedLengthChanged() + extLineFixedLenChanged() + block1NameChanged() + block2NameChanged() + blockLeaderNameChanged() + blockWidthChanged() + blockScaleChanged() + textFontChanged() + textHeightChanged() + textVerticalPosChanged() + textHorizontalPosChanged() + textDirectionChanged() + textOffsetDistChanged() + textForcedRotChanged() + blockSuppressionForNoSpaceToggled() + textDecimalsChanged() + textDecimalSepChanged() + textPrefixChanged() + textSuffixChanged() + textSuppressLeadingZerosToggled() + textDecimalZerosSuppressionToggled() + textRotModeHorizontalToggled() + textRotModeAlignedToggled() + textRotModeISOToggled() + textBlockAdjustWhicheverFitsBestOutsideToggled() + textBlockAdjustFirstSymbolOutsideToggled() + textBlockAdjustFirstTextOutsideToggled() + textBlockAdjustBothOutsideToggled() + arcSymbolPrecedingToggled() + arcSymbolAboveToggled() + arcSymbolNoneToggled() + dimLineOffsetExtLineChanged() + centerMarkNoneChanged() + centerMarkMarkChanged() + centerMarkLineChanged() + centerMarkLengthChanged() + + diff --git a/qad_dimstyle_details_dlg.py b/qad_dimstyle_details_dlg.py index c2933ba3..214f0fca 100644 --- a/qad_dimstyle_details_dlg.py +++ b/qad_dimstyle_details_dlg.py @@ -1,1038 +1,1126 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - classe per gestire la dialog per DIMSTYLE - - ------------------- - begin : 2015-05-19 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -from qgis.core import QgsApplication -from qgis.utils import * -from qgis.gui import * - -import qad_dimstyle_details_ui - -from qad_variables import * -from qad_dim import * -from qad_msg import QadMsg, qadShowPluginHelp -import qad_layer -import qad_utils - - -####################################################################################### -# Classe che gestisce l'interfaccia grafica della funzione di creazione nuovo stile -class QadDIMSTYLE_DETAILS_Dialog(QDialog, QObject, qad_dimstyle_details_ui.Ui_DimStyle_Details_Dialog): - def __init__(self, plugIn, dimStyle): - self.plugIn = plugIn - self.dimStyle = QadDimStyle(dimStyle) # copio lo stile di quotatura - self.iface = self.plugIn.iface.mainWindow() - QDialog.__init__(self, self.iface) - - self.onInit = False # vero se si è in fase di inizializzazione - - self.setupUi(self) - - self.init_db_tab() - self.init_lines_tab() - self.init_symbols_tab() - self.init_text_tab() - self.init_adjust_tab() - self.init_primaryUnits_tab() - self.previewDim.drawDim(self.dimStyle) - - def closeEvent(self, event): - del self.previewDim # cancello il canvans di preview della quota chiamato QadPreviewDim - return QDialog.closeEvent(self, event) - - def setupUi(self, Dialog): - qad_dimstyle_details_ui.Ui_DimStyle_Details_Dialog.setupUi(self, self) - # aggiungo il bottone di qgis QgsColorButtonV2 chiamato dimLineColor - # che eredita la posizione di dimLineColorDummy (che viene nascosto) - self.dimLineColorDummy.setHidden(True) - self.dimLineColor = QgsColorButtonV2(self.dimLineColorDummy.parent()) - self.dimLineColor.setGeometry(self.dimLineColorDummy.geometry()) - self.dimLineColor.setObjectName("dimLineColor") - QObject.connect(self.dimLineColor, SIGNAL("colorChanged(QColor)"), self.dimLineColorChanged) - - # aggiungo il bottone di qgis QgsColorButtonV2 chiamato extLineColor - # che eredita la posizione di extLineColorDummy (che viene nascosto) - self.extLineColorDummy.setHidden(True) - self.extLineColor = QgsColorButtonV2(self.extLineColorDummy.parent()) - self.extLineColor.setGeometry(self.extLineColorDummy.geometry()) - self.extLineColor.setObjectName("extLineColor") - QObject.connect(self.extLineColor, SIGNAL("colorChanged(QColor)"), self.extLineColorChanged) - - # aggiungo il bottone di qgis QgsColorButtonV2 chiamato textColor - # che eredita la posizione di textColorDummy (che viene nascosto) - self.textColorDummy.setHidden(True) - self.textColor = QgsColorButtonV2(self.textColorDummy.parent()) - self.textColor.setGeometry(self.textColorDummy.geometry()) - self.textColor.setObjectName("textColor") - QObject.connect(self.textColor, SIGNAL("colorChanged(QColor)"), self.textColorChanged) - - # aggiungo il canvans di preview della quota chiamato QadPreviewDim - # che eredita la posizione di previewDummy (che viene nascosto) - self.previewDummy.setHidden(True) - self.previewDim = QadPreviewDim(self.previewDummy.parent(), self.plugIn) - self.previewDim.setGeometry(self.previewDummy.geometry()) - self.previewDim.setObjectName("previewDim") - - self.tabWidget.setCurrentIndex(0) - - def currentTabChanged(self, index): - self.previewDim.setParent(self.tabWidget.widget(index)) - self.previewDim.show() - - - #################################################################### - # Inizializzazione del TAB che riguarda i campi di database - inizio - #################################################################### - - def init_db_tab(self): - self.onInit = True - # layer linee - for index, layer in enumerate(self.plugIn.iface.legendInterface().layers()): - if (layer.type() == QgsMapLayer.VectorLayer) and layer.geometryType() == QGis.Line: - self.linearLayerName.addItem(layer.name(), index) - # seleziono un elemento della lista - if self.dimStyle.linearLayerName is not None: - index = self.linearLayerName.findText(self.dimStyle.linearLayerName) - self.linearLayerName.setCurrentIndex(index) - self.linearLayerNameChanged(index) - - # layer simboli - for index, layer in enumerate(self.plugIn.iface.legendInterface().layers()): - if qad_layer.isSymbolLayer(layer): - self.symbolLayerName.addItem(layer.name(), index) - # seleziono un elemento della lista - if self.dimStyle.symbolLayerName is not None: - index = self.symbolLayerName.findText(self.dimStyle.symbolLayerName) - self.symbolLayerName.setCurrentIndex(index) - self.symbolLayerNameChanged(index) - - # layer testi - for index, layer in enumerate(self.plugIn.iface.legendInterface().layers()): - if qad_layer.isTextLayer(layer): - self.textualLayerName.addItem(layer.name(), index) - # seleziono un elemento della lista - if self.dimStyle.textualLayerName is not None: - index = self.textualLayerName.findText(self.dimStyle.textualLayerName) - self.textualLayerName.setCurrentIndex(index) - self.textualLayerNameChanged(index) - self.onInit = False - - def accept_db_tab(self): - # layer linee - self.dimStyle.linearLayerName = self.linearLayerName.currentText() - self.dimStyle.lineTypeFieldName = self.lineTypeFieldName.currentText() - - # layer simboli - self.dimStyle.symbolLayerName = self.symbolLayerName.currentText() - self.dimStyle.symbolFieldName = self.symbolFieldName.currentText() - self.dimStyle.scaleFieldName = self.scaleFieldName.currentText() - - self.dimStyle.colorFieldName = self.colorFieldName.currentText() - self.dimStyle.rotFieldName = self.rotFieldName.currentText() - self.dimStyle.componentFieldName = self.componentFieldName.currentText() - self.dimStyle.idParentFieldName = self.idParentFieldName.currentText() - - # layer testi - self.dimStyle.textualLayerName = self.textualLayerName.currentText() - self.dimStyle.idFieldName = self.idFieldName.currentText() - self.dimStyle.dimStyleFieldName = self.dimStyleFieldName.currentText() - self.dimStyle.dimTypeFieldName = self.dimTypeFieldName.currentText() - - def redrawDimOnDBTabChanged(self): - if self.onInit == True: # esco se sono in fase di inizializzazione - return - self.accept_db_tab() - self.previewDim.drawDim(self.dimStyle) - - def colorFieldNameChanged(self, index): - self.redrawDimOnDBTabChanged() - - def componentFieldNameChanged(self, index): - self.redrawDimOnDBTabChanged() - - def dimStyleFieldNameChanged(self, index): - self.redrawDimOnDBTabChanged() - - def dimTypeFieldNameChanged(self, index): - self.redrawDimOnDBTabChanged() - - def idFieldNameChanged(self, index): - self.redrawDimOnDBTabChanged() - - def idParentFieldNameChanged(self, index): - self.redrawDimOnDBTabChanged() - - def linetypeFieldNameChanged(self, index): - self.redrawDimOnDBTabChanged() - - def linearLayerNameChanged(self, index): - if index == -1: - return - # leggo l'elemento selezionato - legendIndex = self.linearLayerName.itemData(index) - layer = iface.legendInterface().layers()[legendIndex] - if layer is not None: - self.lineTypeFieldName.clear() # remove all items and add an empty row (optional parameter) - self.lineTypeFieldName.addItem("") - - for field in layer.pendingFields(): - if field.type() == QVariant.String: - self.lineTypeFieldName.addItem(field.name(), field) - - # seleziono un elemento della lista - if self.dimStyle.lineTypeFieldName is not None: - index = self.lineTypeFieldName.findText(self.dimStyle.lineTypeFieldName) - self.lineTypeFieldName.setCurrentIndex(index) - self.redrawDimOnDBTabChanged() - - def rotFieldNameChanged(self, index): - self.redrawDimOnDBTabChanged() - - def scaleFieldNameChanged(self, index): - self.redrawDimOnDBTabChanged() - - def symbolFieldNameChanged(self, index): - self.redrawDimOnDBTabChanged() - - def symbolLayerNameChanged(self, index): - if index == -1: - return - # leggo l'elemento selezionato - legendIndex = self.symbolLayerName.itemData(index) - layer = iface.legendInterface().layers()[legendIndex] - if layer is not None: - self.symbolFieldName.clear() # remove all items and add an empty row (optional parameter) - self.symbolFieldName.addItem("") - self.scaleFieldName.clear() # remove all items and add an empty row (optional parameter) - self.scaleFieldName.addItem("") - - self.rotFieldName.clear() # remove all items - self.componentFieldName.clear() # remove all items and add an empty row (optional parameter) - self.componentFieldName.addItem("") - self.idParentFieldName.clear() # remove all items and add an empty row (optional parameter) - self.idParentFieldName.addItem("") - - for field in layer.pendingFields(): - if field.type() == QVariant.String: - self.symbolFieldName.addItem(field.name(), field) - self.componentFieldName.addItem(field.name(), field) - elif qad_utils.isNumericField(field): - self.scaleFieldName.addItem(field.name(), field) - self.rotFieldName.addItem(field.name(), field) - self.idParentFieldName.addItem(field.name(), field) - - # seleziono un elemento della lista - if self.dimStyle.symbolFieldName is not None: - index = self.symbolFieldName.findText(self.dimStyle.symbolFieldName) - self.symbolFieldName.setCurrentIndex(index) - if self.dimStyle.scaleFieldName is not None: - index = self.scaleFieldName.findText(self.dimStyle.scaleFieldName) - self.scaleFieldName.setCurrentIndex(index) - - if self.dimStyle.rotFieldName is not None: - index = self.rotFieldName.findText(self.dimStyle.rotFieldName) - self.rotFieldName.setCurrentIndex(index) - if self.dimStyle.componentFieldName is not None: - index = self.componentFieldName.findText(self.dimStyle.componentFieldName) - self.componentFieldName.setCurrentIndex(index) - if self.dimStyle.idParentFieldName is not None: - index = self.idParentFieldName.findText(self.dimStyle.idParentFieldName) - self.idParentFieldName.setCurrentIndex(index) - self.redrawDimOnDBTabChanged() - - def textualLayerNameChanged(self, index): - if index == -1: - return - # leggo l'elemento selezionato - legendIndex = self.textualLayerName.itemData(index) - layer = iface.legendInterface().layers()[legendIndex] - if layer is not None: - self.idFieldName.clear() # remove all items and add an empty row (optional parameter) - self.idFieldName.addItem("") - self.dimStyleFieldName.clear() # remove all items and add an empty row (optional parameter) - self.dimStyleFieldName.addItem("") - self.dimTypeFieldName.clear() # remove all items and add an empty row (optional parameter) - self.dimTypeFieldName.addItem("") - self.colorFieldName.clear() # remove all items and add an empty row (optional parameter) - self.colorFieldName.addItem("") - - for field in layer.pendingFields(): - if field.type() == QVariant.String: - self.dimStyleFieldName.addItem(field.name(), field) - self.dimTypeFieldName.addItem(field.name(), field) - self.colorFieldName.addItem(field.name(), field) - elif qad_utils.isNumericField(field): - self.idFieldName.addItem(field.name(), field) - - # seleziono un elemento della lista - if self.dimStyle.idFieldName is not None: - index = self.idFieldName.findText(self.dimStyle.idFieldName) - self.idFieldName.setCurrentIndex(index) - if self.dimStyle.dimStyleFieldName is not None: - index = self.dimStyleFieldName.findText(self.dimStyle.dimStyleFieldName) - self.dimStyleFieldName.setCurrentIndex(index) - if self.dimStyle.dimTypeFieldName is not None: - index = self.dimTypeFieldName.findText(self.dimStyle.dimTypeFieldName) - self.dimTypeFieldName.setCurrentIndex(index) - if self.dimStyle.colorFieldName is not None: - index = self.colorFieldName.findText(self.dimStyle.colorFieldName) - self.colorFieldName.setCurrentIndex(index) - self.redrawDimOnDBTabChanged() - - - #################################################################### - # Inizializzazione del TAB che riguarda i campi di database - fine - # Inizializzazione del TAB che riguarda le linee di quotatura - inizio - #################################################################### - - def init_lines_tab(self): - self.onInit = True - self.dimLineColor.setColor(QColor(self.dimStyle.dimLineColor)) - self.dimLineLineType.setText(self.dimStyle.dimLineLineType) - self.dimLine1Hide.setChecked(not self.dimStyle.dimLine1Show) - self.dimLine2Hide.setChecked(not self.dimStyle.dimLine2Show) - - self.extLineColor.setColor(QColor(self.dimStyle.extLineColor)) - self.extLine1LineType.setText(self.dimStyle.extLine1LineType) - self.extLine2LineType.setText(self.dimStyle.extLine2LineType) - self.extLine1Hide.setChecked(not self.dimStyle.extLine1Show) - self.extLine2Hide.setChecked(not self.dimStyle.extLine2Show) - self.extLineOffsetDimLine.setValue(self.dimStyle.extLineOffsetDimLine) - self.extLineOffsetOrigPoints.setValue(self.dimStyle.extLineOffsetOrigPoints) - self.extLineIsFixedLen.setChecked(self.dimStyle.extLineIsFixedLen) - self.extLineFixedLen.setValue(self.dimStyle.extLineFixedLen) - self.extLineIsFixedLenToggled(self.dimStyle.extLineIsFixedLen) - self.onInit = False - - def accept_lines_tab(self): - self.dimStyle.dimLineColor = self.dimLineColor.color().name() - self.dimStyle.dimLineLineType = self.dimLineLineType.text() - self.dimStyle.dimLine1Show = not self.dimLine1Hide.isChecked() - self.dimStyle.dimLine2Show = not self.dimLine2Hide.isChecked() - - self.dimStyle.extLineColor = self.extLineColor.color().name() - self.dimStyle.extLine1LineType = self.extLine1LineType.text() - self.dimStyle.extLine2LineType = self.extLine2LineType.text() - self.dimStyle.extLine1Show = not self.extLine1Hide.isChecked() - self.dimStyle.extLine2Show = not self.extLine2Hide.isChecked() - self.dimStyle.extLineOffsetDimLine = self.extLineOffsetDimLine.value() - self.dimStyle.extLineOffsetOrigPoints = self.extLineOffsetOrigPoints.value() - self.dimStyle.extLineIsFixedLen = self.extLineIsFixedLen.isChecked() - self.dimStyle.extLineFixedLen = self.extLineFixedLen.value() - - def redrawDimOnLinesTabChanged(self): - if self.onInit == True: # esco se sono in fase di inizializzazione - return - self.accept_lines_tab() - self.previewDim.drawDim(self.dimStyle) - - def dimLine1HideToggled(self, value): - self.redrawDimOnLinesTabChanged() - - def dimLine2HideToggled(self, value): - self.redrawDimOnLinesTabChanged() - - def dimLineColorChanged(self, value): - self.redrawDimOnLinesTabChanged() - - def dimLineLineTypeChanged(self, value): - self.redrawDimOnLinesTabChanged() - - def extLineColorChanged(self, value): - self.redrawDimOnLinesTabChanged() - - def extLineFixedLenChanged(self, value): - self.redrawDimOnLinesTabChanged() - - def extLineIsFixedLenToggled(self, value): - self.redrawDimOnLinesTabChanged() - - def extLineOffsetDimLineChanged(self, value): - self.redrawDimOnLinesTabChanged() - - def extLineOffsetOrigPointsChanged(self, value): - self.redrawDimOnLinesTabChanged() - - def extLine1HideToggled(self, value): - self.redrawDimOnLinesTabChanged() - - def extLine1LineTypeChanged(self, value): - self.redrawDimOnLinesTabChanged() - - def extLine2HideToggled(self, value): - self.redrawDimOnLinesTabChanged() - - def extLine2LineTypeChanged(self, value): - self.redrawDimOnLinesTabChanged() - - def extLineIsFixedLenToggled(self, value): - self.extLineFixedLen.setEnabled(value) - self.redrawDimOnLinesTabChanged() - - - #################################################################### - # Inizializzazione del TAB che riguarda le linee di quotatura - fine - # Inizializzazione del TAB che riguarda i simboli di quotatura - inizio - #################################################################### - - def init_symbols_tab(self): - self.onInit = True - self.block1Name.setText(self.dimStyle.block1Name) - self.block2Name.setText(self.dimStyle.block2Name) - self.blockLeaderName.setText(self.dimStyle.blockLeaderName) - self.blockWidth.setValue(self.dimStyle.blockWidth) - self.blockScale.setValue(self.dimStyle.blockScale) - self.onInit = False - - def accept_symbols_tab(self): - self.dimStyle.block1Name = self.block1Name.text() - self.dimStyle.block2Name = self.block2Name.text() - self.dimStyle.blockLeaderName = self.blockLeaderName.text() - self.dimStyle.blockWidth = self.blockWidth.value() - self.dimStyle.blockScale = self.blockScale.value() - - def redrawDimOnSymbolsTabChanged(self): - if self.onInit == True: # esco se sono in fase di inizializzazione - return - self.accept_symbols_tab() - self.previewDim.drawDim(self.dimStyle) - - def block1NameChanged(self, value): - self.redrawDimOnSymbolsTabChanged() - - def block2NameChanged(self, value): - self.redrawDimOnSymbolsTabChanged() - - def blockLeaderNameChanged(self, value): - self.redrawDimOnSymbolsTabChanged() - - def blockScaleNameChanged(self, value): - self.redrawDimOnSymbolsTabChanged() - - def blockWidthChanged(self, value): - self.redrawDimOnSymbolsTabChanged() - - def blockScaleChanged(self, value): - self.redrawDimOnSymbolsTabChanged() - - - #################################################################### - # Inizializzazione del TAB che riguarda i simboli di quotatura - fine - # Inizializzazione del TAB che riguarda i testi di quotatura - inizio - #################################################################### - - def init_text_tab(self): - self.onInit = True - index = self.textFont.findText(self.dimStyle.textFont) - self.textFont.setCurrentIndex(index) - self.textColor.setColor(QColor(self.dimStyle.textColor)) - self.textHeight.setValue(self.dimStyle.textHeight) - - # textVerticalPos - self.textVerticalPos.addItem(QadMsg.translate("DimStyle_Details_Dialog", "Centered")) - self.textVerticalPos.addItem(QadMsg.translate("DimStyle_Details_Dialog", "Above")) - self.textVerticalPos.addItem(QadMsg.translate("DimStyle_Details_Dialog", "Outside")) - self.textVerticalPos.addItem(QadMsg.translate("DimStyle_Details_Dialog", "Below")) - if self.dimStyle.textVerticalPos == QadDimStyleTxtVerticalPosEnum.CENTERED_LINE: - self.textVerticalPos.setCurrentIndex(0) - elif self.dimStyle.textVerticalPos == QadDimStyleTxtVerticalPosEnum.ABOVE_LINE: - self.textVerticalPos.setCurrentIndex(1) - elif self.dimStyle.textVerticalPos == QadDimStyleTxtVerticalPosEnum.EXTERN_LINE: - self.textVerticalPos.setCurrentIndex(2) - elif self.dimStyle.textVerticalPos == QadDimStyleTxtVerticalPosEnum.BELOW_LINE: - self.textVerticalPos.setCurrentIndex(3) - - # textHorizontalPos - self.textHorizontalPos.addItem(QadMsg.translate("DimStyle_Details_Dialog", "Centered")) - self.textHorizontalPos.addItem(QadMsg.translate("DimStyle_Details_Dialog", "At Ext Line 1")) - self.textHorizontalPos.addItem(QadMsg.translate("DimStyle_Details_Dialog", "At Ext Line 2")) - self.textHorizontalPos.addItem(QadMsg.translate("DimStyle_Details_Dialog", "Over Ext Line 1")) - self.textHorizontalPos.addItem(QadMsg.translate("DimStyle_Details_Dialog", "Over Ext Line 2")) - if self.dimStyle.textHorizontalPos == QadDimStyleTxtHorizontalPosEnum.CENTERED_LINE: - self.textHorizontalPos.setCurrentIndex(0) - elif self.dimStyle.textHorizontalPos == QadDimStyleTxtHorizontalPosEnum.FIRST_EXT_LINE: - self.textHorizontalPos.setCurrentIndex(1) - elif self.dimStyle.textHorizontalPos == QadDimStyleTxtHorizontalPosEnum.SECOND_EXT_LINE: - self.textHorizontalPos.setCurrentIndex(2) - elif self.dimStyle.textHorizontalPos == QadDimStyleTxtHorizontalPosEnum.FIRST_EXT_LINE_UP: - self.textHorizontalPos.setCurrentIndex(3) - elif self.dimStyle.textHorizontalPos == QadDimStyleTxtHorizontalPosEnum.SECOND_EXT_LINE_UP: - self.textHorizontalPos.setCurrentIndex(4) - - # textDirection - self.textDirection.addItem(QadMsg.translate("DimStyle_Details_Dialog", "Left-to-Right")) - self.textDirection.addItem(QadMsg.translate("DimStyle_Details_Dialog", "Right-to-Left")) - if self.dimStyle.textDirection == QadDimStyleTxtDirectionEnum.SX_TO_DX: - self.textDirection.setCurrentIndex(0) - elif self.dimStyle.textDirection == QadDimStyleTxtDirectionEnum.DX_TO_SX: - self.textDirection.setCurrentIndex(1) - - self.textOffsetDist.setValue(self.dimStyle.textOffsetDist) - - # textForcedRot - if self.dimStyle.textRotMode == QadDimStyleTxtRotModeEnum.HORIZONTAL: - self.textRotModeHorizontal.setChecked(True) - elif self.dimStyle.textRotMode == QadDimStyleTxtRotModeEnum.ALIGNED_LINE: - self.textRotModeAligned.setChecked(True) - elif self.dimStyle.textRotMode == QadDimStyleTxtRotModeEnum.ISO: - self.textRotModeISO.setChecked(True) - elif self.dimStyle.textRotMode == QadDimStyleTxtRotModeEnum.FORCED_ROTATION: - self.textRotModeFixedRot.setChecked(True) - - self.textForcedRot.setValue(self.dimStyle.textForcedRot) - self.textRotModeFixedRotToggled(self.textRotModeFixedRot.isChecked()) - self.onInit = False - - def accept_text_tab(self): - self.dimStyle.textFont = self.textFont.currentText() - self.dimStyle.textColor = self.textColor.color().name() - self.dimStyle.textHeight = self.textHeight.value() - - # textVerticalPos - if self.textVerticalPos.currentIndex() == 0: - self.dimStyle.textVerticalPos = QadDimStyleTxtVerticalPosEnum.CENTERED_LINE - elif self.textVerticalPos.currentIndex() == 1: - self.dimStyle.textVerticalPos = QadDimStyleTxtVerticalPosEnum.ABOVE_LINE - elif self.textVerticalPos.currentIndex() == 2: - self.dimStyle.textVerticalPos = QadDimStyleTxtVerticalPosEnum.EXTERN_LINE - elif self.textVerticalPos.currentIndex() == 3: - self.dimStyle.textVerticalPos = QadDimStyleTxtVerticalPosEnum.BELOW_LINE - - # textHorizontalPos - if self.textHorizontalPos.currentIndex() == 0: - self.dimStyle.textHorizontalPos = QadDimStyleTxtHorizontalPosEnum.CENTERED_LINE - elif self.textHorizontalPos.currentIndex() == 1: - self.dimStyle.textHorizontalPos = QadDimStyleTxtHorizontalPosEnum.FIRST_EXT_LINE - elif self.textHorizontalPos.currentIndex() == 2: - self.dimStyle.textHorizontalPos = QadDimStyleTxtHorizontalPosEnum.SECOND_EXT_LINE - elif self.textHorizontalPos.currentIndex() == 3: - self.dimStyle.textHorizontalPos = QadDimStyleTxtHorizontalPosEnum.FIRST_EXT_LINE_UP - elif self.textHorizontalPos.currentIndex() == 4: - self.dimStyle.textHorizontalPos = QadDimStyleTxtHorizontalPosEnum.SECOND_EXT_LINE_UP - - # textDirection - if self.textDirection.currentIndex() == 0: - self.dimStyle.textDirection = QadDimStyleTxtDirectionEnum.SX_TO_DX - elif self.textDirection.currentIndex() == 1: - self.dimStyle.textDirection = QadDimStyleTxtDirectionEnum.DX_TO_SX - - self.dimStyle.textOffsetDist = self.textOffsetDist.value() - - # textForcedRot - if self.textRotModeHorizontal.isChecked(): - self.dimStyle.textRotMode = QadDimStyleTxtRotModeEnum.HORIZONTAL - elif self.textRotModeAligned.isChecked(): - self.dimStyle.textRotMode = QadDimStyleTxtRotModeEnum.ALIGNED_LINE - elif self.textRotModeISO.isChecked(): - self.dimStyle.textRotMode = QadDimStyleTxtRotModeEnum.ISO - elif self.textRotModeFixedRot.isChecked(): - self.dimStyle.textRotMode = QadDimStyleTxtRotModeEnum.FORCED_ROTATION - - self.dimStyle.textForcedRot = self.textForcedRot.value() - - def redrawDimOnTextTabChanged(self): - if self.onInit == True: # esco se sono in fase di inizializzazione - return - self.accept_text_tab() - self.previewDim.drawDim(self.dimStyle) - - - def textColorChanged(self, value): - self.redrawDimOnTextTabChanged() - - def textDirectionChanged(self, value): - self.redrawDimOnTextTabChanged() - - def textFontChanged(self, value): - self.redrawDimOnTextTabChanged() - - def textForcedRotChanged(self, value): - self.redrawDimOnTextTabChanged() - - def textHeightChanged(self, value): - self.redrawDimOnTextTabChanged() - - def textHorizontalPosChanged(self, value): - self.redrawDimOnTextTabChanged() - - def textOffsetDistChanged(self, value): - self.redrawDimOnTextTabChanged() - - def textRotModeHorizontalToggled(self, value): - self.redrawDimOnTextTabChanged() - - def textRotModeAlignedToggled(self, value): - self.redrawDimOnTextTabChanged() - - def textRotModeISOToggled(self, value): - self.redrawDimOnTextTabChanged() - - def textRotModeFixedRotToggled(self, value): - self.textForcedRot.setEnabled(value) - self.redrawDimOnTextTabChanged() - - def textVerticalPosChanged(self, value): - self.redrawDimOnTextTabChanged() - - - #################################################################### - # Inizializzazione del TAB che riguarda i testi di quotatura - fine - # Inizializzazione del TAB che riguarda l'adattamento dei componenti di quotatura - inizio - #################################################################### - - def init_adjust_tab(self): - self.onInit = True - # self.textAdjustAlwaysInside in futuro - - if self.dimStyle.textBlockAdjust == QadDimStyleTextBlocksAdjustEnum.WHICHEVER_FITS_BEST: - self.textBlockAdjustWhicheverFitsBestOutside.setChecked(True) - elif self.dimStyle.textBlockAdjust == QadDimStyleTextBlocksAdjustEnum.FIRST_BLOCKS_THEN_TEXT: - self.textBlockAdjustFirstSymbolOutside.setChecked(True) - elif self.dimStyle.textBlockAdjust == QadDimStyleTextBlocksAdjustEnum.FIRST_TEXT_THEN_BLOCKS: - self.textBlockAdjustFirstTextOutside.setChecked(True) - elif self.dimStyle.textBlockAdjust == QadDimStyleTextBlocksAdjustEnum.BOTH_OUTSIDE_EXT_LINES: - self.textBlockAdjustBothtOutside.setChecked(True) - - self.blockSuppressionForNoSpace.setChecked(self.dimStyle.blockSuppressionForNoSpace) - self.onInit = False - - def accept_adjust_tab(self): - if self.textBlockAdjustWhicheverFitsBestOutside.isChecked(): - self.dimStyle.textBlockAdjust = QadDimStyleTextBlocksAdjustEnum.WHICHEVER_FITS_BEST - elif self.textBlockAdjustFirstSymbolOutside.isChecked(): - self.dimStyle.textBlockAdjust = QadDimStyleTextBlocksAdjustEnum.FIRST_BLOCKS_THEN_TEXT - elif self.textBlockAdjustFirstTextOutside.isChecked(): - self.dimStyle.textBlockAdjust = QadDimStyleTextBlocksAdjustEnum.FIRST_TEXT_THEN_BLOCKS - elif self.textBlockAdjustBothOutside.isChecked(): - self.dimStyle.textBlockAdjust = QadDimStyleTextBlocksAdjustEnum.BOTH_OUTSIDE_EXT_LINES - - self.dimStyle.blockSuppressionForNoSpace = self.blockSuppressionForNoSpace.isChecked() - - def redrawDimOnAdjustTabChanged(self): - if self.onInit == True: # esco se sono in fase di inizializzazione - return - self.accept_adjust_tab() - self.previewDim.drawDim(self.dimStyle) - - def blockSuppressionForNoSpaceToggled(self, value): - self.redrawDimOnAdjustTabChanged() - - def textBlockAdjustWhicheverFitsBestOutsideToggled(self, value): - self.redrawDimOnAdjustTabChanged() - - def textBlockAdjustFirstSymbolOutsideToggled(self, value): - self.redrawDimOnAdjustTabChanged() - - def textBlockAdjustFirstTextOutsideToggled(self, value): - self.redrawDimOnAdjustTabChanged() - - def textBlockAdjustBothOutsideToggled(self, value): - self.redrawDimOnAdjustTabChanged() - - - #################################################################### - # Inizializzazione del TAB che riguarda l'adattamento dei componenti di quotatura - fine - # Inizializzazione del TAB che riguarda le unità primarie di quotatura - inizio - #################################################################### - - def init_primaryUnits_tab(self): - self.onInit = True - # textDecimals - self.textDecimals.addItem(QadMsg.translate("DimStyle_Details_Dialog", "0")) - self.textDecimals.addItem(QadMsg.translate("DimStyle_Details_Dialog", "0.0")) - self.textDecimals.addItem(QadMsg.translate("DimStyle_Details_Dialog", "0.00")) - self.textDecimals.addItem(QadMsg.translate("DimStyle_Details_Dialog", "0.000")) - self.textDecimals.addItem(QadMsg.translate("DimStyle_Details_Dialog", "0.0000")) - self.textDecimals.addItem(QadMsg.translate("DimStyle_Details_Dialog", "0.00000")) - self.textDecimals.addItem(QadMsg.translate("DimStyle_Details_Dialog", "0.000000")) - self.textDecimals.addItem(QadMsg.translate("DimStyle_Details_Dialog", "0.0000000")) - self.textDecimals.addItem(QadMsg.translate("DimStyle_Details_Dialog", "0.00000000")) - self.textDecimals.setCurrentIndex(self.dimStyle.textDecimals) - - # textDecimalSep - self.textDecimalSep.addItem(QadMsg.translate("DimStyle_Details_Dialog", "'.' Period")) - self.textDecimalSep.addItem(QadMsg.translate("DimStyle_Details_Dialog", "',' Comma")) - self.textDecimalSep.addItem(QadMsg.translate("DimStyle_Details_Dialog", "' ' Space")) - if self.dimStyle.textDecimalSep == ".": # punto - self.textDecimalSep.setCurrentIndex(0) - elif self.dimStyle.textDecimalSep == ",": # virgola - self.textDecimalSep.setCurrentIndex(1) - elif self.dimStyle.textDecimalSep == " ": # spazio - self.textDecimalSep.setCurrentIndex(2) - - self.textPrefix.setText(self.dimStyle.textPrefix) - self.textSuffix.setText(self.dimStyle.textSuffix) - - self.textSuppressLeadingZeros.setChecked(self.dimStyle.textSuppressLeadingZeros) - self.textDecimalZerosSuppression.setChecked(self.dimStyle.textDecimalZerosSuppression) - self.onInit = False - - def accept_primaryUnits_tab(self): - # textDecimals - self.dimStyle.textDecimals = self.textDecimals.currentIndex() - - # textDecimalSep - if self.textDecimalSep.currentIndex() == 0: # punto - self.dimStyle.textDecimalSep = "." - elif self.textDecimalSep.currentIndex() == 1: # virgola - self.dimStyle.textDecimalSep = "," - elif self.textDecimalSep.currentIndex() == 2: # spazio - self.dimStyle.textDecimalSep = " " - - self.dimStyle.textPrefix = self.textPrefix.text() - self.dimStyle.textSuffix = self.textSuffix.text() - - self.dimStyle.textSuppressLeadingZeros = self.textSuppressLeadingZeros.isChecked() - self.dimStyle.textDecimalZerosSuppression = self.textDecimalZerosSuppression.isChecked() - - def redrawDimOnPrimaryUnitsTabChanged(self): - if self.onInit == True: # esco se sono in fase di inizializzazione - return - self.accept_primaryUnits_tab() - self.previewDim.drawDim(self.dimStyle) - - def textDecimalsChanged(self, index): - self.redrawDimOnPrimaryUnitsTabChanged() - - def textDecimalSepChanged(self, index): - self.redrawDimOnPrimaryUnitsTabChanged() - - def textDecimalZerosSuppressionToggled(self, value): - self.redrawDimOnPrimaryUnitsTabChanged() - - def textPrefixChanged(self, value): - self.redrawDimOnPrimaryUnitsTabChanged() - - def textSuffixChanged(self, value): - self.redrawDimOnPrimaryUnitsTabChanged() - - def textSuppressLeadingZerosToggled(self, value): - self.redrawDimOnPrimaryUnitsTabChanged() - - - #################################################################### - # Inizializzazione del TAB che riguarda le unità primarie di quotatura - fine - #################################################################### - - def ButtonHELP_Pressed(self): - qadShowPluginHelp(QadMsg.translate("Help", "Dimensioning")) - - def accept(self): - self.accept_db_tab() - self.accept_lines_tab() - self.accept_symbols_tab() - self.accept_text_tab() - self.accept_adjust_tab() - self.accept_primaryUnits_tab() - errMsg = self.dimStyle.getInValidErrMsg() - if errMsg is not None: - errMsg += QadMsg.translate("DimStyle_Details_Dialog", "\nDo you want to accepts these settings ?") - res = QMessageBox.question(self, QadMsg.translate("QAD", "QAD"), errMsg, \ - QMessageBox.Yes | QMessageBox.No) - if res == QMessageBox.No: - return - QDialog.accept(self) - - -####################################################################################### -# Classe che gestisce il widget per visualizzare il preview della quota -class QadPreviewDim(QgsMapCanvas): - - def __init__(self, parent, plugIn): - QgsMapCanvas.__init__(self, parent) - self.plugIn = plugIn - self.setAttribute(Qt.WA_DeleteOnClose) - - self.iface = self.plugIn.iface - self.layerId2canvasLayer = {} - self.canvasLayers = [] - - self.setupUi() - self.bookmark = False - self.dimStyle = None - - - def __del__(self): - self.eraseDim() - - - def setupUi(self): - self.setObjectName("QadPreviewCanvas") - - settings = QSettings() - red = settings.value("/qgis/default_canvas_color_red", 255, type=int) - green = settings.value("/qgis/default_canvas_color_green", 255, type=int) - blue = settings.value("/qgis/default_canvas_color_blue", 255, type=int) - self.setCanvasColor(QColor(red, green, blue)) - self.enableAntiAliasing( settings.value( "/qgis/enable_anti_aliasing", False, type=bool )) - self.useImageToRender( settings.value( "/qgis/use_qimage_to_render", False, type=bool )) - action = settings.value( "/qgis/wheel_action", 0, type=int) - zoomFactor = settings.value( "/qgis/zoom_factor", 2.0, type=float ) - self.setWheelAction( QgsMapCanvas.WheelAction(action), zoomFactor ) - - self.onExtentsChanged() - self.onCrsChanged() - self.onCrsTransformEnabled( self.iface.mapCanvas().hasCrsTransformEnabled() ) - - def onExtentsChanged(self): - prevFlag = self.renderFlag() - self.setRenderFlag(False) - - self.setExtent(self.iface.mapCanvas().extent()) - - self.setRenderFlag( prevFlag ) - - def onCrsChanged(self): - prevFlag = self.renderFlag() - self.setRenderFlag( False ) - - renderer = self.iface.mapCanvas().mapRenderer() - self._setRendererCrs( self.mapRenderer(), self._rendererCrs(renderer) ) - self.mapRenderer().setMapUnits( renderer.mapUnits() ) - - self.setRenderFlag( prevFlag ) - - def onCrsTransformEnabled(self, enabled): - prevFlag = self.renderFlag() - self.setRenderFlag( False ) - - self.mapRenderer().setProjectionsEnabled( enabled ) - - self.setRenderFlag( prevFlag ) - - - def getLayerSet(self): - return map(lambda x: self._layerId(x.layer()), self.canvasLayers) - - def setLayerSet(self, layerIds=None): - prevFlag = self.renderFlag() - self.setRenderFlag( False ) - - if layerIds == None: - self.layerId2canvasLayer = {} - self.canvasLayers = [] - QgsMapCanvas.setLayerSet(self, []) - - else: - for lid in layerIds: - self.addLayer( lid ) - - self.onExtentsChanged() - self.setRenderFlag( prevFlag ) - - - def addLayer(self, layerId=None): - if layerId == None: - layer = self.iface.activeLayer() - else: - layer = QgsMapLayerRegistry.instance().mapLayer( layerId ) - - if layer == None: - return - - prevFlag = self.renderFlag() - self.setRenderFlag( False ) - - # add the layer to the map canvas layer set - self.canvasLayers = [] - id2cl_dict = {} - for l in self.iface.legendInterface().layers(): - lid = self._layerId(l) - if self.layerId2canvasLayer.has_key( lid ): # previously added - cl = self.layerId2canvasLayer[ lid ] - elif l == layer: # selected layer - cl = QgsMapCanvasLayer( layer ) - else: - continue - - id2cl_dict[ lid ] = cl - self.canvasLayers.append( cl ) - - self.layerId2canvasLayer = id2cl_dict - QgsMapCanvas.setLayerSet(self, self.canvasLayers ) - - self.onExtentsChanged() - self.setRenderFlag( prevFlag ) - - def delLayer(self, layerId=None): - if layerId == None: - layer = self.iface.activeLayer() - if layer == None: - return - layerId = self._layerId(layer) - - # remove the layer from the map canvas layer set - if not self.layerId2canvasLayer.has_key( layerId ): - return - - prevFlag = self.renderFlag() - self.setRenderFlag( False ) - - cl = self.layerId2canvasLayer[ layerId ] - del self.layerId2canvasLayer[ layerId ] - self.canvasLayers.remove( cl ) - QgsMapCanvas.setLayerSet(self, self.canvasLayers ) - del cl - - self.onExtentsChanged() - self.setRenderFlag( prevFlag ) - - - def _layerId(self, layer): - if hasattr(layer, 'id'): - return layer.id() - return layer.getLayerID() - - def _rendererCrs(self, renderer): - if hasattr(renderer, 'destinationCrs'): - return renderer.destinationCrs() - return renderer.destinationSrs() - - def _setRendererCrs(self, renderer, crs): - if hasattr(renderer, 'setDestinationCrs'): - return renderer.setDestinationCrs( crs ) - return renderer.setDestinationSrs( crs ) - - def zoomOnRect(self, zoomRect): - mapSettings = self.mapSettings() - canvasSize = mapSettings.outputSize() - sfx = zoomRect.width() / canvasSize.width() - sfy = zoomRect.height() / canvasSize.height() - sf = max(sfx, sfy) - - prevFlag = self.renderFlag() - self.setRenderFlag(False) - - self.setExtent(zoomRect) - self.setCenter(zoomRect.center()) - - self.setRenderFlag( prevFlag ) - - def drawDim(self, dimStyle): - if dimStyle is None: - return - - self.dimStyle = dimStyle - self.eraseDim() - - if self.plugIn.insertBookmark() == True: - self.bookmark = True - - for layerId in self.getLayerSet(): # tolgo tutti i layer - self.delLayer(layerId) - # inserisco i layer della quotatura - self.isEditableTextualLayer = None - layer = self.dimStyle.getTextualLayer() - if layer is not None: - self.isEditableTextualLayer = layer.isEditable() - if self.isEditableTextualLayer == False: - layer.startEditing() - self.addLayer(layer.id()) - - self.isEditableSymbolLayer = None - layer = self.dimStyle.getSymbolLayer() - if layer is not None: - self.isEditableSymbolLayer = layer.isEditable() - if self.isEditableSymbolLayer == False: - layer.startEditing() - self.addLayer(layer.id()) - - self.isEditableLinearLayer = None - layer = self.dimStyle.getLinearLayer() - if layer is not None: - self.isEditableLinearLayer = layer.isEditable() - if self.isEditableLinearLayer == False: - layer.startEditing() - self.addLayer(layer.id()) - - if self.dimStyle.getInValidErrMsg() is not None: - return - - - ########################### - # quota lineare orizzontale - dimPt1 = QgsPoint(0, 0) - dimPt2 = QgsPoint(13.45, 0) - linePosPt = QgsPoint(0, 10) - - # calcolo il rettangolo di occupazione della quota - dimEntity, textOffsetRectGeom = self.dimStyle.getLinearDimFeatures(self.plugIn.canvas, \ - dimPt1, dimPt2, linePosPt) - rect = textOffsetRectGeom.boundingBox() - for g in dimEntity.getLinearGeometryCollection(): - rect.combineExtentWith(g.boundingBox()) - for g in dimEntity.getSymbolGeometryCollection(): - rect.combineExtentWith(g.boundingBox()) - - self.dimStyle.addLinearDimToLayers(self.plugIn, dimPt1, dimPt2, linePosPt) - - ########################### - # quota lineare verticale - dimPt1 = QgsPoint(0, 0) - dimPt2 = QgsPoint(0, -15.7) - linePosPt = QgsPoint(-9, 0) - - # calcolo il rettangolo di occupazione della quota - dimEntity, textOffsetRectGeom = self.dimStyle.getLinearDimFeatures(self.plugIn.canvas, \ - dimPt1, dimPt2, linePosPt, None, \ - QadDimStyleAlignmentEnum.VERTICAL) - rect.combineExtentWith(textOffsetRectGeom.boundingBox()) - for g in dimEntity.getLinearGeometryCollection(): - rect.combineExtentWith(g.boundingBox()) - for g in dimEntity.getSymbolGeometryCollection(): - rect.combineExtentWith(g.boundingBox()) - - self.dimStyle.addLinearDimToLayers(self.plugIn, dimPt1, dimPt2, linePosPt, None, \ - QadDimStyleAlignmentEnum.VERTICAL) - - ########################### - # quota allineata obliqua - dimPt1 = QgsPoint(13.45, 0) - dimPt2 = QgsPoint(23, -20) - linePosPt = QgsPoint(23, 0) - - # calcolo il rettangolo di occupazione della quota - dimEntity, textOffsetRectGeom = self.dimStyle.getAlignedDimFeatures(self.plugIn.canvas, \ - dimPt1, dimPt2, linePosPt) - rect.combineExtentWith(textOffsetRectGeom.boundingBox()) - for g in dimEntity.getLinearGeometryCollection(): - rect.combineExtentWith(g.boundingBox()) - for g in dimEntity.getSymbolGeometryCollection(): - rect.combineExtentWith(g.boundingBox()) - - self.dimStyle.addAlignedDimToLayers(self.plugIn, dimPt1, dimPt2, linePosPt, None) - - self.zoomOnRect(rect) - - def eraseDim(self): - if self.bookmark == True: - self.plugIn.undoUntilBookmark() - self.bookmark = False - - for layerId in self.getLayerSet(): # tolgo tutti i layer - self.delLayer(layerId) +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + classe per gestire la dialog per DIMSTYLE + + ------------------- + begin : 2015-05-19 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.core import QgsMapLayer, QgsWkbTypes, QgsProject, QgsPointXY +from qgis.gui import * +import qgis.utils + +from qgis.PyQt.QtCore import Qt, QObject, QVariant, QSettings +from qgis.PyQt.QtGui import * +from qgis.PyQt.QtWidgets import QDialog, QMessageBox + +from . import qad_dimstyle_details_ui + +from .qad_dim import QadDimStyle, QadDimStyleArcSymbolPosEnum, \ + QadDimStyleTxtVerticalPosEnum, QadDimStyleTxtHorizontalPosEnum, \ + QadDimStyleTxtDirectionEnum, QadDimStyleTxtRotModeEnum, \ + QadDimStyleTextBlocksAdjustEnum, QadDimStyleAlignmentEnum +from .qad_msg import QadMsg, qadShowPluginPDFHelp +from . import qad_layer +from . import qad_utils +from .qad_arc import QadArc + + +# =============================================================================== +# QadDIMSTYLEDETAILSTabIndexEnum class. +# =============================================================================== +class QadDIMSTYLEDETAILSTabIndexEnum(): + DB = 0 + LINES = 1 + SYMBOLS_ARROWS = 2 + TEXT = 3 + FIT = 4 + PRIMARY_UNITS = 5 + + +####################################################################################### +# Classe che gestisce l'interfaccia grafica della funzione di creazione nuovo stile +class QadDIMSTYLE_DETAILS_Dialog(QDialog, QObject, qad_dimstyle_details_ui.Ui_DimStyle_Details_Dialog): + def __init__(self, plugIn, parent, dimStyle): + self.plugIn = plugIn + self.dimStyle = QadDimStyle(dimStyle) # copio lo stile di quotatura + self.iface = self.plugIn.iface.mainWindow() + + QDialog.__init__(self, parent) + + self.onInit = False # vero se si è in fase di inizializzazione + + self.setupUi(self) + self.setWindowTitle(QadMsg.getQADTitle() + " - " + self.windowTitle()) + + self.init_db_tab() + self.init_lines_tab() + self.init_symbols_tab() + self.init_text_tab() + self.init_adjust_tab() + self.init_primaryUnits_tab() + self.previewDim.drawDim(self.dimStyle) + + if self.plugIn.dimStyleLastUsedTabIndex == -1: # non inizializzato + self.plugIn.dimStyleLastUsedTabIndex = QadDIMSTYLEDETAILSTabIndexEnum.DB + self.tabWidget.setCurrentIndex(self.plugIn.dimStyleLastUsedTabIndex) + + + def closeEvent(self, event): + del self.previewDim # cancello il canvans di preview della quota chiamato QadPreviewDim + return QDialog.closeEvent(self, event) + + def setupUi(self, Dialog): + qad_dimstyle_details_ui.Ui_DimStyle_Details_Dialog.setupUi(self, self) + # aggiungo il bottone di qgis QgsColorButton chiamato dimLineColor + # che eredita la posizione di dimLineColorDummy (che viene nascosto) + self.dimLineColorDummy.setHidden(True) + self.dimLineColor = QgsColorButton(self.dimLineColorDummy.parent()) + self.dimLineColor.setGeometry(self.dimLineColorDummy.geometry()) + self.dimLineColor.setObjectName("dimLineColor") + self.dimLineColor.colorChanged.connect(self.dimLineColorChanged) + + # aggiungo il bottone di qgis QgsColorButton chiamato extLineColor + # che eredita la posizione di extLineColorDummy (che viene nascosto) + self.extLineColorDummy.setHidden(True) + self.extLineColor = QgsColorButton(self.extLineColorDummy.parent()) + self.extLineColor.setGeometry(self.extLineColorDummy.geometry()) + self.extLineColor.setObjectName("extLineColor") + self.extLineColor.colorChanged.connect(self.extLineColorChanged) + + # aggiungo il bottone di qgis QgsColorButton chiamato textColor + # che eredita la posizione di textColorDummy (che viene nascosto) + self.textColorDummy.setHidden(True) + self.textColor = QgsColorButton(self.textColorDummy.parent()) + self.textColor.setGeometry(self.textColorDummy.geometry()) + self.textColor.setObjectName("textColor") + self.textColor.colorChanged.connect(self.textColorChanged) + + # aggiungo il canvans di preview della quota chiamato QadPreviewDim + # che eredita la posizione di previewDummy (che viene nascosto) + self.previewDummy.setHidden(True) + self.previewDim = QadPreviewDim(self.previewDummy.parent(), self.plugIn) + self.previewDim.setGeometry(self.previewDummy.geometry()) + self.previewDim.setObjectName("previewDim") + + self.tabWidget.setCurrentIndex(0) + + def currentTabChanged(self, index): + self.previewDim.setParent(self.tabWidget.widget(index)) + self.previewDim.show() + + + #################################################################### + # Inizializzazione del TAB che riguarda i campi di database - inizio + #################################################################### + + def init_db_tab(self): + self.onInit = True + # layer linee + for index, layer in enumerate(QgsProject.instance().mapLayers().values()): + if (layer.type() == QgsMapLayer.VectorLayer) and layer.geometryType() == QgsWkbTypes.LineGeometry: + self.linearLayerName.addItem(layer.name(), index) + # seleziono un elemento della lista + if self.dimStyle.linearLayerName is not None: + index = self.linearLayerName.findText(self.dimStyle.linearLayerName) + self.linearLayerName.setCurrentIndex(index) + self.linearLayerNameChanged(index) + + # layer simboli + for index, layer in enumerate(QgsProject.instance().mapLayers().values()): + if qad_layer.isSymbolLayer(layer): + self.symbolLayerName.addItem(layer.name(), index) + # seleziono un elemento della lista + if self.dimStyle.symbolLayerName is not None: + index = self.symbolLayerName.findText(self.dimStyle.symbolLayerName) + self.symbolLayerName.setCurrentIndex(index) + self.symbolLayerNameChanged(index) + + # layer testi + for index, layer in enumerate(QgsProject.instance().mapLayers().values()): + if qad_layer.isTextLayer(layer): + self.textualLayerName.addItem(layer.name(), index) + # seleziono un elemento della lista + if self.dimStyle.textualLayerName is not None: + index = self.textualLayerName.findText(self.dimStyle.textualLayerName) + self.textualLayerName.setCurrentIndex(index) + self.textualLayerNameChanged(index) + self.onInit = False + + def accept_db_tab(self): + # layer linee + self.dimStyle.linearLayerName = self.linearLayerName.currentText() + self.dimStyle.lineTypeFieldName = self.lineTypeFieldName.currentText() + + # layer simboli + self.dimStyle.symbolLayerName = self.symbolLayerName.currentText() + self.dimStyle.symbolFieldName = self.symbolFieldName.currentText() + self.dimStyle.scaleFieldName = self.scaleFieldName.currentText() + + self.dimStyle.colorFieldName = self.colorFieldName.currentText() + self.dimStyle.rotFieldName = self.rotFieldName.currentText() + self.dimStyle.componentFieldName = self.componentFieldName.currentText() + self.dimStyle.idParentFieldName = self.idParentFieldName.currentText() + + # layer testi + self.dimStyle.textualLayerName = self.textualLayerName.currentText() + self.dimStyle.idFieldName = self.idFieldName.currentText() + self.dimStyle.dimStyleFieldName = self.dimStyleFieldName.currentText() + self.dimStyle.dimTypeFieldName = self.dimTypeFieldName.currentText() + + def redrawDimOnDBTabChanged(self): + if self.onInit == True: # esco se sono in fase di inizializzazione + return + self.accept_db_tab() + self.previewDim.drawDim(self.dimStyle) + + def colorFieldNameChanged(self, index): + self.redrawDimOnDBTabChanged() + + def componentFieldNameChanged(self, index): + self.redrawDimOnDBTabChanged() + + def dimStyleFieldNameChanged(self, index): + self.redrawDimOnDBTabChanged() + + def dimTypeFieldNameChanged(self, index): + self.redrawDimOnDBTabChanged() + + def idFieldNameChanged(self, index): + self.redrawDimOnDBTabChanged() + + def idParentFieldNameChanged(self, index): + self.redrawDimOnDBTabChanged() + + def linetypeFieldNameChanged(self, index): + self.redrawDimOnDBTabChanged() + + def linearLayerNameChanged(self, index): + if index == -1: + return + # leggo l'elemento selezionato + legendIndex = self.linearLayerName.itemData(index) + layer = list(QgsProject.instance().mapLayers().values())[legendIndex] + if layer is not None: + self.lineTypeFieldName.clear() # remove all items and add an empty row (optional parameter) + self.lineTypeFieldName.addItem("") + + for field in layer.fields(): + if field.type() == QVariant.String: + self.lineTypeFieldName.addItem(field.name(), field) + + # seleziono un elemento della lista + if self.dimStyle.lineTypeFieldName is not None: + index = self.lineTypeFieldName.findText(self.dimStyle.lineTypeFieldName) + self.lineTypeFieldName.setCurrentIndex(index) + self.redrawDimOnDBTabChanged() + + def rotFieldNameChanged(self, index): + self.redrawDimOnDBTabChanged() + + def scaleFieldNameChanged(self, index): + self.redrawDimOnDBTabChanged() + + def symbolFieldNameChanged(self, index): + self.redrawDimOnDBTabChanged() + + def symbolLayerNameChanged(self, index): + if index == -1: + return + # leggo l'elemento selezionato + legendIndex = self.symbolLayerName.itemData(index) + layer = list(QgsProject.instance().mapLayers().values())[legendIndex] + if layer is not None: + self.symbolFieldName.clear() # remove all items and add an empty row (optional parameter) + self.symbolFieldName.addItem("") + self.scaleFieldName.clear() # remove all items and add an empty row (optional parameter) + self.scaleFieldName.addItem("") + + self.rotFieldName.clear() # remove all items + self.componentFieldName.clear() # remove all items and add an empty row (optional parameter) + self.componentFieldName.addItem("") + self.idParentFieldName.clear() # remove all items and add an empty row (optional parameter) + self.idParentFieldName.addItem("") + + for field in layer.fields(): + if field.type() == QVariant.String: + self.symbolFieldName.addItem(field.name(), field) + self.componentFieldName.addItem(field.name(), field) + elif qad_utils.isNumericField(field): + self.scaleFieldName.addItem(field.name(), field) + self.rotFieldName.addItem(field.name(), field) + self.idParentFieldName.addItem(field.name(), field) + + # seleziono un elemento della lista + if self.dimStyle.symbolFieldName is not None: + index = self.symbolFieldName.findText(self.dimStyle.symbolFieldName) + self.symbolFieldName.setCurrentIndex(index) + if self.dimStyle.scaleFieldName is not None: + index = self.scaleFieldName.findText(self.dimStyle.scaleFieldName) + self.scaleFieldName.setCurrentIndex(index) + + if self.dimStyle.rotFieldName is not None: + index = self.rotFieldName.findText(self.dimStyle.rotFieldName) + self.rotFieldName.setCurrentIndex(index) + if self.dimStyle.componentFieldName is not None: + index = self.componentFieldName.findText(self.dimStyle.componentFieldName) + self.componentFieldName.setCurrentIndex(index) + if self.dimStyle.idParentFieldName is not None: + index = self.idParentFieldName.findText(self.dimStyle.idParentFieldName) + self.idParentFieldName.setCurrentIndex(index) + self.redrawDimOnDBTabChanged() + + def textualLayerNameChanged(self, index): + if index == -1: + return + # leggo l'elemento selezionato + legendIndex = self.textualLayerName.itemData(index) + layer = list(QgsProject.instance().mapLayers().values())[legendIndex] + if layer is not None: + self.idFieldName.clear() # remove all items and add an empty row (optional parameter) + self.idFieldName.addItem("") + self.dimStyleFieldName.clear() # remove all items and add an empty row (optional parameter) + self.dimStyleFieldName.addItem("") + self.dimTypeFieldName.clear() # remove all items and add an empty row (optional parameter) + self.dimTypeFieldName.addItem("") + self.colorFieldName.clear() # remove all items and add an empty row (optional parameter) + self.colorFieldName.addItem("") + + for field in layer.fields(): + if field.type() == QVariant.String: + self.dimStyleFieldName.addItem(field.name(), field) + self.dimTypeFieldName.addItem(field.name(), field) + self.colorFieldName.addItem(field.name(), field) + elif qad_utils.isNumericField(field): + self.idFieldName.addItem(field.name(), field) + + # seleziono un elemento della lista + if self.dimStyle.idFieldName is not None: + index = self.idFieldName.findText(self.dimStyle.idFieldName) + self.idFieldName.setCurrentIndex(index) + if self.dimStyle.dimStyleFieldName is not None: + index = self.dimStyleFieldName.findText(self.dimStyle.dimStyleFieldName) + self.dimStyleFieldName.setCurrentIndex(index) + if self.dimStyle.dimTypeFieldName is not None: + index = self.dimTypeFieldName.findText(self.dimStyle.dimTypeFieldName) + self.dimTypeFieldName.setCurrentIndex(index) + if self.dimStyle.colorFieldName is not None: + index = self.colorFieldName.findText(self.dimStyle.colorFieldName) + self.colorFieldName.setCurrentIndex(index) + self.redrawDimOnDBTabChanged() + + + #################################################################### + # Inizializzazione del TAB che riguarda i campi di database - fine + # Inizializzazione del TAB che riguarda le linee di quotatura - inizio + #################################################################### + + def init_lines_tab(self): + self.onInit = True + self.dimLineColor.setColor(QColor(self.dimStyle.dimLineColor)) + self.dimLineLineType.setText(self.dimStyle.dimLineLineType) + self.dimLineOffsetExtLine.setValue(self.dimStyle.dimLineOffsetExtLine) + self.dimLine1Hide.setChecked(not self.dimStyle.dimLine1Show) + self.dimLine2Hide.setChecked(not self.dimStyle.dimLine2Show) + + self.extLineColor.setColor(QColor(self.dimStyle.extLineColor)) + self.extLine1LineType.setText(self.dimStyle.extLine1LineType) + self.extLine2LineType.setText(self.dimStyle.extLine2LineType) + self.extLine1Hide.setChecked(not self.dimStyle.extLine1Show) + self.extLine2Hide.setChecked(not self.dimStyle.extLine2Show) + self.extLineOffsetDimLine.setValue(self.dimStyle.extLineOffsetDimLine) + self.extLineOffsetOrigPoints.setValue(self.dimStyle.extLineOffsetOrigPoints) + self.extLineIsFixedLen.setChecked(self.dimStyle.extLineIsFixedLen) + self.extLineFixedLen.setValue(self.dimStyle.extLineFixedLen) + self.extLineIsFixedLenToggled(self.dimStyle.extLineIsFixedLen) + self.onInit = False + + def accept_lines_tab(self): + self.dimStyle.dimLineColor = self.dimLineColor.color().name() + self.dimStyle.dimLineLineType = self.dimLineLineType.text() + self.dimStyle.dimLineOffsetExtLine = self.dimLineOffsetExtLine.value() + self.dimStyle.dimLine1Show = not self.dimLine1Hide.isChecked() + self.dimStyle.dimLine2Show = not self.dimLine2Hide.isChecked() + + self.dimStyle.extLineColor = self.extLineColor.color().name() + self.dimStyle.extLine1LineType = self.extLine1LineType.text() + self.dimStyle.extLine2LineType = self.extLine2LineType.text() + self.dimStyle.extLine1Show = not self.extLine1Hide.isChecked() + self.dimStyle.extLine2Show = not self.extLine2Hide.isChecked() + self.dimStyle.extLineOffsetDimLine = self.extLineOffsetDimLine.value() + self.dimStyle.extLineOffsetOrigPoints = self.extLineOffsetOrigPoints.value() + self.dimStyle.extLineIsFixedLen = self.extLineIsFixedLen.isChecked() + self.dimStyle.extLineFixedLen = self.extLineFixedLen.value() + + def redrawDimOnLinesTabChanged(self): + if self.onInit == True: # esco se sono in fase di inizializzazione + return + self.accept_lines_tab() + self.previewDim.drawDim(self.dimStyle) + + def dimLine1HideToggled(self, value): + self.redrawDimOnLinesTabChanged() + + def dimLine2HideToggled(self, value): + self.redrawDimOnLinesTabChanged() + + def dimLineColorChanged(self, value): + self.redrawDimOnLinesTabChanged() + + def dimLineLineTypeChanged(self, value): + self.redrawDimOnLinesTabChanged() + + def dimLineOffsetExtLineChanged(self, value): + self.redrawDimOnLinesTabChanged() + + def extLineColorChanged(self, value): + self.redrawDimOnLinesTabChanged() + + def extLineFixedLenChanged(self, value): + self.redrawDimOnLinesTabChanged() + + def extLineIsFixedLenToggled(self, value): + self.redrawDimOnLinesTabChanged() + + def extLineOffsetDimLineChanged(self, value): + self.redrawDimOnLinesTabChanged() + + def extLineOffsetOrigPointsChanged(self, value): + self.redrawDimOnLinesTabChanged() + + def extLine1HideToggled(self, value): + self.redrawDimOnLinesTabChanged() + + def extLine1LineTypeChanged(self, value): + self.redrawDimOnLinesTabChanged() + + def extLine2HideToggled(self, value): + self.redrawDimOnLinesTabChanged() + + def extLine2LineTypeChanged(self, value): + self.redrawDimOnLinesTabChanged() + + def extLineIsFixedLenToggled(self, value): + self.extLineFixedLen.setEnabled(value) + self.redrawDimOnLinesTabChanged() + + + #################################################################### + # Inizializzazione del TAB che riguarda le linee di quotatura - fine + # Inizializzazione del TAB che riguarda i simboli di quotatura - inizio + #################################################################### + + def init_symbols_tab(self): + self.onInit = True + self.block1Name.setText(self.dimStyle.block1Name) + self.block2Name.setText(self.dimStyle.block2Name) + self.blockLeaderName.setText(self.dimStyle.blockLeaderName) + self.blockWidth.setValue(self.dimStyle.blockWidth) + self.blockScale.setValue(self.dimStyle.blockScale) + + # centerMarkSize 0 = niente, > 0 dimensione marcatore di centro, < 0 dimensione linee d'asse + if self.dimStyle.centerMarkSize == 0: + self.centerMarkNone.setChecked(True) + self.centerMarkLength.setValue(self.dimStyle.centerMarkSize) + elif self.dimStyle.centerMarkSize > 0: + self.centerMarkMark.setChecked(True) + self.centerMarkLength.setValue(self.dimStyle.centerMarkSize) + elif self.dimStyle.centerMarkSize < 0: + self.centerMarkLine.setChecked(True) + self.centerMarkLength.setValue(-self.dimStyle.centerMarkSize) + + # arcSymbPos + if self.dimStyle.arcSymbPos == QadDimStyleArcSymbolPosEnum.BEFORE_TEXT: + self.arcSymbolPreceding.setChecked(True) + elif self.dimStyle.arcSymbPos == QadDimStyleArcSymbolPosEnum.ABOVE_TEXT: + self.arcSymbolAbove.setChecked(True) + elif self.dimStyle.arcSymbPos == QadDimStyleArcSymbolPosEnum.NONE: + self.arcSymbolNone.setChecked(True) + + self.onInit = False + + def accept_symbols_tab(self): + self.dimStyle.block1Name = self.block1Name.text() + self.dimStyle.block2Name = self.block2Name.text() + self.dimStyle.blockLeaderName = self.blockLeaderName.text() + self.dimStyle.blockWidth = self.blockWidth.value() + self.dimStyle.blockScale = self.blockScale.value() + + # centerMarkSize 0 = niente, > 0 dimensione marcatore di centro, < 0 dimensione linee d'asse + if self.centerMarkNone.isChecked(): + self.dimStyle.centerMarkSize = 0 + elif self.centerMarkMark.isChecked(): + self.dimStyle.centerMarkSize = self.centerMarkLength.value() + elif self.centerMarkLine.isChecked(): + self.dimStyle.centerMarkSize = -self.centerMarkLength.value() + + # textForcedRot + if self.arcSymbolPreceding.isChecked(): + self.dimStyle.arcSymbPos = QadDimStyleArcSymbolPosEnum.BEFORE_TEXT + elif self.arcSymbolAbove.isChecked(): + self.dimStyle.arcSymbPos = QadDimStyleArcSymbolPosEnum.ABOVE_TEXT + elif self.arcSymbolNone.isChecked(): + self.dimStyle.arcSymbPos = QadDimStyleArcSymbolPosEnum.NONE + + def redrawDimOnSymbolsTabChanged(self): + if self.onInit == True: # esco se sono in fase di inizializzazione + return + self.accept_symbols_tab() + self.previewDim.drawDim(self.dimStyle) + + def block1NameChanged(self, value): + self.redrawDimOnSymbolsTabChanged() + + def block2NameChanged(self, value): + self.redrawDimOnSymbolsTabChanged() + + def blockLeaderNameChanged(self, value): + self.redrawDimOnSymbolsTabChanged() + + def blockScaleNameChanged(self, value): + self.redrawDimOnSymbolsTabChanged() + + def blockWidthChanged(self, value): + self.redrawDimOnSymbolsTabChanged() + + def blockScaleChanged(self, value): + self.redrawDimOnSymbolsTabChanged() + + def centerMarkNoneChanged(self, value): + self.centerMarkMark.setChecked(False) + self.centerMarkLine.setChecked(False) + self.centerMarkLength.setEnabled(False) + self.redrawDimOnSymbolsTabChanged() + + def centerMarkMarkChanged(self, value): + self.centerMarkNone.setChecked(False) + self.centerMarkLine.setChecked(False) + self.centerMarkLength.setEnabled(True) + self.redrawDimOnSymbolsTabChanged() + + def centerMarkLineChanged(self, value): + self.centerMarkNone.setChecked(False) + self.centerMarkMark.setChecked(False) + self.centerMarkLength.setEnabled(True) + self.redrawDimOnSymbolsTabChanged() + + def centerMarkLengthChanged(self, value): + self.redrawDimOnSymbolsTabChanged() + + def arcSymbolPrecedingToggled(self, value): + self.redrawDimOnSymbolsTabChanged() + + def arcSymbolAboveToggled(self, value): + self.redrawDimOnSymbolsTabChanged() + + def arcSymbolNoneToggled(self, value): + self.redrawDimOnSymbolsTabChanged() + + + #################################################################### + # Inizializzazione del TAB che riguarda i simboli di quotatura - fine + # Inizializzazione del TAB che riguarda i testi di quotatura - inizio + #################################################################### + + def init_text_tab(self): + self.onInit = True + index = self.textFont.findText(self.dimStyle.textFont) + self.textFont.setCurrentIndex(index) + self.textColor.setColor(QColor(self.dimStyle.textColor)) + self.textHeight.setValue(self.dimStyle.textHeight) + + # textVerticalPos + self.textVerticalPos.addItem(QadMsg.translate("DimStyle_Details_Dialog", "Centered")) + self.textVerticalPos.addItem(QadMsg.translate("DimStyle_Details_Dialog", "Above")) + self.textVerticalPos.addItem(QadMsg.translate("DimStyle_Details_Dialog", "Outside")) + self.textVerticalPos.addItem(QadMsg.translate("DimStyle_Details_Dialog", "Below")) + if self.dimStyle.textVerticalPos == QadDimStyleTxtVerticalPosEnum.CENTERED_LINE: + self.textVerticalPos.setCurrentIndex(0) + elif self.dimStyle.textVerticalPos == QadDimStyleTxtVerticalPosEnum.ABOVE_LINE: + self.textVerticalPos.setCurrentIndex(1) + elif self.dimStyle.textVerticalPos == QadDimStyleTxtVerticalPosEnum.EXTERN_LINE: + self.textVerticalPos.setCurrentIndex(2) + elif self.dimStyle.textVerticalPos == QadDimStyleTxtVerticalPosEnum.BELOW_LINE: + self.textVerticalPos.setCurrentIndex(3) + + # textHorizontalPos + self.textHorizontalPos.addItem(QadMsg.translate("DimStyle_Details_Dialog", "Centered")) + self.textHorizontalPos.addItem(QadMsg.translate("DimStyle_Details_Dialog", "At Ext Line 1")) + self.textHorizontalPos.addItem(QadMsg.translate("DimStyle_Details_Dialog", "At Ext Line 2")) + self.textHorizontalPos.addItem(QadMsg.translate("DimStyle_Details_Dialog", "Over Ext Line 1")) + self.textHorizontalPos.addItem(QadMsg.translate("DimStyle_Details_Dialog", "Over Ext Line 2")) + if self.dimStyle.textHorizontalPos == QadDimStyleTxtHorizontalPosEnum.CENTERED_LINE: + self.textHorizontalPos.setCurrentIndex(0) + elif self.dimStyle.textHorizontalPos == QadDimStyleTxtHorizontalPosEnum.FIRST_EXT_LINE: + self.textHorizontalPos.setCurrentIndex(1) + elif self.dimStyle.textHorizontalPos == QadDimStyleTxtHorizontalPosEnum.SECOND_EXT_LINE: + self.textHorizontalPos.setCurrentIndex(2) + elif self.dimStyle.textHorizontalPos == QadDimStyleTxtHorizontalPosEnum.FIRST_EXT_LINE_UP: + self.textHorizontalPos.setCurrentIndex(3) + elif self.dimStyle.textHorizontalPos == QadDimStyleTxtHorizontalPosEnum.SECOND_EXT_LINE_UP: + self.textHorizontalPos.setCurrentIndex(4) + + # textDirection + self.textDirection.addItem(QadMsg.translate("DimStyle_Details_Dialog", "Left-to-Right")) + self.textDirection.addItem(QadMsg.translate("DimStyle_Details_Dialog", "Right-to-Left")) + if self.dimStyle.textDirection == QadDimStyleTxtDirectionEnum.SX_TO_DX: + self.textDirection.setCurrentIndex(0) + elif self.dimStyle.textDirection == QadDimStyleTxtDirectionEnum.DX_TO_SX: + self.textDirection.setCurrentIndex(1) + + self.textOffsetDist.setValue(self.dimStyle.textOffsetDist) + + # textForcedRot + if self.dimStyle.textRotMode == QadDimStyleTxtRotModeEnum.HORIZONTAL: + self.textRotModeHorizontal.setChecked(True) + elif self.dimStyle.textRotMode == QadDimStyleTxtRotModeEnum.ALIGNED_LINE: + self.textRotModeAligned.setChecked(True) + elif self.dimStyle.textRotMode == QadDimStyleTxtRotModeEnum.ISO: + self.textRotModeISO.setChecked(True) + elif self.dimStyle.textRotMode == QadDimStyleTxtRotModeEnum.FORCED_ROTATION: + self.textRotModeFixedRot.setChecked(True) + + self.textForcedRot.setValue(qad_utils.toDegrees(self.dimStyle.textForcedRot)) + self.textRotModeFixedRotToggled(self.textRotModeFixedRot.isChecked()) + self.onInit = False + + def accept_text_tab(self): + self.dimStyle.textFont = self.textFont.currentText() + self.dimStyle.textColor = self.textColor.color().name() + self.dimStyle.textHeight = self.textHeight.value() + + # textVerticalPos + if self.textVerticalPos.currentIndex() == 0: + self.dimStyle.textVerticalPos = QadDimStyleTxtVerticalPosEnum.CENTERED_LINE + elif self.textVerticalPos.currentIndex() == 1: + self.dimStyle.textVerticalPos = QadDimStyleTxtVerticalPosEnum.ABOVE_LINE + elif self.textVerticalPos.currentIndex() == 2: + self.dimStyle.textVerticalPos = QadDimStyleTxtVerticalPosEnum.EXTERN_LINE + elif self.textVerticalPos.currentIndex() == 3: + self.dimStyle.textVerticalPos = QadDimStyleTxtVerticalPosEnum.BELOW_LINE + + # textHorizontalPos + if self.textHorizontalPos.currentIndex() == 0: + self.dimStyle.textHorizontalPos = QadDimStyleTxtHorizontalPosEnum.CENTERED_LINE + elif self.textHorizontalPos.currentIndex() == 1: + self.dimStyle.textHorizontalPos = QadDimStyleTxtHorizontalPosEnum.FIRST_EXT_LINE + elif self.textHorizontalPos.currentIndex() == 2: + self.dimStyle.textHorizontalPos = QadDimStyleTxtHorizontalPosEnum.SECOND_EXT_LINE + elif self.textHorizontalPos.currentIndex() == 3: + self.dimStyle.textHorizontalPos = QadDimStyleTxtHorizontalPosEnum.FIRST_EXT_LINE_UP + elif self.textHorizontalPos.currentIndex() == 4: + self.dimStyle.textHorizontalPos = QadDimStyleTxtHorizontalPosEnum.SECOND_EXT_LINE_UP + + # textDirection + if self.textDirection.currentIndex() == 0: + self.dimStyle.textDirection = QadDimStyleTxtDirectionEnum.SX_TO_DX + elif self.textDirection.currentIndex() == 1: + self.dimStyle.textDirection = QadDimStyleTxtDirectionEnum.DX_TO_SX + + self.dimStyle.textOffsetDist = self.textOffsetDist.value() + + # textForcedRot + if self.textRotModeHorizontal.isChecked(): + self.dimStyle.textRotMode = QadDimStyleTxtRotModeEnum.HORIZONTAL + elif self.textRotModeAligned.isChecked(): + self.dimStyle.textRotMode = QadDimStyleTxtRotModeEnum.ALIGNED_LINE + elif self.textRotModeISO.isChecked(): + self.dimStyle.textRotMode = QadDimStyleTxtRotModeEnum.ISO + elif self.textRotModeFixedRot.isChecked(): + self.dimStyle.textRotMode = QadDimStyleTxtRotModeEnum.FORCED_ROTATION + + self.dimStyle.textForcedRot = qad_utils.toRadians(self.textForcedRot.value()) + + def redrawDimOnTextTabChanged(self): + if self.onInit == True: # esco se sono in fase di inizializzazione + return + self.accept_text_tab() + self.previewDim.drawDim(self.dimStyle) + + + def textColorChanged(self, value): + self.redrawDimOnTextTabChanged() + + def textDirectionChanged(self, value): + self.redrawDimOnTextTabChanged() + + def textFontChanged(self, value): + self.redrawDimOnTextTabChanged() + + def textForcedRotChanged(self, value): + self.redrawDimOnTextTabChanged() + + def textHeightChanged(self, value): + self.redrawDimOnTextTabChanged() + + def textHorizontalPosChanged(self, value): + self.redrawDimOnTextTabChanged() + + def textOffsetDistChanged(self, value): + self.redrawDimOnTextTabChanged() + + def textRotModeHorizontalToggled(self, value): + self.redrawDimOnTextTabChanged() + + def textRotModeAlignedToggled(self, value): + self.redrawDimOnTextTabChanged() + + def textRotModeISOToggled(self, value): + self.redrawDimOnTextTabChanged() + + def textRotModeFixedRotToggled(self, value): + self.textForcedRot.setEnabled(value) + self.redrawDimOnTextTabChanged() + + def textVerticalPosChanged(self, value): + self.redrawDimOnTextTabChanged() + + + #################################################################### + # Inizializzazione del TAB che riguarda i testi di quotatura - fine + # Inizializzazione del TAB che riguarda l'adattamento dei componenti di quotatura - inizio + #################################################################### + + def init_adjust_tab(self): + self.onInit = True + # self.textAdjustAlwaysInside in futuro + + if self.dimStyle.textBlockAdjust == QadDimStyleTextBlocksAdjustEnum.WHICHEVER_FITS_BEST: + self.textBlockAdjustWhicheverFitsBestOutside.setChecked(True) + elif self.dimStyle.textBlockAdjust == QadDimStyleTextBlocksAdjustEnum.FIRST_BLOCKS_THEN_TEXT: + self.textBlockAdjustFirstSymbolOutside.setChecked(True) + elif self.dimStyle.textBlockAdjust == QadDimStyleTextBlocksAdjustEnum.FIRST_TEXT_THEN_BLOCKS: + self.textBlockAdjustFirstTextOutside.setChecked(True) + elif self.dimStyle.textBlockAdjust == QadDimStyleTextBlocksAdjustEnum.BOTH_OUTSIDE_EXT_LINES: + self.textBlockAdjustBothOutside.setChecked(True) + + self.blockSuppressionForNoSpace.setChecked(self.dimStyle.blockSuppressionForNoSpace) + self.onInit = False + + def accept_adjust_tab(self): + if self.textBlockAdjustWhicheverFitsBestOutside.isChecked(): + self.dimStyle.textBlockAdjust = QadDimStyleTextBlocksAdjustEnum.WHICHEVER_FITS_BEST + elif self.textBlockAdjustFirstSymbolOutside.isChecked(): + self.dimStyle.textBlockAdjust = QadDimStyleTextBlocksAdjustEnum.FIRST_BLOCKS_THEN_TEXT + elif self.textBlockAdjustFirstTextOutside.isChecked(): + self.dimStyle.textBlockAdjust = QadDimStyleTextBlocksAdjustEnum.FIRST_TEXT_THEN_BLOCKS + elif self.textBlockAdjustBothOutside.isChecked(): + self.dimStyle.textBlockAdjust = QadDimStyleTextBlocksAdjustEnum.BOTH_OUTSIDE_EXT_LINES + + self.dimStyle.blockSuppressionForNoSpace = self.blockSuppressionForNoSpace.isChecked() + + def redrawDimOnAdjustTabChanged(self): + if self.onInit == True: # esco se sono in fase di inizializzazione + return + self.accept_adjust_tab() + self.previewDim.drawDim(self.dimStyle) + + def blockSuppressionForNoSpaceToggled(self, value): + self.redrawDimOnAdjustTabChanged() + + def textBlockAdjustWhicheverFitsBestOutsideToggled(self, value): + self.redrawDimOnAdjustTabChanged() + + def textBlockAdjustFirstSymbolOutsideToggled(self, value): + self.redrawDimOnAdjustTabChanged() + + def textBlockAdjustFirstTextOutsideToggled(self, value): + self.redrawDimOnAdjustTabChanged() + + def textBlockAdjustBothOutsideToggled(self, value): + self.redrawDimOnAdjustTabChanged() + + + #################################################################### + # Inizializzazione del TAB che riguarda l'adattamento dei componenti di quotatura - fine + # Inizializzazione del TAB che riguarda le unità primarie di quotatura - inizio + #################################################################### + + def init_primaryUnits_tab(self): + self.onInit = True + # textDecimals + self.textDecimals.addItem(QadMsg.translate("DimStyle_Details_Dialog", "0")) + self.textDecimals.addItem(QadMsg.translate("DimStyle_Details_Dialog", "0.0")) + self.textDecimals.addItem(QadMsg.translate("DimStyle_Details_Dialog", "0.00")) + self.textDecimals.addItem(QadMsg.translate("DimStyle_Details_Dialog", "0.000")) + self.textDecimals.addItem(QadMsg.translate("DimStyle_Details_Dialog", "0.0000")) + self.textDecimals.addItem(QadMsg.translate("DimStyle_Details_Dialog", "0.00000")) + self.textDecimals.addItem(QadMsg.translate("DimStyle_Details_Dialog", "0.000000")) + self.textDecimals.addItem(QadMsg.translate("DimStyle_Details_Dialog", "0.0000000")) + self.textDecimals.addItem(QadMsg.translate("DimStyle_Details_Dialog", "0.00000000")) + self.textDecimals.setCurrentIndex(self.dimStyle.textDecimals) + + # textDecimalSep + self.textDecimalSep.addItem(QadMsg.translate("DimStyle_Details_Dialog", "'.' Period")) + self.textDecimalSep.addItem(QadMsg.translate("DimStyle_Details_Dialog", "',' Comma")) + self.textDecimalSep.addItem(QadMsg.translate("DimStyle_Details_Dialog", "' ' Space")) + if self.dimStyle.textDecimalSep == ".": # punto + self.textDecimalSep.setCurrentIndex(0) + elif self.dimStyle.textDecimalSep == ",": # virgola + self.textDecimalSep.setCurrentIndex(1) + elif self.dimStyle.textDecimalSep == " ": # spazio + self.textDecimalSep.setCurrentIndex(2) + + self.textPrefix.setText(self.dimStyle.textPrefix) + self.textSuffix.setText(self.dimStyle.textSuffix) + + self.textSuppressLeadingZeros.setChecked(self.dimStyle.textSuppressLeadingZeros) + self.textDecimalZerosSuppression.setChecked(self.dimStyle.textDecimalZerosSuppression) + self.onInit = False + + def accept_primaryUnits_tab(self): + # textDecimals + self.dimStyle.textDecimals = self.textDecimals.currentIndex() + + # textDecimalSep + if self.textDecimalSep.currentIndex() == 0: # punto + self.dimStyle.textDecimalSep = "." + elif self.textDecimalSep.currentIndex() == 1: # virgola + self.dimStyle.textDecimalSep = "," + elif self.textDecimalSep.currentIndex() == 2: # spazio + self.dimStyle.textDecimalSep = " " + + self.dimStyle.textPrefix = self.textPrefix.text() + self.dimStyle.textSuffix = self.textSuffix.text() + + self.dimStyle.textSuppressLeadingZeros = self.textSuppressLeadingZeros.isChecked() + self.dimStyle.textDecimalZerosSuppression = self.textDecimalZerosSuppression.isChecked() + + def redrawDimOnPrimaryUnitsTabChanged(self): + if self.onInit == True: # esco se sono in fase di inizializzazione + return + self.accept_primaryUnits_tab() + self.previewDim.drawDim(self.dimStyle) + + def textDecimalsChanged(self, index): + self.redrawDimOnPrimaryUnitsTabChanged() + + def textDecimalSepChanged(self, index): + self.redrawDimOnPrimaryUnitsTabChanged() + + def textDecimalZerosSuppressionToggled(self, value): + self.redrawDimOnPrimaryUnitsTabChanged() + + def textPrefixChanged(self, value): + self.redrawDimOnPrimaryUnitsTabChanged() + + def textSuffixChanged(self, value): + self.redrawDimOnPrimaryUnitsTabChanged() + + def textSuppressLeadingZerosToggled(self, value): + self.redrawDimOnPrimaryUnitsTabChanged() + + + #################################################################### + # Inizializzazione del TAB che riguarda le unità primarie di quotatura - fine + #################################################################### + + def ButtonHELP_Pressed(self): + qadShowPluginPDFHelp(QadMsg.translate("Help", "Dimensioning")) + + def accept(self): + self.accept_db_tab() + self.accept_lines_tab() + self.accept_symbols_tab() + self.accept_text_tab() + self.accept_adjust_tab() + self.accept_primaryUnits_tab() + errMsg = self.dimStyle.getInValidErrMsg() + if errMsg is not None: + errMsg += QadMsg.translate("DimStyle_Details_Dialog", "\nDo you want to accepts these settings ?") + res = QMessageBox.question(self, QadMsg.getQADTitle(), errMsg, \ + QMessageBox.Yes | QMessageBox.No) + if res == QMessageBox.No: + return + + self.plugIn.dimStyleLastUsedTabIndex = self.tabWidget.currentIndex() + QDialog.accept(self) + + +####################################################################################### +# Classe che gestisce il widget per visualizzare il preview della quota +class QadPreviewDim(QgsMapCanvas): + + def __init__(self, parent, plugIn): + QgsMapCanvas.__init__(self, parent) + self.plugIn = plugIn + self.setAttribute(Qt.WA_DeleteOnClose) + + self.iface = self.plugIn.iface + self.layerId2canvasLayer = {} + self.canvasLayers = [] + + self.setupUi() + self.bookmark = False + self.dimStyle = None + + + def __del__(self): + self.eraseDim() + + + def setupUi(self): + self.setObjectName("QadPreviewCanvas") + + self.setCanvasColor(qgis.utils.iface.mapCanvas().canvasColor()) + self.enableAntiAliasing(qgis.utils.iface.mapCanvas().antiAliasingEnabled()) + + self.onExtentsChanged() + self.onCrsChanged() + + + def onExtentsChanged(self): + prevFlag = self.renderFlag() + self.setRenderFlag(False) + self.setExtent(self.iface.mapCanvas().extent()) + self.setRenderFlag( prevFlag ) + + + def onCrsChanged(self): + prevFlag = self.renderFlag() + self.setRenderFlag( False ) + mapSettings = self.iface.mapCanvas().mapSettings() + self.mapSettings().setDestinationCrs(mapSettings.destinationCrs()) + self.setRenderFlag( prevFlag ) + + + def getLayers(self): + return map(lambda x: self._layerId(x), self.canvasLayers) + + def setLayers(self, layerIds=None): + prevFlag = self.renderFlag() + self.setRenderFlag( False ) + + if layerIds == None: + self.layerId2canvasLayer = {} + self.canvasLayers = [] + QgsMapCanvas.setLayers(self, []) + + else: + for lid in layerIds: + self.addLayer( lid ) + + self.onExtentsChanged() + self.setRenderFlag( prevFlag ) + + + def addLayer(self, layerId=None): + if layerId == None: + layer = self.iface.activeLayer() + else: + layer = QgsProject.instance().mapLayer( layerId ) + + if layer == None: + return + + prevFlag = self.renderFlag() + self.setRenderFlag( False ) + + # add the layer to the map canvas layer set + self.canvasLayers = [] + id2cl_dict = {} + for l in QgsProject.instance().mapLayers().values(): + lid = self._layerId(l) + if lid in self.layerId2canvasLayer: # previously added + cl = self.layerId2canvasLayer[ lid ] + elif l == layer: # selected layer + cl = layer + else: + continue + + id2cl_dict[ lid ] = cl + self.canvasLayers.append( cl ) + + self.layerId2canvasLayer = id2cl_dict + QgsMapCanvas.setLayers(self, self.canvasLayers ) + + self.onExtentsChanged() + self.setRenderFlag( prevFlag ) + + def delLayer(self, layerId=None): + if layerId == None: + layer = self.iface.activeLayer() + if layer == None: + return + layerId = self._layerId(layer) + + # remove the layer from the map canvas layer set + if layerId in self.layerId2canvasLayer == False: + return + + prevFlag = self.renderFlag() + self.setRenderFlag( False ) + + cl = self.layerId2canvasLayer[ layerId ] + del self.layerId2canvasLayer[ layerId ] + self.canvasLayers.remove( cl ) + QgsMapCanvas.setLayers(self, self.canvasLayers ) + del cl + + self.onExtentsChanged() + self.setRenderFlag( prevFlag ) + + + def _layerId(self, layer): + if hasattr(layer, 'id'): + return layer.id() + return layer.getLayerID() + + + def zoomOnRect(self, zoomRect): + mapSettings = self.mapSettings() + canvasSize = mapSettings.outputSize() + sfx = zoomRect.width() / canvasSize.width() + sfy = zoomRect.height() / canvasSize.height() + sf = max(sfx, sfy) + + prevFlag = self.renderFlag() + self.setRenderFlag(False) + + self.setExtent(zoomRect) + self.setCenter(zoomRect.center()) + + self.setRenderFlag( prevFlag ) + + + def drawDim(self, dimStyle): + if dimStyle is None: + return + + self.eraseDim() + + if dimStyle.isValid() == False: + return + self.dimStyle = dimStyle + + if self.plugIn.insertBookmark() == True: + self.bookmark = True + + for layerId in self.getLayers(): # tolgo tutti i layer + self.delLayer(layerId) + # inserisco i layer della quotatura + self.isEditableTextualLayer = None + layer = self.dimStyle.getTextualLayer() + if layer is not None: + self.isEditableTextualLayer = layer.isEditable() + if self.isEditableTextualLayer == False: + layer.startEditing() + self.addLayer(layer.id()) + + self.isEditableSymbolLayer = None + layer = self.dimStyle.getSymbolLayer() + if layer is not None: + self.isEditableSymbolLayer = layer.isEditable() + if self.isEditableSymbolLayer == False: + layer.startEditing() + self.addLayer(layer.id()) + + self.isEditableLinearLayer = None + layer = self.dimStyle.getLinearLayer() + if layer is not None: + self.isEditableLinearLayer = layer.isEditable() + if self.isEditableLinearLayer == False: + layer.startEditing() + self.addLayer(layer.id()) + + if self.dimStyle.isValid() == False: + return + + + ########################### + # quota lineare orizzontale + dimPt1 = QgsPointXY(0, 0) + dimPt2 = QgsPointXY(13.45, 0) + linePosPt = QgsPointXY(0, 10) + + # calcolo il rettangolo di occupazione della quota + dimEntity, textOffsetRectGeom = self.dimStyle.getLinearDimFeatures(self.plugIn.canvas, \ + dimPt1, dimPt2, linePosPt) + rect = textOffsetRectGeom.boundingBox() + for g in dimEntity.getLinearGeometryCollection(): + rect.combineExtentWith(g.boundingBox()) + for g in dimEntity.getSymbolGeometryCollection(): + rect.combineExtentWith(g.boundingBox()) + + self.dimStyle.addLinearDimToLayers(self.plugIn, dimPt1, dimPt2, linePosPt) + + ########################### + # quota lineare verticale + dimPt1 = QgsPointXY(0, 0) + dimPt2 = QgsPointXY(0, -15.7) + linePosPt = QgsPointXY(-9, 0) + + # calcolo il rettangolo di occupazione della quota + dimEntity, textOffsetRectGeom = self.dimStyle.getLinearDimFeatures(self.plugIn.canvas, \ + dimPt1, dimPt2, linePosPt, None, \ + QadDimStyleAlignmentEnum.VERTICAL) + rect.combineExtentWith(textOffsetRectGeom.boundingBox()) + for g in dimEntity.getLinearGeometryCollection(): + rect.combineExtentWith(g.boundingBox()) + for g in dimEntity.getSymbolGeometryCollection(): + rect.combineExtentWith(g.boundingBox()) + + self.dimStyle.addLinearDimToLayers(self.plugIn, dimPt1, dimPt2, linePosPt, None, \ + QadDimStyleAlignmentEnum.VERTICAL) + + ########################### + # quota allineata obliqua + dimPt1 = QgsPointXY(13.45, 0) + dimPt2 = QgsPointXY(23, -20) + linePosPt = QgsPointXY(23, 0) + + # calcolo il rettangolo di occupazione della quota + dimEntity, textOffsetRectGeom = self.dimStyle.getAlignedDimFeatures(self.plugIn.canvas, \ + dimPt1, dimPt2, linePosPt) + rect.combineExtentWith(textOffsetRectGeom.boundingBox()) + for g in dimEntity.getLinearGeometryCollection(): + rect.combineExtentWith(g.boundingBox()) + for g in dimEntity.getSymbolGeometryCollection(): + rect.combineExtentWith(g.boundingBox()) + + self.dimStyle.addAlignedDimToLayers(self.plugIn, dimPt1, dimPt2, linePosPt, None) + + ########################### + # quota arco + dimArc = QadArc() + dimArc.fromStartSecondEndPts(QgsPointXY(23, -20), QgsPointXY(10, -15), QgsPointXY(0, -15.7)) + linePosPt = QgsPointXY(13, -13) + + # calcolo il rettangolo di occupazione della quota + dimEntity, textOffsetRectGeom = self.dimStyle.getArcDimFeatures(self.plugIn.canvas, \ + dimArc, linePosPt) + rect.combineExtentWith(textOffsetRectGeom.boundingBox()) + for g in dimEntity.getLinearGeometryCollection(): + rect.combineExtentWith(g.boundingBox()) + for g in dimEntity.getSymbolGeometryCollection(): + rect.combineExtentWith(g.boundingBox()) + + self.dimStyle.addArcDimToLayers(self.plugIn, dimArc, linePosPt) + + self.zoomOnRect(rect) + + def eraseDim(self): + if self.bookmark == True: + self.plugIn.undoUntilBookmark() + self.bookmark = False + + for layerId in self.getLayers(): # tolgo tutti i layer + self.delLayer(layerId) diff --git a/qad_dimstyle_details_ui.py b/qad_dimstyle_details_ui.py index b3c722ea..d4bbcc07 100644 --- a/qad_dimstyle_details_ui.py +++ b/qad_dimstyle_details_ui.py @@ -1,610 +1,665 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file 'qad_dimstyle_details.ui' -# -# Created: Tue Sep 08 15:55:20 2015 -# by: PyQt4 UI code generator 4.10.2 -# -# WARNING! All changes made in this file will be lost! - -from PyQt4 import QtCore, QtGui - -try: - _fromUtf8 = QtCore.QString.fromUtf8 -except AttributeError: - def _fromUtf8(s): - return s - -try: - _encoding = QtGui.QApplication.UnicodeUTF8 - def _translate(context, text, disambig): - return QtGui.QApplication.translate(context, text, disambig, _encoding) -except AttributeError: - def _translate(context, text, disambig): - return QtGui.QApplication.translate(context, text, disambig) - -class Ui_DimStyle_Details_Dialog(object): - def setupUi(self, DimStyle_Details_Dialog): - DimStyle_Details_Dialog.setObjectName(_fromUtf8("DimStyle_Details_Dialog")) - DimStyle_Details_Dialog.resize(568, 446) - self.tabWidget = QtGui.QTabWidget(DimStyle_Details_Dialog) - self.tabWidget.setGeometry(QtCore.QRect(10, 10, 551, 391)) - self.tabWidget.setObjectName(_fromUtf8("tabWidget")) - self.DBTab = QtGui.QWidget() - self.DBTab.setObjectName(_fromUtf8("DBTab")) - self.groupBox_5 = QtGui.QGroupBox(self.DBTab) - self.groupBox_5.setGeometry(QtCore.QRect(10, 10, 261, 81)) - self.groupBox_5.setObjectName(_fromUtf8("groupBox_5")) - self.label_2 = QtGui.QLabel(self.groupBox_5) - self.label_2.setGeometry(QtCore.QRect(12, 20, 51, 21)) - self.label_2.setObjectName(_fromUtf8("label_2")) - self.linearLayerName = QtGui.QComboBox(self.groupBox_5) - self.linearLayerName.setGeometry(QtCore.QRect(70, 20, 181, 22)) - self.linearLayerName.setObjectName(_fromUtf8("linearLayerName")) - self.lineTypeFieldName = QtGui.QComboBox(self.groupBox_5) - self.lineTypeFieldName.setGeometry(QtCore.QRect(110, 50, 141, 22)) - self.lineTypeFieldName.setObjectName(_fromUtf8("lineTypeFieldName")) - self.label_21 = QtGui.QLabel(self.groupBox_5) - self.label_21.setGeometry(QtCore.QRect(10, 50, 81, 21)) - self.label_21.setObjectName(_fromUtf8("label_21")) - self.groupBox_6 = QtGui.QGroupBox(self.DBTab) - self.groupBox_6.setGeometry(QtCore.QRect(9, 100, 261, 111)) - self.groupBox_6.setObjectName(_fromUtf8("groupBox_6")) - self.symbolLayerName = QtGui.QComboBox(self.groupBox_6) - self.symbolLayerName.setGeometry(QtCore.QRect(70, 20, 181, 22)) - self.symbolLayerName.setObjectName(_fromUtf8("symbolLayerName")) - self.label_13 = QtGui.QLabel(self.groupBox_6) - self.label_13.setGeometry(QtCore.QRect(10, 20, 51, 21)) - self.label_13.setObjectName(_fromUtf8("label_13")) - self.label_22 = QtGui.QLabel(self.groupBox_6) - self.label_22.setGeometry(QtCore.QRect(10, 50, 81, 21)) - self.label_22.setObjectName(_fromUtf8("label_22")) - self.symbolFieldName = QtGui.QComboBox(self.groupBox_6) - self.symbolFieldName.setGeometry(QtCore.QRect(110, 50, 141, 22)) - self.symbolFieldName.setObjectName(_fromUtf8("symbolFieldName")) - self.scaleFieldName = QtGui.QComboBox(self.groupBox_6) - self.scaleFieldName.setGeometry(QtCore.QRect(110, 80, 141, 22)) - self.scaleFieldName.setObjectName(_fromUtf8("scaleFieldName")) - self.label_23 = QtGui.QLabel(self.groupBox_6) - self.label_23.setGeometry(QtCore.QRect(10, 80, 81, 21)) - self.label_23.setObjectName(_fromUtf8("label_23")) - self.groupBox_7 = QtGui.QGroupBox(self.DBTab) - self.groupBox_7.setGeometry(QtCore.QRect(280, 220, 261, 141)) - self.groupBox_7.setObjectName(_fromUtf8("groupBox_7")) - self.label_24 = QtGui.QLabel(self.groupBox_7) - self.label_24.setGeometry(QtCore.QRect(10, 50, 81, 21)) - self.label_24.setObjectName(_fromUtf8("label_24")) - self.rotFieldName = QtGui.QComboBox(self.groupBox_7) - self.rotFieldName.setGeometry(QtCore.QRect(110, 50, 141, 22)) - self.rotFieldName.setObjectName(_fromUtf8("rotFieldName")) - self.label_16 = QtGui.QLabel(self.groupBox_7) - self.label_16.setGeometry(QtCore.QRect(10, 20, 81, 21)) - self.label_16.setObjectName(_fromUtf8("label_16")) - self.colorFieldName = QtGui.QComboBox(self.groupBox_7) - self.colorFieldName.setGeometry(QtCore.QRect(110, 20, 141, 22)) - self.colorFieldName.setObjectName(_fromUtf8("colorFieldName")) - self.label_28 = QtGui.QLabel(self.groupBox_7) - self.label_28.setGeometry(QtCore.QRect(10, 110, 91, 21)) - self.label_28.setObjectName(_fromUtf8("label_28")) - self.idParentFieldName = QtGui.QComboBox(self.groupBox_7) - self.idParentFieldName.setGeometry(QtCore.QRect(110, 110, 141, 22)) - self.idParentFieldName.setObjectName(_fromUtf8("idParentFieldName")) - self.label_25 = QtGui.QLabel(self.groupBox_7) - self.label_25.setGeometry(QtCore.QRect(10, 80, 91, 21)) - self.label_25.setObjectName(_fromUtf8("label_25")) - self.componentFieldName = QtGui.QComboBox(self.groupBox_7) - self.componentFieldName.setGeometry(QtCore.QRect(110, 80, 141, 22)) - self.componentFieldName.setObjectName(_fromUtf8("componentFieldName")) - self.groupBox_8 = QtGui.QGroupBox(self.DBTab) - self.groupBox_8.setGeometry(QtCore.QRect(10, 220, 261, 141)) - self.groupBox_8.setObjectName(_fromUtf8("groupBox_8")) - self.label_27 = QtGui.QLabel(self.groupBox_8) - self.label_27.setGeometry(QtCore.QRect(10, 50, 81, 21)) - self.label_27.setObjectName(_fromUtf8("label_27")) - self.idFieldName = QtGui.QComboBox(self.groupBox_8) - self.idFieldName.setGeometry(QtCore.QRect(110, 50, 141, 22)) - self.idFieldName.setObjectName(_fromUtf8("idFieldName")) - self.dimStyleFieldName = QtGui.QComboBox(self.groupBox_8) - self.dimStyleFieldName.setGeometry(QtCore.QRect(110, 80, 141, 22)) - self.dimStyleFieldName.setObjectName(_fromUtf8("dimStyleFieldName")) - self.label_29 = QtGui.QLabel(self.groupBox_8) - self.label_29.setGeometry(QtCore.QRect(10, 80, 91, 21)) - self.label_29.setObjectName(_fromUtf8("label_29")) - self.label_30 = QtGui.QLabel(self.groupBox_8) - self.label_30.setGeometry(QtCore.QRect(10, 110, 91, 21)) - self.label_30.setObjectName(_fromUtf8("label_30")) - self.dimTypeFieldName = QtGui.QComboBox(self.groupBox_8) - self.dimTypeFieldName.setGeometry(QtCore.QRect(110, 110, 141, 22)) - self.dimTypeFieldName.setObjectName(_fromUtf8("dimTypeFieldName")) - self.label_14 = QtGui.QLabel(self.groupBox_8) - self.label_14.setGeometry(QtCore.QRect(10, 20, 51, 21)) - self.label_14.setObjectName(_fromUtf8("label_14")) - self.textualLayerName = QtGui.QComboBox(self.groupBox_8) - self.textualLayerName.setGeometry(QtCore.QRect(70, 20, 181, 22)) - self.textualLayerName.setObjectName(_fromUtf8("textualLayerName")) - self.previewDummy = QtGui.QPushButton(self.DBTab) - self.previewDummy.setGeometry(QtCore.QRect(290, 20, 251, 181)) - self.previewDummy.setText(_fromUtf8("")) - self.previewDummy.setObjectName(_fromUtf8("previewDummy")) - self.tabWidget.addTab(self.DBTab, _fromUtf8("")) - self.LineTab = QtGui.QWidget() - self.LineTab.setObjectName(_fromUtf8("LineTab")) - self.groupBox = QtGui.QGroupBox(self.LineTab) - self.groupBox.setGeometry(QtCore.QRect(10, 10, 271, 141)) - self.groupBox.setObjectName(_fromUtf8("groupBox")) - self.label = QtGui.QLabel(self.groupBox) - self.label.setGeometry(QtCore.QRect(10, 20, 51, 21)) - self.label.setObjectName(_fromUtf8("label")) - self.dimLineLineType = QtGui.QLineEdit(self.groupBox) - self.dimLineLineType.setGeometry(QtCore.QRect(80, 50, 181, 20)) - self.dimLineLineType.setObjectName(_fromUtf8("dimLineLineType")) - self.label_3 = QtGui.QLabel(self.groupBox) - self.label_3.setGeometry(QtCore.QRect(10, 50, 61, 21)) - self.label_3.setObjectName(_fromUtf8("label_3")) - self.label_5 = QtGui.QLabel(self.groupBox) - self.label_5.setGeometry(QtCore.QRect(10, 110, 61, 21)) - self.label_5.setObjectName(_fromUtf8("label_5")) - self.dimLine1Hide = QtGui.QCheckBox(self.groupBox) - self.dimLine1Hide.setGeometry(QtCore.QRect(70, 110, 91, 21)) - self.dimLine1Hide.setObjectName(_fromUtf8("dimLine1Hide")) - self.dimLine2Hide = QtGui.QCheckBox(self.groupBox) - self.dimLine2Hide.setGeometry(QtCore.QRect(170, 110, 91, 21)) - self.dimLine2Hide.setObjectName(_fromUtf8("dimLine2Hide")) - self.dimLineColorDummy = QtGui.QPushButton(self.groupBox) - self.dimLineColorDummy.setGeometry(QtCore.QRect(80, 20, 181, 23)) - self.dimLineColorDummy.setText(_fromUtf8("")) - self.dimLineColorDummy.setObjectName(_fromUtf8("dimLineColorDummy")) - self.groupBox_2 = QtGui.QGroupBox(self.LineTab) - self.groupBox_2.setGeometry(QtCore.QRect(10, 190, 531, 141)) - self.groupBox_2.setObjectName(_fromUtf8("groupBox_2")) - self.label_6 = QtGui.QLabel(self.groupBox_2) - self.label_6.setGeometry(QtCore.QRect(11, 20, 51, 21)) - self.label_6.setObjectName(_fromUtf8("label_6")) - self.extLine1LineType = QtGui.QLineEdit(self.groupBox_2) - self.extLine1LineType.setGeometry(QtCore.QRect(100, 50, 161, 20)) - self.extLine1LineType.setObjectName(_fromUtf8("extLine1LineType")) - self.label_7 = QtGui.QLabel(self.groupBox_2) - self.label_7.setGeometry(QtCore.QRect(10, 50, 81, 21)) - self.label_7.setObjectName(_fromUtf8("label_7")) - self.label_8 = QtGui.QLabel(self.groupBox_2) - self.label_8.setGeometry(QtCore.QRect(10, 80, 81, 21)) - self.label_8.setObjectName(_fromUtf8("label_8")) - self.extLine2LineType = QtGui.QLineEdit(self.groupBox_2) - self.extLine2LineType.setGeometry(QtCore.QRect(100, 80, 161, 20)) - self.extLine2LineType.setObjectName(_fromUtf8("extLine2LineType")) - self.label_9 = QtGui.QLabel(self.groupBox_2) - self.label_9.setGeometry(QtCore.QRect(10, 110, 61, 21)) - self.label_9.setObjectName(_fromUtf8("label_9")) - self.extLine1Hide = QtGui.QCheckBox(self.groupBox_2) - self.extLine1Hide.setGeometry(QtCore.QRect(70, 110, 91, 21)) - self.extLine1Hide.setObjectName(_fromUtf8("extLine1Hide")) - self.extLine2Hide = QtGui.QCheckBox(self.groupBox_2) - self.extLine2Hide.setGeometry(QtCore.QRect(170, 110, 91, 21)) - self.extLine2Hide.setObjectName(_fromUtf8("extLine2Hide")) - self.label_10 = QtGui.QLabel(self.groupBox_2) - self.label_10.setGeometry(QtCore.QRect(290, 20, 141, 21)) - self.label_10.setObjectName(_fromUtf8("label_10")) - self.extLineOffsetDimLine = QtGui.QDoubleSpinBox(self.groupBox_2) - self.extLineOffsetDimLine.setGeometry(QtCore.QRect(440, 20, 81, 22)) - self.extLineOffsetDimLine.setDecimals(6) - self.extLineOffsetDimLine.setMaximum(1000000.0) - self.extLineOffsetDimLine.setSingleStep(0.0005) - self.extLineOffsetDimLine.setObjectName(_fromUtf8("extLineOffsetDimLine")) - self.extLineOffsetOrigPoints = QtGui.QDoubleSpinBox(self.groupBox_2) - self.extLineOffsetOrigPoints.setGeometry(QtCore.QRect(440, 50, 81, 22)) - self.extLineOffsetOrigPoints.setDecimals(6) - self.extLineOffsetOrigPoints.setMaximum(1000000.0) - self.extLineOffsetOrigPoints.setSingleStep(0.0005) - self.extLineOffsetOrigPoints.setObjectName(_fromUtf8("extLineOffsetOrigPoints")) - self.label_11 = QtGui.QLabel(self.groupBox_2) - self.label_11.setGeometry(QtCore.QRect(290, 50, 141, 21)) - self.label_11.setObjectName(_fromUtf8("label_11")) - self.extLineIsFixedLen = QtGui.QCheckBox(self.groupBox_2) - self.extLineIsFixedLen.setGeometry(QtCore.QRect(290, 80, 231, 21)) - self.extLineIsFixedLen.setObjectName(_fromUtf8("extLineIsFixedLen")) - self.label_12 = QtGui.QLabel(self.groupBox_2) - self.label_12.setGeometry(QtCore.QRect(290, 110, 141, 21)) - self.label_12.setObjectName(_fromUtf8("label_12")) - self.extLineFixedLen = QtGui.QDoubleSpinBox(self.groupBox_2) - self.extLineFixedLen.setGeometry(QtCore.QRect(440, 110, 81, 22)) - self.extLineFixedLen.setDecimals(6) - self.extLineFixedLen.setMaximum(1000000.0) - self.extLineFixedLen.setSingleStep(0.0005) - self.extLineFixedLen.setObjectName(_fromUtf8("extLineFixedLen")) - self.extLineColorDummy = QtGui.QPushButton(self.groupBox_2) - self.extLineColorDummy.setGeometry(QtCore.QRect(80, 20, 181, 23)) - self.extLineColorDummy.setText(_fromUtf8("")) - self.extLineColorDummy.setObjectName(_fromUtf8("extLineColorDummy")) - self.tabWidget.addTab(self.LineTab, _fromUtf8("")) - self.SymbolTab = QtGui.QWidget() - self.SymbolTab.setObjectName(_fromUtf8("SymbolTab")) - self.groupBox_3 = QtGui.QGroupBox(self.SymbolTab) - self.groupBox_3.setGeometry(QtCore.QRect(10, 10, 271, 171)) - self.groupBox_3.setObjectName(_fromUtf8("groupBox_3")) - self.block1Name = QtGui.QLineEdit(self.groupBox_3) - self.block1Name.setGeometry(QtCore.QRect(110, 20, 151, 20)) - self.block1Name.setObjectName(_fromUtf8("block1Name")) - self.label_15 = QtGui.QLabel(self.groupBox_3) - self.label_15.setGeometry(QtCore.QRect(10, 20, 91, 21)) - self.label_15.setObjectName(_fromUtf8("label_15")) - self.block2Name = QtGui.QLineEdit(self.groupBox_3) - self.block2Name.setGeometry(QtCore.QRect(110, 50, 151, 20)) - self.block2Name.setObjectName(_fromUtf8("block2Name")) - self.label_18 = QtGui.QLabel(self.groupBox_3) - self.label_18.setGeometry(QtCore.QRect(10, 50, 91, 21)) - self.label_18.setObjectName(_fromUtf8("label_18")) - self.blockLeaderName = QtGui.QLineEdit(self.groupBox_3) - self.blockLeaderName.setGeometry(QtCore.QRect(110, 80, 151, 20)) - self.blockLeaderName.setObjectName(_fromUtf8("blockLeaderName")) - self.label_19 = QtGui.QLabel(self.groupBox_3) - self.label_19.setGeometry(QtCore.QRect(10, 80, 91, 21)) - self.label_19.setObjectName(_fromUtf8("label_19")) - self.blockWidth = QtGui.QDoubleSpinBox(self.groupBox_3) - self.blockWidth.setGeometry(QtCore.QRect(180, 110, 81, 22)) - self.blockWidth.setDecimals(6) - self.blockWidth.setMaximum(1000000.0) - self.blockWidth.setSingleStep(0.0005) - self.blockWidth.setObjectName(_fromUtf8("blockWidth")) - self.label_17 = QtGui.QLabel(self.groupBox_3) - self.label_17.setGeometry(QtCore.QRect(10, 110, 111, 21)) - self.label_17.setObjectName(_fromUtf8("label_17")) - self.blockScale = QtGui.QDoubleSpinBox(self.groupBox_3) - self.blockScale.setGeometry(QtCore.QRect(180, 140, 81, 22)) - self.blockScale.setDecimals(6) - self.blockScale.setMaximum(1000000.0) - self.blockScale.setSingleStep(0.0005) - self.blockScale.setObjectName(_fromUtf8("blockScale")) - self.label_20 = QtGui.QLabel(self.groupBox_3) - self.label_20.setGeometry(QtCore.QRect(10, 140, 111, 21)) - self.label_20.setObjectName(_fromUtf8("label_20")) - self.tabWidget.addTab(self.SymbolTab, _fromUtf8("")) - self.TextTab = QtGui.QWidget() - self.TextTab.setObjectName(_fromUtf8("TextTab")) - self.groupBox_9 = QtGui.QGroupBox(self.TextTab) - self.groupBox_9.setGeometry(QtCore.QRect(10, 10, 271, 111)) - self.groupBox_9.setObjectName(_fromUtf8("groupBox_9")) - self.label_26 = QtGui.QLabel(self.groupBox_9) - self.label_26.setGeometry(QtCore.QRect(10, 80, 111, 21)) - self.label_26.setObjectName(_fromUtf8("label_26")) - self.textHeight = QtGui.QDoubleSpinBox(self.groupBox_9) - self.textHeight.setGeometry(QtCore.QRect(180, 80, 81, 22)) - self.textHeight.setDecimals(6) - self.textHeight.setMaximum(1000000.0) - self.textHeight.setSingleStep(0.0005) - self.textHeight.setObjectName(_fromUtf8("textHeight")) - self.label_40 = QtGui.QLabel(self.groupBox_9) - self.label_40.setGeometry(QtCore.QRect(10, 20, 91, 21)) - self.label_40.setObjectName(_fromUtf8("label_40")) - self.textFont = QtGui.QFontComboBox(self.groupBox_9) - self.textFont.setGeometry(QtCore.QRect(100, 20, 161, 22)) - self.textFont.setObjectName(_fromUtf8("textFont")) - self.textColorDummy = QtGui.QPushButton(self.groupBox_9) - self.textColorDummy.setGeometry(QtCore.QRect(80, 50, 181, 23)) - self.textColorDummy.setText(_fromUtf8("")) - self.textColorDummy.setObjectName(_fromUtf8("textColorDummy")) - self.label_4 = QtGui.QLabel(self.groupBox_9) - self.label_4.setGeometry(QtCore.QRect(10, 50, 51, 21)) - self.label_4.setObjectName(_fromUtf8("label_4")) - self.groupBox_10 = QtGui.QGroupBox(self.TextTab) - self.groupBox_10.setGeometry(QtCore.QRect(10, 220, 271, 141)) - self.groupBox_10.setObjectName(_fromUtf8("groupBox_10")) - self.textVerticalPos = QtGui.QComboBox(self.groupBox_10) - self.textVerticalPos.setGeometry(QtCore.QRect(100, 20, 161, 22)) - self.textVerticalPos.setObjectName(_fromUtf8("textVerticalPos")) - self.label_31 = QtGui.QLabel(self.groupBox_10) - self.label_31.setGeometry(QtCore.QRect(10, 20, 81, 21)) - self.label_31.setObjectName(_fromUtf8("label_31")) - self.label_32 = QtGui.QLabel(self.groupBox_10) - self.label_32.setGeometry(QtCore.QRect(10, 50, 81, 21)) - self.label_32.setObjectName(_fromUtf8("label_32")) - self.textHorizontalPos = QtGui.QComboBox(self.groupBox_10) - self.textHorizontalPos.setGeometry(QtCore.QRect(100, 50, 161, 22)) - self.textHorizontalPos.setObjectName(_fromUtf8("textHorizontalPos")) - self.label_33 = QtGui.QLabel(self.groupBox_10) - self.label_33.setGeometry(QtCore.QRect(10, 80, 81, 21)) - self.label_33.setObjectName(_fromUtf8("label_33")) - self.textDirection = QtGui.QComboBox(self.groupBox_10) - self.textDirection.setGeometry(QtCore.QRect(100, 80, 161, 22)) - self.textDirection.setObjectName(_fromUtf8("textDirection")) - self.textOffsetDist = QtGui.QDoubleSpinBox(self.groupBox_10) - self.textOffsetDist.setGeometry(QtCore.QRect(180, 110, 81, 22)) - self.textOffsetDist.setDecimals(6) - self.textOffsetDist.setMaximum(1000000.0) - self.textOffsetDist.setSingleStep(0.0005) - self.textOffsetDist.setObjectName(_fromUtf8("textOffsetDist")) - self.label_34 = QtGui.QLabel(self.groupBox_10) - self.label_34.setGeometry(QtCore.QRect(10, 110, 141, 21)) - self.label_34.setObjectName(_fromUtf8("label_34")) - self.textAlignment_groupBox = QtGui.QGroupBox(self.TextTab) - self.textAlignment_groupBox.setGeometry(QtCore.QRect(290, 250, 241, 111)) - self.textAlignment_groupBox.setObjectName(_fromUtf8("textAlignment_groupBox")) - self.textRotModeHorizontal = QtGui.QRadioButton(self.textAlignment_groupBox) - self.textRotModeHorizontal.setGeometry(QtCore.QRect(10, 20, 251, 17)) - self.textRotModeHorizontal.setObjectName(_fromUtf8("textRotModeHorizontal")) - self.textRotModeAligned = QtGui.QRadioButton(self.textAlignment_groupBox) - self.textRotModeAligned.setGeometry(QtCore.QRect(10, 40, 251, 17)) - self.textRotModeAligned.setObjectName(_fromUtf8("textRotModeAligned")) - self.textRotModeISO = QtGui.QRadioButton(self.textAlignment_groupBox) - self.textRotModeISO.setGeometry(QtCore.QRect(10, 60, 251, 17)) - self.textRotModeISO.setObjectName(_fromUtf8("textRotModeISO")) - self.textRotModeFixedRot = QtGui.QRadioButton(self.textAlignment_groupBox) - self.textRotModeFixedRot.setGeometry(QtCore.QRect(10, 80, 101, 17)) - self.textRotModeFixedRot.setObjectName(_fromUtf8("textRotModeFixedRot")) - self.textForcedRot = QtGui.QDoubleSpinBox(self.textAlignment_groupBox) - self.textForcedRot.setGeometry(QtCore.QRect(150, 80, 81, 22)) - self.textForcedRot.setDecimals(6) - self.textForcedRot.setMaximum(360.0) - self.textForcedRot.setSingleStep(0.0005) - self.textForcedRot.setObjectName(_fromUtf8("textForcedRot")) - self.tabWidget.addTab(self.TextTab, _fromUtf8("")) - self.AdjustTab = QtGui.QWidget() - self.AdjustTab.setObjectName(_fromUtf8("AdjustTab")) - self.groupBox_12 = QtGui.QGroupBox(self.AdjustTab) - self.groupBox_12.setGeometry(QtCore.QRect(10, 10, 271, 211)) - self.groupBox_12.setObjectName(_fromUtf8("groupBox_12")) - self.label_35 = QtGui.QLabel(self.groupBox_12) - self.label_35.setGeometry(QtCore.QRect(10, 20, 251, 51)) - self.label_35.setWordWrap(True) - self.label_35.setObjectName(_fromUtf8("label_35")) - self.textBlockAdjustWhicheverFitsBestOutside = QtGui.QRadioButton(self.groupBox_12) - self.textBlockAdjustWhicheverFitsBestOutside.setGeometry(QtCore.QRect(10, 80, 251, 17)) - self.textBlockAdjustWhicheverFitsBestOutside.setObjectName(_fromUtf8("textBlockAdjustWhicheverFitsBestOutside")) - self.textBlockAdjustFirstSymbolOutside = QtGui.QRadioButton(self.groupBox_12) - self.textBlockAdjustFirstSymbolOutside.setGeometry(QtCore.QRect(10, 100, 251, 17)) - self.textBlockAdjustFirstSymbolOutside.setObjectName(_fromUtf8("textBlockAdjustFirstSymbolOutside")) - self.textBlockAdjustFirstTextOutside = QtGui.QRadioButton(self.groupBox_12) - self.textBlockAdjustFirstTextOutside.setGeometry(QtCore.QRect(10, 120, 251, 17)) - self.textBlockAdjustFirstTextOutside.setObjectName(_fromUtf8("textBlockAdjustFirstTextOutside")) - self.textBlockAdjustBothOutside = QtGui.QRadioButton(self.groupBox_12) - self.textBlockAdjustBothOutside.setGeometry(QtCore.QRect(10, 140, 251, 17)) - self.textBlockAdjustBothOutside.setObjectName(_fromUtf8("textBlockAdjustBothOutside")) - self.blockSuppressionForNoSpace = QtGui.QCheckBox(self.groupBox_12) - self.blockSuppressionForNoSpace.setGeometry(QtCore.QRect(10, 160, 251, 51)) - self.blockSuppressionForNoSpace.setObjectName(_fromUtf8("blockSuppressionForNoSpace")) - self.tabWidget.addTab(self.AdjustTab, _fromUtf8("")) - self.PrimaryUnitTab = QtGui.QWidget() - self.PrimaryUnitTab.setObjectName(_fromUtf8("PrimaryUnitTab")) - self.groupBox_13 = QtGui.QGroupBox(self.PrimaryUnitTab) - self.groupBox_13.setGeometry(QtCore.QRect(10, 10, 271, 141)) - self.groupBox_13.setObjectName(_fromUtf8("groupBox_13")) - self.label_36 = QtGui.QLabel(self.groupBox_13) - self.label_36.setGeometry(QtCore.QRect(10, 20, 81, 21)) - self.label_36.setObjectName(_fromUtf8("label_36")) - self.textDecimals = QtGui.QComboBox(self.groupBox_13) - self.textDecimals.setGeometry(QtCore.QRect(100, 20, 161, 22)) - self.textDecimals.setObjectName(_fromUtf8("textDecimals")) - self.textDecimalSep = QtGui.QComboBox(self.groupBox_13) - self.textDecimalSep.setGeometry(QtCore.QRect(170, 50, 91, 22)) - self.textDecimalSep.setObjectName(_fromUtf8("textDecimalSep")) - self.label_37 = QtGui.QLabel(self.groupBox_13) - self.label_37.setGeometry(QtCore.QRect(10, 50, 111, 21)) - self.label_37.setObjectName(_fromUtf8("label_37")) - self.textPrefix = QtGui.QLineEdit(self.groupBox_13) - self.textPrefix.setGeometry(QtCore.QRect(110, 80, 151, 20)) - self.textPrefix.setObjectName(_fromUtf8("textPrefix")) - self.label_38 = QtGui.QLabel(self.groupBox_13) - self.label_38.setGeometry(QtCore.QRect(10, 80, 91, 21)) - self.label_38.setObjectName(_fromUtf8("label_38")) - self.label_39 = QtGui.QLabel(self.groupBox_13) - self.label_39.setGeometry(QtCore.QRect(10, 110, 91, 21)) - self.label_39.setObjectName(_fromUtf8("label_39")) - self.textSuffix = QtGui.QLineEdit(self.groupBox_13) - self.textSuffix.setGeometry(QtCore.QRect(110, 110, 151, 20)) - self.textSuffix.setObjectName(_fromUtf8("textSuffix")) - self.groupBox_14 = QtGui.QGroupBox(self.PrimaryUnitTab) - self.groupBox_14.setGeometry(QtCore.QRect(10, 160, 271, 51)) - self.groupBox_14.setObjectName(_fromUtf8("groupBox_14")) - self.textSuppressLeadingZeros = QtGui.QCheckBox(self.groupBox_14) - self.textSuppressLeadingZeros.setGeometry(QtCore.QRect(20, 20, 70, 17)) - self.textSuppressLeadingZeros.setObjectName(_fromUtf8("textSuppressLeadingZeros")) - self.textDecimalZerosSuppression = QtGui.QCheckBox(self.groupBox_14) - self.textDecimalZerosSuppression.setGeometry(QtCore.QRect(170, 20, 70, 17)) - self.textDecimalZerosSuppression.setObjectName(_fromUtf8("textDecimalZerosSuppression")) - self.tabWidget.addTab(self.PrimaryUnitTab, _fromUtf8("")) - self.layoutWidget = QtGui.QWidget(DimStyle_Details_Dialog) - self.layoutWidget.setGeometry(QtCore.QRect(320, 410, 239, 25)) - self.layoutWidget.setObjectName(_fromUtf8("layoutWidget")) - self.horizontalLayout = QtGui.QHBoxLayout(self.layoutWidget) - self.horizontalLayout.setMargin(0) - self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) - self.okButton = QtGui.QPushButton(self.layoutWidget) - self.okButton.setObjectName(_fromUtf8("okButton")) - self.horizontalLayout.addWidget(self.okButton) - self.cancelButton = QtGui.QPushButton(self.layoutWidget) - self.cancelButton.setObjectName(_fromUtf8("cancelButton")) - self.horizontalLayout.addWidget(self.cancelButton) - self.helpButton = QtGui.QPushButton(self.layoutWidget) - self.helpButton.setObjectName(_fromUtf8("helpButton")) - self.horizontalLayout.addWidget(self.helpButton) - - self.retranslateUi(DimStyle_Details_Dialog) - self.tabWidget.setCurrentIndex(1) - QtCore.QObject.connect(self.linearLayerName, QtCore.SIGNAL(_fromUtf8("currentIndexChanged(int)")), DimStyle_Details_Dialog.linearLayerNameChanged) - QtCore.QObject.connect(self.symbolLayerName, QtCore.SIGNAL(_fromUtf8("currentIndexChanged(int)")), DimStyle_Details_Dialog.symbolLayerNameChanged) - QtCore.QObject.connect(self.textualLayerName, QtCore.SIGNAL(_fromUtf8("currentIndexChanged(int)")), DimStyle_Details_Dialog.textualLayerNameChanged) - QtCore.QObject.connect(self.textRotModeFixedRot, QtCore.SIGNAL(_fromUtf8("toggled(bool)")), DimStyle_Details_Dialog.textRotModeFixedRotToggled) - QtCore.QObject.connect(self.extLineIsFixedLen, QtCore.SIGNAL(_fromUtf8("toggled(bool)")), DimStyle_Details_Dialog.extLineIsFixedLenToggled) - QtCore.QObject.connect(self.helpButton, QtCore.SIGNAL(_fromUtf8("clicked()")), DimStyle_Details_Dialog.ButtonHELP_Pressed) - QtCore.QObject.connect(self.okButton, QtCore.SIGNAL(_fromUtf8("clicked()")), DimStyle_Details_Dialog.accept) - QtCore.QObject.connect(self.cancelButton, QtCore.SIGNAL(_fromUtf8("clicked()")), DimStyle_Details_Dialog.reject) - QtCore.QObject.connect(self.tabWidget, QtCore.SIGNAL(_fromUtf8("currentChanged(int)")), DimStyle_Details_Dialog.currentTabChanged) - QtCore.QObject.connect(self.lineTypeFieldName, QtCore.SIGNAL(_fromUtf8("currentIndexChanged(int)")), DimStyle_Details_Dialog.linetypeFieldNameChanged) - QtCore.QObject.connect(self.symbolFieldName, QtCore.SIGNAL(_fromUtf8("currentIndexChanged(int)")), DimStyle_Details_Dialog.symbolFieldNameChanged) - QtCore.QObject.connect(self.scaleFieldName, QtCore.SIGNAL(_fromUtf8("currentIndexChanged(int)")), DimStyle_Details_Dialog.scaleFieldNameChanged) - QtCore.QObject.connect(self.idFieldName, QtCore.SIGNAL(_fromUtf8("currentIndexChanged(int)")), DimStyle_Details_Dialog.idFieldNameChanged) - QtCore.QObject.connect(self.dimStyleFieldName, QtCore.SIGNAL(_fromUtf8("currentIndexChanged(int)")), DimStyle_Details_Dialog.dimStyleFieldNameChanged) - QtCore.QObject.connect(self.dimTypeFieldName, QtCore.SIGNAL(_fromUtf8("currentIndexChanged(int)")), DimStyle_Details_Dialog.dimTypeFieldNameChanged) - QtCore.QObject.connect(self.colorFieldName, QtCore.SIGNAL(_fromUtf8("currentIndexChanged(int)")), DimStyle_Details_Dialog.colorFieldNameChanged) - QtCore.QObject.connect(self.rotFieldName, QtCore.SIGNAL(_fromUtf8("currentIndexChanged(int)")), DimStyle_Details_Dialog.rotFieldNameChanged) - QtCore.QObject.connect(self.componentFieldName, QtCore.SIGNAL(_fromUtf8("currentIndexChanged(int)")), DimStyle_Details_Dialog.componentFieldNameChanged) - QtCore.QObject.connect(self.idParentFieldName, QtCore.SIGNAL(_fromUtf8("currentIndexChanged(int)")), DimStyle_Details_Dialog.idParentFieldNameChanged) - QtCore.QObject.connect(self.dimLineLineType, QtCore.SIGNAL(_fromUtf8("textChanged(QString)")), DimStyle_Details_Dialog.dimLineLineTypeChanged) - QtCore.QObject.connect(self.dimLine1Hide, QtCore.SIGNAL(_fromUtf8("toggled(bool)")), DimStyle_Details_Dialog.dimLine1HideToggled) - QtCore.QObject.connect(self.dimLine2Hide, QtCore.SIGNAL(_fromUtf8("toggled(bool)")), DimStyle_Details_Dialog.dimLine2HideToggled) - QtCore.QObject.connect(self.extLine1LineType, QtCore.SIGNAL(_fromUtf8("textChanged(QString)")), DimStyle_Details_Dialog.extLine1LineTypeChanged) - QtCore.QObject.connect(self.extLine2LineType, QtCore.SIGNAL(_fromUtf8("textChanged(QString)")), DimStyle_Details_Dialog.extLine2LineTypeChanged) - QtCore.QObject.connect(self.extLine1Hide, QtCore.SIGNAL(_fromUtf8("toggled(bool)")), DimStyle_Details_Dialog.extLine1HideToggled) - QtCore.QObject.connect(self.extLine2Hide, QtCore.SIGNAL(_fromUtf8("toggled(bool)")), DimStyle_Details_Dialog.extLine2HideToggled) - QtCore.QObject.connect(self.extLineOffsetDimLine, QtCore.SIGNAL(_fromUtf8("valueChanged(double)")), DimStyle_Details_Dialog.extLineOffsetDimLineChanged) - QtCore.QObject.connect(self.extLineOffsetOrigPoints, QtCore.SIGNAL(_fromUtf8("valueChanged(double)")), DimStyle_Details_Dialog.extLineOffsetOrigPointsChanged) - QtCore.QObject.connect(self.extLineFixedLen, QtCore.SIGNAL(_fromUtf8("valueChanged(double)")), DimStyle_Details_Dialog.extLineFixedLenChanged) - QtCore.QObject.connect(self.block1Name, QtCore.SIGNAL(_fromUtf8("textChanged(QString)")), DimStyle_Details_Dialog.block1NameChanged) - QtCore.QObject.connect(self.block2Name, QtCore.SIGNAL(_fromUtf8("textChanged(QString)")), DimStyle_Details_Dialog.block2NameChanged) - QtCore.QObject.connect(self.blockLeaderName, QtCore.SIGNAL(_fromUtf8("textChanged(QString)")), DimStyle_Details_Dialog.blockLeaderNameChanged) - QtCore.QObject.connect(self.blockWidth, QtCore.SIGNAL(_fromUtf8("valueChanged(double)")), DimStyle_Details_Dialog.blockWidthChanged) - QtCore.QObject.connect(self.blockScale, QtCore.SIGNAL(_fromUtf8("valueChanged(double)")), DimStyle_Details_Dialog.blockScaleChanged) - QtCore.QObject.connect(self.textFont, QtCore.SIGNAL(_fromUtf8("currentIndexChanged(int)")), DimStyle_Details_Dialog.textFontChanged) - QtCore.QObject.connect(self.textHeight, QtCore.SIGNAL(_fromUtf8("valueChanged(double)")), DimStyle_Details_Dialog.textHeightChanged) - QtCore.QObject.connect(self.textVerticalPos, QtCore.SIGNAL(_fromUtf8("currentIndexChanged(int)")), DimStyle_Details_Dialog.textVerticalPosChanged) - QtCore.QObject.connect(self.textHorizontalPos, QtCore.SIGNAL(_fromUtf8("currentIndexChanged(int)")), DimStyle_Details_Dialog.textHorizontalPosChanged) - QtCore.QObject.connect(self.textDirection, QtCore.SIGNAL(_fromUtf8("currentIndexChanged(int)")), DimStyle_Details_Dialog.textDirectionChanged) - QtCore.QObject.connect(self.textOffsetDist, QtCore.SIGNAL(_fromUtf8("valueChanged(double)")), DimStyle_Details_Dialog.textOffsetDistChanged) - QtCore.QObject.connect(self.textForcedRot, QtCore.SIGNAL(_fromUtf8("valueChanged(double)")), DimStyle_Details_Dialog.textForcedRotChanged) - QtCore.QObject.connect(self.blockSuppressionForNoSpace, QtCore.SIGNAL(_fromUtf8("toggled(bool)")), DimStyle_Details_Dialog.blockSuppressionForNoSpaceToggled) - QtCore.QObject.connect(self.textDecimals, QtCore.SIGNAL(_fromUtf8("currentIndexChanged(int)")), DimStyle_Details_Dialog.textDecimalsChanged) - QtCore.QObject.connect(self.textDecimalSep, QtCore.SIGNAL(_fromUtf8("currentIndexChanged(int)")), DimStyle_Details_Dialog.textDecimalSepChanged) - QtCore.QObject.connect(self.textPrefix, QtCore.SIGNAL(_fromUtf8("textChanged(QString)")), DimStyle_Details_Dialog.textPrefixChanged) - QtCore.QObject.connect(self.textSuffix, QtCore.SIGNAL(_fromUtf8("textChanged(QString)")), DimStyle_Details_Dialog.textSuffixChanged) - QtCore.QObject.connect(self.textSuppressLeadingZeros, QtCore.SIGNAL(_fromUtf8("toggled(bool)")), DimStyle_Details_Dialog.textSuppressLeadingZerosToggled) - QtCore.QObject.connect(self.textDecimalZerosSuppression, QtCore.SIGNAL(_fromUtf8("toggled(bool)")), DimStyle_Details_Dialog.textDecimalZerosSuppressionToggled) - QtCore.QObject.connect(self.textRotModeHorizontal, QtCore.SIGNAL(_fromUtf8("toggled(bool)")), DimStyle_Details_Dialog.textRotModeHorizontalToggled) - QtCore.QObject.connect(self.textRotModeAligned, QtCore.SIGNAL(_fromUtf8("toggled(bool)")), DimStyle_Details_Dialog.textRotModeAlignedToggled) - QtCore.QObject.connect(self.textRotModeISO, QtCore.SIGNAL(_fromUtf8("toggled(bool)")), DimStyle_Details_Dialog.textRotModeISOToggled) - QtCore.QObject.connect(self.textBlockAdjustWhicheverFitsBestOutside, QtCore.SIGNAL(_fromUtf8("toggled(bool)")), DimStyle_Details_Dialog.textBlockAdjustWhicheverFitsBestOutsideToggled) - QtCore.QObject.connect(self.textBlockAdjustFirstSymbolOutside, QtCore.SIGNAL(_fromUtf8("toggled(bool)")), DimStyle_Details_Dialog.textBlockAdjustFirstSymbolOutsideToggled) - QtCore.QObject.connect(self.textBlockAdjustFirstTextOutside, QtCore.SIGNAL(_fromUtf8("toggled(bool)")), DimStyle_Details_Dialog.textBlockAdjustFirstTextOutsideToggled) - QtCore.QObject.connect(self.textBlockAdjustBothOutside, QtCore.SIGNAL(_fromUtf8("toggled(bool)")), DimStyle_Details_Dialog.textBlockAdjustBothOutsideToggled) - QtCore.QMetaObject.connectSlotsByName(DimStyle_Details_Dialog) - - def retranslateUi(self, DimStyle_Details_Dialog): - DimStyle_Details_Dialog.setWindowTitle(_translate("DimStyle_Details_Dialog", "QAD - Dimension style details", None)) - self.groupBox_5.setTitle(_translate("DimStyle_Details_Dialog", "Lines", None)) - self.label_2.setText(_translate("DimStyle_Details_Dialog", "Layer:", None)) - self.linearLayerName.setToolTip(_translate("DimStyle_Details_Dialog", "Name of the layer for dimension lines.", None)) - self.lineTypeFieldName.setToolTip(_translate("DimStyle_Details_Dialog", "Field storing the linetype name.", None)) - self.label_21.setText(_translate("DimStyle_Details_Dialog", "Linetype field:", None)) - self.groupBox_6.setTitle(_translate("DimStyle_Details_Dialog", "Symbols and arrows", None)) - self.symbolLayerName.setToolTip(_translate("DimStyle_Details_Dialog", "Name of the layer for dimension symbols and arrows.", None)) - self.label_13.setText(_translate("DimStyle_Details_Dialog", "Layer:", None)) - self.label_22.setText(_translate("DimStyle_Details_Dialog", "Symbol field:", None)) - self.symbolFieldName.setToolTip(_translate("DimStyle_Details_Dialog", "Field storing the symbol name.", None)) - self.scaleFieldName.setToolTip(_translate("DimStyle_Details_Dialog", "Field storing the symbol size.", None)) - self.label_23.setText(_translate("DimStyle_Details_Dialog", "Scale field:", None)) - self.groupBox_7.setTitle(_translate("DimStyle_Details_Dialog", "Generics fields", None)) - self.label_24.setText(_translate("DimStyle_Details_Dialog", "Rotation:", None)) - self.rotFieldName.setToolTip(_translate("DimStyle_Details_Dialog", "Field storing the rotation of the punctual elements od dimension (symbols, arrows, text).", None)) - self.label_16.setText(_translate("DimStyle_Details_Dialog", "Color:", None)) - self.colorFieldName.setToolTip(_translate("DimStyle_Details_Dialog", "Field storing the RGB colorfor all elements of dimension (e.g. \"255,255,255,255\" = white with total opacity).", None)) - self.label_28.setText(_translate("DimStyle_Details_Dialog", "Linking ID:", None)) - self.idParentFieldName.setToolTip(_translate("DimStyle_Details_Dialog", "Field storing the ID of the dimension to group all elements (except the text which is the root element).", None)) - self.label_25.setText(_translate("DimStyle_Details_Dialog", "Component type:", None)) - self.componentFieldName.setToolTip(_translate("DimStyle_Details_Dialog", "

      Field storing the component type of dimension:

      "D1" = Dimension line 1.

      "D2" = Dimension line 2.

      "E1" = Extension line 1.

      "E2" = Extension line 2.

      "L" = Leader.

      "B1" = Block 1.

      "B2" = Block 2.

      "LB" = Leader Block.

      "AB" = Arc Block.

      "D1" = Dimension point 1.

      "D2" = Dimension point 2.

      ", None)) - self.groupBox_8.setTitle(_translate("DimStyle_Details_Dialog", "Text", None)) - self.label_27.setText(_translate("DimStyle_Details_Dialog", "ID Field:", None)) - self.idFieldName.setToolTip(_translate("DimStyle_Details_Dialog", "Field storing the unique code for each dimension.", None)) - self.dimStyleFieldName.setToolTip(_translate("DimStyle_Details_Dialog", "Field storing the name of the dimension style.", None)) - self.label_29.setText(_translate("DimStyle_Details_Dialog", "Dim. style field:", None)) - self.label_30.setText(_translate("DimStyle_Details_Dialog", "Dim. type field:", None)) - self.dimTypeFieldName.setToolTip(_translate("DimStyle_Details_Dialog", "

      Field storing the dimension type:

      "AL" = linear dimension that is aligned with the origin points of the extension lines.

      "AN" = angular dimension, it measures the angle between selected objects or 3 points.

      "BL" = linear, angular, or ordinate dimension from the baseline of the previous or selected dimension.

      "CE" = creates the center mark or the centerlines of circles and arcs.

      "DI" = creates a diameter dimension for a circle or an arc.

      "LD" = creates a line that connects annotation to a feature..

      "LI" = linear dimension with a horizontal, vertical, or rotated dimension line.

      "RA" = radial dimension, measures the radius of a selected circle or arc and displays the dimension text with a radius symbol in front of it.

      "AR" = arc length dimensions measure the distance along an arc or polyline arc segment.

      ", None)) - self.label_14.setText(_translate("DimStyle_Details_Dialog", "Layer:", None)) - self.textualLayerName.setToolTip(_translate("DimStyle_Details_Dialog", "Name of the layer storing dimension texts.", None)) - self.tabWidget.setTabText(self.tabWidget.indexOf(self.DBTab), _translate("DimStyle_Details_Dialog", "DB", None)) - self.groupBox.setTitle(_translate("DimStyle_Details_Dialog", "Dimension lines", None)) - self.label.setText(_translate("DimStyle_Details_Dialog", "Color:", None)) - self.dimLineLineType.setToolTip(_translate("DimStyle_Details_Dialog", "Linetype of dimension line.", None)) - self.label_3.setText(_translate("DimStyle_Details_Dialog", "Linetype:", None)) - self.label_5.setText(_translate("DimStyle_Details_Dialog", "Suppress:", None)) - self.dimLine1Hide.setToolTip(_translate("DimStyle_Details_Dialog", "Suppresses display of dimension line 1.", None)) - self.dimLine1Hide.setText(_translate("DimStyle_Details_Dialog", "Dim. line 1", None)) - self.dimLine2Hide.setToolTip(_translate("DimStyle_Details_Dialog", "Suppresses display of dimension line 2.", None)) - self.dimLine2Hide.setText(_translate("DimStyle_Details_Dialog", "Dim. line 2", None)) - self.groupBox_2.setTitle(_translate("DimStyle_Details_Dialog", "Extension lines", None)) - self.label_6.setText(_translate("DimStyle_Details_Dialog", "Color:", None)) - self.extLine1LineType.setToolTip(_translate("DimStyle_Details_Dialog", "Linetype for extension line 1.", None)) - self.label_7.setText(_translate("DimStyle_Details_Dialog", "Linetype ext. 1:", None)) - self.label_8.setText(_translate("DimStyle_Details_Dialog", "Linetype ext. 2:", None)) - self.extLine2LineType.setToolTip(_translate("DimStyle_Details_Dialog", "Linetype for extension line 2.", None)) - self.label_9.setText(_translate("DimStyle_Details_Dialog", "Suppress:", None)) - self.extLine1Hide.setToolTip(_translate("DimStyle_Details_Dialog", "Suppresses display of extension line 1.", None)) - self.extLine1Hide.setText(_translate("DimStyle_Details_Dialog", "Ext. line 1", None)) - self.extLine2Hide.setToolTip(_translate("DimStyle_Details_Dialog", "Suppresses display of extension line 2.", None)) - self.extLine2Hide.setText(_translate("DimStyle_Details_Dialog", "Ext. line 2", None)) - self.label_10.setText(_translate("DimStyle_Details_Dialog", "Extend beyond dim lines:", None)) - self.extLineOffsetDimLine.setToolTip(_translate("DimStyle_Details_Dialog", "Specifies a distance to extend the extension lines above the dimension line.", None)) - self.extLineOffsetOrigPoints.setToolTip(_translate("DimStyle_Details_Dialog", "Sets the distance to offset the extension lines from the points on the drawing that define the dimension.", None)) - self.label_11.setText(_translate("DimStyle_Details_Dialog", "Offset from origin:", None)) - self.extLineIsFixedLen.setToolTip(_translate("DimStyle_Details_Dialog", "Enables fixed length extension lines.", None)) - self.extLineIsFixedLen.setText(_translate("DimStyle_Details_Dialog", "Fixed length extension lines", None)) - self.label_12.setText(_translate("DimStyle_Details_Dialog", "Length:", None)) - self.extLineFixedLen.setToolTip(_translate("DimStyle_Details_Dialog", "Total length of the extension lines starting from the dimension line toward the dimension origin.", None)) - self.tabWidget.setTabText(self.tabWidget.indexOf(self.LineTab), _translate("DimStyle_Details_Dialog", "Lines", None)) - self.groupBox_3.setTitle(_translate("DimStyle_Details_Dialog", "Arrowheads", None)) - self.block1Name.setToolTip(_translate("DimStyle_Details_Dialog", "Arrowhead for the first dimension line. When you change the first arrowhead type, the second arrowhead automatically changes to match it.", None)) - self.label_15.setText(_translate("DimStyle_Details_Dialog", "Arrowhead 1:", None)) - self.block2Name.setToolTip(_translate("DimStyle_Details_Dialog", "Arrowhead for the second dimension line.", None)) - self.label_18.setText(_translate("DimStyle_Details_Dialog", "Arrowhead 2:", None)) - self.blockLeaderName.setToolTip(_translate("DimStyle_Details_Dialog", "Arrowhead for the leader line.", None)) - self.label_19.setText(_translate("DimStyle_Details_Dialog", "Leader:", None)) - self.blockWidth.setToolTip(_translate("DimStyle_Details_Dialog", "Arrowhead horizontal size in map units using the symbol scale factor = 1.", None)) - self.label_17.setText(_translate("DimStyle_Details_Dialog", "Arrowhead size:", None)) - self.blockScale.setToolTip(_translate("DimStyle_Details_Dialog", "Arrowhead scale.", None)) - self.label_20.setText(_translate("DimStyle_Details_Dialog", "Arrowhead scale:", None)) - self.tabWidget.setTabText(self.tabWidget.indexOf(self.SymbolTab), _translate("DimStyle_Details_Dialog", "Symbols and arrows", None)) - self.groupBox_9.setTitle(_translate("DimStyle_Details_Dialog", "Text appearance", None)) - self.label_26.setText(_translate("DimStyle_Details_Dialog", "Text height:", None)) - self.textHeight.setToolTip(_translate("DimStyle_Details_Dialog", "Text height in map units.", None)) - self.label_40.setText(_translate("DimStyle_Details_Dialog", "Character type:", None)) - self.textFont.setToolTip(_translate("DimStyle_Details_Dialog", "Dimension text character type.", None)) - self.label_4.setText(_translate("DimStyle_Details_Dialog", "Color:", None)) - self.groupBox_10.setTitle(_translate("DimStyle_Details_Dialog", "Text placement", None)) - self.textVerticalPos.setToolTip(_translate("DimStyle_Details_Dialog", "Controls the vertical placement of dimension text in relation to the dimension line.", None)) - self.label_31.setText(_translate("DimStyle_Details_Dialog", "Vertical:", None)) - self.label_32.setText(_translate("DimStyle_Details_Dialog", "Horizontal:", None)) - self.textHorizontalPos.setToolTip(_translate("DimStyle_Details_Dialog", "Controls the horizontal placement of dimension text along the dimension line, in relation to the extension lines.", None)) - self.label_33.setText(_translate("DimStyle_Details_Dialog", "View direction:", None)) - self.textDirection.setToolTip(_translate("DimStyle_Details_Dialog", "Controls the dimension text viewing direction.", None)) - self.textOffsetDist.setToolTip(_translate("DimStyle_Details_Dialog", "Sets the current text gap, which is the distance around the dimension text when the dimension line is broken to accommodate the dimension text.", None)) - self.label_34.setText(_translate("DimStyle_Details_Dialog", "Offset from dim line:", None)) - self.textAlignment_groupBox.setTitle(_translate("DimStyle_Details_Dialog", "Text alignment", None)) - self.textRotModeHorizontal.setToolTip(_translate("DimStyle_Details_Dialog", "Places text in a horizontal position.", None)) - self.textRotModeHorizontal.setText(_translate("DimStyle_Details_Dialog", "Horizontal", None)) - self.textRotModeAligned.setToolTip(_translate("DimStyle_Details_Dialog", "Text aligned with Dimension Line.", None)) - self.textRotModeAligned.setText(_translate("DimStyle_Details_Dialog", "Aligned with dimension line", None)) - self.textRotModeISO.setToolTip(_translate("DimStyle_Details_Dialog", "Aligns text with the dimension line when text is inside the extension lines, but aligns it horizontally when text is outside the extension lines.", None)) - self.textRotModeISO.setText(_translate("DimStyle_Details_Dialog", "ISO Standard", None)) - self.textRotModeFixedRot.setToolTip(_translate("DimStyle_Details_Dialog", "Place text with fixed angle.", None)) - self.textRotModeFixedRot.setText(_translate("DimStyle_Details_Dialog", "Fixed rotation", None)) - self.textForcedRot.setToolTip(_translate("DimStyle_Details_Dialog", "Text angle when fixed rotation mode is on.", None)) - self.tabWidget.setTabText(self.tabWidget.indexOf(self.TextTab), _translate("DimStyle_Details_Dialog", "Text", None)) - self.groupBox_12.setTitle(_translate("DimStyle_Details_Dialog", "Fit options", None)) - self.label_35.setText(_translate("DimStyle_Details_Dialog", "If there isn\'t enaugh room to place both text and arrows inside extension lines, the first thing to move outside the extension lines is:", None)) - self.textBlockAdjustWhicheverFitsBestOutside.setToolTip(_translate("DimStyle_Details_Dialog", "Moves either the text or the arrowheads outside the extension lines based on the best fit.", None)) - self.textBlockAdjustWhicheverFitsBestOutside.setText(_translate("DimStyle_Details_Dialog", "Either text or arrows (best fit)", None)) - self.textBlockAdjustFirstSymbolOutside.setToolTip(_translate("DimStyle_Details_Dialog", "Moves arrowheads outside the extension lines first, then text.", None)) - self.textBlockAdjustFirstSymbolOutside.setText(_translate("DimStyle_Details_Dialog", "Arrows", None)) - self.textBlockAdjustFirstTextOutside.setToolTip(_translate("DimStyle_Details_Dialog", "Moves text outside the extension lines first, then arrowheads.", None)) - self.textBlockAdjustFirstTextOutside.setText(_translate("DimStyle_Details_Dialog", "Text", None)) - self.textBlockAdjustBothOutside.setToolTip(_translate("DimStyle_Details_Dialog", "When not enough space is available for text and arrowheads, moves both outside the extension lines.", None)) - self.textBlockAdjustBothOutside.setText(_translate("DimStyle_Details_Dialog", "Both text and arrows", None)) - self.blockSuppressionForNoSpace.setToolTip(_translate("DimStyle_Details_Dialog", "Suppresses arrowheads if not enough space is available inside the extension lines.", None)) - self.blockSuppressionForNoSpace.setText(_translate("DimStyle_Details_Dialog", "Suppress arrows if they don\'t fit inside ext. lines", None)) - self.tabWidget.setTabText(self.tabWidget.indexOf(self.AdjustTab), _translate("DimStyle_Details_Dialog", "Fit", None)) - self.groupBox_13.setTitle(_translate("DimStyle_Details_Dialog", "Linear dimensions", None)) - self.label_36.setText(_translate("DimStyle_Details_Dialog", "Precision:", None)) - self.textDecimals.setToolTip(_translate("DimStyle_Details_Dialog", "Displays and sets the number of decimal places in the dimension text.", None)) - self.textDecimalSep.setToolTip(_translate("DimStyle_Details_Dialog", "Sets the separator for decimal formats.", None)) - self.label_37.setText(_translate("DimStyle_Details_Dialog", "Decimal separator:", None)) - self.textPrefix.setToolTip(_translate("DimStyle_Details_Dialog", "Includes a prefix that you specify in the dimension text.", None)) - self.label_38.setText(_translate("DimStyle_Details_Dialog", "Prefix:", None)) - self.label_39.setText(_translate("DimStyle_Details_Dialog", "Suffix:", None)) - self.textSuffix.setToolTip(_translate("DimStyle_Details_Dialog", "Includes a suffix that you specify in the dimension text.", None)) - self.groupBox_14.setTitle(_translate("DimStyle_Details_Dialog", "Zero suppression", None)) - self.textSuppressLeadingZeros.setToolTip(_translate("DimStyle_Details_Dialog", "Suppresses leading zeros in all decimal dimensions (0.5 becomes .5).", None)) - self.textSuppressLeadingZeros.setText(_translate("DimStyle_Details_Dialog", "Leading", None)) - self.textDecimalZerosSuppression.setToolTip(_translate("DimStyle_Details_Dialog", "Suppresses trailing zeros in all decimal dimensions (5.50 becomes 5.5 and 5.0 becomes 5).", None)) - self.textDecimalZerosSuppression.setText(_translate("DimStyle_Details_Dialog", "Trailing", None)) - self.tabWidget.setTabText(self.tabWidget.indexOf(self.PrimaryUnitTab), _translate("DimStyle_Details_Dialog", "Primary units", None)) - self.okButton.setText(_translate("DimStyle_Details_Dialog", "OK", None)) - self.cancelButton.setText(_translate("DimStyle_Details_Dialog", "Cancel", None)) - self.helpButton.setText(_translate("DimStyle_Details_Dialog", "?", None)) - +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file '.\qad_dimstyle_details.ui' +# +# Created by: PyQt5 UI code generator 5.15.4 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_DimStyle_Details_Dialog(object): + def setupUi(self, DimStyle_Details_Dialog): + DimStyle_Details_Dialog.setObjectName("DimStyle_Details_Dialog") + DimStyle_Details_Dialog.resize(637, 446) + DimStyle_Details_Dialog.setMinimumSize(QtCore.QSize(600, 446)) + DimStyle_Details_Dialog.setMaximumSize(QtCore.QSize(700, 446)) + self.tabWidget = QtWidgets.QTabWidget(DimStyle_Details_Dialog) + self.tabWidget.setGeometry(QtCore.QRect(10, 10, 621, 391)) + self.tabWidget.setObjectName("tabWidget") + self.DBTab = QtWidgets.QWidget() + self.DBTab.setObjectName("DBTab") + self.groupBox_5 = QtWidgets.QGroupBox(self.DBTab) + self.groupBox_5.setGeometry(QtCore.QRect(10, 10, 291, 81)) + self.groupBox_5.setObjectName("groupBox_5") + self.label_2 = QtWidgets.QLabel(self.groupBox_5) + self.label_2.setGeometry(QtCore.QRect(12, 20, 81, 21)) + self.label_2.setObjectName("label_2") + self.linearLayerName = QtWidgets.QComboBox(self.groupBox_5) + self.linearLayerName.setGeometry(QtCore.QRect(100, 20, 181, 22)) + self.linearLayerName.setObjectName("linearLayerName") + self.lineTypeFieldName = QtWidgets.QComboBox(self.groupBox_5) + self.lineTypeFieldName.setGeometry(QtCore.QRect(140, 50, 141, 22)) + self.lineTypeFieldName.setObjectName("lineTypeFieldName") + self.label_21 = QtWidgets.QLabel(self.groupBox_5) + self.label_21.setGeometry(QtCore.QRect(10, 50, 121, 21)) + self.label_21.setObjectName("label_21") + self.groupBox_6 = QtWidgets.QGroupBox(self.DBTab) + self.groupBox_6.setGeometry(QtCore.QRect(9, 100, 291, 111)) + self.groupBox_6.setObjectName("groupBox_6") + self.symbolLayerName = QtWidgets.QComboBox(self.groupBox_6) + self.symbolLayerName.setGeometry(QtCore.QRect(100, 20, 181, 22)) + self.symbolLayerName.setObjectName("symbolLayerName") + self.label_13 = QtWidgets.QLabel(self.groupBox_6) + self.label_13.setGeometry(QtCore.QRect(10, 20, 81, 21)) + self.label_13.setObjectName("label_13") + self.label_22 = QtWidgets.QLabel(self.groupBox_6) + self.label_22.setGeometry(QtCore.QRect(10, 50, 121, 21)) + self.label_22.setObjectName("label_22") + self.symbolFieldName = QtWidgets.QComboBox(self.groupBox_6) + self.symbolFieldName.setGeometry(QtCore.QRect(140, 50, 141, 22)) + self.symbolFieldName.setObjectName("symbolFieldName") + self.scaleFieldName = QtWidgets.QComboBox(self.groupBox_6) + self.scaleFieldName.setGeometry(QtCore.QRect(140, 80, 141, 22)) + self.scaleFieldName.setObjectName("scaleFieldName") + self.label_23 = QtWidgets.QLabel(self.groupBox_6) + self.label_23.setGeometry(QtCore.QRect(10, 80, 121, 21)) + self.label_23.setObjectName("label_23") + self.groupBox_7 = QtWidgets.QGroupBox(self.DBTab) + self.groupBox_7.setGeometry(QtCore.QRect(330, 220, 271, 141)) + self.groupBox_7.setObjectName("groupBox_7") + self.label_24 = QtWidgets.QLabel(self.groupBox_7) + self.label_24.setGeometry(QtCore.QRect(10, 50, 101, 21)) + self.label_24.setObjectName("label_24") + self.rotFieldName = QtWidgets.QComboBox(self.groupBox_7) + self.rotFieldName.setGeometry(QtCore.QRect(120, 50, 141, 22)) + self.rotFieldName.setObjectName("rotFieldName") + self.label_16 = QtWidgets.QLabel(self.groupBox_7) + self.label_16.setGeometry(QtCore.QRect(10, 20, 101, 21)) + self.label_16.setObjectName("label_16") + self.colorFieldName = QtWidgets.QComboBox(self.groupBox_7) + self.colorFieldName.setGeometry(QtCore.QRect(120, 20, 141, 22)) + self.colorFieldName.setObjectName("colorFieldName") + self.label_28 = QtWidgets.QLabel(self.groupBox_7) + self.label_28.setGeometry(QtCore.QRect(10, 110, 101, 21)) + self.label_28.setObjectName("label_28") + self.idParentFieldName = QtWidgets.QComboBox(self.groupBox_7) + self.idParentFieldName.setGeometry(QtCore.QRect(120, 110, 141, 22)) + self.idParentFieldName.setObjectName("idParentFieldName") + self.label_25 = QtWidgets.QLabel(self.groupBox_7) + self.label_25.setGeometry(QtCore.QRect(10, 80, 101, 21)) + self.label_25.setObjectName("label_25") + self.componentFieldName = QtWidgets.QComboBox(self.groupBox_7) + self.componentFieldName.setGeometry(QtCore.QRect(120, 80, 141, 22)) + self.componentFieldName.setObjectName("componentFieldName") + self.groupBox_8 = QtWidgets.QGroupBox(self.DBTab) + self.groupBox_8.setGeometry(QtCore.QRect(10, 220, 291, 141)) + self.groupBox_8.setObjectName("groupBox_8") + self.label_27 = QtWidgets.QLabel(self.groupBox_8) + self.label_27.setGeometry(QtCore.QRect(10, 50, 121, 21)) + self.label_27.setObjectName("label_27") + self.idFieldName = QtWidgets.QComboBox(self.groupBox_8) + self.idFieldName.setGeometry(QtCore.QRect(140, 50, 141, 22)) + self.idFieldName.setObjectName("idFieldName") + self.dimStyleFieldName = QtWidgets.QComboBox(self.groupBox_8) + self.dimStyleFieldName.setGeometry(QtCore.QRect(140, 80, 141, 22)) + self.dimStyleFieldName.setObjectName("dimStyleFieldName") + self.label_29 = QtWidgets.QLabel(self.groupBox_8) + self.label_29.setGeometry(QtCore.QRect(10, 80, 121, 21)) + self.label_29.setObjectName("label_29") + self.label_30 = QtWidgets.QLabel(self.groupBox_8) + self.label_30.setGeometry(QtCore.QRect(10, 110, 121, 21)) + self.label_30.setObjectName("label_30") + self.dimTypeFieldName = QtWidgets.QComboBox(self.groupBox_8) + self.dimTypeFieldName.setGeometry(QtCore.QRect(140, 110, 141, 22)) + self.dimTypeFieldName.setObjectName("dimTypeFieldName") + self.label_14 = QtWidgets.QLabel(self.groupBox_8) + self.label_14.setGeometry(QtCore.QRect(10, 20, 81, 21)) + self.label_14.setObjectName("label_14") + self.textualLayerName = QtWidgets.QComboBox(self.groupBox_8) + self.textualLayerName.setGeometry(QtCore.QRect(100, 20, 181, 22)) + self.textualLayerName.setObjectName("textualLayerName") + self.previewDummy = QtWidgets.QPushButton(self.DBTab) + self.previewDummy.setGeometry(QtCore.QRect(340, 20, 251, 181)) + self.previewDummy.setText("") + self.previewDummy.setObjectName("previewDummy") + self.tabWidget.addTab(self.DBTab, "") + self.LineTab = QtWidgets.QWidget() + self.LineTab.setObjectName("LineTab") + self.groupBox = QtWidgets.QGroupBox(self.LineTab) + self.groupBox.setGeometry(QtCore.QRect(10, 10, 271, 141)) + self.groupBox.setObjectName("groupBox") + self.label = QtWidgets.QLabel(self.groupBox) + self.label.setGeometry(QtCore.QRect(10, 20, 71, 21)) + self.label.setObjectName("label") + self.dimLineLineType = QtWidgets.QLineEdit(self.groupBox) + self.dimLineLineType.setGeometry(QtCore.QRect(90, 50, 171, 20)) + self.dimLineLineType.setObjectName("dimLineLineType") + self.label_3 = QtWidgets.QLabel(self.groupBox) + self.label_3.setGeometry(QtCore.QRect(10, 50, 71, 21)) + self.label_3.setObjectName("label_3") + self.label_5 = QtWidgets.QLabel(self.groupBox) + self.label_5.setGeometry(QtCore.QRect(10, 110, 61, 21)) + self.label_5.setObjectName("label_5") + self.dimLine1Hide = QtWidgets.QCheckBox(self.groupBox) + self.dimLine1Hide.setGeometry(QtCore.QRect(70, 110, 91, 21)) + self.dimLine1Hide.setObjectName("dimLine1Hide") + self.dimLine2Hide = QtWidgets.QCheckBox(self.groupBox) + self.dimLine2Hide.setGeometry(QtCore.QRect(170, 110, 91, 21)) + self.dimLine2Hide.setObjectName("dimLine2Hide") + self.dimLineColorDummy = QtWidgets.QPushButton(self.groupBox) + self.dimLineColorDummy.setGeometry(QtCore.QRect(90, 20, 171, 23)) + self.dimLineColorDummy.setText("") + self.dimLineColorDummy.setObjectName("dimLineColorDummy") + self.label_41 = QtWidgets.QLabel(self.groupBox) + self.label_41.setGeometry(QtCore.QRect(10, 80, 161, 21)) + self.label_41.setObjectName("label_41") + self.dimLineOffsetExtLine = QtWidgets.QDoubleSpinBox(self.groupBox) + self.dimLineOffsetExtLine.setGeometry(QtCore.QRect(180, 80, 81, 22)) + self.dimLineOffsetExtLine.setDecimals(6) + self.dimLineOffsetExtLine.setMaximum(1000000.0) + self.dimLineOffsetExtLine.setSingleStep(0.0005) + self.dimLineOffsetExtLine.setObjectName("dimLineOffsetExtLine") + self.groupBox_2 = QtWidgets.QGroupBox(self.LineTab) + self.groupBox_2.setGeometry(QtCore.QRect(10, 210, 591, 141)) + self.groupBox_2.setObjectName("groupBox_2") + self.label_6 = QtWidgets.QLabel(self.groupBox_2) + self.label_6.setGeometry(QtCore.QRect(11, 20, 81, 21)) + self.label_6.setObjectName("label_6") + self.extLine1LineType = QtWidgets.QLineEdit(self.groupBox_2) + self.extLine1LineType.setGeometry(QtCore.QRect(130, 50, 161, 20)) + self.extLine1LineType.setObjectName("extLine1LineType") + self.label_7 = QtWidgets.QLabel(self.groupBox_2) + self.label_7.setGeometry(QtCore.QRect(10, 50, 111, 21)) + self.label_7.setObjectName("label_7") + self.label_8 = QtWidgets.QLabel(self.groupBox_2) + self.label_8.setGeometry(QtCore.QRect(10, 80, 111, 21)) + self.label_8.setObjectName("label_8") + self.extLine2LineType = QtWidgets.QLineEdit(self.groupBox_2) + self.extLine2LineType.setGeometry(QtCore.QRect(130, 80, 161, 20)) + self.extLine2LineType.setObjectName("extLine2LineType") + self.label_9 = QtWidgets.QLabel(self.groupBox_2) + self.label_9.setGeometry(QtCore.QRect(10, 110, 61, 21)) + self.label_9.setObjectName("label_9") + self.extLine1Hide = QtWidgets.QCheckBox(self.groupBox_2) + self.extLine1Hide.setGeometry(QtCore.QRect(80, 110, 101, 21)) + self.extLine1Hide.setObjectName("extLine1Hide") + self.extLine2Hide = QtWidgets.QCheckBox(self.groupBox_2) + self.extLine2Hide.setGeometry(QtCore.QRect(190, 110, 101, 21)) + self.extLine2Hide.setObjectName("extLine2Hide") + self.label_10 = QtWidgets.QLabel(self.groupBox_2) + self.label_10.setGeometry(QtCore.QRect(310, 20, 181, 21)) + self.label_10.setObjectName("label_10") + self.extLineOffsetDimLine = QtWidgets.QDoubleSpinBox(self.groupBox_2) + self.extLineOffsetDimLine.setGeometry(QtCore.QRect(500, 20, 81, 22)) + self.extLineOffsetDimLine.setDecimals(6) + self.extLineOffsetDimLine.setMaximum(1000000.0) + self.extLineOffsetDimLine.setSingleStep(0.0005) + self.extLineOffsetDimLine.setObjectName("extLineOffsetDimLine") + self.extLineOffsetOrigPoints = QtWidgets.QDoubleSpinBox(self.groupBox_2) + self.extLineOffsetOrigPoints.setGeometry(QtCore.QRect(500, 50, 81, 22)) + self.extLineOffsetOrigPoints.setDecimals(6) + self.extLineOffsetOrigPoints.setMaximum(1000000.0) + self.extLineOffsetOrigPoints.setSingleStep(0.0005) + self.extLineOffsetOrigPoints.setObjectName("extLineOffsetOrigPoints") + self.label_11 = QtWidgets.QLabel(self.groupBox_2) + self.label_11.setGeometry(QtCore.QRect(310, 50, 181, 21)) + self.label_11.setObjectName("label_11") + self.extLineIsFixedLen = QtWidgets.QCheckBox(self.groupBox_2) + self.extLineIsFixedLen.setGeometry(QtCore.QRect(310, 80, 271, 21)) + self.extLineIsFixedLen.setObjectName("extLineIsFixedLen") + self.label_12 = QtWidgets.QLabel(self.groupBox_2) + self.label_12.setGeometry(QtCore.QRect(310, 110, 181, 21)) + self.label_12.setObjectName("label_12") + self.extLineFixedLen = QtWidgets.QDoubleSpinBox(self.groupBox_2) + self.extLineFixedLen.setGeometry(QtCore.QRect(500, 110, 81, 22)) + self.extLineFixedLen.setDecimals(6) + self.extLineFixedLen.setMaximum(1000000.0) + self.extLineFixedLen.setSingleStep(0.0005) + self.extLineFixedLen.setObjectName("extLineFixedLen") + self.extLineColorDummy = QtWidgets.QPushButton(self.groupBox_2) + self.extLineColorDummy.setGeometry(QtCore.QRect(110, 20, 181, 23)) + self.extLineColorDummy.setText("") + self.extLineColorDummy.setObjectName("extLineColorDummy") + self.tabWidget.addTab(self.LineTab, "") + self.SymbolTab = QtWidgets.QWidget() + self.SymbolTab.setObjectName("SymbolTab") + self.groupBox_3 = QtWidgets.QGroupBox(self.SymbolTab) + self.groupBox_3.setGeometry(QtCore.QRect(10, 10, 271, 171)) + self.groupBox_3.setObjectName("groupBox_3") + self.block1Name = QtWidgets.QLineEdit(self.groupBox_3) + self.block1Name.setGeometry(QtCore.QRect(110, 20, 151, 20)) + self.block1Name.setObjectName("block1Name") + self.label_15 = QtWidgets.QLabel(self.groupBox_3) + self.label_15.setGeometry(QtCore.QRect(10, 20, 91, 21)) + self.label_15.setObjectName("label_15") + self.block2Name = QtWidgets.QLineEdit(self.groupBox_3) + self.block2Name.setGeometry(QtCore.QRect(110, 50, 151, 20)) + self.block2Name.setObjectName("block2Name") + self.label_18 = QtWidgets.QLabel(self.groupBox_3) + self.label_18.setGeometry(QtCore.QRect(10, 50, 91, 21)) + self.label_18.setObjectName("label_18") + self.blockLeaderName = QtWidgets.QLineEdit(self.groupBox_3) + self.blockLeaderName.setGeometry(QtCore.QRect(110, 80, 151, 20)) + self.blockLeaderName.setObjectName("blockLeaderName") + self.label_19 = QtWidgets.QLabel(self.groupBox_3) + self.label_19.setGeometry(QtCore.QRect(10, 80, 91, 21)) + self.label_19.setObjectName("label_19") + self.blockWidth = QtWidgets.QDoubleSpinBox(self.groupBox_3) + self.blockWidth.setGeometry(QtCore.QRect(180, 110, 81, 22)) + self.blockWidth.setDecimals(6) + self.blockWidth.setMaximum(1000000.0) + self.blockWidth.setSingleStep(0.0005) + self.blockWidth.setObjectName("blockWidth") + self.label_17 = QtWidgets.QLabel(self.groupBox_3) + self.label_17.setGeometry(QtCore.QRect(10, 110, 111, 21)) + self.label_17.setObjectName("label_17") + self.blockScale = QtWidgets.QDoubleSpinBox(self.groupBox_3) + self.blockScale.setGeometry(QtCore.QRect(180, 140, 81, 22)) + self.blockScale.setDecimals(6) + self.blockScale.setMaximum(1000000.0) + self.blockScale.setSingleStep(0.0005) + self.blockScale.setObjectName("blockScale") + self.label_20 = QtWidgets.QLabel(self.groupBox_3) + self.label_20.setGeometry(QtCore.QRect(10, 140, 111, 21)) + self.label_20.setObjectName("label_20") + self.groupBox_4 = QtWidgets.QGroupBox(self.SymbolTab) + self.groupBox_4.setGeometry(QtCore.QRect(350, 220, 231, 81)) + self.groupBox_4.setObjectName("groupBox_4") + self.arcSymbolPreceding = QtWidgets.QRadioButton(self.groupBox_4) + self.arcSymbolPreceding.setGeometry(QtCore.QRect(10, 20, 211, 17)) + self.arcSymbolPreceding.setObjectName("arcSymbolPreceding") + self.arcSymbolAbove = QtWidgets.QRadioButton(self.groupBox_4) + self.arcSymbolAbove.setGeometry(QtCore.QRect(10, 40, 211, 17)) + self.arcSymbolAbove.setObjectName("arcSymbolAbove") + self.arcSymbolNone = QtWidgets.QRadioButton(self.groupBox_4) + self.arcSymbolNone.setGeometry(QtCore.QRect(10, 60, 211, 17)) + self.arcSymbolNone.setObjectName("arcSymbolNone") + self.groupBox_11 = QtWidgets.QGroupBox(self.SymbolTab) + self.groupBox_11.setGeometry(QtCore.QRect(10, 190, 271, 81)) + self.groupBox_11.setObjectName("groupBox_11") + self.centerMarkNone = QtWidgets.QRadioButton(self.groupBox_11) + self.centerMarkNone.setGeometry(QtCore.QRect(10, 20, 82, 17)) + self.centerMarkNone.setObjectName("centerMarkNone") + self.centerMarkMark = QtWidgets.QRadioButton(self.groupBox_11) + self.centerMarkMark.setGeometry(QtCore.QRect(10, 40, 82, 17)) + self.centerMarkMark.setObjectName("centerMarkMark") + self.centerMarkLine = QtWidgets.QRadioButton(self.groupBox_11) + self.centerMarkLine.setGeometry(QtCore.QRect(10, 60, 82, 17)) + self.centerMarkLine.setObjectName("centerMarkLine") + self.centerMarkLength = QtWidgets.QDoubleSpinBox(self.groupBox_11) + self.centerMarkLength.setGeometry(QtCore.QRect(180, 30, 81, 22)) + self.centerMarkLength.setDecimals(6) + self.centerMarkLength.setMaximum(1000000.0) + self.centerMarkLength.setSingleStep(0.0005) + self.centerMarkLength.setObjectName("centerMarkLength") + self.tabWidget.addTab(self.SymbolTab, "") + self.TextTab = QtWidgets.QWidget() + self.TextTab.setObjectName("TextTab") + self.groupBox_9 = QtWidgets.QGroupBox(self.TextTab) + self.groupBox_9.setGeometry(QtCore.QRect(10, 10, 271, 111)) + self.groupBox_9.setObjectName("groupBox_9") + self.label_26 = QtWidgets.QLabel(self.groupBox_9) + self.label_26.setGeometry(QtCore.QRect(10, 80, 111, 21)) + self.label_26.setObjectName("label_26") + self.textHeight = QtWidgets.QDoubleSpinBox(self.groupBox_9) + self.textHeight.setGeometry(QtCore.QRect(180, 80, 81, 22)) + self.textHeight.setDecimals(6) + self.textHeight.setMaximum(1000000.0) + self.textHeight.setSingleStep(0.0005) + self.textHeight.setObjectName("textHeight") + self.label_40 = QtWidgets.QLabel(self.groupBox_9) + self.label_40.setGeometry(QtCore.QRect(10, 20, 91, 21)) + self.label_40.setObjectName("label_40") + self.textFont = QtWidgets.QFontComboBox(self.groupBox_9) + self.textFont.setGeometry(QtCore.QRect(100, 20, 161, 22)) + self.textFont.setObjectName("textFont") + self.textColorDummy = QtWidgets.QPushButton(self.groupBox_9) + self.textColorDummy.setGeometry(QtCore.QRect(80, 50, 181, 23)) + self.textColorDummy.setText("") + self.textColorDummy.setObjectName("textColorDummy") + self.label_4 = QtWidgets.QLabel(self.groupBox_9) + self.label_4.setGeometry(QtCore.QRect(10, 50, 51, 21)) + self.label_4.setObjectName("label_4") + self.groupBox_10 = QtWidgets.QGroupBox(self.TextTab) + self.groupBox_10.setGeometry(QtCore.QRect(10, 220, 291, 141)) + self.groupBox_10.setObjectName("groupBox_10") + self.textVerticalPos = QtWidgets.QComboBox(self.groupBox_10) + self.textVerticalPos.setGeometry(QtCore.QRect(120, 20, 161, 22)) + self.textVerticalPos.setObjectName("textVerticalPos") + self.label_31 = QtWidgets.QLabel(self.groupBox_10) + self.label_31.setGeometry(QtCore.QRect(10, 20, 101, 21)) + self.label_31.setObjectName("label_31") + self.label_32 = QtWidgets.QLabel(self.groupBox_10) + self.label_32.setGeometry(QtCore.QRect(10, 50, 101, 21)) + self.label_32.setObjectName("label_32") + self.textHorizontalPos = QtWidgets.QComboBox(self.groupBox_10) + self.textHorizontalPos.setGeometry(QtCore.QRect(120, 50, 161, 22)) + self.textHorizontalPos.setObjectName("textHorizontalPos") + self.label_33 = QtWidgets.QLabel(self.groupBox_10) + self.label_33.setGeometry(QtCore.QRect(10, 80, 101, 21)) + self.label_33.setObjectName("label_33") + self.textDirection = QtWidgets.QComboBox(self.groupBox_10) + self.textDirection.setGeometry(QtCore.QRect(120, 80, 161, 22)) + self.textDirection.setObjectName("textDirection") + self.textOffsetDist = QtWidgets.QDoubleSpinBox(self.groupBox_10) + self.textOffsetDist.setGeometry(QtCore.QRect(200, 110, 81, 22)) + self.textOffsetDist.setDecimals(6) + self.textOffsetDist.setMaximum(1000000.0) + self.textOffsetDist.setSingleStep(0.0005) + self.textOffsetDist.setObjectName("textOffsetDist") + self.label_34 = QtWidgets.QLabel(self.groupBox_10) + self.label_34.setGeometry(QtCore.QRect(10, 110, 181, 21)) + self.label_34.setObjectName("label_34") + self.textAlignment_groupBox = QtWidgets.QGroupBox(self.TextTab) + self.textAlignment_groupBox.setGeometry(QtCore.QRect(340, 250, 251, 111)) + self.textAlignment_groupBox.setObjectName("textAlignment_groupBox") + self.textRotModeHorizontal = QtWidgets.QRadioButton(self.textAlignment_groupBox) + self.textRotModeHorizontal.setGeometry(QtCore.QRect(10, 20, 251, 17)) + self.textRotModeHorizontal.setObjectName("textRotModeHorizontal") + self.textRotModeAligned = QtWidgets.QRadioButton(self.textAlignment_groupBox) + self.textRotModeAligned.setGeometry(QtCore.QRect(10, 40, 251, 17)) + self.textRotModeAligned.setObjectName("textRotModeAligned") + self.textRotModeISO = QtWidgets.QRadioButton(self.textAlignment_groupBox) + self.textRotModeISO.setGeometry(QtCore.QRect(10, 60, 251, 17)) + self.textRotModeISO.setObjectName("textRotModeISO") + self.textRotModeFixedRot = QtWidgets.QRadioButton(self.textAlignment_groupBox) + self.textRotModeFixedRot.setGeometry(QtCore.QRect(10, 80, 101, 17)) + self.textRotModeFixedRot.setObjectName("textRotModeFixedRot") + self.textForcedRot = QtWidgets.QDoubleSpinBox(self.textAlignment_groupBox) + self.textForcedRot.setGeometry(QtCore.QRect(160, 80, 81, 22)) + self.textForcedRot.setDecimals(6) + self.textForcedRot.setMaximum(360.0) + self.textForcedRot.setSingleStep(0.0005) + self.textForcedRot.setObjectName("textForcedRot") + self.tabWidget.addTab(self.TextTab, "") + self.AdjustTab = QtWidgets.QWidget() + self.AdjustTab.setObjectName("AdjustTab") + self.groupBox_12 = QtWidgets.QGroupBox(self.AdjustTab) + self.groupBox_12.setGeometry(QtCore.QRect(10, 10, 301, 211)) + self.groupBox_12.setObjectName("groupBox_12") + self.label_35 = QtWidgets.QLabel(self.groupBox_12) + self.label_35.setGeometry(QtCore.QRect(10, 20, 251, 51)) + self.label_35.setWordWrap(True) + self.label_35.setObjectName("label_35") + self.textBlockAdjustWhicheverFitsBestOutside = QtWidgets.QRadioButton(self.groupBox_12) + self.textBlockAdjustWhicheverFitsBestOutside.setGeometry(QtCore.QRect(10, 80, 251, 17)) + self.textBlockAdjustWhicheverFitsBestOutside.setObjectName("textBlockAdjustWhicheverFitsBestOutside") + self.textBlockAdjustFirstSymbolOutside = QtWidgets.QRadioButton(self.groupBox_12) + self.textBlockAdjustFirstSymbolOutside.setGeometry(QtCore.QRect(10, 100, 251, 17)) + self.textBlockAdjustFirstSymbolOutside.setObjectName("textBlockAdjustFirstSymbolOutside") + self.textBlockAdjustFirstTextOutside = QtWidgets.QRadioButton(self.groupBox_12) + self.textBlockAdjustFirstTextOutside.setGeometry(QtCore.QRect(10, 120, 251, 17)) + self.textBlockAdjustFirstTextOutside.setObjectName("textBlockAdjustFirstTextOutside") + self.textBlockAdjustBothOutside = QtWidgets.QRadioButton(self.groupBox_12) + self.textBlockAdjustBothOutside.setGeometry(QtCore.QRect(10, 140, 251, 17)) + self.textBlockAdjustBothOutside.setObjectName("textBlockAdjustBothOutside") + self.blockSuppressionForNoSpace = QtWidgets.QCheckBox(self.groupBox_12) + self.blockSuppressionForNoSpace.setGeometry(QtCore.QRect(10, 160, 281, 51)) + self.blockSuppressionForNoSpace.setAutoFillBackground(False) + self.blockSuppressionForNoSpace.setObjectName("blockSuppressionForNoSpace") + self.tabWidget.addTab(self.AdjustTab, "") + self.PrimaryUnitTab = QtWidgets.QWidget() + self.PrimaryUnitTab.setObjectName("PrimaryUnitTab") + self.groupBox_13 = QtWidgets.QGroupBox(self.PrimaryUnitTab) + self.groupBox_13.setGeometry(QtCore.QRect(10, 10, 271, 141)) + self.groupBox_13.setObjectName("groupBox_13") + self.label_36 = QtWidgets.QLabel(self.groupBox_13) + self.label_36.setGeometry(QtCore.QRect(10, 20, 81, 21)) + self.label_36.setObjectName("label_36") + self.textDecimals = QtWidgets.QComboBox(self.groupBox_13) + self.textDecimals.setGeometry(QtCore.QRect(100, 20, 161, 22)) + self.textDecimals.setObjectName("textDecimals") + self.textDecimalSep = QtWidgets.QComboBox(self.groupBox_13) + self.textDecimalSep.setGeometry(QtCore.QRect(170, 50, 91, 22)) + self.textDecimalSep.setObjectName("textDecimalSep") + self.label_37 = QtWidgets.QLabel(self.groupBox_13) + self.label_37.setGeometry(QtCore.QRect(10, 50, 111, 21)) + self.label_37.setObjectName("label_37") + self.textPrefix = QtWidgets.QLineEdit(self.groupBox_13) + self.textPrefix.setGeometry(QtCore.QRect(110, 80, 151, 20)) + self.textPrefix.setObjectName("textPrefix") + self.label_38 = QtWidgets.QLabel(self.groupBox_13) + self.label_38.setGeometry(QtCore.QRect(10, 80, 91, 21)) + self.label_38.setObjectName("label_38") + self.label_39 = QtWidgets.QLabel(self.groupBox_13) + self.label_39.setGeometry(QtCore.QRect(10, 110, 91, 21)) + self.label_39.setObjectName("label_39") + self.textSuffix = QtWidgets.QLineEdit(self.groupBox_13) + self.textSuffix.setGeometry(QtCore.QRect(110, 110, 151, 20)) + self.textSuffix.setObjectName("textSuffix") + self.groupBox_14 = QtWidgets.QGroupBox(self.PrimaryUnitTab) + self.groupBox_14.setGeometry(QtCore.QRect(10, 160, 271, 51)) + self.groupBox_14.setObjectName("groupBox_14") + self.textSuppressLeadingZeros = QtWidgets.QCheckBox(self.groupBox_14) + self.textSuppressLeadingZeros.setGeometry(QtCore.QRect(20, 20, 131, 17)) + self.textSuppressLeadingZeros.setObjectName("textSuppressLeadingZeros") + self.textDecimalZerosSuppression = QtWidgets.QCheckBox(self.groupBox_14) + self.textDecimalZerosSuppression.setGeometry(QtCore.QRect(170, 20, 91, 17)) + self.textDecimalZerosSuppression.setObjectName("textDecimalZerosSuppression") + self.tabWidget.addTab(self.PrimaryUnitTab, "") + self.layoutWidget = QtWidgets.QWidget(DimStyle_Details_Dialog) + self.layoutWidget.setGeometry(QtCore.QRect(390, 410, 239, 25)) + self.layoutWidget.setObjectName("layoutWidget") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.layoutWidget) + self.horizontalLayout.setContentsMargins(0, 0, 0, 0) + self.horizontalLayout.setObjectName("horizontalLayout") + self.okButton = QtWidgets.QPushButton(self.layoutWidget) + self.okButton.setObjectName("okButton") + self.horizontalLayout.addWidget(self.okButton) + self.cancelButton = QtWidgets.QPushButton(self.layoutWidget) + self.cancelButton.setObjectName("cancelButton") + self.horizontalLayout.addWidget(self.cancelButton) + self.helpButton = QtWidgets.QPushButton(self.layoutWidget) + self.helpButton.setObjectName("helpButton") + self.horizontalLayout.addWidget(self.helpButton) + + self.retranslateUi(DimStyle_Details_Dialog) + self.tabWidget.setCurrentIndex(1) + self.linearLayerName.currentIndexChanged['int'].connect(DimStyle_Details_Dialog.linearLayerNameChanged) + self.symbolLayerName.currentIndexChanged['int'].connect(DimStyle_Details_Dialog.symbolLayerNameChanged) + self.textualLayerName.currentIndexChanged['int'].connect(DimStyle_Details_Dialog.textualLayerNameChanged) + self.textRotModeFixedRot.toggled['bool'].connect(DimStyle_Details_Dialog.textRotModeFixedRotToggled) + self.extLineIsFixedLen.toggled['bool'].connect(DimStyle_Details_Dialog.extLineIsFixedLenToggled) + self.helpButton.clicked.connect(DimStyle_Details_Dialog.ButtonHELP_Pressed) + self.okButton.clicked.connect(DimStyle_Details_Dialog.accept) + self.cancelButton.clicked.connect(DimStyle_Details_Dialog.reject) + self.tabWidget.currentChanged['int'].connect(DimStyle_Details_Dialog.currentTabChanged) + self.lineTypeFieldName.currentIndexChanged['int'].connect(DimStyle_Details_Dialog.linetypeFieldNameChanged) + self.symbolFieldName.currentIndexChanged['int'].connect(DimStyle_Details_Dialog.symbolFieldNameChanged) + self.scaleFieldName.currentIndexChanged['int'].connect(DimStyle_Details_Dialog.scaleFieldNameChanged) + self.idFieldName.currentIndexChanged['int'].connect(DimStyle_Details_Dialog.idFieldNameChanged) + self.dimStyleFieldName.currentIndexChanged['int'].connect(DimStyle_Details_Dialog.dimStyleFieldNameChanged) + self.dimTypeFieldName.currentIndexChanged['int'].connect(DimStyle_Details_Dialog.dimTypeFieldNameChanged) + self.colorFieldName.currentIndexChanged['int'].connect(DimStyle_Details_Dialog.colorFieldNameChanged) + self.rotFieldName.currentIndexChanged['int'].connect(DimStyle_Details_Dialog.rotFieldNameChanged) + self.componentFieldName.currentIndexChanged['int'].connect(DimStyle_Details_Dialog.componentFieldNameChanged) + self.idParentFieldName.currentIndexChanged['int'].connect(DimStyle_Details_Dialog.idParentFieldNameChanged) + self.dimLineLineType.textChanged['QString'].connect(DimStyle_Details_Dialog.dimLineLineTypeChanged) + self.dimLine1Hide.toggled['bool'].connect(DimStyle_Details_Dialog.dimLine1HideToggled) + self.dimLine2Hide.toggled['bool'].connect(DimStyle_Details_Dialog.dimLine2HideToggled) + self.extLine1LineType.textChanged['QString'].connect(DimStyle_Details_Dialog.extLine1LineTypeChanged) + self.extLine2LineType.textChanged['QString'].connect(DimStyle_Details_Dialog.extLine2LineTypeChanged) + self.extLine1Hide.toggled['bool'].connect(DimStyle_Details_Dialog.extLine1HideToggled) + self.extLine2Hide.toggled['bool'].connect(DimStyle_Details_Dialog.extLine2HideToggled) + self.extLineOffsetDimLine.valueChanged['double'].connect(DimStyle_Details_Dialog.extLineOffsetDimLineChanged) + self.extLineOffsetOrigPoints.valueChanged['double'].connect(DimStyle_Details_Dialog.extLineOffsetOrigPointsChanged) + self.extLineFixedLen.valueChanged['double'].connect(DimStyle_Details_Dialog.extLineFixedLenChanged) + self.block1Name.textChanged['QString'].connect(DimStyle_Details_Dialog.block1NameChanged) + self.block2Name.textChanged['QString'].connect(DimStyle_Details_Dialog.block2NameChanged) + self.blockLeaderName.textChanged['QString'].connect(DimStyle_Details_Dialog.blockLeaderNameChanged) + self.blockWidth.valueChanged['double'].connect(DimStyle_Details_Dialog.blockWidthChanged) + self.blockScale.valueChanged['double'].connect(DimStyle_Details_Dialog.blockScaleChanged) + self.textFont.currentIndexChanged['int'].connect(DimStyle_Details_Dialog.textFontChanged) + self.textHeight.valueChanged['double'].connect(DimStyle_Details_Dialog.textHeightChanged) + self.textVerticalPos.currentIndexChanged['int'].connect(DimStyle_Details_Dialog.textVerticalPosChanged) + self.textHorizontalPos.currentIndexChanged['int'].connect(DimStyle_Details_Dialog.textHorizontalPosChanged) + self.textDirection.currentIndexChanged['int'].connect(DimStyle_Details_Dialog.textDirectionChanged) + self.textOffsetDist.valueChanged['double'].connect(DimStyle_Details_Dialog.textOffsetDistChanged) + self.textForcedRot.valueChanged['double'].connect(DimStyle_Details_Dialog.textForcedRotChanged) + self.blockSuppressionForNoSpace.toggled['bool'].connect(DimStyle_Details_Dialog.blockSuppressionForNoSpaceToggled) + self.textDecimals.currentIndexChanged['int'].connect(DimStyle_Details_Dialog.textDecimalsChanged) + self.textDecimalSep.currentIndexChanged['int'].connect(DimStyle_Details_Dialog.textDecimalSepChanged) + self.textPrefix.textChanged['QString'].connect(DimStyle_Details_Dialog.textPrefixChanged) + self.textSuffix.textChanged['QString'].connect(DimStyle_Details_Dialog.textSuffixChanged) + self.textSuppressLeadingZeros.toggled['bool'].connect(DimStyle_Details_Dialog.textSuppressLeadingZerosToggled) + self.textDecimalZerosSuppression.toggled['bool'].connect(DimStyle_Details_Dialog.textDecimalZerosSuppressionToggled) + self.textRotModeHorizontal.toggled['bool'].connect(DimStyle_Details_Dialog.textRotModeHorizontalToggled) + self.textRotModeAligned.toggled['bool'].connect(DimStyle_Details_Dialog.textRotModeAlignedToggled) + self.textRotModeISO.toggled['bool'].connect(DimStyle_Details_Dialog.textRotModeISOToggled) + self.textBlockAdjustWhicheverFitsBestOutside.toggled['bool'].connect(DimStyle_Details_Dialog.textBlockAdjustWhicheverFitsBestOutsideToggled) + self.textBlockAdjustFirstSymbolOutside.toggled['bool'].connect(DimStyle_Details_Dialog.textBlockAdjustFirstSymbolOutsideToggled) + self.textBlockAdjustFirstTextOutside.toggled['bool'].connect(DimStyle_Details_Dialog.textBlockAdjustFirstTextOutsideToggled) + self.textBlockAdjustBothOutside.toggled['bool'].connect(DimStyle_Details_Dialog.textBlockAdjustBothOutsideToggled) + self.arcSymbolPreceding.toggled['bool'].connect(DimStyle_Details_Dialog.arcSymbolPrecedingToggled) + self.arcSymbolAbove.toggled['bool'].connect(DimStyle_Details_Dialog.arcSymbolAboveToggled) + self.arcSymbolNone.toggled['bool'].connect(DimStyle_Details_Dialog.arcSymbolNoneToggled) + self.dimLineOffsetExtLine.valueChanged['double'].connect(DimStyle_Details_Dialog.dimLineOffsetExtLineChanged) + self.centerMarkNone.toggled['bool'].connect(DimStyle_Details_Dialog.centerMarkNoneChanged) + self.centerMarkMark.toggled['bool'].connect(DimStyle_Details_Dialog.centerMarkMarkChanged) + self.centerMarkLine.toggled['bool'].connect(DimStyle_Details_Dialog.centerMarkLineChanged) + self.centerMarkLength.valueChanged['double'].connect(DimStyle_Details_Dialog.centerMarkLengthChanged) + QtCore.QMetaObject.connectSlotsByName(DimStyle_Details_Dialog) + + def retranslateUi(self, DimStyle_Details_Dialog): + _translate = QtCore.QCoreApplication.translate + DimStyle_Details_Dialog.setWindowTitle(_translate("DimStyle_Details_Dialog", "Dimension style details")) + self.groupBox_5.setTitle(_translate("DimStyle_Details_Dialog", "Lines")) + self.label_2.setText(_translate("DimStyle_Details_Dialog", "Layer:")) + self.linearLayerName.setToolTip(_translate("DimStyle_Details_Dialog", "Name of the layer for dimension lines.")) + self.lineTypeFieldName.setToolTip(_translate("DimStyle_Details_Dialog", "Field storing the linetype name.")) + self.label_21.setText(_translate("DimStyle_Details_Dialog", "Linetype field:")) + self.groupBox_6.setTitle(_translate("DimStyle_Details_Dialog", "Symbols and arrows")) + self.symbolLayerName.setToolTip(_translate("DimStyle_Details_Dialog", "Name of the layer for dimension symbols and arrows.")) + self.label_13.setText(_translate("DimStyle_Details_Dialog", "Layer:")) + self.label_22.setText(_translate("DimStyle_Details_Dialog", "Symbol field:")) + self.symbolFieldName.setToolTip(_translate("DimStyle_Details_Dialog", "Field storing the symbol name.")) + self.scaleFieldName.setToolTip(_translate("DimStyle_Details_Dialog", "Field storing the symbol size.")) + self.label_23.setText(_translate("DimStyle_Details_Dialog", "Scale field:")) + self.groupBox_7.setTitle(_translate("DimStyle_Details_Dialog", "Generics fields")) + self.label_24.setText(_translate("DimStyle_Details_Dialog", "Rotation:")) + self.rotFieldName.setToolTip(_translate("DimStyle_Details_Dialog", "Field storing the rotation of the punctual elements od dimension (symbols, arrows, text).")) + self.label_16.setText(_translate("DimStyle_Details_Dialog", "Color:")) + self.colorFieldName.setToolTip(_translate("DimStyle_Details_Dialog", "Field storing the RGB colorfor all elements of dimension (e.g. \"255,255,255,255\" = white with total opacity).")) + self.label_28.setText(_translate("DimStyle_Details_Dialog", "Linking ID:")) + self.idParentFieldName.setToolTip(_translate("DimStyle_Details_Dialog", "Field storing the ID of the dimension to group all elements (except the text which is the root element).")) + self.label_25.setText(_translate("DimStyle_Details_Dialog", "Component type:")) + self.componentFieldName.setToolTip(_translate("DimStyle_Details_Dialog", "

      Field storing the component type of dimension:

      "D1" = Dimension line 1.

      "D2" = Dimension line 2.

      "E1" = Extension line 1.

      "E2" = Extension line 2.

      "L" = Leader.

      "B1" = Block 1.

      "B2" = Block 2.

      "LB" = Leader Block.

      "AB" = Arc Block.

      "D1" = Dimension point 1.

      "D2" = Dimension point 2.

      ")) + self.groupBox_8.setTitle(_translate("DimStyle_Details_Dialog", "Text")) + self.label_27.setText(_translate("DimStyle_Details_Dialog", "ID Field:")) + self.idFieldName.setToolTip(_translate("DimStyle_Details_Dialog", "Field storing the unique code for each dimension.")) + self.dimStyleFieldName.setToolTip(_translate("DimStyle_Details_Dialog", "Field storing the name of the dimension style.")) + self.label_29.setText(_translate("DimStyle_Details_Dialog", "Dim. style field:")) + self.label_30.setText(_translate("DimStyle_Details_Dialog", "Dim. type field:")) + self.dimTypeFieldName.setToolTip(_translate("DimStyle_Details_Dialog", "

      Field storing the dimension type:

      "AL" = linear dimension that is aligned with the origin points of the extension lines.

      "AN" = angular dimension, it measures the angle between selected objects or 3 points.

      "BL" = linear, angular, or ordinate dimension from the baseline of the previous or selected dimension.

      "CE" = creates the center mark or the centerlines of circles and arcs.

      "DI" = creates a diameter dimension for a circle or an arc.

      "LD" = creates a line that connects annotation to a feature..

      "LI" = linear dimension with a horizontal, vertical, or rotated dimension line.

      "RA" = radial dimension, measures the radius of a selected circle or arc and displays the dimension text with a radius symbol in front of it.

      "AR" = arc length dimensions measure the distance along an arc or polyline arc segment.

      ")) + self.label_14.setText(_translate("DimStyle_Details_Dialog", "Layer:")) + self.textualLayerName.setToolTip(_translate("DimStyle_Details_Dialog", "Name of the layer storing dimension texts.")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.DBTab), _translate("DimStyle_Details_Dialog", "DB")) + self.groupBox.setTitle(_translate("DimStyle_Details_Dialog", "Dimension lines")) + self.label.setText(_translate("DimStyle_Details_Dialog", "Color:")) + self.dimLineLineType.setToolTip(_translate("DimStyle_Details_Dialog", "Linetype of dimension line.")) + self.label_3.setText(_translate("DimStyle_Details_Dialog", "Linetype:")) + self.label_5.setText(_translate("DimStyle_Details_Dialog", "Suppress:")) + self.dimLine1Hide.setToolTip(_translate("DimStyle_Details_Dialog", "Suppresses display of dimension line 1.")) + self.dimLine1Hide.setText(_translate("DimStyle_Details_Dialog", "Dim. line 1")) + self.dimLine2Hide.setToolTip(_translate("DimStyle_Details_Dialog", "Suppresses display of dimension line 2.")) + self.dimLine2Hide.setText(_translate("DimStyle_Details_Dialog", "Dim. line 2")) + self.label_41.setText(_translate("DimStyle_Details_Dialog", "Extend beyond ticks:")) + self.dimLineOffsetExtLine.setToolTip(_translate("DimStyle_Details_Dialog", "Specifies a distance to extend the dimension line above the extension lines.")) + self.groupBox_2.setTitle(_translate("DimStyle_Details_Dialog", "Extension lines")) + self.label_6.setText(_translate("DimStyle_Details_Dialog", "Color:")) + self.extLine1LineType.setToolTip(_translate("DimStyle_Details_Dialog", "Linetype for extension line 1.")) + self.label_7.setText(_translate("DimStyle_Details_Dialog", "Linetype ext. 1:")) + self.label_8.setText(_translate("DimStyle_Details_Dialog", "Linetype ext. 2:")) + self.extLine2LineType.setToolTip(_translate("DimStyle_Details_Dialog", "Linetype for extension line 2.")) + self.label_9.setText(_translate("DimStyle_Details_Dialog", "Suppress:")) + self.extLine1Hide.setToolTip(_translate("DimStyle_Details_Dialog", "Suppresses display of extension line 1.")) + self.extLine1Hide.setText(_translate("DimStyle_Details_Dialog", "Ext. line 1")) + self.extLine2Hide.setToolTip(_translate("DimStyle_Details_Dialog", "Suppresses display of extension line 2.")) + self.extLine2Hide.setText(_translate("DimStyle_Details_Dialog", "Ext. line 2")) + self.label_10.setText(_translate("DimStyle_Details_Dialog", "Extend beyond dim lines:")) + self.extLineOffsetDimLine.setToolTip(_translate("DimStyle_Details_Dialog", "Specifies a distance to extend the extension lines above the dimension line.")) + self.extLineOffsetOrigPoints.setToolTip(_translate("DimStyle_Details_Dialog", "Sets the distance to offset the extension lines from the points on the drawing that define the dimension.")) + self.label_11.setText(_translate("DimStyle_Details_Dialog", "Offset from origin:")) + self.extLineIsFixedLen.setToolTip(_translate("DimStyle_Details_Dialog", "Enables fixed length extension lines.")) + self.extLineIsFixedLen.setText(_translate("DimStyle_Details_Dialog", "Fixed length extension lines")) + self.label_12.setText(_translate("DimStyle_Details_Dialog", "Length:")) + self.extLineFixedLen.setToolTip(_translate("DimStyle_Details_Dialog", "Total length of the extension lines starting from the dimension line toward the dimension origin.")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.LineTab), _translate("DimStyle_Details_Dialog", "Lines")) + self.groupBox_3.setTitle(_translate("DimStyle_Details_Dialog", "Arrowheads")) + self.block1Name.setToolTip(_translate("DimStyle_Details_Dialog", "Arrowhead for the first dimension line. When you change the first arrowhead type, the second arrowhead automatically changes to match it.")) + self.label_15.setText(_translate("DimStyle_Details_Dialog", "Arrowhead 1:")) + self.block2Name.setToolTip(_translate("DimStyle_Details_Dialog", "Arrowhead for the second dimension line.")) + self.label_18.setText(_translate("DimStyle_Details_Dialog", "Arrowhead 2:")) + self.blockLeaderName.setToolTip(_translate("DimStyle_Details_Dialog", "Arrowhead for the leader line.")) + self.label_19.setText(_translate("DimStyle_Details_Dialog", "Leader:")) + self.blockWidth.setToolTip(_translate("DimStyle_Details_Dialog", "Arrowhead horizontal size in map units using the symbol scale factor = 1.")) + self.label_17.setText(_translate("DimStyle_Details_Dialog", "Arrowhead size:")) + self.blockScale.setToolTip(_translate("DimStyle_Details_Dialog", "Arrowhead scale.")) + self.label_20.setText(_translate("DimStyle_Details_Dialog", "Arrowhead scale:")) + self.groupBox_4.setTitle(_translate("DimStyle_Details_Dialog", "Arc length symbol")) + self.arcSymbolPreceding.setToolTip(_translate("DimStyle_Details_Dialog", "Places arc length symbols before the dimension text.")) + self.arcSymbolPreceding.setText(_translate("DimStyle_Details_Dialog", "Preceding dimension text")) + self.arcSymbolAbove.setToolTip(_translate("DimStyle_Details_Dialog", "Places arc length symbols above the dimension text.")) + self.arcSymbolAbove.setText(_translate("DimStyle_Details_Dialog", "Above dimension text")) + self.arcSymbolNone.setToolTip(_translate("DimStyle_Details_Dialog", "Suppresses the display of arc length symbols.")) + self.arcSymbolNone.setText(_translate("DimStyle_Details_Dialog", "None")) + self.groupBox_11.setTitle(_translate("DimStyle_Details_Dialog", "Center marks")) + self.centerMarkNone.setToolTip(_translate("DimStyle_Details_Dialog", "Creates no center mark or centerline.")) + self.centerMarkNone.setText(_translate("DimStyle_Details_Dialog", "None")) + self.centerMarkMark.setToolTip(_translate("DimStyle_Details_Dialog", "Creates a center mark.")) + self.centerMarkMark.setText(_translate("DimStyle_Details_Dialog", "Mark")) + self.centerMarkLine.setToolTip(_translate("DimStyle_Details_Dialog", "Creates a centerline.")) + self.centerMarkLine.setText(_translate("DimStyle_Details_Dialog", "Line")) + self.centerMarkLength.setToolTip(_translate("DimStyle_Details_Dialog", "Size of the center mark or centerline.")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.SymbolTab), _translate("DimStyle_Details_Dialog", "Symbols and arrows")) + self.groupBox_9.setTitle(_translate("DimStyle_Details_Dialog", "Text appearance")) + self.label_26.setText(_translate("DimStyle_Details_Dialog", "Text height:")) + self.textHeight.setToolTip(_translate("DimStyle_Details_Dialog", "Text height in map units.")) + self.label_40.setText(_translate("DimStyle_Details_Dialog", "Character type:")) + self.textFont.setToolTip(_translate("DimStyle_Details_Dialog", "Dimension text character type.")) + self.label_4.setText(_translate("DimStyle_Details_Dialog", "Color:")) + self.groupBox_10.setTitle(_translate("DimStyle_Details_Dialog", "Text placement")) + self.textVerticalPos.setToolTip(_translate("DimStyle_Details_Dialog", "Controls the vertical placement of dimension text in relation to the dimension line.")) + self.label_31.setText(_translate("DimStyle_Details_Dialog", "Vertical:")) + self.label_32.setText(_translate("DimStyle_Details_Dialog", "Horizontal:")) + self.textHorizontalPos.setToolTip(_translate("DimStyle_Details_Dialog", "Controls the horizontal placement of dimension text along the dimension line, in relation to the extension lines.")) + self.label_33.setText(_translate("DimStyle_Details_Dialog", "View direction:")) + self.textDirection.setToolTip(_translate("DimStyle_Details_Dialog", "Controls the dimension text viewing direction.")) + self.textOffsetDist.setToolTip(_translate("DimStyle_Details_Dialog", "Sets the current text gap, which is the distance around the dimension text when the dimension line is broken to accommodate the dimension text.")) + self.label_34.setText(_translate("DimStyle_Details_Dialog", "Offset from dim line:")) + self.textAlignment_groupBox.setTitle(_translate("DimStyle_Details_Dialog", "Text alignment")) + self.textRotModeHorizontal.setToolTip(_translate("DimStyle_Details_Dialog", "Places text in a horizontal position.")) + self.textRotModeHorizontal.setText(_translate("DimStyle_Details_Dialog", "Horizontal")) + self.textRotModeAligned.setToolTip(_translate("DimStyle_Details_Dialog", "Text aligned with Dimension Line.")) + self.textRotModeAligned.setText(_translate("DimStyle_Details_Dialog", "Aligned with dimension line")) + self.textRotModeISO.setToolTip(_translate("DimStyle_Details_Dialog", "Aligns text with the dimension line when text is inside the extension lines, but aligns it horizontally when text is outside the extension lines.")) + self.textRotModeISO.setText(_translate("DimStyle_Details_Dialog", "ISO Standard")) + self.textRotModeFixedRot.setToolTip(_translate("DimStyle_Details_Dialog", "Place text with fixed angle.")) + self.textRotModeFixedRot.setText(_translate("DimStyle_Details_Dialog", "Fixed rotation")) + self.textForcedRot.setToolTip(_translate("DimStyle_Details_Dialog", "Text angle when fixed rotation mode is on.")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.TextTab), _translate("DimStyle_Details_Dialog", "Text")) + self.groupBox_12.setTitle(_translate("DimStyle_Details_Dialog", "Fit options")) + self.label_35.setText(_translate("DimStyle_Details_Dialog", "If there isn\'t enaugh room to place both text and arrows inside extension lines, the first thing to move outside the extension lines is:")) + self.textBlockAdjustWhicheverFitsBestOutside.setToolTip(_translate("DimStyle_Details_Dialog", "Moves either the text or the arrowheads outside the extension lines based on the best fit.")) + self.textBlockAdjustWhicheverFitsBestOutside.setText(_translate("DimStyle_Details_Dialog", "Either text or arrows (best fit)")) + self.textBlockAdjustFirstSymbolOutside.setToolTip(_translate("DimStyle_Details_Dialog", "Moves arrowheads outside the extension lines first, then text.")) + self.textBlockAdjustFirstSymbolOutside.setText(_translate("DimStyle_Details_Dialog", "Arrows")) + self.textBlockAdjustFirstTextOutside.setToolTip(_translate("DimStyle_Details_Dialog", "Moves text outside the extension lines first, then arrowheads.")) + self.textBlockAdjustFirstTextOutside.setText(_translate("DimStyle_Details_Dialog", "Text")) + self.textBlockAdjustBothOutside.setToolTip(_translate("DimStyle_Details_Dialog", "When not enough space is available for text and arrowheads, moves both outside the extension lines.")) + self.textBlockAdjustBothOutside.setText(_translate("DimStyle_Details_Dialog", "Both text and arrows")) + self.blockSuppressionForNoSpace.setToolTip(_translate("DimStyle_Details_Dialog", "Suppresses arrowheads if not enough space is available inside the extension lines.")) + self.blockSuppressionForNoSpace.setText(_translate("DimStyle_Details_Dialog", "Suppress arrows if they don\'t fit inside ext. lines")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.AdjustTab), _translate("DimStyle_Details_Dialog", "Fit")) + self.groupBox_13.setTitle(_translate("DimStyle_Details_Dialog", "Linear dimensions")) + self.label_36.setText(_translate("DimStyle_Details_Dialog", "Precision:")) + self.textDecimals.setToolTip(_translate("DimStyle_Details_Dialog", "Displays and sets the number of decimal places in the dimension text.")) + self.textDecimalSep.setToolTip(_translate("DimStyle_Details_Dialog", "Sets the separator for decimal formats.")) + self.label_37.setText(_translate("DimStyle_Details_Dialog", "Decimal separator:")) + self.textPrefix.setToolTip(_translate("DimStyle_Details_Dialog", "Includes a prefix that you specify in the dimension text.")) + self.label_38.setText(_translate("DimStyle_Details_Dialog", "Prefix:")) + self.label_39.setText(_translate("DimStyle_Details_Dialog", "Suffix:")) + self.textSuffix.setToolTip(_translate("DimStyle_Details_Dialog", "Includes a suffix that you specify in the dimension text.")) + self.groupBox_14.setTitle(_translate("DimStyle_Details_Dialog", "Zero suppression")) + self.textSuppressLeadingZeros.setToolTip(_translate("DimStyle_Details_Dialog", "Suppresses leading zeros in all decimal dimensions (0.5 becomes .5).")) + self.textSuppressLeadingZeros.setText(_translate("DimStyle_Details_Dialog", "Leading")) + self.textDecimalZerosSuppression.setToolTip(_translate("DimStyle_Details_Dialog", "Suppresses trailing zeros in all decimal dimensions (5.50 becomes 5.5 and 5.0 becomes 5).")) + self.textDecimalZerosSuppression.setText(_translate("DimStyle_Details_Dialog", "Trailing")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.PrimaryUnitTab), _translate("DimStyle_Details_Dialog", "Primary units")) + self.okButton.setText(_translate("DimStyle_Details_Dialog", "OK")) + self.cancelButton.setText(_translate("DimStyle_Details_Dialog", "Cancel")) + self.helpButton.setText(_translate("DimStyle_Details_Dialog", "?")) diff --git a/qad_dimstyle_diff.ui b/qad_dimstyle_diff.ui index d2bfc74c..5e399860 100644 --- a/qad_dimstyle_diff.ui +++ b/qad_dimstyle_diff.ui @@ -1,243 +1,255 @@ - - - DimStyle_Diff_Dialog - - - - 0 - 0 - 443 - 526 - - - - QAD - Compare dimension styles - - - - - 10 - 10 - 71 - 21 - - - - Compare: - - - - - - 10 - 40 - 71 - 21 - - - - With: - - - - - - 80 - 10 - 211 - 22 - - - - Specify the first dimension style. - - - - - - 80 - 40 - 211 - 22 - - - - Specify the second dimension style. If you set the second style as the first, all dimension style properties will displayed. - - - - - - 10 - 70 - 421 - 16 - - - - Qt::Horizontal - - - - - - 10 - 80 - 321 - 21 - - - - TextLabel - - - - - - 277 - 490 - 158 - 25 - - - - - - - Close - - - - - - - ? - - - - - - - - - 10 - 110 - 421 - 371 - - - - <html><head/><body><p>Display the result of comparing dimension styles.If you compare two different styles, the settings that are different between the two dimension styles, their current settings, and brief descriptions are listed. If you set the second style as the first, all dimension style properties will displayed.</p></body></html> - - - - - - 404 - 80 - 31 - 23 - - - - Copy the result of comparing into the clipboard. - - - - - - - :/plugins/qad/icons/copy.png:/plugins/qad/icons/copy.png - - - - - - - helpButton - clicked() - DimStyle_Diff_Dialog - ButtonHELP_Pressed() - - - 426 - 500 - - - 397 - 316 - - - - - dimStyle1 - currentIndexChanged(int) - DimStyle_Diff_Dialog - DimStyleName1Changed() - - - 226 - 20 - - - 347 - 20 - - - - - dimStyle2 - currentIndexChanged(int) - DimStyle_Diff_Dialog - DimStyleName2Changed() - - - 235 - 55 - - - 347 - 55 - - - - - copyButton - clicked() - DimStyle_Diff_Dialog - copyToClipboard() - - - 427 - 91 - - - 441 - 99 - - - - - closeButton - clicked() - DimStyle_Diff_Dialog - accept() - - - 303 - 504 - - - 210 - 500 - - - - - - ButtonHELP_Pressed() - DimStyleName1Changed() - DimStyleName2Changed() - copyToClipboard() - - + + + DimStyle_Diff_Dialog + + + + 0 + 0 + 443 + 526 + + + + + 443 + 526 + + + + + 443 + 526 + + + + Compare dimension styles + + + + + 10 + 10 + 81 + 21 + + + + Compare: + + + + + + 10 + 40 + 81 + 21 + + + + With: + + + + + + 100 + 10 + 211 + 22 + + + + Specify the first dimension style. + + + + + + 100 + 40 + 211 + 22 + + + + Specify the second dimension style. If you set the second style as the first, all dimension style properties will displayed. + + + + + + 10 + 70 + 421 + 16 + + + + Qt::Horizontal + + + + + + 10 + 80 + 381 + 21 + + + + TextLabel + + + + + + 277 + 490 + 158 + 25 + + + + + + + Close + + + + + + + ? + + + + + + + + + 10 + 110 + 421 + 371 + + + + <html><head/><body><p>Display the result of comparing dimension styles.If you compare two different styles, the settings that are different between the two dimension styles, their current settings, and brief descriptions are listed. If you set the second style as the first, all dimension style properties will displayed.</p></body></html> + + + + + + 404 + 80 + 31 + 23 + + + + Copy the result of comparing into the clipboard. + + + + + + + :/plugins/qad/icons/copy.png:/plugins/qad/icons/copy.png + + + + + + + helpButton + clicked() + DimStyle_Diff_Dialog + ButtonHELP_Pressed() + + + 426 + 500 + + + 397 + 316 + + + + + dimStyle1 + currentIndexChanged(int) + DimStyle_Diff_Dialog + DimStyleName1Changed() + + + 226 + 20 + + + 347 + 20 + + + + + dimStyle2 + currentIndexChanged(int) + DimStyle_Diff_Dialog + DimStyleName2Changed() + + + 235 + 55 + + + 347 + 55 + + + + + copyButton + clicked() + DimStyle_Diff_Dialog + copyToClipboard() + + + 427 + 91 + + + 441 + 99 + + + + + closeButton + clicked() + DimStyle_Diff_Dialog + accept() + + + 303 + 504 + + + 210 + 500 + + + + + + ButtonHELP_Pressed() + DimStyleName1Changed() + DimStyleName2Changed() + copyToClipboard() + + diff --git a/qad_dimstyle_diff_dlg.py b/qad_dimstyle_diff_dlg.py index 22f6356d..50f04cc8 100644 --- a/qad_dimstyle_diff_dlg.py +++ b/qad_dimstyle_diff_dlg.py @@ -1,218 +1,218 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - classe per gestire la dialog per DIMSTYLE - - ------------------- - begin : 2015-05-19 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -from qgis.core import QgsApplication -from qgis.utils import * - -import qad_dimstyle_diff_ui - -from qad_dim import * -from qad_msg import QadMsg, qadShowPluginHelp -import qad_utils - - -####################################################################################### -# Classe che gestisce l'interfaccia grafica della funzione di comparazione tra stili di quotatura -class QadDIMSTYLE_DIFF_Dialog(QDialog, QObject, qad_dimstyle_diff_ui.Ui_DimStyle_Diff_Dialog): - def __init__(self, plugIn, dimStyleName1 = None, dimStyleName2 = None): - self.plugIn = plugIn - self.iface = self.plugIn.iface.mainWindow() - QDialog.__init__(self, self.iface) - - self.setupUi(self) - - self.dimNameList = [] - for dimStyle in QadDimStyles.dimStyleList: # lista degli stili di quotatura caricati - self.dimStyle1.addItem(dimStyle.name, dimStyle) - self.dimStyle2.addItem(dimStyle.name, dimStyle) - - # sort - self.dimStyle1.model().sort(0) - self.dimStyle2.model().sort(0) - - # seleziono un elemento della lista - if dimStyleName1 is not None: - index = self.dimStyle1.findText(dimStyleName1) - self.dimStyle1.setCurrentIndex(index) - else: - self.dimStyle1.setCurrentIndex(0) - - # seleziono un elemento della lista - if dimStyleName2 is not None: - index = self.dimStyle2.findText(dimStyleName2) - self.dimStyle2.setCurrentIndex(index) - self.DimStyleName2Changed(index) - else: - self.dimStyle2.setCurrentIndex(0) - - def DimStyleName1Changed(self, index): - # leggo l'elemento selezionato - dimStyle1 = self.dimStyle1.itemData(index) - index = self.dimStyle2.currentIndex() - dimStyle2 = self.dimStyle2.itemData(index) if index >= 0 else None - self.showProps(dimStyle1, dimStyle2) - - def DimStyleName2Changed(self, index): - # leggo l'elemento selezionato - dimStyle2 = self.dimStyle2.itemData(index) - index = self.dimStyle1.currentIndex() - dimStyle1 = self.dimStyle1.itemData(index) if index >= 0 else None - self.showProps(dimStyle1, dimStyle2) - - def showProps(self, dimStyle1, dimStyle2): - self.tableWidget.clear() - if dimStyle1 is None: - return - - if dimStyle2 is None or dimStyle1.name == dimStyle2.name: - self.showAllProps(dimStyle1) - else: - self.showDiffProps(dimStyle1, dimStyle2) - - def showAllProps(self, dimStyle): - if self.tableWidget.model() is not None: - # Pulisce la tabella - self.tableWidget.model().reset() - self.tableWidget.setRowCount(0) - - self.tableWidget.setColumnCount(2) - headerLabels = [] - headerLabels.append(QadMsg.translate("DimStyle_Diff_Dialog", "Description")) - headerLabels.append(dimStyle.name) - self.tableWidget.setHorizontalHeaderLabels(headerLabels) - self.tableWidget.horizontalHeader().show() - - self.count = 0 - propsDict = dimStyle.getPropList().items() - for prop in propsDict: - propName = prop[0] - propDescr = prop[1][0] - propValue = prop[1][1] - self.insertProp(propDescr, propValue) - - self.tableWidget.sortItems(0) - - self.tableWidget.horizontalHeader().setResizeMode(0, QHeaderView.ResizeToContents) - self.tableWidget.horizontalHeader().setResizeMode(1, QHeaderView.Interactive) - - self.msg.setText(QadMsg.translate("DimStyle_Diff_Dialog", "All properties of dimension style: ") + dimStyle.name) - - - def showDiffProps(self, dimStyle1, dimStyle2): - if self.tableWidget.model() is not None: - # Pulisce la tabella - self.tableWidget.model().reset() - self.tableWidget.setRowCount(0) - - self.tableWidget.setColumnCount(3) - headerLabels = [] - headerLabels.append(QadMsg.translate("DimStyle_Diff_Dialog", "Description")) - headerLabels.append(dimStyle1.name) - headerLabels.append(dimStyle2.name) - self.tableWidget.setHorizontalHeaderLabels(headerLabels) - self.tableWidget.horizontalHeader().show() - - self.count = 0 - prop1Items = dimStyle1.getPropList().items() # lista di nome con lista [descrizione, valore] - props2Dict = dimStyle2.getPropList() # dizionario di nome con lista [descrizione, valore] - for prop1 in prop1Items: - propName = prop1[0] - propDescr = prop1[1][0] - prop1Value = prop1[1][1] - prop2 = props2Dict[propName] - prop2Value = prop2[1] - if prop1Value is None: - prop1Value = "" - if prop2Value is None: - prop2Value = "" - if unicode(prop1Value) != unicode(prop2Value): - self.insertProp(propDescr, prop1Value, prop2Value) - - self.tableWidget.sortItems(0) - - self.tableWidget.horizontalHeader().setResizeMode(0, QHeaderView.ResizeToContents) - self.tableWidget.horizontalHeader().setResizeMode(2, QHeaderView.Interactive) - self.tableWidget.horizontalHeader().setResizeMode(3, QHeaderView.Interactive) - - self.msg.setText(QadMsg.translate("DimStyle_Diff_Dialog", "Found {0} differences: ").format(str(self.count))) - - - def insertProp(self, description, val1, val2 = None): - self.tableWidget.insertRow(self.count) - - item = QTableWidgetItem(unicode(description)) - item.setFlags(Qt.NoItemFlags | Qt.ItemIsEnabled | Qt.ItemIsSelectable) - self.tableWidget.setItem(self.count, 0, item) - - item = QTableWidgetItem(unicode(val1)) - item.setFlags(Qt.NoItemFlags | Qt.ItemIsEnabled | Qt.ItemIsSelectable) - self.tableWidget.setItem(self.count, 1, item) - - if val2 is not None: - item = QTableWidgetItem(unicode(val2)) - item.setFlags(Qt.NoItemFlags | Qt.ItemIsEnabled | Qt.ItemIsSelectable) - self.tableWidget.setItem(self.count, 2, item) - self.count += 1 - - - def ButtonHELP_Pressed(self): - qadShowPluginHelp(QadMsg.translate("Help", "Dimensioning")) - - def resizeEvent(self, event): - QDialog.resizeEvent(self, event) - if event.oldSize().width() == -1: # non c'era una dimensione precedente - return - tableWidgetSize = self.tableWidget.size() - newWidth = tableWidgetSize.width() + event.size().width() - event.oldSize().width() - newHeight = tableWidgetSize.height() + event.size().height() - event.oldSize().height() - tableWidgetSize.setWidth(newWidth) - tableWidgetSize.setHeight(newHeight) - self.tableWidget.resize(tableWidgetSize) - - - def copyToClipboard(self): - buffer = "" - - # intestazione - for col in xrange(0, self.tableWidget.columnCount(), 1): - if col > 0: - buffer += '\t' # aggiungo un TAB - buffer += self.tableWidget.horizontalHeaderItem(col).text() - buffer += '\n' # vado a capo - - # valori delle righe - for row in xrange(0, self.tableWidget.rowCount(), 1): - for col in xrange(0, self.tableWidget.columnCount(), 1): - if col > 0: - buffer += '\t' # aggiungo un TAB - buffer += self.tableWidget.item(row, col).text() - buffer += '\n' # vado a capo - - QApplication.clipboard().setText(buffer) +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + classe per gestire la dialog per DIMSTYLE + + ------------------- + begin : 2015-05-19 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.PyQt.QtCore import Qt, QObject +from qgis.PyQt.QtWidgets import QDialog, QHeaderView, QTableWidgetItem, QApplication +from qgis.utils import * + +from . import qad_dimstyle_diff_ui + +from .qad_dim import QadDimStyles +from .qad_msg import QadMsg, qadShowPluginPDFHelp +from . import qad_utils + + +####################################################################################### +# Classe che gestisce l'interfaccia grafica della funzione di comparazione tra stili di quotatura +class QadDIMSTYLE_DIFF_Dialog(QDialog, QObject, qad_dimstyle_diff_ui.Ui_DimStyle_Diff_Dialog): + def __init__(self, plugIn, parent, dimStyleName1 = None, dimStyleName2 = None): + self.plugIn = plugIn + self.iface = self.plugIn.iface.mainWindow() + + QDialog.__init__(self, parent) + + self.setupUi(self) + self.setWindowTitle(QadMsg.getQADTitle() + " - " + self.windowTitle()) + + self.dimNameList = [] + for dimStyle in QadDimStyles.dimStyleList: # lista degli stili di quotatura caricati + self.dimStyle1.addItem(dimStyle.name, dimStyle) + self.dimStyle2.addItem(dimStyle.name, dimStyle) + + # sort + self.dimStyle1.model().sort(0) + self.dimStyle2.model().sort(0) + + # seleziono un elemento della lista + if dimStyleName1 is not None: + index = self.dimStyle1.findText(dimStyleName1) + self.dimStyle1.setCurrentIndex(index) + else: + self.dimStyle1.setCurrentIndex(0) + + # seleziono un elemento della lista + if dimStyleName2 is not None: + index = self.dimStyle2.findText(dimStyleName2) + self.dimStyle2.setCurrentIndex(index) + self.DimStyleName2Changed(index) + else: + self.dimStyle2.setCurrentIndex(0) + + def DimStyleName1Changed(self, index): + # leggo l'elemento selezionato + dimStyle1 = self.dimStyle1.itemData(index) + index = self.dimStyle2.currentIndex() + dimStyle2 = self.dimStyle2.itemData(index) if index >= 0 else None + self.showProps(dimStyle1, dimStyle2) + + def DimStyleName2Changed(self, index): + # leggo l'elemento selezionato + dimStyle2 = self.dimStyle2.itemData(index) + index = self.dimStyle1.currentIndex() + dimStyle1 = self.dimStyle1.itemData(index) if index >= 0 else None + self.showProps(dimStyle1, dimStyle2) + + def showProps(self, dimStyle1, dimStyle2): + self.tableWidget.clear() + if dimStyle1 is None: + return + + if dimStyle2 is None or dimStyle1.name == dimStyle2.name: + self.showAllProps(dimStyle1) + else: + self.showDiffProps(dimStyle1, dimStyle2) + + def showAllProps(self, dimStyle): + if self.tableWidget.model() is not None: + # Pulisce la tabella + self.tableWidget.clear() + self.tableWidget.setRowCount(0) + + self.tableWidget.setColumnCount(2) + headerLabels = [] + headerLabels.append(QadMsg.translate("DimStyle_Diff_Dialog", "Description")) + headerLabels.append(dimStyle.name) + self.tableWidget.setHorizontalHeaderLabels(headerLabels) + self.tableWidget.horizontalHeader().show() + + self.count = 0 + propsDict = dimStyle.getPropList().items() + for prop in propsDict: + propName = prop[0] + propDescr = prop[1][0] + propValue = prop[1][1] + self.insertProp(propDescr, propValue) + + self.tableWidget.sortItems(0) + + self.tableWidget.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeToContents) + self.tableWidget.horizontalHeader().setSectionResizeMode(1, QHeaderView.Interactive) + + self.msg.setText(QadMsg.translate("DimStyle_Diff_Dialog", "All properties of dimension style: ") + dimStyle.name) + + + def showDiffProps(self, dimStyle1, dimStyle2): + if self.tableWidget.model() is not None: + # Pulisce la tabella + self.tableWidget.clear() + self.tableWidget.setRowCount(0) + + self.tableWidget.setColumnCount(3) + headerLabels = [] + headerLabels.append(QadMsg.translate("DimStyle_Diff_Dialog", "Description")) + headerLabels.append(dimStyle1.name) + headerLabels.append(dimStyle2.name) + self.tableWidget.setHorizontalHeaderLabels(headerLabels) + self.tableWidget.horizontalHeader().show() + + self.count = 0 + prop1Items = dimStyle1.getPropList().items() # lista di nome con lista [descrizione, valore] + props2Dict = dimStyle2.getPropList() # dizionario di nome con lista [descrizione, valore] + for prop1 in prop1Items: + propName = prop1[0] + propDescr = prop1[1][0] + prop1Value = prop1[1][1] + prop2 = props2Dict[propName] + prop2Value = prop2[1] + if prop1Value is None: + prop1Value = "" + if prop2Value is None: + prop2Value = "" + if unicode(prop1Value) != unicode(prop2Value): + self.insertProp(propDescr, prop1Value, prop2Value) + + self.tableWidget.sortItems(0) + + self.tableWidget.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeToContents) + self.tableWidget.horizontalHeader().setSectionResizeMode(1, QHeaderView.Interactive) + self.tableWidget.horizontalHeader().setSectionResizeMode(2, QHeaderView.Interactive) + + self.msg.setText(QadMsg.translate("DimStyle_Diff_Dialog", "Found {0} differences: ").format(str(self.count))) + + + def insertProp(self, description, val1, val2 = None): + self.tableWidget.insertRow(self.count) + + item = QTableWidgetItem(unicode(description)) + item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) + self.tableWidget.setItem(self.count, 0, item) + + item = QTableWidgetItem(unicode(val1)) + item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) + self.tableWidget.setItem(self.count, 1, item) + + if val2 is not None: + item = QTableWidgetItem(unicode(val2)) + item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) + self.tableWidget.setItem(self.count, 2, item) + self.count += 1 + + + def ButtonHELP_Pressed(self): + qadShowPluginPDFHelp(QadMsg.translate("Help", "Dimensioning")) + + def resizeEvent(self, event): + QDialog.resizeEvent(self, event) + if event.oldSize().width() == -1: # non c'era una dimensione precedente + return + tableWidgetSize = self.tableWidget.size() + newWidth = tableWidgetSize.width() + event.size().width() - event.oldSize().width() + newHeight = tableWidgetSize.height() + event.size().height() - event.oldSize().height() + tableWidgetSize.setWidth(newWidth) + tableWidgetSize.setHeight(newHeight) + self.tableWidget.resize(tableWidgetSize) + + + def copyToClipboard(self): + buffer = "" + + # intestazione + for col in range(0, self.tableWidget.columnCount(), 1): + if col > 0: + buffer += '\t' # aggiungo un TAB + buffer += self.tableWidget.horizontalHeaderItem(col).text() + buffer += '\n' # vado a capo + + # valori delle righe + for row in range(0, self.tableWidget.rowCount(), 1): + for col in range(0, self.tableWidget.columnCount(), 1): + if col > 0: + buffer += '\t' # aggiungo un TAB + buffer += self.tableWidget.item(row, col).text() + buffer += '\n' # vado a capo + + QApplication.clipboard().setText(buffer) \ No newline at end of file diff --git a/qad_dimstyle_diff_ui.py b/qad_dimstyle_diff_ui.py index 748818ac..b8d499d7 100644 --- a/qad_dimstyle_diff_ui.py +++ b/qad_dimstyle_diff_ui.py @@ -1,94 +1,84 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file 'qad_dimstyle_diff.ui' -# -# Created: Tue Sep 08 15:55:22 2015 -# by: PyQt4 UI code generator 4.10.2 -# -# WARNING! All changes made in this file will be lost! - -from PyQt4 import QtCore, QtGui - -try: - _fromUtf8 = QtCore.QString.fromUtf8 -except AttributeError: - def _fromUtf8(s): - return s - -try: - _encoding = QtGui.QApplication.UnicodeUTF8 - def _translate(context, text, disambig): - return QtGui.QApplication.translate(context, text, disambig, _encoding) -except AttributeError: - def _translate(context, text, disambig): - return QtGui.QApplication.translate(context, text, disambig) - -class Ui_DimStyle_Diff_Dialog(object): - def setupUi(self, DimStyle_Diff_Dialog): - DimStyle_Diff_Dialog.setObjectName(_fromUtf8("DimStyle_Diff_Dialog")) - DimStyle_Diff_Dialog.resize(443, 526) - self.label = QtGui.QLabel(DimStyle_Diff_Dialog) - self.label.setGeometry(QtCore.QRect(10, 10, 71, 21)) - self.label.setObjectName(_fromUtf8("label")) - self.label_2 = QtGui.QLabel(DimStyle_Diff_Dialog) - self.label_2.setGeometry(QtCore.QRect(10, 40, 71, 21)) - self.label_2.setObjectName(_fromUtf8("label_2")) - self.dimStyle1 = QtGui.QComboBox(DimStyle_Diff_Dialog) - self.dimStyle1.setGeometry(QtCore.QRect(80, 10, 211, 22)) - self.dimStyle1.setObjectName(_fromUtf8("dimStyle1")) - self.dimStyle2 = QtGui.QComboBox(DimStyle_Diff_Dialog) - self.dimStyle2.setGeometry(QtCore.QRect(80, 40, 211, 22)) - self.dimStyle2.setObjectName(_fromUtf8("dimStyle2")) - self.line = QtGui.QFrame(DimStyle_Diff_Dialog) - self.line.setGeometry(QtCore.QRect(10, 70, 421, 16)) - self.line.setFrameShape(QtGui.QFrame.HLine) - self.line.setFrameShadow(QtGui.QFrame.Sunken) - self.line.setObjectName(_fromUtf8("line")) - self.msg = QtGui.QLabel(DimStyle_Diff_Dialog) - self.msg.setGeometry(QtCore.QRect(10, 80, 321, 21)) - self.msg.setObjectName(_fromUtf8("msg")) - self.layoutWidget = QtGui.QWidget(DimStyle_Diff_Dialog) - self.layoutWidget.setGeometry(QtCore.QRect(277, 490, 158, 25)) - self.layoutWidget.setObjectName(_fromUtf8("layoutWidget")) - self.horizontalLayout = QtGui.QHBoxLayout(self.layoutWidget) - self.horizontalLayout.setMargin(0) - self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) - self.closeButton = QtGui.QPushButton(self.layoutWidget) - self.closeButton.setObjectName(_fromUtf8("closeButton")) - self.horizontalLayout.addWidget(self.closeButton) - self.helpButton = QtGui.QPushButton(self.layoutWidget) - self.helpButton.setObjectName(_fromUtf8("helpButton")) - self.horizontalLayout.addWidget(self.helpButton) - self.tableWidget = QtGui.QTableWidget(DimStyle_Diff_Dialog) - self.tableWidget.setGeometry(QtCore.QRect(10, 110, 421, 371)) - self.tableWidget.setObjectName(_fromUtf8("tableWidget")) - self.tableWidget.setColumnCount(0) - self.tableWidget.setRowCount(0) - self.copyButton = QtGui.QPushButton(DimStyle_Diff_Dialog) - self.copyButton.setGeometry(QtCore.QRect(404, 80, 31, 23)) - self.copyButton.setText(_fromUtf8("")) - icon = QtGui.QIcon() - icon.addPixmap(QtGui.QPixmap(_fromUtf8(":/plugins/qad/icons/copy.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) - self.copyButton.setIcon(icon) - self.copyButton.setObjectName(_fromUtf8("copyButton")) - - self.retranslateUi(DimStyle_Diff_Dialog) - QtCore.QObject.connect(self.helpButton, QtCore.SIGNAL(_fromUtf8("clicked()")), DimStyle_Diff_Dialog.ButtonHELP_Pressed) - QtCore.QObject.connect(self.dimStyle1, QtCore.SIGNAL(_fromUtf8("currentIndexChanged(int)")), DimStyle_Diff_Dialog.DimStyleName1Changed) - QtCore.QObject.connect(self.dimStyle2, QtCore.SIGNAL(_fromUtf8("currentIndexChanged(int)")), DimStyle_Diff_Dialog.DimStyleName2Changed) - QtCore.QObject.connect(self.copyButton, QtCore.SIGNAL(_fromUtf8("clicked()")), DimStyle_Diff_Dialog.copyToClipboard) - QtCore.QObject.connect(self.closeButton, QtCore.SIGNAL(_fromUtf8("clicked()")), DimStyle_Diff_Dialog.accept) - QtCore.QMetaObject.connectSlotsByName(DimStyle_Diff_Dialog) - - def retranslateUi(self, DimStyle_Diff_Dialog): - DimStyle_Diff_Dialog.setWindowTitle(_translate("DimStyle_Diff_Dialog", "QAD - Compare dimension styles", None)) - self.label.setText(_translate("DimStyle_Diff_Dialog", "Compare:", None)) - self.label_2.setText(_translate("DimStyle_Diff_Dialog", "With:", None)) - self.dimStyle1.setToolTip(_translate("DimStyle_Diff_Dialog", "Specify the first dimension style.", None)) - self.dimStyle2.setToolTip(_translate("DimStyle_Diff_Dialog", "Specify the second dimension style. If you set the second style as the first, all dimension style properties will displayed.", None)) - self.msg.setText(_translate("DimStyle_Diff_Dialog", "TextLabel", None)) - self.closeButton.setText(_translate("DimStyle_Diff_Dialog", "Close", None)) - self.helpButton.setText(_translate("DimStyle_Diff_Dialog", "?", None)) - self.tableWidget.setToolTip(_translate("DimStyle_Diff_Dialog", "

      Display the result of comparing dimension styles.If you compare two different styles, the settings that are different between the two dimension styles, their current settings, and brief descriptions are listed. If you set the second style as the first, all dimension style properties will displayed.

      ", None)) - self.copyButton.setToolTip(_translate("DimStyle_Diff_Dialog", "Copy the result of comparing into the clipboard.", None)) - +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file '.\qad_dimstyle_diff.ui' +# +# Created by: PyQt5 UI code generator 5.15.4 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_DimStyle_Diff_Dialog(object): + def setupUi(self, DimStyle_Diff_Dialog): + DimStyle_Diff_Dialog.setObjectName("DimStyle_Diff_Dialog") + DimStyle_Diff_Dialog.resize(443, 526) + DimStyle_Diff_Dialog.setMinimumSize(QtCore.QSize(443, 526)) + DimStyle_Diff_Dialog.setMaximumSize(QtCore.QSize(443, 526)) + self.label = QtWidgets.QLabel(DimStyle_Diff_Dialog) + self.label.setGeometry(QtCore.QRect(10, 10, 81, 21)) + self.label.setObjectName("label") + self.label_2 = QtWidgets.QLabel(DimStyle_Diff_Dialog) + self.label_2.setGeometry(QtCore.QRect(10, 40, 81, 21)) + self.label_2.setObjectName("label_2") + self.dimStyle1 = QtWidgets.QComboBox(DimStyle_Diff_Dialog) + self.dimStyle1.setGeometry(QtCore.QRect(100, 10, 211, 22)) + self.dimStyle1.setObjectName("dimStyle1") + self.dimStyle2 = QtWidgets.QComboBox(DimStyle_Diff_Dialog) + self.dimStyle2.setGeometry(QtCore.QRect(100, 40, 211, 22)) + self.dimStyle2.setObjectName("dimStyle2") + self.line = QtWidgets.QFrame(DimStyle_Diff_Dialog) + self.line.setGeometry(QtCore.QRect(10, 70, 421, 16)) + self.line.setFrameShape(QtWidgets.QFrame.HLine) + self.line.setFrameShadow(QtWidgets.QFrame.Sunken) + self.line.setObjectName("line") + self.msg = QtWidgets.QLabel(DimStyle_Diff_Dialog) + self.msg.setGeometry(QtCore.QRect(10, 80, 381, 21)) + self.msg.setObjectName("msg") + self.layoutWidget = QtWidgets.QWidget(DimStyle_Diff_Dialog) + self.layoutWidget.setGeometry(QtCore.QRect(277, 490, 158, 25)) + self.layoutWidget.setObjectName("layoutWidget") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.layoutWidget) + self.horizontalLayout.setContentsMargins(0, 0, 0, 0) + self.horizontalLayout.setObjectName("horizontalLayout") + self.closeButton = QtWidgets.QPushButton(self.layoutWidget) + self.closeButton.setObjectName("closeButton") + self.horizontalLayout.addWidget(self.closeButton) + self.helpButton = QtWidgets.QPushButton(self.layoutWidget) + self.helpButton.setObjectName("helpButton") + self.horizontalLayout.addWidget(self.helpButton) + self.tableWidget = QtWidgets.QTableWidget(DimStyle_Diff_Dialog) + self.tableWidget.setGeometry(QtCore.QRect(10, 110, 421, 371)) + self.tableWidget.setObjectName("tableWidget") + self.tableWidget.setColumnCount(0) + self.tableWidget.setRowCount(0) + self.copyButton = QtWidgets.QPushButton(DimStyle_Diff_Dialog) + self.copyButton.setGeometry(QtCore.QRect(404, 80, 31, 23)) + self.copyButton.setText("") + icon = QtGui.QIcon() + icon.addPixmap(QtGui.QPixmap(":/plugins/qad/icons/copy.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.copyButton.setIcon(icon) + self.copyButton.setObjectName("copyButton") + + self.retranslateUi(DimStyle_Diff_Dialog) + self.helpButton.clicked.connect(DimStyle_Diff_Dialog.ButtonHELP_Pressed) + self.dimStyle1.currentIndexChanged['int'].connect(DimStyle_Diff_Dialog.DimStyleName1Changed) + self.dimStyle2.currentIndexChanged['int'].connect(DimStyle_Diff_Dialog.DimStyleName2Changed) + self.copyButton.clicked.connect(DimStyle_Diff_Dialog.copyToClipboard) + self.closeButton.clicked.connect(DimStyle_Diff_Dialog.accept) + QtCore.QMetaObject.connectSlotsByName(DimStyle_Diff_Dialog) + + def retranslateUi(self, DimStyle_Diff_Dialog): + _translate = QtCore.QCoreApplication.translate + DimStyle_Diff_Dialog.setWindowTitle(_translate("DimStyle_Diff_Dialog", "Compare dimension styles")) + self.label.setText(_translate("DimStyle_Diff_Dialog", "Compare:")) + self.label_2.setText(_translate("DimStyle_Diff_Dialog", "With:")) + self.dimStyle1.setToolTip(_translate("DimStyle_Diff_Dialog", "Specify the first dimension style.")) + self.dimStyle2.setToolTip(_translate("DimStyle_Diff_Dialog", "Specify the second dimension style. If you set the second style as the first, all dimension style properties will displayed.")) + self.msg.setText(_translate("DimStyle_Diff_Dialog", "TextLabel")) + self.closeButton.setText(_translate("DimStyle_Diff_Dialog", "Close")) + self.helpButton.setText(_translate("DimStyle_Diff_Dialog", "?")) + self.tableWidget.setToolTip(_translate("DimStyle_Diff_Dialog", "

      Display the result of comparing dimension styles.If you compare two different styles, the settings that are different between the two dimension styles, their current settings, and brief descriptions are listed. If you set the second style as the first, all dimension style properties will displayed.

      ")) + self.copyButton.setToolTip(_translate("DimStyle_Diff_Dialog", "Copy the result of comparing into the clipboard.")) diff --git a/qad_dimstyle_dlg.py b/qad_dimstyle_dlg.py index e006330c..5832f2e6 100644 --- a/qad_dimstyle_dlg.py +++ b/qad_dimstyle_dlg.py @@ -1,270 +1,288 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - classe per gestire la dialog per DIMSTYLE - - ------------------- - begin : 2015-05-19 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -from qgis.core import QgsApplication -from qgis.utils import * - -import qad_dimstyle_ui - -from qad_variables import * -from qad_dim import * -from qad_msg import QadMsg, qadShowPluginHelp -from qad_dimstyle_new_dlg import QadDIMSTYLE_NEW_Dialog -from qad_dimstyle_details_dlg import QadDIMSTYLE_DETAILS_Dialog, QadPreviewDim -from qad_dimstyle_diff_dlg import QadDIMSTYLE_DIFF_Dialog -import qad_utils - - -####################################################################################### -# Classe che gestisce l'interfaccia grafica del comando DIMSTYLE -class QadDIMSTYLEDialog(QDialog, QObject, qad_dimstyle_ui.Ui_DimStyle_Dialog): - def __init__(self, plugIn): - self.plugIn = plugIn - self.iface = self.plugIn.iface.mainWindow() - QDialog.__init__(self, self.iface) - - self.selectedDimStyle = None - - self.setupUi(self) - self.retranslateUi(self) # aggiungo alcune traduzioni personalizzate - self.dimStyleList.setContextMenuPolicy(Qt.CustomContextMenu) - - # aggiungo il canvans di preview della quota chiamato QadPreviewDim - # che eredita la posizione di previewDummy (che viene nascosto) - self.previewDummy.setHidden(True) - self.previewDim = QadPreviewDim(self.previewDummy.parent(), self.plugIn) - self.previewDim.setGeometry(self.previewDummy.geometry()) - self.previewDim.setObjectName("previewDim") - - self.init() - - - def closeEvent(self, event): - del self.previewDim # cancello il canvans di preview della quota chiamato QadPreviewDim - return QDialog.closeEvent(self, event) - - def init(self): - # Inizializzazione dello stile corrente - currDimStyleName = QadVariables.get(QadMsg.translate("Environment variables", "DIMSTYLE")) - self.currentDimStyle.setText("" if currDimStyleName is None else currDimStyleName) - - # Inizializzazione della lista degli stili - model = QStandardItemModel(self.dimStyleList) - for dimStyle in QadDimStyles.dimStyleList: # lista degli stili di quotatura caricati - # Create an item with a caption - item = QStandardItem(dimStyle.name) - item.setEditable(True) - item.setData(dimStyle) - model.appendRow(item) - - self.dimStyleList.setModel(model) - # sort - self.dimStyleList.model().sort(0) - # collego l'evento "cambio di selezione" alla funzione dimStyleListCurrentChanged - self.dimStyleList.selectionModel().selectionChanged.connect(self.dimStyleListCurrentChanged) - - self.dimStyleList.itemDelegate().closeEditor.connect(self.dimStyleListcloseEditor) - - # seleziono il primo elemento della lista - index = self.dimStyleList.model().index(0,0) - if self.selectedDimStyle is not None: - # seleziono l'elemento precedentemente selezionato - item = self.dimStyleList.model().findItems(self.selectedDimStyle.name)[0] - index = self.dimStyleList.model().indexFromItem(item) - elif len(currDimStyleName) > 0: - item = self.dimStyleList.model().findItems(currDimStyleName)[0] - if item is not None: - index = self.dimStyleList.model().indexFromItem(item) - - self.dimStyleList.selectionModel().setCurrentIndex(index, QItemSelectionModel.SelectCurrent) - - - def retranslateUi(self, DimStyle_Dialog): - qad_dimstyle_ui.Ui_DimStyle_Dialog.retranslateUi(self, self) - # "none" viene tradotto in italiano in "nessuno" nel contesto "currentDimStyle" - # "none" viene tradotto in italiano in "nessuna" nel contesto "descriptionSelectedStyle" - # "none" viene tradotto in italiano in "nessuno" nel contesto "selectedStyle" - self.currentDimStyle.setText(QadMsg.translate("DimStyle_Dialog", "none", "currentDimStyle")) - self.descriptionSelectedStyle.setText(QadMsg.translate("DimStyle_Dialog", "none", "descriptionSelectedStyle")) - self.selectedStyle.setText(QadMsg.translate("DimStyle_Dialog", "none", "selectedStyle")) - - - def dimStyleListCurrentChanged(self, current, previous): - # leggo l'elemento selezionato - index = current.indexes()[0] - item = self.dimStyleList.model().itemFromIndex(index) - self.selectedDimStyle = item.data() - self.selectedStyle.setText(self.selectedDimStyle.name) - self.descriptionSelectedStyle.setText(self.selectedDimStyle.description) - - self.previewDim.drawDim(self.selectedDimStyle) - - def dimStyleListcloseEditor(self, editor, hint): - self.renSelectedDimStyle(editor.text()) - - def setCurrentStyle(self): - if self.selectedDimStyle is None: - return - QadVariables.set(QadMsg.translate("Environment variables", "DIMSTYLE"), self.selectedDimStyle.name) - QadVariables.save() - self.currentDimStyle.setText(self.selectedDimStyle.name) - - def renSelectedDimStyle(self, newName): - if self.selectedDimStyle is None: - return - if QadDimStyles.renameDimStyle(self.selectedDimStyle.name, newName) == False: - QMessageBox.critical(self, QadMsg.translate("QAD", "QAD"), \ - QadMsg.translate("DimStyle_Dialog", "Dimension style not renamed.")) - else: - self.init() - - def updDescrSelectedDimStyle(self): - if self.selectedDimStyle is None: - return - Title = QadMsg.translate("DimStyle_Dialog", "QAD - Editing dimension style description: ") + self.selectedDimStyle.name - inputDlg = QInputDialog(self) - inputDlg.setWindowTitle(Title) - inputDlg.setInputMode(QInputDialog.TextInput) - inputDlg.setLabelText(QadMsg.translate("DimStyle_Dialog", "New description:")) - inputDlg.setTextValue(self.selectedDimStyle.description) - inputDlg.resize(600,100) - if inputDlg.exec_(): - self.selectedDimStyle.description = inputDlg.textValue() - self.selectedDimStyle.save() - self.init() - - def delSelectedDimStyle(self): - if self.selectedDimStyle is None: - return - msg = QadMsg.translate("DimStyle_Dialog", "Remove dimension style {0} ?").format(self.selectedDimStyle.name) - res = QMessageBox.question(self, QadMsg.translate("QAD", "QAD"), msg, \ - QMessageBox.Yes | QMessageBox.No) - if res == QMessageBox.Yes: - if QadDimStyles.removeDimStyle(self.selectedDimStyle.name, True) == False: - QMessageBox.critical(self, QadMsg.translate("QAD", "QAD"), \ - QadMsg.translate("DimStyle_Dialog", "Dimension style not removed.")) - else: - self.selectedDimStyle = None - self.init() - - def createNewStyle(self): - self.previewDim.eraseDim() - - Form = QadDIMSTYLE_NEW_Dialog(self.plugIn, self.selectedDimStyle.name if self.selectedDimStyle is not None else None) - if Form.exec_() == QDialog.Accepted: - Form.dimStyle.path = "" - QadDimStyles.addDimStyle(Form.dimStyle, True) - self.selectedDimStyle = QadDimStyles.findDimStyle(Form.dimStyle.name) - # setto lo stile corrente - QadVariables.set(QadMsg.translate("Environment variables", "DIMSTYLE"), self.selectedDimStyle.name) - self.init() - - self.previewDim.drawDim(self.selectedDimStyle) - - def modStyle(self): - if self.selectedDimStyle is None: - return - self.previewDim.eraseDim() - - Form = QadDIMSTYLE_DETAILS_Dialog(self.plugIn, self.selectedDimStyle) - title = QadMsg.translate("DimStyle_Dialog", "Modify dimension style: ") + self.selectedDimStyle.name - Form.setWindowTitle(title) - if Form.exec_() == QDialog.Accepted: - self.selectedDimStyle.set(Form.dimStyle) - self.selectedDimStyle.save() - self.init() - del Form # forzo la chiamata al distruttore per rimuovere il preview della quota - - self.previewDim.drawDim(self.selectedDimStyle) - - - def temporaryModStyle(self): - if self.selectedDimStyle is None: - return - self.previewDim.eraseDim() - - Form = QadDIMSTYLE_DETAILS_Dialog(self.plugIn, self.selectedDimStyle) - title = QadMsg.translate("DimStyle_Dialog", "Set temporary overrides to dimension style: ") + self.selectedDimStyle.name - Form.setWindowTitle(title) - if Form.exec_() == QDialog.Accepted: - self.selectedDimStyle.set(Form.dimStyle) - self.init() - - self.previewDim.drawDim(self.selectedDimStyle) - - - def showDiffBetweenStyles(self): - Form = QadDIMSTYLE_DIFF_Dialog(self.plugIn, self.selectedDimStyle.name) - Form.exec_() - - def startEditingItem(self): - if self.selectedDimStyle is None: - return - item = self.dimStyleList.model().findItems(self.selectedDimStyle.name)[0] - index = self.dimStyleList.model().indexFromItem(item) - self.dimStyleList.edit(index) - - def ButtonBOX_Accepted(self): - self.close() - return True - - - def ButtonHELP_Pressed(self): - qadShowPluginHelp(QadMsg.translate("Help", "Dimensioning")) - - #============================================================================ - # displayPopupMenu - #============================================================================ - def displayPopupMenu(self, pos): - if self.selectedDimStyle is None: - return - - popupMenu = QMenu(self) - action = QAction(QadMsg.translate("DimStyle_Dialog", "Set current"), popupMenu) - popupMenu.addAction(action) - QObject.connect(action, SIGNAL("triggered()"), self.setCurrentStyle) - - action = QAction(QadMsg.translate("DimStyle_Dialog", "Rename"), popupMenu) - popupMenu.addAction(action) - QObject.connect(action, SIGNAL("triggered()"), self.startEditingItem) - - action = QAction(QadMsg.translate("DimStyle_Dialog", "Modify description"), popupMenu) - popupMenu.addAction(action) - QObject.connect(action, SIGNAL("triggered()"), self.updDescrSelectedDimStyle) - - action = QAction(QadMsg.translate("DimStyle_Dialog", "Remove"), popupMenu) - currDimStyleName = QadVariables.get(QadMsg.translate("Environment variables", "DIMSTYLE")) - if self.selectedDimStyle.name == currDimStyleName: - action.setDisabled(True) - popupMenu.addAction(action) - QObject.connect(action, SIGNAL("triggered()"), self.delSelectedDimStyle) - - popupMenu.popup(self.dimStyleList.mapToGlobal(pos)) - - \ No newline at end of file +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + classe per gestire la dialog per DIMSTYLE + + ------------------- + begin : 2015-05-19 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.PyQt.QtCore import Qt, QObject, QItemSelectionModel +from qgis.PyQt.QtGui import * +from qgis.PyQt.QtWidgets import QDialog, QMessageBox, QInputDialog, QMenu, QAction + + +from . import qad_dimstyle_ui +from .qad_variables import QadVariables +from .qad_dim import QadDimStyles +from .qad_msg import QadMsg, qadShowPluginPDFHelp +from .qad_dimstyle_new_dlg import QadDIMSTYLE_NEW_Dialog +from .qad_dimstyle_details_dlg import QadDIMSTYLE_DETAILS_Dialog, QadPreviewDim +from .qad_dimstyle_diff_dlg import QadDIMSTYLE_DIFF_Dialog +from . import qad_utils + + +####################################################################################### +# Classe che gestisce l'interfaccia grafica del comando DIMSTYLE +class QadDIMSTYLEDialog(QDialog, QObject, qad_dimstyle_ui.Ui_DimStyle_Dialog): + def __init__(self, plugIn): + self.plugIn = plugIn + self.iface = self.plugIn.iface.mainWindow() + + QDialog.__init__(self) + # non passo il parent perchè altrimenti il font e la sua dimensione verrebbero ereditati dalla dialog scombinando tutto + #QDialog.__init__(self, self.iface) + + self.selectedDimStyle = None + + self.setupUi(self) + self.retranslateUi(self) # aggiungo alcune traduzioni personalizzate + self.setWindowTitle(QadMsg.getQADTitle() + " - " + self.windowTitle()) + + self.dimStyleList.setContextMenuPolicy(Qt.CustomContextMenu) + + # aggiungo il canvans di preview della quota chiamato QadPreviewDim + # che eredita la posizione di previewDummy (che viene nascosto) + self.previewDummy.setHidden(True) + self.previewDim = QadPreviewDim(self.previewDummy.parent(), self.plugIn) + self.previewDim.setGeometry(self.previewDummy.geometry()) + self.previewDim.setObjectName("previewDim") + + self.init() + + + def closeEvent(self, event): + del self.previewDim # cancello il canvans di preview della quota chiamato QadPreviewDim + return QDialog.closeEvent(self, event) + + def init(self): + # Inizializzazione dello stile corrente + currDimStyleName = QadVariables.get(QadMsg.translate("Environment variables", "DIMSTYLE")) + self.currentDimStyle.setText("" if currDimStyleName is None else currDimStyleName) + + # Inizializzazione della lista degli stili + model = QStandardItemModel(self.dimStyleList) + for dimStyle in QadDimStyles.dimStyleList: # lista degli stili di quotatura caricati + # Create an item with a caption + item = QStandardItem(dimStyle.name) + item.setEditable(True) + item.setData(dimStyle) + model.appendRow(item) + + self.dimStyleList.setModel(model) + # sort + self.dimStyleList.model().sort(0) + # collego l'evento "cambio di selezione" alla funzione dimStyleListCurrentChanged + self.dimStyleList.selectionModel().selectionChanged.connect(self.dimStyleListCurrentChanged) + + self.dimStyleList.itemDelegate().closeEditor.connect(self.dimStyleListcloseEditor) + + # seleziono il primo elemento della lista + index = self.dimStyleList.model().index(0,0) + items = None + if self.selectedDimStyle is not None: + # seleziono l'elemento precedentemente selezionato + items = self.dimStyleList.model().findItems(self.selectedDimStyle.name) + elif len(currDimStyleName) > 0: + items = self.dimStyleList.model().findItems(currDimStyleName) + + if (items is not None) and len(items) > 0: + item = items[0] + if item is not None: + index = self.dimStyleList.model().indexFromItem(item) + + self.dimStyleList.selectionModel().setCurrentIndex(index, QItemSelectionModel.SelectCurrent) + + + def retranslateUi(self, DimStyle_Dialog): + qad_dimstyle_ui.Ui_DimStyle_Dialog.retranslateUi(self, self) + # "none" viene tradotto in italiano in "nessuno" nel contesto "currentDimStyle" + # "none" viene tradotto in italiano in "nessuna" nel contesto "descriptionSelectedStyle" + # "none" viene tradotto in italiano in "nessuno" nel contesto "selectedStyle" + self.currentDimStyle.setText(QadMsg.translate("DimStyle_Dialog", "none", "currentDimStyle")) + self.descriptionSelectedStyle.setText(QadMsg.translate("DimStyle_Dialog", "none", "descriptionSelectedStyle")) + self.selectedStyle.setText(QadMsg.translate("DimStyle_Dialog", "none", "selectedStyle")) + + + def dimStyleListCurrentChanged(self, current, previous): + # leggo l'elemento selezionato + index = current.indexes()[0] + item = self.dimStyleList.model().itemFromIndex(index) + self.selectedDimStyle = item.data() + self.selectedStyle.setText(self.selectedDimStyle.name) + self.descriptionSelectedStyle.setText(self.selectedDimStyle.description) + + self.previewDim.drawDim(self.selectedDimStyle) + + + def dimStyleListcloseEditor(self, editor, hint): + self.renSelectedDimStyle(editor.text()) + + def setCurrentStyle(self): + if self.selectedDimStyle is None: + return + QadVariables.set(QadMsg.translate("Environment variables", "DIMSTYLE"), self.selectedDimStyle.name) + QadVariables.save() + self.currentDimStyle.setText(self.selectedDimStyle.name) + + def renSelectedDimStyle(self, newName): + if self.selectedDimStyle is None: + return + if QadDimStyles.renameDimStyle(self.selectedDimStyle.name, newName) == False: + QMessageBox.critical(self, QadMsg.getQADTitle(), \ + QadMsg.translate("DimStyle_Dialog", "Dimension style not renamed.")) + else: + self.init() + + def updDescrSelectedDimStyle(self): + if self.selectedDimStyle is None: + return + title = QadMsg.translate("DimStyle_Dialog", "Editing dimension style description: ") + self.selectedDimStyle.name + inputDlg = QInputDialog(self) + inputDlg.setWindowTitle(QadMsg.getQADTitle() + " - " + title) + inputDlg.setInputMode(QInputDialog.TextInput) + inputDlg.setLabelText(QadMsg.translate("DimStyle_Dialog", "New description:")) + inputDlg.setTextValue(self.selectedDimStyle.description) + inputDlg.resize(600,100) + if inputDlg.exec_(): + self.selectedDimStyle.description = inputDlg.textValue() + self.selectedDimStyle.save() + self.init() + + def delSelectedDimStyle(self): + if self.selectedDimStyle is None: + return + msg = QadMsg.translate("DimStyle_Dialog", "Remove dimension style {0} ?").format(self.selectedDimStyle.name) + res = QMessageBox.question(self, QadMsg.getQADTitle(), msg, \ + QMessageBox.Yes | QMessageBox.No) + if res == QMessageBox.Yes: + if QadDimStyles.removeDimStyle(self.selectedDimStyle.name, True) == False: + QMessageBox.critical(self, QadMsg.getQADTitle(), \ + QadMsg.translate("DimStyle_Dialog", "Dimension style not removed.")) + else: + self.selectedDimStyle = None + self.init() + + def createNewStyle(self): + self.previewDim.eraseDim() + + Form = QadDIMSTYLE_NEW_Dialog(self.plugIn, self, self.selectedDimStyle.name if self.selectedDimStyle is not None else None) + if Form.exec_() == QDialog.Accepted: + Form.dimStyle.path = "" + QadDimStyles.addDimStyle(Form.dimStyle, True) + self.selectedDimStyle = QadDimStyles.findDimStyle(Form.dimStyle.name) + # setto lo stile corrente + QadVariables.set(QadMsg.translate("Environment variables", "DIMSTYLE"), self.selectedDimStyle.name) + self.init() + + self.previewDim.drawDim(self.selectedDimStyle) + + def modStyle(self): + if self.selectedDimStyle is None: + return + self.previewDim.eraseDim() + + Form = QadDIMSTYLE_DETAILS_Dialog(self.plugIn, self, self.selectedDimStyle) + title = QadMsg.translate("DimStyle_Dialog", "Modify dimension style: ") + self.selectedDimStyle.name + Form.setWindowTitle(QadMsg.getQADTitle() + " - " + title) + if Form.exec_() == QDialog.Accepted: + self.selectedDimStyle.set(Form.dimStyle) + self.selectedDimStyle.save() + self.init() + del Form # forzo la chiamata al distruttore per rimuovere il preview della quota + + self.previewDim.drawDim(self.selectedDimStyle) + + + def temporaryModStyle(self): + if self.selectedDimStyle is None: + return + self.previewDim.eraseDim() + + Form = QadDIMSTYLE_DETAILS_Dialog(self.plugIn, self, self.selectedDimStyle) + title = QadMsg.translate("DimStyle_Dialog", "Set temporary overrides to dimension style: ") + self.selectedDimStyle.name + Form.setWindowTitle(QadMsg.getQADTitle() + " - " + title) + if Form.exec_() == QDialog.Accepted: + self.selectedDimStyle.set(Form.dimStyle) + self.init() + + self.previewDim.drawDim(self.selectedDimStyle) + + + def showDiffBetweenStyles(self): + if self.selectedDimStyle is None: + return + Form = QadDIMSTYLE_DIFF_Dialog(self.plugIn, self, self.selectedDimStyle.name) + Form.exec_() + + + # ============================================================================ + # startEditingItem + # ============================================================================ + def startEditingItem(self): + if self.selectedDimStyle is None: + return + + items = self.dimStyleList.model().findItems(self.selectedDimStyle.name) + if len(items) > 0: + item = items[0] + if item is not None: + index = self.dimStyleList.model().indexFromItem(item) + self.dimStyleList.edit(index) + + + def ButtonBOX_Accepted(self): + QDialog.accept(self) + + + def ButtonHELP_Pressed(self): + qadShowPluginPDFHelp(QadMsg.translate("Help", "Dimensioning")) + + + # ============================================================================ + # displayPopupMenu + # ============================================================================ + def displayPopupMenu(self, pos): + if self.selectedDimStyle is None: + return + + popupMenu = QMenu(self) + action = QAction(QadMsg.translate("DimStyle_Dialog", "Set current"), popupMenu) + popupMenu.addAction(action) + action.triggered.connect(self.setCurrentStyle) + + action = QAction(QadMsg.translate("DimStyle_Dialog", "Rename"), popupMenu) + popupMenu.addAction(action) + action.triggered.connect(self.startEditingItem) + + action = QAction(QadMsg.translate("DimStyle_Dialog", "Modify description"), popupMenu) + popupMenu.addAction(action) + action.triggered.connect(self.updDescrSelectedDimStyle) + + action = QAction(QadMsg.translate("DimStyle_Dialog", "Remove"), popupMenu) + currDimStyleName = QadVariables.get(QadMsg.translate("Environment variables", "DIMSTYLE")) + if self.selectedDimStyle.name == currDimStyleName: + action.setDisabled(True) + popupMenu.addAction(action) + action.triggered.connect(self.delSelectedDimStyle) + + popupMenu.popup(self.dimStyleList.mapToGlobal(pos)) + + diff --git a/qad_dimstyle_new.ui b/qad_dimstyle_new.ui index 85b9d5ba..44a811ee 100644 --- a/qad_dimstyle_new.ui +++ b/qad_dimstyle_new.ui @@ -1,214 +1,226 @@ - - - DimStyle_New_Dialog - - - - 0 - 0 - 372 - 142 - - - - QAD - Create new dimension style - - - - - 10 - 10 - 221 - 16 - - - - New style name: - - - - - - 10 - 30 - 221 - 20 - - - - - - - 10 - 90 - 221 - 16 - - - - Start with: - - - - - - 10 - 110 - 221 - 22 - - - - - - - 290 - 50 - 75 - 23 - - - - Continue... - - - - - - 290 - 80 - 75 - 23 - - - - Cancel - - - - - - 290 - 110 - 75 - 23 - - - - ? - - - - - - 10 - 50 - 221 - 16 - - - - Description: - - - - - - 10 - 70 - 221 - 20 - - - - - - - - DimStyleNameFrom - currentIndexChanged(int) - DimStyle_New_Dialog - DimStyleNameFromChanged() - - - 101 - 119 - - - 253 - 122 - - - - - newDimStyleName - textEdited(QString) - DimStyle_New_Dialog - newStyleNameChanged() - - - 70 - 36 - - - 277 - 26 - - - - - cancelButton - clicked() - DimStyle_New_Dialog - reject() - - - 321 - 91 - - - 256 - 89 - - - - - helpButton - clicked() - DimStyle_New_Dialog - ButtonHELP_Pressed() - - - 333 - 110 - - - 273 - 96 - - - - - continueButton - clicked() - DimStyle_New_Dialog - ButtonBOX_continue() - - - 317 - 58 - - - 262 - 58 - - - - - - DimStyleNameFromChanged() - newStyleNameChanged() - ButtonHELP_Pressed() - ButtonBOX_continue() - - + + + DimStyle_New_Dialog + + + + 0 + 0 + 372 + 142 + + + + + 372 + 142 + + + + + 372 + 142 + + + + Create new dimension style + + + + + 10 + 10 + 221 + 16 + + + + New style name: + + + + + + 10 + 30 + 221 + 20 + + + + + + + 10 + 90 + 221 + 16 + + + + Start with: + + + + + + 10 + 110 + 221 + 22 + + + + + + + 284 + 50 + 81 + 23 + + + + Continue... + + + + + + 284 + 80 + 81 + 23 + + + + Cancel + + + + + + 284 + 110 + 81 + 23 + + + + ? + + + + + + 10 + 50 + 221 + 16 + + + + Description: + + + + + + 10 + 70 + 221 + 20 + + + + + + + + DimStyleNameFrom + currentIndexChanged(int) + DimStyle_New_Dialog + DimStyleNameFromChanged() + + + 101 + 119 + + + 253 + 122 + + + + + newDimStyleName + textEdited(QString) + DimStyle_New_Dialog + newStyleNameChanged() + + + 70 + 36 + + + 277 + 26 + + + + + cancelButton + clicked() + DimStyle_New_Dialog + reject() + + + 321 + 91 + + + 256 + 89 + + + + + helpButton + clicked() + DimStyle_New_Dialog + ButtonHELP_Pressed() + + + 333 + 110 + + + 273 + 96 + + + + + continueButton + clicked() + DimStyle_New_Dialog + ButtonBOX_continue() + + + 317 + 58 + + + 262 + 58 + + + + + + DimStyleNameFromChanged() + newStyleNameChanged() + ButtonHELP_Pressed() + ButtonBOX_continue() + + diff --git a/qad_dimstyle_new_dlg.py b/qad_dimstyle_new_dlg.py index de701eab..85377b4a 100644 --- a/qad_dimstyle_new_dlg.py +++ b/qad_dimstyle_new_dlg.py @@ -1,101 +1,99 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - classe per gestire la dialog per DIMSTYLE - - ------------------- - begin : 2015-05-19 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -from qgis.core import QgsApplication -from qgis.utils import * - -import qad_dimstyle_new_ui -from qad_dimstyle_details_dlg import QadDIMSTYLE_DETAILS_Dialog - -from qad_variables import * -from qad_dim import * -from qad_msg import QadMsg, qadShowPluginHelp -import qad_utils - - -####################################################################################### -# Classe che gestisce l'interfaccia grafica della funzione di creazione nuovo stile -class QadDIMSTYLE_NEW_Dialog(QDialog, QObject, qad_dimstyle_new_ui.Ui_DimStyle_New_Dialog): - def __init__(self, plugIn, fromDimStyleName = None): - self.plugIn = plugIn - self.iface = self.plugIn.iface.mainWindow() - QDialog.__init__(self, self.iface) - - self.newDimStyle = QadDimStyle() - self.newDimStyleNameChanged = False - - self.setupUi(self) - - self.dimNameList = [] - for dimStyle in QadDimStyles.dimStyleList: # lista degli stili di quotatura caricati - self.DimStyleNameFrom.addItem(dimStyle.name, dimStyle) - self.dimNameList.append(dimStyle.name) - - # sort - self.DimStyleNameFrom.model().sort(0) - - # seleziono un elemento della lista - if fromDimStyleName is not None: - index = self.DimStyleNameFrom.findText(fromDimStyleName) - self.DimStyleNameFrom.setCurrentIndex(index) - self.DimStyleNameFromChanged(index) - - def DimStyleNameFromChanged(self, index): - # leggo l'elemento selezionato - dimStyle = self.DimStyleNameFrom.itemData(index) - if dimStyle is not None: - self.newDimStyle.set(dimStyle) - if self.newDimStyleNameChanged == False: - newName = qad_utils.checkUniqueNewName(dimStyle.name, self.dimNameList, QadMsg.translate("QAD", "Copy of ")) - if newName is not None: - self.newDimStyleName.setText(newName) - - def newStyleNameChanged(self, text): - self.newDimStyleNameChanged = True - - def ButtonBOX_continue(self): - if self.newDimStyleName.text() in self.dimNameList: - QMessageBox.critical(self, QadMsg.translate("QAD", "QAD"), \ - QadMsg.translate("DimStyle_Dialog", "Dimension style name already existing. Specify a different name.")) - return False - self.newDimStyle.name = self.newDimStyleName.text() - self.newDimStyle.description = self.newDimStyleDescr.text() - Form = QadDIMSTYLE_DETAILS_Dialog(self.plugIn, self.newDimStyle) - title = QadMsg.translate("DimStyle_Dialog", "New dimension style: ") + self.newDimStyle.name - Form.setWindowTitle(title) - - if Form.exec_() == QDialog.Accepted: - self.dimStyle = Form.dimStyle - QDialog.accept(self) - else: - self.dimStyle = None - QDialog.reject(self) - - def ButtonHELP_Pressed(self): - qadShowPluginHelp(QadMsg.translate("Help", "Dimensioning")) +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + classe per gestire la dialog per DIMSTYLE + + ------------------- + begin : 2015-05-19 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.PyQt.QtCore import QObject +from qgis.PyQt.QtWidgets import QDialog, QMessageBox + +from . import qad_dimstyle_new_ui +from .qad_dimstyle_details_dlg import QadDIMSTYLE_DETAILS_Dialog + +from .qad_dim import QadDimStyle, QadDimStyles +from .qad_msg import QadMsg, qadShowPluginPDFHelp +from . import qad_utils + + +####################################################################################### +# Classe che gestisce l'interfaccia grafica della funzione di creazione nuovo stile +class QadDIMSTYLE_NEW_Dialog(QDialog, QObject, qad_dimstyle_new_ui.Ui_DimStyle_New_Dialog): + def __init__(self, plugIn, parent, fromDimStyleName = None): + self.plugIn = plugIn + self.iface = self.plugIn.iface.mainWindow() + + QDialog.__init__(self, parent) + + self.newDimStyle = QadDimStyle() + self.newDimStyleNameChanged = False + + self.setupUi(self) + self.setWindowTitle(QadMsg.getQADTitle() + " - " + self.windowTitle()) + + self.dimNameList = [] + for dimStyle in QadDimStyles.dimStyleList: # lista degli stili di quotatura caricati + self.DimStyleNameFrom.addItem(dimStyle.name, dimStyle) + self.dimNameList.append(dimStyle.name) + + # sort + self.DimStyleNameFrom.model().sort(0) + + # seleziono un elemento della lista + if fromDimStyleName is not None: + index = self.DimStyleNameFrom.findText(fromDimStyleName) + self.DimStyleNameFrom.setCurrentIndex(index) + self.DimStyleNameFromChanged(index) + + def DimStyleNameFromChanged(self, index): + # leggo l'elemento selezionato + dimStyle = self.DimStyleNameFrom.itemData(index) + if dimStyle is not None: + self.newDimStyle.set(dimStyle) + if self.newDimStyleNameChanged == False: + newName = qad_utils.checkUniqueNewName(dimStyle.name, self.dimNameList, QadMsg.translate("QAD", "Copy of ")) + if newName is not None: + self.newDimStyleName.setText(newName) + + def newStyleNameChanged(self, text): + self.newDimStyleNameChanged = True + + def ButtonBOX_continue(self): + if self.newDimStyleName.text() in self.dimNameList: + QMessageBox.critical(self, QadMsg.getQADTitle(), \ + QadMsg.translate("DimStyle_Dialog", "Dimension style name already existing. Specify a different name.")) + return False + self.newDimStyle.name = self.newDimStyleName.text() + self.newDimStyle.description = self.newDimStyleDescr.text() + Form = QadDIMSTYLE_DETAILS_Dialog(self.plugIn, self, self.newDimStyle) + title = QadMsg.translate("DimStyle_Dialog", "New dimension style: ") + self.newDimStyle.name + Form.setWindowTitle(QadMsg.getQADTitle() + " - " + title) + + if Form.exec_() == QDialog.Accepted: + self.dimStyle = Form.dimStyle + QDialog.accept(self) + else: + self.dimStyle = None + QDialog.reject(self) + + def ButtonHELP_Pressed(self): + qadShowPluginPDFHelp(QadMsg.translate("Help", "Dimensioning")) diff --git a/qad_dimstyle_new_ui.py b/qad_dimstyle_new_ui.py index 0f1a23b6..e4e81ce9 100644 --- a/qad_dimstyle_new_ui.py +++ b/qad_dimstyle_new_ui.py @@ -1,74 +1,64 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file 'qad_dimstyle_new.ui' -# -# Created: Tue Sep 08 15:55:21 2015 -# by: PyQt4 UI code generator 4.10.2 -# -# WARNING! All changes made in this file will be lost! - -from PyQt4 import QtCore, QtGui - -try: - _fromUtf8 = QtCore.QString.fromUtf8 -except AttributeError: - def _fromUtf8(s): - return s - -try: - _encoding = QtGui.QApplication.UnicodeUTF8 - def _translate(context, text, disambig): - return QtGui.QApplication.translate(context, text, disambig, _encoding) -except AttributeError: - def _translate(context, text, disambig): - return QtGui.QApplication.translate(context, text, disambig) - -class Ui_DimStyle_New_Dialog(object): - def setupUi(self, DimStyle_New_Dialog): - DimStyle_New_Dialog.setObjectName(_fromUtf8("DimStyle_New_Dialog")) - DimStyle_New_Dialog.resize(372, 142) - self.label = QtGui.QLabel(DimStyle_New_Dialog) - self.label.setGeometry(QtCore.QRect(10, 10, 221, 16)) - self.label.setObjectName(_fromUtf8("label")) - self.newDimStyleName = QtGui.QLineEdit(DimStyle_New_Dialog) - self.newDimStyleName.setGeometry(QtCore.QRect(10, 30, 221, 20)) - self.newDimStyleName.setObjectName(_fromUtf8("newDimStyleName")) - self.label_2 = QtGui.QLabel(DimStyle_New_Dialog) - self.label_2.setGeometry(QtCore.QRect(10, 90, 221, 16)) - self.label_2.setObjectName(_fromUtf8("label_2")) - self.DimStyleNameFrom = QtGui.QComboBox(DimStyle_New_Dialog) - self.DimStyleNameFrom.setGeometry(QtCore.QRect(10, 110, 221, 22)) - self.DimStyleNameFrom.setObjectName(_fromUtf8("DimStyleNameFrom")) - self.continueButton = QtGui.QPushButton(DimStyle_New_Dialog) - self.continueButton.setGeometry(QtCore.QRect(290, 50, 75, 23)) - self.continueButton.setObjectName(_fromUtf8("continueButton")) - self.cancelButton = QtGui.QPushButton(DimStyle_New_Dialog) - self.cancelButton.setGeometry(QtCore.QRect(290, 80, 75, 23)) - self.cancelButton.setObjectName(_fromUtf8("cancelButton")) - self.helpButton = QtGui.QPushButton(DimStyle_New_Dialog) - self.helpButton.setGeometry(QtCore.QRect(290, 110, 75, 23)) - self.helpButton.setObjectName(_fromUtf8("helpButton")) - self.label_3 = QtGui.QLabel(DimStyle_New_Dialog) - self.label_3.setGeometry(QtCore.QRect(10, 50, 221, 16)) - self.label_3.setObjectName(_fromUtf8("label_3")) - self.newDimStyleDescr = QtGui.QLineEdit(DimStyle_New_Dialog) - self.newDimStyleDescr.setGeometry(QtCore.QRect(10, 70, 221, 20)) - self.newDimStyleDescr.setObjectName(_fromUtf8("newDimStyleDescr")) - - self.retranslateUi(DimStyle_New_Dialog) - QtCore.QObject.connect(self.DimStyleNameFrom, QtCore.SIGNAL(_fromUtf8("currentIndexChanged(int)")), DimStyle_New_Dialog.DimStyleNameFromChanged) - QtCore.QObject.connect(self.newDimStyleName, QtCore.SIGNAL(_fromUtf8("textEdited(QString)")), DimStyle_New_Dialog.newStyleNameChanged) - QtCore.QObject.connect(self.cancelButton, QtCore.SIGNAL(_fromUtf8("clicked()")), DimStyle_New_Dialog.reject) - QtCore.QObject.connect(self.helpButton, QtCore.SIGNAL(_fromUtf8("clicked()")), DimStyle_New_Dialog.ButtonHELP_Pressed) - QtCore.QObject.connect(self.continueButton, QtCore.SIGNAL(_fromUtf8("clicked()")), DimStyle_New_Dialog.ButtonBOX_continue) - QtCore.QMetaObject.connectSlotsByName(DimStyle_New_Dialog) - - def retranslateUi(self, DimStyle_New_Dialog): - DimStyle_New_Dialog.setWindowTitle(_translate("DimStyle_New_Dialog", "QAD - Create new dimension style", None)) - self.label.setText(_translate("DimStyle_New_Dialog", "New style name:", None)) - self.label_2.setText(_translate("DimStyle_New_Dialog", "Start with:", None)) - self.continueButton.setText(_translate("DimStyle_New_Dialog", "Continue...", None)) - self.cancelButton.setText(_translate("DimStyle_New_Dialog", "Cancel", None)) - self.helpButton.setText(_translate("DimStyle_New_Dialog", "?", None)) - self.label_3.setText(_translate("DimStyle_New_Dialog", "Description:", None)) - +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file '.\qad_dimstyle_new.ui' +# +# Created by: PyQt5 UI code generator 5.15.4 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_DimStyle_New_Dialog(object): + def setupUi(self, DimStyle_New_Dialog): + DimStyle_New_Dialog.setObjectName("DimStyle_New_Dialog") + DimStyle_New_Dialog.resize(372, 142) + DimStyle_New_Dialog.setMinimumSize(QtCore.QSize(372, 142)) + DimStyle_New_Dialog.setMaximumSize(QtCore.QSize(372, 142)) + self.label = QtWidgets.QLabel(DimStyle_New_Dialog) + self.label.setGeometry(QtCore.QRect(10, 10, 221, 16)) + self.label.setObjectName("label") + self.newDimStyleName = QtWidgets.QLineEdit(DimStyle_New_Dialog) + self.newDimStyleName.setGeometry(QtCore.QRect(10, 30, 221, 20)) + self.newDimStyleName.setObjectName("newDimStyleName") + self.label_2 = QtWidgets.QLabel(DimStyle_New_Dialog) + self.label_2.setGeometry(QtCore.QRect(10, 90, 221, 16)) + self.label_2.setObjectName("label_2") + self.DimStyleNameFrom = QtWidgets.QComboBox(DimStyle_New_Dialog) + self.DimStyleNameFrom.setGeometry(QtCore.QRect(10, 110, 221, 22)) + self.DimStyleNameFrom.setObjectName("DimStyleNameFrom") + self.continueButton = QtWidgets.QPushButton(DimStyle_New_Dialog) + self.continueButton.setGeometry(QtCore.QRect(284, 50, 81, 23)) + self.continueButton.setObjectName("continueButton") + self.cancelButton = QtWidgets.QPushButton(DimStyle_New_Dialog) + self.cancelButton.setGeometry(QtCore.QRect(284, 80, 81, 23)) + self.cancelButton.setObjectName("cancelButton") + self.helpButton = QtWidgets.QPushButton(DimStyle_New_Dialog) + self.helpButton.setGeometry(QtCore.QRect(284, 110, 81, 23)) + self.helpButton.setObjectName("helpButton") + self.label_3 = QtWidgets.QLabel(DimStyle_New_Dialog) + self.label_3.setGeometry(QtCore.QRect(10, 50, 221, 16)) + self.label_3.setObjectName("label_3") + self.newDimStyleDescr = QtWidgets.QLineEdit(DimStyle_New_Dialog) + self.newDimStyleDescr.setGeometry(QtCore.QRect(10, 70, 221, 20)) + self.newDimStyleDescr.setObjectName("newDimStyleDescr") + + self.retranslateUi(DimStyle_New_Dialog) + self.DimStyleNameFrom.currentIndexChanged['int'].connect(DimStyle_New_Dialog.DimStyleNameFromChanged) + self.newDimStyleName.textEdited['QString'].connect(DimStyle_New_Dialog.newStyleNameChanged) + self.cancelButton.clicked.connect(DimStyle_New_Dialog.reject) + self.helpButton.clicked.connect(DimStyle_New_Dialog.ButtonHELP_Pressed) + self.continueButton.clicked.connect(DimStyle_New_Dialog.ButtonBOX_continue) + QtCore.QMetaObject.connectSlotsByName(DimStyle_New_Dialog) + + def retranslateUi(self, DimStyle_New_Dialog): + _translate = QtCore.QCoreApplication.translate + DimStyle_New_Dialog.setWindowTitle(_translate("DimStyle_New_Dialog", "Create new dimension style")) + self.label.setText(_translate("DimStyle_New_Dialog", "New style name:")) + self.label_2.setText(_translate("DimStyle_New_Dialog", "Start with:")) + self.continueButton.setText(_translate("DimStyle_New_Dialog", "Continue...")) + self.cancelButton.setText(_translate("DimStyle_New_Dialog", "Cancel")) + self.helpButton.setText(_translate("DimStyle_New_Dialog", "?")) + self.label_3.setText(_translate("DimStyle_New_Dialog", "Description:")) diff --git a/qad_dimstyle_ui.py b/qad_dimstyle_ui.py index 08afc06e..66e41f21 100644 --- a/qad_dimstyle_ui.py +++ b/qad_dimstyle_ui.py @@ -1,119 +1,109 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file 'qad_dimstyle.ui' -# -# Created: Tue Sep 08 15:55:19 2015 -# by: PyQt4 UI code generator 4.10.2 -# -# WARNING! All changes made in this file will be lost! - -from PyQt4 import QtCore, QtGui - -try: - _fromUtf8 = QtCore.QString.fromUtf8 -except AttributeError: - def _fromUtf8(s): - return s - -try: - _encoding = QtGui.QApplication.UnicodeUTF8 - def _translate(context, text, disambig): - return QtGui.QApplication.translate(context, text, disambig, _encoding) -except AttributeError: - def _translate(context, text, disambig): - return QtGui.QApplication.translate(context, text, disambig) - -class Ui_DimStyle_Dialog(object): - def setupUi(self, DimStyle_Dialog): - DimStyle_Dialog.setObjectName(_fromUtf8("DimStyle_Dialog")) - DimStyle_Dialog.setWindowModality(QtCore.Qt.WindowModal) - DimStyle_Dialog.resize(523, 341) - self.label = QtGui.QLabel(DimStyle_Dialog) - self.label.setGeometry(QtCore.QRect(20, 10, 121, 16)) - self.label.setObjectName(_fromUtf8("label")) - self.currentDimStyle = QtGui.QLabel(DimStyle_Dialog) - self.currentDimStyle.setGeometry(QtCore.QRect(140, 10, 331, 16)) - self.currentDimStyle.setObjectName(_fromUtf8("currentDimStyle")) - self.label_2 = QtGui.QLabel(DimStyle_Dialog) - self.label_2.setGeometry(QtCore.QRect(20, 40, 47, 16)) - self.label_2.setObjectName(_fromUtf8("label_2")) - self.dimStyleList = QtGui.QListView(DimStyle_Dialog) - self.dimStyleList.setGeometry(QtCore.QRect(10, 60, 171, 171)) - self.dimStyleList.setObjectName(_fromUtf8("dimStyleList")) - self.SetCurrent = QtGui.QPushButton(DimStyle_Dialog) - self.SetCurrent.setGeometry(QtCore.QRect(410, 60, 101, 23)) - self.SetCurrent.setObjectName(_fromUtf8("SetCurrent")) - self.new_2 = QtGui.QPushButton(DimStyle_Dialog) - self.new_2.setGeometry(QtCore.QRect(410, 90, 101, 23)) - self.new_2.setObjectName(_fromUtf8("new_2")) - self.Mod = QtGui.QPushButton(DimStyle_Dialog) - self.Mod.setGeometry(QtCore.QRect(410, 120, 101, 23)) - self.Mod.setObjectName(_fromUtf8("Mod")) - self.TempMod = QtGui.QPushButton(DimStyle_Dialog) - self.TempMod.setGeometry(QtCore.QRect(410, 150, 101, 23)) - self.TempMod.setObjectName(_fromUtf8("TempMod")) - self.Diff = QtGui.QPushButton(DimStyle_Dialog) - self.Diff.setGeometry(QtCore.QRect(410, 180, 101, 23)) - self.Diff.setObjectName(_fromUtf8("Diff")) - self.groupBox = QtGui.QGroupBox(DimStyle_Dialog) - self.groupBox.setGeometry(QtCore.QRect(10, 240, 501, 61)) - self.groupBox.setObjectName(_fromUtf8("groupBox")) - self.descriptionSelectedStyle = QtGui.QLabel(self.groupBox) - self.descriptionSelectedStyle.setGeometry(QtCore.QRect(10, 10, 481, 41)) - self.descriptionSelectedStyle.setObjectName(_fromUtf8("descriptionSelectedStyle")) - self.label_3 = QtGui.QLabel(DimStyle_Dialog) - self.label_3.setGeometry(QtCore.QRect(190, 40, 71, 16)) - self.label_3.setObjectName(_fromUtf8("label_3")) - self.selectedStyle = QtGui.QLabel(DimStyle_Dialog) - self.selectedStyle.setGeometry(QtCore.QRect(270, 40, 241, 16)) - self.selectedStyle.setObjectName(_fromUtf8("selectedStyle")) - self.layoutWidget = QtGui.QWidget(DimStyle_Dialog) - self.layoutWidget.setGeometry(QtCore.QRect(350, 310, 158, 25)) - self.layoutWidget.setObjectName(_fromUtf8("layoutWidget")) - self.horizontalLayout = QtGui.QHBoxLayout(self.layoutWidget) - self.horizontalLayout.setMargin(0) - self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) - self.closeButton = QtGui.QPushButton(self.layoutWidget) - self.closeButton.setObjectName(_fromUtf8("closeButton")) - self.horizontalLayout.addWidget(self.closeButton) - self.helpButton = QtGui.QPushButton(self.layoutWidget) - self.helpButton.setObjectName(_fromUtf8("helpButton")) - self.horizontalLayout.addWidget(self.helpButton) - self.previewDummy = QtGui.QPushButton(DimStyle_Dialog) - self.previewDummy.setGeometry(QtCore.QRect(190, 60, 211, 171)) - self.previewDummy.setText(_fromUtf8("")) - self.previewDummy.setObjectName(_fromUtf8("previewDummy")) - - self.retranslateUi(DimStyle_Dialog) - QtCore.QObject.connect(self.SetCurrent, QtCore.SIGNAL(_fromUtf8("clicked()")), DimStyle_Dialog.setCurrentStyle) - QtCore.QObject.connect(self.new_2, QtCore.SIGNAL(_fromUtf8("clicked()")), DimStyle_Dialog.createNewStyle) - QtCore.QObject.connect(self.Mod, QtCore.SIGNAL(_fromUtf8("clicked()")), DimStyle_Dialog.modStyle) - QtCore.QObject.connect(self.dimStyleList, QtCore.SIGNAL(_fromUtf8("customContextMenuRequested(QPoint)")), DimStyle_Dialog.displayPopupMenu) - QtCore.QObject.connect(self.TempMod, QtCore.SIGNAL(_fromUtf8("clicked()")), DimStyle_Dialog.temporaryModStyle) - QtCore.QObject.connect(self.helpButton, QtCore.SIGNAL(_fromUtf8("clicked()")), DimStyle_Dialog.ButtonHELP_Pressed) - QtCore.QObject.connect(self.Diff, QtCore.SIGNAL(_fromUtf8("clicked()")), DimStyle_Dialog.showDiffBetweenStyles) - QtCore.QObject.connect(self.closeButton, QtCore.SIGNAL(_fromUtf8("clicked()")), DimStyle_Dialog.accept) - QtCore.QMetaObject.connectSlotsByName(DimStyle_Dialog) - - def retranslateUi(self, DimStyle_Dialog): - DimStyle_Dialog.setWindowTitle(_translate("DimStyle_Dialog", "QAD - Dimension style manager", None)) - self.label.setText(_translate("DimStyle_Dialog", "Current dimension style:", None)) - self.currentDimStyle.setText(_translate("DimStyle_Dialog", "none", None)) - self.label_2.setText(_translate("DimStyle_Dialog", "Styles", None)) - self.SetCurrent.setToolTip(_translate("DimStyle_Dialog", "Sets the style selected under Styles to current. The current style is applied to dimensions you create.", None)) - self.SetCurrent.setText(_translate("DimStyle_Dialog", "Set current", None)) - self.new_2.setToolTip(_translate("DimStyle_Dialog", "Define a new dimension style.", None)) - self.new_2.setText(_translate("DimStyle_Dialog", "New...", None)) - self.Mod.setToolTip(_translate("DimStyle_Dialog", "Modify the selected dimension style.", None)) - self.Mod.setText(_translate("DimStyle_Dialog", "Modify...", None)) - self.TempMod.setToolTip(_translate("DimStyle_Dialog", "Set temporary modifications for the selected style. The temporary modifications will not saved.", None)) - self.TempMod.setText(_translate("DimStyle_Dialog", "Override...", None)) - self.Diff.setToolTip(_translate("DimStyle_Dialog", "Compare two dimension styles or list all the properties of one dimension style.", None)) - self.Diff.setText(_translate("DimStyle_Dialog", "Compare...", None)) - self.groupBox.setTitle(_translate("DimStyle_Dialog", "Description", None)) - self.descriptionSelectedStyle.setText(_translate("DimStyle_Dialog", "none", None)) - self.label_3.setText(_translate("DimStyle_Dialog", "Preview of:", None)) - self.selectedStyle.setText(_translate("DimStyle_Dialog", "none", None)) - self.closeButton.setText(_translate("DimStyle_Dialog", "Close", None)) - self.helpButton.setText(_translate("DimStyle_Dialog", "?", None)) - +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file '.\qad_dimstyle.ui' +# +# Created by: PyQt5 UI code generator 5.15.4 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_DimStyle_Dialog(object): + def setupUi(self, DimStyle_Dialog): + DimStyle_Dialog.setObjectName("DimStyle_Dialog") + DimStyle_Dialog.setWindowModality(QtCore.Qt.WindowModal) + DimStyle_Dialog.resize(539, 341) + DimStyle_Dialog.setMinimumSize(QtCore.QSize(539, 341)) + DimStyle_Dialog.setMaximumSize(QtCore.QSize(539, 341)) + self.label = QtWidgets.QLabel(DimStyle_Dialog) + self.label.setGeometry(QtCore.QRect(20, 10, 151, 16)) + self.label.setObjectName("label") + self.currentDimStyle = QtWidgets.QLabel(DimStyle_Dialog) + self.currentDimStyle.setGeometry(QtCore.QRect(180, 10, 341, 16)) + self.currentDimStyle.setObjectName("currentDimStyle") + self.label_2 = QtWidgets.QLabel(DimStyle_Dialog) + self.label_2.setGeometry(QtCore.QRect(10, 40, 171, 20)) + self.label_2.setObjectName("label_2") + self.dimStyleList = QtWidgets.QListView(DimStyle_Dialog) + self.dimStyleList.setGeometry(QtCore.QRect(10, 60, 171, 171)) + self.dimStyleList.setObjectName("dimStyleList") + self.SetCurrent = QtWidgets.QPushButton(DimStyle_Dialog) + self.SetCurrent.setGeometry(QtCore.QRect(410, 60, 121, 23)) + self.SetCurrent.setObjectName("SetCurrent") + self.new_2 = QtWidgets.QPushButton(DimStyle_Dialog) + self.new_2.setGeometry(QtCore.QRect(410, 90, 121, 23)) + self.new_2.setObjectName("new_2") + self.Mod = QtWidgets.QPushButton(DimStyle_Dialog) + self.Mod.setGeometry(QtCore.QRect(410, 120, 121, 23)) + self.Mod.setObjectName("Mod") + self.TempMod = QtWidgets.QPushButton(DimStyle_Dialog) + self.TempMod.setGeometry(QtCore.QRect(410, 150, 121, 23)) + self.TempMod.setObjectName("TempMod") + self.Diff = QtWidgets.QPushButton(DimStyle_Dialog) + self.Diff.setGeometry(QtCore.QRect(410, 180, 121, 23)) + self.Diff.setObjectName("Diff") + self.groupBox = QtWidgets.QGroupBox(DimStyle_Dialog) + self.groupBox.setGeometry(QtCore.QRect(10, 240, 521, 61)) + self.groupBox.setObjectName("groupBox") + self.descriptionSelectedStyle = QtWidgets.QLabel(self.groupBox) + self.descriptionSelectedStyle.setGeometry(QtCore.QRect(10, 10, 481, 41)) + self.descriptionSelectedStyle.setObjectName("descriptionSelectedStyle") + self.label_3 = QtWidgets.QLabel(DimStyle_Dialog) + self.label_3.setGeometry(QtCore.QRect(190, 40, 101, 16)) + self.label_3.setObjectName("label_3") + self.selectedStyle = QtWidgets.QLabel(DimStyle_Dialog) + self.selectedStyle.setGeometry(QtCore.QRect(300, 40, 221, 20)) + self.selectedStyle.setObjectName("selectedStyle") + self.layoutWidget = QtWidgets.QWidget(DimStyle_Dialog) + self.layoutWidget.setGeometry(QtCore.QRect(370, 310, 158, 25)) + self.layoutWidget.setObjectName("layoutWidget") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.layoutWidget) + self.horizontalLayout.setContentsMargins(0, 0, 0, 0) + self.horizontalLayout.setObjectName("horizontalLayout") + self.closeButton = QtWidgets.QPushButton(self.layoutWidget) + self.closeButton.setObjectName("closeButton") + self.horizontalLayout.addWidget(self.closeButton) + self.helpButton = QtWidgets.QPushButton(self.layoutWidget) + self.helpButton.setObjectName("helpButton") + self.horizontalLayout.addWidget(self.helpButton) + self.previewDummy = QtWidgets.QPushButton(DimStyle_Dialog) + self.previewDummy.setGeometry(QtCore.QRect(190, 60, 211, 171)) + self.previewDummy.setText("") + self.previewDummy.setObjectName("previewDummy") + + self.retranslateUi(DimStyle_Dialog) + self.SetCurrent.clicked.connect(DimStyle_Dialog.setCurrentStyle) + self.new_2.clicked.connect(DimStyle_Dialog.createNewStyle) + self.Mod.clicked.connect(DimStyle_Dialog.modStyle) + self.dimStyleList.customContextMenuRequested['QPoint'].connect(DimStyle_Dialog.displayPopupMenu) + self.TempMod.clicked.connect(DimStyle_Dialog.temporaryModStyle) + self.helpButton.clicked.connect(DimStyle_Dialog.ButtonHELP_Pressed) + self.Diff.clicked.connect(DimStyle_Dialog.showDiffBetweenStyles) + self.closeButton.clicked.connect(DimStyle_Dialog.accept) + QtCore.QMetaObject.connectSlotsByName(DimStyle_Dialog) + + def retranslateUi(self, DimStyle_Dialog): + _translate = QtCore.QCoreApplication.translate + DimStyle_Dialog.setWindowTitle(_translate("DimStyle_Dialog", "Dimension style manager")) + self.label.setText(_translate("DimStyle_Dialog", "Current dimension style:")) + self.currentDimStyle.setText(_translate("DimStyle_Dialog", "none")) + self.label_2.setText(_translate("DimStyle_Dialog", "Styles")) + self.SetCurrent.setToolTip(_translate("DimStyle_Dialog", "Sets the style selected under Styles to current. The current style is applied to dimensions you create.")) + self.SetCurrent.setText(_translate("DimStyle_Dialog", "Set current")) + self.new_2.setToolTip(_translate("DimStyle_Dialog", "Define a new dimension style.")) + self.new_2.setText(_translate("DimStyle_Dialog", "New...")) + self.Mod.setToolTip(_translate("DimStyle_Dialog", "Modify the selected dimension style.")) + self.Mod.setText(_translate("DimStyle_Dialog", "Modify...")) + self.TempMod.setToolTip(_translate("DimStyle_Dialog", "Set temporary modifications for the selected style. The temporary modifications will not saved.")) + self.TempMod.setText(_translate("DimStyle_Dialog", "Override...")) + self.Diff.setToolTip(_translate("DimStyle_Dialog", "Compare two dimension styles or list all the properties of one dimension style.")) + self.Diff.setText(_translate("DimStyle_Dialog", "Compare...")) + self.groupBox.setTitle(_translate("DimStyle_Dialog", "Description")) + self.descriptionSelectedStyle.setText(_translate("DimStyle_Dialog", "none")) + self.label_3.setText(_translate("DimStyle_Dialog", "Preview of:")) + self.selectedStyle.setText(_translate("DimStyle_Dialog", "none")) + self.closeButton.setText(_translate("DimStyle_Dialog", "Close")) + self.helpButton.setText(_translate("DimStyle_Dialog", "?")) diff --git a/qad_dsettings.qrc b/qad_dsettings.qrc index 99039937..8a895663 100644 --- a/qad_dsettings.qrc +++ b/qad_dsettings.qrc @@ -1,34 +1,38 @@ - - - icons/dsettings/OSNAP_ToolTIP_END_PLINE.png - icons/dsettings/OSNAP_ToolTIP_EXTINT.png - icons/dsettings/OSNAP_ToolTIP_CENTROID.png - icons/dsettings/OSNAP_ToolTIP_EXTP.png - icons/dsettings/OSNAP_ToolTIP_TANP.png - icons/dsettings/OSNAP_ToolTIP_PROGRESP.png - icons/dsettings/OSNAP_ToolTIP_PARLP.png - icons/dsettings/OSNAP_ToolTIP_PERP.png - icons/dsettings/OSNAP_ToolTIP_QUADP.png - icons/dsettings/OSNAP_ToolTIP_NEARP.png - icons/dsettings/OSNAP_ToolTIP_NODP.png - icons/dsettings/OSNAP_ToolTIP_INTP.png - icons/dsettings/OSNAP_ToolTIP_MIDP.png - icons/dsettings/OSNAP_ToolTIP_CENP.png - icons/dsettings/OSNAP_ToolTIP_ENDP.png - icons/dsettings/OSNAP_EXTINT.png - icons/dsettings/OSNAP_EXTP.png - icons/dsettings/OSNAP_INSP.png - icons/dsettings/OSNAP_INTAPP.png - icons/dsettings/OSNAP_INTP.png - icons/dsettings/OSNAP_MEDP.png - icons/dsettings/OSNAP_NEARP.png - icons/dsettings/OSNAP_NODP.png - icons/dsettings/OSNAP_PARP.png - icons/dsettings/OSNAP_PERP.png - icons/dsettings/OSNAP_PROGP.png - icons/dsettings/OSNAP_QUADP.png - icons/dsettings/OSNAP_TANP.png - icons/dsettings/OSNAP_CENP.png - icons/dsettings/OSNAP_ENDP.png - - + + + icons/dsettings/OSNAP_ToolTIP_END_PLINE.png + icons/dsettings/OSNAP_ToolTIP_EXTINT.png + icons/dsettings/OSNAP_ToolTIP_CENTROID.png + icons/dsettings/OSNAP_ToolTIP_EXTP.png + icons/dsettings/OSNAP_ToolTIP_TANP.png + icons/dsettings/OSNAP_ToolTIP_PROGRESP.png + icons/dsettings/OSNAP_ToolTIP_PARLP.png + icons/dsettings/OSNAP_ToolTIP_PERP.png + icons/dsettings/OSNAP_ToolTIP_QUADP.png + icons/dsettings/OSNAP_ToolTIP_NEARP.png + icons/dsettings/OSNAP_ToolTIP_NODP.png + icons/dsettings/OSNAP_ToolTIP_INTP.png + icons/dsettings/OSNAP_ToolTIP_MIDP.png + icons/dsettings/OSNAP_ToolTIP_CENP.png + icons/dsettings/OSNAP_ToolTIP_ENDP.png + icons/dsettings/OSNAP_EXTINT.png + icons/dsettings/OSNAP_EXTP.png + icons/dsettings/OSNAP_INSP.png + icons/dsettings/OSNAP_INTAPP.png + icons/dsettings/OSNAP_INTP.png + icons/dsettings/OSNAP_MEDP.png + icons/dsettings/OSNAP_NEARP.png + icons/dsettings/OSNAP_NODP.png + icons/dsettings/OSNAP_PARP.png + icons/dsettings/OSNAP_PERP.png + icons/dsettings/OSNAP_PROGP.png + icons/dsettings/OSNAP_QUADP.png + icons/dsettings/OSNAP_TANP.png + icons/dsettings/OSNAP_CENP.png + icons/dsettings/OSNAP_ENDP.png + icons/dsettings/dimension_input.png + icons/dsettings/dynamic_prompts.png + icons/dsettings/pointer_input.png + icons/lamp_on.png + + diff --git a/qad_dsettings.ui b/qad_dsettings.ui index c880b20c..6ff82d49 100644 --- a/qad_dsettings.ui +++ b/qad_dsettings.ui @@ -1,704 +1,1216 @@ - - - DSettings_Dialog - - - Qt::ApplicationModal - - - - 0 - 0 - 441 - 455 - - - - true - - - Qt::DefaultContextMenu - - - QAD - Drawing settings - - - true - - - - - 10 - 10 - 421 - 401 - - - - 0 - - - - Object Snap - - - - - 10 - 40 - 391 - 321 - - - - Object Snap modes - - - - - 60 - 290 - 261 - 25 - - - - - - - Select All - - - - - - - Deselect All - - - - - - - - - 190 - 20 - 181 - 261 - - - - - - - - - - :/plugins/qad/icons/dsettings/OSNAP_EXTP.png - - - - - - - - - - :/plugins/qad/icons/dsettings/OSNAP_PARP.png - - - - - - - - - - :/plugins/qad/icons/dsettings/OSNAP_PROGP.png - - - - - - - - - - :/plugins/qad/icons/dsettings/OSNAP_EXTINT.png - - - - - - - - - - :/plugins/qad/icons/dsettings/OSNAP_PERP.png - - - - - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Perpendicular OSnap: orthogonal projection of a given point on a segment.</p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><img src=":/plugins/qad/icons/dsettings/OSNAP_ToolTIP_PERP.png" /></p></body></html> - - - Perpendicular - - - false - - - - - - - - - - :/plugins/qad/icons/dsettings/OSNAP_TANP.png - - - - - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">tangent point on a curve of a line passing through a given point.</p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><img src=":/plugins/qad/icons/dsettings/OSNAP_ToolTIP_TANP.png" /></p></body></html> - - - Tangent - - - false - - - - - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Extension OSnap: point on the segment extension until the cursor position.</p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><img src=":/plugins/qad/icons/dsettings/OSNAP_ToolTIP_EXTP.png" /></p></body></html> - - - Extend - - - false - - - - - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Parallel OSnap: point on a line, passing through a given point, parallel to a segment.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><img src=":/plugins/qad/icons/dsettings/OSNAP_ToolTIP_PARLP.png" /></p></body></html> - - - Parallel - - - false - - - - - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Progressive OSnap: point at a given distance along a geometry line: from a vertex we can set a point at a distance measured along the geometry line.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><img src=":/plugins/qad/icons/dsettings/OSNAP_ToolTIP_PROGRESP.png" /></p></body></html> - - - Progressive - - - false - - - - - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Intersection on extension OSnap: intersection point of the extensions of two segments.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><img src=":/plugins/qad/icons/dsettings/OSNAP_ToolTIP_EXTINT.png" /></p></body></html> - - - Intersection on extension - - - - - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Quadrant OSnap: intersections of the cartesian axis with a circumference of a circle or an arc.</p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><img src=":/plugins/qad/icons/dsettings/OSNAP_ToolTIP_QUADP.png" /></p></body></html> - - - Quadrant - - - false - - - - - - - - - - :/plugins/qad/icons/dsettings/OSNAP_QUADP.png - - - - - - - - - 340 - 210 - 41 - 20 - - - - - - - 10 - 20 - 154 - 261 - - - - - - - - - - :/plugins/qad/icons/dsettings/OSNAP_ENDP.png - - - - - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Start / End OSnap: starting and ending vertices of a linear geometry.</span></p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><img src=":/plugins/qad/icons/dsettings/OSNAP_ToolTIP_END_PLINE.png" /></p></body></html> - - - Start / End - - - - - - - - - - :/plugins/qad/icons/dsettings/OSNAP_ENDP.png - - - - - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Start / End segment OSnap: starting and ending vertices of each segment of a geometry.</p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><img src=":/plugins/qad/icons/dsettings/OSNAP_ToolTIP_ENDP.png" /></p></body></html> - - - Segment Start / End - - - false - - - - - - - - - - :/plugins/qad/icons/dsettings/OSNAP_MEDP.png - - - - - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Middle point OSnap: middle point of each segment of a geometry.</p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><img src=":/plugins/qad/icons/dsettings/OSNAP_ToolTIP_MIDP.png" /></p></body></html> - - - Middle point - - - false - - - - - - - - - - :/plugins/qad/icons/dsettings/OSNAP_INTP.png - - - - - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Intersection OSnap: intersection between two segments.</p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><img src=":/plugins/qad/icons/dsettings/OSNAP_ToolTIP_INTP.png" /></p></body></html> - - - Intersection - - - false - - - - - - - - - - :/plugins/qad/icons/dsettings/OSNAP_CENP.png - - - - - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Center OSnap: center of a circle or arc or centroid of an areal geometry.</p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><img src=":/plugins/qad/icons/dsettings/OSNAP_ToolTIP_CENP.png" /></p></body></html> - - - Center - - - false - - - - - - - - - - :/plugins/qad/icons/dsettings/OSNAP_NODP.png - - - - - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Node OSnap: coordinate of a punctual geometry.</p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><img src=":/plugins/qad/icons/dsettings/OSNAP_ToolTIP_NODP.png" /></p></body></html> - - - Node - - - false - - - - - - - - - - :/plugins/qad/icons/dsettings/OSNAP_NEARP.png - - - - - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Near OSnap: point of a segment close to the cursor position.</p> -<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><img src=":/plugins/qad/icons/dsettings/OSNAP_ToolTIP_NEARP.png" /></p></body></html> - - - Near - - - false - - - - - - - - - - 10 - 10 - 126 - 17 - - - - Object Snap (F3) - - - - - - Polar Tracking - - - - - 10 - 10 - 171 - 17 - - - - Polar Tracking (F10) - - - - - - 10 - 40 - 151 - 81 - - - - Polar angle settings - - - - - 10 - 20 - 121 - 16 - - - - Increment angle: - - - - - - 10 - 40 - 131 - 22 - - - - true - - - - - - - - - 190 - 420 - 239 - 25 - - - - - - - OK - - - - - - - Cancel - - - - - - - ? - - - - - - - - - - - - pushButton_DeSelectALL - pressed() - DSettings_Dialog - ButtonDeselectALL_Pressed() - - - 305 - 385 - - - 439 - 0 - - - - - pushButton_SelectALL - pressed() - DSettings_Dialog - ButtonSelectALL_Pressed() - - - 171 - 385 - - - 436 - 0 - - - - - pushButton_HELP - clicked() - DSettings_Dialog - ButtonHELP_Pressed() - - - 391 - 439 - - - 372 - 426 - - - - - okButton - clicked() - DSettings_Dialog - ButtonBOX_Accepted() - - - 64 - 433 - - - 8 - 418 - - - - - cancelButton - clicked() - DSettings_Dialog - reject() - - - 142 - 428 - - - 136 - 413 - - - - - - ButtonSelectALL_Pressed() - ButtonDeselectALL_Pressed() - ButtonBOX_Accepted() - Hovered_Cursor_ENDP() - Hovered_Cursor_MIDP() - ContextMenu_Activated_ENDP() - MainTabChanged() - ButtonHELP_Pressed() - - + + + DSettings_Dialog + + + Qt::ApplicationModal + + + + 0 + 0 + 550 + 455 + + + + + 550 + 455 + + + + + 550 + 455 + + + + true + + + Qt::DefaultContextMenu + + + Drawing settings + + + true + + + + + 10 + 0 + 521 + 401 + + + + 2 + + + + Object Snap + + + + + 10 + 40 + 491 + 321 + + + + Object Snap modes + + + + + 120 + 290 + 261 + 30 + + + + + + + Select All + + + + + + + Deselect All + + + + + + + + + 270 + 20 + 197 + 261 + + + + + + + + + + :/plugins/qad/icons/dsettings/OSNAP_EXTP.png + + + + + + + + + + :/plugins/qad/icons/dsettings/OSNAP_PARP.png + + + + + + + + + + :/plugins/qad/icons/dsettings/OSNAP_PROGP.png + + + + + + + + + + :/plugins/qad/icons/dsettings/OSNAP_EXTINT.png + + + + + + + + + + :/plugins/qad/icons/dsettings/OSNAP_PERP.png + + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Perpendicular OSnap: orthogonal projection of a given point on a segment.</p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><img src=":/plugins/qad/icons/dsettings/OSNAP_ToolTIP_PERP.png" /></p></body></html> + + + Perpendicular + + + false + + + + + + + + + + :/plugins/qad/icons/dsettings/OSNAP_TANP.png + + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">tangent point on a curve of a line passing through a given point.</p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><img src=":/plugins/qad/icons/dsettings/OSNAP_ToolTIP_TANP.png" /></p></body></html> + + + Tangent + + + false + + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Extension OSnap: point on the segment extension until the cursor position.</p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><img src=":/plugins/qad/icons/dsettings/OSNAP_ToolTIP_EXTP.png" /></p></body></html> + + + Extend + + + false + + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Parallel OSnap: point on a line, passing through a given point, parallel to a segment.</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><img src=":/plugins/qad/icons/dsettings/OSNAP_ToolTIP_PARLP.png" /></p></body></html> + + + Parallel + + + false + + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Progressive OSnap: point at a given distance along a geometry line: from a vertex we can set a point at a distance measured along the geometry line.</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><img src=":/plugins/qad/icons/dsettings/OSNAP_ToolTIP_PROGRESP.png" /></p></body></html> + + + Progressive + + + false + + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Intersection on extension OSnap: intersection point of the extensions of two segments.</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><img src=":/plugins/qad/icons/dsettings/OSNAP_ToolTIP_EXTINT.png" /></p></body></html> + + + Intersection on extension + + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Quadrant OSnap: intersections of the cartesian axis with a circumference of a circle or an arc.</p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><img src=":/plugins/qad/icons/dsettings/OSNAP_ToolTIP_QUADP.png" /></p></body></html> + + + Quadrant + + + false + + + + + + + + + + :/plugins/qad/icons/dsettings/OSNAP_QUADP.png + + + + + + + + + 410 + 210 + 41 + 20 + + + + + + + 30 + 20 + 171 + 261 + + + + + QLayout::SetMinimumSize + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:8pt;">Start / End OSnap: starting and ending vertices of a linear geometry.</span></p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><img src=":/plugins/qad/icons/dsettings/OSNAP_ToolTIP_END_PLINE.png" /></p></body></html> + + + Start / End + + + + + + + + + + :/plugins/qad/icons/dsettings/OSNAP_ENDP.png + + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Start / End segment OSnap: starting and ending vertices of each segment of a geometry.</p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><img src=":/plugins/qad/icons/dsettings/OSNAP_ToolTIP_ENDP.png" /></p></body></html> + + + Segment Start / End + + + false + + + + + + + + + + :/plugins/qad/icons/dsettings/OSNAP_MEDP.png + + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Middle point OSnap: middle point of each segment of a geometry.</p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><img src=":/plugins/qad/icons/dsettings/OSNAP_ToolTIP_MIDP.png" /></p></body></html> + + + Middle point + + + false + + + + + + + + + + :/plugins/qad/icons/dsettings/OSNAP_INTP.png + + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Intersection OSnap: intersection between two segments.</p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><img src=":/plugins/qad/icons/dsettings/OSNAP_ToolTIP_INTP.png" /></p></body></html> + + + Intersection + + + false + + + + + + + + + + :/plugins/qad/icons/dsettings/OSNAP_CENP.png + + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Center OSnap: center of a circle or arc or centroid of an areal geometry.</p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><img src=":/plugins/qad/icons/dsettings/OSNAP_ToolTIP_CENP.png" /></p></body></html> + + + Center + + + false + + + + + + + + + + :/plugins/qad/icons/dsettings/OSNAP_NODP.png + + + false + + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Node OSnap: coordinate of a punctual geometry.</p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><img src=":/plugins/qad/icons/dsettings/OSNAP_ToolTIP_NODP.png" /></p></body></html> + + + Node + + + false + + + + + + + + + + :/plugins/qad/icons/dsettings/OSNAP_NEARP.png + + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Near OSnap: point of a segment close to the cursor position.</p> +<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><img src=":/plugins/qad/icons/dsettings/OSNAP_ToolTIP_NEARP.png" /></p></body></html> + + + Near + + + false + + + + + + + + + + :/plugins/qad/icons/dsettings/OSNAP_ENDP.png + + + + + + + + + + 10 + 10 + 191 + 17 + + + + <html><head/><body><p>Turns object snap on and off. The selected object snap modes are active when the object snap is activated (system variable OSMODE).</p></body></html> + + + Object Snap (F3) + + + + + + 210 + 10 + 251 + 20 + + + + <html><head/><body><p>Turns object snap tracking on and off. Using the object snap tracking, the cursor can track along alignment paths that are based on object snap points. To use the object snap tracking, select one or more object snap (system variable AUTOSNAP).</p></body></html> + + + Object Snap Tracking (F11) + + + + + + Polar Tracking + + + + + 10 + 10 + 171 + 17 + + + + Turns polar tracking on and off (system variable AUTOSNAP). + + + Polar Tracking (F10) + + + + + + 10 + 40 + 181 + 321 + + + + Polar angle settings + + + + + 10 + 20 + 121 + 16 + + + + Increment angle: + + + + + + 10 + 40 + 151 + 22 + + + + true + + + + + + 10 + 70 + 131 + 17 + + + + Makes additional angles in the list available for polar tracking. (POLARMODE and POLARADDANG system variables). Note: Additional angles are absolute, not incremental. + + + Additional angles + + + + + + 10 + 90 + 81 + 221 + + + + <html><head/><body><p>If Additional Angles is selected, the available additional angles are listed.</p><p>To add new angles, choose New. To remove existing angles, click Delete.</p><p>(POLARADDANG system variable)</p></body></html> + + + QAbstractItemView::MultiSelection + + + + + + 100 + 90 + 71 + 23 + + + + Adds additional polar tracking alignment angles. + + + New + + + + + + 100 + 120 + 71 + 23 + + + + Deletes selected additional angles. + + + Delete + + + + + + + 200 + 40 + 301 + 81 + + + + Object Snap Tracking Settings + + + + + 10 + 30 + 271 + 17 + + + + Displays only orthogonal (horizontal/vertical) object snap tracking paths for acquired object snap points when object snap tracking is on (POLARMODE system variable). + + + Track orthogonally only + + + + + + 10 + 50 + 281 + 21 + + + + Applies polar tracking settings to object snap tracking. When you use object snap tracking, the cursor tracks along polar alignment angles from acquired object snap points (POLARMODE system variable). + + + Track using polar angle settings + + + + + + + 200 + 140 + 301 + 71 + + + + Polar Angle measurement + + + + + 10 + 30 + 271 + 17 + + + + Bases polar tracking angles on the current user coordinate system. + + + Absolute + + + + + + 10 + 50 + 271 + 17 + + + + Bases polar tracking angles on the last segment drawn. + + + Relative to last segment + + + + + + + Dynamic Input + + + + + 10 + 10 + 221 + 17 + + + + Turns on pointer input. When pointer input and dimensional input are both turned on, dimensional input supersedes pointer input when it is available. (DYNMODE system variable) + + + Enable Pointer Input + + + + + + 240 + 10 + 261 + 20 + + + + Turns on dimensional input. Dimensional input is not available for some commands that prompt for a second point. (DYNMODE system variable) + + + Enable Dimension Input where possible + + + + + + 10 + 30 + 221 + 141 + + + + Pointer Input + + + + + 54 + 110 + 111 + 23 + + + + Displays the Pointer Input Settings dialog box. + + + Settings... + + + + + + 10 + 20 + 201 + 81 + + + + QFrame::Box + + + + + + :/plugins/qad/icons/dsettings/pointer_input.png + + + true + + + + + + + 240 + 30 + 261 + 141 + + + + Dimension Input + + + + + 80 + 110 + 111 + 23 + + + + Displays the Dimension Input Settings dialog box. + + + Settings... + + + + + + 10 + 20 + 241 + 81 + + + + QFrame::Box + + + + + + :/plugins/qad/icons/dsettings/dimension_input.png + + + true + + + + + + + 10 + 180 + 491 + 111 + + + + Dynamic Prompts + + + + + 219 + 20 + 261 + 41 + + + + + 0 + 0 + + + + Displays prompts in Dynamic Input tooltips. (DYNPROMPT system variable) + + + Show command prompting and +command input near the crosshairs + + + false + + + + + + 10 + 20 + 201 + 81 + + + + QFrame::Box + + + + + + :/plugins/qad/icons/dsettings/dynamic_prompts.png + + + true + + + + + + + 110 + 320 + 311 + 23 + + + + Displays the Tooltip Appearance dialog box. + + + Drafting Tooltip Appearence... + + + + + + + + 230 + 420 + 295 + 30 + + + + + + + OK + + + + + + + Cancel + + + + + + + ? + + + + + + + + + + + + pushButton_DeSelectALL + pressed() + DSettings_Dialog + ButtonDeselectALL_Pressed() + + + 357 + 385 + + + 439 + 0 + + + + + pushButton_SelectALL + pressed() + DSettings_Dialog + ButtonSelectALL_Pressed() + + + 171 + 385 + + + 436 + 0 + + + + + pushButton_HELP + clicked() + DSettings_Dialog + ButtonHELP_Pressed() + + + 487 + 440 + + + 372 + 426 + + + + + okButton + clicked() + DSettings_Dialog + ButtonBOX_Accepted() + + + 251 + 434 + + + 8 + 418 + + + + + cancelButton + clicked() + DSettings_Dialog + reject() + + + 332 + 429 + + + 136 + 413 + + + + + pushButton_DI_PointerInputSettings + clicked() + DSettings_Dialog + Button_DI_PointerInputSettings_Pressed() + + + 135 + 186 + + + 3 + 183 + + + + + pushButton_DI_DimensionInputSettings + clicked() + DSettings_Dialog + Button_DI_DimensionInputSettings_Pressed() + + + 363 + 183 + + + 494 + 192 + + + + + pushButton_DI_TootipAppearance + clicked() + DSettings_Dialog + Button_DI_TootipAppearance_Pressed() + + + 287 + 362 + + + 494 + 360 + + + + + pushButton_AddAngle + clicked() + DSettings_Dialog + ButtonNewAngle() + + + 150 + 174 + + + 46 + 432 + + + + + pushButton_DelAngle + clicked() + DSettings_Dialog + ButtonDelAngle() + + + 168 + 206 + + + 179 + 433 + + + + + checkBox_AdditionaltAngles + clicked() + DSettings_Dialog + CheckAdditionalAngles() + + + 37 + 149 + + + 93 + 442 + + + + + + ButtonSelectALL_Pressed() + ButtonDeselectALL_Pressed() + ButtonBOX_Accepted() + Hovered_Cursor_ENDP() + Hovered_Cursor_MIDP() + ContextMenu_Activated_ENDP() + MainTabChanged() + ButtonHELP_Pressed() + Button_DI_PointerInputSettings_Pressed() + Button_DI_DimensionInputSettings_Pressed() + Button_DI_TootipAppearance_Pressed() + ButtonNewAngle() + ButtonDelAngle() + CheckAdditionalAngles() + + diff --git a/qad_dsettings_dlg.py b/qad_dsettings_dlg.py index 0003e977..39d2a923 100644 --- a/qad_dsettings_dlg.py +++ b/qad_dsettings_dlg.py @@ -1,239 +1,879 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - comando DSETTINGS per impostazione disegno - - ------------------- - begin : 2013-05-22 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -from qgis.core import QgsApplication -from qgis.utils import * - - -import qad_dsettings_ui - - -from qad_variables import * -from qad_snapper import * -from qad_msg import QadMsg, qadShowPluginHelp -import qad_utils - - -####################################################################################### -# Classe che gestisce l'interfaccia grafica del comando DSETTINGS -class QadDSETTINGSDialog(QDialog, QObject, qad_dsettings_ui.Ui_DSettings_Dialog): - def __init__(self, plugIn): - self.plugIn = plugIn - self.iface = self.plugIn.iface.mainWindow() - QDialog.__init__(self, self.iface) - self.setupUi(self) - - # Inizializzazione del TAB che riguarda gli SNAP ad oggetto - self.init_osnap_tab() - - # Inizializzazione del TAB che riguarda il puntamento polare - self.init_polar_tab() - - self.tabWidget.setCurrentIndex(0) - - - def init_osnap_tab(self): - # Inizializzazione del TAB che riguarda gli SNAP ad oggetto - - # Memorizzo il valore dell'OSMODE per determinare gli osnap impostati - OsMode = QadVariables.get(QadMsg.translate("Environment variables", "OSMODE")) - self.checkBox_CENP.setChecked(OsMode & QadSnapTypeEnum.CEN) - self.checkBox_ENDP.setChecked(OsMode & QadSnapTypeEnum.END) - self.checkBox_END_PLINE.setChecked(OsMode & QadSnapTypeEnum.END_PLINE) - self.checkBox_EXTP.setChecked(OsMode & QadSnapTypeEnum.EXT) - self.checkBox_INTP.setChecked(OsMode & QadSnapTypeEnum.INT) - self.checkBox_MIDP.setChecked(OsMode & QadSnapTypeEnum.MID) - self.checkBox_NODP.setChecked(OsMode & QadSnapTypeEnum.NOD) - self.checkBox_QUADP.setChecked(OsMode & QadSnapTypeEnum.QUA) - #self.checkBox_INSP.setChecked(OsMode & QadSnapTypeEnum.INS) - #self.checkBox_INTAPP.setChecked(OsMode & QadSnapTypeEnum.APP) - self.checkBox_NEARP.setChecked(OsMode & QadSnapTypeEnum.NEA) - self.checkBox_PERP.setChecked(OsMode & QadSnapTypeEnum.PER) - self.checkBox_PARALP.setChecked(OsMode & QadSnapTypeEnum.PAR) - self.checkBox_PROGRESP.setChecked(OsMode & QadSnapTypeEnum.PR) - self.checkBox_TANP.setChecked(OsMode & QadSnapTypeEnum.TAN) - self.checkBox_EXT_INT.setChecked(OsMode & QadSnapTypeEnum.EXT_INT) - self.checkBox_TANP.setChecked(OsMode & QadSnapTypeEnum.TAN) - self.checkBox_IsOsnapON.setChecked(not(OsMode & QadSnapTypeEnum.DISABLE)) - - ProgrDistance = QadVariables.get(QadMsg.translate("Environment variables", "OSPROGRDISTANCE")) - stringA = str(ProgrDistance) - self.lineEdit_ProgrDistance.setText(stringA) - self.lineEdit_ProgrDistance.setValidator(QDoubleValidator(self.lineEdit_ProgrDistance)) - self.lineEdit_ProgrDistance.installEventFilter(self) - - - def init_polar_tab(self): - # Inizializzazione del TAB che riguarda il puntamento polare - UserAngle = QadVariables.get(QadMsg.translate("Environment variables", "POLARANG")) - angoliDef = ["90", "45", "30", "22.5", "18", "15", "10", "5"] - self.comboBox_increment_angle.addItems(angoliDef) - stringA = str(UserAngle) - self.comboBox_increment_angle.lineEdit().setText(stringA) - self.comboBox_increment_angle.installEventFilter(self) - - AutoSnap = QadVariables.get(QadMsg.translate("Environment variables", "AUTOSNAP")) - self.checkBox_PolarPickPoint.setChecked(AutoSnap & 8) - - - def eventFilter(self, obj, event): - if event is not None: - if event.type() == QEvent.FocusOut: - if obj == self.lineEdit_ProgrDistance: - return not self.lineEdit_ProgrDistance_Validation() - elif obj == self.comboBox_increment_angle: - return not self.comboBox_increment_angle_Validation() - - # standard event processing - return QObject.eventFilter(self, obj, event); - - def ButtonSelectALL_Pressed(self): - self.checkBox_CENP.setChecked(True) - self.checkBox_ENDP.setChecked(True) - self.checkBox_END_PLINE.setChecked(True) - self.checkBox_EXTP.setChecked(True) - self.checkBox_INTP.setChecked(True) - self.checkBox_MIDP.setChecked(True) - self.checkBox_NODP.setChecked(True) - self.checkBox_QUADP.setChecked(True) - #self.checkBox_INSP.setChecked(True) - #self.checkBox_INTAPP.setChecked(True) - self.checkBox_NEARP.setChecked(True) - self.checkBox_PARALP.setChecked(True) - self.checkBox_PERP.setChecked(True) - self.checkBox_PROGRESP.setChecked(True) - self.checkBox_TANP.setChecked(True) - self.checkBox_EXT_INT.setChecked(True) - return True - - - def ButtonDeselectALL_Pressed(self): - self.checkBox_CENP.setChecked(False) - self.checkBox_ENDP.setChecked(False) - self.checkBox_END_PLINE.setChecked(False) - self.checkBox_EXTP.setChecked(False) - self.checkBox_INTP.setChecked(False) - self.checkBox_MIDP.setChecked(False) - self.checkBox_NODP.setChecked(False) - self.checkBox_QUADP.setChecked(False) - #self.checkBox_INSP.setChecked(False) - #self.checkBox_INTAPP.setChecked(False) - self.checkBox_NEARP.setChecked(False) - self.checkBox_PARALP.setChecked(False) - self.checkBox_PERP.setChecked(False) - self.checkBox_PROGRESP.setChecked(False) - self.checkBox_TANP.setChecked(False) - self.checkBox_EXT_INT.setChecked(False) - return True - - def ButtonBOX_Accepted(self): - newOSMODE = 0 - if self.checkBox_CENP.checkState() == Qt.Checked: - newOSMODE = newOSMODE | QadSnapTypeEnum.CEN - if self.checkBox_ENDP.checkState() == Qt.Checked: - newOSMODE = newOSMODE | QadSnapTypeEnum.END - if self.checkBox_END_PLINE.checkState() == Qt.Checked: - newOSMODE = newOSMODE | QadSnapTypeEnum.END_PLINE - if self.checkBox_EXTP.checkState() == Qt.Checked: - newOSMODE = newOSMODE | QadSnapTypeEnum.EXT - if self.checkBox_INTP.checkState() == Qt.Checked: - newOSMODE = newOSMODE | QadSnapTypeEnum.INT - if self.checkBox_MIDP.checkState() == Qt.Checked: - newOSMODE = newOSMODE | QadSnapTypeEnum.MID - if self.checkBox_NODP.checkState() == Qt.Checked: - newOSMODE = newOSMODE | QadSnapTypeEnum.NOD - if self.checkBox_QUADP.checkState() == Qt.Checked: - newOSMODE = newOSMODE | QadSnapTypeEnum.QUA - #if self.checkBox_INSP.checkState() == Qt.Checked: - # newOSMODE = newOSMODE | QadSnapTypeEnum.INS - #if self.checkBox_INTAPP.checkState() == Qt.Checked: - # newOSMODE = newOSMODE | QadSnapTypeEnum.APP - if self.checkBox_NEARP.checkState() == Qt.Checked: - newOSMODE = newOSMODE | QadSnapTypeEnum.NEA - if self.checkBox_PARALP.checkState() == Qt.Checked: - newOSMODE = newOSMODE | QadSnapTypeEnum.PAR - if self.checkBox_PERP.checkState() == Qt.Checked: - newOSMODE = newOSMODE | QadSnapTypeEnum.PER - if self.checkBox_PROGRESP.checkState() == Qt.Checked: - newOSMODE = newOSMODE | QadSnapTypeEnum.PR - if self.checkBox_TANP.checkState() == Qt.Checked: - newOSMODE = newOSMODE | QadSnapTypeEnum.TAN - if self.checkBox_EXT_INT.checkState() == Qt.Checked: - newOSMODE = newOSMODE | QadSnapTypeEnum.EXT_INT - if self.checkBox_IsOsnapON.checkState() == Qt.Unchecked: - newOSMODE = newOSMODE | QadSnapTypeEnum.DISABLE - QadVariables.set(QadMsg.translate("Environment variables", "OSMODE"), newOSMODE) - - AutoSnap = QadVariables.get(QadMsg.translate("Environment variables", "AUTOSNAP")) - if self.checkBox_PolarPickPoint.checkState() == Qt.Checked: - AutoSnap = AutoSnap | 8 - elif AutoSnap & 8: - AutoSnap = AutoSnap - 8 - QadVariables.set(QadMsg.translate("Environment variables", "AUTOSNAP"), AutoSnap) - - # Memorizzo il valore di PolarANG - SUserAngle = self.comboBox_increment_angle.currentText() - UserAngle = qad_utils.str2float(SUserAngle) - QadVariables.set(QadMsg.translate("Environment variables", "POLARANG"), UserAngle) - - SProgrDist = self.lineEdit_ProgrDistance.text() - ProgrDist = qad_utils.str2float(SProgrDist) - QadVariables.set(QadMsg.translate("Environment variables", "OSPROGRDISTANCE"), ProgrDist) - - QadVariables.save() - - - self.close() - return True - - - def lineEdit_ProgrDistance_Validation(self): - string = self.lineEdit_ProgrDistance.text() - if qad_utils.str2float(string) is None or qad_utils.str2float(string) == 0: - msg = QadMsg.translate("DSettings_Dialog", "Invalid progressive distance object snap: enter a number not zero.") - QMessageBox.critical(self, "QAD", msg) - self.lineEdit_ProgrDistance.setFocus() - self.lineEdit_ProgrDistance.selectAll() - return False - return True - - - def comboBox_increment_angle_Validation(self): - string = self.comboBox_increment_angle.lineEdit().text() - if qad_utils.str2float(string) is None or qad_utils.str2float(string) <= 0 or qad_utils.str2float(string) >= 360: - msg = QadMsg.translate("DSettings_Dialog", "Invalid increment angle: enter a number greater than zero and less than 360 degree.") - QMessageBox.critical(self, "QAD", msg) - self.comboBox_increment_angle.lineEdit().setFocus() - self.comboBox_increment_angle.lineEdit().selectAll() - return False - return True - - - def ButtonHELP_Pressed(self): - qadShowPluginHelp(QadMsg.translate("Help", "DSETTINGS")) +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + comando DSETTINGS per impostazione disegno + + ------------------- + begin : 2013-05-22 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.PyQt.QtCore import Qt, QObject, QEvent +from qgis.PyQt.QtGui import QDoubleValidator, QIntValidator, QColor, QPainter, \ + QFontMetrics, QStandardItemModel, QStandardItem +from qgis.PyQt.QtWidgets import QDialog, QMessageBox, QTextEdit, QSizePolicy, \ + QWidget, QInputDialog + +from . import qad_dsettings_ui +from . import qad_dimensioninput_settings_ui +from . import qad_pointerinput_settings_ui +from . import qad_tooltip_appearance_ui + + +from .qad_variables import QadVariable, QadVariables, QadAUTOSNAPEnum, QadPOLARMODEnum, POLARADDANG_to_list +from .qad_snapper import QadSnapTypeEnum +from .qad_msg import QadMsg, qadShowPluginPDFHelp +from . import qad_utils +from .qad_windowcolor_dlg import QadColorContextEnum, QadColorElementEnum, QadWindowColorDialog + + +# =============================================================================== +# QadDSETTINGSTabIndexEnum class. +# =============================================================================== +class QadDSETTINGSTabIndexEnum(): + OBJECT_SNAP = 0 + POLAR_TRACKING = 1 + DYNAMIC_INPUT = 2 + + +####################################################################################### +# Classe che gestisce l'interfaccia grafica del comando DSETTINGS +class QadDSETTINGSDialog(QDialog, QObject, qad_dsettings_ui.Ui_DSettings_Dialog): + def __init__(self, plugIn, dsettingsTabIndex = None): + self.plugIn = plugIn + self.iface = self.plugIn.iface.mainWindow() + + QDialog.__init__(self) + # non passo il parent perchè altrimenti il font e la sua dimensione verrebbero ereditati dalla dialog scombinando tutto + #QDialog.__init__(self, self.iface) + + self.setupUi(self) + self.setWindowTitle(QadMsg.getQADTitle() + " - " + self.windowTitle()) + + # Inizializzazione del TAB che riguarda gli SNAP ad oggetto + self.init_osnap_tab() + + # Inizializzazione del TAB che riguarda il puntamento polare + self.init_polar_tab() + + # Inizializzazione del TAB che riguarda l'input dinamico + self.init_dynamic_input_tab() + + if dsettingsTabIndex is not None: + self.tabWidget.setCurrentIndex(dsettingsTabIndex) + else: + if self.plugIn.dsettingsLastUsedTabIndex == -1: # non inizializzato + self.plugIn.dsettingsLastUsedTabIndex = QadDSETTINGSTabIndexEnum.OBJECT_SNAP + self.tabWidget.setCurrentIndex(self.plugIn.dsettingsLastUsedTabIndex) + + + ###################################### + # TAB che riguarda gli SNAP ad oggetto + def init_osnap_tab(self): + # Inizializzazione del TAB che riguarda gli SNAP ad oggetto + + # Memorizzo il valore dell'OSMODE per determinare gli osnap impostati + OsMode = QadVariables.get(QadMsg.translate("Environment variables", "OSMODE")) + self.checkBox_CENP.setChecked(OsMode & QadSnapTypeEnum.CEN) + self.checkBox_ENDP.setChecked(OsMode & QadSnapTypeEnum.END) + self.checkBox_END_PLINE.setChecked(OsMode & QadSnapTypeEnum.END_PLINE) + self.checkBox_EXTP.setChecked(OsMode & QadSnapTypeEnum.EXT) + self.checkBox_INTP.setChecked(OsMode & QadSnapTypeEnum.INT) + self.checkBox_MIDP.setChecked(OsMode & QadSnapTypeEnum.MID) + self.checkBox_NODP.setChecked(OsMode & QadSnapTypeEnum.NOD) + self.checkBox_QUADP.setChecked(OsMode & QadSnapTypeEnum.QUA) + #self.checkBox_INSP.setChecked(OsMode & QadSnapTypeEnum.INS) + #self.checkBox_INTAPP.setChecked(OsMode & QadSnapTypeEnum.APP) + self.checkBox_NEARP.setChecked(OsMode & QadSnapTypeEnum.NEA) + self.checkBox_PERP.setChecked(OsMode & QadSnapTypeEnum.PER) + self.checkBox_PARALP.setChecked(OsMode & QadSnapTypeEnum.PAR) + self.checkBox_PROGRESP.setChecked(OsMode & QadSnapTypeEnum.PR) + self.checkBox_TANP.setChecked(OsMode & QadSnapTypeEnum.TAN) + self.checkBox_EXT_INT.setChecked(OsMode & QadSnapTypeEnum.EXT_INT) + self.checkBox_TANP.setChecked(OsMode & QadSnapTypeEnum.TAN) + + self.checkBox_IsOsnapON.setChecked(not(OsMode & QadSnapTypeEnum.DISABLE)) + + AutoSnap = QadVariables.get(QadMsg.translate("Environment variables", "AUTOSNAP")) + self.checkBox_ObjectSnapTracking.setChecked(AutoSnap & QadAUTOSNAPEnum.OBJ_SNAP_TRACKING) + + ProgrDistance = QadVariables.get(QadMsg.translate("Environment variables", "OSPROGRDISTANCE")) + stringA = str(ProgrDistance) + self.lineEdit_ProgrDistance.setText(stringA) + self.lineEdit_ProgrDistance.setValidator(QDoubleValidator(self.lineEdit_ProgrDistance)) + self.lineEdit_ProgrDistance.installEventFilter(self) + + + def accept_osnap_tab(self): + # Memorizzo il valore di OSMODE + newOSMODE = 0 + if self.checkBox_CENP.checkState() == Qt.Checked: + newOSMODE = newOSMODE | QadSnapTypeEnum.CEN + if self.checkBox_ENDP.checkState() == Qt.Checked: + newOSMODE = newOSMODE | QadSnapTypeEnum.END + if self.checkBox_END_PLINE.checkState() == Qt.Checked: + newOSMODE = newOSMODE | QadSnapTypeEnum.END_PLINE + if self.checkBox_EXTP.checkState() == Qt.Checked: + newOSMODE = newOSMODE | QadSnapTypeEnum.EXT + if self.checkBox_INTP.checkState() == Qt.Checked: + newOSMODE = newOSMODE | QadSnapTypeEnum.INT + if self.checkBox_MIDP.checkState() == Qt.Checked: + newOSMODE = newOSMODE | QadSnapTypeEnum.MID + if self.checkBox_NODP.checkState() == Qt.Checked: + newOSMODE = newOSMODE | QadSnapTypeEnum.NOD + if self.checkBox_QUADP.checkState() == Qt.Checked: + newOSMODE = newOSMODE | QadSnapTypeEnum.QUA + #if self.checkBox_INSP.checkState() == Qt.Checked: + # newOSMODE = newOSMODE | QadSnapTypeEnum.INS + #if self.checkBox_INTAPP.checkState() == Qt.Checked: + # newOSMODE = newOSMODE | QadSnapTypeEnum.APP + if self.checkBox_NEARP.checkState() == Qt.Checked: + newOSMODE = newOSMODE | QadSnapTypeEnum.NEA + if self.checkBox_PARALP.checkState() == Qt.Checked: + newOSMODE = newOSMODE | QadSnapTypeEnum.PAR + if self.checkBox_PERP.checkState() == Qt.Checked: + newOSMODE = newOSMODE | QadSnapTypeEnum.PER + if self.checkBox_PROGRESP.checkState() == Qt.Checked: + newOSMODE = newOSMODE | QadSnapTypeEnum.PR + if self.checkBox_TANP.checkState() == Qt.Checked: + newOSMODE = newOSMODE | QadSnapTypeEnum.TAN + if self.checkBox_EXT_INT.checkState() == Qt.Checked: + newOSMODE = newOSMODE | QadSnapTypeEnum.EXT_INT + if self.checkBox_IsOsnapON.checkState() == Qt.Unchecked: + newOSMODE = newOSMODE | QadSnapTypeEnum.DISABLE + QadVariables.set(QadMsg.translate("Environment variables", "OSMODE"), newOSMODE) + + # Memorizzo il valore di OSPROGRDISTANCE + SProgrDist = self.lineEdit_ProgrDistance.text() + ProgrDist = qad_utils.str2float(SProgrDist) + QadVariables.set(QadMsg.translate("Environment variables", "OSPROGRDISTANCE"), ProgrDist) + + # Memorizzo il valore di AUTOSNAP + AutoSnap = QadVariables.get(QadMsg.translate("Environment variables", "AUTOSNAP")) + if self.checkBox_ObjectSnapTracking.checkState() == Qt.Checked: + AutoSnap = AutoSnap | QadAUTOSNAPEnum.OBJ_SNAP_TRACKING + elif AutoSnap & QadAUTOSNAPEnum.OBJ_SNAP_TRACKING: + AutoSnap = AutoSnap - QadAUTOSNAPEnum.OBJ_SNAP_TRACKING + QadVariables.set(QadMsg.translate("Environment variables", "AUTOSNAP"), AutoSnap) + + + def ButtonSelectALL_Pressed(self): + self.checkBox_CENP.setChecked(True) + self.checkBox_ENDP.setChecked(True) + self.checkBox_END_PLINE.setChecked(True) + self.checkBox_EXTP.setChecked(True) + self.checkBox_INTP.setChecked(True) + self.checkBox_MIDP.setChecked(True) + self.checkBox_NODP.setChecked(True) + self.checkBox_QUADP.setChecked(True) + #self.checkBox_INSP.setChecked(True) + #self.checkBox_INTAPP.setChecked(True) + self.checkBox_NEARP.setChecked(True) + self.checkBox_PARALP.setChecked(True) + self.checkBox_PERP.setChecked(True) + self.checkBox_PROGRESP.setChecked(True) + self.checkBox_TANP.setChecked(True) + self.checkBox_EXT_INT.setChecked(True) + return True + + + def ButtonDeselectALL_Pressed(self): + self.checkBox_CENP.setChecked(False) + self.checkBox_ENDP.setChecked(False) + self.checkBox_END_PLINE.setChecked(False) + self.checkBox_EXTP.setChecked(False) + self.checkBox_INTP.setChecked(False) + self.checkBox_MIDP.setChecked(False) + self.checkBox_NODP.setChecked(False) + self.checkBox_QUADP.setChecked(False) + #self.checkBox_INSP.setChecked(False) + #self.checkBox_INTAPP.setChecked(False) + self.checkBox_NEARP.setChecked(False) + self.checkBox_PARALP.setChecked(False) + self.checkBox_PERP.setChecked(False) + self.checkBox_PROGRESP.setChecked(False) + self.checkBox_TANP.setChecked(False) + self.checkBox_EXT_INT.setChecked(False) + return True + + + def lineEdit_ProgrDistance_Validation(self): + string = self.lineEdit_ProgrDistance.text() + if qad_utils.str2float(string) is None or qad_utils.str2float(string) == 0: + msg = QadMsg.translate("DSettings_Dialog", "Invalid progressive distance object snap: enter a number not zero.") + QMessageBox.critical(self, QadMsg.getQADTitle(), msg) + self.lineEdit_ProgrDistance.setFocus() + self.lineEdit_ProgrDistance.selectAll() + return False + return True + + + ###################################### + # TAB che riguarda il puntamento polare + def init_polar_tab(self): + # Inizializzazione del TAB che riguarda il puntamento polare + UserAngle = QadVariables.get(QadMsg.translate("Environment variables", "POLARANG")) + angoliDef = ["90", "45", "30", "22.5", "18", "15", "10", "5"] + self.comboBox_increment_angle.addItems(angoliDef) + stringA = str(UserAngle) + self.comboBox_increment_angle.lineEdit().setText(stringA) + self.comboBox_increment_angle.installEventFilter(self) + + AutoSnap = QadVariables.get(QadMsg.translate("Environment variables", "AUTOSNAP")) + self.checkBox_PolarPickPoint.setChecked(AutoSnap & QadAUTOSNAPEnum.POLAR_TRACKING) + + PolarMode = QadVariables.get(QadMsg.translate("Environment variables", "POLARMODE")) + if PolarMode & QadPOLARMODEnum.POLAR_TRACKING: + self.radioButton_OsnapPolarAngle.setChecked(True) + else: + self.radioButton_OsnapOrtho.setChecked(True) + + if PolarMode & QadPOLARMODEnum.MEASURE_RELATIVE_ANGLE: + self.radioButton_OsnapPolarRelative.setChecked(True) + else: + self.radioButton_OsnapPolarAbolute.setChecked(True) + + if PolarMode & QadPOLARMODEnum.ADDITIONAL_ANGLES: + self.checkBox_AdditionaltAngles.setChecked(True) + self.CheckAdditionalAngles() + + PolarAddAngles = QadVariables.get(QadMsg.translate("Environment variables", "POLARADDANG")) + + addAngleList = POLARADDANG_to_list(PolarAddAngles) # es. "1;2.3" genera la lista in ordine crescente 1 2.3 + model = QStandardItemModel() + self.listView_AdditionalAngles.setModel(model) + for addAngle in addAngleList: + item = QStandardItem(qad_utils.numToStringFmt(addAngle)) + item.setEditable(False) + model.appendRow(item) + + + def accept_polar_tab(self): + # Memorizzo il valore di AUTOSNAP + AutoSnap = QadVariables.get(QadMsg.translate("Environment variables", "AUTOSNAP")) + if self.checkBox_PolarPickPoint.checkState() == Qt.Checked: + AutoSnap = AutoSnap | QadAUTOSNAPEnum.POLAR_TRACKING + elif AutoSnap & QadAUTOSNAPEnum.POLAR_TRACKING: + AutoSnap = AutoSnap - QadAUTOSNAPEnum.POLAR_TRACKING + QadVariables.set(QadMsg.translate("Environment variables", "AUTOSNAP"), AutoSnap) + + # Memorizzo il valore di POLARANG + SUserAngle = self.comboBox_increment_angle.currentText() + UserAngle = qad_utils.str2float(SUserAngle) + QadVariables.set(QadMsg.translate("Environment variables", "POLARANG"), UserAngle) + + # Memorizzo il valore di POLARMODE + PolarMode = 0 + if self.radioButton_OsnapPolarAngle.isChecked(): + PolarMode = PolarMode | QadPOLARMODEnum.POLAR_TRACKING + if self.radioButton_OsnapPolarRelative.isChecked(): + PolarMode = PolarMode | QadPOLARMODEnum.MEASURE_RELATIVE_ANGLE + if self.checkBox_AdditionaltAngles.isChecked(): + PolarMode = PolarMode | QadPOLARMODEnum.ADDITIONAL_ANGLES + QadVariables.set(QadMsg.translate("Environment variables", "POLARMODE"), PolarMode) + + # Memorizzo il valore di POLARADDANG + PolarAddAngles = "" + model = self.listView_AdditionalAngles.model() + for row in range(0, model.rowCount()): + index = model.index(row, 0) + if row > 0: + PolarAddAngles += ";" + PolarAddAngles += index.data() + QadVariables.set(QadMsg.translate("Environment variables", "POLARADDANG"), PolarAddAngles) + + + def comboBox_increment_angle_Validation(self): + string = self.comboBox_increment_angle.lineEdit().text() + if qad_utils.str2float(string) is None or qad_utils.str2float(string) <= 0 or qad_utils.str2float(string) >= 360: + msg = QadMsg.translate("DSettings_Dialog", "Invalid increment angle: enter a number greater than zero and less than 360 degree.") + QMessageBox.critical(self, QadMsg.getQADTitle(), msg) + self.comboBox_increment_angle.lineEdit().setFocus() + self.comboBox_increment_angle.lineEdit().selectAll() + return False + return True + + + def CheckAdditionalAngles(self): + if self.checkBox_AdditionaltAngles.checkState() == Qt.Checked: + self.pushButton_DelAngle.setEnabled(True) + self.listView_AdditionalAngles.setEnabled(True) + else: + self.pushButton_DelAngle.setEnabled(False) + self.listView_AdditionalAngles.setEnabled(False) + + + def ButtonNewAngle(self): + self.checkBox_AdditionaltAngles.setChecked(True) + self.CheckAdditionalAngles() + + while True: + # restituisce (valore, True o False a seconda del tasto OK o Cancel) + res = QInputDialog.getDouble(self, + QadMsg.translate("DSettings_Dialog", "QAD - Additional angle"), \ + QadMsg.translate("DSettings_Dialog", "New angle:"), \ + 0, -360, 360, 8) + if res[1] == False: + return + newAngle = res[0] + reAsk = False + # controllo che l'angolo non sia già presente + model = self.listView_AdditionalAngles.model() + for row in range(0, model.rowCount()): + index = model.index(row, 0) + angle = qad_utils.str2float(index.data()) + if newAngle == angle: + QMessageBox.critical(self, QadMsg.getQADTitle(), \ + QadMsg.translate("DSettings_Dialog", "Angle already in list")) + reAsk = True + break + if newAngle < angle: + item = QStandardItem(qad_utils.numToStringFmt(newAngle)) + item.setEditable(False) + model.insertRow(row, item) + return + + if reAsk == False: + item = QStandardItem(qad_utils.numToStringFmt(newAngle)) + item.setEditable(False) + model.appendRow(item) + return + + + def ButtonDelAngle(self): + # cancello le righe selezionate (devo ordinarle in modo inverso) + indexes = self.listView_AdditionalAngles.selectedIndexes() + for index in reversed(sorted(indexes)): + self.listView_AdditionalAngles.model().removeRow(index.row()) + + + + ###################################### + # TAB che riguarda l'input dinamico + def init_dynamic_input_tab(self): + # Inizializzazione del TAB che riguarda l'input dinamico + + # Memorizzo il valore di DYNMODE = Attiva e disattiva le funzioni di input dinamico + dynMode = QadVariables.get(QadMsg.translate("Environment variables", "DYNMODE")) + self.checkBox_DI_EnableInputPointer.setChecked(abs(dynMode) & 1) + self.checkBox_DI_EnableDimPointer.setChecked(abs(dynMode) & 2) + + # Memorizzo il valore di DYNPROMPT = Controlla la visualizzazione dei messaggi di richiesta nelle descrizioni di input dinamico + dynPrompt = QadVariables.get(QadMsg.translate("Environment variables", "DYNPROMPT")) + self.checkBox_DI_ShowPrompt.setChecked(dynPrompt == 1) + + + def Button_DI_DimensionInputSettings_Pressed(self): + Form = QadDIMINPUTDialog(self.plugIn, self) + Form.exec_() + + + def Button_DI_PointerInputSettings_Pressed(self): + Form = QadPOINTERINPUTDialog(self.plugIn, self) + Form.exec_() + + + def Button_DI_TootipAppearance_Pressed(self): + Form = QadTOOLTIPAPPEARANCEDialog(self.plugIn, self) + Form.exec_() + + + def accept_dynamic_input_tab(self): + # Memorizzo il valore di DYNMODE = Attiva e disattiva le funzioni di input dinamico + dynMode = 0 + if self.checkBox_DI_EnableInputPointer.checkState() == Qt.Checked: + dynMode = dynMode + 1 + if self.checkBox_DI_EnableDimPointer.checkState() == Qt.Checked: + dynMode = dynMode + 2 + + if QadVariables.get(QadMsg.translate("Environment variables", "DYNMODE")) < 0: + dynMode = -dynMode + QadVariables.set(QadMsg.translate("Environment variables", "DYNMODE"), dynMode) + + # Memorizzo il valore di DYNPROMPT = Controlla la visualizzazione dei messaggi di richiesta nelle descrizioni di input dinamico + dynPrompt = 1 if self.checkBox_DI_ShowPrompt.checkState() == Qt.Checked else 0 + QadVariables.set(QadMsg.translate("Environment variables", "DYNPROMPT"), dynPrompt) + + + ###################################### + # Funzioni generiche + def eventFilter(self, obj, event): + if event is not None: + if event.type() == QEvent.FocusOut: + if obj == self.lineEdit_ProgrDistance: + return not self.lineEdit_ProgrDistance_Validation() + elif obj == self.comboBox_increment_angle: + return not self.comboBox_increment_angle_Validation() + + # standard event processing + return QObject.eventFilter(self, obj, event); + + + def ButtonBOX_Accepted(self): + self.accept_osnap_tab() # salvo i valori del tab "object snap" + self.accept_polar_tab() # salvo i valori del tab "polar tracking" + self.accept_dynamic_input_tab() # salvo i valori del tab "dynamic input" + + QadVariables.save() + + self.plugIn.dsettingsLastUsedTabIndex = self.tabWidget.currentIndex() + + QDialog.accept(self) + + + def ButtonHELP_Pressed(self): + qadShowPluginPDFHelp(QadMsg.translate("Help", "DSETTINGS")) + + +####################################################################################### +# Classe che gestisce l'interfaccia grafica dei settaggi dell'input di quota +class QadDIMINPUTDialog(QDialog, QObject, qad_dimensioninput_settings_ui.Ui_DimInput_Settings_Dialog): + def __init__(self, plugIn, parent): + self.plugIn = plugIn + self.iface = self.plugIn.iface.mainWindow() + + QDialog.__init__(self, parent) + + self.setupUi(self) + self.setWindowTitle(QadMsg.getQADTitle() + " - " + self.windowTitle()) + + # Inizializzazione della finestra + self.init() + + + def init(self): + # Memorizzo il valore di DYNDIVIS = Controlla il numero di quote dinamiche visualizzate durante la modifica dello stiramento dei grip + dynDiVis = QadVariables.get(QadMsg.translate("Environment variables", "DYNDIVIS")) + if dynDiVis == 0: + self.radioShow1Dim.setChecked(True) + self.radioShow2Dim.setChecked(False) + self.radioShowMoreDim.setChecked(False) + elif dynDiVis == 1: + self.radioShow1Dim.setChecked(False) + self.radioShow2Dim.setChecked(True) + self.radioShowMoreDim.setChecked(False) + elif dynDiVis == 2: + self.radioShow1Dim.setChecked(False) + self.radioShow2Dim.setChecked(False) + self.radioShowMoreDim.setChecked(True) + + # Memorizzo il valore di DYNDIGRIP = Controlla la visualizzazione delle quote dinamiche durante la modifica dello stiramento dei grip + dynDiGrip = QadVariables.get(QadMsg.translate("Environment variables", "DYNDIGRIP")) + if dynDiGrip & 1: + self.checkResultingDim.setChecked(True) + if dynDiGrip & 2: + self.checkLengthChange.setChecked(True) + if dynDiGrip & 4: + self.checkAbsoluteAngle.setChecked(True) + if dynDiGrip & 8: + self.checkAngleChange.setChecked(True) + + self.radioShowMoreDimChecked() + + + def refreshOnRadioShowDimChecked(self): + value = True if self.radioShowMoreDim.isChecked() else False + self.checkResultingDim.setEnabled(value) + self.checkLengthChange.setEnabled(value) + self.checkAbsoluteAngle.setEnabled(value) + self.checkAngleChange.setEnabled(value) + + + def radioShow1DimChecked(self): + self.refreshOnRadioShowDimChecked() + + + def radioShow2DimChecked(self): + self.refreshOnRadioShowDimChecked() + + + def radioShowMoreDimChecked(self): + self.refreshOnRadioShowDimChecked() + + + def ButtonBOX_Accepted(self): + # Memorizzo il valore di DYNDIVIS = Controlla il numero di quote dinamiche visualizzate durante la modifica dello stiramento dei grip + if self.radioShow1Dim.isChecked(): + dynDiVis = 0 + elif self.radioShow2Dim.isChecked(): + dynDiVis = 1 + elif self.radioShowMoreDim.isChecked(): + dynDiVis = 2 + + QadVariables.set(QadMsg.translate("Environment variables", "DYNDIVIS"), dynDiVis) + + # Memorizzo il valore di DYNDIGRIP = Controlla la visualizzazione delle quote dinamiche durante la modifica dello stiramento dei grip + dynDiGrip = 0 + if self.checkResultingDim.checkState() == Qt.Checked: + dynDiGrip = dynDiGrip + 1 + if self.checkLengthChange.checkState() == Qt.Checked: + dynDiGrip = dynDiGrip + 2 + if self.checkAbsoluteAngle.checkState() == Qt.Checked: + dynDiGrip = dynDiGrip + 4 + if self.checkAngleChange.checkState() == Qt.Checked: + dynDiGrip = dynDiGrip + 8 + QadVariables.set(QadMsg.translate("Environment variables", "DYNDIGRIP"), dynDiGrip) + + QadVariables.save() + QDialog.accept(self) + + + def ButtonHELP_Pressed(self): + qadShowPluginPDFHelp(QadMsg.translate("Help", "DSETTINGS")) + + +####################################################################################### +# Classe che gestisce l'interfaccia grafica dei settaggi dell'input di quota +class QadPOINTERINPUTDialog(QDialog, QObject, qad_pointerinput_settings_ui.Ui_PointerInput_Settings_Dialog): + def __init__(self, plugIn, parent): + self.plugIn = plugIn + self.iface = self.plugIn.iface.mainWindow() + + QDialog.__init__(self, parent) + + self.setupUi(self) + self.setWindowTitle(QadMsg.getQADTitle() + " - " + self.windowTitle()) + + # Inizializzazione della finestra + self.init() + + + def init(self): + # Memorizzo il valore di DYNPIFORMAT = Determina se l'input del puntatore utilizza un formato polare o cartesiano per le coordinate + dynPiFormat = QadVariables.get(QadMsg.translate("Environment variables", "DYNPIFORMAT")) + if dynPiFormat == 0: + self.radioPolarFmt.setChecked(True) + self.radioCartesianFmt.setChecked(False) + elif dynPiFormat == 1: + self.radioPolarFmt.setChecked(False) + self.radioCartesianFmt.setChecked(True) + + # Memorizzo il valore di DYNPICOORDS = Determina se l'input del puntatore utilizza un formato relativo o assoluto per le coordinate + dynPiCoords = QadVariables.get(QadMsg.translate("Environment variables", "DYNPICOORDS")) + if dynPiCoords == 0: + self.radioRelativeCoord.setChecked(True) + self.radioAbsoluteCoord.setChecked(False) + elif dynPiCoords == 1: + self.radioRelativeCoord.setChecked(False) + self.radioAbsoluteCoord.setChecked(True) + + # Memorizzo il valore di DYNPIVIS = Controlla quando è visualizzato l'input puntatore + dynPiVis = QadVariables.get(QadMsg.translate("Environment variables", "DYNPIVIS")) + if dynPiVis == 0: + # caso da implementare + self.radioVisWhenAsksPt.setChecked(False) + self.radioVisAlways.setChecked(False) + elif dynPiVis == 1: + self.radioVisWhenAsksPt.setChecked(True) + self.radioVisAlways.setChecked(False) + elif dynPiVis == 2: + self.radioVisWhenAsksPt.setChecked(False) + self.radioVisAlways.setChecked(True) + + + def ButtonBOX_Accepted(self): + # Memorizzo il valore di DYNPIFORMAT = Determina se l'input del puntatore utilizza un formato polare o cartesiano per le coordinate + if self.radioPolarFmt.isChecked(): + dynPiFormat = 0 + elif self.radioCartesianFmt.isChecked(): + dynPiFormat = 1 + QadVariables.set(QadMsg.translate("Environment variables", "DYNPIFORMAT"), dynPiFormat) + + # Memorizzo il valore di DYNPICOORDS = Determina se l'input del puntatore utilizza un formato relativo o assoluto per le coordinate + if self.radioRelativeCoord.isChecked(): + dynPiCoords = 0 + elif self.radioAbsoluteCoord.isChecked(): + dynPiCoords = 1 + QadVariables.set(QadMsg.translate("Environment variables", "DYNPICOORDS"), dynPiCoords) + + # Memorizzo il valore di DYNPIVIS = Controlla quando è visualizzato l'input puntatore + if self.radioVisWhenAsksPt.isChecked(): + dynPiVis = 1 + elif self.radioVisAlways.isChecked(): + dynPiVis = 2 + QadVariables.set(QadMsg.translate("Environment variables", "DYNPIVIS"), dynPiVis) + + QadVariables.save() + QDialog.accept(self) + + + def ButtonHELP_Pressed(self): + qadShowPluginPDFHelp(QadMsg.translate("Help", "DSETTINGS")) + + +####################################################################################### +# Classe che gestisce l'interfaccia grafica dei settaggi dell'aspetto dei tooltip +class QadTOOLTIPAPPEARANCEDialog(QDialog, QObject, qad_tooltip_appearance_ui.Ui_ToolTipAppearance_Dialog): + def __init__(self, plugIn, parent): + self.plugIn = plugIn + self.iface = self.plugIn.iface.mainWindow() + self.preview = None + self.ColorVariables = [] # lista delle variabili di ambiente modificate dalla finestra QadWindowColorDialog + + QDialog.__init__(self, parent) + + self.setupUi(self) + self.setWindowTitle(QadMsg.getQADTitle() + " - " + self.windowTitle()) + + # Inizializzazione della finestra + self.init() + + # aggiungo il QWidget chiamato QadPreview + # che eredita la posizione di widget_Preview (che viene nascosto) + self.widget_Preview.setHidden(True) + self.preview = QadPreview(self.plugIn, self.widget_Preview.parent(), \ + qad_utils.str2int(self.edit_size.text()), \ + qad_utils.str2int(self.edit_transparency.text())) + self.preview.setGeometry(self.widget_Preview.geometry()) + self.preview.setObjectName("preview") + + + def init(self): + # Memorizzo il valore di TOOLTIPSIZE = dimensione del testo di tooltip. + var = QadVariables.getVariable(QadMsg.translate("Environment variables", "TOOLTIPSIZE")) + self.edit_size.setText(str(var.value)) + self.edit_size.setValidator(QIntValidator(self.edit_size)) + self.edit_size.installEventFilter(self) + self.slider_size.setMinimum(var.minNum) + self.slider_size.setMaximum(var.maxNum) + self.slider_size.setValue(var.value) + + # Memorizzo il valore di TOOLTIPTRANSPARENCY = Imposta la trasparenza della finestra di input dinamico. + var = QadVariables.getVariable(QadMsg.translate("Environment variables", "TOOLTIPTRANSPARENCY")) + self.edit_transparency.setText(str(var.value)) + self.edit_transparency.setValidator(QIntValidator(self.edit_transparency)) + self.edit_transparency.installEventFilter(self) + self.slider_transparency.setMinimum(var.minNum) + self.slider_transparency.setMaximum(var.maxNum) + self.slider_transparency.setValue(var.value) + + # Memorizzo il valore di DYNTOOLTIPS = Determina su quali tooltip hanno effetto le impostazioni dell'aspetto delle descrizioni. + dynTooltips = QadVariables.get(QadMsg.translate("Environment variables", "DYNTOOLTIPS")) + if dynTooltips == 0: + self.radio_for_all_tooltips.setChecked(False) + self.radio_for_DI_tooltips.setChecked(True) + elif dynTooltips == 1: + self.radio_for_all_tooltips.setChecked(True) + self.radio_for_DI_tooltips.setChecked(False) + + def slider_size_moved(self): + self.edit_size.setText(str(self.slider_size.value())) + if self.preview is not None: + self.preview.refresh(self.slider_size.value(), qad_utils.str2int(self.edit_transparency.text())) # forzo il disegno del preview + + + def edit_size_textChanged(self): + value = qad_utils.str2int(self.edit_size.text()) + if value is not None: + self.slider_size.setValue(value) + if self.preview is not None: + self.preview.refresh(value, qad_utils.str2int(self.edit_transparency.text())) # forzo il disegno del preview + + + def lineEdit_TOOLTIPSIZE_Validation(self): + varName = QadMsg.translate("Environment variables", "TOOLTIPSIZE") + var = QadVariables.getVariable(QadMsg.translate("Environment variables", varName)) + return qad_utils.intLineEditWidgetValidation(self.edit_size, \ + var, \ + QadMsg.translate("Options_Dialog", "Invalid tooltip size")) + + + def slider_transparency_moved(self): + self.edit_transparency.setText(str(self.slider_transparency.value())) + if self.preview is not None: + self.preview.refresh(qad_utils.str2int(self.edit_size.text()), self.slider_transparency.value()) # forzo il disegno del preview + + + def edit_transparency_textChanged(self): + value = qad_utils.str2int(self.edit_transparency.text()) + if value is not None: + self.slider_transparency.setValue(value) + if self.preview is not None: + self.preview.refresh(qad_utils.str2int(self.edit_size.text()), value) # forzo il disegno del preview + + + def lineEdit_TOOLTIPTRANSPARENCY_Validation(self): + varName = QadMsg.translate("Environment variables", "TOOLTIPTRANSPARENCY") + var = QadVariables.getVariable(QadMsg.translate("Environment variables", varName)) + return qad_utils.intLineEditWidgetValidation(self.edit_transparency, \ + var, \ + QadMsg.translate("Options_Dialog", "Invalid transparency value")) + + + def eventFilter(self, obj, event): + if event is not None: + if event.type() == QEvent.FocusOut: + if obj == self.edit_size: + return not self.lineEdit_TOOLTIPSIZE_Validation() + elif obj == self.edit_transparency: + return not self.lineEdit_TOOLTIPTRANSPARENCY_Validation() + + # standard event processing + return QObject.eventFilter(self, obj, event); + + + def Button_TooltipColors_Pressed(self): + Form = QadWindowColorDialog(self.plugIn, self, QadColorContextEnum.MODEL_SPACE_2D, QadColorElementEnum.DI_COMMAND_DESCR) + + if Form.exec_() == QDialog.Accepted: + # copio i valori dei colori in QadVariables + self.ColorVariables = Form.getSysVariableList() + + + # ============================================================================ + # getSysVariableList + # ============================================================================ + def getSysVariableList(self): + # ritorna una lista di variabili gestite da questa finestra + variables = list(self.ColorVariables) # copio la lista ColorVariables + + variable = QadVariables.getVariable(QadMsg.translate("Environment variables", "TOOLTIPSIZE")) + varValue = qad_utils.str2int(self.edit_size.text()) + variables.append(QadVariable(variable.name, varValue, variable.typeValue)) + + variable = QadVariables.getVariable(QadMsg.translate("Environment variables", "TOOLTIPTRANSPARENCY")) + varValue = qad_utils.str2int(self.edit_transparency.text()) + variables.append(QadVariable(variable.name, varValue, variable.typeValue)) + + variable = QadVariables.getVariable(QadMsg.translate("Environment variables", "DYNTOOLTIPS")) + if self.radio_for_all_tooltips.isChecked(): + variables.append(QadVariable(variable.name, 1, variable.typeValue)) + elif self.radio_for_DI_tooltips.isChecked(): + variables.append(QadVariable(variable.name, 0, variable.typeValue)) + + return variables + + + def ButtonBOX_Accepted(self): + variables = self.getSysVariableList() # lista delle variabili modificate + for variable in variables: + QadVariables.set(variable.name, variable.value) + + QadVariables.save() + + QDialog.accept(self) + + + def ButtonHELP_Pressed(self): + qadShowPluginPDFHelp(QadMsg.translate("Help", "DSETTINGS")) + + +# =============================================================================== +# QadPreview class. +# =============================================================================== +class QadPreview(QWidget): + def __init__(self, plugIn, parent, size, transparency, windowFlags = Qt.Widget): + self.plugIn = plugIn + self.size = size + self.transparency = transparency + QWidget.__init__(self, parent, windowFlags) + + self.edit1 = QTextEdit(self) + self.edit1.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.edit1.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.edit1.insertPlainText("12.3456") + self.edit1.setReadOnly(True) + + self.edit2 = QTextEdit(self) + self.edit2.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.edit2.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.edit2.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) + self.edit2.insertPlainText("78.9012") + self.edit2.setReadOnly(True) + + + def refresh(self, size, transparency): + self.size = size + self.transparency = transparency + self.update() # forzo il disegno del preview + + + def paintEvent(self, event): + self.paint_preview() + + + def setEdit(self, editWidget, foregroundColor, backGroundColor, borderColor, \ + selectionColor, selectionBackGroundColor, opacity): + # se i colori sono None allora non vengono alterati + # caso particolare per borderColor = "" non viene disegnato + # opacity = 0-100 + oldFmt = self.styleSheet().split(";") + fmt = "rgba({0},{1},{2},{3}%)" + + c = QColor(foregroundColor) + rgbStrForeColor = "color: " + fmt.format(str(c.red()), str(c.green()), str(c.blue()), str(opacity)) + ";" + + c = QColor(backGroundColor) + rgbStrBackColor = "background-color: " + fmt.format(str(c.red()), str(c.green()), str(c.blue()), str(opacity)) + ";" + + c = QColor(borderColor) + rgbStrBorderColor = "border-color: " + fmt.format(str(c.red()), str(c.green()), str(c.blue()), str(opacity)) + ";" + fmtBorder = "border:1px;border-style:solid;" + + c = QColor(selectionColor) + rgbStrSelectionColor = "selection-color: " + fmt.format(str(c.red()), str(c.green()), str(c.blue()), str(opacity)) + ";" + + c = QColor(selectionBackGroundColor) + rgbStrSelectionBackColor = "selection-background-color: " + fmt.format(str(c.red()), str(c.green()), str(c.blue()), str(opacity)) + ";" + + fontSize = 8 + self.size + + fmt = rgbStrForeColor + \ + rgbStrBackColor + \ + fmtBorder + \ + rgbStrBorderColor + \ + rgbStrSelectionColor + \ + rgbStrSelectionBackColor + \ + "font-size: " + str(fontSize) + "pt;" + + editWidget.setStyleSheet(fmt) + + + def paint_preview(self): + rect = self.rect() + painter = QPainter(self) + painter.fillRect(rect, self.plugIn.canvas.canvasColor()) + painter.setRenderHint(QPainter.Antialiasing) + + foregroundColor = QColor(QadVariables.get(QadMsg.translate("Environment variables", "DYNEDITFORECOLOR"))) + backGroundColor = QColor(QadVariables.get(QadMsg.translate("Environment variables", "DYNEDITBACKCOLOR"))) + borderColor = QColor(QadVariables.get(QadMsg.translate("Environment variables", "DYNEDITBORDERCOLOR"))) + opacity = 100 - self.transparency + font_size = 8 + self.size + height = font_size + 15 + + selectionColor = QColor(Qt.white) + selectionBackGroundColor = QColor(51, 153, 255) # azzurro (R=51 G=153 B=255) + self.setEdit(self.edit1, foregroundColor, backGroundColor, borderColor, selectionColor, selectionBackGroundColor, opacity) + fm = QFontMetrics(self.edit1.currentFont()) + width1 = fm.width(self.edit1.toPlainText() + "__") + 2 + + self.edit1.resize(width1, height) + self.edit1.selectAll() # seleziono tutto il testo + + self.setEdit(self.edit2, foregroundColor, backGroundColor, borderColor, backGroundColor, foregroundColor, opacity) + fm = QFontMetrics(self.edit2.currentFont()) + width2 = fm.width(self.edit2.toPlainText() + "__") + 2 + self.edit2.resize(width2, height) + + offset = int(height / 3) + x = int((rect.width() - (width1 + offset + width2)) / 2) + y = int((rect.height() - height) / 2) + self.edit1.move(x, y) + self.edit2.move(x + width1 + offset, y) diff --git a/qad_dsettings_rc.py b/qad_dsettings_rc.py index 1b11dfd3..09fc4de5 100644 --- a/qad_dsettings_rc.py +++ b/qad_dsettings_rc.py @@ -1,3285 +1,3856 @@ -# -*- coding: utf-8 -*- - -# Resource object code -# -# Created: lun 4. gen 11:54:36 2016 -# by: The Resource Compiler for PyQt (Qt v4.8.5) -# -# WARNING! All changes made in this file will be lost! - -from PyQt4 import QtCore - -qt_resource_data = "\ -\x00\x00\x06\x0c\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x01\x18\x00\x00\x00\xa0\x08\x06\x00\x00\x00\xd7\x60\x73\x20\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x01\x1b\ -\x08\x27\x0e\x6d\xf6\xe0\x0b\x00\x00\x00\x45\x69\x54\x58\x74\x43\ -\x6f\x6d\x6d\x65\x6e\x74\x00\x00\x00\x00\x00\x43\x52\x45\x41\x54\ -\x4f\x52\x3a\x20\x67\x64\x2d\x6a\x70\x65\x67\x20\x76\x31\x2e\x30\ -\x20\x28\x75\x73\x69\x6e\x67\x20\x49\x4a\x47\x20\x4a\x50\x45\x47\ -\x20\x76\x36\x32\x29\x2c\x20\x71\x75\x61\x6c\x69\x74\x79\x20\x3d\ -\x20\x39\x30\x0a\x67\x9d\x47\x40\x00\x00\x05\x3b\x49\x44\x41\x54\ -\x78\xda\xed\xdd\x51\x6e\xd3\x4a\x14\x06\xe0\x63\x44\x5f\x61\x11\ -\x48\x97\x1d\xb2\x8d\xb0\x82\xee\x80\x75\x74\x0f\xb7\xab\x28\x02\ -\x24\x9e\xe6\x3e\xdc\x46\x02\xb7\x49\x9d\xc4\x63\xcf\x99\xf9\x3e\ -\xa9\x0f\xa5\x52\x6b\xec\x73\x7e\x9f\x71\xe2\x38\x02\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\xe8\xc8\x64\x17\xb0\x44\x51\x38\ -\x08\x18\xb6\x08\x17\xc5\xc3\x52\xef\xec\x02\xde\xf2\x28\x5c\x10\ -\x30\x80\x25\x12\x5d\x2c\x91\x14\x0e\x26\x18\x9c\x85\x00\x00\x00\ -\x00\xa0\x73\xae\xdf\x41\x1d\xde\xfc\x1c\x5e\x45\x82\x2d\xc2\xc5\ -\x04\x03\x54\x0b\x97\x61\xfb\x4c\xc0\x80\x70\x11\x30\x20\x5c\xf2\ -\x71\x0d\x86\x56\xbb\xf5\x53\x89\x28\x25\xe2\xa1\x44\x7c\x12\x2e\ -\xc0\x9a\x1d\xfb\xf0\x1c\x30\xa5\x44\x3c\x34\x1e\x2e\xf3\x2f\x4c\ -\x30\x60\x72\x01\x4b\xa4\x36\x97\x48\x26\x97\x05\xa4\x2d\x98\x5c\ -\x04\x0c\x08\x17\x01\x03\xc2\x05\x3b\x07\x6e\x0d\x97\xf2\xdc\x3f\ -\x9a\x48\xc0\x40\x95\x70\xd1\x48\xe7\x79\x99\x1a\x6e\x0c\x17\x04\ -\x0c\x08\x17\x01\x03\xed\x86\x8b\x95\xd0\xe5\xec\x30\xb8\x30\x5c\ -\x7c\x92\x94\x80\x81\xd5\xc3\x45\xb0\x58\x22\x81\x65\x51\x43\xde\ -\xdb\x05\x70\x3a\x5c\x8e\xff\xf8\xdd\xfe\x81\xa6\xba\x35\xdb\xe7\ -\xb9\xbc\x7a\xe3\x62\x39\xf3\x05\xec\xd7\xb1\x5d\x7c\x9e\xcb\xd3\ -\xec\x07\x4f\x0e\xed\x45\x5c\x83\xc1\xb2\x68\xb6\x2c\xfa\xf3\x9b\ -\xfb\xd9\x0f\x3f\xd8\x67\x60\x89\x74\xeb\xe4\x72\x74\x17\x11\x87\ -\xd9\x24\x73\xcb\x1f\x1a\x8d\x2b\xe4\x98\x5c\x2a\xf6\x42\x19\xbc\ -\xd9\x2c\x91\x10\x2e\x1b\xf4\xfe\xa8\x67\x72\x01\xc3\xd0\xe1\x52\ -\x76\x0a\x97\x51\x96\x4b\x96\x48\x08\x97\x8d\x1b\xa1\x0c\xd4\x7c\ -\x26\x18\x4c\x2e\x3b\xaf\xcf\x04\x0c\xc3\xf8\x2c\x5c\x36\xd9\x10\ -\x4b\x07\x86\x3c\xcd\x7f\xeb\xef\xbf\xf4\xd7\x97\x77\xe5\xc2\x8e\ -\x9d\xf8\xb3\xe3\x70\x39\xf5\x03\xea\x31\xa9\xd1\xe3\xf8\xee\xae\ -\xe8\x46\xb8\x9b\x9a\xde\xce\xe2\x67\xef\x8a\x96\x38\xdb\x72\x91\ -\x77\x70\xdf\x22\xe2\xd7\x99\xa6\x4b\x78\x57\xb4\x1c\x81\x44\xe3\ -\x40\x97\x77\x45\xbb\xf6\x62\x82\x81\xd5\x26\x97\x7b\xfb\x08\x9a\ -\xec\x5c\x77\x45\x73\x35\xeb\x53\xba\x9e\x5c\x10\x30\x20\x5c\x04\ -\x0c\x08\x17\x04\x0c\xc2\x05\x01\xa3\x72\x50\x22\x02\x66\xc7\xca\ -\x51\x41\x9c\x2b\x91\xe3\x5d\xd1\xea\xa3\x5d\xde\x07\x43\xea\x70\ -\x79\xeb\xe4\x84\x80\x81\xab\xc3\x05\x01\x03\xe9\xc2\xe5\x2e\x22\ -\x7e\xc4\xff\x6f\xcc\x3b\x3c\x7f\x4f\xf2\x4a\xf2\x61\x40\xbc\x51\ -\x12\x9b\xd5\xc8\xef\xd9\xef\x3f\x38\x1e\x9d\x56\x14\x6b\xed\xdb\ -\x6e\x9f\x15\x5d\x83\x47\xc6\xc2\x65\x1d\xeb\xae\xe8\x0b\x1c\x9c\ -\xec\x56\xe1\x1a\x0c\xad\x85\xcb\xdc\x2e\x77\x45\x7f\x89\x88\xaf\ -\x11\xf1\xdd\x31\xc1\x92\xaf\x8b\x25\x92\xbb\xa2\x3b\xe4\xe5\xbe\ -\x0d\x4f\xc5\x76\xfa\xf5\x93\x0b\x02\x86\x05\xe1\x62\xc7\x0b\x97\ -\x91\xb8\x06\xb3\x70\x6e\xff\x77\xc5\x71\x5c\xf7\xe4\x0c\x97\x1a\ -\xb5\x60\x82\x51\xfd\x17\xef\xb4\x62\x67\x77\x19\x2e\x8e\xa9\x09\ -\x46\x92\x0b\x17\xc7\x54\xc0\x08\x17\xe1\x22\x5c\x46\xe0\xc1\x6b\ -\x95\x8a\xaf\x28\xc2\x45\xe1\xf2\xe7\xbd\x45\x53\xa2\xe3\x8b\x09\ -\x66\xd5\x42\x7a\xbc\xb0\xb8\x14\xe1\x65\xe1\x72\x6a\xb4\x69\xad\ -\x16\x1c\x57\x61\x4c\xc2\x70\x51\x90\x26\x18\x58\x25\x5c\xe4\x88\ -\x80\xa1\xdd\x6e\xf5\xac\x68\xa0\x5a\xc7\xba\x2b\x1a\x13\x0c\x96\ -\x45\xe7\x26\x17\xcf\x8a\x06\x4b\xa4\xd5\x27\x97\x23\x77\x45\x8f\ -\xc1\x7a\x98\x4d\x27\x17\x04\x0c\x08\x17\x04\x0c\xed\x86\xcb\xfc\ -\x7d\x2e\x0a\x4d\xc0\x40\x95\x70\x51\x6c\xe3\xf2\x2a\x12\x96\x45\ -\x08\x18\x84\x0b\x02\x06\xe1\x02\x8a\x82\xba\xe1\x52\x14\x1a\x8e\ -\x3b\x26\x17\x04\x0c\xcd\x86\x8b\xc4\xe1\x1c\x9f\x68\xd7\x4e\xd3\ -\x4e\x49\xb6\x53\x8e\xb0\x98\x8b\xbc\x6d\x35\x6d\xba\x70\xf1\x68\ -\x55\x04\x8c\x70\x59\x6d\x3b\xcb\xf3\xb2\xe8\xf8\xe5\xae\x68\x68\ -\xaf\x69\x33\x7c\x14\xca\x8b\xed\x2c\x27\x36\xde\x5d\xd1\x9c\x62\ -\x1d\xbd\xff\x54\x30\x25\xd8\xc6\x88\x13\x17\x74\x15\x11\x96\x48\ -\x02\xfe\xe6\x70\x71\xa8\xb8\x86\x57\x91\x84\x8a\x70\xc1\x12\xa9\ -\x93\x25\x51\xfa\x70\xf1\x0e\x5d\x04\x4c\xbb\x4d\x3b\x25\xd9\x4e\ -\xb5\xc1\x2a\x5c\x83\xd9\xb6\x69\x85\x0b\x26\x18\x86\x69\x5a\xe1\ -\x82\x09\x46\x90\x0b\x17\x72\xf2\x2a\x92\x70\x11\x2e\xc9\xc7\xe3\ -\x49\xf1\xd3\x7a\xb8\x14\x05\x91\xf6\xe0\xb5\x7c\xdc\x4c\x30\xeb\ -\x1f\xf7\xb4\x2f\x45\x0b\x17\xd6\xe6\x1a\x4c\xbd\x93\x8a\x70\x41\ -\xc0\xd8\x05\xc2\x25\xf3\x7f\x0c\x01\x33\x52\xb8\x74\x71\x41\x57\ -\xb8\xb4\x6b\x4a\x52\x74\x26\xe3\xf5\x1b\xd7\xab\x45\x60\x82\x19\ -\x26\xa8\x85\x0b\x1a\x03\xe1\x82\x80\xc1\x07\x74\x83\x25\xd2\x86\ -\x4d\x2b\x5c\x40\xc0\x08\x17\xb0\x44\xd2\xb4\x4d\x6f\xa7\xf4\xc2\ -\x04\x33\x66\x20\xeb\x7d\x04\x8c\x70\xe9\x2b\x5c\x24\x18\x02\x66\ -\x9d\x3e\x9a\xb2\x84\xcb\x5d\xc4\x74\x7c\x46\x11\xd0\x66\xd3\x66\ -\x78\x7e\xd8\xab\x0f\x6f\x3b\x54\xdc\xf0\xf9\x1f\xbb\x1b\xac\x28\ -\x60\xed\x1e\x6a\x2b\x3d\x16\xfc\xd8\xe4\x32\xee\x19\x47\xc7\x46\ -\xc4\x3f\xc9\x26\x82\xbd\x1c\xe2\xe5\xa3\x59\x5b\xdc\x4e\x67\x1c\ -\x74\x6d\xc2\xcd\x7c\x9a\x6d\xcc\xd3\x95\xdb\xa9\x29\xba\xaf\xdd\ -\xe6\xb9\xc8\xfb\xd2\xee\x17\x74\xef\x67\xdf\x7f\xbc\xf2\xd5\x22\ -\xaf\xf4\x5c\x1f\x2e\x24\x68\xa6\x24\x6f\xd0\x68\xee\x1e\xa3\xd9\ -\x06\x79\x9f\x4b\x23\xe1\x62\xa7\x0b\x98\xb4\x45\x3c\xd9\x85\x19\ -\x8f\x0f\xa6\xe8\xf6\x26\x96\x85\x05\xac\xce\x05\x47\x7a\xef\xd4\ -\x98\x5e\x70\xe0\x11\x30\xc2\x05\xd2\x19\xe5\xb9\x48\x55\x9a\xb6\ -\xc2\x2f\x15\x2e\x08\x98\x0e\x4c\x49\xfe\xb8\x70\xc1\x12\x49\xb8\ -\x0c\xbd\x9d\x69\x77\xa6\x1d\xac\x88\xc7\x5b\x77\xcd\x7e\xb7\x03\ -\x84\x09\x06\xe9\x0f\x00\x4e\x92\xbc\xba\x64\x9a\x76\xfe\xfb\x0a\ -\x07\x4b\xa4\x4e\xc3\x65\xe4\xbf\x8f\x80\xa1\xf2\x98\xb9\xe7\xc4\ -\xf0\x68\x7a\x41\xc0\x60\x4d\x8d\x7a\x61\x93\x25\xcc\x54\xe1\xf7\ -\x4f\x6f\xfc\x6d\xc5\xc4\xdc\x7b\xbb\x80\x6b\xcf\x42\xae\xcd\x60\ -\x89\xc4\x66\x53\x13\x08\x18\x40\xc0\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x40\xd7\xfe\x03\x18\xf6\x50\x35\x80\x40\x4b\x14\x00\ -\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x16\xa3\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x01\x18\x00\x00\x00\xa0\x08\x06\x00\x00\x00\xd7\x60\x73\x20\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x01\x16\ -\x0f\x1f\x37\xd2\x49\x1a\xa0\x00\x00\x00\x45\x69\x54\x58\x74\x43\ -\x6f\x6d\x6d\x65\x6e\x74\x00\x00\x00\x00\x00\x43\x52\x45\x41\x54\ -\x4f\x52\x3a\x20\x67\x64\x2d\x6a\x70\x65\x67\x20\x76\x31\x2e\x30\ -\x20\x28\x75\x73\x69\x6e\x67\x20\x49\x4a\x47\x20\x4a\x50\x45\x47\ -\x20\x76\x36\x32\x29\x2c\x20\x71\x75\x61\x6c\x69\x74\x79\x20\x3d\ -\x20\x39\x30\x0a\x67\x9d\x47\x40\x00\x00\x15\xd2\x49\x44\x41\x54\ -\x78\xda\xed\x5d\xbf\xaf\x5d\xc5\x11\x9e\xbd\x72\x40\x0e\x06\x45\ -\x4a\x91\xc2\x96\x28\x6d\x14\x51\xc1\xdf\x10\xd3\x44\x8a\x8c\x04\ -\x05\x6d\xba\x14\x94\x80\xdb\x28\x09\x4d\x90\x90\x9c\xff\x21\x44\ -\xe2\xb5\x24\x76\x9f\x0a\xba\x44\x09\xa1\x89\x25\xbb\x88\x84\xd3\ -\x00\x46\xa0\x48\x9b\xe2\xde\x6b\x9f\x77\xde\xfe\x9a\xd9\x99\xd9\ -\xd9\x73\x67\xa4\x2b\x1e\x3e\xf7\x9e\xb3\x3b\xbb\xfb\x9d\x6f\x66\ -\x67\x67\x42\x8c\x11\x5c\x5c\x5c\x5c\x24\x64\xe7\x2a\x70\x71\x71\ -\x71\x80\x71\x71\x71\x71\x80\x71\x71\x71\x71\x71\x80\x71\x71\x71\ -\x71\x80\x71\x71\x71\x71\x80\x71\x71\x71\x71\x71\x80\x71\x71\x71\ -\x71\x80\x71\x71\x71\x71\x80\x71\x71\x71\x71\x71\x80\x71\x71\x71\ -\x71\x80\xd9\x98\xdc\xbb\x07\xf0\xda\x6b\x00\x21\x00\xbc\xf0\xc2\ -\xfe\xef\x7b\xf7\x5c\x2f\x2e\x27\x23\xc1\xcf\x22\x09\xc9\xd9\x19\ -\xc0\xeb\xaf\xa7\xaf\x7d\xfc\x31\xc0\xad\x5b\xa7\x39\xe1\x42\x80\ -\xdc\x9c\x0b\x21\xb0\x3d\xc7\xe7\xb5\x03\xcc\xb6\xe5\xd5\x57\x01\ -\x3e\xfb\x2c\x7d\xed\x95\x57\x00\x3e\xfd\xf4\x24\xc1\x65\xa4\xf8\ -\x5c\x77\x80\xd9\x8e\x3c\xfb\x2c\xc0\xf7\xdf\xa7\xaf\x3d\xf3\x0c\ -\xc0\x77\xdf\x39\xb8\x08\x00\x81\xd6\x73\x5c\xda\xc4\x7d\x30\x52\ -\xf2\xf2\xcb\xb4\x6b\x1b\x02\x94\xe5\xa7\xb4\xc8\xd7\x9f\x5e\x96\ -\xd2\x7a\xbf\xd6\x36\xba\x38\xc0\xd8\x93\xdb\xb7\x69\xd7\x36\x02\ -\x2c\x35\x00\xd0\x34\x9b\xb0\x80\xe3\xe2\x00\x63\x5f\x6e\xdd\x82\ -\x9f\x01\xc0\x5f\x0e\xff\xfb\x15\x00\xc0\xcd\x9b\x00\x77\xef\x6e\ -\xce\xc1\x5b\x62\x01\xb9\xc5\xad\x0d\x32\x18\xc0\x71\x56\xc3\x38\ -\x37\xdc\x0e\xd5\xf1\x3b\x6c\x51\xcf\x35\xa6\x42\xb9\xc7\x48\x3d\ -\x71\xf4\x67\xee\xf1\xcc\xf5\xdd\x01\xc6\xfc\xe2\xdb\x92\x9e\x73\ -\x0b\x91\xda\x47\x8b\xba\xe2\xee\xe3\xac\xe0\xd2\x0b\x32\x6e\x22\ -\xb9\xa0\x4d\xa1\x9c\xc9\xd1\x63\xb2\xb4\x32\x09\x6d\x33\xaa\x55\ -\x07\x2e\xce\x60\xec\xb1\x97\xf5\x44\x35\x3c\x16\x39\x60\x39\x15\ -\xb3\x52\xab\xff\x5b\x63\x30\x68\x80\x29\x45\x62\xba\x20\x00\x46\ -\xc2\xe0\xdd\xc8\xc2\xb2\xec\xbb\xda\x1c\xd0\xac\xfa\x13\x20\x8e\ -\x03\x98\xad\x3b\x2d\x7b\xd1\x3e\xc6\x0e\xf6\x62\x0c\x60\x46\xfb\ -\x20\x66\x03\x99\x29\xd7\x44\xa6\x1f\x29\x90\xd1\xf2\xc1\x44\x4b\ -\x76\xb2\x35\x2a\xb9\x15\x95\x48\xf8\x59\x28\x3e\x10\xab\x73\xad\ -\xe4\x9f\x71\x21\x02\x4c\x08\x21\x3a\xc8\xd0\x17\xc9\x5a\xde\x9c\ -\xc8\xbc\x1b\xf5\x66\xb6\x0c\x32\x39\xdd\xf8\x9a\x20\x02\x4c\x8c\ -\x31\x9c\x2c\x93\x09\xe1\xc9\x27\xc2\xfe\xd3\x2b\x7f\x5a\x80\xcc\ -\x17\x87\xbf\xc3\x60\x60\xb1\xb8\x5d\x6c\x1d\x64\x52\x7a\x9a\x65\ -\xa7\xe9\xcd\xe6\xfe\x75\xcc\x2b\xa4\x0f\x26\x2e\x01\xe7\x24\x7c\ -\x32\x85\x89\x92\x76\x88\xd5\x75\x92\xd2\xdb\x48\x5d\xce\xe0\xb8\ -\x9c\x61\xae\xcd\xe6\x00\x0e\x21\xc0\x1b\x00\xf0\x11\x17\x9a\xf4\ -\x02\xcc\x49\x82\x0c\x0a\x60\xfe\x07\x00\x3f\xa8\xea\x22\xa7\xb3\ -\x11\xba\x9c\x29\x20\x70\x96\xb9\x96\xd4\x69\x6a\x1e\x19\x8b\x5a\ -\x96\xd0\xe9\x25\x0e\x7a\x78\x6c\xac\x6f\x61\xff\xbd\x6b\x91\xf4\ -\xea\x12\xfb\x9b\xd9\xa2\x8d\x47\xcf\x35\xb2\xd9\x93\xfb\x5d\x08\ -\x43\x40\x46\x93\x69\xed\x08\x83\x1c\xd6\x6c\x66\x06\x3b\x59\x47\ -\x7e\x5e\xd4\x43\xcb\x1b\x98\xaa\xcb\xe5\xc2\xdb\x22\xb8\x68\xfa\ -\x64\xd6\x69\x1c\xa4\x7c\x2a\x67\x03\xd6\x8b\xb6\x19\x47\x8a\xe4\ -\x5d\x9b\x49\xb3\x51\x58\x39\x13\x29\x34\x2f\x08\x8a\x09\xc5\x35\ -\x69\xb6\x70\x4e\x8a\xbb\x0f\x52\x89\xaa\x42\xc8\x85\xae\x01\x3c\ -\x06\x80\xe7\x94\x7d\x36\xda\x63\x4f\x3a\x8b\x94\x62\x31\x9b\x65\ -\x32\x31\xc2\x8d\xc3\xdb\xe6\xf1\xe1\xbf\x37\x0e\x50\x12\xe3\x91\ -\xe1\x06\xc8\xed\x01\xa5\x98\x45\x6d\x50\x31\x7a\x2c\xf9\x73\x6a\ -\x2c\x6a\xe6\x17\x01\xc7\xf6\x70\x8d\x9d\xa4\x92\x57\x61\xf5\x55\ -\xfa\xfe\x9f\x09\x6d\x92\x00\x51\xc9\xb5\x1a\x3a\x4e\xc0\x26\x59\ -\xcc\xd6\x98\x0c\x56\xf9\x2d\xe0\x40\x49\x65\x50\x0b\xec\x2a\x3d\ -\x37\xb5\x53\xb5\x15\x96\x89\xed\x53\x6d\x3c\x45\x74\x52\x7a\x66\ -\x66\x6c\xb8\xdb\xd5\x32\x8f\x45\xce\x96\x75\xe6\x3f\xdd\x3c\xc8\ -\xe4\xcc\x8f\x56\xf0\xe8\x5d\xd4\xd4\x1d\xa7\x21\x0b\xc9\xc8\x38\ -\x61\xf5\xa1\xa2\x0b\xc4\x2e\x12\x77\x5b\x47\x81\x8b\x28\xc0\x6c\ -\x01\x64\x6a\x20\xd2\x0a\x1e\x12\x20\x43\xf1\xe7\x6c\x15\x5c\x5a\ -\xf5\x64\x41\x07\x5c\x8c\xab\x67\xb7\x50\x53\x17\x81\x81\x7a\x6d\ -\x16\x64\x6a\x8b\xb3\x65\xb2\x70\xbd\x8d\xa8\x13\x6d\xcb\x89\xaf\ -\xac\x2d\x26\x29\x90\xe1\x1c\x7f\x6d\x5d\xec\x34\x95\x37\x93\xe3\ -\x97\xea\x34\x2c\x81\x6b\x8f\x63\x32\x35\x19\xa8\x74\x79\xcb\xa1\ -\x04\xa5\xed\x7f\x2b\xe0\x4a\x69\x07\xe5\x80\xa5\x85\x14\xa0\x3b\ -\x06\x65\x5d\xd8\x51\x5a\x4f\xe2\xad\xec\x2e\xb5\xf4\x03\xfb\x06\ -\x15\xf5\xe0\x17\xd8\xe3\x16\x41\xa6\x94\x71\x6f\xc6\xb9\xd4\x0a\ -\x34\xa9\x7e\x5b\xc9\x2f\xcc\x92\xd1\x6e\x69\x26\xc1\x6a\xbf\x76\ -\x74\x28\xbc\x84\xef\x25\xc7\xd4\xb0\x34\x16\x43\x95\xb9\xee\xbd\ -\x55\x93\x69\x26\xa7\x36\x15\xdc\x5b\x5e\x12\x25\xdf\xd3\x08\x1d\ -\xb0\xa5\xcc\xdc\x12\xc8\xb4\x0c\x0e\x86\xa1\xf5\x06\xbd\x61\x9d\ -\xbc\xd4\x7b\x6e\x05\x58\xac\xcd\x37\x29\xb6\x18\x1b\xb7\xb8\x87\ -\xf6\x5d\x02\x60\x8e\x66\x53\xcb\x5b\x74\xc6\x6c\x65\xb5\x05\xdc\ -\x1b\x89\x9b\x5d\x20\xfb\x8b\x28\x90\xe3\x8c\x18\x9e\xf1\x45\x30\ -\xaa\x9f\x98\x45\xdf\x1b\x72\x60\x15\x5c\x58\x01\x66\x2b\x20\x83\ -\x65\x2f\xbd\x0c\xa6\xd9\x6e\x5e\x81\x4b\x6e\xf1\xf4\x80\xdb\x4c\ -\x20\xd3\xb3\xdd\x2b\x16\xf3\xd1\x61\xa6\x61\xda\x37\x53\xfd\x6d\ -\xf6\xaa\x02\x33\x83\x0c\x37\x7b\xe1\x02\x99\x1c\xb8\x48\x3d\xd3\ -\x3a\xd0\x50\xdb\x2b\x35\xdf\xb8\x0b\xd0\x71\xc6\xb8\x8c\x1e\x4f\ -\x95\xba\x48\xa9\x7a\xc4\xeb\xe3\xf6\xb3\x64\x2b\xc3\x4c\x12\x4a\ -\xff\x92\xcf\x43\x9c\x5d\xa2\xb4\x7b\xa6\x1d\xa6\x1e\x30\x6c\x39\ -\xab\xc5\xc5\x78\xb1\xdb\xe2\x92\x6b\x61\xe4\x78\xee\x04\x16\x64\ -\xf2\x20\x64\x6e\x22\x5b\x01\x99\x96\x67\x53\x26\x37\xc7\xa4\xd6\ -\x88\x5d\x99\x01\x64\x38\x98\x16\x57\x3f\x73\x29\x46\x19\x7d\x9a\ -\xac\xc0\x31\x6a\x3c\xd5\x2b\x3b\x5a\x06\x19\x4e\xf6\x42\x9d\xd4\ -\x23\x63\x57\xa8\xac\x6b\x16\x70\xe1\xd2\xab\xd4\x16\x30\xc6\x9c\ -\xa2\x98\x65\x23\xc6\x33\x08\x3a\xbc\x8a\x47\x08\xb8\x6c\x57\x2d\ -\xdf\x4b\x69\xf1\x53\xcf\x05\x51\xb7\xbe\xb9\xfd\x3e\x9a\xfe\x8a\ -\xd1\xe0\xd2\xd3\x4f\x0b\xd5\x2d\x39\x02\x39\x35\xc7\x73\x68\x6d\ -\x6a\x4a\x60\xd9\x48\xa6\x25\xc9\x10\x6a\x0c\xa8\xd5\xd4\xe2\x62\ -\x20\x12\xfe\x0a\x6b\xe0\x82\xd1\x97\x85\xc0\x35\xca\x18\x8c\xae\ -\xdf\x24\x5a\x9b\xba\x85\xc5\x8c\x66\x32\xbd\x69\x17\xb8\xbd\xff\ -\x5c\x87\xd7\xb8\x18\xc8\xc8\x1d\x26\xcd\x67\xf7\x06\x2e\x6a\x82\ -\x49\x2e\x6a\xdc\xe2\xc1\xd7\xa1\x0c\x26\xf7\x86\x1e\xcd\x64\x34\ -\x26\x18\x75\xe7\xa7\xf5\xd9\x12\x4c\x46\x73\x3c\xb4\x17\x43\x0b\ -\xb3\xb4\x60\x2e\xb6\xe4\x72\xb6\x34\x96\x3b\x61\x65\x64\x77\x94\ -\x30\x4a\xd2\x4e\x21\x68\xdd\x4f\xd1\xba\x5b\xc1\x09\x32\x23\x9d\ -\xbf\x5a\xba\xad\x99\x85\xa3\x4a\xe7\x96\x58\x33\xb6\x5d\xda\x20\ -\x63\x82\xc1\x58\xf2\xcb\x44\x42\xcc\x09\x17\xb8\x71\x02\x1e\x37\ -\xc8\x70\xdf\xcb\x22\x70\xf7\x32\x04\xce\xfe\x4b\x1e\x56\xd5\x04\ -\x99\x9d\xc2\x80\xa1\x58\x8c\x96\xc9\x44\x2d\xef\xc1\xb1\x08\xd6\ -\xe7\x8b\x38\x17\xee\xec\x20\x63\x61\x6b\xbc\x16\x66\x30\xca\xf7\ -\x32\x63\x8a\x8d\x9d\xe5\xc6\x69\xfb\x65\x34\x42\xce\x73\x87\x17\ -\xb1\x0b\xb7\x16\x35\x2c\x99\xf8\x4b\x6b\x87\xc9\xca\xe9\x67\x8d\ -\x85\x4d\x19\xb3\x91\xb1\x40\xa6\x00\x86\xc2\x62\x6a\x6c\xa6\x47\ -\x21\x66\x8a\x93\x15\xde\x94\x1c\x5b\x92\x5a\x20\xc3\x35\x41\x2d\ -\xa7\x56\xd0\x32\x09\x6b\x0c\x9e\x53\x3f\x1a\x20\x63\x9a\xc1\xd4\ -\x14\xca\xa9\x10\x75\xf6\xd2\x51\xd9\xb1\x27\x6a\xd8\x2a\xc8\x58\ -\xa4\xff\x1a\xce\xed\xda\x4b\x8c\x2b\x55\xea\xe6\x4d\xa4\x1e\x16\ -\xc3\x09\x32\x5c\xd9\xc4\x24\xdf\xce\x1c\x4c\xa6\x64\xc6\x70\x83\ -\x0c\xf7\x22\xb4\xc0\x5e\x96\x6d\x90\x3a\x24\x59\xcb\xd9\xac\x01\ -\xbc\xd2\x2c\x46\x95\xc1\x70\x81\x0c\xb7\x52\x52\x1e\x7a\x6a\x6d\ -\x69\x2e\xd6\xc3\x51\xc0\xad\x27\x0b\x5f\xcf\x44\xfd\x6d\x08\xfb\ -\x3a\x40\xcb\xcf\xa4\xa6\x91\x06\x5b\xeb\xd9\x66\xb6\x0e\x32\xd3\ -\x98\x48\x1c\x4a\x69\xf1\xd0\x73\xef\x1a\xf5\x2c\x1e\x0e\x10\xd0\ -\xdc\x92\x8c\x31\xc2\x6f\x00\xe0\x76\x5a\x11\xd3\xce\x2d\x2e\x5d\ -\x62\x63\x6b\xb6\x90\x98\x3d\x0c\x49\x04\xdc\x78\x84\x80\xfb\x6d\ -\x80\xad\xd2\x98\xfb\x8e\x76\x82\x23\xae\xc3\x8c\xb5\x44\xe5\x2c\ -\x73\xa1\xa1\x4c\xaa\x65\xf6\x22\x35\xb6\x9c\x25\x6e\xb5\x32\xf2\ -\x71\x3c\x67\x5a\x06\xd3\xea\x6f\xa8\x0d\x18\x65\x4b\x58\x13\x5c\ -\x38\x27\xd4\x4c\xa9\x32\x66\x62\x39\x01\x61\x02\xf6\x82\xcb\x6c\ -\xb2\x1b\x34\x38\xdd\xbe\x18\x6e\xfa\xca\x19\xc1\x79\x21\xd6\x45\ -\xa0\x7f\x54\xd0\x91\x06\x99\xff\x66\xfe\xfd\xa1\x51\xdf\x4b\x8f\ -\x4e\x6b\x7a\x6b\x75\xe4\x62\x5f\xa2\x56\xfa\x7f\x12\x0c\xa6\x05\ -\x64\xb4\x92\x31\x27\xc1\x85\x71\xd0\xb4\xce\x16\xf5\xdc\xfb\xad\ -\xcc\xbf\xff\x12\xe6\xc8\x92\xc7\x35\x26\x1c\x79\x83\x47\x03\x2f\ -\xcb\x6e\xd9\xe0\x0e\xb0\xf9\x62\xa8\x8a\xe1\x4c\xde\x24\x09\x2e\ -\xb9\xb6\x71\x6e\x9f\x73\xf9\x78\x5e\x03\x80\x4f\xd6\xd7\x12\xf7\ -\x3d\x7e\xdf\xca\x22\x92\xf0\x93\x71\x82\x8b\x16\xe3\xe3\x7c\xce\ -\x26\x01\xa6\x15\x64\x30\x66\x95\x85\x12\x20\x12\x99\xeb\x24\x16\ -\x07\xf6\x88\x87\x05\x80\x61\xaa\x70\x4a\xee\x57\x4f\xb1\x3f\xcb\ -\x00\x33\x3a\xa3\x1d\xbb\x2f\x46\x62\x00\xac\xd5\x17\xe2\x0e\x6e\ -\xeb\x3d\x5c\xda\xda\xf7\xd5\xb5\x38\x08\x50\x22\xa7\x09\x20\x71\ -\xaf\x9c\x1e\x07\xed\xf8\xba\x0f\x86\xd3\x5f\x33\x1a\xf1\x47\xf8\ -\x64\xa8\xcc\x83\xd2\xf7\xc3\x77\xe2\x02\x64\xa2\x16\xb0\x1c\xc1\ -\x85\xf3\x65\x56\x8a\xec\x35\x73\xe6\x6d\xe0\xcb\x79\x67\x60\xa1\ -\x8b\xb1\x18\x0e\x64\xb6\x50\x31\xd0\x2a\xc8\x30\xfa\x2f\xa2\xd4\ -\xd8\xb7\xdc\x9b\xda\xf6\xdc\x21\x45\xc9\x2d\xec\xd9\x64\xb3\x0c\ -\xa6\x95\xda\xcf\xc2\x5c\x28\xb9\x78\xb9\x4d\x26\xe9\x5d\x20\x4e\ -\xa0\x91\x04\xad\x16\x60\xc0\x1e\x5a\x1d\x6d\x92\x6d\x1a\x60\x46\ -\xb1\x98\x9e\xc1\xb2\x1c\xc3\xc1\x35\xf9\x4a\x20\xc3\xd5\xff\x94\ -\x73\xbf\x77\x0e\xa4\x7e\xbf\x7e\x0e\x57\xc1\xb5\x92\xff\x0a\x7b\ -\x48\xd2\x12\x7b\xe1\x9a\x4f\x9b\x64\x30\x9c\xbb\x15\x92\xa6\x01\ -\xb7\x7d\xac\x0d\x32\x94\xfe\x67\x76\x4b\x02\x9c\xdf\xc9\x26\x31\ -\x90\xd4\x6f\x52\xf7\x96\x62\x2d\x2d\xfa\xcb\xc5\x66\x51\x0e\xc1\ -\x3a\x83\xe9\x60\x31\xb5\xcf\x28\x5a\x69\x8d\xb9\x8c\x02\x19\xee\ -\xfe\x1f\xee\x55\x06\x9a\x10\x2e\x43\x08\xef\x42\x08\x11\x42\x78\ -\x70\xf8\xfb\x72\x0e\x58\x62\x8c\xa1\x16\x6c\xa9\x11\x00\x67\xa1\ -\x0c\xca\xb0\x97\xbd\xa5\xce\x51\x80\x83\x8b\xfa\x72\xb3\x09\x29\ -\x56\x36\xe2\x2c\x54\x85\x7d\x50\xee\x11\x2b\x26\xd2\xb9\xef\x00\ -\x00\x5c\x06\x80\x7f\x00\xc0\x8b\xab\xef\xde\x07\x80\x97\x00\xe0\ -\xdb\xfc\xcb\xea\x5c\x73\x97\x8f\x69\x69\x3f\x37\x18\x30\xea\x50\ -\xb5\x3e\x13\xf5\x59\x97\xac\x23\x60\x02\x40\xe2\xfa\xff\xb9\x83\ -\xf4\x38\x19\x91\x32\x08\x45\x29\xe6\x26\xc5\x0a\x5b\x5f\x2a\x6f\ -\x27\xc0\x05\x0e\xff\xf6\x36\x00\xbc\x8f\xbf\x67\xa4\xb4\xdf\x52\ -\xa2\x73\x46\x86\x2a\x36\x41\xcc\x30\x98\xdc\xa4\x28\xd4\xb5\xbe\ -\x40\x89\x31\x34\x78\x36\xa6\x53\x7b\x93\x8c\x70\x8e\x6b\xca\x03\ -\x00\xb8\x9a\xb9\xf6\x10\x00\xae\x81\x8b\x04\xc8\xf4\x32\x18\x13\ -\x3e\x18\x26\xd3\x28\x72\xf8\x0f\x6a\x27\x57\x53\xb1\x0e\xa3\x19\ -\xcc\xd6\xc1\xc5\x65\x0c\xb8\x70\xc8\x25\xab\x9d\x5d\x46\x5d\xe6\ -\x14\x70\x60\x2d\xac\x0e\xdf\x63\xfa\x82\x54\x22\xaa\x96\x94\x09\ -\x06\x02\xef\x02\xa7\x2f\xa1\xd6\x1f\x8c\x4f\x61\xf1\xdd\xe6\x33\ -\x68\xc7\xf1\xbd\x03\x00\xbf\xcb\x7c\xe7\x4e\xfe\xa5\x73\xae\x4d\ -\xab\xb9\x72\xc1\x07\x23\xe9\x7c\xe5\xac\x6d\x3e\xe2\x90\xe8\x3a\ -\xad\xc7\x34\x0c\xa6\x74\xe0\xf1\x32\x00\xbc\xbb\xff\xd2\xb9\x5d\ -\x83\xc2\x82\x8a\xd4\x09\x72\xfc\xee\x75\x00\x38\x3b\xe4\x91\x7d\ -\x94\x50\xb2\xb5\x00\xa7\xd6\x03\xa3\xbd\x29\x46\x31\x4c\x90\x69\ -\xb7\xee\xdc\xce\xd0\x87\xb0\x77\xe8\xae\xe5\xfe\xe1\x5a\xea\x37\ -\x98\xc5\xc3\x91\xb7\x05\x0b\x2e\x35\x80\xdb\x82\x0c\xf7\xc1\x64\ -\x17\x48\x08\x97\xef\x03\x3c\x7e\x31\x3d\xa7\x5e\x82\x18\xbf\xcd\ -\x98\x08\xa1\x05\x60\x92\x83\x9e\x8a\x79\x41\x98\x3e\x92\xec\x25\ -\xf5\xd6\xa2\x9e\x46\xc7\x9e\x7c\xa6\xa4\x84\xac\x2c\xa0\xda\x2e\ -\x52\x3a\x50\x6e\xff\x72\x79\xfb\x40\x66\x1e\x1e\xc8\xcb\x87\x01\ -\xe0\x71\xaa\x49\x09\xb6\x10\x6b\x43\x1b\x85\x53\x6c\x70\xe8\x7b\ -\xc4\x2e\x12\xf5\x79\x66\x00\xe6\xc2\x44\x0b\xe1\xdd\x02\x2b\x7e\ -\x0f\x62\x7c\x7f\xa5\x84\x98\xa3\xbe\xcd\x83\x8e\x00\x18\x0e\xaa\ -\x3b\x0a\x60\xb0\xa6\x0f\x35\x56\xa4\xa0\xa3\x64\xbb\x5b\x22\x70\ -\x4b\x6d\xcb\xb0\x97\xd0\x0a\x30\xa3\xc0\x05\xab\x73\x07\x18\x1e\ -\x80\x29\x6f\x1c\xc4\x78\x2d\x33\x69\x59\x01\x66\x3d\x13\x6b\x49\ -\xc2\xa5\x13\x32\xa7\x16\x0b\xd5\x49\xd7\x52\xc6\x84\x23\x52\x37\ -\xa1\xb7\x58\xf3\xa3\x35\xf8\x66\x4a\x4c\x29\x36\xf8\xeb\x82\xd6\ -\x98\x51\x99\x60\x8d\xed\xcc\x00\x30\x9b\x3c\x2a\x40\x02\x17\xc8\ -\xe7\x93\xd5\x02\x11\x6e\xbf\x0b\xa6\x1f\x5c\x67\x8c\xb0\x29\x1f\ -\x28\xe0\x52\xf3\x29\x49\x9c\x6f\xd2\x00\x17\x6b\x3e\x19\x8e\xe7\ -\x5b\x06\x98\x3b\xb5\x6b\xdc\x09\x83\xde\x42\x2e\xa4\x2d\x84\x78\ -\x73\x31\x17\x0e\x00\x3e\x86\xf7\x53\x9f\xb3\x02\xc9\xe6\x33\x48\ -\xdc\x0b\xb9\x77\x37\x4a\xa2\x92\xe4\x30\x90\xb2\xec\xe4\x85\x52\ -\x74\x78\x8c\xdf\xa6\x28\x77\xa9\x4f\x25\xd3\xe8\x78\xa5\x96\x4f\ -\x36\xe7\x77\x91\x66\x38\x94\x2d\x5e\x4d\x2a\x8c\x5c\xb8\x11\xcb\ -\x58\x18\x4c\xb3\x6a\x60\xe6\xe8\xb4\x99\xad\xf7\xd2\x32\x91\x38\ -\xfa\x62\x97\xc1\xec\x77\x89\x5e\x02\x80\xf7\xf6\x4e\x97\xfd\x1f\ -\x3f\x04\x78\x71\x05\x2e\xdd\x4a\x58\xde\xe9\x93\x18\xf7\xc9\xba\ -\x17\x1f\xac\x13\x54\xf0\x8d\xa3\x92\x24\x5d\xfa\x90\xe4\x42\xe5\ -\xa1\x14\xb7\xb3\xfe\x74\x9a\x66\x4f\x18\x8d\xd6\xd1\x12\xee\x7a\ -\x58\x33\x32\x19\x13\x47\x05\x5a\x7c\x0a\xeb\xef\x60\xd9\x4b\x0e\ -\x14\xb0\x9e\x7b\xec\x24\x60\x66\x03\xaa\x55\x18\x14\x98\x8c\xda\ -\x42\xea\x0d\x12\xd4\x66\x2f\xd2\x6d\xd5\x7a\x9e\x39\x06\xd3\x72\ -\x26\x69\xf1\x9d\xe6\xc0\x3a\xad\xbc\x2e\x82\x8b\x45\xc5\x34\xd2\ -\x70\x32\x8e\x48\x5f\xd0\xca\x88\xb8\x0e\x1e\x72\xf7\x61\x56\x7f\ -\x9f\xb9\x8c\x76\x25\x90\x29\xd9\xf1\x50\x70\xe8\x71\x80\x0b\x67\ -\xfe\x8f\x4e\xd0\x15\xf7\xbb\x68\xe4\xf9\xad\x95\xfc\xe5\xaa\x64\ -\xa9\xf4\x52\x54\x69\xeb\xe8\x9c\xcf\xd3\x02\x4c\x0e\x64\x32\xd9\ -\xc9\x58\x4d\x3d\xa9\x9a\x42\xad\x35\xb3\x2d\x2c\x8a\x5a\x89\x0c\ -\x6e\x90\xa1\xfa\xaf\x96\x07\x4d\xd7\x1f\x69\xf3\xc0\x22\x08\x48\ -\xcd\x27\xce\xfb\x9a\x32\x91\x72\xf1\x0b\x95\x4c\x76\x81\xaa\x2c\ -\xea\xf6\x21\xc6\x94\xe8\x65\x33\x12\xec\x85\x52\xc7\x48\x72\x2b\ -\x37\xa7\xa3\xd6\xe7\xb7\x82\xfc\x1a\xa4\x38\xe6\xc5\x16\x62\x55\ -\x36\x6f\x22\xad\x41\x06\xeb\x63\xe0\x0c\x08\xc3\xfc\x4e\x1a\x64\ -\x72\xce\x6f\xcd\x4a\x08\x9a\x20\x93\x62\x7c\xb5\x1a\xda\xd8\x03\ -\x99\xa5\x67\x62\xd9\x90\x76\xea\xcb\x96\xc8\x6b\x6e\xf3\x68\xb3\ -\x85\xd7\x8e\x40\xb3\xfc\xc0\xd3\xad\xc6\x50\xf0\xc5\x34\xbd\xd9\ -\x24\x6c\xd4\x16\x7a\x6f\xd5\x64\xb2\x00\x32\x39\x30\xae\x45\x1c\ -\xa7\x16\x43\xad\x8c\x48\xcd\x2f\x67\x0d\x5c\xb4\xc6\x85\x7b\x7c\ -\xa7\x39\x2a\xc0\x95\xda\x50\x62\x22\x60\x06\xba\x95\xcd\xa4\xd8\ -\x0b\xa7\xbf\xa8\x37\xba\x54\x92\xfd\xd5\x74\xc4\x95\x91\x5f\x21\ -\xde\x47\x1c\x58\xb4\x8e\x16\x4c\x9d\xd1\x8e\xba\x5e\x32\x7e\x0a\ -\xf3\x6f\xf9\xd4\x44\x5f\xfe\x86\xeb\xac\x11\x17\xb8\x48\x4d\xe6\ -\x1a\xc8\xd4\xda\x49\x71\xf0\xb6\x32\xa4\x99\xfc\x1e\x5c\xe3\x22\ -\x11\xb6\x31\x05\xc0\x58\x66\x2f\x3d\x20\xd3\xf3\x46\xa5\x7e\x97\ -\x33\xba\x54\x1a\x64\x24\x19\x67\xcd\xb7\x63\xcd\x34\xc2\xbc\xb4\ -\x7a\x0b\x09\x9e\xa4\x89\x94\xf3\xd3\xb4\xb2\x18\xad\x89\x80\x1d\ -\xe8\x04\xc8\xb0\xb2\x17\xe9\x60\x42\x69\x90\x69\x5d\xec\x94\x7e\ -\xf6\xa6\xa2\xb0\x16\x9b\xc3\xe5\xe3\xe3\xec\x97\x79\x80\xc1\xb2\ -\x11\x0b\xf4\x95\x02\x32\x87\xdf\xa8\x67\x5c\x1b\x05\x32\x95\x7c\ -\x2e\x6a\x0b\x9a\x3a\x8f\x46\x8d\x43\x8b\xd9\x88\x5d\x0f\x92\xbb\ -\x53\xd3\xe7\x83\x19\x5d\xd7\x5a\xf2\x2d\xcf\x10\x01\xac\xb6\x38\ -\x35\x7c\x32\xd4\x05\xc4\x01\x32\x33\x1d\x34\xc4\xe8\x48\xba\x5f\ -\xa6\x01\x86\xcb\x97\x62\xe1\x6d\x53\x1b\xc8\x5c\x40\x1d\x75\x72\ -\x68\x55\x37\xe8\x35\x09\xb1\xfe\x12\x6d\x90\xb1\x60\x1a\x51\xfa\ -\x47\x39\x57\x26\x51\x22\x78\x13\x19\xed\x4a\x95\x05\x66\x60\x32\ -\x6b\xbf\x4b\x6f\xcc\x8c\x76\xe9\x14\x2a\x93\x69\xf1\xb7\x60\xa2\ -\x74\x7b\xfb\xdb\x72\xe2\x7a\xb6\x43\x87\xad\xfe\x2d\xa9\x2d\x7c\ -\xb3\x00\x33\x3b\x7b\xe1\x58\x80\x94\x10\xfa\x51\x75\x99\x7a\xcc\ -\x25\xce\xa0\xc3\xde\xfb\x94\xb6\xbe\x47\xcc\x25\xee\xf0\x82\x3f\ -\x1c\x4b\xf2\x84\x50\x4c\x19\x79\x12\x26\x12\x72\x10\xcc\xb2\x98\ -\xd2\x02\xac\xed\x1a\x51\xed\x69\xcb\x91\xa5\x2d\xf1\x2f\xd4\x63\ -\x18\x1a\x26\xd3\xa4\x2c\x1f\xee\x00\xc0\xaf\x0e\xff\xff\xe3\xc3\ -\xdf\x77\x00\xd8\xce\x65\x25\xe7\xa4\x45\x45\x76\xd4\x98\x8e\x93\ -\xf5\x0d\xb5\x25\x8d\x28\x0b\x32\x74\x81\x60\xeb\x2e\xb5\x8e\x6f\ -\xcd\x4f\xd2\x52\x8d\x53\x9b\x41\x73\xe9\x92\xe5\xb9\x39\xe0\x2d\ -\x27\x50\x3f\x61\x06\x73\x51\x61\xa6\x59\x8c\x84\x8f\x20\x65\x62\ -\x8c\x06\xd6\x1a\xfb\xc0\x44\xed\x62\x99\x8c\x74\x76\x3c\xcd\xdd\ -\x24\xee\x67\x3d\x1a\x30\x17\x2c\x66\xb4\x6b\x5b\x5c\x8b\x44\xdd\ -\xb3\x51\xda\x75\xcc\x4b\x4f\x26\x7d\xab\xd4\xbe\x60\x12\x8a\xfb\ -\x77\x18\x52\x64\x14\xc1\x6f\xb6\x4c\xff\xc7\xf6\xfe\x71\x80\x99\ -\x68\xce\x8c\x68\x02\x98\x15\xb8\xa4\xbe\x4e\x59\xbc\x8a\x7d\x2c\ -\x16\x61\xa7\xea\x6a\x26\x73\x97\x32\x99\x39\xaa\x4d\x62\xaa\x2b\ -\xb6\x80\x9b\x46\xd1\x36\xce\x3c\xc1\x4b\x3f\x8c\xb4\x79\x04\x00\ -\x70\x69\x0b\xe0\xc2\x91\xb7\xc2\x42\xdf\x39\x4e\x4b\x5b\x0c\x5f\ -\xaf\x95\xdf\xa0\xdc\xab\xb5\xaf\xeb\xe7\x3f\xf5\xd5\x24\x35\x58\ -\x04\x8f\x54\xbb\x35\xab\x2c\xf6\xae\xa5\x11\x6d\x9c\xce\x07\x13\ -\xda\x26\xa2\xc9\xe8\xde\x75\x7b\x8e\x31\x2f\x9c\x36\xb7\xe5\xd4\ -\x9c\xa5\x45\x49\x61\x3a\x54\x76\x14\xc2\x8f\xba\x18\x95\xd4\xce\ -\x15\x17\x8b\xa0\x82\xcb\xa6\x4f\x53\xb7\xb0\x97\x9c\xe2\x32\xff\ -\x6e\x16\x64\x38\x16\x4d\x43\x64\xb0\x09\xbb\x5f\xa2\x7d\x54\x90\ -\x79\xfa\xbb\x5f\x17\x5f\x5d\xad\xcc\xc8\x9a\x6f\x26\xf5\xfc\xd1\ -\x15\x48\x2f\xc1\x24\x62\x85\xf2\x71\xb2\x17\x2a\xfd\xaf\xd5\x77\ -\x1a\x6d\x32\xb5\x26\x90\x5a\x6f\x2d\x63\xda\xdb\xf7\xbb\xfb\x05\ -\x5e\xfc\xef\x6e\x13\xb0\x87\x09\x60\x7f\xcb\x95\x73\x5a\x6a\x17\ -\xd2\x4a\xe1\xb5\xa2\x82\x1a\xca\x82\x96\x26\xb3\x09\x87\x6f\x6b\ -\x3b\x6a\x03\x4d\x3d\xf9\x3a\x2a\x6f\x09\xf6\xb4\xb2\xf4\x82\xdc\ -\xff\x26\x07\x2e\xc7\xad\x6e\xde\x79\xdc\xda\xbe\x5e\xdd\x71\x99\ -\x55\x9c\x73\xc5\x3c\x83\xb1\x12\xf0\xa4\x69\x2e\x9d\x7b\x33\x2f\ -\x66\x7c\x2d\xe7\xec\xfa\x7a\xca\xb9\x39\x1a\x58\x6a\xa6\x2d\xb6\ -\x9d\x54\x26\x33\xc2\xd4\xc5\xe8\x48\xfa\x10\xa9\x06\xb8\x98\xf0\ -\xc1\xb4\xb2\x13\x6a\x71\x34\x0b\xbe\x18\x14\x8b\x3a\x82\x0a\x00\ -\x5c\x07\x80\xb3\xc3\xbf\x9d\x85\x00\xd7\x2b\xba\x58\x4f\x4a\xad\ -\xda\xc6\xc9\x85\x73\x38\xf3\x72\xe1\xc3\xf0\x86\xe6\xf0\xc9\xe4\ -\xd8\x0b\x27\xd8\xd4\xfc\x1f\xa5\x4a\x93\x2d\x35\xb9\x39\xea\x42\ -\x49\xfb\x8c\x86\x9b\x48\x59\x80\x81\xfc\x8e\x51\xce\x74\x2a\x4c\ -\x58\x59\x33\x29\xd5\x87\xa7\x6d\xc4\x3d\xbb\x32\xe0\x37\x00\xe0\ -\x9f\x48\xff\x0c\x85\x61\x74\xf9\xc5\x6a\x93\xb6\x01\xf8\x24\x69\ -\x7e\xed\xe8\x9a\xf4\x92\xb0\x90\x69\x4e\x83\xbd\x0c\x07\x98\x1e\ -\x70\xc1\x2e\x1a\x31\x90\x29\x4d\x96\x3d\x0b\x63\x05\x98\x96\x15\ -\xa0\x01\x32\xc5\x7b\x21\x01\x86\x0b\x64\x30\xe7\x96\x46\x81\xcb\ -\x88\x45\x3e\xf2\xb9\x26\xe3\x60\x5a\x8e\x00\x4c\x12\xc5\x8a\x3c\ -\xcc\xa8\x3b\x89\x2c\x15\x7a\xe7\x88\x2d\xa1\x6d\x5f\x9f\xff\x58\ -\x79\xc9\x6e\xe5\xb9\xbb\x81\x8b\xaf\x69\xc2\x70\xa1\xac\xb6\x2f\ -\x26\xa0\x4f\x4a\x1f\x7f\x57\x6e\xda\x19\x61\xb1\xb6\x54\x50\x6c\ -\x1d\xb3\xda\x39\x1d\x6e\x90\xe9\xb9\x47\xcd\xd1\x6a\xcd\x3d\x60\ -\x35\xcf\xef\xe6\x18\x0c\x8e\xe2\x6e\x6f\x47\x29\x40\x84\x1b\x99\ -\x6b\xb7\x81\x67\x57\x01\xcb\x66\xb4\x74\xbe\xbc\xef\x4f\x01\xe0\ -\x3f\x05\x27\x31\x16\x64\x2c\x81\x8b\xe6\x22\x1f\x09\xb2\x43\x00\ -\xa6\x75\x1f\x9f\x5b\x11\x5a\x2c\x26\x74\x1f\xb6\x0c\xf0\x39\x40\ -\x12\x64\x3e\x47\x32\x40\x8c\x8f\xa3\x90\xd2\xf3\xfc\xef\xa4\xec\ -\xb9\x45\xbb\xae\x03\xc0\xdf\x00\xe0\x27\x44\x3b\xf2\xd8\xb7\x37\ -\x0e\xbf\xf9\x22\x84\xfd\xdf\x46\xc0\xc5\x82\xdf\x45\xe5\x79\x16\ -\x22\x3d\x5b\x16\x45\xcb\xbf\x4b\xf8\x45\x1a\x6f\xda\x0d\x2e\x98\ -\xe0\x2f\x4c\xff\xa9\xc1\x5b\xc9\x20\xc6\x94\x03\x03\xeb\x50\x6a\ -\x1d\xaf\x8a\xf3\x9c\x7a\x8f\x37\x01\xe0\xa3\x13\x35\x8d\x46\x3c\ -\x37\x58\xca\x90\x8e\x05\x17\xaa\x92\x24\xb7\xad\xa9\xf7\x7e\xda\ -\xad\xf4\x0e\xc7\xc5\x74\x38\xf2\x20\x53\x04\x17\xcc\x42\xef\x04\ -\x6d\x76\x90\x32\xb4\x73\xba\x65\x70\x31\xe5\x83\xe1\x62\x28\x83\ -\xdf\x4c\xdd\xa6\x51\x65\x49\x37\x9b\x3f\x14\x53\xc8\x52\xb2\xeb\ -\x9c\x9c\xc1\xbc\x72\x6a\xe0\xa2\xce\x60\x5a\x27\xb6\x52\x1c\x07\ -\x77\x89\xd6\xae\xfb\x95\x62\x33\x4a\x2f\x5d\x09\x26\x73\xfc\x6e\ -\x35\xb6\x65\x00\x83\xb9\x01\x7b\x3f\x54\xd3\xd8\x1b\x62\x30\xa7\ -\x08\x2e\xa6\x18\x8c\x36\x7b\xb1\x94\xce\x81\x0a\x2e\x52\x4c\xe6\ -\xc9\x77\xd7\xbb\x37\x9a\x41\x23\x89\xfb\x1f\xc1\xa5\xa5\xaf\x59\ -\xc5\x39\xb8\xe8\xf6\x7d\x54\x67\xb1\xec\x45\x02\x7c\xb8\x58\x0c\ -\x27\x7b\xe1\xc8\x82\xcf\xc2\x64\xb8\xdf\xfe\xeb\xfb\x31\x65\xfb\ -\xb7\x6e\x42\x9f\x32\xb8\x98\x63\x30\x1a\x95\xe6\xb8\x59\x8c\x05\ -\x70\x91\x64\x32\x62\xe6\x4e\xe9\x99\x85\x43\x92\xea\x6d\x77\x70\ -\xb1\x0f\x30\xd8\x6c\x75\x5b\x4d\xc5\x20\x6d\x0e\x9a\x06\x99\x4e\ -\xdf\xcb\x6c\x20\xe3\xe0\x62\xd4\x07\xa3\xad\xb4\x1e\x16\xd3\xcc\ -\x5e\x12\x6f\x64\x29\x40\xdd\x04\xc8\x20\xfb\x79\x6c\xbb\x95\xf6\ -\x0f\x89\x37\xc9\xa4\xcb\x1c\x2d\x3b\x6d\x65\x6f\x85\xbd\xf4\x98\ -\x46\xd2\xf5\x9c\x7a\x41\x26\x84\x60\xc6\x41\x8a\x69\xfb\x68\x90\ -\x1c\xb5\xc8\x2d\xa7\x93\xdd\x09\xf7\x5c\x65\x52\x59\xf0\xc5\x58\ -\x01\x17\x0e\x90\x79\xf2\x1b\xae\x5d\x23\x41\xb0\xb2\x92\x7c\x7b\ -\xc4\x22\xb7\x98\xe4\x5b\x15\x60\xb0\x39\x5d\xac\xbd\x91\x24\xd8\ -\x8b\xa6\x2f\x23\x22\xf4\x98\x5b\xa8\x8c\x88\x27\xba\xc5\x3d\x72\ -\xee\x8c\x58\xe4\xb3\x24\xc1\x0f\x1a\x95\xe9\x72\x8a\xa0\x14\x49\ -\x17\xce\x2b\x5b\x05\x0e\x12\xb8\x1c\x73\xc6\x6a\x4d\x86\x75\xfa\ -\x45\xe4\x73\x87\x4f\xde\xce\xa0\x3e\xad\xf6\x8f\x62\x2d\x33\x00\ -\xcb\x51\x2e\x8d\x02\x17\xea\x9b\x61\x4a\x61\xcc\x6b\x43\x7a\xfc\ -\x02\x64\x5a\x12\x63\x97\x4a\x71\xa8\xb4\xbd\xf3\x19\xd2\xed\x1f\ -\x91\x88\x7e\xd6\xe4\xf7\x3b\x01\x4d\x9c\xfb\xdf\xeb\x0d\x0a\x33\ -\x54\xb0\xbd\xe8\x8b\xa1\x1f\x64\x1c\xdf\x57\x8c\xb9\x54\xa2\xf9\ -\xb3\xec\x34\x95\xda\xdf\xd3\x87\x1c\x83\x90\xb4\x04\xb4\x9f\x69\ -\x97\xc1\xac\xea\x46\xaf\x2b\xbc\x73\x05\x92\xcd\xe4\x77\xb1\x04\ -\xa4\x58\x26\x93\x33\x67\xad\xd6\x63\x6e\x6d\x3f\x65\x5c\x46\x15\ -\xbe\x9f\x8d\xb1\xc8\xf9\x60\x0a\xe0\xb2\x06\x18\x6b\xbe\x97\x16\ -\x30\xa1\x00\xcc\x30\x70\xa9\x27\x22\x67\xab\x79\x3c\xdb\xa4\x97\ -\x3e\x79\xae\xd5\xae\x59\x84\x8d\xc1\xd4\x12\x0d\xcc\x1c\x9d\x3b\ -\xc5\xae\xd1\xda\x87\x51\x70\x94\x52\xc7\xa2\xc6\x06\x66\x62\x34\ -\xb9\x45\xad\xb9\xd0\xb7\x0a\x2a\xec\x0c\xe6\xdc\x9b\xba\x34\xe9\ -\x27\x60\x2f\x25\x1f\xcc\xac\xa6\xd1\x08\x36\x30\x61\xdd\x70\x31\ -\x73\x5d\xf2\xde\x27\xc1\x60\x8e\x5e\xfb\x58\x65\xed\xa7\x71\xde\ -\xe8\x54\xce\x55\x95\x6a\x87\xcf\xc4\x6a\xb0\x4e\x5f\x6e\x47\xf7\ -\x96\xe7\xc8\x8e\x53\x49\x25\x88\x89\x10\xe0\x5f\x00\x4f\x12\x2f\ -\xb7\x0e\xde\xc0\x52\x0e\x81\xca\x5e\x62\xa7\x29\x32\x23\xd0\xd4\ -\x76\x6d\xac\x9d\x15\x2a\xb5\x69\xd9\x1f\x8e\xf2\xac\xd2\xf7\xdd\ -\xbc\x89\x74\x9e\xa5\x5c\x04\x96\x92\xc9\x64\x15\x60\x96\xa6\xd2\ -\x14\x7e\x97\x49\x59\xc1\xa8\xb3\x3a\xa7\xc6\x24\xa6\x36\x91\xa4\ -\x29\xf8\xe0\x36\x38\xb8\x30\x8c\x5d\x6b\x49\x94\xde\x71\x3f\x65\ -\x7f\x87\x03\x8c\xb2\x6d\xeb\x32\x1f\xe0\x48\xce\x03\x07\x14\x67\ -\x30\x2e\x27\x0a\x38\xdc\xc0\xe2\x60\x62\x84\x34\x48\xfb\x60\x00\ -\x12\x7e\x98\x86\xc9\xe5\x13\xc4\xc5\xc5\x19\xcc\x05\xdc\x48\xa6\ -\x5f\x85\x98\x3f\xbf\xf6\xe8\x11\xc0\x07\x1f\x40\x04\x80\xaf\xae\ -\x5c\x81\xbf\x5e\xbf\x0e\xbf\x77\x70\x71\x71\x71\x06\xd3\x2d\x0f\ -\x1f\x02\x5c\xbb\x96\xbe\xf6\xe0\x01\xc0\xd5\xab\x3e\x42\x2e\x2e\ -\x13\xcb\xd8\x9c\xbc\xef\xbc\x43\xbb\xe6\xe2\xe2\xe2\x0c\xa6\x2a\ -\x57\xae\x00\x7c\xf3\x4d\xfa\xda\x73\xcf\x01\x7c\xfd\xb5\x8f\x90\ -\x8b\x8b\x33\x18\xa2\xfc\xe2\x17\xb4\x6b\x2e\x2e\x2e\xce\x60\xaa\ -\xe2\x3e\x18\x17\x17\x67\x30\x62\x72\xf5\x2a\x7c\x76\xf7\x2e\x7c\ -\x7a\xf3\x26\x00\x00\x7c\xf5\xfc\xf3\x00\xb7\x6f\x03\x7c\xf9\xa5\ -\x83\x8b\x8b\x8b\x33\x18\x17\x17\x17\x17\xab\x0c\xc6\xc5\xc5\xc5\ -\x01\xc6\xc5\xc5\xc5\xc5\x01\xc6\xc5\xc5\xc5\x01\xc6\xc5\xc5\xc5\ -\x01\xc6\xc5\xc5\xc5\xc5\x01\xc6\xc5\xc5\xc5\xa6\xfc\x1f\x10\x8b\ -\x37\x21\x70\x93\x2c\x3e\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\ -\x60\x82\ -\x00\x00\x00\xb8\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x12\x00\x00\x00\x12\x08\x06\x00\x00\x00\x56\xce\x8e\x57\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdd\x06\x13\ -\x0c\x37\x04\x31\xe8\x35\xc9\x00\x00\x00\x38\x49\x44\x41\x54\x38\ -\xcb\x63\x60\x18\x20\xf0\x1f\x97\x04\x13\x35\x0c\x21\xd5\x25\xff\ -\x87\x8c\x21\xff\x09\x85\x11\xcc\x00\x46\x4a\xc2\x8d\x58\x97\xfc\ -\x1f\x35\x84\xf4\x64\xf0\x7f\x58\x25\x48\xfa\x18\x42\xb5\xdc\x4e\ -\x32\x00\x00\x8e\x77\x28\xdd\x40\x15\x3d\x50\x00\x00\x00\x00\x49\ -\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x00\xda\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x12\x00\x00\x00\x12\x08\x06\x00\x00\x00\x56\xce\x8e\x57\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdd\x06\x13\ -\x0d\x01\x01\xc9\x5c\x3a\x04\x00\x00\x00\x5a\x49\x44\x41\x54\x38\ -\xcb\x63\x60\xc0\x0f\x18\x19\x18\x18\x98\x18\x18\x18\xb8\xa0\x6c\ -\xfa\x02\x46\x6a\xda\x4a\x7f\xe7\x0f\x1a\x97\xe1\xb4\x80\x19\x4a\ -\x73\x20\x05\x36\x1b\x92\x86\xff\x48\x6a\xb0\x19\x86\x61\xf0\x7f\ -\x3c\x98\x17\x6a\x18\x8c\xcf\x02\x35\x00\xc6\x67\x60\x80\x26\x36\ -\x42\xe0\x2f\x54\xf3\x30\x02\x8c\xc3\xcb\x3b\x4c\x43\xc2\xfb\x1c\ -\x50\x8c\x17\x00\x00\x3c\xb4\x0d\x4d\x25\x0f\x12\x1c\x00\x00\x00\ -\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x01\x9b\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x12\x00\x00\x00\x12\x08\x06\x00\x00\x00\x56\xce\x8e\x57\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdd\x06\x13\ -\x0c\x2c\x0e\x78\x0b\x17\x4d\x00\x00\x01\x1b\x49\x44\x41\x54\x38\ -\xcb\xa5\xd4\x3d\x2f\x83\x51\x14\x07\xf0\xdf\xf3\x54\x23\x08\x91\ -\x2e\x12\x1f\x82\xa5\x83\x45\x62\x90\x48\x2c\x06\x31\x8b\xd9\x2a\ -\x21\x16\x89\x6f\x60\x30\x88\xd8\x2d\x12\x9b\xc9\x20\xc1\xc0\x60\ -\xc2\xe0\xa5\x2a\x42\xd2\x81\xc5\xcb\x22\xa9\xe5\x56\x9e\xd4\xd3\ -\x6a\xfb\xfc\x97\x9b\x7b\xce\xb9\xff\x73\xcf\x6b\xa4\x31\x62\x14\ -\xd0\x1b\xee\x1f\x78\x45\x35\xcd\x38\x0a\x67\x55\x36\x44\x51\x13\ -\xe5\x3e\x46\xf1\x15\x1c\x76\xe3\x02\xb3\xad\x30\x0f\xe0\x0c\x77\ -\x98\x4c\xd1\x4f\xe3\x11\x47\x89\x90\xff\xa0\x1f\x37\x58\xfe\xc7\ -\x59\x8c\x35\x5c\xa1\x27\xcd\xe0\x04\x2b\x6d\xe4\x65\x1d\x07\xf5\ -\xc2\x39\x94\x13\xc9\x6f\x05\x79\x54\x30\x55\x13\x14\x70\x8a\x99\ -\x0e\xaa\x35\x8f\xc3\x90\x5b\xc3\x78\xc8\x50\xfa\x32\x86\xe2\x70\ -\x79\xca\x40\x54\xa9\x55\xe0\x13\xb9\x0c\x44\xb9\xd0\xf5\xbf\xdf\ -\xeb\x04\x79\x94\x6a\x3f\x82\x5b\x2c\x74\x40\xb4\x88\xcb\xa4\x60\ -\x02\x6f\x8d\x1a\xac\xc9\x14\xbc\xa3\x58\xaf\xd8\xc5\x46\x1b\x44\ -\xdb\xd8\x4a\x53\x74\xe1\x1c\x9b\x61\x5c\x1a\x61\x10\x3b\x38\x4e\ -\x36\x70\x9c\x30\xf8\xc6\x58\x08\xef\x05\x4b\xe8\xab\x0b\x65\x15\ -\xcf\x61\x23\x8c\xb7\xb2\x7e\x8a\x61\x8d\x94\xc2\x70\x5e\xe3\x1e\ -\x7b\x18\x49\x7b\xf0\x03\x3d\xd6\x32\x0d\x0a\x21\xca\xef\x00\x00\ -\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x01\x0c\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x12\x00\x00\x00\x12\x08\x06\x00\x00\x00\x56\xce\x8e\x57\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x01\x16\ -\x0d\x23\x01\x6d\x84\x22\xa8\x00\x00\x00\x8c\x49\x44\x41\x54\x38\ -\xcb\xbd\x93\xd1\x0e\x83\x30\x08\x45\xcf\xb4\xd3\xda\x19\x7d\x31\ -\xfb\xff\x2f\x5d\x7d\x61\x09\x59\xd0\x41\x13\xbd\x49\x03\x29\xed\ -\x85\x5e\x28\x9c\xe3\x01\x74\x40\x11\xff\x7a\xf4\x62\x93\xd8\x1a\ -\xb8\x3b\x4a\xb5\x9f\xdf\x40\x02\x66\x27\xc9\x53\xd6\x21\xaa\xa3\ -\x2a\x7d\xa6\x6b\x25\xd3\x31\x97\xf8\x16\x59\x0d\x6a\x68\x5e\x74\ -\x93\x8c\xd2\xc1\x5e\xf9\xab\x22\xa8\xc0\x5b\xe6\xc9\x4c\xf0\x15\ -\x2b\xab\xcd\xd7\x49\x85\xc3\x6d\x4f\x8b\x8a\xdd\xdc\xb1\x70\xe7\ -\xa2\x03\x69\xa2\xa8\x3f\xf7\x0f\x19\x98\xac\xc0\xd6\xd0\x91\x45\ -\x08\xef\x47\xf6\x64\xde\x01\xde\xc8\x2c\xd0\x78\x42\x6d\x42\x00\ -\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x00\xc2\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x12\x00\x00\x00\x12\x08\x06\x00\x00\x00\x56\xce\x8e\x57\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdd\x06\x13\ -\x0b\x2b\x0c\xdc\x0b\xf6\x23\x00\x00\x00\x42\x49\x44\x41\x54\x38\ -\xcb\x63\x60\x18\x8a\xe0\x3f\x14\xe3\x05\x4c\xd4\xb2\x8d\x91\x08\ -\xd7\x90\xa2\x9e\x28\x2f\xe1\xf5\x22\xd3\x40\x05\x30\x4e\x57\x31\ -\x0d\x64\x74\x63\x95\x67\x1a\xd0\xc4\x87\x4d\x1d\xd3\x40\xba\x06\ -\xab\x7a\x16\x22\x52\x33\x51\x80\x89\x61\xd8\x02\x00\x13\xce\x17\ -\xfb\xcd\x4f\x38\x19\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\ -\x82\ -\x00\x00\x00\xb0\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x12\x00\x00\x00\x12\x08\x06\x00\x00\x00\x56\xce\x8e\x57\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdd\x06\x13\ -\x0b\x29\x19\x83\xe0\x70\x4a\x00\x00\x00\x30\x49\x44\x41\x54\x38\ -\xcb\x63\x60\xa0\x12\x60\x84\xd2\xff\x29\x35\x87\x89\x5a\x2e\x62\ -\xc1\xe1\x42\x62\x01\xdc\x27\x54\x73\xd1\xa8\x41\xa3\x06\x0d\x4f\ -\x83\x58\x70\xe5\x9d\x01\x73\x11\xd5\x00\x00\x03\xf8\x03\x23\xd9\ -\x76\xec\x65\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x01\x13\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x12\x00\x00\x00\x12\x08\x06\x00\x00\x00\x56\xce\x8e\x57\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdd\x06\x13\ -\x0c\x23\x10\x05\x9c\x36\xe1\x00\x00\x00\x93\x49\x44\x41\x54\x38\ -\xcb\xcd\x94\xcd\x0e\x84\x20\x0c\x84\x3f\xaa\x86\x98\x6c\xf6\xe6\ -\xfb\xbf\xa7\x51\xf0\xa2\x1b\xd2\x45\x8a\xe8\xc1\x49\x38\xf0\xd3\ -\xc9\x4c\x07\x80\x87\x11\xf7\xf1\x69\xa8\x75\x00\xa2\x16\xd7\x46\ -\x11\x7f\x8a\x9a\xd1\xab\xb9\x00\xe1\x90\x9b\x48\x97\x4c\xed\x52\ -\x22\xaa\xb5\xe6\xc9\x28\x68\x81\x58\xd6\x5c\x21\x99\x1e\x98\x73\ -\x0d\xbe\xa2\x28\xaa\x9e\x98\x44\x2e\x19\xa5\x98\x07\x8b\x68\xac\ -\xbc\x0a\x8b\x45\x34\x01\x5d\x85\xe5\xd5\x7a\x22\x3a\x08\xaf\x02\ -\x39\xce\x79\x2b\xb5\x14\x61\x2f\x0a\x35\x8a\x4a\x16\x3a\xe0\x7b\ -\xb2\x37\xa9\x70\x7e\xe9\xc4\x9b\xbf\x87\x13\xde\x86\x0d\xfe\xc8\ -\x1c\x79\x0a\xbf\x98\xa7\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\ -\x60\x82\ -\x00\x00\x06\x04\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x01\x18\x00\x00\x00\xa0\x08\x06\x00\x00\x00\xd7\x60\x73\x20\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x01\x18\ -\x0c\x37\x05\xa5\x5a\x2c\xe0\x00\x00\x00\x45\x69\x54\x58\x74\x43\ -\x6f\x6d\x6d\x65\x6e\x74\x00\x00\x00\x00\x00\x43\x52\x45\x41\x54\ -\x4f\x52\x3a\x20\x67\x64\x2d\x6a\x70\x65\x67\x20\x76\x31\x2e\x30\ -\x20\x28\x75\x73\x69\x6e\x67\x20\x49\x4a\x47\x20\x4a\x50\x45\x47\ -\x20\x76\x36\x32\x29\x2c\x20\x71\x75\x61\x6c\x69\x74\x79\x20\x3d\ -\x20\x39\x30\x0a\x67\x9d\x47\x40\x00\x00\x05\x33\x49\x44\x41\x54\ -\x78\xda\xed\xdd\x4f\x6e\x1b\x37\x14\x07\xe0\x47\xc3\x0a\xfa\x0f\ -\xed\xae\xab\xae\xba\x48\x80\x16\xe8\x2d\x7a\x3e\xf5\x04\xb9\x41\ -\x2e\x54\x9f\x42\x40\x1b\x20\x05\x0a\x76\x61\x19\x90\xc6\x52\x35\ -\x1a\x73\x66\x38\xe4\xf7\x01\x42\x2c\x07\x71\xac\x47\xf2\x47\xbe\ -\x91\x2c\x47\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0d\ -\x49\x4a\xc0\x18\xd9\xc4\x41\xc0\xb0\x44\xb8\x98\x3c\x8c\xf5\xa0\ -\x04\xdc\xf2\x24\x5c\x10\x30\x80\x16\x89\x26\x5a\x24\x13\x07\x27\ -\x18\xec\x42\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\xc0\x7c\xbc\x59\x19\x6b\xca\xe6\xaf\x80\x81\ -\x1a\xc2\xc4\x9c\x16\x30\x30\x29\x54\x52\x25\x5f\x17\x01\xc3\xc6\ -\x43\x25\x35\xf6\xff\xa2\xe8\x34\x1a\x2a\xc2\x46\xc0\xd0\x69\xb0\ -\x24\xdf\x23\x0a\x4c\xc9\x85\x9b\x7c\xcf\x9c\xf2\x8b\xd7\xb8\xc7\ -\xfb\xe3\x22\xcd\x17\x16\xe9\x16\x16\xea\xa5\xef\x33\xc7\x72\xcf\ -\x70\x39\xc1\xc0\x9d\xad\x46\xf2\x78\x70\x82\xe1\x2d\x0b\xf1\xda\ -\x62\xfc\x65\xe3\x9b\x6b\xba\x23\x78\x70\x82\xc1\x2e\x5f\xec\xb1\ -\x5a\x1b\x4e\x30\x2c\x1c\x2e\xbf\x36\xbc\xf0\x66\xbb\x36\x93\xc3\ -\x85\x1e\xb8\xb4\x1e\x5e\x6e\xbf\x77\xfe\xf8\x85\x0b\xcc\xb4\xb8\ -\x7e\x52\x8b\xe9\xf9\xd0\x7b\xb8\xe8\x33\xb9\xd6\x12\x99\x1b\x05\ -\x6a\x92\xe3\xfa\x95\xe4\x1e\x0a\x6c\x12\xe1\xc5\x67\x0b\x07\x6f\ -\xee\xa8\xc8\x26\x92\xc5\x63\x3e\x2c\x58\xa7\xdc\x59\xb1\x3d\x8b\ -\x64\xd1\x9c\xcd\xf7\x0f\xea\x32\x66\x23\xce\x6f\x2d\xb8\x24\xa7\ -\xe5\x60\xb9\x78\x01\x33\x47\xc4\x27\xf5\x19\x5b\x3b\xe0\x46\xb8\ -\xbc\xfa\x8b\xbf\xd5\x48\xc8\xcc\x78\xf4\xa3\xd3\xeb\x08\x8e\xef\ -\x65\xea\x88\xe2\xf4\xbe\x20\xfe\x37\x5c\x4c\x0a\x21\x53\x92\x8b\ -\xbc\x9d\x87\xcb\xa7\x88\xf8\x6c\xa5\xd8\x98\x15\x8a\xd2\xe1\x42\ -\xd1\x1a\xab\xed\x05\x8f\x4a\xd0\x45\xb0\x9c\x2e\x80\x77\x11\xb1\ -\x3b\x8e\xfd\x41\xb9\x70\x82\xa1\x54\xb8\xe0\x14\xb3\x28\xd7\x60\ -\x84\x0b\xf3\x8f\x81\x13\x0c\x82\x05\xa7\x18\x01\x83\x70\x11\x32\ -\x5a\x24\x84\x0b\xa4\xda\x57\x8c\xd5\xa2\x54\x1b\x1c\x17\x63\x51\ -\x5b\x21\xfc\xfa\x3d\xa7\x16\x6d\x92\x16\x09\xe1\x02\x02\x46\xb8\ -\xd0\x73\x47\x20\x60\x18\x1b\x2c\xd7\xae\xb7\x98\xcc\x33\xda\x45\ -\xc4\x5f\xf1\xfc\x52\xe7\xfd\xf1\x7e\xc1\xae\x9f\xb5\x56\x52\xd1\ -\xdf\x19\xd1\x66\x49\x58\xc0\x97\x41\xd1\xf7\xd3\xc6\x8d\xda\x57\ -\x94\x52\x28\xc7\x1a\x0e\x83\xc2\x1f\xa6\x8d\x9f\x16\xa9\xb6\x06\ -\x76\x78\xd3\x12\x69\x89\xd6\xf0\x71\x70\xff\xfb\xe9\xe3\x09\x4e\ -\x2d\x9c\xdb\x1d\xdb\xa2\xc3\xb4\x81\x30\x7e\x27\x3b\x23\x33\x26\ -\xc6\xc8\x82\x7b\x96\xa8\xdd\xe1\xef\x7a\x0c\xbd\x1f\xcc\x82\xe1\ -\xf2\xf2\xf9\x24\x58\xe8\x84\xa7\xa9\x17\x0c\x97\xc1\xdf\x0b\x17\ -\x04\x8c\xb0\x78\xbe\xfd\x59\xb0\xa1\x4e\xc2\xa5\xa7\xb9\xd0\xf5\ -\x75\x18\x13\x7a\xc2\xcc\x48\xd3\xff\x9d\x60\xe9\x67\x2e\xf8\xb9\ -\x24\x27\x98\x45\x93\x5c\xb8\xd8\xbc\x05\x0c\xc2\x05\x4a\xf1\x2c\ -\xd2\x4c\x5b\x57\x16\x2c\xe0\x04\x33\xf6\x8c\xfb\x74\x67\x32\x08\ -\x17\x73\x01\x35\x9a\x8b\x70\xe1\x74\x1e\x74\x3b\xee\x5a\xa4\x65\ -\xc2\x45\xb0\x20\x60\x70\x6a\x81\x92\x5c\x83\x11\x2e\x20\x60\x84\ -\x0b\x68\x91\x04\x8b\x60\x01\x27\x18\xe1\x02\x02\x46\xb8\x80\x16\ -\xa9\xeb\x70\x11\x2c\xdc\xb3\x11\x39\xc1\x30\x9a\x70\x01\x01\xf3\ -\xe6\x9d\xe8\xd2\x2b\x32\x85\x0b\x36\x21\x0f\xbe\xe8\x31\x57\xbd\ -\xb8\x77\xee\x74\x3d\x67\x9c\x60\x00\x01\xa3\x25\x62\xc3\x27\x5f\ -\x2d\x12\x5a\x22\x66\x99\x47\x5a\x24\xc0\x06\x2e\x60\xb4\x44\x68\ -\x8f\x24\xac\x96\x08\x73\xca\x7c\x72\x82\x01\x9b\xb7\x80\xd1\x12\ -\xa1\x3d\x92\xb2\x5a\x22\x04\x8c\xb9\xa5\x45\x02\x1b\xb7\x80\xd1\ -\x12\xa1\x3d\x92\xb4\x5a\x22\xd0\x1e\x69\x91\x40\xb8\x08\x18\x2d\ -\x11\x5a\x23\x2d\x92\x96\x08\x9c\x5e\xb4\x48\x20\x5c\xd6\xf2\xd8\ -\xd0\x20\xa7\xe3\xcd\x1b\xfd\xd0\x55\x4f\x56\xf3\x44\x4f\x6a\x0d\ -\xdb\x39\xbd\x6c\xed\x57\x5a\x68\x91\xa0\xdc\x3a\xa7\x81\x80\xf1\ -\x2c\x11\x4e\xcd\x02\x66\xf6\xc1\x1d\x5e\x7b\x81\xa5\x99\x77\x5a\ -\x24\x98\x65\x83\x5b\x25\x5c\xd2\xc6\x52\x2e\x6d\x68\x50\xd3\x95\ -\xfb\xa0\x35\x12\x30\x06\x15\xe1\xa2\x45\xaa\xcb\xbf\x86\x08\xe1\ -\x42\xe9\x01\xcd\xf1\xfc\x22\xc0\x87\x38\x7f\xd6\x08\xd6\x98\x8b\ -\xe6\x60\x23\x2d\x92\xdd\x02\x73\xb1\x21\xb5\xfc\xa8\xc0\x37\x76\ -\x08\x2a\x0d\x16\xe1\xd2\xc8\x31\xf4\x5d\x44\x7c\xe5\x38\x5a\xed\ -\x20\xed\x72\xc4\x3e\x47\x1c\x8e\x7f\xee\x3a\x68\x89\xcc\x43\x3d\ -\x2e\x0b\x0d\xd4\x3e\x47\xe4\x93\xdb\xde\x5c\xa4\x56\x5f\x0f\x4e\ -\x2b\x06\xb5\xfe\xd5\x77\x18\x04\xcc\xc1\xa9\x85\x5b\x1e\x56\x1a\ -\xd0\xcf\xc7\x80\xf9\x56\x9f\xbb\x19\x1f\x6f\xdc\xdf\x6a\xb0\x0c\ -\x99\x87\x0d\xed\x16\x3f\xc4\xf3\x05\x5e\xea\x1f\xb8\xb3\x6b\x30\ -\xbb\x6d\xef\xf8\x77\x9d\x5a\x1c\x69\xa6\x4b\x2b\x0c\xac\xdd\xe2\ -\xce\x22\x55\x5a\xa8\x4b\x3f\xc2\x91\x5a\x2b\xad\xb7\xac\xab\xb8\ -\x45\x1a\xee\x7a\xef\x23\x7e\x36\x5e\xb7\xfd\x71\xe3\x7e\x45\x9b\ -\xd3\xf0\xe7\xc3\x6a\xdc\xec\xf3\xd4\x76\xc8\xc9\xa5\xfe\xed\xa2\ -\xf5\x67\x1e\x66\x71\x18\xac\x8a\xc3\x26\x86\xfa\xd5\x5b\x69\xac\ -\xd9\x3e\xe5\x1b\xb7\xb2\x3d\x14\xab\x8d\x72\xab\xcf\x3c\xcc\x6a\ -\x3f\x98\xd8\xfb\xcd\x0d\xfb\xd5\xc0\xa9\x3a\x54\x6e\x7d\x21\x9c\ -\x60\x5a\xa9\x5b\x4b\x93\x7b\xf8\x30\x5e\x3e\xfe\xf0\xc6\xaf\xfb\ -\xe3\xe0\x6b\x15\x29\x5b\x16\x30\x9b\x9a\x59\x3d\xbc\xfa\xb3\xf7\ -\x00\x99\xfa\xf0\xbf\x1b\x3c\xfc\x7f\x2e\x84\xd0\x98\x8f\x73\x44\ -\xfc\x56\xb2\x94\xc2\xa5\x1c\x17\x5a\x57\x5a\x61\x06\xe2\xac\x14\ -\x69\xe2\xc7\xe6\xaf\x80\x41\xc0\xd0\x2b\xef\xc9\x0b\xcc\xe6\x51\ -\x09\x9c\x44\x40\xc0\xe8\x4b\x41\x8b\x04\x20\x60\x00\x01\x03\x00\ -\xf0\x8a\xeb\x8d\x8c\xe2\x7d\x36\x10\x30\x2c\x12\x2e\x26\x0f\x63\ -\xb9\x06\xc3\x4d\x4f\x23\x3f\x07\x02\x06\xd0\x22\x51\x7f\x8b\x64\ -\x32\xe1\x04\x83\x5d\x08\x73\x87\xed\x9f\x6a\x4c\x26\x9c\x60\x00\ -\x01\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x4d\xfb\x0f\x74\ -\x4e\x10\x25\x18\x9c\x47\x0f\x00\x00\x00\x00\x49\x45\x4e\x44\xae\ -\x42\x60\x82\ -\x00\x00\x06\x75\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x01\x18\x00\x00\x00\xa0\x08\x06\x00\x00\x00\xd7\x60\x73\x20\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x01\x1b\ -\x0a\x13\x2e\xee\x36\xe7\x5a\x00\x00\x00\x45\x69\x54\x58\x74\x43\ -\x6f\x6d\x6d\x65\x6e\x74\x00\x00\x00\x00\x00\x43\x52\x45\x41\x54\ -\x4f\x52\x3a\x20\x67\x64\x2d\x6a\x70\x65\x67\x20\x76\x31\x2e\x30\ -\x20\x28\x75\x73\x69\x6e\x67\x20\x49\x4a\x47\x20\x4a\x50\x45\x47\ -\x20\x76\x36\x32\x29\x2c\x20\x71\x75\x61\x6c\x69\x74\x79\x20\x3d\ -\x20\x39\x30\x0a\x67\x9d\x47\x40\x00\x00\x05\xa4\x49\x44\x41\x54\ -\x78\xda\xed\xdd\x51\x6e\xe3\x36\x10\x06\x60\xd2\xb0\x5f\xdb\x5b\ -\x74\x8f\xab\x1b\xe4\x06\x7b\xa1\xe6\x14\x0e\xda\x02\xfb\xa4\x3e\ -\x6c\x80\x66\xd5\x24\x8e\x6c\x51\xe2\x70\xbe\x0f\xc8\x43\x80\xc4\ -\xb6\xa8\xe1\xaf\xa1\x2c\xd9\xa5\x00\x00\x44\x53\x1b\x3d\xee\xbc\ -\xc3\x73\x00\x9d\x3b\xed\xf0\x1c\xb3\x61\x06\x01\x23\x64\x80\x10\ -\x4b\xa4\x8f\x42\xc5\x72\x09\x04\x8c\x90\x01\xfa\x0f\x18\x21\x03\ -\x02\x46\xc8\x00\x71\x03\x46\xc8\x80\x80\x11\x32\x40\xdc\x80\x11\ -\x32\x41\xcd\x76\x18\x41\x02\x46\xc8\x04\x0f\x17\x3b\x8c\xaf\x3a\ -\x1d\xf4\xbc\x75\x45\x1d\x73\xb0\x67\xe1\x42\xb0\x80\x11\x32\x60\ -\x89\x64\xb9\x84\x1d\x44\xcc\x0e\x46\x27\xe3\x28\x84\xda\x71\xa0\ -\x04\xc6\x38\x38\x09\x19\x10\x30\x42\x06\x88\xbb\xbc\x16\x32\x20\ -\x60\x84\x0c\x10\x33\x60\x84\x0c\x08\x18\x21\x03\xc4\x0d\x18\x21\ -\x03\x02\x46\xc8\x00\xbf\x3a\x05\x7a\xad\xae\xf8\x4d\xc4\x8e\xd5\ -\xc1\xe8\x64\x80\xe1\x02\x46\xc8\x80\x25\x92\xe5\x12\x96\x4b\x3a\ -\x98\xf1\x6a\x4f\x27\x03\x3a\x18\x01\x09\x02\x46\xc8\x00\xc0\xfe\ -\xeb\x73\xe7\x87\x1c\xfd\xa1\x49\xb8\x98\x48\xb7\x9d\x93\xd4\x80\ -\xfd\x4f\xb3\x70\xe1\x63\x27\x35\x01\x0a\x49\xc0\xa8\x0d\x08\x67\ -\xf4\xa5\x83\xeb\x64\x68\x5e\x54\x0a\x2a\x6f\xc0\xa8\x07\x14\x92\ -\x25\x92\x10\x85\x11\x9d\x93\x6c\xa7\x90\xe1\xa1\xae\xe5\xc5\x50\ -\x98\x78\xf4\x35\x31\xeb\x20\xdb\x61\xf2\x58\x22\xdd\x5b\x3b\x2e\ -\xc6\x74\xe4\xfa\xd4\xcb\x3b\xbf\x57\xe1\x22\x60\x36\x3c\x50\x91\ -\xd8\xd3\xe2\xf7\xdf\x0c\x09\x77\x74\x30\x3a\x19\xe9\xfd\xae\x4b\ -\x29\x65\x2a\xa5\x5c\x1f\x28\x92\xcc\x05\x56\xd5\xbe\x31\x61\x9f\ -\x02\xcb\x58\x58\xd9\x97\x48\xc2\x04\x85\x26\x60\xec\x7b\xe2\x2e\ -\xdf\x32\x7f\xbe\xab\xc9\x85\x70\xd9\x79\x22\xcc\x89\x26\x9f\x0e\ -\x06\x9d\x4b\x82\xe7\x15\x30\x7d\xd6\x60\xba\x93\xff\xdf\x84\xcb\ -\x70\x1d\x13\xfd\x07\xcc\x9c\x65\x83\xbf\x27\xdb\xa9\xae\x4f\xa0\ -\xa7\x5a\x1c\x7e\x63\xff\x76\xe4\x60\x43\x3a\xb5\xf5\xdd\x74\x1d\ -\x7d\x43\x15\x05\x5b\x39\x1b\x82\x9b\x01\x3c\xfc\x41\x2e\xc3\x51\ -\xdc\x15\x95\xc7\x70\x92\x37\x79\x97\xf7\xbd\x94\xf2\xcf\x27\x1b\ -\x6b\x09\x01\x70\xc3\xdb\x7b\x89\x9c\x7b\xd1\xc1\xc0\xa6\x9e\x0c\ -\x01\x01\x97\xf5\xa9\x0e\x86\xd9\xef\x8a\x66\x3d\xe7\xb9\xb6\x9b\ -\x6f\xc6\x12\x2c\x91\x1c\xe0\x41\x07\x13\x37\x54\x8c\x29\xe8\x60\ -\x04\x34\xa4\x9a\x20\x81\xdb\x01\x17\xc2\x42\xcf\xf3\xd7\xd7\x43\ -\x70\x6f\xcd\xa8\x0f\x4b\x24\x68\x76\x40\x72\x76\xbd\x5f\xee\x45\ -\xb2\x6c\x0a\x3d\xb0\xe8\x60\x30\x27\xc2\x0d\xe4\xa5\x94\xf2\x57\ -\xf9\x79\x61\xde\xf4\xfa\x3b\xc1\x0b\x67\xb0\x0f\x03\xf2\xb9\x46\ -\x3b\xd6\xc9\xd6\x7e\x2c\x1e\x7f\x32\xf4\x63\x16\x8f\x4d\xd2\x42\ -\x1d\x31\xa0\xcb\x9b\x23\xaf\xa6\x67\xfc\x25\x52\x7d\xe7\x27\xb8\ -\xea\x85\x6c\xeb\x65\xa7\xe7\xf1\x95\xb1\x44\x3d\xf0\xf2\x80\x69\ -\xa7\x0e\xc6\xcd\x91\x68\xe7\x13\x2e\x97\x4c\x7c\x2d\x3c\xc5\x85\ -\x83\xa0\xd6\x0f\xec\x10\xaa\xeb\x64\x48\xc0\x75\x30\x5f\x5c\xe6\ -\xfc\xb9\x61\x3b\x5e\xc7\x5a\xb5\xa8\x05\x74\x30\x5b\x76\x22\x75\ -\x9b\xff\xf3\x51\x0f\x09\x6a\x41\x07\xc3\x51\x49\x5e\x4b\xf9\xf5\ -\xa4\xe5\x54\xca\x3c\xbb\x68\xd4\xd1\x99\x5c\x2d\xf1\xda\x77\x82\ -\xd6\xfc\xfd\x6b\xa8\xbc\xfd\x71\xd1\x68\x80\x5a\x80\xc3\x02\xa6\ -\xac\xfb\xbb\xeb\x22\x60\xae\x59\xc7\xd8\xeb\xb4\x44\x4a\xdb\x02\ -\x3f\xaf\x6c\x8b\x57\xb4\xcf\x4f\x37\x7e\x1f\x3a\x5c\xee\x09\xe5\ -\x23\x6b\xc1\xb2\x88\x68\x93\xec\x32\x97\x32\xbd\x76\x32\x53\x96\ -\x73\x30\x83\xdd\xd8\xca\xe3\x07\x5a\x68\xd7\xbd\x28\x48\x4b\x24\ -\x3a\x58\xce\x3b\xca\x23\x60\x68\x96\x0f\x23\xdf\x15\xad\x7b\x11\ -\x30\x1c\x18\x2e\x23\xf1\x5d\xd1\xd0\x66\x59\xb4\xd9\xf9\x4c\x77\ -\x45\xd3\x3b\x5d\xe9\x31\xdd\x8b\x71\xc7\x12\x09\xa1\x0e\x8a\x1d\ -\x2d\x1b\x02\x86\x9c\xe1\xa2\xd8\xf2\xf2\xc5\x6b\x6d\xe7\x97\x39\ -\x45\x6a\xce\xc1\xec\x73\xf0\x06\x01\x83\x70\x81\x2d\x69\xe1\xdb\ -\x85\x4b\x35\x20\x06\x43\xc0\x60\x3e\x41\x23\x4e\xf2\x0a\x6d\xad\ -\x1b\x02\x46\xa8\x08\x08\xe2\x71\x92\x97\x87\xbc\x18\x02\x04\xcc\ -\xe6\x07\xfd\xb4\xf7\xe7\x2d\xef\xd8\x74\x57\x34\xb4\x9b\x5f\xc3\ -\x6d\xd0\xbc\xf2\x6f\xe7\xe2\xae\x68\x06\x3c\x77\x70\xd0\x5c\x1c\ -\x6e\xfc\xd6\x6c\xd4\x9e\xb7\x00\x38\x37\x34\x06\x27\x79\x93\xd7\ -\xbb\x49\x4b\x4b\xce\xc1\x98\x97\x20\x60\x84\x4b\x9f\x1b\x2e\x69\ -\x31\x59\xb0\x26\x05\x40\x07\x93\xe9\xe0\x69\x7c\xe0\x01\xce\xc1\ -\xdc\xee\xcc\x81\x3b\x79\x9b\x5a\xb8\x10\xbc\x50\x7b\x6e\xb3\x2d\ -\x01\x3e\x0e\x97\x94\x77\x45\x2b\x88\x98\x47\xc1\x5e\xf7\x9b\x25\ -\x92\xe0\x75\xb4\x41\xc0\x98\x67\x36\x1a\x52\xb7\xae\x93\x61\x00\ -\x5a\x84\xcb\x5c\x7e\xde\x51\x0c\xe4\x5e\x22\x6d\xf6\x71\x0b\xd3\ -\xe2\x41\x7c\x36\x0a\xe4\x5e\x7a\x6f\xfa\x0e\x9f\xcb\xd9\x41\xc0\ -\xc8\x03\x10\x30\xc2\x05\x46\x93\xf5\x6d\x6a\xe1\x02\x02\x46\xb8\ -\x80\xc9\x86\x35\x26\xe8\x60\x80\x3d\x8d\x78\x37\xb5\xcf\x73\xd1\ -\xfa\xa2\x83\xd9\xad\x93\x4f\xe3\x52\xfe\xfb\x8e\x22\x60\xfb\x70\ -\x19\xee\x8b\xd1\xd6\x98\x1a\x6e\xf4\x72\x60\x2f\xc9\x0a\x0b\xe1\ -\x32\x4c\xb8\xdc\xbb\x31\x3a\x97\xb6\xfb\x82\xce\x27\xca\x1f\xc2\ -\x65\x55\x27\xf2\xf6\xab\x59\x39\xbe\x7e\xc9\xd7\x56\x0c\xb9\x2c\ -\xba\x2e\x36\xe8\xfa\xc0\xe0\xa0\xb0\x8e\x74\x0a\x5e\x03\x4b\x43\ -\xbc\xb9\xb1\xbc\x2b\xfb\xf7\x3b\x1f\xc7\x3b\x3d\x82\xf9\x68\x35\ -\x68\x02\x0c\x7d\xdd\x97\xf7\xd9\xfb\x0c\x17\xfb\x22\x47\x07\xd3\ -\x4d\xb8\x2c\x5f\xc8\x65\xc5\xff\x7d\xd6\x7e\xd7\x37\x3f\xf4\x71\ -\xc4\xb5\x2f\x2c\x8d\x77\x37\xdd\xf8\x1d\x10\x2e\x77\xdb\xea\x64\ -\x2c\x58\x22\x59\x16\xfd\xcf\xd3\x62\x39\xe3\x23\x33\x21\xde\xd2\ -\xb2\xdb\x13\xba\xf3\xe2\x85\xcc\x5f\x7c\x61\xee\x4c\xee\xa3\x88\ -\x8c\x7b\x7b\xe7\x80\x75\x11\xbe\x26\x14\x35\x96\x48\xc2\x85\xc1\ -\x5b\x75\xc5\x94\xb7\x83\xf1\x5d\xd1\x26\x81\xce\x51\x07\x23\x5c\ -\x4c\x08\x88\x53\xc3\xc2\x05\x04\x8c\x70\x39\x62\x70\x6a\x47\x3b\ -\xc7\x8e\x21\xd2\x12\x49\xb8\xac\x1c\x9c\x4c\xcf\x8f\x80\x11\x2e\ -\x8d\xdb\xcc\x23\x07\xe4\xd9\xce\x21\x68\xc0\x08\x17\x6b\x6a\xd4\ -\x8b\x70\x89\xb6\x84\xaa\x0d\x1e\xbf\xde\x78\x6e\x3b\x8f\xa5\xf3\ -\xa0\x73\x82\x1d\x8e\x42\xce\xcd\xd0\xe3\x12\x49\xb8\x0c\xda\x35\ -\xc1\xd1\x01\x23\x5c\x40\xc0\x08\x17\xa0\xdd\xf2\x5a\xb8\x00\x21\ -\x02\x46\xb8\x80\x80\x11\x2e\x40\x9c\x80\x11\x2e\x20\x60\x84\x0b\ -\xd0\xc6\x5e\xef\x22\x09\x17\x10\x30\xc2\x05\x00\x00\xc8\xec\x5f\ -\x7c\x1d\xfd\x3c\xa5\xd3\xc9\xa6\x00\x00\x00\x00\x49\x45\x4e\x44\ -\xae\x42\x60\x82\ -\x00\x00\x10\x78\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x01\x18\x00\x00\x00\xa0\x08\x06\x00\x00\x00\xd7\x60\x73\x20\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x01\x16\ -\x0e\x25\x31\x1f\x41\x0b\xdb\x00\x00\x00\x45\x69\x54\x58\x74\x43\ -\x6f\x6d\x6d\x65\x6e\x74\x00\x00\x00\x00\x00\x43\x52\x45\x41\x54\ -\x4f\x52\x3a\x20\x67\x64\x2d\x6a\x70\x65\x67\x20\x76\x31\x2e\x30\ -\x20\x28\x75\x73\x69\x6e\x67\x20\x49\x4a\x47\x20\x4a\x50\x45\x47\ -\x20\x76\x36\x32\x29\x2c\x20\x71\x75\x61\x6c\x69\x74\x79\x20\x3d\ -\x20\x39\x30\x0a\x67\x9d\x47\x40\x00\x00\x0f\xa7\x49\x44\x41\x54\ -\x78\xda\xed\x9d\x3f\x8c\x5d\xc5\x15\xc6\xcf\xac\x2c\xb0\xb5\x5e\ -\x12\xc9\xa1\x61\x51\xf8\xd3\x50\xbd\xd7\x40\x03\x1d\x2e\xbc\x34\ -\x91\x90\xa9\xe9\x22\xd1\xda\x45\x84\xe5\x96\x06\x29\x0a\x76\x9b\ -\x22\x0d\xa9\xa1\xf7\x5a\x82\x2e\x96\x90\xdd\xec\x2a\x4d\x2a\x13\ -\x0c\xd5\x22\x25\x32\xc8\x06\x45\x9a\x14\x6f\x9f\xf7\xbe\x79\x33\ -\x73\x67\xe6\xce\xcc\x9d\x73\xe6\xfb\xa4\x15\xde\xbd\xcb\xdb\x7b\ -\x67\xce\xfc\xe6\x3b\x67\xe6\xde\xab\xb4\xd6\x04\x41\x10\x54\x42\ -\x3b\x68\x02\x08\x82\x00\x18\x08\x82\x00\x18\x08\x82\x20\x00\x06\ -\x82\x20\x00\x06\x82\x20\x00\x06\x82\x20\x08\x80\x81\x20\x08\x80\ -\x81\x20\x08\x80\x81\x20\x08\x02\x60\x20\x08\x02\x60\x84\xe9\xee\ -\x5d\xa2\xf7\xde\x23\x52\x8a\xe8\x85\x17\x56\xff\xbe\x7b\x17\xed\ -\x02\x75\x23\x85\x7b\x91\x0a\xe9\xcb\x2f\x89\x3e\xf8\xc0\x7e\xec\ -\x8b\x2f\x88\xae\x5e\x45\x1b\x99\xc1\xa8\x54\xb6\xcf\x42\x5c\x03\ -\x30\xb2\xf5\xd6\x5b\x44\x0f\x1e\xd8\x8f\xbd\xf9\x26\xd1\xfd\xfb\ -\x80\x49\x65\x21\xd6\x01\x18\x39\x7a\xfe\x79\xa2\x5f\x7f\xb5\x1f\ -\x7b\xee\x39\xa2\x5f\x7e\x01\x50\x0a\x80\xa0\xd6\xdf\x81\xc2\x84\ -\x1a\x4c\x29\x2d\x16\x69\xc7\x04\x01\x65\xf8\xe5\x1b\xe4\xe6\xd7\ -\x54\x97\x12\xfa\x79\xa1\xe7\x08\x01\x30\xed\xe9\xe6\xcd\xb4\x63\ -\x42\xc0\x12\x0a\x80\x5a\xa9\x51\x0c\x70\x20\x00\xa6\x7d\x5d\xbd\ -\x4a\x57\x88\xe8\xce\xe9\xb7\x8f\x89\x88\x0e\x0e\x88\x0e\x0f\xc5\ -\x15\x78\x7d\x2e\x60\x0e\xa0\x4c\x05\x0e\x5c\x4d\xc6\xd8\xe8\x2d\ -\x0f\x55\x4a\x69\xad\xb5\xaa\x35\xf0\x24\xe7\xfb\x63\x4e\x05\xd7\ -\xc3\xed\xfa\x5d\xd7\x0e\xc0\x04\xc3\x65\x10\x30\xaa\x66\xb0\x4a\ -\x6a\x67\xd7\x40\xc4\x35\xca\x83\xcb\x54\xc8\x9c\xeb\xd6\xba\x55\ -\x74\x32\x18\x74\xfc\xb4\xbe\x26\xf3\x9a\xd7\xdf\x63\x05\x2a\x4c\ -\x3b\x1d\x0d\x0e\x6d\xfb\x99\xed\xe7\x70\x2f\x61\x70\x69\xa9\xae\ -\x52\x12\x34\xae\x3a\x0d\xb7\x78\x07\x60\xea\x05\x8d\x6a\xb1\x33\ -\x5a\x05\x8b\x0d\x98\xbd\xcd\xe0\xb6\x6b\x6e\xb5\x10\xbc\x8e\xe7\ -\x16\xe2\xba\xdb\x55\xa4\x29\x90\x51\xca\xfe\x25\xc9\xbd\xf8\x56\ -\x85\x7a\x96\x6f\xd5\xa9\x35\xe7\xd2\x42\x09\xa0\x0b\xc0\xb8\x1a\ -\x5d\x6b\xad\x86\xdf\x87\x40\xc6\x17\x47\x52\x56\x35\x7b\x4d\x87\ -\xa4\xa7\x4d\x00\x4c\x03\x6e\x26\xa7\xad\x94\xb0\x54\x0b\xb0\xc4\ -\xa7\x4d\x3c\xdd\xcb\x6f\x89\xe8\x5f\xf9\xcf\xa9\x8f\xf5\xfd\xb0\ -\x86\x0f\xf9\xbd\xf1\xf8\xe1\xb9\xf7\x05\xe9\x10\xff\xf6\x4b\x05\ -\xcc\xd9\xb9\xef\x13\xd1\x23\xcb\x75\x00\x30\xd9\x1a\xdd\x74\x30\ -\xdb\xb5\x1a\x79\x80\x01\x5c\xf8\xb7\xe3\x74\xb8\x94\x39\x57\xa4\ -\x48\x9e\x74\x29\x3e\x65\xfa\x1f\x7b\xb8\x20\x25\x92\x93\x32\xb5\ -\x20\x00\x26\x2b\x64\xfe\xc9\x7a\xc6\x05\x58\xf2\x81\xa6\x26\x64\ -\x5a\x75\x2f\xe2\x01\x33\xa5\xe8\xe5\x5e\x61\xfa\x3d\x11\x39\x9e\ -\xf3\x42\x7f\x60\x33\x73\x01\x2e\xb2\x20\x33\xa5\xff\x4b\xf6\x3d\ -\x1c\x4c\x84\x9b\x59\x41\xe6\x3b\x22\x7a\x87\x88\x1e\x5a\x7e\xfb\ -\x3b\x16\xf6\x18\x70\x91\x03\x99\x94\x49\xb4\xe6\x4d\xb8\xa2\x8b\ -\xbc\x39\x37\x1d\x19\x69\xd2\x46\xbb\x8d\x05\x4e\x4b\x6d\x0c\xb8\ -\xc8\x6a\x73\x00\x46\x00\x5c\x1c\x90\x21\xad\xb5\xf2\x05\x4f\x6b\ -\x83\x19\x70\x91\xd5\xf6\xad\xc3\x05\x29\x52\x7c\x80\x44\x15\x7f\ -\x1b\xdb\x84\x05\xb8\x08\x4f\x97\x5a\x83\x0b\x00\x93\xd8\x4f\x66\ -\xec\xf8\x3a\xad\xb5\xc0\x02\x5c\x64\x40\x26\x61\x7f\xd7\x2c\xfd\ -\x0f\xc0\x24\x90\xdf\x5c\x61\x1a\x42\xc6\x15\x58\x66\xea\x54\x0b\ -\x34\x80\x0b\x9c\xcc\x9c\xda\x11\x0a\x84\x22\x77\x94\x2e\xb6\xdf\ -\x06\x10\x0c\x99\x16\x82\x0b\x70\x69\x13\x32\xad\xb9\x97\xe5\x72\ -\x09\xc0\xd4\x76\x2f\x6b\xb8\xac\x1b\x7f\xd0\x69\x1b\xcb\xd8\x45\ -\xeb\x32\x91\x40\x92\xfe\x4c\x60\x29\x90\x29\x39\xd1\xa4\xc2\x25\ -\x17\x64\x94\xd0\x87\x51\xe7\x5c\x9e\xb6\x39\x17\x22\x22\x3a\x3e\ -\x3e\x26\x97\x83\x89\x9d\x59\x46\x03\x60\xfd\xbb\x81\xfd\x85\xd4\ -\x88\x57\xea\x1d\xda\x47\x31\xb1\x3d\xc5\xb9\x1c\x1d\x1d\xc1\xc1\ -\xd4\x80\x4b\x48\xca\xb4\xae\xcb\xb8\xce\xa3\x76\xca\x04\xb8\xf0\ -\x4c\x95\xe6\xac\xc7\x94\x80\x0b\x52\xa4\x00\x0d\xdd\xcb\xd1\xd1\ -\xd1\x56\xe3\x0f\x8f\xa7\x3c\xc0\x2a\x28\xc8\x26\x04\x1e\xe0\xc2\ -\x0b\x32\x73\xb8\x97\x52\x70\x01\x60\x12\xdd\xcb\xf1\xf1\xf1\x46\ -\x7a\xb4\x5c\x2e\x37\x3a\x29\x05\x32\xce\x55\xa6\xe1\x79\x84\x59\ -\x68\xc0\x85\x31\x64\x72\xb8\x98\x56\xe0\x22\x0e\x30\xb9\x57\x8f\ -\x4c\xf7\x62\x76\x9e\x51\x83\x99\x04\x99\x51\x37\x03\x58\xf4\x3e\ -\xe1\x85\x3e\x34\xad\x19\xb8\xc0\xc1\x24\xb8\x17\x53\x66\xc7\xf8\ -\x20\x93\x94\x32\x45\xc0\x05\xee\x05\x2e\x66\xee\x9a\xcb\x56\x4c\ -\x0a\x7b\x1b\x5f\x16\x07\x63\xae\x1c\xd9\xdc\x8b\x6d\x10\x9b\x4b\ -\x7b\x83\xff\x2f\x6e\x85\xe9\xf4\xef\xa8\x88\x7c\x1d\x85\x5d\x79\ -\x13\xdb\xe0\xe5\x6f\x59\xdd\x4b\x2d\xb8\x88\x72\x30\x39\xe1\x92\ -\x2a\x97\x9b\x89\x5a\x61\xf2\x04\x49\xe8\xb9\x01\x2e\xfc\x5d\x4c\ -\xa9\xba\x4b\x4d\xb8\x20\x45\x72\xc8\xe6\x5e\x42\x83\xc1\x07\x99\ -\xd1\xba\x8c\xa5\xe6\x12\x02\x19\xa4\x46\x72\x53\xa5\xb0\x07\xd1\ -\xb7\x09\x17\x00\x26\xc2\xbd\xc4\xd6\x65\x86\x1d\x38\x5c\x65\x72\ -\x42\xc6\x53\xd0\x9d\xf3\x5e\x26\x48\x86\xe6\x80\x8b\x18\xc0\xe4\ -\x5c\x3d\x0a\x71\x2f\x39\x53\x26\xa5\x94\x0e\x5d\x8a\x1e\x7b\x75\ -\x29\xdc\x8b\x38\x17\x93\xc5\xbd\xcc\x05\x17\x38\x98\x44\xf7\x12\ -\x33\x90\x83\x20\x43\xa4\x15\x91\x0e\x59\x2d\x02\x44\xfa\xe1\x0c\ -\x77\xb8\x10\x09\x59\x45\xca\xe1\x60\x5c\x2b\x47\x53\x01\x33\xd6\ -\xd9\xd1\x2b\x4c\x99\xc0\x07\xf1\x89\x69\xdb\x38\xe5\x00\x17\x11\ -\x0e\x26\x17\x5c\x12\xec\xeb\xa4\x94\x69\x5d\x97\xd1\x91\x4f\xc9\ -\xcb\x75\x1d\x10\x1f\xb8\xa4\x38\x97\x56\x84\x14\xe9\x54\xa1\xee\ -\x65\xaa\xb6\x52\xa6\xc5\x22\xe9\x1e\x26\x40\x06\x8a\x75\xcb\x00\ -\xcc\x3c\xb3\x45\xf5\xbf\x79\xe4\xb8\xc5\x20\x06\x32\x96\xa7\xec\ -\x6d\x1c\x03\x68\x64\x39\xf2\x75\x7f\x72\x49\x8d\x9c\xb9\x5d\x8f\ -\xe9\x51\xe9\xda\x8b\xf1\x07\x87\x1f\x68\xdd\xfd\x3b\x65\xef\x03\ -\xea\x32\xb2\x00\xe3\x9a\x28\x38\xc0\xa5\x7b\x07\x53\x7d\x96\xb7\ -\x2c\x47\xdb\x56\x99\xc6\xee\x61\xf2\xcd\x62\x78\x37\xb2\x2c\xf7\ -\x12\xf2\x20\xf9\x56\xe1\x82\x14\x89\xc2\xf7\xbd\x4c\x76\x02\x9e\ -\xbd\x2e\x63\x90\x09\x49\x99\xcc\x73\x45\xca\x84\x9a\x0b\x00\x33\ -\x63\x7a\x34\x36\xe0\xb2\x0e\xc8\x80\x8d\x74\xb6\xdd\xbf\x8b\xc5\ -\xc2\x0b\x99\x31\xe8\xc1\xcd\xc8\x8b\x67\x5b\x9f\xb7\x0a\x97\xee\ -\x1d\x4c\xce\x5d\xbb\x53\xe0\xe2\x73\x33\x8b\xc5\x62\xd2\x0a\x13\ -\x20\x23\x3b\x8d\x6f\x19\x2e\xdd\x02\xc6\x57\xd8\xb5\x75\x6a\x72\ -\x7a\x94\xf8\xc0\xa8\x80\x94\x29\x1a\x32\x48\x99\xf8\xb9\x97\xb1\ -\x1b\x5b\x5b\x87\x0b\x5b\xc0\x94\x7a\xef\xd1\x9c\xce\x25\x24\x65\ -\x9a\x02\x19\xb8\x19\x59\xe2\x00\x17\x22\xa6\xcb\xd4\x53\x00\xd3\ -\xba\x7b\x19\x0b\x26\x22\xa2\xe3\xe3\xe3\xe4\xdb\x0b\xb2\x5e\x23\ -\x54\xc5\xbd\x0c\x1e\x3c\x55\x2f\xb5\x47\x8a\xd4\x74\xd4\x64\x83\ -\x8b\x2d\x88\xc6\x8a\xbf\x48\x99\xf8\xa7\xf0\x2e\xf8\x73\x82\x0b\ -\x4b\xc0\x94\x74\x2f\x63\x29\xc5\x1c\x70\x29\x05\x19\xa4\x4c\x6d\ -\xc6\xb2\x24\xb8\xc0\xc1\x8c\xa4\x0e\xad\xc0\x65\x90\x1a\x6d\xbc\ -\xc9\x60\xb1\x58\xa8\x21\x68\x00\x19\xb9\x1a\xa6\xc9\xe6\xdb\x2c\ -\x00\x98\x46\xe0\x51\x74\x06\xa8\xf8\x8a\x11\x9f\x9b\x49\x85\x0c\ -\x52\xa6\x76\xdd\x0b\x57\xb8\xc0\xc1\x78\x66\xee\xa8\xf4\x68\x86\ -\xf7\x17\xe5\x86\x0c\xdc\x4c\x3b\xf1\xe7\x82\x0b\x97\xb4\x68\xe3\ -\xba\x38\xad\x20\xa4\xd6\x5f\x42\xdc\x4b\x32\x60\x2a\xc2\x65\x7d\ -\x8e\xbe\xd7\xa5\x4c\x5d\x61\x9a\x0c\x5b\x28\x3a\x8e\x63\xe0\xe2\ -\x8a\x01\x38\x18\x46\x6a\x19\x2e\x63\x6e\xc6\x5a\xfc\x55\xea\x0a\ -\x29\xf5\x15\x29\xf5\xe4\xf4\xbf\x57\x62\x53\x26\xa8\xae\xc6\x9c\ -\x0b\x97\x3e\x11\xef\x60\x8a\xb9\x97\xca\x69\x51\xec\x73\x40\xd6\ -\x4e\xe6\x16\x11\x5d\xb3\x7f\xe4\x6d\xd2\xfa\x3a\xdc\x4c\x7b\xee\ -\xc5\x07\x17\x6e\x0f\x79\xdf\x91\x0c\x97\x82\x27\x53\xbd\xe6\x12\ -\x12\x54\xc3\xdd\xbf\x8b\xc5\x42\x69\xa2\x83\x6b\xee\x8f\xba\x36\ -\xe6\x64\x50\x97\xa9\x3f\x69\x8c\x39\x17\x6e\x80\xdf\x91\xde\x81\ -\xd9\xdd\x0b\x83\x17\xd2\x0f\xae\xf3\xc6\xc8\xaf\xde\x08\xf9\x3c\ -\x40\x66\x52\x10\x3e\x4b\x4f\xbf\x56\x4a\x0f\x88\xae\x62\xe1\xc2\ -\x51\xa8\xc1\x30\x71\x2e\x89\x90\x79\x7b\xe4\xd7\xde\x0e\xfd\x3c\ -\x2c\x65\x27\xc5\xcb\x2d\x22\xba\x43\x44\xef\x12\xd1\xf9\x77\x4f\ -\xbf\xb9\x95\x50\x73\x01\x60\x18\xd6\x5e\xb8\xdb\xcf\x00\xdd\x1b\ -\x3b\xae\xd4\x26\x37\xe1\x66\x32\x3a\x17\x47\xf9\xeb\x1a\x11\x69\ -\xa2\x2b\xab\xfb\x55\x35\xbd\xf6\xda\x47\x22\xe1\xd2\xbd\x83\x89\ -\x1a\x1c\x8c\xdc\xcb\x40\x9f\xfa\x0e\x1e\xac\x66\xd6\xad\xcb\x03\ -\x64\xb2\x28\x38\x3d\x7d\xf8\xf0\xaf\xf4\xc3\x0f\x7f\x12\x07\x17\ -\x22\x26\xab\x48\xb1\x0e\x26\xd4\xbd\x04\xd7\x5f\x1a\x80\x4b\xf2\ -\xfe\x87\x95\x4d\xdf\x9a\x49\x6f\x13\xd1\xd9\x12\x92\x56\xa9\x97\ -\x87\x55\x26\x67\xc3\x3c\x21\xa2\xf3\x9e\xdf\x78\xaa\x48\x5f\xd8\ -\x86\x77\xc1\x58\x80\x83\xc9\x93\x1e\x65\x1d\x18\x3c\x9d\xcb\xf0\ -\x9c\xaf\xaf\xcc\x0a\x7d\x4d\x44\x4f\x4f\xff\x7b\x70\x9d\x86\x6d\ -\x99\xfe\x1e\x26\xb8\x99\xf4\xf4\xb4\x87\x46\x68\x17\x30\xa7\xd5\ -\xf7\x27\x44\xf4\x15\x11\x5d\x89\x00\x47\xb6\x7b\x8e\xb8\xc3\xe5\ -\xec\xdc\x0f\x49\xeb\xcb\x8a\xf4\x05\x45\xfa\xb2\x22\x7d\x68\x3a\ -\x17\x40\xa6\x6e\x7a\x1a\x70\x1c\x80\x29\x08\x97\x67\xd5\xf7\xf3\ -\xb4\x2a\x14\xdc\x39\xfb\x79\x81\xf1\xa7\xe5\xc2\x65\xfc\xea\x37\ -\x20\x33\xe5\x3e\x26\xac\x32\x6d\x42\xfd\xb6\xfb\xe8\xed\x33\xc8\ -\x0b\xcf\x14\x9b\xcb\xe3\x56\xd5\xf7\x3b\x9e\xdf\x38\x20\x6d\xef\ -\x9c\x6c\xb5\x97\x06\xe1\x92\x23\xef\xf6\x8f\x77\x35\xf9\x1e\xa6\ -\xe8\xf4\x53\xfa\xe0\x3a\xdd\xf7\x72\x63\x35\x49\x3e\x3d\x4d\x8b\ -\x3e\xf5\xc1\x05\x35\x98\xf2\x4a\xde\x1c\x96\x25\x35\x12\xec\x5c\ -\x7c\x97\x73\xe9\xd2\xe7\x93\x1f\x60\x85\x94\x69\xbb\xfd\x0e\x89\ -\xe8\xf2\x2a\xaa\x2e\x2c\x17\x8b\xcb\xbf\xbb\xf4\xf9\x24\xb8\x20\ -\x45\x9a\xae\xa4\xcd\x61\xa1\x41\xec\x9d\x5d\x3b\x48\x8b\x5c\x97\ -\x75\x72\xf2\x61\x96\x07\x58\x21\x65\xf2\x4f\x7e\x2f\xbd\xf4\xe7\ -\x6e\xe0\xd2\x2a\x60\x92\xaa\xef\x93\xdd\x4b\x37\x35\x97\xd5\xe5\ -\x99\x5f\xc3\x76\xcb\x05\x99\x5e\xdd\x8c\xd1\x66\xca\x8c\x4d\x57\ -\xfb\x03\x30\x75\x54\xac\xfa\xee\x74\x2f\x1d\xc1\x65\x4c\xeb\x1b\ -\x26\x01\x99\xbc\xce\x65\x52\xda\x0e\xc0\x64\x9d\x5e\x0f\x69\xb5\ -\x0f\xcc\xa6\xdb\xb6\x02\xaf\xd4\xfb\x38\xe6\x06\x8d\x09\x19\xa4\ -\x4c\x71\xee\x65\xd8\x7e\xbd\xc6\x65\x93\xcb\xd4\x8a\xe8\xda\x7a\ -\x67\x18\x0d\x36\x87\x85\x3c\xbf\x24\x7a\x56\x65\xe6\x5e\x6a\x0e\ -\x4a\x13\x32\x70\x33\xe9\xed\xc8\xb1\xff\xb3\x9c\x6f\x8b\x4b\x5d\ -\x63\xbb\x77\x87\x6d\xbc\xbb\xfb\x0d\xbd\xf8\xe2\xdf\x68\x6f\xef\ -\x9e\xb7\x23\xad\xe9\x11\x23\xb8\xcc\xf9\xa0\xa1\xe5\x72\x99\xe5\ -\x51\x9c\x41\xe9\xaa\x30\xf7\x92\xdb\xb9\x70\x7b\xe0\x14\x3b\xc0\ -\xb8\x00\x7e\xe9\xd2\xdf\xe9\xe4\xe4\xc3\xf0\x80\xde\x8c\x6e\x2e\ -\x01\x3c\x6b\x60\x99\xa0\xc9\x71\xeb\x86\x14\xd0\x9c\x5e\xc7\x06\ -\x60\x4a\xa4\x45\xdc\x9e\xc9\xdb\x1c\x60\x52\xe0\x32\xc6\x0a\x09\ -\x70\x69\x25\xb8\x00\x99\xf9\xe0\x02\xc0\x30\x00\x0c\x57\xb8\xb4\ -\x14\x5c\x80\x8c\x1f\x30\x25\x1f\xe9\x0a\xc0\x34\x0c\x18\xce\x70\ -\x69\x31\xff\x36\x0b\xbe\x3d\x82\x66\x0e\xb8\x70\x02\xcc\x4e\x63\ -\x9d\x95\xbd\xa3\xa4\xc0\xa5\x45\x6d\x4f\x00\x4a\x67\xf8\x4c\x2f\ -\x70\x5a\x83\xcb\x70\x9f\x0b\xd4\x38\x60\x8a\x0d\x04\x21\x70\x69\ -\x71\xd6\xea\x15\x32\x6b\xb8\xe4\x4e\x15\xb9\xc6\x81\x08\xc0\x8c\ -\xb5\xeb\xf6\x4d\xd1\x0a\xce\xa5\x12\x64\x86\x83\x4b\x29\xa5\x87\ -\x9b\x1f\x53\x07\x51\xcb\x1b\xf3\xe0\x5c\x98\x01\x26\x34\x3d\x5a\ -\x2c\x96\xf4\xea\xab\x1f\xd1\xee\xee\x37\xa3\xf0\xd9\xb8\x21\x44\ -\x58\xc7\xb5\x38\xab\x0f\xfb\xed\xf8\xf8\x78\x32\x64\x5a\x75\x33\ -\xeb\xeb\xaa\xe9\x5e\xb8\x6e\x48\x64\x99\x22\xed\xed\xdd\xa3\xd7\ -\x5f\xff\xa3\xff\x66\xb1\x41\x87\x28\xc2\xb3\x62\x01\x99\xbc\x70\ -\x81\x02\xc1\xa8\xdb\x79\xa0\xd2\xe8\x6c\x10\x75\xcf\x91\x60\xc0\ -\x70\x58\xaa\x2c\xb1\xc2\x64\x83\x4b\xcd\x36\xb0\xbd\x9a\xb7\x86\ -\x7b\xe1\xd2\xe7\x62\x1c\x4c\x40\x6f\x88\x4d\x8d\x38\x3a\x99\x35\ -\x70\x38\xbb\x19\x03\x2e\xe8\x60\x4e\x80\xc9\xea\x5e\x2c\x70\x91\ -\x9a\x1e\xb5\x9e\x97\x9b\x7d\xc9\x35\x65\xb2\xc0\x65\x16\xf7\x02\ -\xc0\xc0\xb9\x40\x16\xc8\x94\xaa\xcb\xd4\x58\x65\x32\xe1\x82\x5a\ -\x5e\xe4\x90\x6c\x6d\x47\xa8\x6d\x46\x08\x72\x2f\x0e\xb8\xf4\xe0\ -\x5e\x34\x9f\x9b\x35\x8b\xdc\x6d\x5c\xaa\x2e\x63\x83\x4b\xa9\xf7\ -\x74\x49\xea\xe7\xa6\x1c\x4c\x0c\x5c\x9c\xb9\x2f\x9c\x0b\x1b\x37\ -\x33\x74\x32\x66\xff\xb6\x94\x32\xa1\xe6\xd2\x61\x8a\x14\xfb\xfe\ -\x22\xc9\x76\x96\xeb\xb5\x99\x90\x69\x31\x65\xb2\xc1\x65\x0e\xf7\ -\x22\xa1\xbf\x67\x4f\x91\x7c\x9d\x36\x9a\xff\x1a\x70\xe9\xed\x7d\ -\x3c\x9c\xed\xb3\xb9\x8c\xdd\x4a\xca\xd4\x12\x5c\xb8\xa7\x47\xb3\ -\x3b\x98\x98\x4e\xe3\xf0\x72\x34\x28\xcd\xc9\xb4\x92\x32\xb9\xe0\ -\x02\x09\x4c\x91\xbc\x39\xb0\x05\x2e\x3d\xbe\x4d\xd0\x4c\x09\x38\ -\x42\xa6\x64\x5d\x26\x26\x65\xf2\xc5\x1b\xdc\x8b\xf0\x1a\x4c\x6f\ -\x2f\x47\xeb\xd9\xcd\xe4\x84\x8c\xcf\xcd\x28\xb5\xf9\x25\xa9\xee\ -\xd1\x54\x2a\x3c\x57\x23\x26\xd7\x5e\x1c\x80\x91\x42\xfc\x9e\x67\ -\xbb\x92\xaf\xfc\xd8\x74\x2f\xbf\x21\xa2\xff\xd0\xd9\xdf\x1a\xc6\ -\xdb\xbf\x89\xe8\xbf\xcf\xda\x11\xee\x45\xb8\x83\x89\x85\x0b\x24\ -\xc7\xc9\x94\x4b\x99\x3e\x71\xc0\xe5\x88\x88\x3e\x81\x73\x91\xec\ -\x60\x9c\xee\xc5\x93\x1a\xf5\x58\x7f\x91\x3c\xeb\x95\x5c\x61\x5a\ -\x7d\xfe\xb7\x44\xf4\x8a\x05\x2e\x44\x44\xdf\x92\xd6\xaf\xc0\xbd\ -\x70\x76\x30\xa1\x1d\x07\xb8\xe4\x48\x07\x78\x3b\x19\xb3\x2e\x93\ -\xa7\x36\xe3\x82\xcb\xea\x18\xfa\x4d\x68\x8a\x64\xad\xe4\xa3\xa8\ -\x1b\x9f\x4a\x0a\x49\x97\x6c\xc5\xdf\x1c\x29\x93\x1b\x2e\xf1\x93\ -\x20\xfa\x93\x11\x60\xb6\x1a\x38\x12\x2e\xbd\xbb\x17\x2d\xb0\x2e\ -\x95\x1b\x32\x9b\x93\x58\x3b\xef\x8b\x96\xba\x48\x51\xbd\x06\x13\ -\x5c\x7b\xd9\x8c\xb2\x20\x4b\x89\xf4\x48\x70\xa0\x3a\xea\x32\x44\ -\xe1\xb5\x99\x70\xb8\xa0\xf6\x22\xda\xc1\xe0\x41\xdd\x70\x31\x3e\ -\x27\x93\xe2\x66\x42\x9f\x27\x34\x47\xb8\x49\xde\x62\xd1\x84\x83\ -\x71\xba\x17\xcf\xb9\xc9\x7c\x79\x7a\x9e\xa0\x97\xec\xec\x52\x9c\ -\x4c\xcc\xa3\x56\xe7\x7c\x1c\x83\x44\xc0\xec\xcc\x0d\x17\xa7\x7b\ -\x91\xd2\xd0\xe6\x96\x51\x07\x45\x7c\x66\x23\xd6\x88\x70\x7a\x79\ -\x59\x8a\x93\xf1\xd5\x65\x4c\x37\x13\xf5\x1c\xe7\x19\x9d\x8b\xd4\ -\x14\x7f\xf6\x14\xc9\xba\x72\x34\xd2\xd0\x6c\x3a\xc6\x35\xb0\x2b\ -\x0c\x78\xe9\xf5\x28\x17\x64\x86\x31\x15\x0b\x17\x3c\x8e\x41\x20\ -\x60\xac\xee\x45\x8a\x73\x99\x72\x3c\x73\xd0\x4a\xdc\x63\x11\x02\ -\x99\x16\x9d\x8b\xd9\x1f\x92\x27\x83\x6a\x35\x98\xe0\xda\x4b\xc0\ -\xf9\xb0\xe8\x9c\x90\x01\xbd\x01\x80\xe0\x5f\x85\x0d\xf7\xc4\x16\ -\x51\xda\x0a\x53\x4d\xf7\xd2\xd3\xea\x67\x33\x0e\x26\x05\x2e\x50\ -\xbc\xf5\x96\xee\x64\x86\x6e\xa6\x75\xe7\xd2\x43\x2a\x3b\x9b\x83\ -\x49\x7d\x5a\x3b\xfb\xfa\x4b\x65\x07\xd3\xb3\x93\x09\x75\x22\xb5\ -\xdc\x4b\x8f\xfb\xb6\x76\x00\x17\x38\x19\x49\x4e\x66\x08\x08\x13\ -\x38\x70\x2e\x1d\xa7\x48\x02\xa3\x3d\xea\xb8\xef\xd7\x73\xc5\x62\ -\x0f\x90\x31\x5d\xc8\x18\x64\x6a\xb8\x97\x9e\x27\xc6\xea\x80\xc9\ -\xf5\x22\x2b\x16\x9d\xe4\x3a\x47\xc7\xcf\xb5\xb6\x7f\xc1\xc9\x94\ -\x85\x0c\x9c\x0b\x63\xc0\x6c\xcc\x10\x13\x5e\x5b\xc4\x76\x20\x94\ -\x26\x06\x20\x13\x0c\x19\x13\x34\xa5\xdd\x0b\x52\xfa\xca\x0e\x66\ -\xb9\x58\x64\x71\x2f\x10\x20\x93\x02\x99\x9a\x6e\x06\x70\x99\x29\ -\x45\x42\x67\x01\x32\x2d\x40\xa6\xa4\x7b\x41\xbc\x9e\xe9\xdc\x1c\ -\x7f\x94\xab\x7b\xb1\x8d\x3f\xee\xb1\xa3\x8d\x17\xd6\xad\xff\x2d\ -\xf1\x01\x56\xa5\x1d\x8c\x0d\xd0\xbd\x4f\x86\x3b\x65\x07\xa4\xfd\ -\x29\xf1\x39\x67\xdd\x39\xe1\xe2\xfb\x39\x67\x27\xd3\x93\x9b\xc9\ -\xe5\x5e\x00\x97\x46\x52\xa4\x58\xf7\xd2\x42\xa0\x37\x70\x5b\x51\ -\x15\xc8\xf4\x96\x32\x95\x82\x8b\xad\x2d\x01\x98\xda\xa3\x12\x62\ -\xe1\x66\xa6\xbc\x44\x9e\x93\x93\x49\x05\x0b\xea\x2d\x73\x00\x46\ -\x29\x52\x83\x9b\xa3\xd7\xe9\xd1\x54\xf7\x82\xce\x9b\x37\x65\xc2\ -\x7d\x60\xfe\xb6\x40\x7c\xd6\x00\x0c\x82\x50\x0c\x64\x7a\xaa\xcd\ -\x4c\x49\xd9\x91\x12\xb9\x75\x2e\x73\xeb\x3b\x0f\x4d\x5d\x39\x42\ -\x07\xce\xeb\x66\x7a\x58\x69\x4a\x01\x0b\x54\xcb\xc1\x0c\x03\xd0\ -\x92\x1e\xe5\xe8\xd0\xf9\x06\xd9\xb4\xe3\x48\x9b\x90\x0e\x01\x30\ -\xb9\x46\xa1\xa5\xe1\x25\xec\xda\x8d\xbc\xad\xa8\xbb\xb4\x49\x1a\ -\x68\x7c\x60\x01\x5c\xe6\x4a\x91\xb4\x76\x64\x49\xfb\x93\xdc\x4b\ -\x2b\x1d\x8a\xb8\x72\xa7\x4d\xe6\xf7\x3c\x37\x52\x2a\xa4\xe8\x2d\ -\x03\x66\xd5\x3f\x9b\x9b\xeb\xd6\x2f\xb8\x52\x0a\x03\xb4\x17\xd0\ -\x0c\x7f\xc6\x61\x60\x02\x2c\x5c\x1c\x4c\x81\xce\x46\x07\xf3\x01\ -\xcd\x98\xab\x69\xa5\x3f\xc7\xd2\x39\xc4\x5c\xc6\xb6\xce\xd9\x98\ -\xa6\x83\x21\x32\xb7\x65\x03\x30\xdd\x04\x56\x60\x4d\xa6\x46\xff\ -\xb6\x74\x2e\xbd\x29\xf3\x3e\x98\xbc\xf7\x1e\xa1\xc3\x79\xbb\x9a\ -\xe1\x97\x6f\xf0\x9b\x5f\x53\x61\x12\xfa\x79\xa1\xe7\x08\x35\xe3\ -\x60\xdc\xee\x25\xc4\xc1\xc0\xbd\xc0\xe1\xd4\x4a\xe5\xa0\x3a\xca\ -\x5c\x83\xd1\x6a\xe5\x62\x34\xb6\xf3\x42\xd1\x83\x3d\x27\x74\x00\ -\x13\x91\x80\xb1\x3b\x97\x94\x19\x0d\x01\x02\xe8\x40\x00\xcc\x56\ -\x0a\xe4\x9a\x84\x9c\xb1\xf3\xe3\x8f\x44\x9f\x7d\x46\x9a\x88\x1e\ -\x5f\xbc\x48\xff\x78\xe3\x0d\xfa\x0b\x02\x0d\x82\x64\xa4\xc2\xb3\ -\xce\x1a\xdf\x7f\x4f\xf4\xf2\xcb\xf6\x63\x8f\x1e\x11\xed\xef\xa3\ -\x87\x20\x88\xb1\xe6\x7d\x26\xef\xc7\x1f\xa7\x1d\x83\x20\x08\x0e\ -\x66\x54\x17\x2f\x12\xfd\xfc\xb3\xfd\xd8\xee\x2e\xd1\x4f\x3f\xa1\ -\x87\x20\x08\x0e\x26\x51\xef\xbf\x9f\x76\x0c\x82\x20\x38\x98\x51\ -\xa1\x06\x03\x41\x70\x30\xc5\xb4\xbf\x4f\x0f\x0e\x0f\xe9\xfe\xc1\ -\x01\x11\x11\x3d\xde\xdb\x23\xba\x79\x93\xe8\xe4\x04\x70\x81\x20\ -\x38\x18\x08\x82\xa0\x56\x1d\x0c\x04\x41\x00\x0c\x04\x41\x10\x00\ -\x03\x41\x10\x00\x03\x41\x10\x00\x03\x41\x10\x04\xc0\x40\x10\xd4\ -\xa6\xfe\x0f\x2c\x19\xd6\x9f\x4d\xd0\xf4\xbb\x00\x00\x00\x00\x49\ -\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x00\xfa\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x12\x00\x00\x00\x12\x08\x06\x00\x00\x00\x56\xce\x8e\x57\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdd\x06\x13\ -\x0c\x32\x1c\x5f\xf3\x59\xda\x00\x00\x00\x7a\x49\x44\x41\x54\x38\ -\xcb\xc5\x93\x4b\x0e\xc0\x20\x08\x44\x87\xe9\xe7\xfe\x17\xae\x74\ -\x53\x93\x96\x88\x60\x6b\x52\x36\x2a\xea\x84\xe1\x29\x30\x29\xe4\ -\x1a\xf5\xab\x0e\x67\x55\xb4\x76\xaa\x8c\xe2\xe1\x82\xd1\x81\x8c\ -\x48\x4b\x28\xd3\x33\x6d\xdd\xe5\x20\x80\x9a\x5b\x32\xd6\x3c\xb1\ -\x3a\xdf\x01\x94\x4c\xb3\xef\x62\x6a\xc4\xe8\xd9\x9e\x86\x9f\x89\ -\xa6\xca\xcd\x6a\x01\xb0\x45\x38\xb5\xb3\xb6\x79\xda\x73\x0c\x2a\ -\xf1\x20\x1c\x76\xdf\x43\x2d\x83\x0f\x52\xf8\xf2\x8b\x48\x06\xbf\ -\xce\xa6\xf6\x4f\x9c\x58\x59\x1e\x24\x0c\x1b\x13\xc1\x00\x00\x00\ -\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x15\x12\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x01\x18\x00\x00\x00\xa0\x08\x06\x00\x00\x00\xd7\x60\x73\x20\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x01\x16\ -\x0e\x32\x2f\xe0\xcd\xb2\x2e\x00\x00\x00\x45\x69\x54\x58\x74\x43\ -\x6f\x6d\x6d\x65\x6e\x74\x00\x00\x00\x00\x00\x43\x52\x45\x41\x54\ -\x4f\x52\x3a\x20\x67\x64\x2d\x6a\x70\x65\x67\x20\x76\x31\x2e\x30\ -\x20\x28\x75\x73\x69\x6e\x67\x20\x49\x4a\x47\x20\x4a\x50\x45\x47\ -\x20\x76\x36\x32\x29\x2c\x20\x71\x75\x61\x6c\x69\x74\x79\x20\x3d\ -\x20\x39\x30\x0a\x67\x9d\x47\x40\x00\x00\x14\x41\x49\x44\x41\x54\ -\x78\xda\xed\x9d\xbf\xaf\x5c\x47\x15\xc7\xbf\xb3\x38\x31\xc2\x4e\ -\x44\x12\x24\x24\x9c\x82\xee\x59\xa0\x14\x28\xa9\x91\x68\xb0\x5d\ -\x20\x45\x8e\x14\x17\xb4\x74\xa1\x8f\xe4\x16\x11\x2a\x52\x85\xff\ -\x21\x01\xe5\xb5\x71\x6c\xd1\x80\x84\x90\x48\x1a\x44\x24\x9b\x34\ -\x20\x25\x34\xc4\x69\x12\x5b\xca\x0f\x34\x14\x6f\xd7\x6f\xdf\xdd\ -\xf9\x3d\xe7\xcc\x9c\x99\x7b\x8e\xb4\xf2\xf3\xdb\xb7\x7b\xef\xcc\ -\x9c\xf9\xdc\xef\x9c\x99\x39\x63\xac\xb5\x50\x53\x53\x53\xe3\xb0\ -\x8d\x56\x81\x9a\x9a\x9a\x02\x46\x4d\x4d\x4d\x01\xa3\xa6\xa6\xa6\ -\xa6\x80\x51\x53\x53\x53\xc0\xa8\xa9\xa9\x29\x60\xd4\xd4\xd4\xd4\ -\x14\x30\x6a\x6a\x6a\x0a\x18\x35\x35\x35\x05\x8c\x9a\x9a\x9a\x9a\ -\x02\x46\x4d\x4d\x4d\x01\x33\x99\xdd\xb9\x03\x5c\xbd\x0a\x18\x03\ -\x3c\xf9\xe4\xc9\xcf\x77\xee\x68\xbd\xa8\xad\xc6\x8c\xee\x45\x62\ -\xb2\xe3\x63\xe0\xa5\x97\xdc\xef\xbd\xfd\x36\x70\xfd\xfa\x3a\x1d\ -\xce\x18\x6b\xad\x35\x9e\xf7\xc8\xae\xa3\x7e\xad\x80\x99\xdb\x5e\ -\x78\x01\x78\xff\x7d\xf7\x7b\xcf\x3f\x0f\xbc\xf7\xde\x2a\xe1\xb2\ -\xff\xdf\xd6\xd7\x57\x5f\x57\xc0\xcc\x63\xe7\xcf\x03\x5f\x7e\xe9\ -\x7e\xef\xf1\xc7\x81\x2f\xbe\x58\x1b\x5c\x1e\xf5\xf3\x18\x64\x6a\ -\x7c\x32\x47\x05\xa9\xef\xf3\x9b\xc6\x60\xb8\xec\xb9\xe7\xca\xde\ -\x9b\x08\x28\xfb\x2f\x0f\x54\xac\xb5\x16\xcb\x57\xad\x4a\x49\xfd\ -\xbe\xc0\x3d\xaa\x29\x60\x84\xdb\xcd\x9b\x65\xef\x4d\x02\x96\x08\ -\x00\x8c\x67\xd8\xc4\x36\x34\xca\x01\x8e\x9a\x02\x46\xbe\x5d\xbf\ -\x8e\x9f\x02\x78\x77\xfb\xdf\xcf\x00\xe0\xca\x15\xe0\xf6\xed\xe9\ -\x02\xbc\x21\x15\xe0\xeb\xdc\xad\x21\x93\x03\x1c\x55\x35\x84\xbe\ -\xa1\xe3\xd0\x26\x71\x87\x29\xc7\xfb\x31\xa5\x92\xf8\x1d\x76\xf1\ -\x39\x33\x72\x79\xc6\x6e\x4f\x5f\xd9\x15\x30\xe2\x3b\xdf\x4c\xf5\ -\xec\xeb\x88\xa5\x65\x94\x04\x19\xae\x32\x8e\x0a\x97\x5a\xc8\xe8\ -\x10\x49\x2d\x7b\x28\xe4\x1b\x72\x54\x0c\x59\x4c\x08\x38\x3d\x2c\ -\x36\x7c\x52\x53\x05\x23\x5f\xbd\x2c\x1d\x55\x70\x5b\xf8\xc0\xc2\ -\x70\x1d\x2b\x49\xc9\xb4\x2e\xff\x6c\x0a\x26\x1b\x30\xa1\x95\x98\ -\x6a\x19\x80\xe1\x18\xf0\x4e\xd2\xb1\xa4\x42\x66\x4a\xd0\x2c\xca\ -\x63\x60\xfb\x01\x46\x72\xc3\x4b\xa0\xbd\xb5\x15\xea\x45\x18\x60\ -\x7a\xc7\x20\x46\x83\xcc\x90\xa0\xf1\x94\xc3\x05\x99\x26\x31\x98\ -\x9e\x53\x8b\x23\x48\xc9\x59\x86\xe6\x1c\x71\x96\x82\x18\x88\x58\ -\x5f\x0b\xc5\x67\xd4\x2a\x00\xa3\x90\xc9\x77\x44\x9f\xdd\x18\x68\ -\x78\xd7\xeb\xc9\x2c\xdd\xd7\x5c\x75\xa3\x90\xa9\x04\xcc\x2a\x21\ -\x63\xcc\xa3\x97\xc5\xc9\xab\xd6\x7e\xbf\x07\x99\x0f\xb7\x3f\x9b\ -\xce\x60\x91\x38\xb5\x3e\x82\xaf\xb9\x20\x33\x02\x68\x6e\x24\x97\ -\xaf\xc2\xaf\x2a\x37\x96\xcd\x1f\x93\x09\x38\x8a\x3b\x20\x16\x5f\ -\x5c\xe7\x5a\x80\xd7\x73\x51\xde\x08\x81\xcb\x11\x7c\x6d\xb4\x00\ -\xb0\x31\x06\x2f\x03\x78\x8b\x8a\x26\xd4\x80\x59\x05\x64\xb2\x00\ -\xf3\x35\x80\xc7\xa2\x8e\xe5\x83\x49\x0f\xc8\x8c\xb4\x20\x70\x14\ -\x5f\x73\xd6\xa9\xcb\x8f\x3a\xd6\x75\x2b\x18\x56\x2f\xb4\xd3\x98\ -\xcc\xbe\x7d\x90\xd5\xb0\xcb\x06\xf5\xc1\xa6\xa4\x03\xce\x06\x17\ -\x09\xbe\xb6\xdc\x7d\xed\x7b\x25\x3f\xa4\x3a\x0d\xa3\x5a\x2a\xad\ -\xcd\x0c\x0d\x2f\xc7\x7e\x16\x6c\xc4\x14\x85\x52\x0a\x99\x5d\xbd\ -\xa7\xd6\xff\xa8\x5b\x19\x5a\xf8\x5a\x32\x38\x2a\xed\x18\xed\x83\ -\xc2\xad\x87\x71\xa4\x2b\x79\xa7\x1c\x2e\x65\x0d\x91\x4c\x32\x30\ -\x4a\x86\x50\x39\xca\x25\x54\xff\x33\xec\x93\xa2\xde\xbf\xc4\x95\ -\xa8\xca\x18\xdf\xd2\x35\xe0\x21\x80\x0b\x8d\x63\x36\xad\xdb\x9e\ -\x74\x2f\xd2\x94\x4a\xc6\x5a\x5c\xde\x3e\x6d\x1e\x6e\xff\xbd\xbc\ -\x45\x89\xb5\xbb\x61\xb4\x81\x6f\x0e\x68\xd7\xa0\x39\xd0\xc8\x51\ -\x31\x3e\xa8\x1b\x63\xac\xab\x0d\x66\xd9\x84\x49\xb1\x7f\x29\xa6\ -\x4e\x5c\xc9\xab\x72\xeb\x2b\xf4\xf7\xb7\x0a\xee\xa9\x15\x44\x45\ -\x2a\x98\x19\x95\x4c\x6e\xa3\xa4\xc0\x21\x23\x95\x41\xf0\x33\x21\ -\xb8\xb8\x3a\xe3\x8c\x3b\xbc\x73\x95\x4c\xac\x3d\x59\xea\x24\x74\ -\x4d\xc7\x2c\x22\xc7\x7d\xf5\x9a\xe1\x62\xdb\xec\x38\x0b\x64\x7c\ -\x0d\x93\x0a\x8f\xda\x4e\x1d\x98\x71\x0a\xd6\xaf\xe3\x89\x6e\x66\ -\x83\x4b\x8e\xbf\x75\xcf\xf5\x92\x31\x8b\x44\x7d\xaf\x3d\xa7\xcf\ -\x59\x77\x53\x8f\x0e\x99\x18\x44\x52\xe1\xc1\x01\x99\xd4\xba\x75\ -\x65\xf2\x9f\x37\xa7\xc9\x61\x9d\x48\xdb\x37\x94\xeb\x0b\x14\xf7\ -\xdf\x53\xb9\x9a\x35\xef\x8c\xad\x25\x7f\x4a\xc3\x51\x3d\x8d\x16\ -\xdf\x93\x5c\xa7\xdb\xcf\xad\x66\x93\x6a\xec\x68\x14\x09\x70\x2d\ -\xe9\xf0\xa5\xa0\xe9\xbd\xf8\x8f\x3d\xe1\xd4\xa8\x81\xdf\xc2\x75\ -\x28\x41\xe5\x51\xb3\x6f\x65\xef\xb3\x25\xb0\x88\x06\x7f\x67\xb1\ -\x45\x9d\xd8\x50\xfd\x77\xbc\xc7\xa2\xcf\xe4\x3e\xc0\x24\xac\x2c\ -\xde\xb4\x6e\xf4\x91\x9d\x3b\x25\x80\x9b\x1b\xac\xcb\x04\x59\xd6\ -\xc1\x65\x0b\xc0\x89\xcb\x1a\xc7\xf1\x50\xd8\x96\xf9\xe0\x68\x94\ -\x11\x7d\x29\x15\x34\xae\x59\x27\x29\xdb\x16\x9a\x66\xb4\x1b\x75\ -\xa9\x77\xea\xf0\x28\x57\xc6\xe6\x48\x65\x9f\xf4\xcf\xfd\x6e\x89\ -\xf9\x6f\x19\xdb\x6c\x98\xec\x78\x81\x07\x49\xe8\x21\xe1\xf5\x35\ -\x29\x33\x86\xa6\xc3\xc6\xba\x29\x36\xad\xa5\xec\x27\x4a\x04\x47\ -\x4a\x1c\x27\x18\xbc\x2c\x99\xb9\x9a\x7d\x96\x4f\x52\x39\x0b\x86\ -\xdb\x49\x9d\x32\x16\xc8\x5e\x25\x60\xa4\x3b\x77\xa9\x7a\x49\x81\ -\x4f\x09\x64\xce\xd4\x15\x60\x90\xb0\x32\x38\x63\xc5\xf0\x74\xb3\ -\x7c\x52\x60\x9a\xd3\xe9\x73\xd6\x3b\x85\x2e\x29\x0d\x2e\xdd\x00\ -\x23\xd9\xb9\x73\xd5\x4b\xad\x82\x89\x5c\xdb\x0b\x17\x1f\x4c\x6a\ -\xb6\x19\x8c\x04\x99\x82\xe9\x5e\xf6\x72\xd6\x2c\x96\xcb\x69\xb7\ -\x04\xe0\x18\x09\x70\xe9\x0a\x18\x89\xce\x4d\xad\x5e\x2a\x21\x13\ -\x85\x0b\xe1\x35\x87\x8a\xcb\x94\xc6\x17\xb8\xfc\x8d\xe8\x00\xba\ -\x22\xc5\x11\x81\x4d\xf7\x75\x4f\xdd\x8f\x2d\x91\x04\x19\x4a\xf5\ -\x52\x93\x48\x6a\x19\xd0\xad\x1d\x66\xcd\x04\x19\x69\xe5\xa4\x9c\ -\xad\xa9\xc9\x07\xb4\x5c\xef\x24\x05\x32\xdd\x0f\x5e\x93\x32\x85\ -\x9d\x32\x66\x2e\x71\xee\x25\x80\x4a\xd7\xd7\x70\x6f\x54\x1b\x61\ -\x1a\x9b\x62\x66\x84\xaa\x9c\xbe\x14\xa3\x54\x1d\x39\x33\x55\xc7\ -\x3e\x4c\x8c\x63\x98\x6d\x7b\xa5\xf0\x14\x71\xb2\xa3\xd4\x75\x32\ -\xa5\x2b\x6d\x43\x89\xa4\x62\xce\x13\x52\x74\x2d\x20\x23\xb5\x2d\ -\x28\xa7\x5d\x6b\x21\xc3\xb5\xc6\x84\x70\x9f\x91\xcb\x51\xec\x34\ -\xbb\xa9\x47\x1b\x2e\x95\x6c\x5c\xac\xcd\xf3\xe2\xf9\x7e\xdf\xee\ -\x68\xd6\xb8\xcf\x68\x43\xd7\x5e\x39\x8c\x1a\x9e\x6e\x99\xfc\xfd\ -\x39\x3e\xd5\xa3\x3d\x45\x9d\x4d\x2d\xe9\xe9\xc9\x71\x1c\xc5\x52\ -\x42\xef\x7f\x67\xc8\xd1\x5d\x9f\x2b\x09\x48\xd7\xb4\x45\xaf\xf6\ -\xe0\x5e\x30\x96\xea\x73\x12\x13\x7a\x27\x4e\x85\xa7\xec\xb4\x5f\ -\x87\x82\xe9\xf1\xf4\xac\x4d\xbb\x50\x11\xfd\x5f\x8e\x93\x8b\x9f\ -\xa2\x35\xaa\x2a\xa7\x2d\x7a\x2b\xcb\x5e\x3b\xff\x7b\xac\x8a\x4d\ -\x58\x1b\x93\x0d\xbc\x1e\x6d\xb9\x81\x40\xeb\xad\x64\xa8\x73\xba\ -\x44\xae\x91\x05\xd3\xd4\x6b\x73\x28\x99\x96\xed\xd1\xba\x53\xfb\ -\x7c\x6e\x94\x24\x5d\x89\x13\x0e\xcd\xdb\x52\x24\x60\x5a\x49\xf4\ -\xda\x61\x0f\x81\x4a\xc8\xda\xbc\xe8\x1b\x32\xb5\x80\x4c\x4f\xe8\ -\xb7\xea\xd4\x0e\x9f\xeb\x06\x97\x50\xdb\xd5\xdc\x57\x6b\xc8\x88\ -\x05\x4c\x2f\xe2\xe6\xe4\xcb\xa5\x92\xe4\x3b\xb8\x50\x02\x8f\x1a\ -\x32\x2d\x95\x65\xcf\x43\xe8\x42\xe9\x1e\x84\x84\x0f\xaa\xfd\xb1\ -\x65\xbf\x12\x0d\x18\xce\xca\x48\xed\x6c\x29\x69\x19\x6a\x16\x7b\ -\xd9\x45\x1c\x6c\xed\x90\x91\x70\xe4\xea\x12\x32\x1d\xcf\xe6\xe6\ -\xac\x9b\x26\x15\x2d\x1e\x30\x2d\x25\x3a\xf7\xb2\x6e\x17\x5c\x76\ -\x5b\x00\x72\x21\x10\x4a\x66\x15\x3b\x67\x9a\x1a\x32\xbd\xdb\x83\ -\x09\x72\xa2\xd6\x03\x51\xcf\x62\xd9\xd3\xe3\x30\x58\xcb\x38\x04\ -\x60\xa8\x9f\x9e\xa5\xea\x85\xdc\xe1\x89\x4f\x76\xac\xdd\x94\xd9\ -\x5b\x59\xf6\x1c\x1a\x79\xea\x45\x6c\xa2\x34\x8e\x85\x7d\x1c\x65\ -\x1c\x0a\x30\x9c\x12\xbd\xb9\x7a\xf1\xcc\x18\xe5\x66\xcd\xcb\x5d\ -\x35\x2c\x15\x32\x12\x86\x46\xae\xb6\x90\x36\xa3\xc9\x00\x5f\xd6\ -\x8a\x1f\x0e\x30\xb5\x90\x29\x75\x64\x82\x73\x69\x92\xa7\xa3\x29\ -\x94\x4c\x68\xc8\x44\x0d\x19\xea\x4e\x28\x41\xbd\xec\xdf\x43\xcf\ -\x45\x87\x0d\xb6\x87\xb0\x2a\xb5\x21\x01\x43\xad\x64\x52\x17\x32\ -\x51\x6c\xa9\x4f\x5d\xdc\x44\x71\x80\x5b\x4d\x16\xbe\x9a\xf6\x78\ -\xcd\x18\x8b\xd3\xd7\xa7\x30\xe6\xea\x60\x43\xa3\x26\x43\x42\x29\ -\xf0\xe5\x1c\x2a\x0d\x0b\x98\x12\xc8\xd4\x74\xa4\x96\x70\xa1\x84\ -\x00\xf3\x4c\xc4\x41\x7b\xfc\x1a\xc0\xcd\xb3\xbf\x7e\x0a\xc0\x3b\ -\x31\xc8\x8c\x10\xdf\x68\x0d\x99\xc6\xc3\x46\x9e\x24\x5c\xb3\x1d\ -\x1f\x9a\x73\xe8\x7b\xaa\x7a\x29\xc9\x14\x57\x0b\x97\x14\x47\xab\ -\x39\xc0\xad\xa6\x5c\x91\x8b\x7c\x05\xe0\x9c\xe3\x9d\xff\xc0\xda\ -\x4b\xd2\xd5\x4b\xef\xe4\x55\x29\x70\x61\x3c\x8d\x15\x20\x4e\x94\ -\xbe\xc1\x04\x96\xa2\x64\x5a\xab\x17\x4a\x07\xa4\x72\x28\x97\x9a\ -\xa1\x1e\x2a\x79\xe0\x02\x00\xdf\xc3\x44\xc6\x19\xfc\x95\x18\xf0\ -\x5e\x35\x60\x4a\x1b\x3c\x55\xbd\x54\x29\x2a\x22\xe9\x19\x9b\x2d\ -\x92\x02\x99\x4f\x3d\xbf\xff\x58\x68\xec\xa5\xa6\x4e\x5b\xcd\x30\ -\xb5\x3a\x34\x6e\x19\xf0\x55\xc0\x24\x36\x78\x4b\xf5\x12\x3a\x05\ -\x80\xb2\x03\x54\xee\x2d\xca\xda\xeb\x92\x63\x3f\xf7\xfc\xfe\x17\ -\x18\x23\x4b\x5e\x6f\x25\x23\x69\x73\x25\x45\x79\xa6\x02\x4c\x4e\ -\x83\x17\x3c\xad\xba\xc2\x85\x1a\x04\xb1\x32\x15\xa6\xf6\xb4\xb7\ -\x00\x5c\xdb\xc5\x5c\x4e\xff\xbd\x76\x2b\xd0\x26\xa3\xc7\x01\xb9\ -\x16\x80\xa6\x1e\xaa\xd7\x53\xc5\x45\xcb\x34\x43\x90\x37\x81\xbe\ -\x49\x87\xa0\xd7\x06\xd6\x7a\x05\xfe\x08\xd6\xe8\x54\x3b\x5c\x4a\ -\xd9\x1d\x9d\xaf\x7f\xd6\xfb\x6d\xd9\x29\xee\xa1\xa6\xfd\x6b\x0e\ -\xfb\x63\xaa\x13\x4b\xe1\xc7\x1b\x4c\x6a\xbe\x5d\xb1\x4d\xd4\x0b\ -\x73\x22\x9f\xd8\x10\x87\x72\x8c\x9f\xb8\x2f\x2a\xa9\xec\x8e\xf7\ -\xba\x3e\xdd\xa8\x95\x00\xf7\xda\xac\xde\x87\x24\x2a\x60\x1c\xf5\ -\x93\xea\xcc\x95\x71\x9a\x2e\xf9\x6b\x19\x16\xcb\x65\xd7\x4d\x6e\ -\xd9\xa5\x41\x46\xc2\x70\x49\x5a\x52\x2b\xca\xeb\xcf\x0e\x98\x03\ -\xc8\x50\xab\x97\xde\xc9\xb1\x7b\x42\xa6\xa2\xec\x67\x8e\xd7\x90\ -\x96\x7f\xb9\x25\x64\x04\x67\xcc\x33\x0a\x98\xf4\x86\x63\x71\x66\ -\x09\xb9\x83\xb9\x20\x93\x1b\xa3\xaa\x75\x62\x89\x33\x4c\xdc\x90\ -\xa1\x9a\xdd\x94\x6c\x6b\x50\x30\x51\x67\x2e\x09\x9e\x4a\x3b\xf6\ -\x96\x61\xc1\x5c\x10\x32\x14\xe5\x77\xed\x56\xee\x95\xf3\xb7\x05\ -\x64\x62\x65\x93\x34\xe1\xb2\xf0\x27\xab\x80\x49\x03\x46\xf2\x13\ -\x53\x2a\x5c\x42\xf7\xd5\x10\x32\xd4\xb3\x25\xe2\x4f\x95\xa4\x80\ -\xcc\x7e\xd9\x4a\x87\x46\xa3\xcd\xfa\xae\x46\xc1\xd8\xd3\xcc\x71\ -\xc1\xc4\xce\x92\x86\x45\x42\x95\x0c\x4b\xf9\x7b\x41\xa6\xd1\x0a\ -\xd9\x60\xd9\x04\x43\x43\xf7\x22\x95\x48\xdf\xd0\x14\x76\xa8\xc1\ -\xa5\xc3\xa5\x01\x64\xec\xd9\xea\x30\xa4\x1d\x5b\xf2\xd1\xb5\x14\ -\x90\x89\xf9\x9d\x64\x3f\x52\xc0\x14\x38\x73\x4e\x63\x73\xec\x2f\ -\x1a\x09\x32\xbe\x85\x8b\x1c\xb1\x8c\x59\x21\xe3\x50\x05\x76\xd6\ -\x85\xae\x53\x02\xa6\xc0\xd9\x0f\x20\x93\xd4\xe0\x83\x38\x05\x87\ -\x92\x71\xd5\x91\x42\x26\xdb\x3f\x67\x06\xe8\xba\x14\x4c\x02\x30\ -\xa2\x4a\x66\x94\xa1\x51\x0a\x64\x4a\xf7\x18\x2d\xcb\xef\x82\x0c\ -\xf7\xca\xd8\x91\x3b\xa3\x23\xa8\xbb\x0a\xc8\x4c\x03\x98\x82\x60\ -\x2d\x3c\x92\xdf\xce\x02\x17\x0a\xc5\x11\x2a\x7f\xee\x5a\x99\xc2\ -\xfc\xc2\xab\x98\x61\x92\x5e\xae\xd2\x87\xc7\xd4\x0a\xa6\xe4\x48\ -\xcd\x23\x00\xc7\xdb\x7c\xb2\x9f\x1a\x63\xaf\x0e\x0e\x97\x1a\xc8\ -\xe4\x9e\x82\xc0\x35\x64\x1a\x1d\x32\xa1\x29\xe9\xd9\x95\xcc\x14\ -\x80\x21\x9b\x29\x01\x7e\x73\x17\xc0\xf5\xed\xff\x9f\x06\xf0\xce\ -\xc9\xef\xaf\xcd\x02\xdc\xd4\xb8\x4c\xe9\x29\x08\x29\x43\xa6\xd2\ -\xe3\x4e\x47\xec\x8c\x29\xeb\x5d\x66\x86\xcc\x2c\x39\x79\xb3\x9c\ -\xd8\xdb\xe8\x99\xf9\x64\x7b\x94\x91\xf0\x6c\xec\x18\x20\xb2\x87\ -\x86\x31\xa0\x50\x95\x81\x62\xd8\x4a\x5d\x9f\xa5\x70\x29\x29\x57\ -\x8b\x7b\x77\xdc\x53\x11\x2b\x36\x33\xc2\xa5\xc2\x56\x91\x4f\x76\ -\xe9\x9c\x8e\x44\x56\x45\x1d\xb8\x55\x82\xa4\x99\x82\xbf\xb3\x2b\ -\x99\x29\x63\x30\x45\xea\x05\xfe\x7c\xb2\xc7\x98\xd3\x5c\x90\xa9\ -\x55\x07\x3d\x20\x53\xd3\x21\xb9\xf6\x24\x55\x6c\x05\x10\x03\x99\ -\xd5\xa7\xcc\xa4\x74\x0e\x63\x8c\xf5\xe5\x93\xbd\x89\x79\xa7\x12\ -\x7d\x9b\xda\x6a\x82\xda\xad\x86\xdd\x52\x83\xbf\xb5\x29\x18\x66\ -\x52\x68\x6b\xda\x4d\x1d\x8a\xbd\x00\x00\x7c\xf9\x64\xef\x4d\x26\ -\x5b\x3d\x40\x20\x9d\x8e\xa7\xce\xf7\x1b\xea\x8c\x54\x27\x4a\x48\ -\x80\x8b\x50\x78\x96\x9f\xe9\x35\x6a\x90\xb7\x30\xc5\x82\xf3\x6f\ -\x73\xf3\xc9\xf6\x98\xb2\xe6\x0c\xec\xb9\xb6\x01\x70\x04\x93\xb9\ -\x3b\x7a\x6e\x1b\x71\xd4\x29\x75\xde\x5c\xc7\xc6\x48\xd3\x2a\xc8\ -\xbb\x1f\xe0\x2d\xbd\xde\xaa\x14\x4c\x29\x5c\x24\x8d\x8d\x5b\x25\ -\x1a\xe2\x4c\xf7\xc0\xa9\x66\x4a\xda\x88\xea\x1e\xb8\xb6\x4c\xf4\ -\xf0\x3d\xaa\xeb\x0c\x09\x18\x0a\xf5\x52\xf2\xc4\x9b\x74\x7f\x8c\ -\x5d\x38\x33\x3b\xcc\x38\xf7\x32\x49\x49\x60\xc5\x99\x86\x13\x03\ -\xe5\x31\x5e\x85\x82\xa1\xcc\x27\xdb\x0b\x32\xdc\x43\xa3\xfd\x72\ -\xb5\xc8\xf3\xeb\x5a\xf4\x47\xa8\x9a\x92\xe2\x17\x5c\xc3\x40\xae\ -\xa1\x4b\x0f\xc8\xac\xee\xd8\x12\xa2\xd5\xa1\xb5\x53\xb1\xc3\x2b\ -\x99\x18\x60\x39\x21\x13\x8a\x53\xb4\x86\x4c\xeb\x21\x21\x45\xd3\ -\x71\xfb\x1e\xe5\xf7\x4e\xaf\x60\x1c\x8e\x43\x35\x15\x3b\x2c\x64\ -\x32\xe2\x4e\x6c\xc3\xa5\x56\x90\xe1\x6e\xa7\x4e\xc9\xb7\x87\xf1\ -\xbd\xcd\xe0\xb0\xc8\x7d\x6a\x50\x4f\xc5\x76\x69\xe8\x96\x67\x38\ -\xb5\x84\x4c\x8b\x21\x93\x27\x5d\xe5\x39\x00\x8f\x49\x1d\x1a\x79\ -\xea\x8b\xdd\xf7\xf6\x67\xac\x54\xc1\xc4\x1d\x80\x3d\x97\xec\x4a\ -\x16\xe3\xb1\xc2\xb2\xc5\x90\x69\x1b\xfc\x7d\x6e\xef\xed\x6f\x00\ -\x78\xda\x18\x73\x4e\x2a\x5c\x5a\xf9\x1e\xb5\x0f\x6f\x06\x85\x45\ -\xb4\x61\x5b\xc0\xa5\x35\x64\x7a\x6e\x12\xa4\x80\xcc\x22\x4d\x41\ -\x57\xc8\x00\xf8\xbb\x31\xe6\x27\xdb\x9f\x1f\x03\xf0\x3f\x14\x2e\ -\x28\x6b\x0d\x97\x1e\xe9\x1e\x4a\xcb\x38\xcc\x42\x3b\xc9\x80\xa1\ -\xe8\xc0\xb9\xf5\x90\xdb\x6e\x54\xf7\x56\xbb\x90\x6c\x79\xff\x09\ -\xbb\xba\xc9\x3b\xb3\x67\xf1\xda\xe3\x00\xbe\x06\xf0\x8c\xb5\xf6\ -\xbf\x12\xd5\x4b\xa8\xed\x89\x76\x98\x1f\x7c\x47\xed\xa2\xbe\x8d\ -\xc2\x65\x3c\x25\x93\xfb\x54\xa7\x04\x5f\xab\xd9\x25\x4e\x35\xe3\ -\x09\xfe\x7e\x7f\xdb\x1f\x3e\x37\xc6\x7c\x53\x2a\x5c\xb8\x7c\x8f\ -\xcb\x5f\x67\x9f\x45\x6a\xbe\xbc\x5f\x5a\x4c\x86\x43\x55\xcd\x00\ -\x19\x47\x7d\xdc\x03\xf0\x3c\x80\x2f\x25\xc7\x5d\x32\xe3\x4c\x96\ -\xa0\x5e\xe6\x06\x4c\x85\x7a\x71\x1e\xb3\x31\x3a\x64\xa4\x38\x75\ -\x2d\x64\x42\x9d\xd5\x07\x19\x8e\x59\xa6\x45\x67\xfa\x2b\x80\x1f\ -\x01\xf8\x2a\x16\xf0\xed\xd5\x0e\xb1\xeb\x96\x6c\x92\xf4\x3d\x84\ -\x28\xea\x77\xd6\x63\x4b\xba\xc1\x45\x92\x92\xe1\x8e\x09\xb5\x56\ -\x32\x9c\x43\xa6\xbd\xff\xfe\x0d\xc0\x1f\x00\x3c\x65\x8c\x39\x4f\ -\xad\x9a\x5a\xfb\x5e\xcc\xff\xd8\x87\xf2\x92\x83\xbc\x85\xea\x85\ -\x6d\x67\xb0\x94\x4e\x9e\x12\x68\x6d\xb9\xfb\x3b\x37\xf0\x1b\x0b\ -\x1c\x26\x96\x8f\x5c\x51\x38\x3a\xdb\x8f\xad\xb5\x7f\xee\x3d\x34\ -\xaa\x09\xac\xc7\xfc\x20\xf4\x3e\xc5\xae\xed\xd9\x8e\x2d\xe9\xae\ -\x5c\x24\x28\x99\xd6\xa9\x25\x4a\x95\x4c\xca\x03\x24\xf4\x37\xd4\ -\x43\x26\x47\x5d\xfd\xc9\x18\xf3\x03\xc9\x71\x97\x1a\xff\x4b\x81\ -\xcb\xb4\x43\xa4\x82\x55\xbb\x4e\xb8\x48\x70\x08\x6a\xc8\x70\xe4\ -\xd3\x95\x36\x5c\x2a\x8d\x49\x50\x40\x66\x51\x6f\x1f\x20\xe7\xd4\ -\x4f\x41\xea\xc5\xe5\x07\xbf\xdb\x1e\xc9\x73\xdf\x18\xfb\x46\x83\ -\xfb\x9f\xe5\xd8\x12\x71\xca\xa5\x87\x92\xe9\x9d\x14\x2b\x15\x32\ -\x29\x1d\x25\x07\x58\x0d\xe2\x32\xcb\x07\xd8\x70\x66\xad\x35\x6f\ -\x00\x78\x65\xfb\xff\x67\xb6\x3f\xbf\x81\xf4\xfd\x68\xd3\xc4\x60\ -\x72\xd4\x4b\x08\x2e\x42\xcb\x46\x06\x01\xc7\xa2\x35\x11\xa7\x50\ -\xe6\xc6\x50\x52\xf3\xf8\xe4\xc4\x77\x6a\x7d\xc0\x33\x61\xd0\xbc\ -\x5e\x49\xb3\xd7\x19\xf3\xc9\x96\x2d\xfb\x76\x1f\xd6\x7e\x87\x5a\ -\x35\xcd\xa1\x60\xcc\x78\x87\x2d\x32\xed\x1f\x11\x75\xc4\x2d\xe5\ -\x70\x29\xf7\xbb\x18\xd4\x4c\xb7\xdc\xb8\xd4\x43\xcd\xfb\x87\x70\ -\x59\xdf\x10\x29\x59\xbd\x18\x03\xd3\x61\x21\x9d\x50\xc8\x88\xab\ -\x87\xdc\xc5\x73\x92\x20\xe3\xd8\xe5\x7d\x00\x99\x91\x36\xb6\xee\ -\xee\xf7\x4d\xf7\xdb\x6f\x72\x0d\x8f\x44\x0e\x91\x92\x00\xb3\x80\ -\x8b\x2b\xee\x32\x42\xb4\x9f\x6b\xff\x88\xd4\xf6\x0c\x75\xfc\xdc\ -\xe1\x4f\x6a\xfb\x16\x9c\xae\x18\x82\x9b\x4d\x88\xd5\x34\x1b\x66\ -\xe6\xfa\x07\xb6\x31\x97\x57\x1e\x09\x1a\xbc\x09\x6b\x7f\xc9\x35\ -\x3c\x12\x07\x98\x52\xb8\x58\x6b\xc5\x2f\xe3\xe6\x00\xc4\x32\xfe\ -\x24\xb5\xcc\x81\xd3\x1c\xaa\x80\x51\x03\x19\xb7\xab\xa5\xf9\x50\ -\x0b\xd0\x10\x6c\x2a\x2d\xba\x47\x6a\xc0\x8c\x97\x32\x33\x61\xad\ -\xcb\x48\x6b\x15\x28\x87\x4b\x52\x57\x9b\xfa\x94\x4b\x49\xac\xa6\ -\xf6\x33\x27\x9f\xfb\x76\xf6\x67\x62\x1d\x95\x72\xc8\x34\x0b\x5c\ -\x44\x01\x26\x71\xd1\x95\x73\x95\xae\x19\x30\xd8\x5b\x0b\x99\x11\ -\xa6\xe6\x29\xe3\x22\x14\x90\x39\xfd\xdc\xaf\x82\xea\x25\x71\x1a\ -\xdd\x48\x8b\xcd\xb8\xae\xef\xba\xcf\x96\x76\x6e\x94\x4e\xc8\x79\ -\x38\x98\x14\xc8\xec\xca\x68\x8c\xb1\x21\xa7\x08\xe5\xed\x30\xc6\ -\x88\xad\x97\xe5\xd0\x68\x77\xaf\x25\xf7\x5e\xf7\xb9\x7f\x07\x86\ -\x46\xff\x2a\x6e\x37\xa2\x61\x6f\x16\xe8\x42\x40\xcb\xb9\x36\x87\ -\x7a\x11\x33\x6e\x8f\xa9\x17\xdf\xd3\x7a\xe4\xd8\x4b\x69\x4c\x26\ -\x75\xe7\xab\xf4\x7a\x20\x3a\x1d\xa2\x30\x26\xe3\x83\x8b\xdd\x7e\ -\x0f\xc9\x43\x30\xbb\xc3\xa7\xb6\x61\xed\x75\x14\x30\x67\x01\x12\ -\xdc\x02\x30\x1b\x60\x0e\x20\x02\x18\x24\x2e\xa4\xe3\x72\x92\xd9\ -\x20\xc3\x05\x98\xdc\x98\x4c\x68\xff\x4f\x6c\xf6\x8a\x02\x2a\x2d\ -\xfc\x66\x33\x0a\x5c\x72\x33\xe0\x0f\x4c\x97\x13\xa8\x00\x38\x02\ -\x70\x0c\x58\x18\xf3\xe0\xd8\x18\x7b\x14\xa9\x8b\x5e\xfb\x81\xa8\ -\xe2\x32\xa5\xf7\x5e\x5e\x6e\xbe\x73\xcc\x76\xb1\x8f\xd8\x50\x77\ -\xff\xb5\xbd\x09\x0b\x60\xf9\xfb\xe2\x6b\x94\xf6\xbf\x69\x14\x8c\ -\x17\x30\xf0\xcf\x18\x89\x53\x2f\xae\x32\x94\xeb\xec\xdd\x4f\x47\ -\x00\xee\x2e\xdf\xbe\x0c\xe0\x6e\xd8\x69\x87\x03\x6e\xed\x42\xbc\ -\xdc\xb2\x9f\xfc\x99\x1f\x2e\xdc\xd5\x26\x21\xd3\x5c\x2b\x7f\x39\ -\x37\x2a\x5c\x24\x29\x0e\xef\xef\xeb\xee\xf5\x35\xd7\x2f\xef\x02\ -\xc7\xb1\xa7\xf9\x08\x01\x5f\xdf\x3d\xef\xfb\x06\xdf\xbd\xf7\x83\ -\x4b\xce\xd0\xa8\x65\xff\xe3\xba\xae\xe9\x9c\x8c\xc9\xdb\x06\xfb\ -\x8d\xe1\xaa\x08\xd1\xea\xa5\xd0\x5b\x8d\x01\xec\xa9\xf3\x3f\x00\ -\xf0\x2d\xc7\x9f\x3d\x84\xb5\x17\x72\xea\x75\x4d\x4a\x86\x72\xa3\ -\x65\x8f\x32\xf7\x4a\x22\xce\x75\xdd\xcd\x88\x70\x99\xd1\x76\xc5\ -\x34\xa7\xc5\xbf\xe5\xfa\xbb\x63\x37\x74\x82\xce\x32\xca\x3a\x21\ -\x8a\xf6\x8d\xc5\x63\x14\x2e\x3c\xf5\x2e\x4e\xc1\x78\x9c\xde\xbb\ -\xbe\x43\xa4\x7a\x21\x54\x30\xcb\xaf\x39\x82\xb1\x77\x1d\x7f\x77\ -\x19\x27\xe9\xef\x7b\xad\xcc\x6c\xe9\x1b\x3f\x04\xf0\x47\x00\xdf\ -\x2d\xa8\xd3\x98\xdf\x48\x9b\x3d\x9d\x6d\x68\xd4\x55\xc1\xd4\xc0\ -\x65\x1d\x66\xec\xbd\x2d\x4c\x4e\x44\x0b\x1e\x6e\xff\xbd\x7c\xef\ -\xb4\x0e\x53\xa6\x2e\xe9\x54\x8c\x31\x87\x2f\xc6\x27\xea\x11\x80\ -\x7f\x2c\xe1\x12\x03\xba\xa3\xec\x2f\x6f\x3f\xf3\xa1\x31\x27\x3f\ -\x2b\x5c\x1a\x7b\x72\xe7\x34\x80\x3e\xb8\x84\x1a\x41\x1c\x78\x08\ -\x66\x91\x16\x6b\x33\xf6\x3e\x6c\xcd\xf2\xeb\x72\xa6\xef\xc9\xd4\ -\x1e\xe5\x4c\x59\x2b\x65\xe8\xf8\x8e\x1b\x00\xde\xd2\xb8\x4b\xb3\ -\xeb\x9a\x9e\x19\xd2\x6b\xe1\x32\x93\xb2\x39\x2d\xd6\x21\x5c\x5c\ -\x7d\xaa\x39\x64\x26\x01\x0c\xfb\x7d\x2b\x5c\xfa\x0f\x91\x4a\xe1\ -\xb2\x86\xa1\x91\x0b\x2e\x9e\x21\x40\xf2\x06\xc9\x51\x83\xbe\x2e\ -\x3b\x1e\xfa\x21\xb2\x2e\xb8\x34\x57\x30\xa9\x67\x46\xaf\x4d\xbd\ -\x1c\x42\xc2\x9a\xd4\x87\x6e\x33\x25\x23\x44\xc1\xec\x05\xb9\x87\ -\x52\x30\x6b\x84\x8b\x04\x05\x13\xac\x90\xb5\xa8\x97\x52\xb8\xb0\ -\x2b\x99\x65\x40\xd7\xda\xb3\x2f\x56\x6d\x6b\xbd\x70\x49\x56\x61\ -\xae\x7b\x54\xb8\x34\xd6\xe4\x7d\x0a\x9b\xad\x5e\x66\x86\x4f\xeb\ -\xd4\x99\x49\x8e\x47\xfd\xf4\x5f\x7e\x5f\x7d\xb6\xff\x21\xfc\x60\ -\xcd\x70\xe9\xa5\x60\xbc\x89\x92\x52\xe1\x32\xab\x7a\xa9\xd9\x6b\ -\x22\x3a\x26\xe3\xfa\xfe\xd0\x35\x03\x53\xe2\x23\xf9\xc5\xda\xe1\ -\xd2\x0c\x30\xbe\x43\xe9\x45\x4f\x3d\x0f\x04\x97\x21\x20\x53\x19\ -\x7b\x19\x0d\x32\x0a\x97\xf6\x0a\xa6\x38\xc5\xe3\xf0\xc1\x5d\xc7\ -\x13\x99\xeb\x34\x80\xb5\xcc\x2e\xb9\xee\x5d\xca\xfd\x77\x59\x6f\ -\xe2\x28\xbf\x84\x7e\xb2\x69\x54\xd9\xc1\x74\x97\x6b\x53\x2f\xdc\ -\xe7\x39\xd5\x42\xc6\xf8\x76\x82\x0b\x6b\x1b\xcf\x49\x9f\x5d\xc1\ -\xd2\x0b\x2e\x29\x75\x33\x1f\x60\x3c\x70\xa1\x76\x2a\x85\x0b\x2d\ -\x64\x1e\x39\x2d\xd5\xac\x11\x23\xac\xce\x26\xf3\xee\xa7\x66\x7a\ -\x74\x72\x1f\xd0\x24\xf5\x91\x4d\xab\xce\x04\x4f\x4e\x97\x98\x7a\ -\x99\x35\xb8\xdb\x62\x58\x66\xf7\xea\x3c\x05\x32\xac\x43\x26\xe6\ -\x29\xee\x9e\xbe\xd3\xa3\x93\x4b\x56\x2d\x67\xee\x93\x2d\xd1\x4c\ -\xc2\xa1\xf4\xb9\xb9\x3b\x86\x55\x30\xbb\x04\x50\xad\x8e\x78\x5d\ -\x26\x6f\xca\xbc\x6e\x77\xe7\xad\x5c\xd4\xd7\xea\xfe\x7b\xa9\x96\ -\x91\x54\xbd\x61\xaa\xf8\x28\x5c\x4a\xd4\xcb\xc8\xc3\xa3\xa6\x47\ -\xbc\xba\xf2\xa0\x10\x40\x66\xa4\x36\xe0\xbc\xff\x1e\x75\x33\x6a\ -\x7b\x6c\xb8\x9d\xfb\x28\xa1\xc2\x66\x0f\xec\x4a\x38\x3f\x3a\x67\ -\xb8\x14\x92\xf9\x23\x25\xaf\xf2\xdd\x7f\x4d\x19\x7c\x0a\x82\x71\ -\x24\xd0\xfc\x9a\x72\x15\xcc\xe2\xdc\xe8\x33\x4e\x8d\xfc\x99\xa3\ -\x19\x20\xd4\x05\x2e\x81\x0e\x54\x3a\x4c\x1b\x4d\x9a\xe7\x80\x31\ -\xf1\x80\xb3\x66\xe5\xaf\xbd\xd7\x39\x01\x13\x80\xcb\x12\x30\x6b\ -\x89\xbd\x74\x53\x2e\x91\x54\x07\xb1\x93\x23\x67\x1d\x36\xa5\x28\ -\xb0\xd4\x40\x37\x75\x99\x73\xef\x6b\x14\x23\x3b\x55\x20\x04\x97\ -\x35\x0c\x83\x84\x8d\x0f\x82\x81\xd2\x52\xd8\xf9\x52\x96\xba\x0e\ -\xb3\x97\x3c\x74\x0a\x75\xea\x96\x1d\x7d\x56\xa8\x90\x2b\x98\x83\ -\x93\x08\x7d\x4e\xaf\xea\x65\x2a\x9b\x4a\xca\x67\xc4\x65\x6a\x4e\ -\x3a\xa0\xfe\xee\xd5\x0c\x91\x8c\x31\xd6\x06\x16\xd2\x99\xdd\x69\ -\x47\x09\x15\x39\x3a\x60\xd6\x00\x97\x99\x40\xd3\x3b\x70\x3d\xb3\ -\xba\xdf\x10\x56\x92\x31\x81\xe3\x37\x2d\x0c\xfe\x09\x3c\x4a\xbc\ -\x3c\x2b\x5c\xf6\xa1\xb2\x06\xb8\xec\xda\x28\x36\x6b\x23\x6d\xaf\ -\x50\xe8\x9e\xf6\xcb\x13\x2a\x5b\x4d\x3d\x51\x7e\xef\x2a\x14\xcc\ -\x49\xc3\xb9\xc1\x12\x1a\x32\xcd\x08\x18\xb5\x74\x55\xd0\x6b\xaf\ -\xce\xda\x94\x44\x2f\x3b\x27\xf9\xe6\xb4\xc1\xc7\x56\x35\x29\x9d\ -\x9c\x7a\xfa\x77\xcd\xf1\x0e\x05\x8c\xf0\xb1\xb0\x5a\x7f\xe0\x70\ -\xfa\x81\x02\x45\x15\x8c\xda\x4a\x81\x43\x0d\x16\x85\x89\x10\xd1\ -\xc0\x1d\x83\x01\x1c\x71\x98\xc9\xf7\x1d\xa9\xa9\xa9\x31\x28\x18\ -\xdf\xfa\x2e\x03\xeb\xdf\x0c\x7b\xff\x3e\xf0\xfa\xeb\xb0\x00\x3e\ -\xbb\x78\x11\x7f\x39\x3a\xc2\x6f\x15\x2e\x6a\x6a\xaa\x60\xaa\xed\ -\xe3\x8f\x81\x67\x9f\x75\xbf\xf7\xd1\x47\xc0\xa5\x4b\xda\x42\x6a\ -\x6a\x03\x5b\xdf\x73\x91\x5e\x7d\xb5\xec\x3d\x35\x35\x35\x55\x30\ -\x51\xbb\x78\x11\x78\xf0\xc0\xfd\xde\x85\x0b\xc0\xe7\x9f\x6b\x0b\ -\xa9\xa9\xa9\x82\x29\xb4\x17\x5f\x2c\x7b\x4f\x4d\x4d\x4d\x15\x4c\ -\xd4\x34\x06\xa3\xa6\xa6\x0a\x86\xcd\x2e\x5d\xc2\xfb\xb7\x6f\xe3\ -\xbd\x2b\x57\x00\x00\x9f\x3d\xf1\x04\x70\xf3\x26\xf0\xc9\x27\x0a\ -\x17\x35\x35\x55\x30\x6a\x6a\x6a\x6a\x52\x15\x8c\x9a\x9a\x9a\x02\ -\x46\x4d\x4d\x4d\x4d\x01\xa3\xa6\xa6\xa6\x80\x51\x53\x53\x53\xc0\ -\xa8\xa9\xa9\xa9\x29\x60\xd4\xd4\xd4\x64\xda\xff\x01\x44\x08\x3a\ -\x88\x31\xa7\xcd\x9c\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\ -\x82\ -\x00\x00\x01\x0b\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x12\x00\x00\x00\x12\x08\x06\x00\x00\x00\x56\xce\x8e\x57\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdd\x06\x13\ -\x0d\x07\x0f\x78\xbe\xb0\x85\x00\x00\x00\x8b\x49\x44\x41\x54\x38\ -\xcb\xbd\x93\xbb\x0a\x80\x30\x0c\x45\x4f\x2b\xbe\x3a\x88\x88\x83\ -\xa3\xa3\xff\xff\x65\x8e\x82\xa0\x2e\x41\x1c\x6a\x4d\x04\xbd\x50\ -\x12\xc8\xcd\x6d\x1e\x2d\xa4\xe1\x00\x0f\x04\xf1\xbf\x47\x26\xb6\ -\x05\x2a\x60\x31\xe4\x96\x52\xed\x86\x38\x00\x33\x90\x03\x9d\x52\ -\x64\x92\x22\xd6\x3b\xc2\x2e\x27\x85\x2b\xc7\x6b\x89\xa9\x98\x6a\ -\xf8\x31\x31\x4d\xb5\x8f\x62\x8f\x22\xee\x42\xb4\xc0\x49\xce\x69\ -\xfd\x1f\x6f\xcc\xd4\xda\xdb\x61\xbf\xde\x98\x79\x73\xd6\x07\x19\ -\x45\x00\x46\x65\xeb\x03\x50\xc7\x02\x3d\x50\x18\xe7\xd8\xc8\x47\ -\xff\x1f\x95\xe6\xe6\x03\x00\xf2\x27\xca\xc2\x2b\x23\x90\x00\x00\ -\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x05\xec\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x01\x18\x00\x00\x00\xa0\x08\x06\x00\x00\x00\xd7\x60\x73\x20\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x01\x16\ -\x0f\x17\x2c\x90\xf5\x59\x44\x00\x00\x00\x45\x69\x54\x58\x74\x43\ -\x6f\x6d\x6d\x65\x6e\x74\x00\x00\x00\x00\x00\x43\x52\x45\x41\x54\ -\x4f\x52\x3a\x20\x67\x64\x2d\x6a\x70\x65\x67\x20\x76\x31\x2e\x30\ -\x20\x28\x75\x73\x69\x6e\x67\x20\x49\x4a\x47\x20\x4a\x50\x45\x47\ -\x20\x76\x36\x32\x29\x2c\x20\x71\x75\x61\x6c\x69\x74\x79\x20\x3d\ -\x20\x39\x30\x0a\x67\x9d\x47\x40\x00\x00\x05\x1b\x49\x44\x41\x54\ -\x78\xda\xed\xdd\xcb\x6d\x1d\x47\x10\x05\xd0\xe9\x81\x72\x61\x8c\ -\x8c\x41\xb1\x70\xaf\x28\x14\x0a\xb5\xe2\xba\xb5\xe1\x46\x80\x1e\ -\x7f\x8f\x3d\x5d\x9f\x73\x00\xc1\x80\x05\x58\x9c\xaa\xee\x3b\x55\ -\x8f\xb6\x79\x1c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc0\xf5\ -\x86\x12\xc0\xb7\x99\xee\xa2\x80\x81\x08\x81\xd2\xe2\x6e\x0a\x18\ -\xf8\xfe\x50\x19\x1b\xff\x79\x02\x06\x8a\x85\xca\x48\xfa\x67\x09\ -\x18\x08\x1a\x2c\xa3\xc1\x9f\x2f\x60\x0a\xed\xe2\x7a\x11\xbf\x9f\ -\xc3\xd7\xe4\x50\x47\x0f\x12\x3d\xca\xd7\xe3\xe1\xeb\x73\x78\x23\ -\x04\xca\x08\xfa\x67\x50\xec\xe2\x66\xf9\x7a\x1d\xd6\xaf\x5d\xf8\ -\xd1\xe8\xeb\x10\x2e\xbe\x6e\x01\xb3\xf8\x32\x0f\x5f\x63\x9b\xde\ -\x0f\xcf\x20\x60\xba\x5f\x58\x61\x23\x5c\x52\x3c\xcb\x70\xa8\xca\ -\xbd\xbd\x04\xcd\xd7\xea\x37\x0a\x9e\xed\xed\xcf\x34\x1a\x1e\xa4\ -\xca\x35\x98\xfa\x2c\x30\x23\xf5\x7e\x34\x2d\x7a\xf5\x67\x9f\x8d\ -\x7b\x4d\xa0\x90\x19\xcd\x0a\x6d\x6a\x13\x34\x42\x46\xc0\x58\x15\ -\xd4\x82\x0a\x21\x33\x9a\x5c\x28\x97\x49\x5d\xf4\x7d\x43\xcf\x87\ -\x0b\x24\x68\xd4\xa9\x55\xcf\x05\x8c\x4b\xa3\x5e\xd4\x08\x19\xdf\ -\xfb\x77\xe8\xd4\x4e\xaf\x97\x39\x15\xad\xb5\xf1\x81\xc9\x86\x9a\ -\xbd\x36\xc1\x18\xf1\xd5\x93\xdc\xab\xd2\xe9\x32\x70\xa3\x7e\xa6\ -\x19\xda\x4d\x30\x56\x22\x35\x26\xd1\x14\x73\x3a\xf8\xbc\x53\x57\ -\x93\x0c\xe5\x03\x46\xb8\x08\x19\x12\x6e\x2e\x19\x02\x46\xb8\x08\ -\x19\xae\xbb\x5f\x6d\x57\x24\xe1\xa2\xde\x58\x91\x96\xa5\xab\xc3\ -\xbe\x3f\x64\x4c\x31\x5e\x1e\x65\x02\xc6\x61\xd6\x17\x92\xf7\xf4\ -\x4c\xf2\xc0\xa6\x97\x58\x6f\x3b\x21\x43\xda\x80\x11\x2e\x42\x86\ -\x22\x6b\xd2\xd9\xbd\x00\xe8\x07\x7d\x02\xc6\x87\xba\xb9\x42\xc6\ -\x14\x53\xc7\x92\x5e\x9e\xd5\x1f\x10\x7d\xc3\x04\x63\x14\xb7\x2a\ -\x55\x4e\xe0\x87\x79\x1c\x4f\xf3\x38\x5e\x5e\xff\xfa\xa0\x97\xd7\ -\xbf\x05\xa7\xb7\xa1\xbe\x15\x0d\x97\xf9\x9f\x5f\x0f\x01\xfb\xe8\ -\xa0\xa2\x6f\xc9\x0a\xf4\x74\x23\x60\x9e\x04\x8c\x43\x8a\xfe\xdd\ -\x5b\x9c\x97\x1b\x01\xf3\xd2\x21\x60\xce\x00\x0f\x46\xad\xb0\xe1\ -\x5f\xbf\x3e\xf9\xf7\xf1\xf6\x43\x1f\x3f\x5c\x18\x9f\xc1\x38\x94\ -\xe8\xe7\xf2\x90\x89\xfa\x5d\xa4\xa5\x01\x33\x36\x3f\x54\x84\xaf\ -\x03\xfd\xd4\xbb\x45\x7d\x3b\x1d\x46\x16\xbc\xac\x4c\x31\x6c\x0d\ -\x18\x40\xc0\x5c\xfa\xd6\xa3\xd6\x14\x03\x5b\x02\xc6\xf8\xdc\x6b\ -\xaf\xa7\x71\x9f\xac\x48\x40\xc9\x80\x31\x4e\x5b\x93\x28\xde\xb7\ -\xab\x03\xc6\xd8\x6c\xfc\xc6\x04\x03\x78\x01\xe4\x0d\x18\x63\xb4\ -\x71\x1b\x13\x8c\x71\x19\x7d\xf7\x42\xb0\x22\x01\x01\x83\x7f\x47\ -\xc0\x18\x9f\xbd\x15\xb1\x22\x01\x5e\x04\x39\x02\xc6\x1e\x6e\x1c\ -\xa7\x61\x3f\x4c\x30\x40\x99\x80\xb1\x97\x1b\xbf\x89\x33\xbd\xb4\ -\xff\xd1\xb1\x80\x09\x06\x30\xbd\x08\x18\x10\x2e\xc5\x26\x18\xdf\ -\x41\xc0\x39\x88\xe5\xb2\xcf\xc6\x4c\x30\x6b\x6f\xd4\xe3\x3c\x8e\ -\xe7\xd7\x1f\x53\xf1\x3c\x8f\xe3\x51\x55\xe8\xb0\x1a\x5d\xfd\x70\ -\xb3\x61\x47\x7f\xdf\xf8\x79\x38\xbf\x9b\x1e\x70\x13\xcc\xde\xda\ -\x97\xed\x41\xbb\xc3\xf5\x3a\xb9\xcc\x37\x7e\x3d\x36\x3d\xe4\x08\ -\x17\x87\xeb\x1b\x1e\xf8\xf9\x9d\x80\x79\x16\x30\x74\x08\x97\x71\ -\xd1\x83\x96\xdc\xfd\xde\x78\xe0\xf9\x81\xc2\x8f\x66\x87\xbd\xd5\ -\x19\x08\x52\xef\x63\x77\xdd\x7d\xc8\xbb\xc6\x9f\x3b\x7f\x1f\xd2\ -\x87\x8b\x80\x59\xe7\xe7\x9d\xbf\x0f\xe9\xc3\xc5\xfe\xbd\xf6\xa1\ -\x7d\x17\xa9\xf9\x19\xd8\x54\xe3\x8e\x9f\x79\xf6\x3c\x5c\xfe\x3d\ -\x18\x67\xa0\x7b\xb8\xf8\x90\x97\xc3\x19\x28\xb3\x12\x85\xab\xb1\ -\xcf\x60\x40\xb8\x2c\xf3\x43\x9f\x40\xb0\x08\x18\x10\x2c\x47\xa6\ -\x70\x11\x30\x60\x6a\x59\xca\x0f\x5e\x43\xdf\x73\x19\x47\xa2\x0f\ -\xcb\x7d\xc8\x0b\x39\x42\x25\x65\x60\x9f\x17\x17\x07\x97\x84\x46\ -\x4c\x30\x60\x8a\x11\x30\x80\x09\x06\x30\xc5\x6c\x0b\x18\xdf\x51\ -\xe8\x45\xbf\x4d\x30\x00\xb9\x03\xc6\x77\x10\x8c\xf6\x34\x5c\x93\ -\x4c\x30\x40\xa9\x80\xb1\x97\xf7\xa0\xcf\xa6\x18\x13\x0c\x50\x23\ -\x60\xec\xe1\xde\xb6\x08\x18\xe3\x33\xfa\x6b\x4d\xb2\x22\x01\x26\ -\x18\xe3\xb2\xb7\x2c\x5d\xa7\x98\x9d\x13\x8c\x31\xda\x7a\x84\x09\ -\x06\x30\xc5\xe4\x09\x18\x63\xb3\xf5\x08\x13\x8c\x71\x1a\xfd\xc4\ -\x8a\x04\xa6\xc5\xa0\x21\x7f\x2a\x08\x0b\xa6\x17\xeb\x11\x26\x18\ -\x30\xc5\xd4\x0d\x18\x53\x8c\xe9\x05\x13\xcc\x96\x43\x4a\xce\x70\ -\x81\x50\x01\xe3\x6d\x57\x77\x4c\xc7\x56\x10\x62\x82\xb1\x2a\x59\ -\x8d\x30\xc1\x00\xa6\x98\x9c\x01\x63\x8a\x31\xbd\x60\x82\xd9\x72\ -\x78\xd1\x1f\x92\xbe\xb0\xcf\xa0\x45\x21\xe7\x61\x86\xd0\x13\x8c\ -\x55\xc9\x6a\x84\x80\x31\x8a\x0b\x17\xac\x49\x39\x03\x66\x38\xd4\ -\x29\xc2\xc5\xf4\x42\xea\xfd\xd9\x81\xd6\x0b\x92\xaf\xb4\x91\x57\ -\x24\x87\x58\x5f\x70\x58\x6a\x27\x30\xea\xaf\x87\x35\x27\x98\x8f\ -\x8c\xea\xa8\x37\x81\x65\x08\x18\x1f\xfa\xc6\x08\x17\xd3\x0b\xa5\ -\xf7\x69\x07\x5e\xad\xb7\x8d\x6d\xa3\xd6\x63\x5d\xf6\x38\x99\x56\ -\x24\x93\x8c\x70\xd9\xb6\x13\x3a\x6c\xf5\x27\x98\xe2\x2f\x18\x75\ -\x8d\x1c\x30\xa6\x98\xfa\x13\xcc\x5b\x85\xf1\x82\x11\x2e\x04\x94\ -\xf5\xff\x07\x33\xac\x4c\x4b\x57\x22\xe1\x42\xeb\x80\xb9\xf5\x96\ -\x15\x32\xf7\x87\x0b\x75\x5d\xfe\xdf\x27\x8d\xa2\x17\xc5\x65\x51\ -\x2f\xc5\x79\xff\xf1\x96\x3f\xd6\x70\x2e\xdc\x1f\x75\x6a\x7d\x06\ -\x96\xf6\x7e\xb8\x40\x82\x05\x01\x23\x60\xee\xbf\x4c\x5d\x2f\x94\ -\x5a\xb0\x2d\x64\x46\xc3\xcb\x35\x9a\x1e\x22\xc1\x82\x80\x71\xd1\ -\x3c\x2f\x02\xc6\xaa\xe0\x19\x11\x32\x02\xe6\x13\x97\x30\x6b\x3d\ -\xa6\xfe\x22\x60\x72\x5d\xcc\xe8\xb5\x99\xfa\x4a\x86\x90\x71\x10\ -\xf3\x5c\x58\xa1\x82\x80\x69\x10\x36\x57\xd5\x6d\xea\x1f\x02\x46\ -\xd8\xdc\x5b\xcf\xa9\x67\x54\x0d\x19\x87\x75\x6d\x18\xe8\x11\x02\ -\x86\x10\xc1\xa3\x17\x94\x0b\x99\x1f\xea\x2a\xa8\xc1\xc5\x00\xd2\ -\x4d\x31\xa7\x9a\x02\x00\x57\x4f\x31\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x40\x4a\x7f\x01\xe5\x23\xdb\x3f\x56\x5c\x46\x67\x00\ -\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x06\x0e\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x01\x18\x00\x00\x00\xa0\x08\x06\x00\x00\x00\xd7\x60\x73\x20\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x01\x18\ -\x0c\x3a\x27\xc5\x94\x13\x49\x00\x00\x00\x45\x69\x54\x58\x74\x43\ -\x6f\x6d\x6d\x65\x6e\x74\x00\x00\x00\x00\x00\x43\x52\x45\x41\x54\ -\x4f\x52\x3a\x20\x67\x64\x2d\x6a\x70\x65\x67\x20\x76\x31\x2e\x30\ -\x20\x28\x75\x73\x69\x6e\x67\x20\x49\x4a\x47\x20\x4a\x50\x45\x47\ -\x20\x76\x36\x32\x29\x2c\x20\x71\x75\x61\x6c\x69\x74\x79\x20\x3d\ -\x20\x39\x30\x0a\x67\x9d\x47\x40\x00\x00\x05\x3d\x49\x44\x41\x54\ -\x78\xda\xed\xdd\x5d\x4e\x1b\x31\x14\x86\x61\x1b\x95\x5e\x76\x19\ -\xed\x8e\xd8\x16\x5d\x01\x3b\x60\x43\x61\x15\x91\xda\x4a\xbd\x72\ -\x2f\xca\x45\x08\x81\xf1\x24\x63\x8f\x7f\x9e\x57\xca\x45\x54\x11\ -\x12\x9f\xcf\xaf\xcf\x19\x60\x1a\x02\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x18\x88\x68\x09\x90\x43\ -\x12\x1c\x10\x0c\x6a\xc8\x45\x78\x90\xcb\x9d\x25\xc0\x12\x2f\xe4\ -\x02\x82\x01\x60\x44\xc2\x10\x23\x92\xe0\x40\x07\x03\xa7\x10\x00\ -\x00\x00\xa0\xfb\x1d\x00\xbf\x76\x02\x82\x41\x15\xb9\xa8\x19\x08\ -\x06\xd5\x04\xa3\x7e\x20\x18\x14\x97\x8b\x7a\x82\x60\x70\x93\x5c\ -\xe2\x0d\xf2\x51\x5b\x10\x0c\x3e\x14\x48\xdc\xb0\xd3\x51\x67\x10\ -\x0c\xb9\x5c\x5d\x17\xd7\x6d\x40\x30\xb8\x6a\x34\xda\xe2\xf5\xd4\ -\x1f\x04\x43\x30\x9b\xd7\x84\x6c\x40\x30\xe4\x52\xa5\x1e\x64\x03\ -\x82\x31\x1a\xed\xf6\xbd\xe5\x04\x04\xa3\x7b\x21\x1b\x10\x0c\xfa\ -\x90\x0b\xd9\x80\x60\x8c\x46\x5d\x08\x47\x96\x20\x14\xba\x17\xb2\ -\x01\xc1\x90\x0b\xd9\x80\x60\x30\xe8\x68\x54\x4a\x36\x32\x47\x30\ -\xd0\xbd\x90\x0d\x08\x86\x5c\xc8\x06\x04\x83\xe0\x16\x98\x6b\x85\ -\x23\x97\x04\x03\x72\x21\x1b\x28\x5e\x6b\x1b\xc9\x9a\x93\x0d\xc1\ -\x40\xf7\xd2\xb8\x6c\xac\x27\xc1\xd8\x2c\xd6\x9a\x6c\x08\x06\x46\ -\x23\xb2\x01\xc1\xe8\x5e\xb0\x5a\x38\xd6\x9e\x60\xc8\x05\xe3\xca\ -\x26\x4d\x1c\x06\xe1\x37\x1a\x91\x0d\xb9\x10\x8c\xee\x05\x3d\xca\ -\x26\x4d\x1e\x04\x1b\x80\x5c\xc8\xa6\xe0\xbe\x48\x1f\xbc\x58\x9a\ -\x24\x20\x36\x81\xd1\x48\xad\x2a\xef\x91\x99\xba\x1a\x9b\x40\xf7\ -\xa2\x6e\x15\xf7\xcb\x6c\xd7\x64\xee\x64\x8c\x5c\x4e\xf9\x31\xdf\ -\x25\x82\xd3\x47\x4e\xad\xd3\x16\x41\x71\x02\x21\xac\x08\x5c\xba\ -\x35\x7c\xad\x7c\x98\x67\x35\xfd\xac\xb6\xdd\xd7\x18\x7d\x87\xb0\ -\xfb\x0f\xf2\x5b\x4d\x87\x3f\x48\x6a\xb7\x88\x30\x1a\x69\xdf\x27\ -\xab\x77\x2d\x5c\x83\xd9\x26\x6c\x3e\x8c\xc3\x18\x04\x23\x7c\xe7\ -\x3c\x87\x10\xfe\xd8\x45\xb0\x31\xb4\xca\x50\x7b\x1d\x8c\xd1\x08\ -\x00\xc1\xe8\x00\xa1\xde\x04\xa3\x3d\x06\x08\xc6\x68\x04\xb9\x00\ -\xc1\x68\x95\x01\x1b\xc5\x68\x84\x4e\x32\x11\x85\xb8\x13\xc1\x34\ -\x78\xcf\x03\xb7\x61\x40\xf7\x87\x4e\xda\x71\xf3\xdf\xb5\xbe\x08\ -\x8d\x0d\xb9\xe4\x02\xf4\x28\x18\xa7\x14\x3a\x42\x16\x08\xa6\x48\ -\x43\x05\x80\x60\x9c\x58\xb8\x8d\xfb\x10\xc2\xaf\x10\xc2\x31\x84\ -\xf0\xf8\xfa\xbc\xd7\x03\x29\x0a\x74\x53\x77\xf7\x71\xdf\x0f\x84\ -\xbf\x67\x21\x78\x94\x93\x31\x25\x33\xdf\x5b\x40\x0b\x1c\xcf\x42\ -\x70\x5c\xce\x0b\x5a\x1f\x91\x62\x58\x7f\x93\x54\xa3\x11\x4a\xf0\ -\x74\xf6\xfc\x5b\xde\xe1\x04\x18\x8d\xb0\xcc\xfd\xeb\x58\x74\xfc\ -\x3c\x10\x32\xe3\x84\x5e\x25\x98\x4d\xd7\xc6\x6f\xe9\x4d\x97\x1b\ -\xe5\x6d\x79\x44\x1a\x5d\x2e\xfa\x68\x10\x0c\xb9\x14\x7f\x41\x92\ -\x91\x25\x82\x31\x36\xbe\x19\xb2\x0f\x1b\xa6\x47\x1f\xdd\xaf\x3d\ -\xb6\xce\xc2\x54\x9b\xc9\x89\xf3\x5e\x2e\xd7\x2c\x5a\xb2\xd8\xd3\ -\xb4\x26\xf1\xfd\x3f\x2b\xb3\x0e\xa6\x7e\x3b\x2b\x75\x20\x18\xdd\ -\x1c\xb9\xa0\xab\x83\x8b\x60\x3a\x1c\x8d\xb6\x12\x4a\x23\xbf\x2c\ -\x08\x10\x4c\x8b\x27\xcc\xa9\x1c\x5e\x56\x9a\x88\x58\xc6\x6d\x6f\ -\x2f\x64\x41\xb9\xed\x83\x3a\xdd\x0b\x8c\x46\x72\x35\x6f\x07\x23\ -\x04\x00\xc1\xec\x3b\x1a\x01\xa6\x01\x82\x11\x08\x80\x60\x8c\x46\ -\x03\x2c\xde\x43\x0a\x21\xa5\x10\x0e\x29\x84\x07\x2b\xa2\x63\xc6\ -\xdb\x42\xfb\xb3\xfa\xdb\xe5\x72\xfa\x20\x99\xbc\xac\x41\xd1\xb1\ -\xb0\x78\x87\x0b\x82\x39\x58\x99\xac\xbc\x19\x91\x8c\x46\x80\x31\ -\x09\x46\x23\x23\x92\x8e\x19\x0a\x3d\xb4\x64\x5c\xe4\x35\x26\xad\ -\x22\x0e\x5e\xe4\x99\x3e\x2f\xda\xcc\xde\xd4\x99\x1b\xf9\x1a\x4c\ -\x34\x13\xa3\xd1\x83\x8e\x60\x74\x68\x00\x08\x66\xbd\x64\x74\x31\ -\x70\xb0\x11\x8c\xb6\x15\x20\x98\x7e\x4f\x14\x92\x81\x03\x8d\x60\ -\x48\x06\x20\x18\x92\x01\x96\xb2\x46\x30\x24\x03\x18\x93\x08\xc6\ -\xe9\x02\x10\x4c\x47\x92\xd1\xc5\xc0\x41\x46\x30\xda\x58\x80\x60\ -\xfa\x3d\x61\x48\x06\x0e\x30\x82\xd9\xac\x8a\x24\x03\x98\x11\x77\ -\xf1\x92\xf5\xc1\xd6\xd9\x9a\x2a\x53\xae\xc1\xe8\x64\x60\x4c\x22\ -\x18\x1d\x1d\x40\x30\x23\x4a\x46\x17\x03\x07\xd7\x95\x7c\x51\xfb\ -\xec\xb6\x56\x77\x83\x26\x67\xac\x96\x83\xa9\x83\xc9\x3f\x71\x74\ -\x32\x28\xe5\x88\x9b\xbe\xb0\xe5\x60\x12\x0c\xc9\x00\x04\x43\x32\ -\x18\x30\x4b\x04\x03\x92\x01\x08\xc6\xe9\x83\xbe\x48\x5b\x05\x31\ -\xda\x38\xc3\x86\xc2\xfa\x41\x7e\x74\x30\x6d\x9d\x42\xd0\x09\x1b\ -\x91\x90\x1b\x10\x92\x81\x03\x8a\x60\x48\x06\x20\x18\x92\x81\x31\ -\x89\x60\x40\x32\x00\x9b\xd6\x9f\xa3\xe3\x0c\x1f\x52\x78\x64\x46\ -\x07\xb3\x8f\xa4\x75\x31\x00\xc1\x54\x3f\xf0\x81\xe9\x9a\x3f\x82\ -\x29\x17\x18\x92\xc1\xf4\x07\x12\xc1\x90\xcc\x55\x7c\xb5\xfb\xa1\ -\x55\x6b\x26\x8f\xb1\xea\x0b\xa0\x48\x31\x63\x77\x2f\x3f\x78\x07\ -\x93\xce\x1e\xdf\x75\x32\x59\xfc\x5c\x78\x0e\x9d\x0b\x2e\x08\x26\ -\xcd\xfd\xf1\xb3\x39\x9e\x7d\xe1\x51\x94\x46\xcd\xee\xf0\xdb\xc3\ -\x35\x98\x7a\x9d\x4c\x76\x80\x9e\x16\x9e\x43\xe7\xd2\x73\x3b\xdf\ -\xce\xc5\x88\x31\xb3\x1a\xad\x5b\x9f\x72\x89\xe5\xbf\xe5\x70\x65\ -\x26\x98\x1d\x32\x9b\x2e\x2c\x03\x81\x74\x7e\x2a\x10\x4c\x7d\xc1\ -\x20\x5f\x32\x0a\x21\x1b\x23\x46\x41\xae\x1b\x91\x8c\x42\x98\xc2\ -\x08\x06\xc5\x24\xa3\x10\xc3\x8b\x63\xca\x3d\xe9\x7f\x76\xac\x3b\ -\xab\xc7\xe0\x87\x14\xb3\x8a\x63\x4a\x1c\x9c\x75\x05\x33\xc5\xdc\ -\x4d\x1c\xf6\x9e\x0e\xa6\xbd\xb0\x93\xcc\x18\xdd\x86\x3a\x5a\x8c\ -\xdd\x3b\x98\x0d\xbe\x9c\x38\xec\x15\x82\x21\x18\x92\x21\x0e\x82\ -\x41\xdf\x9e\x22\x0e\xb9\x26\x18\xf4\x2d\x19\xdd\x06\x14\x6c\x20\ -\xc9\xc4\x9d\xbe\xaf\x1c\x42\x61\x49\x86\x38\xd0\x3e\x6e\xd7\xd0\ -\xdf\xe8\x94\x32\x1f\xa5\xde\x50\x4c\x21\xc4\xc3\x7f\x79\x2c\x3d\ -\xa0\x83\x41\x07\x52\xd9\x35\x0b\x49\x98\x40\x30\x24\x53\xb2\xc6\ -\x49\x98\x40\x30\x53\x49\x26\xb6\xf6\xe6\x84\x09\xe7\xf8\x53\x01\ -\xf2\x07\x8a\xe1\x22\x2f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\xb0\x07\xff\x00\xcf\xe0\xb9\x5d\xbc\xc2\x7e\ -\xd0\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x04\xd8\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x01\x18\x00\x00\x00\xa0\x08\x06\x00\x00\x00\xd7\x60\x73\x20\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x01\x1b\ -\x0d\x1e\x28\xb7\xb4\x2a\xa7\x00\x00\x00\x45\x69\x54\x58\x74\x43\ -\x6f\x6d\x6d\x65\x6e\x74\x00\x00\x00\x00\x00\x43\x52\x45\x41\x54\ -\x4f\x52\x3a\x20\x67\x64\x2d\x6a\x70\x65\x67\x20\x76\x31\x2e\x30\ -\x20\x28\x75\x73\x69\x6e\x67\x20\x49\x4a\x47\x20\x4a\x50\x45\x47\ -\x20\x76\x36\x32\x29\x2c\x20\x71\x75\x61\x6c\x69\x74\x79\x20\x3d\ -\x20\x39\x30\x0a\x67\x9d\x47\x40\x00\x00\x04\x07\x49\x44\x41\x54\ -\x78\xda\xed\xdc\x4f\x6e\x13\x31\x14\x07\xe0\x37\xfd\xb3\x85\x25\ -\x12\xe7\xe0\x9e\xb9\x01\x27\xea\x39\x2a\xb1\x0c\x7b\x30\x1b\x2a\ -\x55\x55\x9a\xcc\x24\x63\xc7\x7e\xfe\x3e\x29\x42\x45\x11\xc9\x38\ -\xcf\x3f\xfb\x79\x1a\x22\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\xf6\xb1\x18\x02\xe8\x4a\xc9\x34\x4f\x9f\x7c\x9e\xd0\x4d\x90\xa4\ -\x23\x60\x40\x90\x68\x91\x60\xb2\x30\x49\x31\x37\x05\x0c\x08\x12\ -\x01\x03\x82\x44\xc0\x80\x30\x31\xaf\x0c\x04\x82\xc4\xfc\x11\x30\ -\x20\x48\x04\x0c\xa4\x08\x12\x73\xc2\x60\x22\x4c\xd4\xbe\x80\x01\ -\x41\x22\x60\x0c\x01\x82\x04\x01\x83\x50\x51\xb7\xc3\xf1\x5d\x24\ -\x2c\x80\x08\x18\x04\x09\x00\x58\x39\x68\xa6\xa8\xb7\x79\x3d\x18\ -\x02\x1a\x85\x0b\x02\x06\x84\x0b\x5a\x24\xc6\x0b\x17\xb5\x66\x07\ -\x03\x16\x32\x04\x0c\xc2\x05\x00\xab\x0b\xdc\xc6\xad\x68\xb4\x48\ -\x54\x0f\x17\x10\x30\x08\x17\xb4\x48\x8c\x17\x2e\xea\x09\x3b\x18\ -\x2c\x56\x08\x18\x84\x0b\x00\x58\x79\xb8\x8d\x5b\xd1\x68\x91\xa8\ -\x1e\x2e\x20\x60\x10\x2e\x68\x91\x18\x2f\x5c\xd4\x0c\x76\x30\x58\ -\x90\x10\x30\x08\x17\x26\x2f\x1a\x77\x0f\x80\x26\x01\x23\x64\x72\ -\xb3\x98\xa0\x45\xa2\x7a\xb8\x40\xb3\x80\x59\x14\xa2\x70\x81\x96\ -\x3b\x18\x05\x99\x3b\x5c\xb4\x47\x34\x0d\x18\x05\x37\x0f\x9f\x35\ -\x77\xd9\xc1\x68\x95\x84\x0b\x54\x2d\x28\xdb\x69\xa0\xea\x8a\x25\ -\x64\xc6\xe6\x56\x34\x5d\xb6\x48\x8a\x32\x57\xb8\x40\x97\x01\x73\ -\x2a\x64\x14\xae\x70\x81\xdd\x02\x46\x01\x8f\x1f\x2e\x76\xa2\x74\ -\x1d\x30\x0a\x74\x5c\x3e\x3b\x86\xd8\xc1\x68\x95\x84\x0b\x54\x2d\ -\x36\xdb\x6f\xa0\xea\x6a\xe6\x1b\xd7\x7d\x71\x2b\x9a\x14\x2d\x92\ -\x56\xa9\xef\x70\x81\x14\x01\xa3\xd0\x85\x0b\x54\x6b\x91\xce\x15\ -\xb8\x2d\xfa\xfd\xc2\xc5\xd8\x93\x6a\x07\xa3\xa0\xf3\x2e\x1e\xd0\ -\x45\x8b\xe4\x3c\x46\xb8\x40\xd5\x42\xb4\x5d\x07\xaa\x4e\x7a\xb7\ -\xae\xeb\x72\x2b\x9a\x29\x5b\x24\xad\x52\xdb\x70\x81\x29\x03\xc6\ -\xc4\x30\x86\x08\x98\x61\x5b\x30\xe1\x62\x8c\xb1\x83\xd1\x2a\x09\ -\x70\x04\x8c\x6d\xbe\x70\x81\x81\x8b\xd4\xf6\x1e\x04\x4c\xd3\x90\ -\x11\x30\xeb\xc7\xcb\x58\xa1\x45\xda\x18\x68\x5a\x25\x6d\x24\x02\ -\xc6\x44\x32\x26\x30\x46\xc0\xd8\xea\x6f\x0f\x17\x63\x86\x80\xd1\ -\x2a\x09\x64\xe8\xad\x45\x12\x32\xc2\x05\x05\xdc\x24\x54\x4c\x28\ -\x10\x30\x42\xa6\xd2\xf5\x0b\x58\xb4\x48\x5a\x25\xd7\x0a\xa3\x04\ -\xcc\x32\xe9\xc4\x13\x2e\x08\x98\x89\x5a\xb5\x7b\x87\x8b\xf6\x08\ -\x01\xd3\x30\x64\x66\x5a\xe1\x85\x0b\x02\x46\x1b\x21\x5c\x60\xf4\ -\xe2\x76\xeb\x1a\x04\x8c\x90\xb9\xf2\x7a\x04\x26\x5a\x24\xad\x92\ -\xf7\x0e\x59\x03\x26\xcb\x2a\x2f\x5c\xd0\x22\x0d\x34\x41\x47\x0a\ -\x1e\xb7\xa2\xb1\x83\xb1\x23\x10\xe6\x30\x6b\xc0\x64\xf9\x2d\x5f\ -\xe1\x82\x16\x49\xab\x04\xcc\xba\xb2\xf6\x1e\x32\x6e\x45\xc3\x60\ -\x2d\xd2\x96\xc0\xf1\x5e\x40\xc0\xe4\xd9\x6d\x95\x88\xe7\x43\x44\ -\x39\x46\xc4\x21\x22\x9e\xd5\x13\x0c\xaf\xf4\xf2\x38\x44\x94\xf2\ -\xee\x71\xe8\xe8\xbd\x79\x4c\xfd\xb0\x2b\xc8\xd0\x8e\x1c\x23\xe2\ -\xcb\xbb\x9f\x7f\x47\xc4\x57\x0b\x00\xe6\xf5\xd0\x67\x30\xdd\x0c\ -\xde\xcf\x0b\x3f\x03\xdc\x74\x06\x53\x22\x0e\x25\xe2\xf8\xff\x4f\ -\xc7\x30\x00\x30\x55\xbb\x31\xf6\x46\xc6\x78\xa2\x0e\x4f\x79\xf0\ -\xb9\x00\x02\x06\x00\xa0\xbb\x5e\x4d\xef\x0b\xf9\xea\xf0\xc9\xe7\ -\xc2\x0d\x85\x7c\xae\xa0\xcb\x0e\x8b\xda\xa5\x2f\xb8\xfa\x96\x7d\ -\xe7\x04\x0c\xd7\x84\xcb\xf2\xe1\xef\xca\x27\x93\x7b\xd9\xf1\x75\ -\x4e\x3d\x67\xed\xfb\x80\xe9\xbc\x26\xdf\xd5\x94\x9d\xff\xbd\x38\ -\x11\x26\x7b\xbe\x66\x2f\x5e\x4c\x0d\x4e\x15\xfb\xda\xe2\x7e\x4d\ -\x16\x2e\x7b\x07\xcc\x9a\xb1\xcc\x1c\x30\x6f\x21\xf3\x52\xb9\x0e\ -\x9b\xb0\x9d\xdc\x77\x72\x5d\xda\xd2\xff\x8d\x88\x5f\x83\x5c\xd7\ -\xb7\x88\x78\xbc\xa1\x9d\xb9\xf6\x7c\x64\xcd\xd9\xcd\xa5\xd7\xfc\ -\xec\xb5\xfe\x0c\x34\xfe\xef\x7d\xdf\xb1\x0e\x9b\x72\x06\xd3\x3e\ -\xcc\xdf\x56\xa6\x1f\x89\xc2\x75\xb9\x70\xcd\x25\xd6\x9d\xab\x9c\ -\x0b\x94\x8f\xe7\x2b\xd7\x4c\xa2\xc7\x41\xc6\x35\x53\x8d\xa0\xd7\ -\xde\xb5\x35\xba\xf5\xf9\x65\x45\x5b\x74\xae\x45\x2a\xea\x82\x6c\ -\x2d\x52\x86\xc2\xbe\xe6\x9a\x6b\x04\xd2\x9a\xf0\xc8\x7e\x06\x93\ -\xa6\x0e\x7d\x55\x80\xbd\xdb\x22\x10\x30\x54\x0d\x97\xb2\xe1\xef\ -\xb6\x1c\x06\x2f\x67\x9e\xe3\x3f\x5e\xef\x90\x55\x88\x3d\x5b\x9c\ -\xe5\xc2\xf3\xb6\xdc\xf5\xd9\xfa\x5b\xbc\xea\x59\xc0\xa4\x9f\x74\ -\xc6\x13\x75\xa8\x45\x02\x04\x0c\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x50\xc1\x3f\x25\x05\xa1\xfc\x8e\x95\x2f\x33\x00\x00\x00\x00\x49\ -\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x0f\xff\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x01\x18\x00\x00\x00\xa0\x08\x06\x00\x00\x00\xd7\x60\x73\x20\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x01\x16\ -\x0e\x22\x36\xce\x64\x08\xbf\x00\x00\x00\x45\x69\x54\x58\x74\x43\ -\x6f\x6d\x6d\x65\x6e\x74\x00\x00\x00\x00\x00\x43\x52\x45\x41\x54\ -\x4f\x52\x3a\x20\x67\x64\x2d\x6a\x70\x65\x67\x20\x76\x31\x2e\x30\ -\x20\x28\x75\x73\x69\x6e\x67\x20\x49\x4a\x47\x20\x4a\x50\x45\x47\ -\x20\x76\x36\x32\x29\x2c\x20\x71\x75\x61\x6c\x69\x74\x79\x20\x3d\ -\x20\x39\x30\x0a\x67\x9d\x47\x40\x00\x00\x0f\x2e\x49\x44\x41\x54\ -\x78\xda\xed\x9d\x4d\xab\x5e\x57\x15\xc7\xff\xeb\x31\xb6\x98\xa4\ -\x55\x74\xe0\x20\x05\x27\xca\x0d\x94\x82\xd0\x8e\xfc\x00\xa6\x13\ -\xa1\x24\x90\x0a\xe2\xc0\xcf\xa0\xb4\x10\x70\xa2\x90\x91\x76\xe4\ -\x40\xfc\x06\x2d\xe4\xe2\x30\x26\x13\x47\x8e\x92\x81\x10\x21\xd7\ -\x20\x28\xb4\x03\xb1\x05\x21\xbd\x17\x5a\xa5\xdb\xc1\xf3\x3c\xe6\ -\xb9\x27\xfb\x9c\xb3\xdf\x5f\xd6\xfe\x2f\xb8\xe4\xe6\x9e\xfb\x72\ -\xce\x5e\x7b\xff\xf6\x6f\xed\xf3\x26\xc6\x18\x30\x18\x0c\x46\x8e\ -\xd8\xb0\x09\x18\x0c\x06\x01\xc3\x60\x30\x08\x18\x06\x83\xc1\x20\ -\x60\x18\x0c\x06\x01\xc3\x60\x30\x08\x18\x06\x83\xc1\x20\x60\x18\ -\x0c\x06\x01\xc3\x60\x30\x08\x18\x06\x83\xc1\x20\x60\x18\x0c\x06\ -\x01\xa3\x2c\xee\xdf\x07\xde\x7c\x13\x10\x01\x5e\x7e\x79\xfb\xf9\ -\xfd\xfb\x6c\x17\xc6\x30\x21\xbc\x17\x29\x53\x1c\x1f\x03\x37\x6e\ -\xd8\xb7\xdd\xb9\x03\x5c\xbf\xce\x36\x9a\x76\x46\x91\x64\xbf\x8b\ -\xfd\x9a\x80\xd1\x1d\x6f\xbc\x01\x3c\x7c\x68\xdf\xf6\xfa\xeb\xc0\ -\x83\x07\x84\x49\xe1\x60\x5f\x27\x60\xf4\xc4\x8b\x2f\x02\x9f\x7f\ -\x6e\xdf\xf6\xc2\x0b\xc0\x67\x9f\x11\x28\x19\x40\x50\xea\xef\x30\ -\xdc\x82\x6b\x30\xb9\xe2\xb5\xd7\xc2\xb6\x29\x02\xca\xe1\xc7\xd2\ -\x20\x9f\x7e\xc4\x5a\x8a\xeb\xef\x73\xdd\x47\x06\x01\xd3\x5e\xdc\ -\xba\x15\xb6\x4d\x09\x58\x5c\x01\x50\xaa\x34\xf2\x01\x0e\x83\x80\ -\x69\x3f\xae\x5f\xc7\xf7\x01\xfc\x61\xf7\xdf\xa7\x00\x70\xed\x1a\ -\x70\xef\x9e\xba\x05\xde\x25\x0b\xa8\x01\x94\x58\xe0\xd0\x6a\x12\ -\xf6\x0d\xd6\xa1\x65\xd6\x1d\x34\xb6\xf3\x9a\xa9\xf0\x78\x7a\x3b\ -\xfe\xb9\x63\x27\x60\x9a\xef\xac\x9a\xda\x79\x6e\x20\xf2\x18\xf5\ -\xc1\x25\x16\x32\x17\x88\x02\x06\x07\xdd\xfc\x31\x4d\x8f\x79\xff\ -\x7f\x4e\xcc\x04\x4c\xfb\xf6\x32\x1d\xb0\x0d\x77\xda\xb9\xf5\x15\ -\xed\xb1\x04\x1a\x42\x86\x80\xe9\xcb\x49\x45\x9a\x83\xcc\xa8\x60\ -\x71\x01\x8d\x0a\x9b\xd9\x1f\xc3\xfe\xbf\x48\x7b\x2c\x04\x4c\xc2\ -\x7a\xd5\x18\x3d\x6b\x2f\xa3\xad\x41\xf8\x80\x46\x4d\xd9\x64\x9b\ -\x3c\x20\x49\x21\xc3\xd3\xd4\x89\xe0\xb2\xb6\xad\x77\xb8\xb4\x74\ -\x9a\xb9\x05\xc8\xcc\x9d\xde\x66\x10\x30\xc5\xb5\xda\x16\x6f\x77\ -\x02\x17\x82\xc5\x0f\x34\x84\x0c\x01\x13\x32\xea\x8e\x20\x72\x07\ -\x22\xa7\x06\x72\xc7\x40\x8e\x62\x7f\xe5\x07\x07\x90\x79\xb2\xfb\ -\x5c\xaa\x1e\xa2\xa8\x3e\xb5\x5e\x72\x22\xe9\xe5\x22\xbd\xb7\x9d\ -\x8f\x2f\xa2\x5f\xb1\x13\x39\xc0\x05\x78\x6c\xd9\x72\x55\x60\x4e\ -\x2c\x3f\xb0\x3a\x40\x6d\x17\xe0\xd5\xbc\x28\x8f\x0b\xb9\x63\xb6\ -\xa3\x88\xe0\x26\x80\xf7\x53\xd1\x84\x06\x13\x14\xb7\xdd\xbf\xfe\ -\xdf\xe0\x8e\x75\xf8\xfd\x25\x67\x3f\x96\x44\x85\x4a\xa6\x03\x0b\ -\xde\xfd\x7b\x54\x73\x5f\xf7\xfb\xf5\xc1\x6e\x4a\x94\x0c\x70\xa1\ -\xc1\xb8\x65\xe2\x14\xc0\x45\xcb\x96\x33\x81\xb9\x74\xfe\x4b\x7f\ -\x06\xf0\x5d\x6f\x7b\xf1\xd9\x9e\x1b\x2e\x0c\x3f\x43\x71\x02\x0f\ -\x30\x6b\xc1\x30\x36\x0b\xd6\x63\x5a\x34\x98\xf5\xb8\xeb\xfe\xf5\ -\x1f\x2c\x26\xd1\x05\x1e\xa5\x4c\x86\x70\x59\x6e\x1b\xdb\x47\x6a\ -\x0b\x3e\x06\x1e\x8b\x88\xd1\x0a\x17\x1a\x8c\x5b\x46\x3c\xd6\x60\ -\xc4\x19\x18\x6b\x8f\x0d\xc8\x99\x7c\xc2\x25\xce\x4e\x7c\xda\x4b\ -\x44\x60\x00\xab\x05\x9f\x01\xb8\xf4\xfc\xef\x96\x92\xc7\x99\x3b\ -\xf7\x34\x98\xf5\xde\x74\x72\x75\x3b\xdb\xe0\x6c\xf7\xef\xd5\x2d\ -\x4a\x1e\x1b\xb3\x2f\x5b\x05\x73\xe7\x80\xf6\x09\xf5\x81\x46\x4e\ -\x8b\x21\x5c\xec\xa6\xb2\xb4\xa6\x12\xf3\xc8\x89\xdd\xf7\xdf\x75\ -\x55\x63\x11\x31\xfb\x8f\x56\x4a\x3c\x1a\x4c\xde\x4e\x68\x2c\x8a\ -\x62\xe6\xb4\xc5\x05\x0e\xae\x6d\x9e\xda\x64\x08\x97\xf5\x41\x96\ -\xa5\x4d\x16\x2c\x78\xbf\x06\xb3\x06\x94\x58\xb3\xa9\x75\x86\x8b\ -\x06\x13\x24\x35\xe7\x92\x6d\x7c\x3a\xab\x4f\x52\x53\x9a\xcc\xe8\ -\x70\x71\x35\x95\x5c\x16\xbc\x13\xdf\xf3\x22\x7c\xb0\xc0\x6b\x8c\ -\x91\xfd\x07\x16\xcc\xa6\x27\xb8\xd0\x60\xc2\xec\xe5\xf0\xda\x15\ -\xe3\xd2\x96\xb1\x83\x3b\xd6\x64\x46\x86\x4b\x6b\xf7\x54\xf9\xe6\ -\x62\x0e\x2a\x3e\x46\x53\x33\xff\x04\x8c\x27\x60\x2c\x56\x61\x96\ -\x92\x9e\xea\x29\x69\xa1\x90\x19\x15\x2e\x2d\xdf\xac\x19\x92\x93\ -\x50\xd0\xd4\xbe\xf8\x8f\x25\x92\xa7\xbd\xd8\xbe\x75\xae\x13\x4c\ -\xa1\x10\x73\xdf\x4a\x8a\x72\x69\x64\xb8\xb4\xf6\x4c\xe0\x90\xb2\ -\x7c\x66\x02\x33\xad\xc2\x85\x80\x89\xe8\x18\x93\xe4\x89\x4b\xc2\ -\x6d\x09\xf6\x82\x8c\xe7\xcf\x69\x7f\x26\xb0\xcb\x3a\x4b\xab\x57\ -\x26\x87\x4e\x18\x36\xd0\xd8\xd6\x67\x5a\xb9\x6d\x81\x80\x89\xb3\ -\x17\xab\xaa\xee\x12\x3e\x9b\xd8\x69\xa7\x77\xba\x90\xcb\xf2\xcc\ -\x91\xa5\x9f\x19\xed\xae\xde\xde\xef\xa7\xf2\xcd\x97\xaf\xcd\xd4\ -\x6a\x0b\x02\x26\x81\xd6\xee\xb7\xb9\x9e\x5d\x8a\xb5\x99\x35\xc8\ -\x8c\xb4\xee\xd2\x93\xb5\xa4\xb4\x59\x1f\x9b\xa9\x19\x04\x4c\x80\ -\xbd\xac\x2c\xdc\xca\xf9\xff\x9a\xf8\x4e\x26\xe2\x0d\x99\x51\xe0\ -\xd2\xfb\xf1\xa6\xd8\xdf\x99\x85\x5e\xd3\x42\x9b\x10\x30\x89\x3b\ -\xc1\xae\xd3\x3b\xaf\xc9\xac\x96\x4c\x87\x83\xc8\x52\x6a\x4d\x07\ -\xdb\x28\xeb\x2e\x9a\xee\x02\x4f\xb4\x80\x6f\xbb\x9c\xdc\xd4\x6e\ -\x13\x02\x26\xd2\x5e\x16\x12\xe8\x05\x99\x55\x9b\xa9\x7c\x73\x64\ -\xeb\x70\x61\xd8\xfb\x6d\xed\x72\x89\x80\x49\x6f\x2f\x87\x33\x6a\ -\x3c\x64\x16\xe0\xb2\xb4\x8f\x7a\x5f\x10\xa6\x13\x2e\xb1\x13\xc5\ -\xdc\x59\xcd\xda\x90\x21\x60\xf2\xd8\x8b\xb5\x3e\x76\x5d\x80\x33\ -\x78\xfe\x94\x34\x9f\xf5\x3a\xde\xe2\x75\xcc\xf7\xda\x16\x7f\x09\ -\x18\x45\xf6\x12\x9c\xec\x85\xdf\x33\x32\x64\x46\x80\x4b\x8a\xdb\ -\x40\x0e\x7f\x4f\x0b\x90\x21\x60\x10\x7c\xdd\x8b\x13\x5c\xbc\x20\ -\x63\x59\x73\x71\x81\x8c\xeb\xd7\x08\x17\xfd\xa5\x52\xd4\xe4\x46\ -\xc0\xd4\x9b\x4d\x52\x0c\xd8\xe9\x75\x0b\xe7\x92\xbd\x72\xb6\xc8\ -\xf5\xc2\x3c\x6d\x8b\xbe\x5c\xd0\x8d\x2f\xa3\x6a\x42\x66\xc3\x24\ -\xf9\xdb\x8b\x6b\x69\xe4\x92\x70\x11\x31\x70\x38\x5b\x34\x07\x3d\ -\xdb\xdf\xd7\x02\x99\x51\xe1\xe2\x92\x3f\xdf\x6b\x80\x6a\x41\x86\ -\x06\x13\x60\x2f\xa9\x2f\x8e\x92\xfd\x9a\xae\xdb\xa2\x71\x11\xd5\ -\x26\x5c\xfa\xee\xb3\xad\x40\x66\x68\xc0\xd4\xb0\x97\x55\xc8\x38\ -\x76\x26\xe3\x09\xa3\x9e\x4d\x66\x44\xb8\x2c\xe5\x2e\x06\xbe\xb9\ -\x9f\xf9\x4b\xc0\x44\x76\xe6\xa4\x9d\x7d\xfb\x40\x68\xef\x6b\x65\ -\x7c\x6a\xf2\x1e\x21\x33\xda\x5d\xe0\xa5\xcd\x2e\xe4\xfa\x2c\x02\ -\x26\x93\xbd\xb8\xd4\xc0\x41\x83\xe0\xfc\xcf\x13\x32\x0a\x4c\xab\ -\x74\xa9\x9e\x69\x2c\x10\x30\xb5\x55\x3c\x25\x5c\x60\xb9\x66\xc1\ -\x25\xe1\x4b\x0f\xb3\x5a\x7b\xcf\x74\x2f\x83\x98\xf6\xe2\x3e\x71\ -\xc4\x58\x0c\x0d\xa6\x11\x7b\x49\xd2\xe1\x97\x4f\x47\x47\x99\x8c\ -\xcf\xe2\x74\x8b\x90\x61\x69\x54\x16\xbe\x25\x4a\x25\x1a\x4c\x49\ -\x7b\x71\xbb\x79\x71\x35\xe9\x6b\x0f\xb3\xea\x11\x32\x2c\x8d\xdc\ -\xfb\x65\x2e\xf8\xe6\x80\xcc\x70\x80\x09\x3d\x73\x14\x9d\x54\xc7\ -\x6b\x5d\x52\x99\xcc\x52\xc9\xd4\xba\xc9\xd0\x5e\xca\x81\x37\x77\ -\xa9\x44\x83\xc1\xfa\x4b\xe8\xa3\xed\xc5\x03\x2e\x3e\x90\x89\x79\ -\x98\x55\x4b\x90\x61\x69\x54\x17\xbe\x39\x4b\xa5\xa1\x00\x13\x73\ -\xdd\x4b\x49\xb8\xa4\x4c\x3c\x6f\x98\xa4\xbd\xd4\x8c\xe1\x0d\xc6\ -\xc5\x5e\x82\x67\x8d\x08\xb8\xcc\x41\x26\xf4\x06\x38\x5b\xc9\xd4\ -\x82\xc5\xd0\x5e\xfc\xe0\x92\x2b\x4f\xb9\x2c\x66\x33\x50\xc2\xca\ -\xda\x4b\x02\xb8\xcc\xd4\xc9\x26\xe2\xf7\x3c\x77\x5c\x23\x3e\x11\ -\x8f\xe6\x42\x83\xe9\xdf\x5e\x32\xce\x30\x88\x78\xd6\x6a\x4b\x90\ -\xa1\xbd\x84\x19\x68\x89\x3e\x96\xca\x62\x86\x00\x4c\xcf\xf6\x92\ -\xa3\x03\xd8\x4a\x26\xad\x33\x68\xef\xf6\xd2\x3b\x78\x87\x35\x18\ -\xe3\xbf\xe0\x5a\x15\x2e\x07\x9d\x2f\xd9\x2c\xb3\x74\x4c\x25\x21\ -\x43\x7b\xf1\x83\x4b\xce\xdc\xa4\x3e\x6d\xbd\x19\x20\x69\xce\xf6\ -\x12\x9d\xb8\x8c\x70\x99\xfe\xa5\xde\x21\x43\x4b\x6a\xbf\x3d\x52\ -\x94\x49\x43\x1a\x4c\x16\x7b\x29\x07\x97\xe0\x37\x16\x84\xd4\xf8\ -\x04\x41\x3b\x7d\xb4\x47\xd3\x53\x0d\x98\x62\xf6\x52\x10\x2e\x73\ -\x3a\x9b\x62\xb6\xa9\xa4\xe4\xb4\x97\xc6\xd6\x5d\x52\x96\x49\xc3\ -\x19\x4c\x72\x7b\xa9\x04\x97\x9e\x21\x43\x2b\x6a\x17\x2e\xa9\xcb\ -\xa4\x8d\xe2\xc4\x45\x3d\xad\xae\x35\x73\x71\x7d\x1f\x76\x2a\xc8\ -\xb8\xde\x3e\xc1\xa8\x03\xda\x5e\x72\x31\x94\xc1\xf8\xce\xce\x8b\ -\xb3\x49\x65\x73\xc9\x0d\x99\xb9\xe3\x4f\xd9\xb1\x59\x1e\xb5\xdb\ -\x1e\xa9\xca\x24\x95\x80\x49\x61\x2f\xad\xc2\xc5\xf5\xc9\xf1\xad\ -\x42\x86\x16\x14\x57\x1a\xd5\x80\x50\x4c\x5f\x1a\xc6\x60\x92\xad\ -\x2d\x34\x66\x2e\x1a\x4c\x86\x70\xd1\x6b\x72\x1b\x85\xc9\xcb\x67\ -\x2f\x8d\xc3\x25\x37\x64\x52\xde\x99\xcd\xf2\xa8\x8b\x49\x39\x7a\ -\x16\xd9\x0c\xd2\x50\x43\x77\x8e\x94\x77\xc7\xf2\xf1\x0f\xb4\x97\ -\x61\x01\xe3\x6b\x2f\x5e\x0f\x52\xee\xc4\x5e\x08\x19\xc2\x85\x80\ -\x69\xd4\x5e\x34\xc1\x65\x0e\x32\xb9\xd6\x65\x96\xde\x97\xcd\x18\ -\xf7\x4d\x95\x1b\x45\x09\x8c\xb6\x17\x6d\x70\x99\xab\xa5\x6b\x2d\ -\xfe\x12\x40\x6a\xc6\x17\x0d\x26\x89\xbd\x28\x80\x4b\x6b\x90\xa1\ -\xbd\x8c\xb5\x26\xb8\x51\x92\xc0\xf4\x57\xed\x2a\x82\xcb\x21\x64\ -\x4a\x9e\xc6\x0e\xba\x80\x91\x70\x69\x7a\x62\xa2\xc1\x38\x24\x72\ -\x35\xe9\x0a\xe1\x32\xd7\x69\x72\x42\x66\x74\x9b\x19\xd9\x5c\xd4\ -\x00\x26\xb9\xbd\x28\x87\x0b\x21\xc3\x20\x60\x6a\xd9\xcb\x20\x70\ -\x21\x64\x68\x2f\x04\x4c\x69\x7b\x19\x0c\x2e\x35\x20\x43\xb8\x10\ -\x30\x63\x26\x7e\x50\xb8\xd4\x82\x8c\x66\x9b\x21\x5c\x94\x00\x66\ -\xce\x5e\x78\x8f\x4b\x3f\x90\xd1\x5e\x36\xb1\x2f\xf2\xad\x02\xdd\ -\xd9\x4b\xa9\x27\xca\x97\x2a\x97\x34\x41\xa6\xc4\xb1\xf4\xd6\x5e\ -\x9b\x4e\x13\xe9\x6d\x2f\xd6\xc4\x0c\x5e\x1a\x95\x82\xcc\x1a\x74\ -\x34\x40\x86\xa5\xd1\xc0\x06\xa3\x01\x2e\x46\xd9\xc3\xc4\xe7\xde\ -\x97\x4d\xb8\xe8\x2a\xbf\x36\x1d\x26\x33\x7a\xed\xc5\x9c\xcf\x14\ -\xa7\x99\xc2\x90\x59\x7a\x5b\x66\xef\x36\x43\x73\x19\xcc\x60\x9e\ -\x9b\x5d\x08\x97\x26\xca\x25\x2d\x90\xe1\x45\x84\x8a\x00\x13\x6b\ -\x2f\x5a\xe0\x52\xf8\xb5\xae\xc5\x20\xd3\x5b\xc9\x54\xba\x34\xaa\ -\xd1\x1e\x7c\x6d\x89\x6b\x07\xa6\xb9\x34\x0f\x99\x9e\x6c\x86\x8b\ -\xba\xca\x00\xe3\x6b\x2f\x1a\xe1\x52\xb3\x13\xa7\x80\xcc\xe1\xfe\ -\xcf\xad\xc9\xf4\x58\x32\x99\x86\xde\x2c\x51\x22\xff\x34\x98\xf9\ -\x56\xe2\x94\xd2\x90\xc9\xac\x41\xa6\xd5\x92\x89\xeb\x2e\xca\x00\ -\x93\xc2\x5e\xb4\x75\x89\x5a\x9d\xbc\x24\x64\x5a\xb4\x99\x5a\xa5\ -\x51\xaf\x50\x1b\xe3\x3a\x18\xd6\xc8\x84\x4c\xc7\x70\xa1\xc1\x34\ -\x6e\x2f\xca\x06\xb7\x0a\xc8\x2c\x0d\xd6\x39\xc8\xb4\x54\x32\xd5\ -\xca\x43\xc9\xbf\x9b\x62\xf2\xd0\xf6\xda\x12\xf5\xa5\xd1\xc8\x26\ -\x53\xd3\x66\xb8\xee\xa2\x10\x30\xb1\xd7\xbd\x88\x72\x95\x6d\xa1\ -\xd3\xfb\x42\x66\x2d\x17\x2d\x42\xa6\x76\x69\x54\x3b\xcf\x31\xcf\ -\xe5\xdd\x68\x1c\x6c\x86\xe6\xd2\x85\xc9\xb8\x00\x64\xe9\x7b\x6a\ -\x94\x4c\xa3\xac\xbb\xa4\xba\xde\x69\xd3\xc9\x01\x7a\xd9\x8b\x28\ -\xef\x10\x2e\x03\xb0\xf7\x72\x29\x74\xc0\xa7\x6e\x8f\xa5\x5b\x1a\ -\x8a\x4f\x9c\x1d\xf6\x65\x2d\xaf\x2d\xa1\x46\x74\x04\x19\x97\x81\ -\xe2\x03\xd1\x5c\x90\x61\xbf\x52\xfa\xda\x12\x2f\x7b\x59\xe8\x04\ -\x46\xf7\x5b\x01\x54\x94\x4b\xe2\x98\x3f\x17\xc8\xa4\x2c\x99\x5a\ -\x3b\x25\xdd\xdb\xd9\x23\x1d\x06\x23\xc2\xb5\x96\x06\x67\xda\x94\ -\xe5\x92\x6f\x39\x98\xc3\x66\x6a\xc1\x45\x83\x41\x6d\x1a\x1c\x2c\ -\x6e\xf6\x32\x81\x0b\x2f\x7a\xea\xcb\x64\x42\x5f\xeb\x5b\x02\x32\ -\xbd\xaf\x7b\xb4\x52\x1e\xf5\x6b\x30\x96\x0e\x33\xe2\x55\x96\x2d\ -\x2e\xf6\x86\x98\x8c\x2f\x34\x5c\xbf\x3f\xa4\x64\x6a\xf1\x7e\xa7\ -\x5e\xcb\xa3\xe6\x00\xe3\x64\x2f\xbb\x86\xa7\xbd\xf4\x69\x32\x22\ -\xf2\xf3\x83\xcd\x5f\xce\x0d\xd4\x39\x9b\x11\xb1\x7d\xf0\x56\x00\ -\xf5\x25\x52\xea\x99\x9d\x16\xd3\x9c\x6a\xff\x42\x44\x7e\xba\xfb\ -\xfa\x7f\xf6\x7d\x30\x04\x18\xe1\x90\xf9\x5a\xd3\x7d\xa8\x05\x7b\ -\x49\x51\x1e\x35\x05\x98\x18\x7b\xe1\xe9\xc4\xee\xea\xf9\x5f\x1d\ -\xe4\xfb\x7b\xf0\xbc\x2e\x32\x14\x32\xcf\x7e\xee\x97\xb6\x1e\x48\ -\x73\xc9\x10\x17\xba\xd9\x53\x96\x46\x8b\x83\xe7\x99\xfa\x4b\xf3\ -\xa7\xb0\xf7\x70\x11\x11\x63\x8c\x11\x11\xb9\x08\xe0\xcc\x67\xdf\ -\x43\x8f\x79\xfb\x73\xff\x98\x85\x0b\xf0\xf7\xe1\xd7\x5e\x52\xd9\ -\x4b\x33\x06\xb3\x6a\x2f\x0b\xb3\x14\xeb\xe6\xf5\x36\x69\xd9\x66\ -\x76\xb9\xff\x31\x80\xaf\x97\x30\x99\x6d\x7c\x6b\x06\x2e\x66\xb2\ -\x8d\x79\x53\x53\x22\xad\x99\xcb\x50\xf6\x32\x5d\x7d\x0c\x58\x67\ -\xe8\xac\x64\xfa\x2d\x80\x9f\x00\xf8\x8a\xef\x20\xeb\x61\x0d\x2a\ -\xd4\x4a\x7b\xb7\x17\x00\x90\xda\x1d\x73\xd1\x5e\x26\x2f\x47\xb3\ -\xe9\xa3\xba\x6b\x16\xe6\x06\x89\xeb\x1d\xe4\x9d\xb5\xc7\x24\xff\ -\x37\x01\xfc\x11\xc0\xbf\x7c\xf7\xdd\xe7\xb8\x9f\x7d\xeb\xd4\x5e\ -\xbc\x9a\x5a\x5d\x69\x94\x03\x30\x9b\x2e\x06\x9a\x03\x5c\x9a\x32\ -\x0e\x2e\x3a\x87\x9a\xcc\x07\x00\x7e\x06\xe0\x62\xcc\x8c\xef\xd6\ -\x2f\xec\x70\x61\x2e\x14\x95\x48\xb3\xf6\x32\x99\xb9\x9b\x9e\x95\ -\xe7\x3a\x73\xbd\x67\xe6\x76\x57\x32\x4c\x3a\xf6\x3b\x00\x7e\x24\ -\x22\x5f\xca\x98\xb4\x59\xb8\x8c\x6a\x2f\x39\xe0\xd2\xb6\xc1\xac\ -\x64\x5b\xe3\xe2\x6e\x0e\x1e\x74\x0a\x99\xdf\x01\x78\x47\x44\xbe\ -\x91\x1b\xae\xc6\x9c\xff\xa8\x09\x97\x8a\x93\x7b\xb6\xd8\xd4\x1b\ -\x4c\x2b\xf6\xa2\x75\x8d\x65\x05\x2e\x62\xd1\x75\xf1\x54\xf8\x5e\ -\x5f\xc5\x3a\x81\xcc\x6d\x00\x3f\x4c\x6d\x70\x2d\xf5\xa7\x56\x26\ -\xc9\x5c\xf6\xd2\xae\xc1\xac\x2e\xd2\xe9\x3e\x35\x2d\x30\xe7\x3e\ -\x62\x4b\xa5\x5e\xd7\x01\x5e\x05\x7e\xf3\x4f\x91\x7f\x43\xe4\x0b\ -\x88\x3c\x81\xc8\xab\x31\x90\x69\x79\xb2\xd2\x56\x1a\x55\x05\x8c\ -\xd3\xda\x0b\x78\x57\x6b\xca\x4e\xdb\xd3\x29\x5c\x63\x8c\x1c\x01\ -\x78\x04\xe0\x9b\xc0\x57\x77\x7d\xe4\xdb\x00\x1e\x41\xe4\xc8\xe7\ -\xd8\x6f\xee\xf4\xf0\x89\xc8\xf6\xf3\x46\xfa\x92\x96\x9b\x19\xd7\ -\xe2\x42\x83\xbd\xab\xaf\x99\x7a\x7a\x3a\x3d\xf0\x38\x4a\x75\xea\ -\x5e\x40\xfd\x18\x38\x06\x70\xdd\xb2\xe9\x36\x80\x1b\x4e\xa9\x39\ -\xf8\xfc\x3b\x00\xde\x6f\x28\x0f\x2d\xac\xbb\xe4\xb6\x17\xa0\xc2\ -\x75\x30\x8b\xf6\x32\x33\xe3\xfa\xbc\xc7\xb8\xf7\x35\x98\x44\xdc\ -\xed\xbf\xa4\x14\x39\x85\xfd\x74\xf5\x19\x8c\xb9\x14\xd5\xa8\x8d\ -\xdc\xc8\x58\x32\x17\x35\xe0\x52\xad\x44\x5a\x4b\x3a\x4b\xa3\xbc\ -\xf5\x7d\x27\xe5\xd2\x5d\xdb\x17\x8f\x03\xae\x91\x69\xc9\x20\x47\ -\x82\x4b\x71\x83\x71\x7d\x5a\xdd\x68\xf6\x92\xdb\x60\xba\x6c\xbf\ -\xed\x5a\xcb\xe3\xe9\x97\xaf\x02\x38\x71\x1d\x24\x0d\x19\xcc\x88\ -\x70\x69\xcb\x60\x06\xb7\x97\xa5\x43\x4d\xd5\x0c\x5d\x99\x8c\x31\ -\x27\x3b\x9e\x1c\x03\x38\x03\x70\xbc\x87\x8b\x6d\xe0\x38\x37\x1c\ -\xe1\x52\xf6\xd8\x2b\x1d\xac\xb7\xbd\xb0\x74\xea\xbf\xc3\x6b\x19\ -\x34\x84\x4b\xa7\x06\xe3\x0a\x17\xc6\x40\x26\xf3\xfc\xbe\xcb\xd2\ -\x40\x22\x5c\xda\x82\xf0\xa6\xc2\x01\x5b\xed\x85\x76\x42\xc8\x68\ -\x82\x0c\xe1\xd2\xa0\xc1\x68\x54\xfa\xdd\x01\x34\x79\xb7\xb5\x36\ -\xc8\xb4\x02\x9a\x1a\xfd\xd5\x76\xfc\x2d\x94\x8f\x9b\x12\x07\x4e\ -\x7b\xe9\xcb\x64\x3a\xbd\x77\xa9\xba\xcd\xd8\xda\xae\x14\x5c\x5c\ -\xda\xa6\x46\x5c\xc8\x7c\xe4\xd9\x07\x04\x23\x4d\x9b\x4e\x07\x46\ -\x2f\x57\xfd\x4e\x9f\xf3\x7b\xf8\x79\xd1\xeb\x3d\x2c\x7d\x3d\x77\ -\xfb\xb5\x0c\x96\x22\x06\x23\xe7\xaf\xd6\x0e\xb2\x17\x2e\xee\x96\ -\x83\x8c\xa6\x92\xa9\xa4\xcd\xd8\xac\x85\x70\xc9\x6d\x30\x9e\x6f\ -\xd1\xa3\x99\xb4\x69\x33\xfb\xcf\x69\x33\xb4\x96\xa0\xfd\xcd\xd2\ -\x20\xdb\xf7\x46\x27\xb7\x17\x42\xa8\xec\x7a\x42\xcf\x25\xea\x9c\ -\xbd\xa4\x18\x8c\x35\xda\x26\xe7\xf1\xf4\x55\x22\x6d\x2f\xf1\xbe\ -\x73\x0a\xe0\x0e\x80\x23\xda\x8b\x9a\x92\xa9\xa7\xb2\xc9\x18\x23\ -\x73\x65\x53\x4c\xe9\x34\x67\x2d\xb9\xfa\xf0\xdc\xfe\xce\x1d\x9f\ -\x6e\x83\xd9\x3e\x10\xe8\x91\x65\xcb\x55\x01\x4e\x7c\xcf\x1c\x11\ -\x42\x6d\x1b\x4d\xcf\x57\x00\xfb\x5a\x40\xc9\xe3\x8f\xdd\x57\x9d\ -\x06\xb3\x4d\xc0\xef\x67\xb6\xde\xf6\x05\x07\x17\x77\xdb\x33\x1a\ -\x5b\x8e\x7a\x37\x9a\x43\x4b\xb0\x0d\xec\xb9\x63\x4c\x0d\x97\xa5\ -\x7d\xe8\xc9\x58\x72\x1b\xcc\x17\xb0\xbf\x9d\xef\xdc\x33\x3c\x42\ -\x00\x43\x83\x69\xdb\x66\x7a\xcc\x91\x43\xa9\x24\x39\x8f\x71\xed\ -\xef\xf7\x08\x94\x7c\x06\xb3\x8d\xbf\xcd\x7c\xfd\x2e\xe1\xa2\xcb\ -\x66\x96\x8c\xa6\x37\xab\x59\x18\xc8\x66\xfa\x11\xb2\x7e\x73\x68\ -\x27\xae\xa6\xa2\x01\x2e\x85\xd7\x60\xcc\xc9\xe1\x84\x60\x06\x7b\ -\x25\xc9\x88\x46\xd3\x4b\xee\x2c\xfb\x5f\x74\xa7\xb5\xc0\x24\xbb\ -\xc1\x08\xcc\x5f\x30\x79\x86\xc7\xee\xff\x27\x06\x82\xbf\x02\xff\ -\x7f\xf0\x32\xe1\xa2\xcf\x68\xd6\xac\xa6\x15\xb3\x59\xdb\xa7\x43\ -\x8b\x48\x69\x14\xb9\x7e\xef\x30\x06\x63\x7d\xf6\x35\xfc\x9e\x2a\ -\x46\xc0\xe8\xb7\x9a\xd2\xf9\x6d\x69\x5f\x46\x8b\x0b\x2d\xef\x1c\ -\x13\xde\xb7\xd5\xb8\x0c\xf2\xd4\x67\x68\x7c\x2c\x89\xfd\x6b\x30\ -\xc0\xf0\xd4\x34\x81\x93\xb3\x1f\x10\x28\x34\x18\xc6\xa0\xc0\x49\ -\x0d\x16\xc2\xa4\x11\x69\xc8\xbd\x06\x03\x58\xd6\x61\x78\xdf\x11\ -\x83\x41\x83\xf1\x9f\x35\xec\x90\x11\x98\xf9\x87\xb9\x7f\xf2\x09\ -\xf0\xde\x7b\x30\x00\x9e\x5e\xbe\x8c\x3f\x1d\x1d\xe1\xd7\x84\x0b\ -\x83\x41\x83\x89\x8e\x8f\x3e\x02\x5e\x79\xc5\xbe\xed\xc3\x0f\x81\ -\x2b\x57\x98\x21\x06\xa3\xe3\xa8\xfb\x4c\xde\x77\xdf\x0d\xdb\xc6\ -\x60\x30\x68\x30\xab\x71\xf9\x32\x70\x7a\x6a\xdf\x76\xe9\x12\xf0\ -\xe9\xa7\xcc\x10\x83\x41\x83\x09\x8c\xb7\xde\x0a\xdb\xc6\x60\x30\ -\x68\x30\xab\xc1\x35\x18\x06\x83\x06\x93\x2d\xae\x5c\xc1\xc3\x7b\ -\xf7\xf0\xe0\xda\x35\x00\xc0\xd3\x97\x5e\x02\x6e\xdd\x02\x3e\xfe\ -\x98\x70\x61\x30\x68\x30\x0c\x06\x83\xd1\xaa\xc1\x30\x18\x0c\x02\ -\x86\xc1\x60\x30\x08\x18\x06\x83\x41\xc0\x30\x18\x0c\x02\x86\xc1\ -\x60\x30\x08\x18\x06\x83\xd1\x66\xfc\x0f\x7a\x81\xf3\xb2\x41\xec\ -\xd4\x42\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x06\xaa\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x01\x18\x00\x00\x00\xa0\x08\x06\x00\x00\x00\xd7\x60\x73\x20\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x01\x18\ -\x09\x39\x05\x3d\x12\xc3\x85\x00\x00\x00\x45\x69\x54\x58\x74\x43\ -\x6f\x6d\x6d\x65\x6e\x74\x00\x00\x00\x00\x00\x43\x52\x45\x41\x54\ -\x4f\x52\x3a\x20\x67\x64\x2d\x6a\x70\x65\x67\x20\x76\x31\x2e\x30\ -\x20\x28\x75\x73\x69\x6e\x67\x20\x49\x4a\x47\x20\x4a\x50\x45\x47\ -\x20\x76\x36\x32\x29\x2c\x20\x71\x75\x61\x6c\x69\x74\x79\x20\x3d\ -\x20\x39\x30\x0a\x67\x9d\x47\x40\x00\x00\x05\xd9\x49\x44\x41\x54\ -\x78\xda\xed\xdd\x5d\x6f\x1b\x45\x14\x06\xe0\x77\x9b\xa4\x69\x4b\ -\x49\x80\xf2\x51\x21\x10\x42\xe2\xff\xa6\x42\xe2\xb6\x3f\x8c\x4a\ -\x5c\x56\x88\x1b\x08\x2a\x52\x2b\xb5\x2c\x17\xde\x12\xd7\x75\x1b\ -\x3b\xd9\x5d\xef\xcc\x3c\x8f\x64\xd1\x58\x42\xb2\xcf\xce\xbc\x73\ -\x66\xd6\x4e\x12\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa0\ -\x22\x9d\x12\xb0\x8b\xde\xc0\x41\xc0\x30\x47\xb8\x18\x3c\xec\xea\ -\x8e\x12\x70\x9d\x67\xdb\xc3\xa5\xcf\xd5\x03\x04\x0c\xb3\x37\x39\ -\xd8\x22\xc1\xfe\xe9\xd1\x7d\xf0\x69\xd0\xc1\x70\xfb\x55\x68\xf3\ -\xe9\xa3\xe1\x01\x30\x6a\x83\x73\x94\xe4\x5e\x9c\xc9\x00\x13\x38\ -\xce\xbb\x87\xbe\x42\x06\x18\xbd\x93\x59\x7f\xdc\x55\x12\x9c\xc1\ -\x30\x96\xf5\x33\x99\xb3\xb5\xae\x06\x83\x02\x46\xf3\xb6\x73\x79\ -\x65\x9c\xcd\xd6\x39\x2e\xb6\xce\x2e\x3c\x53\x78\x9d\xf7\xef\x28\ -\x19\x6b\xd3\x86\xcb\x22\x6b\x6c\x8b\xc4\x14\x8e\xd7\xfe\xfd\x32\ -\xc9\x79\x92\xef\x95\x65\xd2\x70\x81\x26\x27\xc1\xc3\x24\x8f\xe3\ -\xee\xd2\xd8\x75\x75\xd7\x0e\x92\xfc\x60\x32\x4c\x1e\x30\x60\x42\ -\xac\x3d\x4e\x95\xa4\x8d\x70\x71\x06\xc3\x1c\xde\x1e\x3e\xbe\x1e\ -\xb6\x4c\xa7\xc3\xe4\x38\x53\x9a\xbd\xc3\x05\xf8\x80\x2f\x92\x7c\ -\xb3\xb1\x02\x7f\xa6\x2c\x37\xea\x5c\x84\x0d\xec\x30\x51\xfe\x8c\ -\x4f\xfd\x0a\x17\x98\x60\xc2\xfc\x33\x84\xcb\x03\x93\x46\xb8\xc0\ -\xd8\x13\xe7\x74\xd8\x22\x99\x3c\x95\x86\x8b\x4f\x57\xb2\x84\x49\ -\x64\x5c\xee\x56\x9b\xe2\xea\xe2\x2e\x12\x4b\x5b\xe4\xce\xb2\x3a\ -\x0c\x16\x2e\x42\x17\x46\x9d\x50\x0f\x73\x75\x1e\xd3\x37\x5e\x8b\ -\x2a\xce\x5d\xa4\xe2\x72\x3d\x48\xf2\x79\x56\xe7\x14\xbf\x35\xf2\ -\x9e\xef\xe6\xdd\x6f\x61\xb7\x38\x46\x6d\x19\x99\x7d\x15\x6b\x79\ -\xf5\x3e\x6d\xf8\xbd\x17\x7f\xed\x9d\xc1\xb0\xd4\xae\xfa\xdf\xac\ -\xbe\x85\xfd\xb0\x91\x90\xd5\xb9\xc0\x8c\x7e\x4c\xf2\x55\x43\x9d\ -\x5c\x95\x9f\x75\x91\x90\xcb\x75\x9e\xd5\xef\x52\x49\xde\x3f\x97\ -\x68\x69\xcb\xd0\xc2\x98\x75\xc7\x88\x83\xad\x68\xbf\xab\xc3\x3b\ -\xbf\x48\xfc\xeb\xca\x3b\x17\x1f\x36\x64\x16\xcf\x87\xc1\xf6\x4a\ -\x29\xd2\x27\x39\x49\xf2\x49\x65\x93\x50\xb8\xc0\x42\xdc\xaf\x6c\ -\x32\x0a\x17\x58\xf8\xa4\xfc\x49\xb8\x2c\x9b\xdb\xd4\x65\x0c\x42\ -\x56\xba\x8d\x8e\xe6\x45\x81\xf5\x71\x3b\x1a\x01\xb3\x70\xdf\x25\ -\x79\x54\x4a\x8d\x36\x5e\x64\x53\xdb\x22\xc9\x49\xc9\x01\xbc\xf8\ -\xf1\xbc\xf1\x57\xd1\x9a\xbb\x1d\x6d\x8b\x44\x2d\x8b\xe3\xbd\x61\ -\xdb\xb4\xd4\x17\xab\x13\xc5\x16\xa9\xd0\x3a\x7d\x9b\xe4\xd3\xac\ -\x6e\xe9\xf7\x4b\x7b\x71\x5b\xb6\x45\xbd\x8b\xca\xa1\xfd\x25\x60\ -\x76\x76\x96\xe5\x9e\x6d\x6c\x0d\x17\x17\x15\xca\xed\xf8\xfa\xac\ -\xbe\x6a\xb1\xd8\x70\x69\x25\x60\x9c\xc1\x50\x8b\xf5\x33\x99\xc7\ -\x0b\xd8\x2e\xf5\x5b\x9e\xe8\xb6\xbd\x58\x38\x84\xe7\xb6\x48\x37\ -\x72\x9a\xe4\x78\x01\xdb\x25\x9f\xd2\x15\xa4\x8b\x6f\xf9\x5d\xa7\ -\x91\xba\x87\x99\x6b\xe8\xdb\xd1\xb6\x48\x45\x84\x7f\x27\x5c\x46\ -\x59\x38\x4f\xb2\xfa\x26\xf6\x1c\x9d\x84\x8e\x53\xb2\xd2\x50\x27\ -\x73\x67\x18\xe7\x6f\x66\x18\xf7\xbe\x06\xa0\x83\x29\x6a\x72\x38\ -\x83\xb9\xfd\x02\xfa\x60\x23\x5c\xa6\xea\x32\x84\x8b\x02\x14\xe5\ -\x65\xae\x7e\x17\xcc\xb9\x72\x8c\x1a\x00\x5f\x26\x39\x4a\xf2\x87\ -\x70\x01\xc6\x0c\x99\xe3\x8c\xff\xb7\x97\xdc\x31\x02\x92\x5c\x1d\ -\xf6\x8e\x15\x08\xc2\xc5\x16\xa9\xf8\x96\xde\x75\x9a\xae\xb6\x19\ -\xba\x9a\x37\xb6\x46\xe3\x73\xc8\x4b\xeb\x0b\xeb\xd1\x30\x0f\xf6\ -\xed\x3e\x84\x8b\x0e\x06\x3e\xea\x64\x08\x97\x97\x7b\xce\x09\xe1\ -\x22\x60\x60\x92\x4e\x44\xb8\xd8\x22\x55\x33\xf0\x1d\x1c\xce\xbf\ -\xc8\xde\xc9\xea\x2e\x93\x70\x41\xc0\x30\x6a\xbd\x1f\x65\x75\x26\ -\xf3\xa1\xba\xbb\x63\x04\xdc\xd8\xf1\x47\x42\x44\xb8\xd8\x22\xc1\ -\xad\xbc\xde\xf2\xdc\x7d\x81\x82\x2d\x12\xa3\xd6\xfe\x24\xe9\x5f\ -\x24\xbf\x5c\x26\xfd\xc5\xf0\xb3\xeb\x81\x80\x61\x14\xaf\x92\x9f\ -\xfb\xd5\xaf\xb8\xec\xfb\x21\x64\x5c\x0b\x60\x14\x97\x6b\xe1\xd2\ -\x27\xfd\xa5\x70\xb9\x11\x67\x30\xb0\xa5\x7b\x7c\xba\xf1\xc4\x59\ -\xf2\x44\x59\xb0\x45\x62\x94\xda\x9f\x0c\xdb\xa2\xa1\x93\xb9\xe8\ -\x57\x9f\xfa\x65\x4f\x3e\x24\x34\x71\x42\xdc\xa2\xe0\xbe\xec\xb8\ -\x8c\xcb\xa6\xf6\x02\xa6\x8c\x70\x51\xf4\xda\xd7\x04\x14\xef\xc0\ -\xe1\xa2\xf0\xc2\xa5\x25\x0e\x79\x77\x18\x75\x7d\x92\x5f\x33\xfb\ -\x61\x88\x33\x98\x85\x85\xcb\x01\xc7\x82\x0e\xa6\xa5\x4e\xa4\xbb\ -\xe1\xff\xb7\x67\xe1\x9d\xc1\x2c\x2c\x5c\x4c\xa0\xfd\x1d\x2b\x81\ -\xf0\x47\xdd\x6d\x91\x60\xfa\xee\x45\xb8\x08\x98\xf2\x97\xc0\x6e\ -\xf7\xc1\xef\x0c\x66\xfe\x1d\x30\x02\x66\xfe\xa0\x78\xb6\xe7\x12\ -\xd7\xe9\xbf\x4b\x0b\x97\x6e\xaa\xb1\x60\x0e\x81\x70\x41\xc0\x80\ -\x70\x11\x30\x8c\x3d\x19\x5c\x27\xe1\x52\x24\x67\x30\x58\x5c\x51\ -\x64\x18\xb1\x7b\x31\xee\x75\x30\x30\xd9\xd6\x08\x1d\x8c\x89\xe1\ -\x3a\x4d\x12\x2e\x6a\xa9\x83\x01\xe1\xa2\x83\x01\xe1\x82\x80\x41\ -\xb8\x70\x08\xbe\x4d\x5d\xc6\x44\x31\x41\x2c\xa2\x45\x72\x06\x43\ -\xcd\xdd\x8b\x70\x91\xee\x60\x6b\x24\x60\x40\xb8\x20\x60\xaa\x9c\ -\x34\xae\x93\x70\x29\x92\x33\x18\x84\x0b\x3a\x18\xd8\x21\x60\x8c\ -\x67\x01\x03\xc2\xa5\x15\x3e\x07\x53\xc6\x04\x32\x79\xae\xdf\x1a\ -\xb1\x40\xce\x60\xa8\x21\x5c\x04\xb0\x2d\x12\x08\x17\x01\x03\xc2\ -\x05\x01\xd3\xd4\x84\xea\xd4\xc2\xd8\x2d\x91\x33\x18\x2c\x8c\xb8\ -\x50\xe8\x5e\x8c\x59\x1d\x0c\x4c\xbd\x35\x42\x07\xc3\xc8\x93\xab\ -\x6b\xfc\xfd\x1b\xaf\x3a\x18\x10\x2e\xb8\x68\x08\x97\xaa\x0a\xb4\ -\xe4\xe2\xb8\x70\x08\x97\xc2\x0b\xb4\xe4\x22\xf9\x2e\x52\x19\x63\ -\xa9\xd5\x49\x26\x5c\x0a\xe7\x0c\x06\x9d\x3f\x3a\x18\xab\x77\x93\ -\x9d\x3f\x06\x31\x8c\x1e\x2e\xc6\x65\x25\xad\x9e\x0b\x59\xc6\x38\ -\xea\x1a\x99\x2b\xc6\x64\x65\x9c\xc1\x20\x5c\xd0\xc1\xa0\xd3\x47\ -\xc0\x80\x70\xe1\x7f\xee\x22\x95\x31\x09\xbb\x4a\xdf\x17\x95\x73\ -\x06\xc3\xa1\xc3\x45\xf7\x62\x8b\x04\xc2\x05\x01\x83\x70\x41\xc0\ -\xb0\xe7\xa4\xec\x2a\x79\x1f\xc6\x5d\x63\x9c\xc1\x60\x51\xc3\xc5\ -\xa6\x8a\xee\xa5\x2b\xf1\x45\x9b\x28\x3a\x98\xda\x66\xe5\x49\x9f\ -\x5c\xf4\xc9\xe5\xf0\xdf\x93\x4a\xb6\x46\xc5\xbe\x68\xf7\xd6\xad\ -\x94\xd5\x3c\x2e\x92\xbe\x5f\x7b\x5c\xd4\xf1\xbe\x8a\x1f\x48\xb4\ -\xbb\x45\xaa\xea\xfa\x5f\x26\x39\x5b\xfb\xf9\xef\x24\xe7\xc6\xd9\ -\xc1\x07\x92\x6d\x92\x2d\x52\x15\x9e\x5e\xf3\xb3\x70\x01\x5a\x3f\ -\x83\xa9\x6e\xaf\x8d\xd5\x05\x26\xd9\x26\x99\x28\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\xc0\xe8\x7c\x40\x91\x9d\xf8\x3b\x23\x08\x18\ -\x66\x09\x17\x83\x87\x5d\xf9\x36\x35\xd7\x7a\xb6\xe3\x73\x20\x60\ -\x00\x5b\x24\x96\xbf\x45\x32\x98\xd0\xc1\x60\x15\xc2\xd8\xa1\xfc\ -\xae\xc6\x60\x42\x07\x03\x08\x18\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\xa8\xda\x7f\xcd\xc0\x5b\x85\xe5\x59\x9f\x86\x00\x00\x00\ -\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x01\x35\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x12\x00\x00\x00\x12\x08\x06\x00\x00\x00\x56\xce\x8e\x57\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdd\x06\x13\ -\x0c\x31\x09\x19\x03\xee\xf2\x00\x00\x00\xb5\x49\x44\x41\x54\x38\ -\xcb\xad\x94\x51\x0b\xc3\x20\x0c\x84\x2f\x99\xdb\x4a\xc7\x60\xff\ -\xff\x8f\xae\x94\x61\xf6\x62\xe0\xcc\x6c\x59\xab\xbe\x54\xd4\x7c\ -\xdc\x5d\xaa\xc0\xa0\x21\xe5\x6b\xbd\x1c\x2d\x93\x4b\x07\xa4\xaa\ -\x55\x00\xaf\x83\xca\xac\xd4\x25\x07\xb8\xc5\xe5\x80\x4d\x3f\x93\ -\x59\x89\x2f\xe8\x9f\x99\x59\xa8\xad\xc6\x83\x20\x7e\xd8\x36\x20\ -\x16\x20\x95\xb5\x4f\x28\x6c\x29\xf3\xf9\x8d\x2c\x89\xaf\x27\xb2\ -\x26\x0d\x58\x54\xa6\x0d\xa5\x99\x15\x69\x47\xeb\x85\x01\x73\xc8\ -\x88\xad\x08\xed\x65\x00\xd7\xa0\x46\x19\xb4\xec\x40\x62\x6e\x2b\ -\xd5\xfd\xfc\xc8\xa9\xa8\xda\xeb\x58\xec\x9c\x2b\x9d\x59\xd1\xb3\ -\xd0\x5b\x4a\xb6\xee\x67\xe6\xf6\x73\x68\xf7\x13\x57\x64\xf2\xcc\ -\x9c\x3e\x01\x78\x9f\xec\x9c\x02\xb0\x61\xcf\xc8\xa8\x77\x0d\x5f\ -\x67\x96\x2b\x7d\x67\x52\x6a\xcf\x00\x00\x00\x00\x49\x45\x4e\x44\ -\xae\x42\x60\x82\ -\x00\x00\x01\x9d\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x12\x00\x00\x00\x12\x08\x06\x00\x00\x00\x56\xce\x8e\x57\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdd\x06\x13\ -\x0c\x0c\x34\x2b\x83\xea\x5d\x00\x00\x01\x1d\x49\x44\x41\x54\x38\ -\xcb\xa5\xd4\x2d\x4b\xc3\x51\x14\x06\xf0\xdf\xfe\x73\xe0\x26\x0e\ -\x35\xfa\x21\x4c\x0b\x16\xc1\x20\x08\x16\xd3\x9a\x20\x66\xab\xa0\ -\x58\x04\xbf\x81\xc1\x20\x62\xb7\x08\x36\x93\x8a\xa0\x22\x1a\x7d\ -\x29\xbe\xe0\x44\x14\x16\xd4\xb0\xcd\x22\xcc\x72\x07\x63\x6e\x6e\ -\xff\xed\x81\x1b\xce\xcb\x7d\xee\x39\xdc\xf3\x9c\x84\xd6\x88\x30\ -\x82\x4c\xb0\xcb\xf8\x40\x55\x0c\xcc\xe1\x0a\x8f\x38\xc2\x31\x9e\ -\x70\x81\x7c\x27\x04\x59\x5c\x06\x82\xa9\x26\xf1\x19\xbc\xe0\xa4\ -\xae\xd2\x3f\x18\xc4\x3d\x96\xdb\x3c\x16\x61\x0d\x77\x48\x37\x4b\ -\x38\xc3\x4a\x8c\xf6\xd7\x71\xd0\xe8\xcc\xa3\x80\x44\x0c\xa2\x14\ -\x8a\x98\xae\x77\x9e\x63\x56\x7c\xcc\xe3\xb0\x66\x8c\xe0\x59\xf7\ -\x28\x20\x1b\xa1\x1f\xaf\x3d\x10\x15\x91\x8e\x82\x91\xec\x81\x28\ -\x59\xfb\xca\x0a\x46\xbb\x24\x49\x61\x18\xe5\x08\x5f\x78\xc0\x42\ -\x17\x44\x8b\xb8\x45\xa9\xe6\x98\xc4\x67\xab\x01\xfb\x47\x05\x25\ -\xe4\x1a\x03\xbb\xd8\x88\x41\xb4\x8d\xad\x66\x81\xbe\x20\xd4\xcd\ -\x20\x97\x56\x18\xc2\x0e\x4e\xeb\x07\x38\xaa\x4b\xf8\xc1\x78\x68\ -\xef\x1d\x4b\x18\x68\x68\x65\x15\x6f\xf8\xc6\x44\x27\x2b\x25\x87\ -\xfd\xb0\x3a\xae\x71\x13\x36\xc2\x1e\xc6\x9a\x5d\x68\xa7\xad\x4c\ -\x38\xd5\x50\x45\xa5\x55\xe2\x2f\xb3\x70\x36\xb1\xc0\x88\x65\x3b\ -\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x0f\xd2\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x01\x18\x00\x00\x00\xa0\x08\x06\x00\x00\x00\xd7\x60\x73\x20\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x01\x1b\ -\x0d\x0c\x00\xfa\xf5\xf2\x8e\x00\x00\x00\x45\x69\x54\x58\x74\x43\ -\x6f\x6d\x6d\x65\x6e\x74\x00\x00\x00\x00\x00\x43\x52\x45\x41\x54\ -\x4f\x52\x3a\x20\x67\x64\x2d\x6a\x70\x65\x67\x20\x76\x31\x2e\x30\ -\x20\x28\x75\x73\x69\x6e\x67\x20\x49\x4a\x47\x20\x4a\x50\x45\x47\ -\x20\x76\x36\x32\x29\x2c\x20\x71\x75\x61\x6c\x69\x74\x79\x20\x3d\ -\x20\x39\x30\x0a\x67\x9d\x47\x40\x00\x00\x0f\x01\x49\x44\x41\x54\ -\x78\xda\xed\x9d\xcf\xab\x25\x47\x15\xc7\xbf\x75\x1d\x13\x9c\x99\ -\x04\x31\x1b\x61\x02\xae\xe4\x0d\x48\x40\x48\x56\xfe\x01\x4e\x36\ -\x42\x98\xc0\x64\x21\x2e\xfc\x1b\x94\x04\x06\xdc\x28\x64\xa5\x59\ -\x8b\xff\x41\x06\x66\x70\x39\xce\x6c\xb2\x72\x35\xb3\x10\x22\xe4\ -\x19\x04\x85\x64\x21\x26\xab\xc9\x3c\x48\x14\xcb\xc5\xbd\x37\xd3\ -\xaf\x5f\x75\x77\xfd\xae\x53\xa7\xbe\x07\x2e\xef\x47\xdf\x77\x5f\ -\x57\x9d\x53\x9f\xfa\x9e\xd3\xd5\xd5\xc6\x5a\x0b\x1a\x8d\x46\x2b\ -\x61\x3b\x76\x01\x8d\x46\x23\x60\x68\x34\x1a\x01\x43\xa3\xd1\x68\ -\x04\x0c\x8d\x46\x23\x60\x68\x34\x1a\x01\x43\xa3\xd1\x68\x04\x0c\ -\x8d\x46\x23\x60\x68\x34\x1a\x01\x43\xa3\xd1\x68\x04\x0c\x8d\x46\ -\x23\x60\x94\xd9\xc3\x87\xc0\xeb\xaf\x03\xc6\x00\x2f\xbe\xb8\xff\ -\xfe\xe1\x43\xf6\x0b\x6d\x18\x33\xbc\x17\xa9\x90\xdd\xbb\x07\xbc\ -\xf9\xa6\xfb\xd8\xdd\xbb\xc0\xcd\x9b\xec\xa3\x79\x30\x1a\x93\xed\ -\xb3\x18\xd7\x04\x8c\x6e\x7b\xed\x35\xe0\xf1\x63\xf7\xb1\x57\x5f\ -\x05\x1e\x3d\x22\x4c\x2a\x1b\x63\x9d\x80\xd1\x63\xcf\x3f\x0f\x7c\ -\xf5\x95\xfb\xd8\x73\xcf\x01\x5f\x7e\x49\xa0\x14\x00\x41\xad\xff\ -\x43\xf3\x33\xd6\x60\x4a\xd9\x2b\xaf\xc4\x1d\x53\x04\x94\xe9\x6b\ -\x6d\x90\xcf\x5f\xa9\x2a\xc5\xf7\xf3\x7c\xcf\x91\x46\xc0\xc8\xb3\ -\xdb\xb7\xe3\x8e\x29\x01\x8b\x2f\x00\x6a\xa5\x46\x21\xc0\xa1\x11\ -\x30\xf2\xed\xe6\x4d\xfc\x18\xc0\x9f\x0e\x3f\x3e\x01\x80\x1b\x37\ -\x80\x07\x0f\xd4\x15\x78\xd7\x54\x40\x0b\xa0\xa4\x02\x87\xaa\x26\ -\x63\x6c\x30\x0f\xad\x53\x77\xd0\xd8\xcf\x5b\x4a\x85\xed\xe9\xad\ -\xfd\x4b\x6d\x27\x60\xc4\x07\xab\xa6\x7e\x5e\x1a\x88\x6c\xa3\x3e\ -\xb8\xa4\x42\xe6\x12\x51\x40\xe3\xa0\x5b\x6e\xd3\xbc\xcd\xc7\x9f\ -\x39\x31\x13\x30\xf2\xd5\xcb\x7c\xc0\x0a\x0e\xda\xa5\xfa\x8a\x76\ -\x5b\x03\x0d\x21\x43\xc0\xf4\xa5\x49\x8d\x11\x07\x99\x51\xc1\xe2\ -\x03\x1a\x15\x6a\xe6\xd8\x86\xe3\x8f\xc8\xdb\x16\x02\x26\x63\xbe\ -\x6a\xad\x9e\xda\xcb\x68\x35\x88\x10\xd0\xa8\x49\x9b\x5c\x93\x07\ -\x4c\x56\xc8\xf0\x32\x75\x26\xb8\x6c\x1d\xeb\x1d\x2e\x92\x2e\x33\ -\x4b\x80\xcc\xd2\xe5\x6d\x1a\x01\x53\x5d\x56\xbb\xec\xad\x4e\xe0\ -\x42\xb0\x84\x81\x86\x90\x21\x60\xe2\x64\xcb\xe1\x65\xb1\x7f\xa5\ -\xda\x9d\x09\x64\x3e\x3e\x7c\x6f\x9a\x36\xd1\xa8\xbe\xb4\x5e\x73\ -\x22\xe9\x65\x91\xde\x5b\xde\xed\x4b\x88\x2b\x06\x51\x7c\x4e\xe4\ -\xce\x55\xb7\x17\xd7\xb9\x16\xe0\xb5\x5c\x94\xc7\x42\xee\x98\xfd\ -\x68\x8c\xc1\x2d\x00\xef\xe7\xa2\x09\x15\x4c\x69\xfb\x6f\x74\x60\ -\x4d\xdf\x5f\x73\xf6\x63\x4a\x54\x29\x65\x32\xe6\x04\xc6\xdc\x85\ -\x31\x4f\x0f\x5f\x4f\x24\xc0\xf0\xce\x61\x4a\x34\x05\xe0\x42\x05\ -\x93\x5d\xc1\xfc\x05\xc0\x0f\x83\xd5\x4b\xc8\xf1\xd2\x70\xa1\x85\ -\x29\x14\x2f\xf0\x00\x27\x00\x3e\x72\x1c\xba\x0e\x6b\x4f\x35\x2b\ -\x2d\x2a\x98\x10\xc7\x00\xd6\x60\xed\x1a\xde\x4f\x56\x9d\xe8\x03\ -\x8f\x5a\x4a\x86\x70\x59\xef\x1b\xd7\x2b\xc1\xde\x75\xfd\xf2\x1e\ -\xf0\x91\x31\xc6\x6a\x85\x0b\x15\x4c\x80\x82\x99\x83\xc5\x1e\x4a\ -\xbe\x17\x10\xe4\x09\x8c\xad\x6d\x03\x4a\x3a\x9f\x70\x49\x53\x27\ -\x21\xfd\x65\xf6\x51\xf2\x14\xc0\xe5\xf9\xb1\x33\x00\x57\x2e\x7e\ -\xb6\xa9\xd9\xce\xd2\xbe\xa7\x82\xd9\x8e\x26\xb8\x54\x8b\x39\xf8\ -\x66\xef\x1f\x83\xa5\x6b\x40\x47\x87\x86\x40\xa3\xa4\x8a\x21\x5c\ -\xdc\x4a\x65\xad\xa6\x92\xb2\xe5\xc4\xe1\xfd\xf7\x5d\xc7\xee\xbb\ -\xcf\xc9\x1e\x5f\x52\x52\x3c\x2a\x98\xb2\x41\x68\x1d\x12\xc5\x2e\ -\xc9\x16\x1f\x38\xf8\xf6\x79\x6e\x25\x43\xb8\x6c\x0f\xb2\x22\x7d\ -\xb2\x2f\xe8\xae\xd6\x60\xb6\x80\x92\xaa\x6c\x5a\x5d\xe1\xa2\x82\ -\x89\x12\x35\xe7\x9c\x6d\x43\x82\x35\xc4\xa9\x39\x95\xcc\xe8\x70\ -\xf1\x55\x2a\x85\x02\xe6\x14\xc0\xf5\x7d\xd9\x05\x67\x87\xaf\xe7\ -\x0a\xbc\xd6\x5a\x73\x7c\x61\x45\xd9\xf4\x04\x17\x2a\x98\x38\xf5\ -\x32\x5d\xbb\x62\x7d\xfa\x32\x75\x70\xa7\x2a\x99\x91\xe1\x22\xed\ -\x9e\xaa\x50\x5f\x2c\x41\x25\x44\xd1\xb4\xf4\x3f\x01\x13\x08\x18\ -\x87\xaa\xb0\x6b\x4e\xcf\xb5\x4b\x5a\x2c\x64\x46\x85\x8b\xe4\x9b\ -\x35\x63\x7c\x12\x0b\x9a\xd6\x8b\xff\x98\x22\x05\xaa\x17\xd7\x5b\ -\x97\x82\x60\x0e\x85\x94\xfb\x56\x72\xa4\x4b\x23\xc3\x45\xda\x9e\ -\xc0\x31\x69\xf9\xc2\x04\x66\xa5\xc2\x85\x80\x49\x08\x8c\x99\xf3\ -\x8c\x8f\xc3\x5d\x0e\x0e\x82\x4c\xe0\xdf\x69\xdf\x13\xd8\xa7\xce\ -\x22\x75\x65\x72\xec\x84\xe1\x02\x8d\xab\x3e\x23\xe5\xb6\x05\x02\ -\x26\x4d\xbd\x38\xa5\xea\xc1\xe1\x8b\x8e\x9d\x07\xbd\xd7\x42\x2e\ -\xc7\x9e\x23\x6b\x7f\x33\xda\x5d\xbd\xbd\xdf\x4f\x15\xea\xaf\x50\ -\x35\xd3\xaa\x2f\x08\x98\x0c\xb2\xf6\x78\xcc\xf7\xea\x52\xaa\x9a\ -\xd9\x82\xcc\x48\x75\x97\x9e\x54\x4b\x4e\x35\x1b\xa2\x66\x5a\x1a\ -\x01\x13\xa1\x5e\x36\x0a\xb7\xe6\xfc\x8f\x36\x3d\xc8\x8c\x09\x86\ -\xcc\x28\x70\xe9\xbd\xbd\x39\xce\x77\xa1\xd0\x6b\x25\xf4\x09\x01\ -\x93\x39\x08\x0e\x41\xef\x5d\x93\xd9\x4c\x99\xa6\x83\xc8\x91\x6a\ -\xcd\x07\xdb\x28\x75\x17\x4d\x77\x81\x67\x2a\xe0\xbb\x96\x93\xdb\ -\xd6\x7d\x42\xc0\x24\xaa\x97\x15\x07\x06\x41\x66\x53\xcd\x34\xbe\ -\x39\x52\x3a\x5c\x68\xee\xb8\x6d\x9d\x2e\x11\x30\xf9\xd5\xcb\x74\ -\x46\x4d\x87\xcc\x0a\x5c\xd6\xce\x51\xef\x03\xc2\x74\xc2\x25\x75\ -\xa2\x58\xba\xaa\xd9\x1a\x32\x04\x4c\x19\xf5\xe2\xcc\x8f\x7d\x0b\ -\x70\x16\x17\x2f\x49\x73\xaf\xd7\xf1\x8a\xd7\x29\xef\x75\x15\x7f\ -\x09\x18\x45\xea\x25\xda\xd9\x2b\x9f\x33\x32\x64\x46\x80\x4b\x8e\ -\xdb\x40\xa6\x9f\x23\x01\x32\x04\x0c\xa2\xd7\xbd\x78\xc1\x25\x08\ -\x32\x8e\x9a\x8b\x0f\x64\x7c\x7f\x47\xb8\xe8\x4f\x95\x92\x26\x37\ -\x02\xa6\xdd\x6c\x92\x63\xc0\xce\xd7\x2d\x9c\x73\xf6\xc6\xd5\x22\ -\xdf\x85\x79\xda\x8a\xbe\x2c\xe8\xa6\xa7\x51\x2d\x21\xb3\xa3\x93\ -\xc2\xd5\x8b\x6f\x6a\xe4\xe3\x70\x63\x8c\x85\xc7\xd5\xa2\x25\xe8\ -\xb9\xfe\xbf\x16\xc8\x8c\x0a\x17\x1f\xff\x85\xae\x01\x6a\x05\x19\ -\x2a\x98\x08\xf5\x92\x7b\x71\xd4\xd7\x3b\xe6\xf9\x15\x8d\xab\x48\ -\x6d\xc2\xa5\xef\x98\x95\x02\x99\xa1\x01\xd3\x42\xbd\x6c\x42\xc6\ -\x33\x98\x6c\x20\x8c\x7a\x56\x32\x23\xc2\x65\xcd\x77\x29\xf0\x2d\ -\xbd\xe7\x2f\x01\x93\x18\xcc\x59\x83\x7d\xbf\x21\x74\xf0\x5a\x99\ -\x90\x9c\xbc\x47\xc8\x8c\x76\x17\x78\x6d\x65\x17\xb3\x3e\x8b\x80\ -\x29\xa4\x5e\x7c\x72\xe0\xa8\x41\x70\xfe\xef\x09\x19\x05\x4a\xab\ -\x76\xaa\x5e\x68\x2c\x10\x30\xad\xa5\x78\x4e\xb8\xc0\xb1\x66\xc1\ -\xc7\xe1\x6b\x9b\x59\x6d\x3d\x67\xba\x97\x41\x4c\xf5\xe2\x3f\x71\ -\xa4\xa8\x18\x2a\x18\x21\xea\x25\x4b\xc0\xaf\x5f\x8e\x4e\x52\x32\ -\x21\xc5\x69\x89\x90\x61\x6a\x54\x17\xbe\x35\x52\x25\x2a\x98\x9a\ -\xea\xc5\xef\xe6\xc5\x4d\xa7\x6f\x6d\x66\xd5\x23\x64\x98\x1a\xf9\ -\xc7\x65\x29\xf8\x96\x80\xcc\x70\x80\x89\xbd\x72\x94\xec\x54\xcf\ -\xb5\x2e\xb9\x94\xcc\x5a\xca\x24\x5d\xc9\x50\xbd\xd4\x03\x6f\xe9\ -\x54\x89\x0a\x06\xdb\x0f\xa1\x4f\x56\x2f\x01\x70\x09\x81\x4c\xca\ -\x66\x56\x92\x20\xc3\xd4\xa8\x2d\x7c\x4b\xa6\x4a\x43\x01\x26\x65\ -\xdd\x4b\x4d\xb8\xe4\x74\x3c\x6f\x98\xa4\x7a\x69\x69\xc3\x2b\x18\ -\x1f\xf5\x12\x3d\x6b\x24\xc0\x65\x09\x32\xb1\x37\xc0\xb9\x52\x26\ -\x09\x2a\x86\xea\x25\x0c\x2e\xa5\xfc\x54\x4a\xc5\xec\x06\x72\x58\ -\x5d\xf5\x92\x01\x2e\x0b\x79\xb2\x4d\xf8\x9c\x0b\xed\x1a\x71\x47\ -\x3c\x2a\x17\x2a\x98\xfe\xd5\x4b\xc1\x19\x06\x09\x7b\xad\x4a\x82\ -\x0c\xd5\x4b\x9c\x02\xad\x11\x63\xb9\x54\xcc\x10\x80\xe9\x59\xbd\ -\x94\x08\x00\x57\xca\xa4\x75\x06\xed\x5d\xbd\xf4\x0e\xde\x61\x15\ -\x8c\x0d\x2f\xb8\x36\x85\xcb\x24\xf8\xb2\xcd\x32\x6b\x6d\xaa\x09\ -\x19\xaa\x97\x30\xb8\x94\xf4\x4d\xee\xcb\xd6\xbb\x01\x9c\xe6\xad\ -\x5e\x92\x1d\x57\x10\x2e\xf3\xff\xd4\x3b\x64\xa8\x92\xe4\xf7\x47\ -\x8e\x34\x69\x48\x05\x53\x44\xbd\xd4\x83\x4b\xf4\x13\x0b\x62\x72\ -\x7c\x82\x40\x4e\x8c\xf6\xa8\xf4\x54\x03\xa6\x9a\x7a\xa9\x08\x97\ -\x25\x39\x9b\x63\xb6\x69\x24\xc9\xa9\x5e\x84\xd5\x5d\x72\xa6\x49\ -\xc3\x29\x98\xec\xea\xa5\x11\x5c\x7a\x86\x0c\x55\x91\x5c\xb8\xe4\ -\x4e\x93\x76\x8a\x1d\x97\xb4\x5b\x9d\x34\xe5\xe2\xfb\x3c\xec\x5c\ -\x90\xf1\xbd\x7d\x82\xd6\x06\xb4\xbd\xf8\x62\x28\x05\x13\x3a\x3b\ -\xaf\xce\x26\x8d\x95\x4b\x69\xc8\x2c\xb5\x3f\x67\x60\x33\x3d\x92\ -\xdb\x1f\xb9\xd2\x24\x95\x80\xc9\xa1\x5e\xa4\xc2\xc5\x77\xe7\x78\ -\xa9\x90\xa1\x0a\x4a\x4b\x8d\x5a\x40\x28\x25\x96\x86\x51\x30\xd9\ -\x6a\x0b\xc2\x94\x8b\x06\x25\x43\xb8\xe8\x55\x72\x3b\x85\xce\x2b\ -\xa7\x5e\x84\xc3\xa5\x34\x64\x72\xde\x99\xcd\xf4\xa8\x8b\x49\x39\ -\x79\x16\xd9\x0d\xd2\x51\x43\x07\x47\xce\xbb\x63\xb9\xfd\x03\xd5\ -\xcb\xb0\x80\x09\x55\x2f\x41\x1b\x29\x77\xa2\x5e\x08\x19\xc2\x85\ -\x80\x11\xaa\x5e\x34\xc1\x65\x09\x32\xa5\xea\x32\x6b\xcf\xcb\xa6\ -\x8d\xfb\xa4\xca\x9d\x22\x07\x26\xab\x17\x6d\x70\x59\xca\xa5\x5b\ -\x15\x7f\x09\x20\x35\xe3\x8b\x0a\x26\x8b\x7a\x51\x00\x17\x69\x90\ -\xa1\x7a\x19\xab\x26\xb8\x53\xe2\xc0\xfc\xab\x76\x15\xc1\x65\x0a\ -\x99\x9a\x97\xb1\xa3\x16\x30\x12\x2e\xa2\x27\x26\x2a\x18\x0f\x47\ -\x6e\x3a\x5d\x21\x5c\x96\x82\xa6\x24\x64\x46\x57\x33\x23\x2b\x17\ -\x35\x80\xc9\xae\x5e\x94\xc3\x85\x90\xa1\x11\x30\xad\xd4\xcb\x20\ -\x70\x21\x64\xa8\x5e\x08\x98\xda\xea\x65\x30\xb8\xb4\x80\x0c\xe1\ -\x42\xc0\x8c\xe9\xf8\x41\xe1\xd2\x0a\x32\x9a\xd5\x0c\xe1\xa2\x04\ -\x30\x4b\xea\x85\xf7\xb8\xf4\x03\x19\xed\x69\x13\x63\x91\x4f\x15\ -\xe8\x4e\xbd\xd4\xda\x51\xbe\x56\xba\xa4\x09\x32\x35\xda\xd2\x5b\ -\x7f\xed\x3a\x75\x64\xb0\x7a\x71\x3a\x66\xf0\xd4\xa8\x16\x64\xb6\ -\xa0\xa3\x01\x32\x4c\x8d\x06\x56\x30\x1a\xe0\x62\x95\x6d\x26\xbe\ -\xf4\xbc\x6c\xc2\x45\x57\xfa\xb5\xeb\xd0\x99\xc9\xb5\x17\x7b\xde\ -\x53\x9c\x66\x2a\x43\x66\xed\x69\x99\xbd\xab\x19\x2a\x97\xc1\x14\ -\xcc\x85\xd9\x85\x70\x11\x91\x2e\x69\x81\x0c\x17\x11\x2a\x02\x4c\ -\xaa\x7a\xd1\x02\x97\xca\x8f\x75\xad\x06\x99\xde\x52\xa6\xda\xa9\ -\x51\x8b\xfe\xe0\x63\x4b\x7c\x03\x98\xca\x45\x3c\x64\x7a\x52\x33\ -\x2c\xea\x2a\x03\x4c\xa8\x7a\xd1\x08\x97\x96\x41\x9c\x03\x32\xd3\ -\xf3\x5f\xaa\xc9\xf4\x98\x32\x59\x41\x4f\x96\xa8\xe1\x7f\x2a\x98\ -\xe5\x5e\xe2\x94\x22\x48\xc9\x6c\x41\x46\x6a\xca\xc4\xba\x8b\x32\ -\xc0\xe4\x50\x2f\xda\x42\xa2\x55\x90\xd7\x84\x8c\x44\x35\xd3\x2a\ -\x35\xea\x15\x6a\x63\xac\x83\x61\x8e\x4c\xc8\x74\x0c\x17\x2a\x18\ -\xe1\xea\x45\xd9\xe0\x56\x01\x99\xb5\xc1\xba\x04\x19\x49\x29\x53\ -\x2b\x3f\xd4\xfc\xbf\x39\x26\x0f\x6d\x8f\x2d\x51\x9f\x1a\x8d\xac\ -\x64\x5a\xaa\x19\xd6\x5d\x14\x02\x26\x75\xdd\x8b\x51\x2e\x65\x25\ -\x04\x7d\x28\x64\xb6\x7c\x21\x11\x32\xad\x53\xa3\xd6\x7e\x4e\xd9\ -\x97\x77\xa7\x71\xb0\x59\x2a\x97\x2e\x94\x8c\x0f\x40\xd6\xde\xd3\ -\x22\x65\x1a\xa5\xee\x92\x6b\xbd\xd3\xae\x93\x06\x06\xa9\x17\xa3\ -\x3c\x20\x7c\x06\x60\xef\xe9\x52\xec\x80\xcf\xdd\x1f\x6b\xb7\x34\ -\x54\x9f\x38\x3b\x8c\x65\x2d\x8f\x2d\xa1\x8c\xe8\x08\x32\x3e\x03\ -\x25\x04\xa2\xa5\x20\xc3\xb8\x52\xfa\xd8\x92\x20\xf5\xb2\x12\x04\ -\x56\xf7\x53\x01\x54\xa4\x4b\xc6\xd3\x7f\x3e\x90\xc9\x99\x32\x49\ -\xbb\x24\xdd\xdb\xd5\x23\x1d\x0a\xc6\x18\xd6\x5a\x04\xce\xb4\x39\ -\xd3\xa5\xd0\x74\xb0\x84\x9a\x69\x05\x17\x0d\x0a\x6a\x27\x70\xb0\ -\xf8\xa9\x97\x19\x5c\xb8\xe8\xa9\x2f\x25\x13\xfb\x58\xdf\x1a\x90\ -\xe9\xbd\xee\x21\x25\x3d\xea\x57\xc1\x38\x02\x66\xc4\x55\x96\x12\ -\x8b\xbd\x31\x4a\x26\x14\x1a\xbe\xef\x8f\x49\x99\x24\xde\xef\xd4\ -\x6b\x7a\x24\x0e\x30\x5e\xea\xe5\xd0\xf1\x54\x2f\x7d\x2a\x19\x63\ -\xcc\xaf\x26\x87\xbf\x59\x1a\xa8\x4b\x6a\xc6\x18\xd7\x8b\xb7\x02\ -\xa8\x4f\x91\x72\xcf\xec\x54\x31\xe2\xa4\xf6\xaf\x8d\x31\xbf\x38\ -\xfc\xfe\x3f\xc7\x18\x8c\x01\x46\x3c\x64\xbe\x2d\x3a\x86\x24\xa8\ -\x97\x1c\xe9\x91\x28\xc0\xa4\xa8\x17\x5e\x4e\xec\x2e\x9f\xff\xed\ -\xc4\xdf\x3f\x42\xe0\xba\xc8\x58\xc8\x3c\xfb\xbb\xdf\xb8\x22\x90\ -\xca\xa5\x80\x5d\xea\xe6\x4c\x99\x1a\xad\x0e\x9e\x67\xd2\xdf\x88\ -\xbf\x84\x7d\x84\x8b\x31\xc6\x5a\x6b\x8d\x31\xe6\x32\x80\xb3\x90\ -\x73\x8f\x6d\xf3\xfe\xef\xfe\xb9\x08\x17\xe0\x1f\xc3\xd7\x5e\x72\ -\xa9\x17\x31\x0a\x66\x53\xbd\xac\xcc\x52\xcc\x9b\xb7\xfb\x44\xb2\ -\x9a\x39\xf8\xfe\x67\x00\xbe\x53\x43\xc9\xec\xed\x7b\x0b\x70\xb1\ -\xb3\x63\xf4\x9b\x9a\x14\x69\x4b\xb9\x0c\xa5\x5e\xe6\xd5\xc7\x88\ -\x3a\x43\x67\x29\xd3\xef\x01\xfc\x1c\xc0\xb7\x42\x07\x59\x0f\x35\ -\xa8\x58\x55\xda\xbb\x7a\x01\x00\xd3\x3a\x30\x57\xd5\xcb\xec\xe1\ -\x68\x2e\xf9\xa8\x6e\xcd\xc2\xd2\x20\xf1\xbd\x83\xbc\xb3\xfe\x98\ -\xf9\xff\x16\x80\x0f\x00\xfc\x3b\xf4\xdc\x43\xda\xfd\xec\xad\x73\ -\xf5\x12\xd4\xd5\xea\x52\xa3\x12\x80\xd9\x75\x31\xd0\x3c\xe0\x22\ -\x4a\x71\xb0\xe8\x1c\xab\x64\xee\x00\xf8\x25\x80\xcb\x29\x33\xbe\ -\x5f\x5c\xb8\xe1\x42\x5f\x28\x4a\x91\x16\xd5\xcb\x6c\xe6\x16\x3d\ -\x2b\x2f\x05\x73\xbb\x3d\x73\xbb\x4b\x19\x66\x81\xfd\x36\x80\x9f\ -\x1a\x63\xbe\x51\xd0\x69\x8b\x70\x19\x55\xbd\x94\x80\x8b\x6c\x05\ -\xb3\xe1\x6d\x8d\xc5\xdd\x12\x3c\xe8\x14\x32\x7f\x00\xf0\xb6\x31\ -\xe6\xa5\xd2\x70\xb5\xf6\xfc\xab\x25\x5c\x1a\x4e\xee\xe5\xfe\x4f\ -\xc3\x1b\xb9\xd6\xd5\xcb\x46\x8d\x45\x0c\x60\xd6\x02\x24\xe0\x9c\ -\xce\x65\x84\xb3\x8b\x29\x06\x36\x38\xf8\x7b\x05\xf0\x3c\x2e\x62\ -\xce\xdb\xf7\xb1\xb4\x02\xea\x8f\xcd\x7c\x54\x43\xbd\xc8\x55\x30\ -\x9b\x45\x3a\xdd\x97\xa6\x0d\xec\xb9\x57\x6a\xaa\xd4\x71\x1d\xc0\ -\xfe\x2b\xf1\x8a\xda\x34\x56\x24\xa7\xda\x1a\xe1\xd2\x0c\x30\x5e\ -\xb5\x17\xf0\xae\xd6\x9c\x41\xdb\xd3\x25\xdc\x69\xd0\x7f\x77\x1e\ -\x16\x81\x90\xb9\x75\xf8\x9b\x8f\x8d\xd9\x7f\x2f\x24\x96\xb4\xdc\ -\xcc\xb8\x65\x97\x04\x46\x57\x5f\x33\xf5\xfc\x72\x7a\x64\x3b\x6a\ -\x05\x75\x2f\xa0\x3e\xf4\xaa\x3d\xcc\x40\xd6\x46\x6c\xb3\x3c\x6d\ -\xe9\xf7\x01\xbc\x2f\xc8\x0f\x12\xea\x2e\xa5\xd5\x4b\x93\x1a\xcc\ -\xaa\x7a\x59\x98\x71\x45\xd6\x5e\xb2\xf7\x4b\x56\xee\xf6\x9f\x52\ -\x3e\xbb\x35\xc4\x4e\xa1\x13\xd4\x19\x89\x6b\x8a\x34\xd5\x5d\x5a\ -\xc0\xa5\x59\x8a\xb4\xe5\x74\xa6\x46\x65\xf3\xfb\xae\xd2\xa5\xc9\ -\x24\x64\x00\x7b\xaf\xeb\x49\x64\x2c\xb8\x54\x57\x30\xbe\xbb\xd5\ -\x8d\xa6\x5e\x4a\x2b\x98\x2e\xfb\xcf\xcc\xaf\xa4\x21\xfc\xea\x92\ -\x20\x05\x33\x22\x5c\x64\x29\x98\xc1\xd5\xcb\x5a\x53\x73\x75\x43\ -\x57\x4a\x66\x76\xae\xb3\x1a\x8c\xf5\x2a\x56\xba\x3a\x8e\x70\xa9\ -\xdb\xf6\x46\x8d\x0d\x56\x2f\x4c\x9d\xfa\x0f\x78\x2d\x83\x86\x70\ -\xe9\x54\xc1\xf8\xc2\x85\x36\x90\x92\xb9\x78\xee\x66\x6d\x20\x11\ -\x2e\xb2\x20\xbc\x6b\xd0\x60\xa7\x7a\xa1\x3a\x21\x64\x34\x41\x86\ -\x70\x11\xa8\x60\x34\x4a\xfa\x43\x03\x44\xde\x6d\xad\x0d\x32\x52\ -\x40\xd3\x22\x5e\x5d\xed\x97\x90\x3e\xee\x6a\x34\x9c\xea\xa5\x2f\ -\x25\xd3\xe9\x0d\x92\xcd\xd5\x8c\xab\xef\x6a\xc1\xc5\xa7\x6f\x5a\ -\xd8\xa5\xc2\x2d\x2f\x3e\x20\x68\x79\xfa\x74\x3e\x30\x7a\x59\xf5\ -\x3b\xdf\xe7\x77\xfa\x7d\xd5\xf5\x1e\x8e\x58\x2f\xdd\x7f\x92\xc1\ -\x52\x45\xc1\xcc\xd7\x2e\xc4\xa8\x17\x16\x77\xeb\x41\x46\x53\xca\ -\x54\x53\xcd\xb8\x54\x0b\xe1\x52\x5a\xc1\x04\x3e\x45\x8f\xca\x44\ -\xa6\x9a\x39\x7e\x4f\x35\x43\xd5\x12\x75\xbe\x45\x3a\x64\xff\xdc\ -\xe8\xec\xea\x85\x10\xaa\x5b\x4f\xe8\x39\x45\x5d\x52\x2f\x39\x06\ -\x63\x8b\xbe\x29\xd9\x9e\xbe\x52\x24\x63\x4e\x00\xdc\x7d\x0a\xe0\ -\x2e\x80\x13\xaa\x17\x35\x29\x53\x4f\x69\x93\xb5\xd6\x2c\xa5\x4d\ -\x29\xa9\xd3\x92\x6a\x29\x15\xc3\x4b\xe7\xbb\xd4\x3e\xdd\x0a\xc6\ -\x98\x1f\x00\xf8\xd0\x71\xe4\xba\x01\x4e\x43\xaf\x1c\x11\x42\xb2\ -\x15\x4d\xcf\x2b\x80\x43\x55\x40\xcd\xf6\xa7\x9e\xab\x4e\x05\xb3\ -\x77\xc0\x1f\x17\x8e\xbe\x1b\x0a\x0e\x16\x77\xe5\x29\x1a\x97\x8f\ -\x7a\x57\x34\x53\x95\xe0\x1a\xd8\x4b\x6d\xcc\x0d\x97\xb5\x73\xe8\ -\x49\xb1\x94\x56\x30\xff\x83\x7b\x63\xa0\x33\x58\x7b\x25\x05\x30\ -\x54\x30\xb2\xd5\x4c\x8f\x3e\xf2\x48\x95\x4c\xc9\x36\x6e\xfd\xff\ -\x1e\x81\x52\x4e\xc1\xec\xed\xef\x0b\xbf\xbf\x4f\xb8\xe8\x52\x33\ -\x6b\x8a\xa6\x37\x55\xb3\x32\x90\xed\xfc\x15\x53\xbf\x99\xaa\x13\ -\x5f\xa5\xa2\x01\x2e\x95\x6b\x30\xf6\x74\x3a\x21\xd8\xc1\x1e\x49\ -\x32\xa2\xa2\xe9\xc5\x77\x8e\xf3\xaf\x7a\xd2\x5a\x60\x52\x5c\xc1\ -\x18\xd8\xbf\x02\xb8\x0e\xe0\x1e\x80\xb3\xc3\xd7\xeb\x00\x4e\x2d\ -\x0c\xfe\x06\x7c\xbd\xf1\x32\xe1\xa2\x4f\xd1\x6c\xa9\x1a\x29\xca\ -\x66\xeb\x9c\xa6\x2a\x22\xa7\xa2\x28\xf5\xb9\xc3\x28\x18\xe7\xde\ -\xd7\x08\xdb\x55\x8c\x80\xd1\xaf\x6a\x6a\xfb\x57\xd2\xb9\x8c\x66\ -\x97\x24\x9f\x1c\x1d\xde\xb7\xaa\xf1\x19\xe4\xb9\xaf\xd0\x84\xa8\ -\x24\xc6\xd7\x60\x80\xe1\xa5\x69\x02\xa7\x64\x1c\x10\x28\x54\x30\ -\xb4\x41\x81\x93\x1b\x2c\x84\x89\x10\xd1\x50\xba\x06\x03\x38\xea\ -\x30\xbc\xef\x88\x46\xa3\x82\x09\x9f\x35\xdc\x90\x59\x7d\x78\xfb\ -\xe7\x9f\x03\xef\xbd\x07\x0b\xe0\xc9\xd5\xab\xf8\xf3\xc9\x09\x7e\ -\x47\xb8\xd0\x68\x54\x30\xc9\xf6\xe9\xa7\xc0\xcb\x2f\xbb\x8f\x7d\ -\xf2\x09\x70\xed\x1a\x3d\x44\xa3\x75\x6c\x6d\xf7\xe4\x7d\xe7\x9d\ -\xb8\x63\x34\x1a\x8d\x0a\x66\xd3\xae\x5e\x05\x9e\x3e\x75\x1f\xbb\ -\x72\x05\xf8\xe2\x0b\x7a\x88\x46\xa3\x82\x89\xb4\x37\xde\x88\x3b\ -\x46\xa3\xd1\xa8\x60\x36\x8d\x35\x18\x1a\x8d\x0a\xa6\x98\x5d\xbb\ -\x86\xc7\x0f\x1e\xe0\xd1\x8d\x1b\x00\x80\x27\x2f\xbc\x00\xdc\xbe\ -\x0d\x7c\xf6\x19\xe1\x42\xa3\x51\xc1\xd0\x68\x34\x9a\x54\x05\x43\ -\xa3\xd1\x08\x18\x1a\x8d\x46\x23\x60\x68\x34\x1a\x01\x43\xa3\xd1\ -\x08\x18\x1a\x8d\x46\x23\x60\x68\x34\x9a\x4c\xfb\x3f\x25\xe7\xf0\ -\xbf\xc2\xb2\xb9\xae\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\ -\x82\ -\x00\x00\x04\x97\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x01\x18\x00\x00\x00\xa0\x08\x06\x00\x00\x00\xd7\x60\x73\x20\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x01\x18\ -\x08\x2d\x07\xfc\x70\x1f\xcb\x00\x00\x00\x45\x69\x54\x58\x74\x43\ -\x6f\x6d\x6d\x65\x6e\x74\x00\x00\x00\x00\x00\x43\x52\x45\x41\x54\ -\x4f\x52\x3a\x20\x67\x64\x2d\x6a\x70\x65\x67\x20\x76\x31\x2e\x30\ -\x20\x28\x75\x73\x69\x6e\x67\x20\x49\x4a\x47\x20\x4a\x50\x45\x47\ -\x20\x76\x36\x32\x29\x2c\x20\x71\x75\x61\x6c\x69\x74\x79\x20\x3d\ -\x20\x39\x30\x0a\x67\x9d\x47\x40\x00\x00\x03\xc6\x49\x44\x41\x54\ -\x78\xda\xed\xdc\x5b\x6e\xdb\x40\x0c\x05\x50\xaa\xf0\x1a\xb5\xf8\ -\xd9\x0c\xf3\x93\x02\x46\xa2\xda\x1a\x5b\xa3\x6a\xcc\x73\x00\x03\ -\x45\xd1\x3a\x31\x49\x5d\x71\xd4\x47\x04\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\xd5\x64\xc4\x9a\x11\x99\x11\x2d\x23\x56\ -\x15\x01\x8e\x0e\x97\xfb\x97\x90\x01\x7a\x72\x64\xfb\xd5\x7e\x87\ -\x4b\xb6\x07\xbf\x5e\x29\xf9\xeb\xa6\x04\x65\xc3\xe4\xec\xf7\x5f\ -\x94\x1d\x8a\x6d\x27\x7b\xb6\x8f\x8e\x23\xd2\x5b\x5f\x07\xf8\xac\ -\x40\xe9\x79\xc3\x57\x1f\xf2\x0a\x9c\xc2\xac\xad\xb5\x8e\x3e\x8b\ -\xef\x11\x01\xc3\x51\x17\xec\xe2\xfb\x47\xc0\xe0\xc2\x14\x34\x02\ -\x86\x4b\x5f\x88\x8b\xcf\x88\x80\xc1\x45\xe7\x33\x0b\x18\x2e\x7f\ -\xa1\x2d\x3e\x3f\x02\x06\x17\x96\x7a\x08\x18\x1c\x0d\xd4\x06\x01\ -\xe3\x2e\x8d\x3a\x09\x18\x0e\xb9\x68\xf4\x47\xcd\x04\x0c\xee\xc6\ -\xea\x87\x80\x71\x71\xa8\x23\x02\xc6\x7a\x8f\x9a\x0a\x18\x5c\x08\ -\x6a\x8b\x80\x71\x01\xa8\xb1\x1a\x0b\x18\x83\x8f\x5a\x0b\x18\x0c\ -\xbc\x9a\x23\x60\x0c\x3a\x6a\x2f\x60\x0c\x38\x7a\x20\x60\x30\xd8\ -\x7a\x81\x80\x31\xd0\xe8\xc9\x55\xfc\x51\x02\x61\xae\x0f\x28\xf6\ -\xdc\x77\x4a\x75\xd6\x1f\x1b\x0c\xc3\xd6\x70\xf4\xcb\x06\x83\x33\ -\xbe\x5e\x61\x83\x11\xe0\xe8\x8f\x02\x3b\xd7\xa3\x6f\x36\x18\x00\ -\x1b\x8c\xbb\x20\x33\xf6\xaf\xf2\xff\x84\x65\x83\x19\x37\x43\xe8\ -\xe7\x3f\xdf\xa8\xca\xc0\x08\x18\x5b\x21\xfa\x26\x60\x1c\x8d\x38\ -\x39\x64\x6c\xa5\x02\x06\x10\x30\xb6\x17\x6c\x31\x02\x06\x18\x97\ -\x56\x7b\x7e\xbe\xca\xe7\xc7\xf6\xa2\xbf\xfa\x6b\x83\x01\x04\x8c\ -\x2d\x10\x7d\x15\x30\xbc\xb0\x3e\xa3\xdf\x08\x18\xc0\x1a\x38\xd7\ -\xdd\x4c\xfd\xf4\x1b\x1b\x0c\x20\x60\x6c\x7f\xe8\xb3\x80\x01\x10\ -\x30\x63\xcf\xe3\xe8\x3f\x02\x06\x10\x30\xce\xe5\xe8\xb7\x80\x01\ -\x10\x30\xce\xdf\x98\x03\x01\x03\x08\x18\x9c\xc7\xf5\x1d\x01\x03\ -\x08\x18\x40\xc0\xf0\xcd\x83\x3d\xcc\x83\x80\x01\x04\xcc\xbc\x3c\ -\xe8\xd3\x7f\x3a\xdc\x94\xe0\xdc\x3d\xda\x84\x62\x83\x61\xd8\x21\ -\xdd\xe1\x1d\x01\xc3\x90\x70\x11\x32\x08\x18\x7e\x86\xc1\x9a\x11\ -\xd1\xbe\x7f\xac\x22\x66\xc1\x2c\x70\xe4\x40\xe5\x8f\xd7\xba\xe3\ -\xf7\x3d\x7d\x51\x63\x16\xe0\xd1\x50\xb5\x8d\xa1\x6a\x02\xc6\x2c\ -\xec\x9d\x05\x47\x24\x00\xe6\x5a\x8b\x6d\x2f\x66\x01\x76\x0f\x56\ -\x7b\x61\xa0\x84\x8b\x59\x80\x9e\xac\x00\xb3\xd0\xc1\x33\x18\x40\ -\xc0\x00\x02\x06\x40\xc0\x00\x02\xe6\x6a\x3c\xdc\xd3\x7f\x04\x0c\ -\x20\x60\xe6\xe1\xbf\x71\xc1\x3c\x08\x18\x40\xc0\x00\x02\x86\x4d\ -\x1e\xf4\xe9\x3b\x02\x06\x10\x30\xf3\xf0\x60\x0f\x73\x20\x60\x00\ -\x01\xe3\x3c\x8e\x7e\x0b\x18\x00\x01\xe3\xfc\x8d\xfe\x0b\x18\x40\ -\xc0\xe0\x5c\x8e\x3e\x0b\x18\xc0\x99\xf2\x93\xef\x6a\x6a\xa8\xcf\ -\xd8\x60\x00\x01\x63\xf3\x43\xbf\x05\x0c\x9b\x6b\x34\xfa\x8a\x80\ -\x01\xac\x7e\x73\xdd\xe5\xd4\x52\x5f\xb1\xc1\x00\x36\x18\x77\x3b\ -\xf4\x73\x7a\x37\x25\x80\x79\x13\xf0\xea\x29\x28\xa1\xdd\xf5\x98\ -\xa8\x8f\x39\xd9\x85\xec\x19\xcc\x79\x33\x80\xbe\x95\x23\x60\x6c\ -\x82\xe8\xa7\x80\x99\x6c\x28\xdd\x0d\x1d\x8d\x10\x30\x30\xff\x6a\ -\xe5\x21\xaf\xbb\x21\xfa\x65\x83\x61\xe8\xf0\xa2\x3f\x36\x2e\x86\ -\x0c\xae\x3a\xeb\x91\x0d\x06\xc1\xad\x5f\x28\xac\xf3\x3d\xfa\x62\ -\x83\xc1\x79\x5f\x1f\x6c\x30\x38\xeb\xeb\x05\x02\xc6\x60\xa3\x07\ -\x02\x06\x03\xae\xf6\x28\xb4\x41\x57\x73\x04\x8c\x81\xe7\xd4\x5a\ -\xa7\x0b\x45\xc0\x08\x19\xce\x0a\x17\x4d\x7a\x9d\x3f\xa6\xbe\x46\ -\xb0\xfb\xa3\x53\x01\x2e\x60\x10\x32\xc2\x05\x47\xa4\xb9\x2f\x08\ -\x7d\xf9\x8f\x75\x74\x44\xb2\xc1\x54\x08\x79\xdb\x8c\x90\xb6\xc1\ -\x60\xbd\xff\xc4\x9a\x49\x2b\x01\xe3\x6e\x8c\x3a\x09\x18\x0e\xbc\ -\x78\xf4\x4b\x6d\x04\x0c\xee\xd2\xea\x81\xe6\xb8\xb0\x7c\x7e\x04\ -\x0c\xa5\x8f\x06\x8e\x43\x02\x06\x17\x9d\xcf\x88\x86\x55\x09\x9a\ -\x59\xfb\xea\xef\xb8\x09\x18\x5c\x98\xbe\x7f\x04\x8c\xb0\xb9\x6e\ -\xbf\xd3\x4c\xd6\xe1\x9f\x0a\x7c\xde\x0d\x63\x79\x72\x71\xdf\xbf\ -\x7a\x52\x61\xcd\x88\xcc\x88\x96\x11\x6b\x67\xa0\xec\xf9\x9a\xcf\ -\xbe\x77\xe0\xc2\x9b\xcd\xde\xd7\xa3\x70\xb9\x7f\xad\x47\x7f\x1d\ -\xa0\x5e\xe0\x64\xfb\x1d\x2e\xd9\x3a\xdf\x43\xc9\x6b\xba\x29\x41\ -\xd9\xa3\xd4\x56\xe8\x8c\x7c\x7f\x80\xb7\x8e\x48\x00\x6f\x85\x4c\ -\x13\x2e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1c\ -\xe9\x0b\x64\xcc\xe1\x13\xf2\xb1\xb1\xdf\x00\x00\x00\x00\x49\x45\ -\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x01\xb4\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x12\x00\x00\x00\x12\x08\x06\x00\x00\x00\x56\xce\x8e\x57\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdd\x06\x13\ -\x0c\x17\x1e\x59\x0e\xe8\x11\x00\x00\x01\x34\x49\x44\x41\x54\x38\ -\xcb\x9d\xd4\x3d\x2f\x04\x51\x14\x06\xe0\x67\x86\x4d\x2c\xb1\x61\ -\x4b\x3f\x42\xb5\x3f\x40\x21\x91\x68\x54\x3a\x89\xa8\xb5\x12\xa2\ -\x91\xf8\x07\x0a\x85\x88\x5e\x23\xd1\xa9\x10\x05\x11\x4a\x54\x3e\ -\x62\x45\x48\x14\x28\x2c\x85\x04\xcd\xdd\x64\xb2\xee\xec\x2c\x6f\ -\x77\xee\x79\xcf\x3b\xe7\x6b\x4e\x22\x1f\x29\xaa\xe8\x0d\x76\x03\ -\xcf\xf8\x6e\x13\xf3\xcb\x39\x85\x53\x5c\x63\x0f\xfb\xb8\xc1\x31\ -\x26\x0b\x62\x7d\xa3\x82\x93\x20\x30\x1a\xf9\xe0\x38\xee\x70\x10\ -\x32\x8d\x66\xd7\x8f\x4b\xcc\x6b\x8f\x14\x4b\x41\xa4\x1c\x23\x1c\ -\x62\xa1\xa8\x07\x99\xec\x97\xb1\xd3\xea\x98\x44\x1d\x49\x5e\xdd\ -\x91\x9e\x94\xf0\x84\xb1\xac\xf3\x08\x13\x45\x4d\x8c\xbc\x4d\x63\ -\xb7\x69\x54\x71\xdb\xc1\x34\xf3\xb2\xac\xa3\xd2\x8d\x1e\xdc\xe7\ -\x90\x92\x8c\x40\x92\xc3\x79\x42\x39\x0d\x46\x97\xff\xa3\xab\x39\ -\xca\x77\x0c\xb5\x29\x2d\x69\xc9\x2c\x8b\x12\x06\xd1\x48\xf1\x8a\ -\x2b\xcc\xe4\x88\xc4\xca\x6c\x62\x16\x17\x78\x6b\x3e\x8c\xe0\x25\ -\xb3\x60\x9d\x8c\xbf\x12\x04\x6a\xad\x84\x4d\xac\xfc\x61\x21\xd7\ -\xb1\x16\x73\x76\x07\xc2\x6a\xf8\x5d\xf2\x30\x80\x8d\xd6\xd2\xd3\ -\x0c\xe1\x33\x88\x95\xf1\x88\x39\xf4\x65\xfc\x15\x2c\xe2\x01\x1f\ -\x21\xf6\xab\x68\x63\x6b\xd8\x0e\xa7\xe3\x0c\xe7\xe1\x22\x6c\x61\ -\x38\x16\x9b\x14\xf4\xa2\x37\x73\x2e\x3e\xc2\xaa\x44\xf1\x03\x0f\ -\x82\x4d\xc9\xa9\xd9\x22\xf7\x00\x00\x00\x00\x49\x45\x4e\x44\xae\ -\x42\x60\x82\ -\x00\x00\x00\xe2\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x12\x00\x00\x00\x12\x08\x06\x00\x00\x00\x56\xce\x8e\x57\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdd\x06\x13\ -\x09\x32\x07\xd3\x5d\x52\xdd\x00\x00\x00\x62\x49\x44\x41\x54\x38\ -\xcb\xb5\x93\xc1\x0a\xc0\x30\x08\x43\x13\x4f\x65\xff\xff\xbf\xed\ -\xc5\x43\x61\xcc\x45\x5b\x03\x42\xa1\xfa\x08\x6d\x04\x62\x11\x80\ -\x01\x78\xfc\x2c\x69\x7a\x65\xf4\x39\x93\x81\xfd\xf6\x2a\xb0\xbd\ -\xc7\xaa\xb0\xfd\x8e\x55\xeb\x95\x77\x7c\x0d\x86\x10\x8a\xb0\x4c\ -\xbf\x0c\xeb\x75\x64\x22\x84\xaa\xb3\xca\xaf\xf1\x34\xb5\x72\x04\ -\xb2\xc9\x46\xdb\xae\x5d\xdd\xfe\x56\x0d\xaf\x50\x0b\x2c\xd6\x30\ -\xf9\x55\x1c\xba\xaf\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\ -\x82\ -\x00\x00\x05\xf0\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x01\x18\x00\x00\x00\xa0\x08\x06\x00\x00\x00\xd7\x60\x73\x20\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x01\x18\ -\x0a\x15\x18\x65\x63\x7a\xab\x00\x00\x00\x45\x69\x54\x58\x74\x43\ -\x6f\x6d\x6d\x65\x6e\x74\x00\x00\x00\x00\x00\x43\x52\x45\x41\x54\ -\x4f\x52\x3a\x20\x67\x64\x2d\x6a\x70\x65\x67\x20\x76\x31\x2e\x30\ -\x20\x28\x75\x73\x69\x6e\x67\x20\x49\x4a\x47\x20\x4a\x50\x45\x47\ -\x20\x76\x36\x32\x29\x2c\x20\x71\x75\x61\x6c\x69\x74\x79\x20\x3d\ -\x20\x39\x30\x0a\x67\x9d\x47\x40\x00\x00\x05\x1f\x49\x44\x41\x54\ -\x78\xda\xed\xdd\x4d\x72\x13\x31\x10\x86\x61\x29\x95\x6c\x58\x70\ -\x0d\x1f\x37\x9c\x20\x37\x30\x6b\xce\x82\x4f\xe1\x2a\xbc\x60\x25\ -\x16\x98\x8d\xcb\xc1\x3f\x33\xd2\x48\xea\xe7\xad\xf2\x22\x54\x81\ -\x3d\x3d\x5f\xbf\xea\x9e\x40\x48\x09\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x80\x66\x64\x25\xc0\x40\x14\xd9\x1d\x8b\x17\x25\xc0\ -\x80\x72\x01\xc1\x00\xe4\x62\x45\x02\xc6\x93\x8b\xdc\x12\x0c\x66\ -\xee\xf2\xbc\xcd\xdb\xca\x2c\xc1\x20\xca\x7e\x92\xdb\xbf\xad\xbc\ -\x0e\x86\x67\x30\xb8\xc9\x81\x5c\x40\x30\x98\x78\x60\x22\x17\x2b\ -\x12\x74\x7c\x95\xb7\x93\x51\x13\x0c\x9c\x42\xe4\x02\x13\x0c\x0c\ -\x4a\x20\x18\x90\x8b\x6c\x12\x0c\x40\x2e\x20\x18\x90\x0b\x08\x06\ -\xe4\x02\x82\x01\xc8\x05\x04\x03\x72\x01\xc1\x80\x5c\x40\x30\x00\ -\xb9\x80\x60\x40\x2e\x20\x18\x90\x0b\x08\x06\x20\x17\x10\x0c\xc8\ -\x05\x9d\xe1\x5f\x53\xc3\x61\x06\x37\x1d\x43\x4f\x2f\x72\x66\x82\ -\x01\xaa\xad\x46\x30\xc1\x00\x55\xe4\x22\x63\x04\x03\x90\x0b\x08\ -\x06\xe4\x02\x82\x01\xb9\x00\x82\x00\x72\x01\xc1\x80\x5c\x40\x30\ -\x20\x97\xe7\x7f\xbf\xdc\x12\x0c\xf0\xb0\x5c\x8a\x2c\x13\x0c\xb0\ -\x96\x5c\x4a\xc5\xfc\x15\xd9\x26\x18\xc4\x94\x4b\xd9\x28\x6b\x45\ -\xc6\x09\x06\x73\xca\xa5\x74\x98\xad\x22\xef\x04\x83\xf1\x05\xd3\ -\x7b\xa6\x88\x86\x60\x30\x89\x5c\xf2\xa0\xd7\x90\x5b\xbf\x79\xb4\ -\x86\x23\x18\x2c\x91\x4b\x9e\xe0\x7a\x72\xab\x37\x8c\xd8\x6c\x04\ -\x83\x88\x2b\x46\x33\xd1\x94\xe0\x8d\xe6\xe7\xc1\x20\x9a\x5c\x3e\ -\xbb\x86\x32\xc5\x9b\x99\x60\x30\xa8\x5c\x72\xa0\xeb\x6d\xb2\x36\ -\x45\x68\x3e\x13\x4c\x6c\x93\xec\x4a\x4a\xfb\x92\xd2\xa9\xa4\xb4\ -\xdf\xc5\x93\x4b\xf3\x01\xc3\x8f\xfb\x43\x24\xb9\x94\xcb\xd7\x59\ -\x32\x25\x68\x2f\x94\x54\xf1\xfa\x23\x17\x16\xf1\x3a\x69\x7f\x4d\ -\x30\x7b\x3d\x50\xb8\x00\x58\xde\x45\xa7\x6b\x82\x39\x69\x2a\x92\ -\x59\x11\xcf\x60\xe2\xf2\xe3\xda\x2f\x7e\x49\xe9\xbb\xd2\x2c\xfa\ -\x07\x9c\x00\x76\x57\xa6\x97\xf3\x6b\xa7\x3a\x26\x19\x60\x71\xe3\ -\xec\xce\xcf\x5c\x4e\x7f\xc5\xb2\x27\x17\x92\xa9\x3d\x0a\x22\x46\ -\xc3\xc8\xc1\xb2\x9a\xa9\x17\xc1\x80\x5c\x48\x66\x6b\x3c\xe4\x75\ -\xc0\x40\xad\x14\x0d\x4e\xe0\x0e\xeb\xa8\x86\x26\x18\x24\x0f\x27\ -\xd5\x95\x60\xd0\xb0\x09\x9c\xbc\xa6\x7e\xc5\x02\xb9\x58\x95\xc6\ -\xe6\x55\x37\x18\xdf\xb1\x4a\xad\x45\xb5\xe7\x15\xa9\xe8\x92\x16\ -\xe5\xd4\x04\xa6\xff\x98\x82\x01\xb9\x0c\x2e\x19\x67\x21\xc1\x58\ -\x8b\x00\x82\xc1\xda\x72\x31\xbd\x3c\xc8\x5b\x4a\xe9\x57\x4a\xe9\ -\x98\x52\x7a\x3f\x7f\x6d\x8a\x19\xbc\x3b\x3e\x7b\x61\x51\xf9\x94\ -\xf0\x09\x7e\x5f\x14\xf0\xfd\xfe\xfa\x63\xa4\x2e\xc1\xd3\x65\x53\ -\xc2\x05\x1c\x2f\x0a\x7a\xbc\xff\x3e\xa0\xd7\x15\x29\x5f\x79\xe1\ -\xe1\xb5\x28\x0b\xfa\x72\x3e\x2e\xbe\xfe\xba\xce\xaa\x0a\x4c\x31\ -\xf0\x19\x02\x17\xf2\x76\x5e\x8b\x8e\xf7\x17\x52\xcd\x3f\x39\xed\ -\xd0\x78\xbc\xa8\xfc\x47\xfb\x1b\xa6\x7d\xdc\x72\xb5\x4f\xbe\x8b\ -\xd4\x54\x2e\x15\xe7\x67\x61\x06\xc1\x44\x97\xcb\x4a\x92\xb9\xf7\ -\xc7\x2e\x90\x8e\x8d\x80\x60\x46\x90\x45\x49\x29\xfd\xec\x63\xb9\ -\xb6\xdf\xcb\x02\xe3\xce\x3e\x89\xe4\x15\x2c\x90\xd7\xf9\x38\xd9\ -\x33\x80\xee\xb2\xe0\x1e\x98\x60\xa6\xc8\x37\x81\x80\x60\xb0\x89\ -\x5c\xc8\xc7\x56\x40\x30\x51\x13\xf6\x40\x02\x4d\x2e\x20\x98\x08\ -\xa2\x38\x3c\xd8\xdd\x2b\x7c\x6b\x87\x5c\x26\xc9\x82\xba\x61\x86\ -\xb5\xc8\xc3\xc5\x3e\xef\x5f\xe8\x7b\x61\x82\x99\x43\x2e\x00\xc1\ -\xa0\x89\x5c\xc8\xc8\x66\x40\x30\x30\xb9\x80\x60\x40\x2e\x00\xc1\ -\x90\x0b\x40\x30\x20\x17\x10\x0c\x36\x82\x5c\x40\x30\xa8\x32\xbd\ -\x90\x0b\x08\x06\xd5\x56\x23\x80\x60\x50\x45\x2e\xa6\x17\x10\x0c\ -\xc8\x05\x20\x18\x72\x01\x08\x86\x5c\x00\x82\xc1\x58\x72\xf1\x00\ -\xb9\xcf\x7b\x4e\x30\x30\xb9\x00\x04\x43\x2e\x89\xb8\x86\xc0\x0f\ -\xfd\x96\x01\x93\x0b\x40\x30\xe4\x02\x10\x0c\xb9\x74\x21\x17\x0f\ -\x19\xfb\xca\x00\xc1\xc0\xe4\x02\x10\x0c\xb9\xfc\x0f\x42\xeb\x07\ -\xf7\x82\x60\x84\x0b\x20\x98\xb1\xa6\x97\xdc\xe9\xe7\x82\x9a\x13\ -\x8c\x40\x01\x46\x79\xf4\xfd\x50\xd7\x7f\xfc\xa5\xe6\x26\x18\x72\ -\x01\x08\x06\xe3\xc9\x85\xec\xd4\xbe\x0b\x5e\x95\x60\xfa\xc9\xa5\ -\x08\xfd\x26\xb9\x68\xf6\x66\x3d\xdf\x5c\x13\x8c\xb5\x08\x83\x87\ -\xb0\xe7\xef\x34\x10\xcc\xbc\x72\xc9\x83\x64\x70\xa6\x6c\x38\x70\ -\x08\xc6\xe4\x02\x10\x0c\xb9\x98\x62\x4c\x2f\x04\x43\x2e\xc3\x5f\ -\x13\x06\xaa\x65\x1e\x2c\x94\xac\x1b\x43\x2e\x4e\x5a\x35\x25\x18\ -\x72\xd1\x10\x6a\x69\x45\x22\x61\xe3\x3d\xb9\x80\x60\x56\x08\xcf\ -\x4c\x72\x71\xd2\xaa\x29\xc1\x38\x99\x9a\x35\x84\x93\xd8\x6a\x44\ -\x30\x1b\xca\x25\x07\xbd\x6e\xa8\x95\x11\x8f\x5c\xac\x82\xea\x45\ -\x30\xe4\xa2\x69\xd4\x89\x60\x04\x27\x68\x3d\x34\x8f\xfa\x10\x0c\ -\xb9\x68\x22\x75\x19\x93\x17\xc1\x09\x1f\x9e\x7c\x47\x7d\x42\x67\ -\xa4\x90\x8b\x09\x66\x26\xb9\x6c\xf4\x21\x9d\xd8\x37\xe4\xc2\x32\ -\x26\x98\x29\x26\x97\x6f\x37\xbe\x36\xc9\x98\x5c\xd0\x5f\x70\x2e\ -\x5f\xdd\x72\xbc\xf8\xa0\x47\xb5\x6a\x7e\xcd\x11\x8b\x60\x82\x99\ -\x7c\x72\xf9\xc7\xc7\x8d\xaf\x37\x58\x9b\x4b\xa4\x7c\x98\x5c\xea\ -\x86\x89\x5c\x7c\xe8\xa1\xeb\xb7\xe4\xda\x4a\xe0\x66\x21\x18\xcd\ -\xa1\x8e\x0d\xae\x49\x70\x08\xe6\x99\x10\xc9\x08\xd1\x70\x07\xc1\ -\x90\xcb\x60\x0d\x3a\x42\x7d\x4b\xb0\xfc\x13\x8c\x53\x6a\x4a\xd1\ -\xf4\x54\x6b\x8f\x53\x08\x86\x5c\x88\x86\x58\x08\x86\x5c\xb0\xac\ -\xb1\x6b\xde\x8b\x12\x28\xe3\x04\x43\x2e\x44\x53\x31\x7f\x25\x60\ -\xb6\x09\x86\x5c\xb0\x92\x14\x1c\x96\x04\x43\x2e\xd8\x5c\x3a\xee\ -\x31\xc1\x6c\x12\x54\xc1\x03\x3a\xe2\x95\x2c\xc1\xe4\xd0\x94\xe8\ -\x6e\x9f\x11\x1e\xdc\xc2\x7f\x5b\x82\x9b\x1c\xee\xfc\x35\x80\x60\ -\x00\x58\x91\xd0\xff\x8a\x24\x4c\x30\xc1\xc0\x29\x04\xd9\xc1\xf8\ -\x53\x8d\x30\xc1\x04\x03\x80\x60\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x80\xa9\xf9\x03\x7d\x79\xb8\x1f\x3b\ -\x4e\x17\x5e\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x01\x27\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x12\x00\x00\x00\x12\x08\x06\x00\x00\x00\x56\xce\x8e\x57\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdd\x06\x13\ -\x0c\x1e\x1a\x8f\xa1\x97\x41\x00\x00\x00\xa7\x49\x44\x41\x54\x38\ -\xcb\xc5\x92\xc1\x0e\x82\x30\x10\x44\x5f\x2d\xc5\xa0\xe1\xff\xff\ -\xd2\xa3\x89\x14\xa8\x97\x69\xa2\x68\xa1\x2b\x24\x4e\x42\x9a\x2c\ -\xed\xf4\xed\x6c\xe1\x20\x39\xad\xc9\x70\xa6\x05\x22\x70\xd5\x3a\ -\x03\x63\xb3\xe3\x72\x27\xa3\x37\x88\xb4\x41\xd5\x14\xf6\x78\xfd\ -\xa3\x96\x68\x2a\xd4\x93\x5a\xe3\x54\x69\x14\x0a\x75\x0f\x74\x16\ -\xa2\x71\x41\xf1\x91\x5b\x2d\xd1\xe6\xbe\x5a\xa2\xb3\xc6\x7e\x11\ -\xd1\x1d\xe8\x81\x5b\xe9\x1d\x05\xd5\xbc\x46\x9b\x49\xe2\x4a\xd8\ -\x00\x6e\x49\x14\x7f\xa5\xae\xc9\x28\x59\x8c\x5a\xdd\xec\x5e\xbe\ -\xac\xce\x62\x34\x1b\x82\x5f\x9d\xda\x94\x5f\xe8\x17\x45\x8b\x51\ -\xa6\xf3\xc0\xa0\x56\xb3\x1e\x96\xd6\x82\x0e\xf7\xca\x27\xf0\x2f\ -\x3d\x01\xe0\xcd\x1f\x66\x39\xd8\xf4\xee\x00\x00\x00\x00\x49\x45\ -\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x01\x55\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x12\x00\x00\x00\x12\x08\x06\x00\x00\x00\x56\xce\x8e\x57\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdd\x06\x13\ -\x09\x27\x12\x89\x35\x50\x22\x00\x00\x00\xd5\x49\x44\x41\x54\x38\ -\xcb\x9d\x94\xdb\x0e\xc2\x20\x10\x44\x4f\xaf\x6a\xed\xcd\x9a\xf8\ -\xa2\x4f\xfe\xff\x87\xf8\x4f\x55\xeb\xcb\x6e\xb2\x45\x68\x69\x27\ -\x21\xc0\xb2\x0c\x33\x2c\x01\x96\x91\x02\x89\x34\x9d\x07\x13\x43\ -\xc8\x80\x13\xf0\x95\x36\x01\xc5\xca\x9e\x3f\x14\xc0\x41\x36\x4f\ -\x12\xd3\xb1\x1e\xb0\xaa\xa8\x02\x4a\xe0\x25\xf3\xc4\xe9\xdf\x32\ -\xae\x97\x94\x34\x42\x6e\x95\xb8\x98\x8c\xcd\xd6\x97\xd0\x46\x90\ -\xb8\x64\x39\x70\xb6\xd6\x0a\x60\x04\x3e\x8e\x8d\x10\x74\x7d\x94\ -\x71\x8e\x78\xcd\x22\x95\x84\x94\x35\xca\xbe\x87\x04\x7b\x15\xa9\ -\xd8\xc2\x57\xd2\x08\x24\xc0\xc5\x27\xb1\xdf\x40\x52\xc9\xe1\x33\ -\x37\xf6\x8e\xea\x08\x92\x41\xaa\xa5\x7b\x66\xef\xf1\x18\x49\x96\ -\xcb\xba\xe6\x56\x21\xcf\x4b\x64\x77\xe0\x19\x52\xe2\xc2\x2a\x1b\ -\x4c\xfc\x06\x3c\xcc\x5a\xb9\xa9\xac\xc0\xd5\x5c\xae\xc6\x36\x55\ -\xd8\x2a\xb3\x3f\x41\xb7\xe3\x99\xd0\x19\x02\xfd\x42\xbc\xf8\x01\ -\x6d\x2c\x3c\xd9\x8c\xba\x79\x52\x00\x00\x00\x00\x49\x45\x4e\x44\ -\xae\x42\x60\x82\ -\x00\x00\x1a\x29\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x01\x18\x00\x00\x00\xa0\x08\x06\x00\x00\x00\xd7\x60\x73\x20\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x01\x16\ -\x0f\x28\x0b\x6d\x21\xc6\x13\x00\x00\x00\x45\x69\x54\x58\x74\x43\ -\x6f\x6d\x6d\x65\x6e\x74\x00\x00\x00\x00\x00\x43\x52\x45\x41\x54\ -\x4f\x52\x3a\x20\x67\x64\x2d\x6a\x70\x65\x67\x20\x76\x31\x2e\x30\ -\x20\x28\x75\x73\x69\x6e\x67\x20\x49\x4a\x47\x20\x4a\x50\x45\x47\ -\x20\x76\x36\x32\x29\x2c\x20\x71\x75\x61\x6c\x69\x74\x79\x20\x3d\ -\x20\x39\x30\x0a\x67\x9d\x47\x40\x00\x00\x19\x58\x49\x44\x41\x54\ -\x78\xda\xed\x5d\xbd\x8b\x65\xc9\x75\xff\x55\xb3\x48\x78\x66\x25\ -\x0c\x0e\x1c\xcc\x82\xc2\xd7\xc6\x12\x36\x48\x7f\x83\x77\x14\xd8\ -\x48\x3d\xa0\x0d\x94\x3a\xb3\x73\xad\x26\x70\x62\x68\x3b\xb1\x40\ -\xb0\xfa\x1f\x2c\x41\x77\xaa\xf1\x6e\xee\x68\x17\x04\x32\x78\xda\ -\x4a\x24\xd8\x0d\x0c\x5a\x25\xab\x1e\x23\x25\xe5\xe0\xdd\xd7\x7d\ -\xdf\x7d\xf5\x71\x4e\xd5\x39\x55\x75\xef\x3b\x07\x1e\xd3\xf3\x3e\ -\xee\xbd\xf5\xf5\xab\xdf\x39\x75\x3e\x9c\xf7\x1e\x26\x26\x26\x26\ -\x1a\x72\x61\x5d\x60\x62\x62\x62\x00\x63\x62\x62\x62\x00\x63\x62\ -\x62\x62\x62\x00\x63\x62\x62\x62\x00\x63\x62\x62\x62\x00\x63\x62\ -\x62\x62\x62\x00\x63\x62\x62\x62\x00\x63\x62\x62\x62\x00\x63\x62\ -\x62\x62\x62\x00\x63\x62\x62\x62\x00\xb3\x31\xf9\xe8\x23\xe0\xf9\ -\x73\xc0\x39\xe0\xab\x5f\xdd\xff\xfd\xd1\x47\xd6\x2f\x26\x67\x23\ -\xce\x62\x91\x94\xe4\xf6\x16\x78\xf1\x22\xfc\xd9\xcd\x0d\x70\x75\ -\x75\x9e\x13\xce\x39\xc4\xe6\x9c\x73\x4e\xec\x3e\x36\xaf\x0d\x60\ -\xb6\x2d\xdf\xfa\x16\xf0\xc9\x27\xe1\xcf\xbe\xf9\x4d\xe0\xe3\x8f\ -\xcf\x12\x5c\x7a\x8a\xcd\x75\x03\x98\xed\xc8\x97\xbf\x0c\xfc\xf1\ -\x8f\xe1\xcf\xbe\xf4\x25\xe0\x0f\x7f\x30\x70\x51\x00\x82\x56\xf7\ -\x31\xa1\x89\xd9\x60\xb4\xe4\x1b\xdf\x28\xfb\x6c\x43\x80\x32\x7f\ -\xa5\x16\xf9\xf2\x55\xcb\x52\xa8\xd7\xa3\x3e\xa3\x89\x01\xcc\x78\ -\xf2\xf2\x65\xd9\x67\x1b\x01\x96\x1c\x00\xb4\x54\x9b\xb8\x80\x63\ -\x62\x00\x33\xbe\x5c\x5d\xe1\x6f\x00\xfc\xc7\xf4\xdf\x2f\x00\xe0\ -\xdd\x77\x81\x0f\x3f\xdc\x9c\x81\x37\xc5\x02\x62\x8b\xbb\x35\xc8\ -\x70\x00\xc7\x58\x8d\xe0\xdc\x30\x3d\xb4\x8d\xdd\x61\x8b\xfd\x9c\ -\x63\x2a\x25\xd7\xe8\xd9\x4f\x12\xed\x59\xf7\x78\xc6\xda\x6e\x00\ -\x33\xfc\xe2\xdb\x52\x3f\xc7\x16\x62\x69\x1b\x47\xec\x2b\xe9\x36\ -\xae\x15\x5c\x6a\x41\xc6\x54\x24\x13\xb6\x2a\x14\x53\x39\x6a\x54\ -\x16\x2a\x93\x68\xad\x46\x51\xfb\xc0\xc4\x18\xcc\x78\xec\x65\x39\ -\x51\x07\x1e\x8b\x18\xb0\x9c\x8b\x5a\xd9\xaa\xfd\x5b\x63\x30\x6c\ -\x80\x49\x79\x62\x9a\x9c\x4c\x46\x1f\xed\x63\x0d\x85\x77\x23\x0b\ -\x6b\x64\xdb\xd5\xe6\x80\x66\xd1\x1e\x07\xdf\x0f\x60\xb6\x6e\xb4\ -\xac\x45\x7b\xef\x4f\xc0\xe5\xd0\x57\x6e\x6d\x00\xd3\xdb\x06\xb1\ -\x36\x90\x59\xe5\x9a\x88\xb4\x23\x04\x32\xcd\x6d\x30\xe7\xaa\x87\ -\xa6\x9a\x3d\x5f\x13\x5b\x52\xf1\x52\x36\x09\x4d\x1b\xc8\xa8\x73\ -\x2d\x65\x9f\xa9\xe8\xf4\x1d\x9c\xbb\x81\x73\xf7\xd3\xbf\xbb\x4d\ -\xac\x97\xd2\xe3\xc4\x73\x65\x32\xf9\x39\xe4\x7c\xa4\x9f\x8e\x7e\ -\xf9\x9e\x73\xf8\xe9\x80\x0c\x66\xb4\x13\x9d\x35\xb0\xe6\xe9\x19\ -\x8b\x1f\x6e\x07\xe0\x75\xf8\xa3\x4b\x78\x7f\x77\x16\x0c\xa6\x45\ -\x04\xec\xd0\xa8\x32\xbd\x3c\xf6\x2f\x02\xb8\x24\x3b\xe6\x67\x00\ -\xde\x9b\xfe\xfe\xd5\xf4\xb7\xeb\xbc\x48\x46\x3c\x2e\xee\xc2\x64\ -\x08\x6c\xc2\x39\xe7\x0f\xaf\x5a\xc6\x7a\x5d\xf4\x51\xbd\xbc\x47\ -\x1e\x83\x06\x0c\x26\xb4\x9b\x9c\x85\x4d\x26\x31\xa9\x8f\x91\xfe\ -\x14\x5c\xa6\x3e\x3a\xb1\xc5\x84\xfa\xad\x67\x5f\xae\xc1\x70\xd9\ -\xac\x7f\xf6\x60\x12\x22\x14\x97\x2e\x4a\x34\x16\xd3\x82\xd1\x8f\ -\xce\x39\x7f\x0f\xe0\x49\xe0\xb3\x37\x00\x9e\x22\x62\xc3\x13\xea\ -\xd3\xef\x01\xc7\x4c\x5a\xb8\x6f\x59\x36\x98\xe5\x6e\x32\xb2\x9e\ -\xdc\x5a\xd3\x0c\x81\xcb\x1a\x6c\x0e\x21\xd6\x32\xe2\x66\xd1\xb0\ -\x7f\x82\xac\xe1\x36\x02\x2e\xde\x7b\xb7\x78\x85\xfb\x77\xc6\x82\ -\x1f\x5e\xd3\xef\x9f\x00\xb7\xa1\x6b\xbf\x0a\xb3\x25\xd1\x71\xff\ -\xd9\x34\x61\x9d\x92\x7a\x5e\x74\x4c\x3d\xd2\xee\xdb\x9f\xc1\xcc\ -\x07\xfd\x17\x0e\xf8\xeb\xd0\x82\x08\xaa\x4e\x39\x43\xa1\xb6\x0b\ -\xc1\x1a\xbd\x8d\xd5\xe7\x9a\x73\x41\x42\x71\x60\x13\x14\xf5\xf7\ -\x04\x84\x72\xfa\x47\x84\x35\x5d\x02\xb8\x8b\x03\xae\x93\xe8\x43\ -\xed\xb1\x67\x9f\x22\x85\x76\x92\xf3\x65\x32\x73\xe0\xf0\x0e\xf8\ -\x5b\x72\x3f\xc4\x06\xb4\xb4\x2f\x0f\xdf\xa5\xfe\x66\xad\xa1\x0c\ -\xda\x73\xed\x36\xac\xad\x60\x62\x19\x0e\x82\xa6\xb2\xdb\x43\x1b\ -\xf6\x86\xdc\xcb\xe9\xad\x37\xd3\xbf\x97\xaf\x67\xcc\x28\x66\x03\ -\x1a\x5d\x1d\x2e\xf2\xe4\x8d\xed\x22\x9b\x64\x32\x51\x4b\x3b\x16\ -\xe0\x12\xde\xd8\x96\x76\x18\x8a\x0a\xc5\xed\x47\xee\xa4\xd9\x42\ -\x9c\x94\x74\x1b\x0e\x63\x94\x3a\xd1\x71\x13\xa1\xe0\xb2\xc4\xd8\ -\xb7\x17\xac\x88\x6c\xb3\xa9\x61\x34\xad\xc7\xbe\xc8\x0f\x86\x02\ -\x2a\x9b\x61\x32\xde\x9f\x6c\x2d\xee\xc4\x89\x2e\xbe\xb1\x4d\x6a\ -\x8b\x3b\xee\x3e\x2f\xb6\x4b\xa7\xfa\x3f\xf4\xdb\xad\x04\x61\x4a\ -\xc4\x2f\xcd\xd2\x32\x3c\x5c\xec\x40\x25\x96\x6c\x02\xde\xdf\x95\ -\xd8\xa7\x52\xdf\x7f\x95\x7e\x26\xa4\x6c\x3e\x54\xe0\xa1\xf4\x91\ -\xe6\x5a\x7d\x4b\x63\xe0\xe7\x74\x7d\xed\x4c\x66\xda\x81\x76\xd8\ -\x1b\xff\x9e\x03\x78\xb2\x7b\xd4\x8d\x5d\x6a\xc1\x72\xd5\x16\x6e\ -\x3f\x86\xc0\x65\x79\xba\x37\xff\xed\xd6\x22\xbc\x53\x6d\x25\x2e\ -\x32\x2f\x69\xdb\xe0\xca\x15\x10\x1d\x9b\x1c\x93\x9d\x9d\x48\xfa\ -\x25\xc8\x44\x00\x28\xab\x5a\xfb\x11\x8c\xbc\xd4\x0e\xd8\x8c\xba\ -\x94\x3e\xb6\xbc\x63\xee\xac\xbe\x86\xd2\x72\xd4\xd1\x52\x1b\xd0\ -\x9a\x37\x02\x66\x7f\xb4\x05\x97\xd0\x78\x14\xf8\x96\x45\xda\x96\ -\x6c\x0b\x65\x83\x1b\xc6\xc8\x5b\xab\x46\xad\x70\xd2\xc6\x9c\x9d\ -\xae\x29\x83\x24\x91\x63\xb6\xa4\x1f\x53\x74\x7e\x8b\x3e\x4b\xb1\ -\x7e\x8a\xa8\x1b\x9e\xa2\x76\x48\xab\xda\xf0\xfe\x41\x99\x76\x48\ -\x7b\xc7\x73\xd2\x45\x84\x9e\x9f\x63\x00\x1e\xce\xc8\x5b\xba\x73\ -\xac\x6d\x62\x4f\xea\x51\xd4\x0f\xca\x2d\x6c\x74\xdc\x9d\x93\x33\ -\xa9\x4b\x43\x35\xb6\x9c\xf8\xaa\xd0\x9e\xe0\x7b\xa9\x44\x35\x63\ -\x42\x1d\xff\x00\xb0\xb8\x9e\x2c\xf6\xa2\x65\xe7\xad\x89\xc9\x1c\ -\x9e\x35\x76\x6c\x89\x80\x8d\x6e\xd9\xbe\x25\xb8\xd6\x18\x26\xa5\ -\xd8\xd1\xd6\x13\x26\x65\x58\x5b\x77\x70\x29\x1d\x37\x6a\x80\xe5\ -\xbc\x4d\x3b\x00\x37\x7b\xee\x74\xef\x81\x9b\xc9\x96\xd8\x74\xa3\ -\x11\x49\x38\x45\x41\xd7\xb5\x31\x99\x43\x00\x1b\xe5\xd8\x32\xd6\ -\xee\x84\xfd\xc4\x97\x8c\x01\x77\xe7\x4b\x85\x76\x6c\xdd\x0e\xb3\ -\x6c\xe7\x72\x67\xef\x05\x2e\x52\xec\x3e\xe7\x9a\x70\xe9\x9c\xef\ -\x12\x40\xd9\xca\x06\x93\x59\x60\xab\x99\xa8\xb1\x63\xcb\x25\xb8\ -\xa4\x8e\x88\x29\x40\xc0\x39\x8e\xa6\xf4\x23\xc5\x37\x66\x4b\x4c\ -\x26\xd3\x17\x43\x81\x4b\x68\xbc\x97\x35\x9a\x42\x2f\x8e\x3a\xfe\ -\x3a\x12\x7e\x00\xe5\x00\x4a\x15\x06\x93\x1b\xe0\xb5\x31\x99\x40\ -\xf8\xbd\xa3\x9e\x92\x11\x17\x76\x34\xea\x9a\x6a\xc7\xa2\x9e\xe0\ -\x71\xae\xb9\x15\x60\x49\xb0\xc5\x21\x6c\x2e\xd2\xaa\x56\xb0\x0f\ -\x12\xb6\x43\x78\xff\x74\xd5\x0c\x26\x54\x5c\x6b\x2d\x61\x05\x21\ -\x70\x29\x05\xd3\x84\xed\xc4\x1d\xcf\x05\xa2\x1d\x87\x00\xda\x4c\ -\x9b\xc4\xaa\x99\x0c\x17\x5c\xd0\x30\x23\x06\xa7\xaa\x65\xe8\x3d\ -\x81\x42\x71\x21\x3f\xbe\x94\x4d\x71\x6c\x06\x43\xd9\x69\xa9\x3b\ -\x70\xe7\x49\x9b\x75\xeb\xe7\x1a\xb2\x73\xbe\x0b\xa1\x34\x0e\x81\ -\x1d\x29\xe8\x37\x51\x9b\x42\x63\xad\x4c\x26\xc7\xd2\x52\xe0\xa2\ -\xd5\xce\x1a\xdf\x23\xae\x4a\x9d\xdd\xe4\x23\x51\x0f\x87\x00\xca\ -\x56\x4c\x4e\x94\xc1\xe4\x02\x21\x63\xef\x8f\xb2\x83\xa6\x54\x17\ -\x8e\xfa\x41\xdc\x69\xdd\xf2\xbe\x51\xb6\x11\x01\x17\x89\x7e\xa4\ -\x84\x17\xac\x0d\x5c\x42\x7d\xad\x39\xdf\x32\xae\xfd\xe2\x29\x30\ -\x48\xec\xe6\xd1\x7c\xf8\x60\x3b\x4c\x45\x67\xaf\x82\xc1\x94\xd8\ -\x06\x46\xd9\x41\xa9\x29\x15\xa8\xec\x85\x68\x2b\xf1\x11\xc0\xa9\ -\x06\xb1\xda\x53\x89\xc1\xed\x63\xc5\xf6\x25\x85\x20\x49\x16\x53\ -\x91\x66\x93\x54\xb0\x9c\xcd\xc7\x22\x3f\xac\x61\x6c\x30\xa9\x9d\ -\x22\x34\xd8\x23\x30\x19\x0a\x73\x29\x9d\x9c\x99\xd3\x25\x97\xa0\ -\xf4\x4d\x99\xc5\x1a\x4e\x98\xb8\xe0\xa2\xd9\xce\xd8\xc9\x4e\x8b\ -\x43\x93\x92\x67\x8f\x9c\x42\xfa\xd5\x01\x4c\xc9\x44\xee\x09\x32\ -\xdc\x13\x9d\x92\xdd\x87\x33\xa9\x39\xbb\xaf\xc6\xd8\x8c\x6a\xfc\ -\xad\x05\x77\x49\x90\xd1\xca\xa9\xc2\x61\x2d\x25\x86\xfd\x40\x64\ -\xff\xfa\x54\x24\x0e\xdd\xeb\x5d\x6c\x3c\xc7\x5c\x4a\x8e\xa6\xb9\ -\x79\x58\x28\xaa\x19\x85\x09\x4a\xf7\xe1\x48\xc6\x5f\x2a\xb8\xd4\ -\xaa\x18\x25\xc6\xd5\xd6\xd5\x2d\x25\x18\x5a\x2c\x57\xf4\x26\x18\ -\x0c\x75\x70\xb4\x77\xcf\x1a\x70\x91\x64\x08\xa1\x23\xeb\xd8\xd1\ -\x25\x65\x07\x93\x62\x20\xa3\x18\x7f\xb5\x6d\x43\xd4\xfe\x1a\x21\ -\x31\x7a\xad\x11\xbf\x07\x33\x55\xad\x4d\x5d\xba\xb3\x37\xd8\x21\ -\xb2\x36\x17\xaa\x71\xb0\x74\xa7\x2f\x4d\xe3\xd0\xda\xa1\xb1\xa7\ -\xf1\x97\x73\xef\xda\xf6\xb6\x34\x0c\xd7\x82\x49\x28\xec\xa3\x90\ -\x81\xa9\xb3\x98\xae\x0c\x26\xb6\x43\x6b\xa2\x2d\x85\x1a\xb6\x98\ -\x60\x8b\x6b\x90\x8c\xbd\x9c\x7b\x6b\x30\x99\x96\xbb\x5f\xeb\x45\ -\x9d\x4a\xf7\xd0\x93\xb5\x94\xda\x95\x7a\x99\x1d\x9a\x02\x0c\x77\ -\x92\xa7\x40\x46\x62\x62\x2f\xc1\xa5\xf6\x9a\xbd\xec\x14\xd4\xd3\ -\x0a\x49\x90\xe9\x69\xfc\xe5\x06\x75\x6a\xa9\x85\xbd\x4a\xe7\xa6\ -\x58\x33\xf7\xb9\x4a\x37\xb6\x55\x33\x98\x16\x76\x19\xaa\x51\x8b\ -\x93\x2f\x57\x98\x02\xbb\x29\xbc\x1e\x6f\x9c\xf3\x25\xf5\x89\x09\ -\xae\xf3\x4d\x37\x8c\xb5\x01\x77\x2d\x43\x90\x6c\x3f\x45\x25\x2f\ -\x7d\xae\xa6\x6d\x69\xad\x4f\xd6\xd4\xed\x29\xed\x20\x8e\x5b\xbe\ -\xb6\xed\xe5\xe4\xb7\xfb\x0b\x24\x53\x73\xc6\xc2\xeb\x39\xfd\x23\ -\xb9\x68\x35\x01\xa0\xc6\xc9\x50\x3b\xec\xa5\x87\x6a\x58\x6b\x77\ -\x21\xde\xc7\x6b\xd9\x61\x86\x63\x30\x54\x95\x49\x9a\xb9\x70\x06\ -\x4b\x1c\x5c\xf6\x42\x4e\xcd\x19\x7a\x06\x0e\xbd\x97\x66\x32\xda\ -\xd1\xc2\x25\x0b\x52\x72\x91\xb7\x50\x09\x4b\xc6\x4c\x4a\x1d\xd4\ -\x54\x93\x5c\xb3\xcc\x56\x82\x56\x7e\x86\x6e\x4e\x72\xc5\xef\xc6\ -\x5e\xe6\xbf\x8d\x54\x14\x44\x24\xbc\x9e\x9a\x2e\x42\x33\x19\xbb\ -\xa6\xdb\x7d\x0b\xb0\xe7\x02\x56\xab\x20\x49\xca\x66\x20\xac\xaa\ -\xab\x9d\x26\x0d\xcd\x60\x38\xb4\xbf\x86\xb9\x74\x61\x2f\xa7\xbf\ -\x7d\x15\xf9\xd9\x2b\xce\x75\x6a\x23\xbf\x6b\xc6\xa4\xd2\x46\xa6\ -\xce\x76\x4a\xae\xa9\x6d\x77\xca\x81\xb4\x46\x22\xf9\xd0\x63\xac\ -\xda\x06\x23\xb9\xdb\x70\x13\x3a\x05\xb2\xad\x57\xd9\x2d\x24\xec\ -\x49\xc1\xdf\x31\x6c\x30\x35\x9e\xc3\x2d\x76\x7e\x69\x3f\x94\x1e\ -\xec\xa5\x75\x90\x24\xc7\x1c\xa0\x64\xf7\x52\x61\x31\x4d\x19\x8c\ -\xc4\x6e\x90\x4b\x9e\x5d\xe2\x02\x1d\x02\x0f\x6e\x21\xac\xea\x85\ -\x30\xab\x4f\xfc\x58\x4e\x70\x1f\x76\x5f\x4a\x93\x6b\xb2\xf0\xd5\ -\x8c\xed\xb5\x73\xfb\x3a\x40\xf3\x57\x43\xa0\xd0\x60\x50\x9a\x6c\ -\xad\xe2\x98\x59\x45\x3b\x90\x64\x6a\xae\xa7\xbb\xb3\x42\xa8\x7c\ -\x12\x5c\xa8\x16\xfa\x98\xc5\x5e\x0d\x5c\x32\x2a\x1e\x16\x89\xaf\ -\xb8\xa7\x26\x2d\x93\x7e\x5d\x3b\x87\x97\xf1\x8e\x93\xee\xa3\xe6\ -\x0c\xac\x06\x1c\x6a\x6b\x88\x37\xb0\x03\x79\x69\x5c\x70\x3d\x76\ -\x0c\x25\x90\xc9\x32\x97\x44\xe0\x57\x16\xe1\x5b\x53\xf8\x92\x70\ -\x86\x12\xa0\x11\x07\x99\x54\x5f\x0a\x1f\x9f\x4b\x01\x4c\x8b\xb1\ -\xad\xa9\x08\xd1\x10\x64\x7c\x68\x43\x5b\x8d\x8a\xa4\xa8\x76\xf9\ -\xe3\x7e\xa2\x67\xe0\xe7\xe6\xdd\x68\x65\x1f\x48\xe5\xed\x95\xa4\ -\xc2\x6b\xaf\x5b\xd5\x79\xde\x91\x9e\x45\x12\x5c\x5a\x35\x6f\x95\ -\x36\x18\x0d\xfd\x3f\xb6\xd3\x73\xaf\x2b\xe9\xc1\x19\x4b\xd4\x2d\ -\x09\x0e\xa5\xbb\x8b\x36\xc8\xfc\x2e\xf2\xfe\x67\x83\xda\x5e\x6a\ -\xfa\x34\xd7\x6f\xa1\xf2\x35\x92\x45\xd7\xd6\x20\xab\x66\x30\x4b\ -\x83\x6e\xcc\x58\xd5\x2a\x19\x73\x10\x5c\x84\x73\xb1\x4a\x00\x41\ -\xee\xf8\xb5\xe6\xda\xdf\x8f\xbc\xff\xf7\x4a\x3b\xb3\x46\xca\x4b\ -\x89\x31\x29\xb5\xd5\x74\x8e\x7f\x72\x92\x7d\xd3\xcd\x06\x23\x6d\ -\xa3\xa8\xe9\x18\xc9\xe4\x4d\x1a\xe0\xc2\xcd\x5b\x23\xb9\xc0\x4a\ -\x6d\x22\xcf\x01\xfc\x7c\xf9\x59\xe0\xba\x35\x36\x94\x9e\xf6\x17\ -\xad\x7e\x6c\xe9\x24\xd9\xca\x0e\xb3\x4a\x06\x93\x3b\x8a\x2e\x49\ -\x69\x20\xae\xfb\x76\xf0\x90\x96\x66\x00\xa5\x39\x5f\x7f\xee\xfd\ -\xbe\xfd\xb3\xd7\xe8\xd5\x0b\x6a\x17\x52\xcc\x21\x4e\x3a\x79\xb7\ -\xe2\x44\xda\xc1\xb9\x9b\x7b\xec\x03\x6e\xe7\x75\xac\x57\xcb\x60\ -\x0a\x55\x10\x91\xe4\x4c\xaa\xec\x45\xde\x11\x2a\x58\x65\xb2\x95\ -\x9b\xbc\x64\xba\x04\xa9\xa3\xd7\x52\x06\x33\xb7\x39\x29\xbb\x4c\ -\x54\x1d\x61\xe7\x1c\xef\x44\xe7\x58\xc2\xc9\xd3\x01\x77\x35\xf7\ -\x5a\x15\x83\x91\xcc\x23\xaa\xe5\x38\xd5\x2b\x67\x88\xb4\xb3\x1c\ -\xb5\x6f\xb8\x6d\xef\xe9\x48\x47\xb5\xc9\x95\x5e\xb7\x94\x95\x0c\ -\x90\xd4\xea\x9a\xf9\xfe\x7a\x18\x0c\x75\x92\x96\x80\x0b\x87\xc1\ -\xb4\xd8\xbd\xb5\xc0\xb5\x17\x93\xe9\x99\x86\x83\xba\x93\x53\x9c\ -\x2b\xa5\xc2\x56\xa4\x12\xc1\xb7\xb0\x3d\x2d\x2e\x1a\x0d\xb4\x75\ -\xc0\xd3\xcd\x33\x18\x0d\x70\x91\xb0\x05\x8c\x72\xcc\xaa\xc1\x64\ -\x7a\x24\x8b\x96\xb4\xcd\x68\xdb\x79\x38\x41\x8a\xb5\x0c\xa7\x81\ -\xcd\xe6\x15\xf3\xfd\x75\x01\x4c\xe6\xc8\x54\x3d\x31\x71\xc9\x60\ -\x75\x00\x97\x64\xc6\x3b\x0d\x87\xb9\x14\xc8\x68\xa4\xa9\x94\x7a\ -\x7e\x62\x40\xac\x08\x70\xa5\x0c\xb9\x5c\xc3\x76\xc7\x4d\xea\x65\ -\xea\xfd\x9a\xf1\x58\xbd\x27\xaf\x16\x05\x4f\x5d\xab\x07\x73\xf1\ -\xc0\xee\x35\x80\xab\x47\x2e\x7b\x05\xe0\x75\x4f\x90\x29\x69\x3f\ -\xd5\x18\x5a\xc2\x40\xb4\xf3\xe8\x4a\x14\x7f\x8b\xd9\x81\x4a\x4f\ -\x3e\x85\x06\xf9\x24\xd0\x16\x93\x81\x77\x13\x0c\xe6\x61\x27\x00\ -\x76\x1e\xb8\x81\x73\xf7\x70\xee\xe6\x72\x71\x7a\x72\x98\x40\x35\ -\xbb\x4f\x0b\x7b\x91\x92\x90\x0c\x71\xad\x40\x46\xba\xfd\xa4\x84\ -\xef\xa1\xe8\xec\xc8\x9c\x38\x30\x8b\x9c\x61\xb7\x85\x03\xdc\x08\ -\x65\x50\x48\x20\xe3\xfd\x8b\xa7\x00\x5e\x3c\x82\x4e\xfd\x7a\x19\ -\xc6\x05\x39\x72\x54\x76\x09\xe0\x2e\x91\x10\xa7\x67\x4d\xe5\xa6\ -\x7d\x27\x94\xf1\x4e\x5b\xf5\x90\x58\xa8\xa1\xfb\xfc\x09\x80\xff\ -\x06\xf0\xb5\xc3\x77\xa6\x7f\x7f\x0d\xe0\x2f\x00\xfc\x5f\x21\xc8\ -\x72\x8d\xc4\xbd\x8f\xb3\xe7\xd7\xd0\xac\x19\xb6\xcc\x5d\x5d\x7c\ -\xaf\x03\xd2\x77\x7f\x01\x37\xa7\xee\x59\xf0\x1e\xb8\x99\x7f\x2f\ -\xd7\x86\xd1\xa4\x65\xff\x6c\x59\xde\xdf\xab\x89\x0f\xaf\x99\xea\ -\x88\xf7\x61\x22\x35\x47\x0f\x5d\xbc\x9c\x53\xa5\xf3\x76\x24\x06\ -\x53\xbc\x43\x1f\x3a\xa0\x34\x5b\xdd\x2a\x98\x4e\x26\xe3\xdd\x9a\ -\x22\xa1\x4b\xe4\x53\x00\xcf\xe6\xdd\xb1\xf8\xec\x1d\xc3\x89\xea\ -\x39\x9a\xaa\xbe\x51\x3a\x87\x2f\xc6\xc0\x16\x07\x30\x8f\xca\x6a\ -\xbd\x70\x73\x6c\x88\x88\xf6\xcd\xd4\xa8\xc9\xe0\x76\x92\xf1\xee\ -\x1c\xc0\xc5\x64\xbd\xaa\xfe\x30\x8e\x76\x3b\xc0\xbf\x0e\x7f\x7c\ -\x99\x32\x38\x95\xea\xd8\x94\xe0\xb4\x9a\x1a\x3d\xd2\x03\xb7\xdc\ -\x49\x62\x41\x90\xd4\xf6\x4a\xe5\xb5\xe1\xd8\x14\x6a\xea\x29\xbf\ -\x0f\xe0\x5f\x4e\x41\xf7\xe1\xb3\x7f\x45\xde\x11\x90\x5b\x98\xae\ -\xc5\x09\x54\x4d\xae\x21\xad\x90\x94\x58\x05\x8e\x92\xfb\x5d\x8c\ -\x00\x2e\x00\x1e\xce\xc9\xa6\xcd\xf9\x61\x93\xfe\x21\x70\x97\xca\ -\xef\x9a\x63\x1b\x5c\x14\xdf\x01\xb8\x9d\xee\xf3\x79\xe0\x59\x47\ -\x66\x0b\x9c\xbe\x90\x4a\xb4\xa5\xa5\x7e\x2e\xfb\xfa\xc7\x00\x7e\ -\x13\xf9\xee\x8f\x2b\xc7\x47\x2a\x6f\x0b\x17\x5c\x72\x00\xb7\x05\ -\x19\x21\xd8\x31\xec\x48\xe7\x1c\x7e\x83\xc7\x53\x83\xc0\xcc\x2e\ -\x56\x8f\xa2\x83\x1e\xf2\x79\x61\x2c\xe6\x16\x15\x0f\x63\x0c\x46\ -\x3a\x4f\x70\x6d\x4a\x48\xea\x02\x62\xb1\xa2\xa5\x13\x26\x13\x78\ -\x6b\xea\xa3\x6b\x80\x4b\x69\x7f\x2b\x55\xb2\xcc\xd6\x10\x5b\x1d\ -\x83\xc9\x79\xe9\x7e\x4d\x69\x37\x97\xa0\xc2\xcb\x5d\xaf\xb5\x7f\ -\x4c\x4d\x4a\xcd\x96\xbe\x32\xdc\x94\x0f\x31\x7f\x96\x87\x4d\xc5\ -\x7b\xb8\x00\xb8\x48\xf4\x79\xcf\x32\xb8\x5b\x65\x32\x17\x23\x80\ -\x0b\x2a\x0a\x3f\x69\x0f\x86\x27\xa8\x1a\x3d\x9c\xef\x34\x0b\xd9\ -\x97\xb6\xa7\x14\x64\xb2\xc0\x42\xbc\x4f\x69\x0c\xd5\x28\xc7\xfc\ -\x23\x80\xcc\x26\x2a\x3b\x4a\x81\x8b\x34\x7b\xf9\x5d\xa7\x9d\xae\ -\x02\x5c\x5c\xa4\x4f\x8b\x27\xb4\x40\xb2\x72\x16\xc8\x94\x3a\x9e\ -\x71\x23\xbb\x5b\x2f\x58\x89\x50\x80\x56\xcf\x1c\x9b\x3b\x12\xf7\ -\xbf\xe8\x09\x2e\xb5\x68\x29\x9d\xd7\xe3\xfb\xcc\x85\xb4\x05\xe7\ -\x36\x8d\x6a\x8f\x35\x09\xc9\x25\x5c\xf0\x4b\x62\x98\x46\x00\x97\ -\xd8\x98\xac\x59\x65\xba\x18\x05\x5c\x5a\x74\x62\x74\xe0\xa7\xf7\ -\x5f\x01\xf8\x36\xe3\x1a\x9c\xf2\x28\x1a\x6d\x98\x16\x64\x15\x8b\ -\x09\x2d\x80\xd6\xf6\x8c\x1a\xb0\x2e\x8d\xf3\xd1\x0a\x31\x91\x3a\ -\xea\xee\x11\x02\xa3\x91\xad\xe0\x62\x04\x70\x89\xb4\xf6\xe8\xbf\ -\x9f\x01\xf8\x21\x26\x57\x5f\xa6\xd7\x6e\xf6\xd9\x66\x7f\xe7\xf2\ -\xc9\x52\x76\xfb\x46\x93\x41\x0d\x78\xb5\x0d\xbf\x54\x95\x6a\xf9\ -\x92\x62\xb9\xcd\x22\xe0\x85\xf3\xfc\x6a\xcc\xab\xd2\x4d\x89\x7c\ -\xfd\x16\x9d\x9d\x03\x97\x92\x63\xd3\xda\xa3\x69\xae\xa3\x53\x49\ -\x96\x37\xc9\xc9\x4c\xc8\x38\x5f\xac\x7a\x4a\x82\x75\x89\x0a\xd2\ -\x62\x21\xd5\x3a\x09\xb6\x64\x2f\x2d\x81\x92\x72\x3c\x5d\x73\xbf\ -\x8b\xde\xe0\x52\x32\xd9\x25\xfc\x5e\x24\x6d\x0e\x23\x2c\x16\x41\ -\x95\x4b\x9d\x89\xf5\x48\x5f\x40\x65\x44\x52\x4c\x49\xba\x0d\x9a\ -\x91\xd3\x5a\xea\x51\x13\x80\x39\x3c\x78\xaa\x01\xda\x8b\x52\x02\ -\x5c\x24\xf3\x7f\x48\x33\x00\x29\x70\xe1\x80\x79\xed\xe2\xf0\x99\ -\x52\x26\x1a\x76\x21\xc5\x4d\xb4\xc9\xb3\xf6\xe8\x83\x55\xd4\x45\ -\xa2\xa0\x63\xce\xbf\x24\xd7\x58\x29\x63\x9a\xd4\xe2\xf7\x8d\x6b\ -\x01\x71\x8d\xbd\x39\x80\xd5\x04\x99\x52\xfb\x55\x2a\x2d\x80\xf6\ -\x86\x34\x22\x08\x54\xe6\xf4\x6d\x72\xe0\x32\x54\xca\xcc\x54\xea\ -\xc4\x9a\x38\x13\xa9\xc9\x70\xf8\x3e\x47\x95\xa8\x65\x33\x4a\x86\ -\x3d\x36\x68\x6b\xb2\xca\x58\x1f\x51\xef\x4f\x05\xf9\x25\x48\x49\ -\xcc\x8b\xde\x47\xc8\xa3\x1f\x61\x0f\x97\x93\xb7\x64\x47\x92\x74\ -\x08\xe3\xfc\x4e\x1b\x64\x62\x8b\x30\xb1\xa0\xb2\x2c\xa6\x46\x35\ -\xd4\x06\x99\x10\xe3\xcb\x79\x1c\x73\x03\x32\x53\xf7\xe4\xce\xbd\ -\xd6\xa9\x2f\xb9\xf3\xa1\xd6\xf6\x92\x3a\x4c\x59\x2d\xc0\xa4\xe8\ -\xb0\xa4\xfa\xa2\xc1\xba\x28\xf4\xbe\x95\xca\xa4\xc5\x2c\x5b\x3b\ -\xa5\xc5\x40\x86\x92\x70\x2c\xb5\x20\x6b\x7d\x98\x46\xc8\xab\x4b\ -\x1e\x97\xa9\x2c\x2c\x9c\xbb\xbf\x75\xce\xef\x1a\x32\x22\xb7\x16\ -\x6f\x54\xae\xaa\xd3\x02\x5c\x4a\x99\x01\x37\x2f\x6d\x68\x22\x65\ -\x4e\xcc\x54\x22\x63\x25\x4e\xdd\xe6\xcf\x2f\xe5\x4b\x94\x8a\x96\ -\xa6\x9c\x58\xd5\xe4\x64\x69\x09\x2e\xb1\xb1\x4f\x3e\x4b\x22\xd7\ -\xf5\x6b\x82\xcb\xc8\x26\x32\xda\x6d\x41\x0f\xad\x49\x34\xdd\x22\ -\x32\x5b\xe2\x9a\xad\x0c\xbf\x54\x75\x85\xf2\x9d\x52\x86\xb4\x26\ -\xbb\x47\xe6\xf9\x83\xd5\x28\x5e\x3f\x54\x27\xd1\x05\xcd\x55\x30\ -\x18\x6e\x1e\x8f\xd6\xec\xa5\x66\x90\xb8\x3b\x6a\x49\x99\x5d\xcd\ -\xd2\xb2\x12\x19\xf1\x4b\x03\x15\xb9\x27\x50\xb5\xac\xb8\xa7\x6a\ -\x54\x9c\xc3\x87\x98\xeb\x3a\x97\x71\xef\x2c\x19\x8c\x64\x79\x8a\ -\x5e\xf6\x0a\xa9\x7a\xcf\x8b\x6b\xba\x08\xd8\x0c\x69\x93\xa1\xa6\ -\x8e\x48\x2d\xa8\x5c\x46\xba\x56\x25\x57\x46\x5a\x0f\x25\xb9\xae\ -\xa5\xdb\x35\x3c\x83\xc9\xed\x5e\x12\x21\x04\x23\x30\x19\xce\x04\ -\xe6\xda\x61\x30\x85\x5b\x69\xfa\x8b\x48\xd8\x2e\x72\x63\x19\x7b\ -\x8f\x62\xb3\x92\x66\x48\xbd\xe6\x13\x97\x15\x53\x72\x5d\x53\xfa\ -\xee\x6c\x6d\x30\xa3\x66\x02\x93\x78\xae\x4a\x87\x2f\x77\xfc\x5f\ -\x3f\x7c\xdf\x53\x80\x81\x6b\x0c\xae\x61\x32\xb9\xc8\xf9\x15\xac\ -\x89\xc7\x9a\xb0\x07\xb5\xe8\x50\x90\x22\x00\x2e\x2a\xc0\xb8\x26\ -\x17\xec\x12\xdd\xbb\x37\x95\xad\x0d\xa6\xac\xb0\xc3\x00\xb3\x84\ -\x7c\x5a\xb1\x26\x35\x76\x27\x0a\x9b\xa8\xa9\x0a\xc1\xbd\x6e\x0c\ -\x7c\x7a\xab\x46\x85\x4c\xd1\x53\xd6\x3a\xd7\xe1\xf1\xec\x18\xcc\ -\x88\xba\x30\x77\x97\x0f\xd9\x0f\x6a\x7c\x66\xa4\x32\xde\x69\x33\ -\x19\x8a\xbd\x85\xe3\xa5\x9b\x9a\x0b\xa5\x4c\x66\x74\xbb\x0b\x15\ -\x5c\x72\xfd\xad\x15\x0f\x78\x31\x70\x27\x89\x80\xc9\x9a\x73\xad\ -\x96\xb8\xd0\xf7\xb2\x13\x54\x7a\x96\xaa\x25\x7c\x2a\x01\x19\x6e\ -\x2e\xe0\x91\xd8\xcb\x12\x5c\xbc\xf7\x47\xcc\xe5\x27\x87\x92\x3c\ -\xce\xe1\x83\x06\xcf\x6f\x7e\x30\x9d\x17\x20\x27\xe8\x30\xd7\xd6\ -\xd0\xb5\x24\x32\xde\x49\x83\x0c\xc5\xff\xa5\x34\x0c\x43\x02\x64\ -\xd6\xc2\x54\x28\xe0\x32\x6f\xcf\x07\x00\xfe\x61\xfa\xff\x9f\x4d\ -\x7f\x7f\x00\x9d\x20\xe2\xa1\x6d\x30\x52\x35\xa6\xd7\xde\x36\xee\ -\x6f\x13\xa7\x01\x4d\x6c\x31\x05\x36\x22\xd6\xf8\xe6\x6c\x58\x1c\ -\x7b\x09\xb7\xca\x63\x6f\xfb\x0b\xe3\xc4\x30\x3e\xce\xb1\xb5\xa2\ -\x78\x5a\xb6\x6e\x06\xb3\xc2\x64\xc8\x1a\xc9\xb4\x7b\x95\x4e\x29\ -\x65\x32\xa5\x25\x4a\x28\x9b\x49\x0d\x43\x19\xcd\x8b\x97\xf8\x3c\ -\x34\x70\x01\x4e\x2a\x95\xb6\x90\x8b\xf1\x30\x83\x38\x21\x9c\x83\ -\x5b\x29\xa5\xad\x49\xa6\x44\x71\x9f\x4f\x4d\x38\x6d\x35\x89\xa3\ -\x12\x6a\xd9\x77\x38\x20\x43\xa9\x73\x45\xc9\x55\xd4\x43\x1d\x8a\ -\xd8\x5b\x5c\x6c\x03\xfa\xf7\x86\x9b\xe1\xba\x19\xcc\x02\x5c\xa8\ -\x3b\xe5\xa8\x20\xca\x7d\x4e\xee\x09\xca\x28\x4c\xa6\x26\x4d\x65\ -\x09\xc8\x50\xef\x5f\x9a\xd1\x50\x73\x7e\xa5\xd4\x5d\x0e\x6b\x99\ -\x5f\xe7\x1f\x01\xfc\x84\xa1\x1e\x89\xb4\x63\xa4\x49\x49\x9a\x00\ -\x01\x70\x19\xc9\x83\x57\xdb\x0e\x53\x62\xc7\xe8\x65\x8b\x49\xa9\ -\x44\xa5\xba\xbe\x94\x87\x74\x78\x08\xca\x0c\xd3\x1a\xf3\x2c\xe0\ -\xd5\xec\x23\xcf\xe5\x24\xe6\x86\x86\xfd\x65\x95\x0c\x86\xb2\x2a\ -\x46\xae\xf3\xab\x9d\x64\x7b\xb4\x3c\x33\x29\x66\x59\xd2\xf6\xda\ -\xdf\xec\x7f\xf7\xa7\xc5\xfd\xba\x8c\xbe\xd6\x98\x67\xb3\x6b\x79\ -\x00\x51\xc6\x32\x3a\xb8\x0c\x05\x30\xa5\xd5\x04\x62\xec\x65\x4d\ -\xc5\xc4\x4b\x9e\x95\x52\xef\xb9\xa7\x2d\x26\x07\x74\x52\x79\x6f\ -\x39\x20\xf3\xf8\xbb\x7f\x4e\x6e\x5d\xb9\x24\x67\x29\x55\xaf\x22\ -\x2c\xc4\xcf\xd4\x9f\xc3\x0b\x5c\x60\x29\x49\x7b\xa1\x29\xab\x61\ -\x30\x3d\x0b\x68\x69\xeb\xd5\xdc\x45\x93\x33\x42\xf6\x06\xd6\x94\ -\x6f\xcb\xb2\x9d\x12\x4e\x88\xfc\xdf\xfd\x5d\x82\x17\xff\x3a\xfa\ -\xec\x25\x6c\x67\x9e\x4d\x6e\xfa\x77\x17\x01\x95\xd4\x33\xbb\x9c\ -\x01\xb7\x76\x7d\x68\x9d\x42\x0e\x61\x83\xe1\xf8\x77\xe4\xd8\xcb\ -\x48\x11\xb0\x25\xcf\xc1\xc9\x8d\x52\x92\xae\x42\xdb\x0e\x53\x1b\ -\x4b\x54\x63\x87\xa2\xdb\x64\x62\xe0\x72\x38\xea\xae\x67\x90\x00\ -\xf0\x4b\xe0\xaf\xbe\x0e\xfc\x62\xf9\xfe\x25\x80\xbb\x8a\xb5\xa9\ -\x11\x7b\xa7\xb5\x4e\x86\x67\x30\x5b\xf2\xb4\x2c\xda\x99\x89\x5e\ -\xbf\xb9\xff\xcf\xec\x1e\x2a\x6a\x12\xe5\x78\xb7\x06\x78\xa4\x99\ -\x8c\xc4\x38\xe5\xd8\xcd\xd7\x81\x7f\x0a\xbd\x7f\x1d\x61\x29\x13\ -\xda\xb9\x25\xb8\x50\x2a\x6b\x48\xe5\xae\x96\x5e\x57\x17\x23\x03\ -\x88\x44\x71\xb4\x11\x6c\x31\xac\x76\x38\xf7\xa0\x7c\xef\x30\x85\ -\xd9\x3b\x87\x5b\xe7\xb0\xcb\x5c\x23\xa5\x7e\x68\xb6\x3f\xc8\x5a\ -\x0e\xe0\xb8\x7c\x0d\x05\x32\xa7\xec\x45\x0a\x70\xbc\xf7\x78\x03\ -\x7c\x37\xf4\xdd\xe7\xc7\x0f\xe0\xf6\x8f\xed\xc8\xaa\x8f\x04\xa8\ -\xb4\x5a\x13\xdd\x55\xa4\x28\xc0\x20\x7e\x62\x14\x53\x9d\x58\xa9\ -\x04\x65\x1b\x11\x7a\xc8\xb2\x7b\x67\x06\x7c\x4a\xd6\xcc\x06\xb3\ -\x40\x3f\x57\xab\x4a\x51\xbd\x3f\x37\x69\x15\xb2\xcd\xf1\xd4\xd0\ -\x34\xb8\xd4\x4c\x91\x39\x2b\xbc\x01\x70\x15\xfe\xda\xad\x03\x5e\ -\xd4\xb0\xdb\x2e\x9b\xdf\xda\x00\xa6\x06\x5c\x38\x00\xa3\xda\x91\ -\xa9\xc5\x54\x92\xb1\x9e\xb2\xa3\x30\x12\x5d\x27\xfa\xab\x0a\x60\ -\x92\x7d\xcf\x04\x18\x29\x90\x29\xcb\xbd\x23\x03\x2e\x21\x75\x33\ -\x98\xce\xff\xb0\x4f\x4c\x09\x9f\x9a\x6c\x80\x9d\xc0\x65\x58\x1b\ -\x0c\x25\x04\x60\x0d\x79\x3a\xf8\xa1\xf6\x4d\x9f\xa1\xd8\x16\xa3\ -\xd1\xf7\x12\xbe\x25\x65\xc7\xd7\xc7\x2f\x2e\xa8\xa4\x4e\x81\xa6\ -\xb2\x20\x87\x84\x72\x27\xd9\xe4\x46\x51\xdb\x37\xa9\x22\x51\xdd\ -\xbd\x6b\x72\xad\x36\x41\xec\x98\x4f\x04\xfb\xd9\xe6\x7b\x6a\xbc\ -\x6f\x6e\x01\x5c\x15\x56\x2b\x08\x7c\xce\x62\x31\xe4\x3e\x2f\x60\ -\x30\x92\xe0\x55\x5b\x95\x81\xcb\x54\x16\xcf\xeb\x4a\xd7\xc0\xd6\ -\xd8\xcb\xb0\x0c\x86\x47\x71\xb7\x77\xa2\xe4\xe0\x71\x19\xf9\xec\ -\x25\xca\x02\xfe\x22\x9f\x93\x59\x4c\xab\x3e\x9f\x5f\xf7\x2f\x01\ -\xfc\x6f\xc2\x48\xcc\x65\x32\xa5\x8b\x8a\xe2\xaf\x92\x72\x80\xeb\ -\x65\x5f\xe9\x0d\x2e\xdd\x00\x86\xe1\xb3\xa1\x46\xc3\x55\x03\xd5\ -\xaa\x27\x8e\xc3\x21\x59\xf3\x52\xee\x98\x0c\x90\x63\xe3\xa0\x06\ -\x23\x7a\x2d\x7d\x6e\xf6\x5c\x3b\x00\xff\x05\xe0\xcf\x0b\xf5\xc8\ -\x43\xdb\xbe\x37\xfd\xe6\x57\xce\xed\xff\x06\x2f\xaf\x2d\x05\x54\ -\x6a\x6d\x58\xbd\xc0\x65\xb3\x2a\x52\x49\xb6\x33\xa9\x72\x0a\xda\ -\x6a\x52\x29\xb8\x70\x9c\xbf\x24\xcb\xd4\x86\xca\x9b\x84\xd4\xd3\ -\x23\x7d\x8a\xa0\xea\x50\x93\x1b\x15\x19\xa4\x2a\xae\xf1\x1e\x80\ -\x9f\x32\x93\x37\x95\xaa\x40\xa3\x31\xf0\x1e\xf7\x75\x3d\x33\xa4\ -\xd7\x82\x8b\x86\x8e\xde\x8b\x7e\x06\xf2\x74\x1f\x2d\xe9\xd3\x74\ -\x38\xfa\x20\x93\x04\x17\xce\x42\xaf\x04\x6d\x71\x90\x4a\xb7\x5f\ -\x05\x54\xce\x11\x5c\x86\xb2\xc1\x48\x17\x7c\xea\xad\xfa\x95\xaa\ -\x46\x99\x25\x5d\xa4\xee\xd5\x3a\xdc\x8d\x34\x06\xb7\x72\x63\xa5\ -\x6e\x57\x39\x77\x70\x69\x0e\x30\xb5\x6e\xff\x52\x1d\xa5\x61\x8b\ -\x69\x09\x2e\xd2\x20\xe3\xbd\x77\x3b\xec\x1d\xc3\xee\xf7\xfb\xfb\ -\x8d\x07\x76\xbd\x23\x71\x43\xf2\xb2\x72\xcc\x5c\x24\xfd\x81\x84\ -\x5d\xc5\xc0\xa5\xb3\x8a\xc4\x4d\xc9\x90\xdb\x79\xab\x33\x9e\x0b\ -\x32\x25\x29\xbb\x50\x08\x5c\xf2\xd1\x05\x95\xea\xd2\x3e\xc2\x37\ -\xe4\x13\x76\x1c\x97\xd7\x1a\x6c\x16\xcf\xba\x0c\x12\xa4\x3a\x2e\ -\xba\x0c\x62\x6b\x27\xdf\x3a\x57\x70\x69\xca\x60\xa4\x83\x16\xc5\ -\x0a\xb9\x63\x76\x2a\xc2\x38\x0a\xd5\x02\xaa\xa5\xe3\x17\xe5\x72\ -\x02\x4c\xe6\x3a\xf2\xf5\x6b\xb2\x5d\x84\x02\x16\xdc\x3e\x5e\x74\ -\xc4\x6b\x5e\x09\x97\xbd\x0a\x94\xc9\xab\x62\xe0\xb2\x11\x06\x33\ -\x1a\x7b\xc9\x2e\x18\xc5\x14\x8e\x23\xb0\xa8\xa3\xef\x02\xf7\x00\ -\x9e\x04\xbe\xf6\x06\xc0\xd3\x92\x7e\x21\xf5\x33\xc7\x51\x8f\x60\ -\xe8\xa7\x78\x24\x8f\x90\x2a\xf4\x5c\xc0\xa5\x19\x83\xa9\x05\x97\ -\xad\x4a\x4f\x3f\x9f\xc5\xfd\x5e\x45\xbe\xf6\xaa\x43\xa7\xc4\xde\ -\x7f\x48\xdc\x74\xb0\x11\x2d\xd9\x4a\x6b\xbb\x8a\x81\xcb\x20\x0c\ -\x66\x48\xf6\x22\xc0\x60\xc8\x00\x11\xca\x4b\xab\x38\x09\xb8\x4c\ -\xc6\xc7\xe3\xf2\x4e\x73\x23\x69\x33\x98\xf0\x77\x83\xcf\x97\x4a\ -\xdc\xd4\x12\x4c\x46\x58\xe4\xa3\x66\x7c\xbc\x18\x1d\x5c\xb6\xc8\ -\x3e\xb4\xeb\x39\x71\x99\x8c\xc3\x83\xe3\xf0\xed\x63\x44\x1e\x20\ -\x02\x2e\x32\x72\x4d\x7c\xf3\x90\xac\xc9\xf5\x9a\x13\x06\x2e\x2d\ -\x19\x4c\xa0\xc4\x48\x09\xc0\xf4\x4a\xb5\x20\x06\x30\x02\x9e\xbe\ -\x2d\x6d\x32\x10\xc8\x15\x93\xed\x6b\xa2\xfd\xc5\x01\x3e\x67\x20\ -\xf2\xde\xbb\xde\x8b\xac\xc7\xfd\xd7\x90\xa7\x5a\x95\xc1\x48\x83\ -\x8b\xc2\x56\xaf\x0f\x2e\xfd\x28\xd6\xd1\xf1\x09\x85\xc9\xa8\xb6\ -\x85\x79\x34\xe6\x80\x87\x13\xa0\x98\x21\xe8\x09\x70\x7b\x00\xc0\ -\xe6\x73\x27\xa3\x12\x19\xb8\x28\x33\x18\x4a\x48\x40\x49\x91\x74\ -\xcd\x4e\xe4\x3e\x8f\x67\xba\xac\x37\x63\x2f\xa7\x2c\x80\x75\xdf\ -\x65\x2a\x07\xb4\xb3\xd5\x79\xb2\x01\xe6\x60\x86\x99\xe5\x56\x69\ -\xbd\xf8\x8c\xb5\x74\x62\x30\xd4\x78\xa3\x92\x9d\x61\x95\xe2\x7d\ -\xd7\x3a\xda\x1c\x26\x93\x62\x03\x1a\x8c\x20\xe7\xb2\xef\xbd\x77\ -\x94\xc4\x4d\x2d\x9f\x5f\xa2\x4c\xc8\x1a\xee\x39\x26\x83\x59\xd8\ -\x5d\x76\x08\x7b\x5f\x8e\xc6\x5e\x28\x0c\xa5\x22\x9f\x48\xdb\x09\ -\x21\x96\x04\x2b\x1e\x69\xad\xc1\x54\x96\xc0\xa2\xb9\xd9\x95\xb6\ -\xa1\x07\xb0\x48\x3e\x7f\x6b\x79\x4b\x13\x5c\x96\xb3\xb3\xa6\x53\ -\x46\x4a\x4e\x3e\x34\xb8\x64\x98\x8c\x9b\x3d\x57\xed\x22\x2b\x08\ -\x89\x68\xee\xb2\x1f\x4b\x10\xcf\x4f\x67\xda\x0e\x58\xa4\x34\x80\ -\x6d\x01\x4c\x02\x5c\x6a\x6d\x1d\x6d\xb5\x99\xc7\x3c\x28\x35\x8b\ -\x70\x34\x70\x59\xb6\x8f\xfb\x3c\xa1\xfc\x30\x14\xa0\xe9\x01\x2a\ -\x1c\xa0\xe1\xb0\x56\xcd\x85\xae\x51\x4c\x6d\x53\x00\x93\x03\x97\ -\x35\x7b\xe7\xae\xce\x4f\x27\x56\x3a\xc4\x7b\x56\x1b\xa6\xe3\x5f\ -\x7f\x00\x8a\x03\x18\xe4\x80\x66\x14\x50\xc9\x2d\xd4\x1c\xd8\x68\ -\x2f\xf4\xad\x82\x8a\xb8\x0d\x66\x11\xd7\x12\x9f\xf4\x03\xdb\x5e\ -\xa4\x29\xf1\x56\x42\x1f\x52\xa5\x66\x63\xbe\x33\x23\x02\x4b\xed\ -\x62\xaf\x99\x8f\x9a\xd7\x3e\x0b\x80\x79\x50\x27\x92\x0c\xe7\xb8\ -\xda\xd1\xe8\x49\xbd\x6b\x00\x66\x6b\x71\x55\x19\x90\xf1\x6b\x05\ -\x95\xde\xea\xf8\x56\x41\x65\x2e\x17\x92\x9d\x94\x82\x18\x0f\x87\ -\xff\x01\x1e\x12\x2f\x8f\x0c\x2e\xa1\xfb\x72\x9e\xc3\x33\x55\x91\ -\x35\x02\x4e\x26\xc0\xf0\xa8\x24\x2a\xa5\x0c\x6a\x6b\x40\x49\x3d\ -\x53\xa8\x0c\xac\x54\x30\xaa\xc6\x75\xcf\x82\xc1\xec\x07\x2e\x0c\ -\x2c\x29\x95\x69\x54\x80\x39\x62\x66\x67\x12\xdd\x4d\x65\x31\x14\ -\xa6\xe2\x0a\x4a\x8c\xf4\x66\x28\x36\xce\xf2\xf2\xd6\xc8\x0f\x37\ -\xc2\x80\xdb\xa4\x2b\xb3\xab\x94\x96\x44\xa9\xed\xf3\x73\xb6\x77\ -\x18\xc0\x0c\xae\x0b\x9b\xa4\x81\xe4\xc0\x62\x4a\xec\x2a\x9e\x97\ -\x8d\xce\x36\x0b\x03\x18\x13\x63\x2b\xb2\x8b\x5d\x12\x58\x0c\x4c\ -\x06\x21\x0d\xda\x36\x18\x20\x60\x87\x21\x4c\x2e\x9b\x20\x26\x26\ -\xc6\x60\x4e\x70\x23\x04\x32\x0e\x3e\x1e\xa1\xff\xf9\xe7\xc0\x8f\ -\x7e\x04\x0f\xe0\x8b\xb7\xdf\xc6\x7f\xee\x76\xf8\x37\x03\x17\x13\ -\x13\x63\x30\xd5\xf2\xd9\x67\xc0\x3b\xef\x84\x3f\xfb\xf4\x53\xe0\ -\xd9\x33\x1b\x21\x13\x93\x15\x4b\xdf\xca\x8e\x3f\xf8\x41\xd9\x67\ -\x26\x26\x26\xc6\x60\xb2\xf2\xf6\xdb\xc0\xfd\x7d\xf8\xb3\xa7\x4f\ -\x81\xdf\xff\xde\x46\xc8\xc4\xc4\x18\x4c\xa1\x7c\xe7\x3b\x65\x9f\ -\x99\x98\x98\x18\x83\xc9\x8a\xd9\x60\x4c\x4c\x8c\xc1\xa8\xc9\xb3\ -\x67\xf8\xe4\xc3\x0f\xf1\xf1\xbb\xef\x02\x00\xbe\xf8\xca\x57\x80\ -\x97\x2f\x81\xdf\xfe\xd6\xc0\xc5\xc4\xc4\x18\x8c\x89\x89\x89\xc9\ -\xa8\x0c\xc6\xc4\xc4\xc4\x00\xc6\xc4\xc4\xc4\xc4\x00\xc6\xc4\xc4\ -\xc4\x00\xc6\xc4\xc4\xc4\x00\xc6\xc4\xc4\xc4\xc4\x00\xc6\xc4\xc4\ -\x64\x4c\xf9\x7f\x2d\xef\x00\x39\x64\x58\x99\x8e\x00\x00\x00\x00\ -\x49\x45\x4e\x44\xae\x42\x60\x82\ -" - -qt_resource_name = "\ -\x00\x07\ -\x07\x3b\xe0\xb3\ -\x00\x70\ -\x00\x6c\x00\x75\x00\x67\x00\x69\x00\x6e\x00\x73\ -\x00\x03\ -\x00\x00\x77\x74\ -\x00\x71\ -\x00\x61\x00\x64\ -\x00\x05\ -\x00\x6f\xa6\x53\ -\x00\x69\ -\x00\x63\x00\x6f\x00\x6e\x00\x73\ -\x00\x09\ -\x0c\xba\x53\xc3\ -\x00\x64\ -\x00\x73\x00\x65\x00\x74\x00\x74\x00\x69\x00\x6e\x00\x67\x00\x73\ -\x00\x17\ -\x0d\x56\x54\xc7\ -\x00\x4f\ -\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x54\x00\x6f\x00\x6f\x00\x6c\x00\x54\x00\x49\x00\x50\x00\x5f\x00\x50\x00\x41\x00\x52\ -\x00\x4c\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x16\ -\x09\x37\x37\x87\ -\x00\x4f\ -\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x54\x00\x6f\x00\x6f\x00\x6c\x00\x54\x00\x49\x00\x50\x00\x5f\x00\x4e\x00\x4f\x00\x44\ -\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0e\ -\x06\x3f\x8a\xe7\ -\x00\x4f\ -\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x50\x00\x41\x00\x52\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0e\ -\x0d\xdf\x85\x27\ -\x00\x4f\ -\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x45\x00\x58\x00\x54\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0e\ -\x06\x7f\x8b\x67\ -\x00\x4f\ -\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x54\x00\x41\x00\x4e\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x10\ -\x02\xe5\x2c\xc7\ -\x00\x4f\ -\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x45\x00\x58\x00\x54\x00\x49\x00\x4e\x00\x54\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0e\ -\x09\xdf\x82\x47\ -\x00\x4f\ -\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x4d\x00\x45\x00\x44\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0e\ -\x02\xdf\x85\x27\ -\x00\x4f\ -\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x45\x00\x4e\x00\x44\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0e\ -\x0a\x3f\x8a\xe7\ -\x00\x4f\ -\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x50\x00\x45\x00\x52\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x16\ -\x07\x37\x08\xa7\ -\x00\x4f\ -\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x54\x00\x6f\x00\x6f\x00\x6c\x00\x54\x00\x49\x00\x50\x00\x5f\x00\x45\x00\x58\x00\x54\ -\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x18\ -\x0a\x68\xb9\x07\ -\x00\x4f\ -\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x54\x00\x6f\x00\x6f\x00\x6c\x00\x54\x00\x49\x00\x50\x00\x5f\x00\x45\x00\x58\x00\x54\ -\x00\x49\x00\x4e\x00\x54\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x16\ -\x07\x37\x37\xc7\ -\x00\x4f\ -\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x54\x00\x6f\x00\x6f\x00\x6c\x00\x54\x00\x49\x00\x50\x00\x5f\x00\x4d\x00\x49\x00\x44\ -\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x10\ -\x0a\x81\xb0\xc7\ -\x00\x4f\ -\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x49\x00\x4e\x00\x54\x00\x41\x00\x50\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x16\ -\x09\x37\x08\x27\ -\x00\x4f\ -\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x54\x00\x6f\x00\x6f\x00\x6c\x00\x54\x00\x49\x00\x50\x00\x5f\x00\x49\x00\x4e\x00\x54\ -\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0f\ -\x07\x0e\x8f\xa7\ -\x00\x4f\ -\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x50\x00\x52\x00\x4f\x00\x47\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x16\ -\x04\x97\x09\x07\ -\x00\x4f\ -\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x54\x00\x6f\x00\x6f\x00\x6c\x00\x54\x00\x49\x00\x50\x00\x5f\x00\x43\x00\x45\x00\x4e\ -\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x1a\ -\x0d\x9e\x6d\x07\ -\x00\x4f\ -\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x54\x00\x6f\x00\x6f\x00\x6c\x00\x54\x00\x49\x00\x50\x00\x5f\x00\x43\x00\x45\x00\x4e\ -\x00\x54\x00\x52\x00\x4f\x00\x49\x00\x44\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x1a\ -\x0a\x98\x67\x27\ -\x00\x4f\ -\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x54\x00\x6f\x00\x6f\x00\x6c\x00\x54\x00\x49\x00\x50\x00\x5f\x00\x50\x00\x52\x00\x4f\ -\x00\x47\x00\x52\x00\x45\x00\x53\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x16\ -\x08\x37\x08\xa7\ -\x00\x4f\ -\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x54\x00\x6f\x00\x6f\x00\x6c\x00\x54\x00\x49\x00\x50\x00\x5f\x00\x45\x00\x4e\x00\x44\ -\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x16\ -\x00\xd7\x07\x67\ -\x00\x4f\ -\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x54\x00\x6f\x00\x6f\x00\x6c\x00\x54\x00\x49\x00\x50\x00\x5f\x00\x50\x00\x45\x00\x52\ -\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0f\ -\x02\xbe\x00\x67\ -\x00\x4f\ -\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x4e\x00\x45\x00\x41\x00\x52\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0e\ -\x0a\x7f\x84\x87\ -\x00\x4f\ -\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x43\x00\x45\x00\x4e\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x1b\ -\x05\x30\x56\xa7\ -\x00\x4f\ -\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x54\x00\x6f\x00\x6f\x00\x6c\x00\x54\x00\x49\x00\x50\x00\x5f\x00\x45\x00\x4e\x00\x44\ -\x00\x5f\x00\x50\x00\x4c\x00\x49\x00\x4e\x00\x45\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x17\ -\x0f\xd6\x51\x27\ -\x00\x4f\ -\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x54\x00\x6f\x00\x6f\x00\x6c\x00\x54\x00\x49\x00\x50\x00\x5f\x00\x51\x00\x55\x00\x41\ -\x00\x44\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0e\ -\x03\xdf\x82\x07\ -\x00\x4f\ -\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x4e\x00\x4f\x00\x44\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0e\ -\x03\xdf\x85\xa7\ -\x00\x4f\ -\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x49\x00\x4e\x00\x54\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x16\ -\x00\x97\x06\x87\ -\x00\x4f\ -\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x54\x00\x6f\x00\x6f\x00\x6c\x00\x54\x00\x49\x00\x50\x00\x5f\x00\x54\x00\x41\x00\x4e\ -\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0e\ -\x03\xcf\x85\xa7\ -\x00\x4f\ -\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x49\x00\x4e\x00\x53\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0f\ -\x01\x5e\x88\x67\ -\x00\x4f\ -\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x51\x00\x55\x00\x41\x00\x44\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x17\ -\x0c\x35\x59\x27\ -\x00\x4f\ -\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x54\x00\x6f\x00\x6f\x00\x6c\x00\x54\x00\x49\x00\x50\x00\x5f\x00\x4e\x00\x45\x00\x41\ -\x00\x52\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ -" - -qt_resource_struct = "\ -\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ -\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ -\x00\x00\x00\x14\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\ -\x00\x00\x00\x20\x00\x02\x00\x00\x00\x01\x00\x00\x00\x04\ -\x00\x00\x00\x30\x00\x02\x00\x00\x00\x1e\x00\x00\x00\x05\ -\x00\x00\x04\xba\x00\x00\x00\x00\x00\x01\x00\x00\x99\x29\ -\x00\x00\x03\x8e\x00\x00\x00\x00\x00\x01\x00\x00\x78\x92\ -\x00\x00\x05\x0e\x00\x00\x00\x00\x00\x01\x00\x00\xa0\x48\ -\x00\x00\x03\xc0\x00\x00\x00\x00\x00\x01\x00\x00\x7f\x40\ -\x00\x00\x01\x5c\x00\x00\x00\x00\x00\x01\x00\x00\x21\xc6\ -\x00\x00\x01\x14\x00\x00\x00\x00\x00\x01\x00\x00\x1f\xf0\ -\x00\x00\x04\xec\x00\x00\x00\x00\x00\x01\x00\x00\x9f\x1d\ -\x00\x00\x04\x76\x00\x00\x00\x00\x00\x01\x00\x00\x96\x8b\ -\x00\x00\x04\x98\x00\x00\x00\x00\x00\x01\x00\x00\x98\x43\ -\x00\x00\x02\xb6\x00\x00\x00\x00\x00\x01\x00\x00\x57\xb1\ -\x00\x00\x04\x06\x00\x00\x00\x00\x00\x01\x00\x00\x82\x1a\ -\x00\x00\x00\xae\x00\x00\x00\x00\x00\x01\x00\x00\x1c\xb7\ -\x00\x00\x00\xf2\x00\x00\x00\x00\x00\x01\x00\x00\x1e\x51\ -\x00\x00\x02\x92\x00\x00\x00\x00\x00\x01\x00\x00\x56\xa2\ -\x00\x00\x01\xa0\x00\x00\x00\x00\x00\x01\x00\x00\x23\x91\ -\x00\x00\x02\x08\x00\x00\x00\x00\x00\x01\x00\x00\x30\x12\ -\x00\x00\x03\x5c\x00\x00\x00\x00\x00\x01\x00\x00\x68\x8f\ -\x00\x00\x02\x60\x00\x00\x00\x00\x00\x01\x00\x00\x41\x8c\ -\x00\x00\x00\x7c\x00\x00\x00\x00\x00\x01\x00\x00\x06\x10\ -\x00\x00\x01\x3a\x00\x00\x00\x00\x00\x01\x00\x00\x21\x00\ -\x00\x00\x01\x7e\x00\x00\x00\x00\x00\x01\x00\x00\x22\x7a\ -\x00\x00\x01\xd2\x00\x00\x00\x00\x00\x01\x00\x00\x29\x99\ -\x00\x00\x03\xe4\x00\x00\x00\x00\x00\x01\x00\x00\x80\x79\ -\x00\x00\x02\x3a\x00\x00\x00\x00\x00\x01\x00\x00\x40\x8e\ -\x00\x00\x03\x22\x00\x00\x00\x00\x00\x01\x00\x00\x63\xb3\ -\x00\x00\x05\x32\x00\x00\x00\x00\x00\x01\x00\x00\xa1\xa1\ -\x00\x00\x00\x48\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ -\x00\x00\x02\xe8\x00\x00\x00\x00\x00\x01\x00\x00\x5d\xa1\ -\x00\x00\x00\xd0\x00\x00\x00\x00\x00\x01\x00\x00\x1d\x73\ -\x00\x00\x04\x42\x00\x00\x00\x00\x00\x01\x00\x00\x91\xf0\ -" - -def qInitResources(): - QtCore.qRegisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) - -def qCleanupResources(): - QtCore.qUnregisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) - -qInitResources() +# -*- coding: utf-8 -*- + +# Resource object code +# +# Created by: The Resource Compiler for PyQt5 (Qt v5.15.2) +# +# WARNING! All changes made in this file will be lost! + +from PyQt5 import QtCore + +qt_resource_data = b"\ +\x00\x00\x03\xfe\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x14\x00\x00\x00\x23\x08\x06\x00\x00\x00\x94\xf6\x2a\x18\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ +\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ +\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdd\x06\x13\ +\x0e\x13\x29\x86\x5b\x5c\x74\x00\x00\x00\x45\x69\x54\x58\x74\x43\ +\x6f\x6d\x6d\x65\x6e\x74\x00\x00\x00\x00\x00\x43\x52\x45\x41\x54\ +\x4f\x52\x3a\x20\x67\x64\x2d\x6a\x70\x65\x67\x20\x76\x31\x2e\x30\ +\x20\x28\x75\x73\x69\x6e\x67\x20\x49\x4a\x47\x20\x4a\x50\x45\x47\ +\x20\x76\x36\x32\x29\x2c\x20\x71\x75\x61\x6c\x69\x74\x79\x20\x3d\ +\x20\x39\x30\x0a\x67\x9d\x47\x40\x00\x00\x03\x2d\x49\x44\x41\x54\ +\x48\xc7\xd5\x55\x4d\x4c\x13\x51\x10\xfe\xde\xdb\x6d\x11\xda\xd2\ +\x96\x18\x25\x21\x1a\x20\x68\x09\x61\x95\x18\x35\x69\x39\x68\x48\ +\x90\x83\x26\x26\x06\x12\xbd\x70\xe2\x68\x22\x9c\xf4\x62\x4c\xbc\ +\xe1\x85\x78\xf2\x02\x37\x4d\x4c\xb8\x0a\x07\x21\xa4\x10\x28\x1c\ +\x20\x62\xb7\x11\x6d\x54\x2a\x22\x3f\x0a\xb1\x14\xcd\xd2\xdd\xed\ +\x8e\x07\x6d\x5d\xd7\xa5\xb4\xe0\xc5\x2f\x79\x87\x7d\x3b\xf3\xbd\ +\x6f\xe6\xbd\x99\x01\x2c\x18\x18\x18\x00\x00\x84\xc3\xe1\x0e\x22\ +\x90\xdd\x9a\x98\x98\xe8\x22\x22\x14\x84\xc9\xc9\xc9\xd3\xbb\x11\ +\x59\xd7\xec\xec\xec\x15\xab\x3f\x33\x7f\x10\xe1\x8f\x63\xa7\x23\ +\x3d\xb6\x87\x06\x43\x7d\x7f\x92\xb0\xdf\x3c\xcc\x8e\xcc\x4c\x14\ +\x6a\xee\x63\x92\x24\x39\x01\x94\xcb\xb2\xbc\x11\x99\xea\x21\x3b\ +\xe2\x1c\x69\x34\x1a\xc5\xcc\xcc\x4c\x2e\xcc\xc8\x54\x0f\x99\x9d\ +\x00\x40\x92\xa4\x43\x92\x24\x95\x9b\xf7\xb2\x76\x59\xbf\xf1\xf1\ +\xf1\x9a\xce\xce\xce\x9f\xac\x59\x75\x59\x65\xa1\xe6\x3e\x66\x21\ +\xac\x04\x00\x59\x96\xd7\xac\xa4\x66\xa5\x8c\x81\xf1\x68\x34\x1a\ +\x32\x1b\x59\xc9\x7e\x61\xf5\xd7\x42\x3e\xdb\x70\x38\x7c\x82\x4b\ +\xd2\xa9\xa9\x7c\x17\x50\x08\xb2\xbe\x17\x2e\x5c\x8c\xf3\x02\xd4\ +\xe5\x85\xd5\x87\xe3\x1f\xe3\x3f\x23\xb4\xbe\xbf\x03\x11\x5a\xcb\ +\xa9\x50\x58\x45\x70\x73\x1d\xee\x17\x59\x31\x0b\x0b\xaf\xce\xf0\ +\xbd\x4e\x2c\x46\x5d\x43\x43\xc3\x0b\x0e\x00\x8b\x8b\xef\xcf\xed\ +\x37\xec\xac\xcf\xe8\xe8\xc8\xd9\x5c\x0e\x6b\x6b\x6b\x67\x8b\x51\ +\x19\x99\xea\x71\x58\x6d\x5a\x5b\x5b\xe7\x72\x84\x89\x44\x22\xd7\ +\x7e\xec\x54\x12\x11\x27\x22\x73\x7a\x74\x6b\x53\x98\x9f\x9f\xff\ +\x7d\xcb\xd5\xd5\xd5\x79\xf3\xc3\x18\x3b\xce\x18\xab\x33\xfd\x36\ +\xac\xf6\x4d\x4d\x4d\x7f\xbf\xc3\x3c\x2a\x13\x00\xe2\x76\xb9\xdb\ +\xda\x4a\x1e\xde\x77\xa5\x34\x36\x36\xfe\xb5\xe7\xf3\xf9\x36\x77\ +\x25\x8c\xc7\xe3\xb6\x61\xcb\xb2\xcc\x64\x59\x66\xb1\x58\xac\xf8\ +\x6a\x32\x4f\x35\x3b\x67\x73\xdb\xb7\x0e\x35\x00\x10\x8b\x7d\xbc\ +\x56\x8c\x8d\x8d\xa1\xa5\xa5\xc5\x7e\x8c\x66\x31\x38\x38\x78\x67\ +\x68\x68\xe8\x06\x00\xe2\x9c\x63\xfb\x5b\x12\xa2\x28\x02\x44\x10\ +\x04\x01\x9a\xa6\x39\xda\xdb\xaf\xbf\xed\xe8\xe8\xb8\x9a\x77\x2e\ +\x03\xc0\xf4\x74\x04\xcf\x9f\x8f\x04\x54\x55\x7d\xcd\x39\x87\x20\ +\x70\xac\xac\x7c\x84\x96\xc9\x80\x81\x83\x0c\x03\x82\x20\x62\x63\ +\x63\xf3\x76\x6f\x6f\xef\x83\x40\x20\x90\x3f\xe4\x60\x30\x84\xc5\ +\xf5\x2f\x4b\x4f\x1f\x3d\x7c\x66\x18\xba\xa1\xeb\xc6\x65\x97\xb3\ +\x44\xf0\x1f\x39\x0a\x32\x08\x82\x28\x42\x51\x14\x28\x4a\x7a\xc9\ +\x4a\xb6\x6b\x0e\xdf\xbe\x9c\x6b\xfa\xbe\xa3\x5d\x51\xb5\x34\x54\ +\x4d\xa5\xb4\xae\x61\x79\x39\x01\x32\x18\x4a\x4b\x4b\xa1\xec\x68\ +\x68\x6b\xbb\x34\xf6\xe4\xc9\xe3\xbd\x3b\x76\x4c\x96\xb1\xfa\xe9\ +\xf3\x8e\xc1\x38\x0c\x26\x6c\x73\xd1\xc9\x04\xd1\x41\x65\x65\x1e\ +\x94\xb9\xdc\x70\x38\x1d\x54\x52\xe2\xc0\xdd\x7b\xf7\x53\x5d\x5d\ +\x5d\x7b\x13\x36\x4a\x12\xca\xbc\xee\x54\x89\x43\xdc\xf2\xba\xdd\ +\xa9\x8a\x72\xff\x1b\x8f\xbb\x9c\xf9\xfd\x15\x38\x19\xa8\x57\x2b\ +\x8f\x56\xae\x67\x32\x84\xe6\xe0\xf9\xba\xfe\xfe\xfe\xc2\x66\x4a\ +\xfb\xb5\x6b\xef\x34\x83\x3e\xa8\x99\x4c\x95\xa2\xa7\x6b\x52\xdb\ +\x5f\xf1\x65\x63\x9d\x5e\x2f\xc4\x9c\xab\x6b\xeb\x95\x44\x84\x64\ +\x32\xb9\x54\xd4\x90\x12\x04\xe1\x26\xc0\xa0\x28\x8a\x53\xd5\x54\ +\xa4\xd3\x69\x46\x44\x86\xcb\xe5\x81\xae\xeb\xc1\xe1\xe1\xe1\x54\ +\xd1\xcd\x33\x14\x0a\x55\xf9\xfd\x7e\xf2\xf9\x7d\xe4\xf5\x79\xc9\ +\xe3\xf1\x1c\x78\x88\x21\xad\x2a\x15\x5e\x8f\xab\x39\x50\x7b\xac\ +\xed\x56\x77\x77\xfd\x5e\xf6\x3f\x00\x80\xfe\xb6\xe8\x7b\x70\xcf\ +\x26\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x06\x75\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x01\x18\x00\x00\x00\xa0\x08\x06\x00\x00\x00\xd7\x60\x73\x20\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ +\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ +\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x01\x1b\ +\x0a\x13\x2e\xee\x36\xe7\x5a\x00\x00\x00\x45\x69\x54\x58\x74\x43\ +\x6f\x6d\x6d\x65\x6e\x74\x00\x00\x00\x00\x00\x43\x52\x45\x41\x54\ +\x4f\x52\x3a\x20\x67\x64\x2d\x6a\x70\x65\x67\x20\x76\x31\x2e\x30\ +\x20\x28\x75\x73\x69\x6e\x67\x20\x49\x4a\x47\x20\x4a\x50\x45\x47\ +\x20\x76\x36\x32\x29\x2c\x20\x71\x75\x61\x6c\x69\x74\x79\x20\x3d\ +\x20\x39\x30\x0a\x67\x9d\x47\x40\x00\x00\x05\xa4\x49\x44\x41\x54\ +\x78\xda\xed\xdd\x51\x6e\xe3\x36\x10\x06\x60\xd2\xb0\x5f\xdb\x5b\ +\x74\x8f\xab\x1b\xe4\x06\x7b\xa1\xe6\x14\x0e\xda\x02\xfb\xa4\x3e\ +\x6c\x80\x66\xd5\x24\x8e\x6c\x51\xe2\x70\xbe\x0f\xc8\x43\x80\xc4\ +\xb6\xa8\xe1\xaf\xa1\x2c\xd9\xa5\x00\x00\x44\x53\x1b\x3d\xee\xbc\ +\xc3\x73\x00\x9d\x3b\xed\xf0\x1c\xb3\x61\x06\x01\x23\x64\x80\x10\ +\x4b\xa4\x8f\x42\xc5\x72\x09\x04\x8c\x90\x01\xfa\x0f\x18\x21\x03\ +\x02\x46\xc8\x00\x71\x03\x46\xc8\x80\x80\x11\x32\x40\xdc\x80\x11\ +\x32\x41\xcd\x76\x18\x41\x02\x46\xc8\x04\x0f\x17\x3b\x8c\xaf\x3a\ +\x1d\xf4\xbc\x75\x45\x1d\x73\xb0\x67\xe1\x42\xb0\x80\x11\x32\x60\ +\x89\x64\xb9\x84\x1d\x44\xcc\x0e\x46\x27\xe3\x28\x84\xda\x71\xa0\ +\x04\xc6\x38\x38\x09\x19\x10\x30\x42\x06\x88\xbb\xbc\x16\x32\x20\ +\x60\x84\x0c\x10\x33\x60\x84\x0c\x08\x18\x21\x03\xc4\x0d\x18\x21\ +\x03\x02\x46\xc8\x00\xbf\x3a\x05\x7a\xad\xae\xf8\x4d\xc4\x8e\xd5\ +\xc1\xe8\x64\x80\xe1\x02\x46\xc8\x80\x25\x92\xe5\x12\x96\x4b\x3a\ +\x98\xf1\x6a\x4f\x27\x03\x3a\x18\x01\x09\x02\x46\xc8\x00\xc0\xfe\ +\xeb\x73\xe7\x87\x1c\xfd\xa1\x49\xb8\x98\x48\xb7\x9d\x93\xd4\x80\ +\xfd\x4f\xb3\x70\xe1\x63\x27\x35\x01\x0a\x49\xc0\xa8\x0d\x08\x67\ +\xf4\xa5\x83\xeb\x64\x68\x5e\x54\x0a\x2a\x6f\xc0\xa8\x07\x14\x92\ +\x25\x92\x10\x85\x11\x9d\x93\x6c\xa7\x90\xe1\xa1\xae\xe5\xc5\x50\ +\x98\x78\xf4\x35\x31\xeb\x20\xdb\x61\xf2\x58\x22\xdd\x5b\x3b\x2e\ +\xc6\x74\xe4\xfa\xd4\xcb\x3b\xbf\x57\xe1\x22\x60\x36\x3c\x50\x91\ +\xd8\xd3\xe2\xf7\xdf\x0c\x09\x77\x74\x30\x3a\x19\xe9\xfd\xae\x4b\ +\x29\x65\x2a\xa5\x5c\x1f\x28\x92\xcc\x05\x56\xd5\xbe\x31\x61\x9f\ +\x02\xcb\x58\x58\xd9\x97\x48\xc2\x04\x85\x26\x60\xec\x7b\xe2\x2e\ +\xdf\x32\x7f\xbe\xab\xc9\x85\x70\xd9\x79\x22\xcc\x89\x26\x9f\x0e\ +\x06\x9d\x4b\x82\xe7\x15\x30\x7d\xd6\x60\xba\x93\xff\xdf\x84\xcb\ +\x70\x1d\x13\xfd\x07\xcc\x9c\x65\x83\xbf\x27\xdb\xa9\xae\x4f\xa0\ +\xa7\x5a\x1c\x7e\x63\xff\x76\xe4\x60\x43\x3a\xb5\xf5\xdd\x74\x1d\ +\x7d\x43\x15\x05\x5b\x39\x1b\x82\x9b\x01\x3c\xfc\x41\x2e\xc3\x51\ +\xdc\x15\x95\xc7\x70\x92\x37\x79\x97\xf7\xbd\x94\xf2\xcf\x27\x1b\ +\x6b\x09\x01\x70\xc3\xdb\x7b\x89\x9c\x7b\xd1\xc1\xc0\xa6\x9e\x0c\ +\x01\x01\x97\xf5\xa9\x0e\x86\xd9\xef\x8a\x66\x3d\xe7\xb9\xb6\x9b\ +\x6f\xc6\x12\x2c\x91\x1c\xe0\x41\x07\x13\x37\x54\x8c\x29\xe8\x60\ +\x04\x34\xa4\x9a\x20\x81\xdb\x01\x17\xc2\x42\xcf\xf3\xd7\xd7\x43\ +\x70\x6f\xcd\xa8\x0f\x4b\x24\x68\x76\x40\x72\x76\xbd\x5f\xee\x45\ +\xb2\x6c\x0a\x3d\xb0\xe8\x60\x30\x27\xc2\x0d\xe4\xa5\x94\xf2\x57\ +\xf9\x79\x61\xde\xf4\xfa\x3b\xc1\x0b\x67\xb0\x0f\x03\xf2\xb9\x46\ +\x3b\xd6\xc9\xd6\x7e\x2c\x1e\x7f\x32\xf4\x63\x16\x8f\x4d\xd2\x42\ +\x1d\x31\xa0\xcb\x9b\x23\xaf\xa6\x67\xfc\x25\x52\x7d\xe7\x27\xb8\ +\xea\x85\x6c\xeb\x65\xa7\xe7\xf1\x95\xb1\x44\x3d\xf0\xf2\x80\x69\ +\xa7\x0e\xc6\xcd\x91\x68\xe7\x13\x2e\x97\x4c\x7c\x2d\x3c\xc5\x85\ +\x83\xa0\xd6\x0f\xec\x10\xaa\xeb\x64\x48\xc0\x75\x30\x5f\x5c\xe6\ +\xfc\xb9\x61\x3b\x5e\xc7\x5a\xb5\xa8\x05\x74\x30\x5b\x76\x22\x75\ +\x9b\xff\xf3\x51\x0f\x09\x6a\x41\x07\xc3\x51\x49\x5e\x4b\xf9\xf5\ +\xa4\xe5\x54\xca\x3c\xbb\x68\xd4\xd1\x99\x5c\x2d\xf1\xda\x77\x82\ +\xd6\xfc\xfd\x6b\xa8\xbc\xfd\x71\xd1\x68\x80\x5a\x80\xc3\x02\xa6\ +\xac\xfb\xbb\xeb\x22\x60\xae\x59\xc7\xd8\xeb\xb4\x44\x4a\xdb\x02\ +\x3f\xaf\x6c\x8b\x57\xb4\xcf\x4f\x37\x7e\x1f\x3a\x5c\xee\x09\xe5\ +\x23\x6b\xc1\xb2\x88\x68\x93\xec\x32\x97\x32\xbd\x76\x32\x53\x96\ +\x73\x30\x83\xdd\xd8\xca\xe3\x07\x5a\x68\xd7\xbd\x28\x48\x4b\x24\ +\x3a\x58\xce\x3b\xca\x23\x60\x68\x96\x0f\x23\xdf\x15\xad\x7b\x11\ +\x30\x1c\x18\x2e\x23\xf1\x5d\xd1\xd0\x66\x59\xb4\xd9\xf9\x4c\x77\ +\x45\xd3\x3b\x5d\xe9\x31\xdd\x8b\x71\xc7\x12\x09\xa1\x0e\x8a\x1d\ +\x2d\x1b\x02\x86\x9c\xe1\xa2\xd8\xf2\xf2\xc5\x6b\x6d\xe7\x97\x39\ +\x45\x6a\xce\xc1\xec\x73\xf0\x06\x01\x83\x70\x81\x2d\x69\xe1\xdb\ +\x85\x4b\x35\x20\x06\x43\xc0\x60\x3e\x41\x23\x4e\xf2\x0a\x6d\xad\ +\x1b\x02\x46\xa8\x08\x08\xe2\x71\x92\x97\x87\xbc\x18\x02\x04\xcc\ +\xe6\x07\xfd\xb4\xf7\xe7\x2d\xef\xd8\x74\x57\x34\xb4\x9b\x5f\xc3\ +\x6d\xd0\xbc\xf2\x6f\xe7\xe2\xae\x68\x06\x3c\x77\x70\xd0\x5c\x1c\ +\x6e\xfc\xd6\x6c\xd4\x9e\xb7\x00\x38\x37\x34\x06\x27\x79\x93\xd7\ +\xbb\x49\x4b\x4b\xce\xc1\x98\x97\x20\x60\x84\x4b\x9f\x1b\x2e\x69\ +\x31\x59\xb0\x26\x05\x40\x07\x93\xe9\xe0\x69\x7c\xe0\x01\xce\xc1\ +\xdc\xee\xcc\x81\x3b\x79\x9b\x5a\xb8\x10\xbc\x50\x7b\x6e\xb3\x2d\ +\x01\x3e\x0e\x97\x94\x77\x45\x2b\x88\x98\x47\xc1\x5e\xf7\x9b\x25\ +\x92\xe0\x75\xb4\x41\xc0\x98\x67\x36\x1a\x52\xb7\xae\x93\x61\x00\ +\x5a\x84\xcb\x5c\x7e\xde\x51\x0c\xe4\x5e\x22\x6d\xf6\x71\x0b\xd3\ +\xe2\x41\x7c\x36\x0a\xe4\x5e\x7a\x6f\xfa\x0e\x9f\xcb\xd9\x41\xc0\ +\xc8\x03\x10\x30\xc2\x05\x46\x93\xf5\x6d\x6a\xe1\x02\x02\x46\xb8\ +\x80\xc9\x86\x35\x26\xe8\x60\x80\x3d\x8d\x78\x37\xb5\xcf\x73\xd1\ +\xfa\xa2\x83\xd9\xad\x93\x4f\xe3\x52\xfe\xfb\x8e\x22\x60\xfb\x70\ +\x19\xee\x8b\xd1\xd6\x98\x1a\x6e\xf4\x72\x60\x2f\xc9\x0a\x0b\xe1\ +\x32\x4c\xb8\xdc\xbb\x31\x3a\x97\xb6\xfb\x82\xce\x27\xca\x1f\xc2\ +\x65\x55\x27\xf2\xf6\xab\x59\x39\xbe\x7e\xc9\xd7\x56\x0c\xb9\x2c\ +\xba\x2e\x36\xe8\xfa\xc0\xe0\xa0\xb0\x8e\x74\x0a\x5e\x03\x4b\x43\ +\xbc\xb9\xb1\xbc\x2b\xfb\xf7\x3b\x1f\xc7\x3b\x3d\x82\xf9\x68\x35\ +\x68\x02\x0c\x7d\xdd\x97\xf7\xd9\xfb\x0c\x17\xfb\x22\x47\x07\xd3\ +\x4d\xb8\x2c\x5f\xc8\x65\xc5\xff\x7d\xd6\x7e\xd7\x37\x3f\xf4\x71\ +\xc4\xb5\x2f\x2c\x8d\x77\x37\xdd\xf8\x1d\x10\x2e\x77\xdb\xea\x64\ +\x2c\x58\x22\x59\x16\xfd\xcf\xd3\x62\x39\xe3\x23\x33\x21\xde\xd2\ +\xb2\xdb\x13\xba\xf3\xe2\x85\xcc\x5f\x7c\x61\xee\x4c\xee\xa3\x88\ +\x8c\x7b\x7b\xe7\x80\x75\x11\xbe\x26\x14\x35\x96\x48\xc2\x85\xc1\ +\x5b\x75\xc5\x94\xb7\x83\xf1\x5d\xd1\x26\x81\xce\x51\x07\x23\x5c\ +\x4c\x08\x88\x53\xc3\xc2\x05\x04\x8c\x70\x39\x62\x70\x6a\x47\x3b\ +\xc7\x8e\x21\xd2\x12\x49\xb8\xac\x1c\x9c\x4c\xcf\x8f\x80\x11\x2e\ +\x8d\xdb\xcc\x23\x07\xe4\xd9\xce\x21\x68\xc0\x08\x17\x6b\x6a\xd4\ +\x8b\x70\x89\xb6\x84\xaa\x0d\x1e\xbf\xde\x78\x6e\x3b\x8f\xa5\xf3\ +\xa0\x73\x82\x1d\x8e\x42\xce\xcd\xd0\xe3\x12\x49\xb8\x0c\xda\x35\ +\xc1\xd1\x01\x23\x5c\x40\xc0\x08\x17\xa0\xdd\xf2\x5a\xb8\x00\x21\ +\x02\x46\xb8\x80\x80\x11\x2e\x40\x9c\x80\x11\x2e\x20\x60\x84\x0b\ +\xd0\xc6\x5e\xef\x22\x09\x17\x10\x30\xc2\x05\x00\x00\xc8\xec\x5f\ +\x7c\x1d\xfd\x3c\xa5\xd3\xc9\xa6\x00\x00\x00\x00\x49\x45\x4e\x44\ +\xae\x42\x60\x82\ +\x00\x00\x10\x78\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x01\x18\x00\x00\x00\xa0\x08\x06\x00\x00\x00\xd7\x60\x73\x20\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ +\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ +\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x01\x16\ +\x0e\x25\x31\x1f\x41\x0b\xdb\x00\x00\x00\x45\x69\x54\x58\x74\x43\ +\x6f\x6d\x6d\x65\x6e\x74\x00\x00\x00\x00\x00\x43\x52\x45\x41\x54\ +\x4f\x52\x3a\x20\x67\x64\x2d\x6a\x70\x65\x67\x20\x76\x31\x2e\x30\ +\x20\x28\x75\x73\x69\x6e\x67\x20\x49\x4a\x47\x20\x4a\x50\x45\x47\ +\x20\x76\x36\x32\x29\x2c\x20\x71\x75\x61\x6c\x69\x74\x79\x20\x3d\ +\x20\x39\x30\x0a\x67\x9d\x47\x40\x00\x00\x0f\xa7\x49\x44\x41\x54\ +\x78\xda\xed\x9d\x3f\x8c\x5d\xc5\x15\xc6\xcf\xac\x2c\xb0\xb5\x5e\ +\x12\xc9\xa1\x61\x51\xf8\xd3\x50\xbd\xd7\x40\x03\x1d\x2e\xbc\x34\ +\x91\x90\xa9\xe9\x22\xd1\xda\x45\x84\xe5\x96\x06\x29\x0a\x76\x9b\ +\x22\x0d\xa9\xa1\xf7\x5a\x82\x2e\x96\x90\xdd\xec\x2a\x4d\x2a\x13\ +\x0c\xd5\x22\x25\x32\xc8\x06\x45\x9a\x14\x6f\x9f\xf7\xbe\x79\x33\ +\x73\x67\xe6\xce\xcc\x9d\x73\xe6\xfb\xa4\x15\xde\xbd\xcb\xdb\x7b\ +\x67\xce\xfc\xe6\x3b\x67\xe6\xde\xab\xb4\xd6\x04\x41\x10\x54\x42\ +\x3b\x68\x02\x08\x82\x00\x18\x08\x82\x00\x18\x08\x82\x20\x00\x06\ +\x82\x20\x00\x06\x82\x20\x00\x06\x82\x20\x08\x80\x81\x20\x08\x80\ +\x81\x20\x08\x80\x81\x20\x08\x02\x60\x20\x08\x02\x60\x84\xe9\xee\ +\x5d\xa2\xf7\xde\x23\x52\x8a\xe8\x85\x17\x56\xff\xbe\x7b\x17\xed\ +\x02\x75\x23\x85\x7b\x91\x0a\xe9\xcb\x2f\x89\x3e\xf8\xc0\x7e\xec\ +\x8b\x2f\x88\xae\x5e\x45\x1b\x99\xc1\xa8\x54\xb6\xcf\x42\x5c\x03\ +\x30\xb2\xf5\xd6\x5b\x44\x0f\x1e\xd8\x8f\xbd\xf9\x26\xd1\xfd\xfb\ +\x80\x49\x65\x21\xd6\x01\x18\x39\x7a\xfe\x79\xa2\x5f\x7f\xb5\x1f\ +\x7b\xee\x39\xa2\x5f\x7e\x01\x50\x0a\x80\xa0\xd6\xdf\x81\xc2\x84\ +\x1a\x4c\x29\x2d\x16\x69\xc7\x04\x01\x65\xf8\xe5\x1b\xe4\xe6\xd7\ +\x54\x97\x12\xfa\x79\xa1\xe7\x08\x01\x30\xed\xe9\xe6\xcd\xb4\x63\ +\x42\xc0\x12\x0a\x80\x5a\xa9\x51\x0c\x70\x20\x00\xa6\x7d\x5d\xbd\ +\x4a\x57\x88\xe8\xce\xe9\xb7\x8f\x89\x88\x0e\x0e\x88\x0e\x0f\xc5\ +\x15\x78\x7d\x2e\x60\x0e\xa0\x4c\x05\x0e\x5c\x4d\xc6\xd8\xe8\x2d\ +\x0f\x55\x4a\x69\xad\xb5\xaa\x35\xf0\x24\xe7\xfb\x63\x4e\x05\xd7\ +\xc3\xed\xfa\x5d\xd7\x0e\xc0\x04\xc3\x65\x10\x30\xaa\x66\xb0\x4a\ +\x6a\x67\xd7\x40\xc4\x35\xca\x83\xcb\x54\xc8\x9c\xeb\xd6\xba\x55\ +\x74\x32\x18\x74\xfc\xb4\xbe\x26\xf3\x9a\xd7\xdf\x63\x05\x2a\x4c\ +\x3b\x1d\x0d\x0e\x6d\xfb\x99\xed\xe7\x70\x2f\x61\x70\x69\xa9\xae\ +\x52\x12\x34\xae\x3a\x0d\xb7\x78\x07\x60\xea\x05\x8d\x6a\xb1\x33\ +\x5a\x05\x8b\x0d\x98\xbd\xcd\xe0\xb6\x6b\x6e\xb5\x10\xbc\x8e\xe7\ +\x16\xe2\xba\xdb\x55\xa4\x29\x90\x51\xca\xfe\x25\xc9\xbd\xf8\x56\ +\x85\x7a\x96\x6f\xd5\xa9\x35\xe7\xd2\x42\x09\xa0\x0b\xc0\xb8\x1a\ +\x5d\x6b\xad\x86\xdf\x87\x40\xc6\x17\x47\x52\x56\x35\x7b\x4d\x87\ +\xa4\xa7\x4d\x00\x4c\x03\x6e\x26\xa7\xad\x94\xb0\x54\x0b\xb0\xc4\ +\xa7\x4d\x3c\xdd\xcb\x6f\x89\xe8\x5f\xf9\xcf\xa9\x8f\xf5\xfd\xb0\ +\x86\x0f\xf9\xbd\xf1\xf8\xe1\xb9\xf7\x05\xe9\x10\xff\xf6\x4b\x05\ +\xcc\xd9\xb9\xef\x13\xd1\x23\xcb\x75\x00\x30\xd9\x1a\xdd\x74\x30\ +\xdb\xb5\x1a\x79\x80\x01\x5c\xf8\xb7\xe3\x74\xb8\x94\x39\x57\xa4\ +\x48\x9e\x74\x29\x3e\x65\xfa\x1f\x7b\xb8\x20\x25\x92\x93\x32\xb5\ +\x20\x00\x26\x2b\x64\xfe\xc9\x7a\xc6\x05\x58\xf2\x81\xa6\x26\x64\ +\x5a\x75\x2f\xe2\x01\x33\xa5\xe8\xe5\x5e\x61\xfa\x3d\x11\x39\x9e\ +\xf3\x42\x7f\x60\x33\x73\x01\x2e\xb2\x20\x33\xa5\xff\x4b\xf6\x3d\ +\x1c\x4c\x84\x9b\x59\x41\xe6\x3b\x22\x7a\x87\x88\x1e\x5a\x7e\xfb\ +\x3b\x16\xf6\x18\x70\x91\x03\x99\x94\x49\xb4\xe6\x4d\xb8\xa2\x8b\ +\xbc\x39\x37\x1d\x19\x69\xd2\x46\xbb\x8d\x05\x4e\x4b\x6d\x0c\xb8\ +\xc8\x6a\x73\x00\x46\x00\x5c\x1c\x90\x21\xad\xb5\xf2\x05\x4f\x6b\ +\x83\x19\x70\x91\xd5\xf6\xad\xc3\x05\x29\x52\x7c\x80\x44\x15\x7f\ +\x1b\xdb\x84\x05\xb8\x08\x4f\x97\x5a\x83\x0b\x00\x93\xd8\x4f\x66\ +\xec\xf8\x3a\xad\xb5\xc0\x02\x5c\x64\x40\x26\x61\x7f\xd7\x2c\xfd\ +\x0f\xc0\x24\x90\xdf\x5c\x61\x1a\x42\xc6\x15\x58\x66\xea\x54\x0b\ +\x34\x80\x0b\x9c\xcc\x9c\xda\x11\x0a\x84\x22\x77\x94\x2e\xb6\xdf\ +\x06\x10\x0c\x99\x16\x82\x0b\x70\x69\x13\x32\xad\xb9\x97\xe5\x72\ +\x09\xc0\xd4\x76\x2f\x6b\xb8\xac\x1b\x7f\xd0\x69\x1b\xcb\xd8\x45\ +\xeb\x32\x91\x40\x92\xfe\x4c\x60\x29\x90\x29\x39\xd1\xa4\xc2\x25\ +\x17\x64\x94\xd0\x87\x51\xe7\x5c\x9e\xb6\x39\x17\x22\x22\x3a\x3e\ +\x3e\x26\x97\x83\x89\x9d\x59\x46\x03\x60\xfd\xbb\x81\xfd\x85\xd4\ +\x88\x57\xea\x1d\xda\x47\x31\xb1\x3d\xc5\xb9\x1c\x1d\x1d\xc1\xc1\ +\xd4\x80\x4b\x48\xca\xb4\xae\xcb\xb8\xce\xa3\x76\xca\x04\xb8\xf0\ +\x4c\x95\xe6\xac\xc7\x94\x80\x0b\x52\xa4\x00\x0d\xdd\xcb\xd1\xd1\ +\xd1\x56\xe3\x0f\x8f\xa7\x3c\xc0\x2a\x28\xc8\x26\x04\x1e\xe0\xc2\ +\x0b\x32\x73\xb8\x97\x52\x70\x01\x60\x12\xdd\xcb\xf1\xf1\xf1\x46\ +\x7a\xb4\x5c\x2e\x37\x3a\x29\x05\x32\xce\x55\xa6\xe1\x79\x84\x59\ +\x68\xc0\x85\x31\x64\x72\xb8\x98\x56\xe0\x22\x0e\x30\xb9\x57\x8f\ +\x4c\xf7\x62\x76\x9e\x51\x83\x99\x04\x99\x51\x37\x03\x58\xf4\x3e\ +\xe1\x85\x3e\x34\xad\x19\xb8\xc0\xc1\x24\xb8\x17\x53\x66\xc7\xf8\ +\x20\x93\x94\x32\x45\xc0\x05\xee\x05\x2e\x66\xee\x9a\xcb\x56\x4c\ +\x0a\x7b\x1b\x5f\x16\x07\x63\xae\x1c\xd9\xdc\x8b\x6d\x10\x9b\x4b\ +\x7b\x83\xff\x2f\x6e\x85\xe9\xf4\xef\xa8\x88\x7c\x1d\x85\x5d\x79\ +\x13\xdb\xe0\xe5\x6f\x59\xdd\x4b\x2d\xb8\x88\x72\x30\x39\xe1\x92\ +\x2a\x97\x9b\x89\x5a\x61\xf2\x04\x49\xe8\xb9\x01\x2e\xfc\x5d\x4c\ +\xa9\xba\x4b\x4d\xb8\x20\x45\x72\xc8\xe6\x5e\x42\x83\xc1\x07\x99\ +\xd1\xba\x8c\xa5\xe6\x12\x02\x19\xa4\x46\x72\x53\xa5\xb0\x07\xd1\ +\xb7\x09\x17\x00\x26\xc2\xbd\xc4\xd6\x65\x86\x1d\x38\x5c\x65\x72\ +\x42\xc6\x53\xd0\x9d\xf3\x5e\x26\x48\x86\xe6\x80\x8b\x18\xc0\xe4\ +\x5c\x3d\x0a\x71\x2f\x39\x53\x26\xa5\x94\x0e\x5d\x8a\x1e\x7b\x75\ +\x29\xdc\x8b\x38\x17\x93\xc5\xbd\xcc\x05\x17\x38\x98\x44\xf7\x12\ +\x33\x90\x83\x20\x43\xa4\x15\x91\x0e\x59\x2d\x02\x44\xfa\xe1\x0c\ +\x77\xb8\x10\x09\x59\x45\xca\xe1\x60\x5c\x2b\x47\x53\x01\x33\xd6\ +\xd9\xd1\x2b\x4c\x99\xc0\x07\xf1\x89\x69\xdb\x38\xe5\x00\x17\x11\ +\x0e\x26\x17\x5c\x12\xec\xeb\xa4\x94\x69\x5d\x97\xd1\x91\x4f\xc9\ +\xcb\x75\x1d\x10\x1f\xb8\xa4\x38\x97\x56\x84\x14\xe9\x54\xa1\xee\ +\x65\xaa\xb6\x52\xa6\xc5\x22\xe9\x1e\x26\x40\x06\x8a\x75\xcb\x00\ +\xcc\x3c\xb3\x45\xf5\xbf\x79\xe4\xb8\xc5\x20\x06\x32\x96\xa7\xec\ +\x6d\x1c\x03\x68\x64\x39\xf2\x75\x7f\x72\x49\x8d\x9c\xb9\x5d\x8f\ +\xe9\x51\xe9\xda\x8b\xf1\x07\x87\x1f\x68\xdd\xfd\x3b\x65\xef\x03\ +\xea\x32\xb2\x00\xe3\x9a\x28\x38\xc0\xa5\x7b\x07\x53\x7d\x96\xb7\ +\x2c\x47\xdb\x56\x99\xc6\xee\x61\xf2\xcd\x62\x78\x37\xb2\x2c\xf7\ +\x12\xf2\x20\xf9\x56\xe1\x82\x14\x89\xc2\xf7\xbd\x4c\x76\x02\x9e\ +\xbd\x2e\x63\x90\x09\x49\x99\xcc\x73\x45\xca\x84\x9a\x0b\x00\x33\ +\x63\x7a\x34\x36\xe0\xb2\x0e\xc8\x80\x8d\x74\xb6\xdd\xbf\x8b\xc5\ +\xc2\x0b\x99\x31\xe8\xc1\xcd\xc8\x8b\x67\x5b\x9f\xb7\x0a\x97\xee\ +\x1d\x4c\xce\x5d\xbb\x53\xe0\xe2\x73\x33\x8b\xc5\x62\xd2\x0a\x13\ +\x20\x23\x3b\x8d\x6f\x19\x2e\xdd\x02\xc6\x57\xd8\xb5\x75\x6a\x72\ +\x7a\x94\xf8\xc0\xa8\x80\x94\x29\x1a\x32\x48\x99\xf8\xb9\x97\xb1\ +\x1b\x5b\x5b\x87\x0b\x5b\xc0\x94\x7a\xef\xd1\x9c\xce\x25\x24\x65\ +\x9a\x02\x19\xb8\x19\x59\xe2\x00\x17\x22\xa6\xcb\xd4\x53\x00\xd3\ +\xba\x7b\x19\x0b\x26\x22\xa2\xe3\xe3\xe3\xe4\xdb\x0b\xb2\x5e\x23\ +\x54\xc5\xbd\x0c\x1e\x3c\x55\x2f\xb5\x47\x8a\xd4\x74\xd4\x64\x83\ +\x8b\x2d\x88\xc6\x8a\xbf\x48\x99\xf8\xa7\xf0\x2e\xf8\x73\x82\x0b\ +\x4b\xc0\x94\x74\x2f\x63\x29\xc5\x1c\x70\x29\x05\x19\xa4\x4c\x6d\ +\xc6\xb2\x24\xb8\xc0\xc1\x8c\xa4\x0e\xad\xc0\x65\x90\x1a\x6d\xbc\ +\xc9\x60\xb1\x58\xa8\x21\x68\x00\x19\xb9\x1a\xa6\xc9\xe6\xdb\x2c\ +\x00\x98\x46\xe0\x51\x74\x06\xa8\xf8\x8a\x11\x9f\x9b\x49\x85\x0c\ +\x52\xa6\x76\xdd\x0b\x57\xb8\xc0\xc1\x78\x66\xee\xa8\xf4\x68\x86\ +\xf7\x17\xe5\x86\x0c\xdc\x4c\x3b\xf1\xe7\x82\x0b\x97\xb4\x68\xe3\ +\xba\x38\xad\x20\xa4\xd6\x5f\x42\xdc\x4b\x32\x60\x2a\xc2\x65\x7d\ +\x8e\xbe\xd7\xa5\x4c\x5d\x61\x9a\x0c\x5b\x28\x3a\x8e\x63\xe0\xe2\ +\x8a\x01\x38\x18\x46\x6a\x19\x2e\x63\x6e\xc6\x5a\xfc\x55\xea\x0a\ +\x29\xf5\x15\x29\xf5\xe4\xf4\xbf\x57\x62\x53\x26\xa8\xae\xc6\x9c\ +\x0b\x97\x3e\x11\xef\x60\x8a\xb9\x97\xca\x69\x51\xec\x73\x40\xd6\ +\x4e\xe6\x16\x11\x5d\xb3\x7f\xe4\x6d\xd2\xfa\x3a\xdc\x4c\x7b\xee\ +\xc5\x07\x17\x6e\x0f\x79\xdf\x91\x0c\x97\x82\x27\x53\xbd\xe6\x12\ +\x12\x54\xc3\xdd\xbf\x8b\xc5\x42\x69\xa2\x83\x6b\xee\x8f\xba\x36\ +\xe6\x64\x50\x97\xa9\x3f\x69\x8c\x39\x17\x6e\x80\xdf\x91\xde\x81\ +\xd9\xdd\x0b\x83\x17\xd2\x0f\xae\xf3\xc6\xc8\xaf\xde\x08\xf9\x3c\ +\x40\x66\x52\x10\x3e\x4b\x4f\xbf\x56\x4a\x0f\x88\xae\x62\xe1\xc2\ +\x51\xa8\xc1\x30\x71\x2e\x89\x90\x79\x7b\xe4\xd7\xde\x0e\xfd\x3c\ +\x2c\x65\x27\xc5\xcb\x2d\x22\xba\x43\x44\xef\x12\xd1\xf9\x77\x4f\ +\xbf\xb9\x95\x50\x73\x01\x60\x18\xd6\x5e\xb8\xdb\xcf\x00\xdd\x1b\ +\x3b\xae\xd4\x26\x37\xe1\x66\x32\x3a\x17\x47\xf9\xeb\x1a\x11\x69\ +\xa2\x2b\xab\xfb\x55\x35\xbd\xf6\xda\x47\x22\xe1\xd2\xbd\x83\x89\ +\x1a\x1c\x8c\xdc\xcb\x40\x9f\xfa\x0e\x1e\xac\x66\xd6\xad\xcb\x03\ +\x64\xb2\x28\x38\x3d\x7d\xf8\xf0\xaf\xf4\xc3\x0f\x7f\x12\x07\x17\ +\x22\x26\xab\x48\xb1\x0e\x26\xd4\xbd\x04\xd7\x5f\x1a\x80\x4b\xf2\ +\xfe\x87\x95\x4d\xdf\x9a\x49\x6f\x13\xd1\xd9\x12\x92\x56\xa9\x97\ +\x87\x55\x26\x67\xc3\x3c\x21\xa2\xf3\x9e\xdf\x78\xaa\x48\x5f\xd8\ +\x86\x77\xc1\x58\x80\x83\xc9\x93\x1e\x65\x1d\x18\x3c\x9d\xcb\xf0\ +\x9c\xaf\xaf\xcc\x0a\x7d\x4d\x44\x4f\x4f\xff\x7b\x70\x9d\x86\x6d\ +\x99\xfe\x1e\x26\xb8\x99\xf4\xf4\xb4\x87\x46\x68\x17\x30\xa7\xd5\ +\xf7\x27\x44\xf4\x15\x11\x5d\x89\x00\x47\xb6\x7b\x8e\xb8\xc3\xe5\ +\xec\xdc\x0f\x49\xeb\xcb\x8a\xf4\x05\x45\xfa\xb2\x22\x7d\x68\x3a\ +\x17\x40\xa6\x6e\x7a\x1a\x70\x1c\x80\x29\x08\x97\x67\xd5\xf7\xf3\ +\xb4\x2a\x14\xdc\x39\xfb\x79\x81\xf1\xa7\xe5\xc2\x65\xfc\xea\x37\ +\x20\x33\xe5\x3e\x26\xac\x32\x6d\x42\xfd\xb6\xfb\xe8\xed\x33\xc8\ +\x0b\xcf\x14\x9b\xcb\xe3\x56\xd5\xf7\x3b\x9e\xdf\x38\x20\x6d\xef\ +\x9c\x6c\xb5\x97\x06\xe1\x92\x23\xef\xf6\x8f\x77\x35\xf9\x1e\xa6\ +\xe8\xf4\x53\xfa\xe0\x3a\xdd\xf7\x72\x63\x35\x49\x3e\x3d\x4d\x8b\ +\x3e\xf5\xc1\x05\x35\x98\xf2\x4a\xde\x1c\x96\x25\x35\x12\xec\x5c\ +\x7c\x97\x73\xe9\xd2\xe7\x93\x1f\x60\x85\x94\x69\xbb\xfd\x0e\x89\ +\xe8\xf2\x2a\xaa\x2e\x2c\x17\x8b\xcb\xbf\xbb\xf4\xf9\x24\xb8\x20\ +\x45\x9a\xae\xa4\xcd\x61\xa1\x41\xec\x9d\x5d\x3b\x48\x8b\x5c\x97\ +\x75\x72\xf2\x61\x96\x07\x58\x21\x65\xf2\x4f\x7e\x2f\xbd\xf4\xe7\ +\x6e\xe0\xd2\x2a\x60\x92\xaa\xef\x93\xdd\x4b\x37\x35\x97\xd5\xe5\ +\x99\x5f\xc3\x76\xcb\x05\x99\x5e\xdd\x8c\xd1\x66\xca\x8c\x4d\x57\ +\xfb\x03\x30\x75\x54\xac\xfa\xee\x74\x2f\x1d\xc1\x65\x4c\xeb\x1b\ +\x26\x01\x99\xbc\xce\x65\x52\xda\x0e\xc0\x64\x9d\x5e\x0f\x69\xb5\ +\x0f\xcc\xa6\xdb\xb6\x02\xaf\xd4\xfb\x38\xe6\x06\x8d\x09\x19\xa4\ +\x4c\x71\xee\x65\xd8\x7e\xbd\xc6\x65\x93\xcb\xd4\x8a\xe8\xda\x7a\ +\x67\x18\x0d\x36\x87\x85\x3c\xbf\x24\x7a\x56\x65\xe6\x5e\x6a\x0e\ +\x4a\x13\x32\x70\x33\xe9\xed\xc8\xb1\xff\xb3\x9c\x6f\x8b\x4b\x5d\ +\x63\xbb\x77\x87\x6d\xbc\xbb\xfb\x0d\xbd\xf8\xe2\xdf\x68\x6f\xef\ +\x9e\xb7\x23\xad\xe9\x11\x23\xb8\xcc\xf9\xa0\xa1\xe5\x72\x99\xe5\ +\x51\x9c\x41\xe9\xaa\x30\xf7\x92\xdb\xb9\x70\x7b\xe0\x14\x3b\xc0\ +\xb8\x00\x7e\xe9\xd2\xdf\xe9\xe4\xe4\xc3\xf0\x80\xde\x8c\x6e\x2e\ +\x01\x3c\x6b\x60\x99\xa0\xc9\x71\xeb\x86\x14\xd0\x9c\x5e\xc7\x06\ +\x60\x4a\xa4\x45\xdc\x9e\xc9\xdb\x1c\x60\x52\xe0\x32\xc6\x0a\x09\ +\x70\x69\x25\xb8\x00\x99\xf9\xe0\x02\xc0\x30\x00\x0c\x57\xb8\xb4\ +\x14\x5c\x80\x8c\x1f\x30\x25\x1f\xe9\x0a\xc0\x34\x0c\x18\xce\x70\ +\x69\x31\xff\x36\x0b\xbe\x3d\x82\x66\x0e\xb8\x70\x02\xcc\x4e\x63\ +\x9d\x95\xbd\xa3\xa4\xc0\xa5\x45\x6d\x4f\x00\x4a\x67\xf8\x4c\x2f\ +\x70\x5a\x83\xcb\x70\x9f\x0b\xd4\x38\x60\x8a\x0d\x04\x21\x70\x69\ +\x71\xd6\xea\x15\x32\x6b\xb8\xe4\x4e\x15\xb9\xc6\x81\x08\xc0\x8c\ +\xb5\xeb\xf6\x4d\xd1\x0a\xce\xa5\x12\x64\x86\x83\x4b\x29\xa5\x87\ +\x9b\x1f\x53\x07\x51\xcb\x1b\xf3\xe0\x5c\x98\x01\x26\x34\x3d\x5a\ +\x2c\x96\xf4\xea\xab\x1f\xd1\xee\xee\x37\xa3\xf0\xd9\xb8\x21\x44\ +\x58\xc7\xb5\x38\xab\x0f\xfb\xed\xf8\xf8\x78\x32\x64\x5a\x75\x33\ +\xeb\xeb\xaa\xe9\x5e\xb8\x6e\x48\x64\x99\x22\xed\xed\xdd\xa3\xd7\ +\x5f\xff\xa3\xff\x66\xb1\x41\x87\x28\xc2\xb3\x62\x01\x99\xbc\x70\ +\x81\x02\xc1\xa8\xdb\x79\xa0\xd2\xe8\x6c\x10\x75\xcf\x91\x60\xc0\ +\x70\x58\xaa\x2c\xb1\xc2\x64\x83\x4b\xcd\x36\xb0\xbd\x9a\xb7\x86\ +\x7b\xe1\xd2\xe7\x62\x1c\x4c\x40\x6f\x88\x4d\x8d\x38\x3a\x99\x35\ +\x70\x38\xbb\x19\x03\x2e\xe8\x60\x4e\x80\xc9\xea\x5e\x2c\x70\x91\ +\x9a\x1e\xb5\x9e\x97\x9b\x7d\xc9\x35\x65\xb2\xc0\x65\x16\xf7\x02\ +\xc0\xc0\xb9\x40\x16\xc8\x94\xaa\xcb\xd4\x58\x65\x32\xe1\x82\x5a\ +\x5e\xe4\x90\x6c\x6d\x47\xa8\x6d\x46\x08\x72\x2f\x0e\xb8\xf4\xe0\ +\x5e\x34\x9f\x9b\x35\x8b\xdc\x6d\x5c\xaa\x2e\x63\x83\x4b\xa9\xf7\ +\x74\x49\xea\xe7\xa6\x1c\x4c\x0c\x5c\x9c\xb9\x2f\x9c\x0b\x1b\x37\ +\x33\x74\x32\x66\xff\xb6\x94\x32\xa1\xe6\xd2\x61\x8a\x14\xfb\xfe\ +\x22\xc9\x76\x96\xeb\xb5\x99\x90\x69\x31\x65\xb2\xc1\x65\x0e\xf7\ +\x22\xa1\xbf\x67\x4f\x91\x7c\x9d\x36\x9a\xff\x1a\x70\xe9\xed\x7d\ +\x3c\x9c\xed\xb3\xb9\x8c\xdd\x4a\xca\xd4\x12\x5c\xb8\xa7\x47\xb3\ +\x3b\x98\x98\x4e\xe3\xf0\x72\x34\x28\xcd\xc9\xb4\x92\x32\xb9\xe0\ +\x02\x09\x4c\x91\xbc\x39\xb0\x05\x2e\x3d\xbe\x4d\xd0\x4c\x09\x38\ +\x42\xa6\x64\x5d\x26\x26\x65\xf2\xc5\x1b\xdc\x8b\xf0\x1a\x4c\x6f\ +\x2f\x47\xeb\xd9\xcd\xe4\x84\x8c\xcf\xcd\x28\xb5\xf9\x25\xa9\xee\ +\xd1\x54\x2a\x3c\x57\x23\x26\xd7\x5e\x1c\x80\x91\x42\xfc\x9e\x67\ +\xbb\x92\xaf\xfc\xd8\x74\x2f\xbf\x21\xa2\xff\xd0\xd9\xdf\x1a\xc6\ +\xdb\xbf\x89\xe8\xbf\xcf\xda\x11\xee\x45\xb8\x83\x89\x85\x0b\x24\ +\xc7\xc9\x94\x4b\x99\x3e\x71\xc0\xe5\x88\x88\x3e\x81\x73\x91\xec\ +\x60\x9c\xee\xc5\x93\x1a\xf5\x58\x7f\x91\x3c\xeb\x95\x5c\x61\x5a\ +\x7d\xfe\xb7\x44\xf4\x8a\x05\x2e\x44\x44\xdf\x92\xd6\xaf\xc0\xbd\ +\x70\x76\x30\xa1\x1d\x07\xb8\xe4\x48\x07\x78\x3b\x19\xb3\x2e\x93\ +\xa7\x36\xe3\x82\xcb\xea\x18\xfa\x4d\x68\x8a\x64\xad\xe4\xa3\xa8\ +\x1b\x9f\x4a\x0a\x49\x97\x6c\xc5\xdf\x1c\x29\x93\x1b\x2e\xf1\x93\ +\x20\xfa\x93\x11\x60\xb6\x1a\x38\x12\x2e\xbd\xbb\x17\x2d\xb0\x2e\ +\x95\x1b\x32\x9b\x93\x58\x3b\xef\x8b\x96\xba\x48\x51\xbd\x06\x13\ +\x5c\x7b\xd9\x8c\xb2\x20\x4b\x89\xf4\x48\x70\xa0\x3a\xea\x32\x44\ +\xe1\xb5\x99\x70\xb8\xa0\xf6\x22\xda\xc1\xe0\x41\xdd\x70\x31\x3e\ +\x27\x93\xe2\x66\x42\x9f\x27\x34\x47\xb8\x49\xde\x62\xd1\x84\x83\ +\x71\xba\x17\xcf\xb9\xc9\x7c\x79\x7a\x9e\xa0\x97\xec\xec\x52\x9c\ +\x4c\xcc\xa3\x56\xe7\x7c\x1c\x83\x44\xc0\xec\xcc\x0d\x17\xa7\x7b\ +\x91\xd2\xd0\xe6\x96\x51\x07\x45\x7c\x66\x23\xd6\x88\x70\x7a\x79\ +\x59\x8a\x93\xf1\xd5\x65\x4c\x37\x13\xf5\x1c\xe7\x19\x9d\x8b\xd4\ +\x14\x7f\xf6\x14\xc9\xba\x72\x34\xd2\xd0\x6c\x3a\xc6\x35\xb0\x2b\ +\x0c\x78\xe9\xf5\x28\x17\x64\x86\x31\x15\x0b\x17\x3c\x8e\x41\x20\ +\x60\xac\xee\x45\x8a\x73\x99\x72\x3c\x73\xd0\x4a\xdc\x63\x11\x02\ +\x99\x16\x9d\x8b\xd9\x1f\x92\x27\x83\x6a\x35\x98\xe0\xda\x4b\xc0\ +\xf9\xb0\xe8\x9c\x90\x01\xbd\x01\x80\xe0\x5f\x85\x0d\xf7\xc4\x16\ +\x51\xda\x0a\x53\x4d\xf7\xd2\xd3\xea\x67\x33\x0e\x26\x05\x2e\x50\ +\xbc\xf5\x96\xee\x64\x86\x6e\xa6\x75\xe7\xd2\x43\x2a\x3b\x9b\x83\ +\x49\x7d\x5a\x3b\xfb\xfa\x4b\x65\x07\xd3\xb3\x93\x09\x75\x22\xb5\ +\xdc\x4b\x8f\xfb\xb6\x76\x00\x17\x38\x19\x49\x4e\x66\x08\x08\x13\ +\x38\x70\x2e\x1d\xa7\x48\x02\xa3\x3d\xea\xb8\xef\xd7\x73\xc5\x62\ +\x0f\x90\x31\x5d\xc8\x18\x64\x6a\xb8\x97\x9e\x27\xc6\xea\x80\xc9\ +\xf5\x22\x2b\x16\x9d\xe4\x3a\x47\xc7\xcf\xb5\xb6\x7f\xc1\xc9\x94\ +\x85\x0c\x9c\x0b\x63\xc0\x6c\xcc\x10\x13\x5e\x5b\xc4\x76\x20\x94\ +\x26\x06\x20\x13\x0c\x19\x13\x34\xa5\xdd\x0b\x52\xfa\xca\x0e\x66\ +\xb9\x58\x64\x71\x2f\x10\x20\x93\x02\x99\x9a\x6e\x06\x70\x99\x29\ +\x45\x42\x67\x01\x32\x2d\x40\xa6\xa4\x7b\x41\xbc\x9e\xe9\xdc\x1c\ +\x7f\x94\xab\x7b\xb1\x8d\x3f\xee\xb1\xa3\x8d\x17\xd6\xad\xff\x2d\ +\xf1\x01\x56\xa5\x1d\x8c\x0d\xd0\xbd\x4f\x86\x3b\x65\x07\xa4\xfd\ +\x29\xf1\x39\x67\xdd\x39\xe1\xe2\xfb\x39\x67\x27\xd3\x93\x9b\xc9\ +\xe5\x5e\x00\x97\x46\x52\xa4\x58\xf7\xd2\x42\xa0\x37\x70\x5b\x51\ +\x15\xc8\xf4\x96\x32\x95\x82\x8b\xad\x2d\x01\x98\xda\xa3\x12\x62\ +\xe1\x66\xa6\xbc\x44\x9e\x93\x93\x49\x05\x0b\xea\x2d\x73\x00\x46\ +\x29\x52\x83\x9b\xa3\xd7\xe9\xd1\x54\xf7\x82\xce\x9b\x37\x65\xc2\ +\x7d\x60\xfe\xb6\x40\x7c\xd6\x00\x0c\x82\x50\x0c\x64\x7a\xaa\xcd\ +\x4c\x49\xd9\x91\x12\xb9\x75\x2e\x73\xeb\x3b\x0f\x4d\x5d\x39\x42\ +\x07\xce\xeb\x66\x7a\x58\x69\x4a\x01\x0b\x54\xcb\xc1\x0c\x03\xd0\ +\x92\x1e\xe5\xe8\xd0\xf9\x06\xd9\xb4\xe3\x48\x9b\x90\x0e\x01\x30\ +\xb9\x46\xa1\xa5\xe1\x25\xec\xda\x8d\xbc\xad\xa8\xbb\xb4\x49\x1a\ +\x68\x7c\x60\x01\x5c\xe6\x4a\x91\xb4\x76\x64\x49\xfb\x93\xdc\x4b\ +\x2b\x1d\x8a\xb8\x72\xa7\x4d\xe6\xf7\x3c\x37\x52\x2a\xa4\xe8\x2d\ +\x03\x66\xd5\x3f\x9b\x9b\xeb\xd6\x2f\xb8\x52\x0a\x03\xb4\x17\xd0\ +\x0c\x7f\xc6\x61\x60\x02\x2c\x5c\x1c\x4c\x81\xce\x46\x07\xf3\x01\ +\xcd\x98\xab\x69\xa5\x3f\xc7\xd2\x39\xc4\x5c\xc6\xb6\xce\xd9\x98\ +\xa6\x83\x21\x32\xb7\x65\x03\x30\xdd\x04\x56\x60\x4d\xa6\x46\xff\ +\xb6\x74\x2e\xbd\x29\xf3\x3e\x98\xbc\xf7\x1e\xa1\xc3\x79\xbb\x9a\ +\xe1\x97\x6f\xf0\x9b\x5f\x53\x61\x12\xfa\x79\xa1\xe7\x08\x35\xe3\ +\x60\xdc\xee\x25\xc4\xc1\xc0\xbd\xc0\xe1\xd4\x4a\xe5\xa0\x3a\xca\ +\x5c\x83\xd1\x6a\xe5\x62\x34\xb6\xf3\x42\xd1\x83\x3d\x27\x74\x00\ +\x13\x91\x80\xb1\x3b\x97\x94\x19\x0d\x01\x02\xe8\x40\x00\xcc\x56\ +\x0a\xe4\x9a\x84\x9c\xb1\xf3\xe3\x8f\x44\x9f\x7d\x46\x9a\x88\x1e\ +\x5f\xbc\x48\xff\x78\xe3\x0d\xfa\x0b\x02\x0d\x82\x64\xa4\xc2\xb3\ +\xce\x1a\xdf\x7f\x4f\xf4\xf2\xcb\xf6\x63\x8f\x1e\x11\xed\xef\xa3\ +\x87\x20\x88\xb1\xe6\x7d\x26\xef\xc7\x1f\xa7\x1d\x83\x20\x08\x0e\ +\x66\x54\x17\x2f\x12\xfd\xfc\xb3\xfd\xd8\xee\x2e\xd1\x4f\x3f\xa1\ +\x87\x20\x08\x0e\x26\x51\xef\xbf\x9f\x76\x0c\x82\x20\x38\x98\x51\ +\xa1\x06\x03\x41\x70\x30\xc5\xb4\xbf\x4f\x0f\x0e\x0f\xe9\xfe\xc1\ +\x01\x11\x11\x3d\xde\xdb\x23\xba\x79\x93\xe8\xe4\x04\x70\x81\x20\ +\x38\x18\x08\x82\xa0\x56\x1d\x0c\x04\x41\x00\x0c\x04\x41\x10\x00\ +\x03\x41\x10\x00\x03\x41\x10\x00\x03\x41\x10\x04\xc0\x40\x10\xd4\ +\xa6\xfe\x0f\x2c\x19\xd6\x9f\x4d\xd0\xf4\xbb\x00\x00\x00\x00\x49\ +\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x01\x13\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x12\x00\x00\x00\x12\x08\x06\x00\x00\x00\x56\xce\x8e\x57\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ +\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ +\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdd\x06\x13\ +\x0c\x23\x10\x05\x9c\x36\xe1\x00\x00\x00\x93\x49\x44\x41\x54\x38\ +\xcb\xcd\x94\xcd\x0e\x84\x20\x0c\x84\x3f\xaa\x86\x98\x6c\xf6\xe6\ +\xfb\xbf\xa7\x51\xf0\xa2\x1b\xd2\x45\x8a\xe8\xc1\x49\x38\xf0\xd3\ +\xc9\x4c\x07\x80\x87\x11\xf7\xf1\x69\xa8\x75\x00\xa2\x16\xd7\x46\ +\x11\x7f\x8a\x9a\xd1\xab\xb9\x00\xe1\x90\x9b\x48\x97\x4c\xed\x52\ +\x22\xaa\xb5\xe6\xc9\x28\x68\x81\x58\xd6\x5c\x21\x99\x1e\x98\x73\ +\x0d\xbe\xa2\x28\xaa\x9e\x98\x44\x2e\x19\xa5\x98\x07\x8b\x68\xac\ +\xbc\x0a\x8b\x45\x34\x01\x5d\x85\xe5\xd5\x7a\x22\x3a\x08\xaf\x02\ +\x39\xce\x79\x2b\xb5\x14\x61\x2f\x0a\x35\x8a\x4a\x16\x3a\xe0\x7b\ +\xb2\x37\xa9\x70\x7e\xe9\xc4\x9b\xbf\x87\x13\xde\x86\x0d\xfe\xc8\ +\x1c\x79\x0a\xbf\x98\xa7\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\ +\x60\x82\ +\x00\x00\x06\x0c\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x01\x18\x00\x00\x00\xa0\x08\x06\x00\x00\x00\xd7\x60\x73\x20\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ +\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ +\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x01\x1b\ +\x08\x27\x0e\x6d\xf6\xe0\x0b\x00\x00\x00\x45\x69\x54\x58\x74\x43\ +\x6f\x6d\x6d\x65\x6e\x74\x00\x00\x00\x00\x00\x43\x52\x45\x41\x54\ +\x4f\x52\x3a\x20\x67\x64\x2d\x6a\x70\x65\x67\x20\x76\x31\x2e\x30\ +\x20\x28\x75\x73\x69\x6e\x67\x20\x49\x4a\x47\x20\x4a\x50\x45\x47\ +\x20\x76\x36\x32\x29\x2c\x20\x71\x75\x61\x6c\x69\x74\x79\x20\x3d\ +\x20\x39\x30\x0a\x67\x9d\x47\x40\x00\x00\x05\x3b\x49\x44\x41\x54\ +\x78\xda\xed\xdd\x51\x6e\xd3\x4a\x14\x06\xe0\x63\x44\x5f\x61\x11\ +\x48\x97\x1d\xb2\x8d\xb0\x82\xee\x80\x75\x74\x0f\xb7\xab\x28\x02\ +\x24\x9e\xe6\x3e\xdc\x46\x02\xb7\x49\x9d\xc4\x63\xcf\x99\xf9\x3e\ +\xa9\x0f\xa5\x52\x6b\xec\x73\x7e\x9f\x71\xe2\x38\x02\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\xe8\xc8\x64\x17\xb0\x44\x51\x38\ +\x08\x18\xb6\x08\x17\xc5\xc3\x52\xef\xec\x02\xde\xf2\x28\x5c\x10\ +\x30\x80\x25\x12\x5d\x2c\x91\x14\x0e\x26\x18\x9c\x85\x00\x00\x00\ +\x00\xa0\x73\xae\xdf\x41\x1d\xde\xfc\x1c\x5e\x45\x82\x2d\xc2\xc5\ +\x04\x03\x54\x0b\x97\x61\xfb\x4c\xc0\x80\x70\x11\x30\x20\x5c\xf2\ +\x71\x0d\x86\x56\xbb\xf5\x53\x89\x28\x25\xe2\xa1\x44\x7c\x12\x2e\ +\xc0\x9a\x1d\xfb\xf0\x1c\x30\xa5\x44\x3c\x34\x1e\x2e\xf3\x2f\x4c\ +\x30\x60\x72\x01\x4b\xa4\x36\x97\x48\x26\x97\x05\xa4\x2d\x98\x5c\ +\x04\x0c\x08\x17\x01\x03\xc2\x05\x3b\x07\x6e\x0d\x97\xf2\xdc\x3f\ +\x9a\x48\xc0\x40\x95\x70\xd1\x48\xe7\x79\x99\x1a\x6e\x0c\x17\x04\ +\x0c\x08\x17\x01\x03\xed\x86\x8b\x95\xd0\xe5\xec\x30\xb8\x30\x5c\ +\x7c\x92\x94\x80\x81\xd5\xc3\x45\xb0\x58\x22\x81\x65\x51\x43\xde\ +\xdb\x05\x70\x3a\x5c\x8e\xff\xf8\xdd\xfe\x81\xa6\xba\x35\xdb\xe7\ +\xb9\xbc\x7a\xe3\x62\x39\xf3\x05\xec\xd7\xb1\x5d\x7c\x9e\xcb\xd3\ +\xec\x07\x4f\x0e\xed\x45\x5c\x83\xc1\xb2\x68\xb6\x2c\xfa\xf3\x9b\ +\xfb\xd9\x0f\x3f\xd8\x67\x60\x89\x74\xeb\xe4\x72\x74\x17\x11\x87\ +\xd9\x24\x73\xcb\x1f\x1a\x8d\x2b\xe4\x98\x5c\x2a\xf6\x42\x19\xbc\ +\xd9\x2c\x91\x10\x2e\x1b\xf4\xfe\xa8\x67\x72\x01\xc3\xd0\xe1\x52\ +\x76\x0a\x97\x51\x96\x4b\x96\x48\x08\x97\x8d\x1b\xa1\x0c\xd4\x7c\ +\x26\x18\x4c\x2e\x3b\xaf\xcf\x04\x0c\xc3\xf8\x2c\x5c\x36\xd9\x10\ +\x4b\x07\x86\x3c\xcd\x7f\xeb\xef\xbf\xf4\xd7\x97\x77\xe5\xc2\x8e\ +\x9d\xf8\xb3\xe3\x70\x39\xf5\x03\xea\x31\xa9\xd1\xe3\xf8\xee\xae\ +\xe8\x46\xb8\x9b\x9a\xde\xce\xe2\x67\xef\x8a\x96\x38\xdb\x72\x91\ +\x77\x70\xdf\x22\xe2\xd7\x99\xa6\x4b\x78\x57\xb4\x1c\x81\x44\xe3\ +\x40\x97\x77\x45\xbb\xf6\x62\x82\x81\xd5\x26\x97\x7b\xfb\x08\x9a\ +\xec\x5c\x77\x45\x73\x35\xeb\x53\xba\x9e\x5c\x10\x30\x20\x5c\x04\ +\x0c\x08\x17\x04\x0c\xc2\x05\x01\xa3\x72\x50\x22\x02\x66\xc7\xca\ +\x51\x41\x9c\x2b\x91\xe3\x5d\xd1\xea\xa3\x5d\xde\x07\x43\xea\x70\ +\x79\xeb\xe4\x84\x80\x81\xab\xc3\x05\x01\x03\xe9\xc2\xe5\x2e\x22\ +\x7e\xc4\xff\x6f\xcc\x3b\x3c\x7f\x4f\xf2\x4a\xf2\x61\x40\xbc\x51\ +\x12\x9b\xd5\xc8\xef\xd9\xef\x3f\x38\x1e\x9d\x56\x14\x6b\xed\xdb\ +\x6e\x9f\x15\x5d\x83\x47\xc6\xc2\x65\x1d\xeb\xae\xe8\x0b\x1c\x9c\ +\xec\x56\xe1\x1a\x0c\xad\x85\xcb\xdc\x2e\x77\x45\x7f\x89\x88\xaf\ +\x11\xf1\xdd\x31\xc1\x92\xaf\x8b\x25\x92\xbb\xa2\x3b\xe4\xe5\xbe\ +\x0d\x4f\xc5\x76\xfa\xf5\x93\x0b\x02\x86\x05\xe1\x62\xc7\x0b\x97\ +\x91\xb8\x06\xb3\x70\x6e\xff\x77\xc5\x71\x5c\xf7\xe4\x0c\x97\x1a\ +\xb5\x60\x82\x51\xfd\x17\xef\xb4\x62\x67\x77\x19\x2e\x8e\xa9\x09\ +\x46\x92\x0b\x17\xc7\x54\xc0\x08\x17\xe1\x22\x5c\x46\xe0\xc1\x6b\ +\x95\x8a\xaf\x28\xc2\x45\xe1\xf2\xe7\xbd\x45\x53\xa2\xe3\x8b\x09\ +\x66\xd5\x42\x7a\xbc\xb0\xb8\x14\xe1\x65\xe1\x72\x6a\xb4\x69\xad\ +\x16\x1c\x57\x61\x4c\xc2\x70\x51\x90\x26\x18\x58\x25\x5c\xe4\x88\ +\x80\xa1\xdd\x6e\xf5\xac\x68\xa0\x5a\xc7\xba\x2b\x1a\x13\x0c\x96\ +\x45\xe7\x26\x17\xcf\x8a\x06\x4b\xa4\xd5\x27\x97\x23\x77\x45\x8f\ +\xc1\x7a\x98\x4d\x27\x17\x04\x0c\x08\x17\x04\x0c\xed\x86\xcb\xfc\ +\x7d\x2e\x0a\x4d\xc0\x40\x95\x70\x51\x6c\xe3\xf2\x2a\x12\x96\x45\ +\x08\x18\x84\x0b\x02\x06\xe1\x02\x8a\x82\xba\xe1\x52\x14\x1a\x8e\ +\x3b\x26\x17\x04\x0c\xcd\x86\x8b\xc4\xe1\x1c\x9f\x68\xd7\x4e\xd3\ +\x4e\x49\xb6\x53\x8e\xb0\x98\x8b\xbc\x6d\x35\x6d\xba\x70\xf1\x68\ +\x55\x04\x8c\x70\x59\x6d\x3b\xcb\xf3\xb2\xe8\xf8\xe5\xae\x68\x68\ +\xaf\x69\x33\x7c\x14\xca\x8b\xed\x2c\x27\x36\xde\x5d\xd1\x9c\x62\ +\x1d\xbd\xff\x54\x30\x25\xd8\xc6\x88\x13\x17\x74\x15\x11\x96\x48\ +\x02\xfe\xe6\x70\x71\xa8\xb8\x86\x57\x91\x84\x8a\x70\xc1\x12\xa9\ +\x93\x25\x51\xfa\x70\xf1\x0e\x5d\x04\x4c\xbb\x4d\x3b\x25\xd9\x4e\ +\xb5\xc1\x2a\x5c\x83\xd9\xb6\x69\x85\x0b\x26\x18\x86\x69\x5a\xe1\ +\x82\x09\x46\x90\x0b\x17\x72\xf2\x2a\x92\x70\x11\x2e\xc9\xc7\xe3\ +\x49\xf1\xd3\x7a\xb8\x14\x05\x91\xf6\xe0\xb5\x7c\xdc\x4c\x30\xeb\ +\x1f\xf7\xb4\x2f\x45\x0b\x17\xd6\xe6\x1a\x4c\xbd\x93\x8a\x70\x41\ +\xc0\xd8\x05\xc2\x25\xf3\x7f\x0c\x01\x33\x52\xb8\x74\x71\x41\x57\ +\xb8\xb4\x6b\x4a\x52\x74\x26\xe3\xf5\x1b\xd7\xab\x45\x60\x82\x19\ +\x26\xa8\x85\x0b\x1a\x03\xe1\x82\x80\xc1\x07\x74\x83\x25\xd2\x86\ +\x4d\x2b\x5c\x40\xc0\x08\x17\xb0\x44\xd2\xb4\x4d\x6f\xa7\xf4\xc2\ +\x04\x33\x66\x20\xeb\x7d\x04\x8c\x70\xe9\x2b\x5c\x24\x18\x02\x66\ +\x9d\x3e\x9a\xb2\x84\xcb\x5d\xc4\x74\x7c\x46\x11\xd0\x66\xd3\x66\ +\x78\x7e\xd8\xab\x0f\x6f\x3b\x54\xdc\xf0\xf9\x1f\xbb\x1b\xac\x28\ +\x60\xed\x1e\x6a\x2b\x3d\x16\xfc\xd8\xe4\x32\xee\x19\x47\xc7\x46\ +\xc4\x3f\xc9\x26\x82\xbd\x1c\xe2\xe5\xa3\x59\x5b\xdc\x4e\x67\x1c\ +\x74\x6d\xc2\xcd\x7c\x9a\x6d\xcc\xd3\x95\xdb\xa9\x29\xba\xaf\xdd\ +\xe6\xb9\xc8\xfb\xd2\xee\x17\x74\xef\x67\xdf\x7f\xbc\xf2\xd5\x22\ +\xaf\xf4\x5c\x1f\x2e\x24\x68\xa6\x24\x6f\xd0\x68\xee\x1e\xa3\xd9\ +\x06\x79\x9f\x4b\x23\xe1\x62\xa7\x0b\x98\xb4\x45\x3c\xd9\x85\x19\ +\x8f\x0f\xa6\xe8\xf6\x26\x96\x85\x05\xac\xce\x05\x47\x7a\xef\xd4\ +\x98\x5e\x70\xe0\x11\x30\xc2\x05\xd2\x19\xe5\xb9\x48\x55\x9a\xb6\ +\xc2\x2f\x15\x2e\x08\x98\x0e\x4c\x49\xfe\xb8\x70\xc1\x12\x49\xb8\ +\x0c\xbd\x9d\x69\x77\xa6\x1d\xac\x88\xc7\x5b\x77\xcd\x7e\xb7\x03\ +\x84\x09\x06\xe9\x0f\x00\x4e\x92\xbc\xba\x64\x9a\x76\xfe\xfb\x0a\ +\x07\x4b\xa4\x4e\xc3\x65\xe4\xbf\x8f\x80\xa1\xf2\x98\xb9\xe7\xc4\ +\xf0\x68\x7a\x41\xc0\x60\x4d\x8d\x7a\x61\x93\x25\xcc\x54\xe1\xf7\ +\x4f\x6f\xfc\x6d\xc5\xc4\xdc\x7b\xbb\x80\x6b\xcf\x42\xae\xcd\x60\ +\x89\xc4\x66\x53\x13\x08\x18\x40\xc0\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x40\xd7\xfe\x03\x18\xf6\x50\x35\x80\x40\x4b\x14\x00\ +\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x00\xfa\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x12\x00\x00\x00\x12\x08\x06\x00\x00\x00\x56\xce\x8e\x57\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ +\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ +\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdd\x06\x13\ +\x0c\x32\x1c\x5f\xf3\x59\xda\x00\x00\x00\x7a\x49\x44\x41\x54\x38\ +\xcb\xc5\x93\x4b\x0e\xc0\x20\x08\x44\x87\xe9\xe7\xfe\x17\xae\x74\ +\x53\x93\x96\x88\x60\x6b\x52\x36\x2a\xea\x84\xe1\x29\x30\x29\xe4\ +\x1a\xf5\xab\x0e\x67\x55\xb4\x76\xaa\x8c\xe2\xe1\x82\xd1\x81\x8c\ +\x48\x4b\x28\xd3\x33\x6d\xdd\xe5\x20\x80\x9a\x5b\x32\xd6\x3c\xb1\ +\x3a\xdf\x01\x94\x4c\xb3\xef\x62\x6a\xc4\xe8\xd9\x9e\x86\x9f\x89\ +\xa6\xca\xcd\x6a\x01\xb0\x45\x38\xb5\xb3\xb6\x79\xda\x73\x0c\x2a\ +\xf1\x20\x1c\x76\xdf\x43\x2d\x83\x0f\x52\xf8\xf2\x8b\x48\x06\xbf\ +\xce\xa6\xf6\x4f\x9c\x58\x59\x1e\x24\x0c\x1b\x13\xc1\x00\x00\x00\ +\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x01\x0c\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x12\x00\x00\x00\x12\x08\x06\x00\x00\x00\x56\xce\x8e\x57\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ +\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ +\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x01\x16\ +\x0d\x23\x01\x6d\x84\x22\xa8\x00\x00\x00\x8c\x49\x44\x41\x54\x38\ +\xcb\xbd\x93\xd1\x0e\x83\x30\x08\x45\xcf\xb4\xd3\xda\x19\x7d\x31\ +\xfb\xff\x2f\x5d\x7d\x61\x09\x59\xd0\x41\x13\xbd\x49\x03\x29\xed\ +\x85\x5e\x28\x9c\xe3\x01\x74\x40\x11\xff\x7a\xf4\x62\x93\xd8\x1a\ +\xb8\x3b\x4a\xb5\x9f\xdf\x40\x02\x66\x27\xc9\x53\xd6\x21\xaa\xa3\ +\x2a\x7d\xa6\x6b\x25\xd3\x31\x97\xf8\x16\x59\x0d\x6a\x68\x5e\x74\ +\x93\x8c\xd2\xc1\x5e\xf9\xab\x22\xa8\xc0\x5b\xe6\xc9\x4c\xf0\x15\ +\x2b\xab\xcd\xd7\x49\x85\xc3\x6d\x4f\x8b\x8a\xdd\xdc\xb1\x70\xe7\ +\xa2\x03\x69\xa2\xa8\x3f\xf7\x0f\x19\x98\xac\xc0\xd6\xd0\x91\x45\ +\x08\xef\x47\xf6\x64\xde\x01\xde\xc8\x2c\xd0\x78\x42\x6d\x42\x00\ +\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x05\xf0\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x01\x18\x00\x00\x00\xa0\x08\x06\x00\x00\x00\xd7\x60\x73\x20\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ +\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ +\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x01\x18\ +\x0a\x15\x18\x65\x63\x7a\xab\x00\x00\x00\x45\x69\x54\x58\x74\x43\ +\x6f\x6d\x6d\x65\x6e\x74\x00\x00\x00\x00\x00\x43\x52\x45\x41\x54\ +\x4f\x52\x3a\x20\x67\x64\x2d\x6a\x70\x65\x67\x20\x76\x31\x2e\x30\ +\x20\x28\x75\x73\x69\x6e\x67\x20\x49\x4a\x47\x20\x4a\x50\x45\x47\ +\x20\x76\x36\x32\x29\x2c\x20\x71\x75\x61\x6c\x69\x74\x79\x20\x3d\ +\x20\x39\x30\x0a\x67\x9d\x47\x40\x00\x00\x05\x1f\x49\x44\x41\x54\ +\x78\xda\xed\xdd\x4d\x72\x13\x31\x10\x86\x61\x29\x95\x6c\x58\x70\ +\x0d\x1f\x37\x9c\x20\x37\x30\x6b\xce\x82\x4f\xe1\x2a\xbc\x60\x25\ +\x16\x98\x8d\xcb\xc1\x3f\x33\xd2\x48\xea\xe7\xad\xf2\x22\x54\x81\ +\x3d\x3d\x5f\xbf\xea\x9e\x40\x48\x09\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x80\x66\x64\x25\xc0\x40\x14\xd9\x1d\x8b\x17\x25\xc0\ +\x80\x72\x01\xc1\x00\xe4\x62\x45\x02\xc6\x93\x8b\xdc\x12\x0c\x66\ +\xee\xf2\xbc\xcd\xdb\xca\x2c\xc1\x20\xca\x7e\x92\xdb\xbf\xad\xbc\ +\x0e\x86\x67\x30\xb8\xc9\x81\x5c\x40\x30\x98\x78\x60\x22\x17\x2b\ +\x12\x74\x7c\x95\xb7\x93\x51\x13\x0c\x9c\x42\xe4\x02\x13\x0c\x0c\ +\x4a\x20\x18\x90\x8b\x6c\x12\x0c\x40\x2e\x20\x18\x90\x0b\x08\x06\ +\xe4\x02\x82\x01\xc8\x05\x04\x03\x72\x01\xc1\x80\x5c\x40\x30\x00\ +\xb9\x80\x60\x40\x2e\x20\x18\x90\x0b\x08\x06\x20\x17\x10\x0c\xc8\ +\x05\x9d\xe1\x5f\x53\xc3\x61\x06\x37\x1d\x43\x4f\x2f\x72\x66\x82\ +\x01\xaa\xad\x46\x30\xc1\x00\x55\xe4\x22\x63\x04\x03\x90\x0b\x08\ +\x06\xe4\x02\x82\x01\xb9\x00\x82\x00\x72\x01\xc1\x80\x5c\x40\x30\ +\x20\x97\xe7\x7f\xbf\xdc\x12\x0c\xf0\xb0\x5c\x8a\x2c\x13\x0c\xb0\ +\x96\x5c\x4a\xc5\xfc\x15\xd9\x26\x18\xc4\x94\x4b\xd9\x28\x6b\x45\ +\xc6\x09\x06\x73\xca\xa5\x74\x98\xad\x22\xef\x04\x83\xf1\x05\xd3\ +\x7b\xa6\x88\x86\x60\x30\x89\x5c\xf2\xa0\xd7\x90\x5b\xbf\x79\xb4\ +\x86\x23\x18\x2c\x91\x4b\x9e\xe0\x7a\x72\xab\x37\x8c\xd8\x6c\x04\ +\x83\x88\x2b\x46\x33\xd1\x94\xe0\x8d\xe6\xe7\xc1\x20\x9a\x5c\x3e\ +\xbb\x86\x32\xc5\x9b\x99\x60\x30\xa8\x5c\x72\xa0\xeb\x6d\xb2\x36\ +\x45\x68\x3e\x13\x4c\x6c\x93\xec\x4a\x4a\xfb\x92\xd2\xa9\xa4\xb4\ +\xdf\xc5\x93\x4b\xf3\x01\xc3\x8f\xfb\x43\x24\xb9\x94\xcb\xd7\x59\ +\x32\x25\x68\x2f\x94\x54\xf1\xfa\x23\x17\x16\xf1\x3a\x69\x7f\x4d\ +\x30\x7b\x3d\x50\xb8\x00\x58\xde\x45\xa7\x6b\x82\x39\x69\x2a\x92\ +\x59\x11\xcf\x60\xe2\xf2\xe3\xda\x2f\x7e\x49\xe9\xbb\xd2\x2c\xfa\ +\x07\x9c\x00\x76\x57\xa6\x97\xf3\x6b\xa7\x3a\x26\x19\x60\x71\xe3\ +\xec\xce\xcf\x5c\x4e\x7f\xc5\xb2\x27\x17\x92\xa9\x3d\x0a\x22\x46\ +\xc3\xc8\xc1\xb2\x9a\xa9\x17\xc1\x80\x5c\x48\x66\x6b\x3c\xe4\x75\ +\xc0\x40\xad\x14\x0d\x4e\xe0\x0e\xeb\xa8\x86\x26\x18\x24\x0f\x27\ +\xd5\x95\x60\xd0\xb0\x09\x9c\xbc\xa6\x7e\xc5\x02\xb9\x58\x95\xc6\ +\xe6\x55\x37\x18\xdf\xb1\x4a\xad\x45\xb5\xe7\x15\xa9\xe8\x92\x16\ +\xe5\xd4\x04\xa6\xff\x98\x82\x01\xb9\x0c\x2e\x19\x67\x21\xc1\x58\ +\x8b\x00\x82\xc1\xda\x72\x31\xbd\x3c\xc8\x5b\x4a\xe9\x57\x4a\xe9\ +\x98\x52\x7a\x3f\x7f\x6d\x8a\x19\xbc\x3b\x3e\x7b\x61\x51\xf9\x94\ +\xf0\x09\x7e\x5f\x14\xf0\xfd\xfe\xfa\x63\xa4\x2e\xc1\xd3\x65\x53\ +\xc2\x05\x1c\x2f\x0a\x7a\xbc\xff\x3e\xa0\xd7\x15\x29\x5f\x79\xe1\ +\xe1\xb5\x28\x0b\xfa\x72\x3e\x2e\xbe\xfe\xba\xce\xaa\x0a\x4c\x31\ +\xf0\x19\x02\x17\xf2\x76\x5e\x8b\x8e\xf7\x17\x52\xcd\x3f\x39\xed\ +\xd0\x78\xbc\xa8\xfc\x47\xfb\x1b\xa6\x7d\xdc\x72\xb5\x4f\xbe\x8b\ +\xd4\x54\x2e\x15\xe7\x67\x61\x06\xc1\x44\x97\xcb\x4a\x92\xb9\xf7\ +\xc7\x2e\x90\x8e\x8d\x80\x60\x46\x90\x45\x49\x29\xfd\xec\x63\xb9\ +\xb6\xdf\xcb\x02\xe3\xce\x3e\x89\xe4\x15\x2c\x90\xd7\xf9\x38\xd9\ +\x33\x80\xee\xb2\xe0\x1e\x98\x60\xa6\xc8\x37\x81\x80\x60\xb0\x89\ +\x5c\xc8\xc7\x56\x40\x30\x51\x13\xf6\x40\x02\x4d\x2e\x20\x98\x08\ +\xa2\x38\x3c\xd8\xdd\x2b\x7c\x6b\x87\x5c\x26\xc9\x82\xba\x61\x86\ +\xb5\xc8\xc3\xc5\x3e\xef\x5f\xe8\x7b\x61\x82\x99\x43\x2e\x00\xc1\ +\xa0\x89\x5c\xc8\xc8\x66\x40\x30\x30\xb9\x80\x60\x40\x2e\x00\xc1\ +\x90\x0b\x40\x30\x20\x17\x10\x0c\x36\x82\x5c\x40\x30\xa8\x32\xbd\ +\x90\x0b\x08\x06\xd5\x56\x23\x80\x60\x50\x45\x2e\xa6\x17\x10\x0c\ +\xc8\x05\x20\x18\x72\x01\x08\x86\x5c\x00\x82\xc1\x58\x72\xf1\x00\ +\xb9\xcf\x7b\x4e\x30\x30\xb9\x00\x04\x43\x2e\x89\xb8\x86\xc0\x0f\ +\xfd\x96\x01\x93\x0b\x40\x30\xe4\x02\x10\x0c\xb9\x74\x21\x17\x0f\ +\x19\xfb\xca\x00\xc1\xc0\xe4\x02\x10\x0c\xb9\xfc\x0f\x42\xeb\x07\ +\xf7\x82\x60\x84\x0b\x20\x98\xb1\xa6\x97\xdc\xe9\xe7\x82\x9a\x13\ +\x8c\x40\x01\x46\x79\xf4\xfd\x50\xd7\x7f\xfc\xa5\xe6\x26\x18\x72\ +\x01\x08\x06\xe3\xc9\x85\xec\xd4\xbe\x0b\x5e\x95\x60\xfa\xc9\xa5\ +\x08\xfd\x26\xb9\x68\xf6\x66\x3d\xdf\x5c\x13\x8c\xb5\x08\x83\x87\ +\xb0\xe7\xef\x34\x10\xcc\xbc\x72\xc9\x83\x64\x70\xa6\x6c\x38\x70\ +\x08\xc6\xe4\x02\x10\x0c\xb9\x98\x62\x4c\x2f\x04\x43\x2e\xc3\x5f\ +\x13\x06\xaa\x65\x1e\x2c\x94\xac\x1b\x43\x2e\x4e\x5a\x35\x25\x18\ +\x72\xd1\x10\x6a\x69\x45\x22\x61\xe3\x3d\xb9\x80\x60\x56\x08\xcf\ +\x4c\x72\x71\xd2\xaa\x29\xc1\x38\x99\x9a\x35\x84\x93\xd8\x6a\x44\ +\x30\x1b\xca\x25\x07\xbd\x6e\xa8\x95\x11\x8f\x5c\xac\x82\xea\x45\ +\x30\xe4\xa2\x69\xd4\x89\x60\x04\x27\x68\x3d\x34\x8f\xfa\x10\x0c\ +\xb9\x68\x22\x75\x19\x93\x17\xc1\x09\x1f\x9e\x7c\x47\x7d\x42\x67\ +\xa4\x90\x8b\x09\x66\x26\xb9\x6c\xf4\x21\x9d\xd8\x37\xe4\xc2\x32\ +\x26\x98\x29\x26\x97\x6f\x37\xbe\x36\xc9\x98\x5c\xd0\x5f\x70\x2e\ +\x5f\xdd\x72\xbc\xf8\xa0\x47\xb5\x6a\x7e\xcd\x11\x8b\x60\x82\x99\ +\x7c\x72\xf9\xc7\xc7\x8d\xaf\x37\x58\x9b\x4b\xa4\x7c\x98\x5c\xea\ +\x86\x89\x5c\x7c\xe8\xa1\xeb\xb7\xe4\xda\x4a\xe0\x66\x21\x18\xcd\ +\xa1\x8e\x0d\xae\x49\x70\x08\xe6\x99\x10\xc9\x08\xd1\x70\x07\xc1\ +\x90\xcb\x60\x0d\x3a\x42\x7d\x4b\xb0\xfc\x13\x8c\x53\x6a\x4a\xd1\ +\xf4\x54\x6b\x8f\x53\x08\x86\x5c\x88\x86\x58\x08\x86\x5c\xb0\xac\ +\xb1\x6b\xde\x8b\x12\x28\xe3\x04\x43\x2e\x44\x53\x31\x7f\x25\x60\ +\xb6\x09\x86\x5c\xb0\x92\x14\x1c\x96\x04\x43\x2e\xd8\x5c\x3a\xee\ +\x31\xc1\x6c\x12\x54\xc1\x03\x3a\xe2\x95\x2c\xc1\xe4\xd0\x94\xe8\ +\x6e\x9f\x11\x1e\xdc\xc2\x7f\x5b\x82\x9b\x1c\xee\xfc\x35\x80\x60\ +\x00\x58\x91\xd0\xff\x8a\x24\x4c\x30\xc1\xc0\x29\x04\xd9\xc1\xf8\ +\x53\x8d\x30\xc1\x04\x03\x80\x60\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x80\xa9\xf9\x03\x7d\x79\xb8\x1f\x3b\ +\x4e\x17\x5e\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x01\x27\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x12\x00\x00\x00\x12\x08\x06\x00\x00\x00\x56\xce\x8e\x57\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ +\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ +\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdd\x06\x13\ +\x0c\x1e\x1a\x8f\xa1\x97\x41\x00\x00\x00\xa7\x49\x44\x41\x54\x38\ +\xcb\xc5\x92\xc1\x0e\x82\x30\x10\x44\x5f\x2d\xc5\xa0\xe1\xff\xff\ +\xd2\xa3\x89\x14\xa8\x97\x69\xa2\x68\xa1\x2b\x24\x4e\x42\x9a\x2c\ +\xed\xf4\xed\x6c\xe1\x20\x39\xad\xc9\x70\xa6\x05\x22\x70\xd5\x3a\ +\x03\x63\xb3\xe3\x72\x27\xa3\x37\x88\xb4\x41\xd5\x14\xf6\x78\xfd\ +\xa3\x96\x68\x2a\xd4\x93\x5a\xe3\x54\x69\x14\x0a\x75\x0f\x74\x16\ +\xa2\x71\x41\xf1\x91\x5b\x2d\xd1\xe6\xbe\x5a\xa2\xb3\xc6\x7e\x11\ +\xd1\x1d\xe8\x81\x5b\xe9\x1d\x05\xd5\xbc\x46\x9b\x49\xe2\x4a\xd8\ +\x00\x6e\x49\x14\x7f\xa5\xae\xc9\x28\x59\x8c\x5a\xdd\xec\x5e\xbe\ +\xac\xce\x62\x34\x1b\x82\x5f\x9d\xda\x94\x5f\xe8\x17\x45\x8b\x51\ +\xa6\xf3\xc0\xa0\x56\xb3\x1e\x96\xd6\x82\x0e\xf7\xca\x27\xf0\x2f\ +\x3d\x01\xe0\xcd\x1f\x66\x39\xd8\xf4\xee\x00\x00\x00\x00\x49\x45\ +\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x06\x04\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x01\x18\x00\x00\x00\xa0\x08\x06\x00\x00\x00\xd7\x60\x73\x20\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ +\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ +\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x01\x18\ +\x0c\x37\x05\xa5\x5a\x2c\xe0\x00\x00\x00\x45\x69\x54\x58\x74\x43\ +\x6f\x6d\x6d\x65\x6e\x74\x00\x00\x00\x00\x00\x43\x52\x45\x41\x54\ +\x4f\x52\x3a\x20\x67\x64\x2d\x6a\x70\x65\x67\x20\x76\x31\x2e\x30\ +\x20\x28\x75\x73\x69\x6e\x67\x20\x49\x4a\x47\x20\x4a\x50\x45\x47\ +\x20\x76\x36\x32\x29\x2c\x20\x71\x75\x61\x6c\x69\x74\x79\x20\x3d\ +\x20\x39\x30\x0a\x67\x9d\x47\x40\x00\x00\x05\x33\x49\x44\x41\x54\ +\x78\xda\xed\xdd\x4f\x6e\x1b\x37\x14\x07\xe0\x47\xc3\x0a\xfa\x0f\ +\xed\xae\xab\xae\xba\x48\x80\x16\xe8\x2d\x7a\x3e\xf5\x04\xb9\x41\ +\x2e\x54\x9f\x42\x40\x1b\x20\x05\x0a\x76\x61\x19\x90\xc6\x52\x35\ +\x1a\x73\x66\x38\xe4\xf7\x01\x42\x2c\x07\x71\xac\x47\xf2\x47\xbe\ +\x91\x2c\x47\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0d\ +\x49\x4a\xc0\x18\xd9\xc4\x41\xc0\xb0\x44\xb8\x98\x3c\x8c\xf5\xa0\ +\x04\xdc\xf2\x24\x5c\x10\x30\x80\x16\x89\x26\x5a\x24\x13\x07\x27\ +\x18\xec\x42\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\xc0\x7c\xbc\x59\x19\x6b\xca\xe6\xaf\x80\x81\ +\x1a\xc2\xc4\x9c\x16\x30\x30\x29\x54\x52\x25\x5f\x17\x01\xc3\xc6\ +\x43\x25\x35\xf6\xff\xa2\xe8\x34\x1a\x2a\xc2\x46\xc0\xd0\x69\xb0\ +\x24\xdf\x23\x0a\x4c\xc9\x85\x9b\x7c\xcf\x9c\xf2\x8b\xd7\xb8\xc7\ +\xfb\xe3\x22\xcd\x17\x16\xe9\x16\x16\xea\xa5\xef\x33\xc7\x72\xcf\ +\x70\x39\xc1\xc0\x9d\xad\x46\xf2\x78\x70\x82\xe1\x2d\x0b\xf1\xda\ +\x62\xfc\x65\xe3\x9b\x6b\xba\x23\x78\x70\x82\xc1\x2e\x5f\xec\xb1\ +\x5a\x1b\x4e\x30\x2c\x1c\x2e\xbf\x36\xbc\xf0\x66\xbb\x36\x93\xc3\ +\x85\x1e\xb8\xb4\x1e\x5e\x6e\xbf\x77\xfe\xf8\x85\x0b\xcc\xb4\xb8\ +\x7e\x52\x8b\xe9\xf9\xd0\x7b\xb8\xe8\x33\xb9\xd6\x12\x99\x1b\x05\ +\x6a\x92\xe3\xfa\x95\xe4\x1e\x0a\x6c\x12\xe1\xc5\x67\x0b\x07\x6f\ +\xee\xa8\xc8\x26\x92\xc5\x63\x3e\x2c\x58\xa7\xdc\x59\xb1\x3d\x8b\ +\x64\xd1\x9c\xcd\xf7\x0f\xea\x32\x66\x23\xce\x6f\x2d\xb8\x24\xa7\ +\xe5\x60\xb9\x78\x01\x33\x47\xc4\x27\xf5\x19\x5b\x3b\xe0\x46\xb8\ +\xbc\xfa\x8b\xbf\xd5\x48\xc8\xcc\x78\xf4\xa3\xd3\xeb\x08\x8e\xef\ +\x65\xea\x88\xe2\xf4\xbe\x20\xfe\x37\x5c\x4c\x0a\x21\x53\x92\x8b\ +\xbc\x9d\x87\xcb\xa7\x88\xf8\x6c\xa5\xd8\x98\x15\x8a\xd2\xe1\x42\ +\xd1\x1a\xab\xed\x05\x8f\x4a\xd0\x45\xb0\x9c\x2e\x80\x77\x11\xb1\ +\x3b\x8e\xfd\x41\xb9\x70\x82\xa1\x54\xb8\xe0\x14\xb3\x28\xd7\x60\ +\x84\x0b\xf3\x8f\x81\x13\x0c\x82\x05\xa7\x18\x01\x83\x70\x11\x32\ +\x5a\x24\x84\x0b\xa4\xda\x57\x8c\xd5\xa2\x54\x1b\x1c\x17\x63\x51\ +\x5b\x21\xfc\xfa\x3d\xa7\x16\x6d\x92\x16\x09\xe1\x02\x02\x46\xb8\ +\xd0\x73\x47\x20\x60\x18\x1b\x2c\xd7\xae\xb7\x98\xcc\x33\xda\x45\ +\xc4\x5f\xf1\xfc\x52\xe7\xfd\xf1\x7e\xc1\xae\x9f\xb5\x56\x52\xd1\ +\xdf\x19\xd1\x66\x49\x58\xc0\x97\x41\xd1\xf7\xd3\xc6\x8d\xda\x57\ +\x94\x52\x28\xc7\x1a\x0e\x83\xc2\x1f\xa6\x8d\x9f\x16\xa9\xb6\x06\ +\x76\x78\xd3\x12\x69\x89\xd6\xf0\x71\x70\xff\xfb\xe9\xe3\x09\x4e\ +\x2d\x9c\xdb\x1d\xdb\xa2\xc3\xb4\x81\x30\x7e\x27\x3b\x23\x33\x26\ +\xc6\xc8\x82\x7b\x96\xa8\xdd\xe1\xef\x7a\x0c\xbd\x1f\xcc\x82\xe1\ +\xf2\xf2\xf9\x24\x58\xe8\x84\xa7\xa9\x17\x0c\x97\xc1\xdf\x0b\x17\ +\x04\x8c\xb0\x78\xbe\xfd\x59\xb0\xa1\x4e\xc2\xa5\xa7\xb9\xd0\xf5\ +\x75\x18\x13\x7a\xc2\xcc\x48\xd3\xff\x9d\x60\xe9\x67\x2e\xf8\xb9\ +\x24\x27\x98\x45\x93\x5c\xb8\xd8\xbc\x05\x0c\xc2\x05\x4a\xf1\x2c\ +\xd2\x4c\x5b\x57\x16\x2c\xe0\x04\x33\xf6\x8c\xfb\x74\x67\x32\x08\ +\x17\x73\x01\x35\x9a\x8b\x70\xe1\x74\x1e\x74\x3b\xee\x5a\xa4\x65\ +\xc2\x45\xb0\x20\x60\x70\x6a\x81\x92\x5c\x83\x11\x2e\x20\x60\x84\ +\x0b\x68\x91\x04\x8b\x60\x01\x27\x18\xe1\x02\x02\x46\xb8\x80\x16\ +\xa9\xeb\x70\x11\x2c\xdc\xb3\x11\x39\xc1\x30\x9a\x70\x01\x01\xf3\ +\xe6\x9d\xe8\xd2\x2b\x32\x85\x0b\x36\x21\x0f\xbe\xe8\x31\x57\xbd\ +\xb8\x77\xee\x74\x3d\x67\x9c\x60\x00\x01\xa3\x25\x62\xc3\x27\x5f\ +\x2d\x12\x5a\x22\x66\x99\x47\x5a\x24\xc0\x06\x2e\x60\xb4\x44\x68\ +\x8f\x24\xac\x96\x08\x73\xca\x7c\x72\x82\x01\x9b\xb7\x80\xd1\x12\ +\xa1\x3d\x92\xb2\x5a\x22\x04\x8c\xb9\xa5\x45\x02\x1b\xb7\x80\xd1\ +\x12\xa1\x3d\x92\xb4\x5a\x22\xd0\x1e\x69\x91\x40\xb8\x08\x18\x2d\ +\x11\x5a\x23\x2d\x92\x96\x08\x9c\x5e\xb4\x48\x20\x5c\xd6\xf2\xd8\ +\xd0\x20\xa7\xe3\xcd\x1b\xfd\xd0\x55\x4f\x56\xf3\x44\x4f\x6a\x0d\ +\xdb\x39\xbd\x6c\xed\x57\x5a\x68\x91\xa0\xdc\x3a\xa7\x81\x80\xf1\ +\x2c\x11\x4e\xcd\x02\x66\xf6\xc1\x1d\x5e\x7b\x81\xa5\x99\x77\x5a\ +\x24\x98\x65\x83\x5b\x25\x5c\xd2\xc6\x52\x2e\x6d\x68\x50\xd3\x95\ +\xfb\xa0\x35\x12\x30\x06\x15\xe1\xa2\x45\xaa\xcb\xbf\x86\x08\xe1\ +\x42\xe9\x01\xcd\xf1\xfc\x22\xc0\x87\x38\x7f\xd6\x08\xd6\x98\x8b\ +\xe6\x60\x23\x2d\x92\xdd\x02\x73\xb1\x21\xb5\xfc\xa8\xc0\x37\x76\ +\x08\x2a\x0d\x16\xe1\xd2\xc8\x31\xf4\x5d\x44\x7c\xe5\x38\x5a\xed\ +\x20\xed\x72\xc4\x3e\x47\x1c\x8e\x7f\xee\x3a\x68\x89\xcc\x43\x3d\ +\x2e\x0b\x0d\xd4\x3e\x47\xe4\x93\xdb\xde\x5c\xa4\x56\x5f\x0f\x4e\ +\x2b\x06\xb5\xfe\xd5\x77\x18\x04\xcc\xc1\xa9\x85\x5b\x1e\x56\x1a\ +\xd0\xcf\xc7\x80\xf9\x56\x9f\xbb\x19\x1f\x6f\xdc\xdf\x6a\xb0\x0c\ +\x99\x87\x0d\xed\x16\x3f\xc4\xf3\x05\x5e\xea\x1f\xb8\xb3\x6b\x30\ +\xbb\x6d\xef\xf8\x77\x9d\x5a\x1c\x69\xa6\x4b\x2b\x0c\xac\xdd\xe2\ +\xce\x22\x55\x5a\xa8\x4b\x3f\xc2\x91\x5a\x2b\xad\xb7\xac\xab\xb8\ +\x45\x1a\xee\x7a\xef\x23\x7e\x36\x5e\xb7\xfd\x71\xe3\x7e\x45\x9b\ +\xd3\xf0\xe7\xc3\x6a\xdc\xec\xf3\xd4\x76\xc8\xc9\xa5\xfe\xed\xa2\ +\xf5\x67\x1e\x66\x71\x18\xac\x8a\xc3\x26\x86\xfa\xd5\x5b\x69\xac\ +\xd9\x3e\xe5\x1b\xb7\xb2\x3d\x14\xab\x8d\x72\xab\xcf\x3c\xcc\x6a\ +\x3f\x98\xd8\xfb\xcd\x0d\xfb\xd5\xc0\xa9\x3a\x54\x6e\x7d\x21\x9c\ +\x60\x5a\xa9\x5b\x4b\x93\x7b\xf8\x30\x5e\x3e\xfe\xf0\xc6\xaf\xfb\ +\xe3\xe0\x6b\x15\x29\x5b\x16\x30\x9b\x9a\x59\x3d\xbc\xfa\xb3\xf7\ +\x00\x99\xfa\xf0\xbf\x1b\x3c\xfc\x7f\x2e\x84\xd0\x98\x8f\x73\x44\ +\xfc\x56\xb2\x94\xc2\xa5\x1c\x17\x5a\x57\x5a\x61\x06\xe2\xac\x14\ +\x69\xe2\xc7\xe6\xaf\x80\x41\xc0\xd0\x2b\xef\xc9\x0b\xcc\xe6\x51\ +\x09\x9c\x44\x40\xc0\xe8\x4b\x41\x8b\x04\x20\x60\x00\x01\x03\x00\ +\xf0\x8a\xeb\x8d\x8c\xe2\x7d\x36\x10\x30\x2c\x12\x2e\x26\x0f\x63\ +\xb9\x06\xc3\x4d\x4f\x23\x3f\x07\x02\x06\xd0\x22\x51\x7f\x8b\x64\ +\x32\xe1\x04\x83\x5d\x08\x73\x87\xed\x9f\x6a\x4c\x26\x9c\x60\x00\ +\x01\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x4d\xfb\x0f\x74\ +\x4e\x10\x25\x18\x9c\x47\x0f\x00\x00\x00\x00\x49\x45\x4e\x44\xae\ +\x42\x60\x82\ +\x00\x00\x01\x35\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x12\x00\x00\x00\x12\x08\x06\x00\x00\x00\x56\xce\x8e\x57\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ +\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ +\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdd\x06\x13\ +\x0c\x31\x09\x19\x03\xee\xf2\x00\x00\x00\xb5\x49\x44\x41\x54\x38\ +\xcb\xad\x94\x51\x0b\xc3\x20\x0c\x84\x2f\x99\xdb\x4a\xc7\x60\xff\ +\xff\x8f\xae\x94\x61\xf6\x62\xe0\xcc\x6c\x59\xab\xbe\x54\xd4\x7c\ +\xdc\x5d\xaa\xc0\xa0\x21\xe5\x6b\xbd\x1c\x2d\x93\x4b\x07\xa4\xaa\ +\x55\x00\xaf\x83\xca\xac\xd4\x25\x07\xb8\xc5\xe5\x80\x4d\x3f\x93\ +\x59\x89\x2f\xe8\x9f\x99\x59\xa8\xad\xc6\x83\x20\x7e\xd8\x36\x20\ +\x16\x20\x95\xb5\x4f\x28\x6c\x29\xf3\xf9\x8d\x2c\x89\xaf\x27\xb2\ +\x26\x0d\x58\x54\xa6\x0d\xa5\x99\x15\x69\x47\xeb\x85\x01\x73\xc8\ +\x88\xad\x08\xed\x65\x00\xd7\xa0\x46\x19\xb4\xec\x40\x62\x6e\x2b\ +\xd5\xfd\xfc\xc8\xa9\xa8\xda\xeb\x58\xec\x9c\x2b\x9d\x59\xd1\xb3\ +\xd0\x5b\x4a\xb6\xee\x67\xe6\xf6\x73\x68\xf7\x13\x57\x64\xf2\xcc\ +\x9c\x3e\x01\x78\x9f\xec\x9c\x02\xb0\x61\xcf\xc8\xa8\x77\x0d\x5f\ +\x67\x96\x2b\x7d\x67\x52\x6a\xcf\x00\x00\x00\x00\x49\x45\x4e\x44\ +\xae\x42\x60\x82\ +\x00\x00\x15\x12\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x01\x18\x00\x00\x00\xa0\x08\x06\x00\x00\x00\xd7\x60\x73\x20\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ +\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ +\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x01\x16\ +\x0e\x32\x2f\xe0\xcd\xb2\x2e\x00\x00\x00\x45\x69\x54\x58\x74\x43\ +\x6f\x6d\x6d\x65\x6e\x74\x00\x00\x00\x00\x00\x43\x52\x45\x41\x54\ +\x4f\x52\x3a\x20\x67\x64\x2d\x6a\x70\x65\x67\x20\x76\x31\x2e\x30\ +\x20\x28\x75\x73\x69\x6e\x67\x20\x49\x4a\x47\x20\x4a\x50\x45\x47\ +\x20\x76\x36\x32\x29\x2c\x20\x71\x75\x61\x6c\x69\x74\x79\x20\x3d\ +\x20\x39\x30\x0a\x67\x9d\x47\x40\x00\x00\x14\x41\x49\x44\x41\x54\ +\x78\xda\xed\x9d\xbf\xaf\x5c\x47\x15\xc7\xbf\xb3\x38\x31\xc2\x4e\ +\x44\x12\x24\x24\x9c\x82\xee\x59\xa0\x14\x28\xa9\x91\x68\xb0\x5d\ +\x20\x45\x8e\x14\x17\xb4\x74\xa1\x8f\xe4\x16\x11\x2a\x52\x85\xff\ +\x21\x01\xe5\xb5\x71\x6c\xd1\x80\x84\x90\x48\x1a\x44\x24\x9b\x34\ +\x20\x25\x34\xc4\x69\x12\x5b\xca\x0f\x34\x14\x6f\xd7\x6f\xdf\xdd\ +\xf9\x3d\xe7\xcc\x9c\x99\x7b\x8e\xb4\xf2\xf3\xdb\xb7\x7b\xef\xcc\ +\x9c\xf9\xdc\xef\x9c\x99\x39\x63\xac\xb5\x50\x53\x53\x53\xe3\xb0\ +\x8d\x56\x81\x9a\x9a\x9a\x02\x46\x4d\x4d\x4d\x01\xa3\xa6\xa6\xa6\ +\xa6\x80\x51\x53\x53\x53\xc0\xa8\xa9\xa9\x29\x60\xd4\xd4\xd4\xd4\ +\x14\x30\x6a\x6a\x6a\x0a\x18\x35\x35\x35\x05\x8c\x9a\x9a\x9a\x9a\ +\x02\x46\x4d\x4d\x4d\x01\x33\x99\xdd\xb9\x03\x5c\xbd\x0a\x18\x03\ +\x3c\xf9\xe4\xc9\xcf\x77\xee\x68\xbd\xa8\xad\xc6\x8c\xee\x45\x62\ +\xb2\xe3\x63\xe0\xa5\x97\xdc\xef\xbd\xfd\x36\x70\xfd\xfa\x3a\x1d\ +\xce\x18\x6b\xad\x35\x9e\xf7\xc8\xae\xa3\x7e\xad\x80\x99\xdb\x5e\ +\x78\x01\x78\xff\x7d\xf7\x7b\xcf\x3f\x0f\xbc\xf7\xde\x2a\xe1\xb2\ +\xff\xdf\xd6\xd7\x57\x5f\x57\xc0\xcc\x63\xe7\xcf\x03\x5f\x7e\xe9\ +\x7e\xef\xf1\xc7\x81\x2f\xbe\x58\x1b\x5c\x1e\xf5\xf3\x18\x64\x6a\ +\x7c\x32\x47\x05\xa9\xef\xf3\x9b\xc6\x60\xb8\xec\xb9\xe7\xca\xde\ +\x9b\x08\x28\xfb\x2f\x0f\x54\xac\xb5\x16\xcb\x57\xad\x4a\x49\xfd\ +\xbe\xc0\x3d\xaa\x29\x60\x84\xdb\xcd\x9b\x65\xef\x4d\x02\x96\x08\ +\x00\x8c\x67\xd8\xc4\x36\x34\xca\x01\x8e\x9a\x02\x46\xbe\x5d\xbf\ +\x8e\x9f\x02\x78\x77\xfb\xdf\xcf\x00\xe0\xca\x15\xe0\xf6\xed\xe9\ +\x02\xbc\x21\x15\xe0\xeb\xdc\xad\x21\x93\x03\x1c\x55\x35\x84\xbe\ +\xa1\xe3\xd0\x26\x71\x87\x29\xc7\xfb\x31\xa5\x92\xf8\x1d\x76\xf1\ +\x39\x33\x72\x79\xc6\x6e\x4f\x5f\xd9\x15\x30\xe2\x3b\xdf\x4c\xf5\ +\xec\xeb\x88\xa5\x65\x94\x04\x19\xae\x32\x8e\x0a\x97\x5a\xc8\xe8\ +\x10\x49\x2d\x7b\x28\xe4\x1b\x72\x54\x0c\x59\x4c\x08\x38\x3d\x2c\ +\x36\x7c\x52\x53\x05\x23\x5f\xbd\x2c\x1d\x55\x70\x5b\xf8\xc0\xc2\ +\x70\x1d\x2b\x49\xc9\xb4\x2e\xff\x6c\x0a\x26\x1b\x30\xa1\x95\x98\ +\x6a\x19\x80\xe1\x18\xf0\x4e\xd2\xb1\xa4\x42\x66\x4a\xd0\x2c\xca\ +\x63\x60\xfb\x01\x46\x72\xc3\x4b\xa0\xbd\xb5\x15\xea\x45\x18\x60\ +\x7a\xc7\x20\x46\x83\xcc\x90\xa0\xf1\x94\xc3\x05\x99\x26\x31\x98\ +\x9e\x53\x8b\x23\x48\xc9\x59\x86\xe6\x1c\x71\x96\x82\x18\x88\x58\ +\x5f\x0b\xc5\x67\xd4\x2a\x00\xa3\x90\xc9\x77\x44\x9f\xdd\x18\x68\ +\x78\xd7\xeb\xc9\x2c\xdd\xd7\x5c\x75\xa3\x90\xa9\x04\xcc\x2a\x21\ +\x63\xcc\xa3\x97\xc5\xc9\xab\xd6\x7e\xbf\x07\x99\x0f\xb7\x3f\x9b\ +\xce\x60\x91\x38\xb5\x3e\x82\xaf\xb9\x20\x33\x02\x68\x6e\x24\x97\ +\xaf\xc2\xaf\x2a\x37\x96\xcd\x1f\x93\x09\x38\x8a\x3b\x20\x16\x5f\ +\x5c\xe7\x5a\x80\xd7\x73\x51\xde\x08\x81\xcb\x11\x7c\x6d\xb4\x00\ +\xb0\x31\x06\x2f\x03\x78\x8b\x8a\x26\xd4\x80\x59\x05\x64\xb2\x00\ +\xf3\x35\x80\xc7\xa2\x8e\xe5\x83\x49\x0f\xc8\x8c\xb4\x20\x70\x14\ +\x5f\x73\xd6\xa9\xcb\x8f\x3a\xd6\x75\x2b\x18\x56\x2f\xb4\xd3\x98\ +\xcc\xbe\x7d\x90\xd5\xb0\xcb\x06\xf5\xc1\xa6\xa4\x03\xce\x06\x17\ +\x09\xbe\xb6\xdc\x7d\xed\x7b\x25\x3f\xa4\x3a\x0d\xa3\x5a\x2a\xad\ +\xcd\x0c\x0d\x2f\xc7\x7e\x16\x6c\xc4\x14\x85\x52\x0a\x99\x5d\xbd\ +\xa7\xd6\xff\xa8\x5b\x19\x5a\xf8\x5a\x32\x38\x2a\xed\x18\xed\x83\ +\xc2\xad\x87\x71\xa4\x2b\x79\xa7\x1c\x2e\x65\x0d\x91\x4c\x32\x30\ +\x4a\x86\x50\x39\xca\x25\x54\xff\x33\xec\x93\xa2\xde\xbf\xc4\x95\ +\xa8\xca\x18\xdf\xd2\x35\xe0\x21\x80\x0b\x8d\x63\x36\xad\xdb\x9e\ +\x74\x2f\xd2\x94\x4a\xc6\x5a\x5c\xde\x3e\x6d\x1e\x6e\xff\xbd\xbc\ +\x45\x89\xb5\xbb\x61\xb4\x81\x6f\x0e\x68\xd7\xa0\x39\xd0\xc8\x51\ +\x31\x3e\xa8\x1b\x63\xac\xab\x0d\x66\xd9\x84\x49\xb1\x7f\x29\xa6\ +\x4e\x5c\xc9\xab\x72\xeb\x2b\xf4\xf7\xb7\x0a\xee\xa9\x15\x44\x45\ +\x2a\x98\x19\x95\x4c\x6e\xa3\xa4\xc0\x21\x23\x95\x41\xf0\x33\x21\ +\xb8\xb8\x3a\xe3\x8c\x3b\xbc\x73\x95\x4c\xac\x3d\x59\xea\x24\x74\ +\x4d\xc7\x2c\x22\xc7\x7d\xf5\x9a\xe1\x62\xdb\xec\x38\x0b\x64\x7c\ +\x0d\x93\x0a\x8f\xda\x4e\x1d\x98\x71\x0a\xd6\xaf\xe3\x89\x6e\x66\ +\x83\x4b\x8e\xbf\x75\xcf\xf5\x92\x31\x8b\x44\x7d\xaf\x3d\xa7\xcf\ +\x59\x77\x53\x8f\x0e\x99\x18\x44\x52\xe1\xc1\x01\x99\xd4\xba\x75\ +\x65\xf2\x9f\x37\xa7\xc9\x61\x9d\x48\xdb\x37\x94\xeb\x0b\x14\xf7\ +\xdf\x53\xb9\x9a\x35\xef\x8c\xad\x25\x7f\x4a\xc3\x51\x3d\x8d\x16\ +\xdf\x93\x5c\xa7\xdb\xcf\xad\x66\x93\x6a\xec\x68\x14\x09\x70\x2d\ +\xe9\xf0\xa5\xa0\xe9\xbd\xf8\x8f\x3d\xe1\xd4\xa8\x81\xdf\xc2\x75\ +\x28\x41\xe5\x51\xb3\x6f\x65\xef\xb3\x25\xb0\x88\x06\x7f\x67\xb1\ +\x45\x9d\xd8\x50\xfd\x77\xbc\xc7\xa2\xcf\xe4\x3e\xc0\x24\xac\x2c\ +\xde\xb4\x6e\xf4\x91\x9d\x3b\x25\x80\x9b\x1b\xac\xcb\x04\x59\xd6\ +\xc1\x65\x0b\xc0\x89\xcb\x1a\xc7\xf1\x50\xd8\x96\xf9\xe0\x68\x94\ +\x11\x7d\x29\x15\x34\xae\x59\x27\x29\xdb\x16\x9a\x66\xb4\x1b\x75\ +\xa9\x77\xea\xf0\x28\x57\xc6\xe6\x48\x65\x9f\xf4\xcf\xfd\x6e\x89\ +\xf9\x6f\x19\xdb\x6c\x98\xec\x78\x81\x07\x49\xe8\x21\xe1\xf5\x35\ +\x29\x33\x86\xa6\xc3\xc6\xba\x29\x36\xad\xa5\xec\x27\x4a\x04\x47\ +\x4a\x1c\x27\x18\xbc\x2c\x99\xb9\x9a\x7d\x96\x4f\x52\x39\x0b\x86\ +\xdb\x49\x9d\x32\x16\xc8\x5e\x25\x60\xa4\x3b\x77\xa9\x7a\x49\x81\ +\x4f\x09\x64\xce\xd4\x15\x60\x90\xb0\x32\x38\x63\xc5\xf0\x74\xb3\ +\x7c\x52\x60\x9a\xd3\xe9\x73\xd6\x3b\x85\x2e\x29\x0d\x2e\xdd\x00\ +\x23\xd9\xb9\x73\xd5\x4b\xad\x82\x89\x5c\xdb\x0b\x17\x1f\x4c\x6a\ +\xb6\x19\x8c\x04\x99\x82\xe9\x5e\xf6\x72\xd6\x2c\x96\xcb\x69\xb7\ +\x04\xe0\x18\x09\x70\xe9\x0a\x18\x89\xce\x4d\xad\x5e\x2a\x21\x13\ +\x85\x0b\xe1\x35\x87\x8a\xcb\x94\xc6\x17\xb8\xfc\x8d\xe8\x00\xba\ +\x22\xc5\x11\x81\x4d\xf7\x75\x4f\xdd\x8f\x2d\x91\x04\x19\x4a\xf5\ +\x52\x93\x48\x6a\x19\xd0\xad\x1d\x66\xcd\x04\x19\x69\xe5\xa4\x9c\ +\xad\xa9\xc9\x07\xb4\x5c\xef\x24\x05\x32\xdd\x0f\x5e\x93\x32\x85\ +\x9d\x32\x66\x2e\x71\xee\x25\x80\x4a\xd7\xd7\x70\x6f\x54\x1b\x61\ +\x1a\x9b\x62\x66\x84\xaa\x9c\xbe\x14\xa3\x54\x1d\x39\x33\x55\xc7\ +\x3e\x4c\x8c\x63\x98\x6d\x7b\xa5\xf0\x14\x71\xb2\xa3\xd4\x75\x32\ +\xa5\x2b\x6d\x43\x89\xa4\x62\xce\x13\x52\x74\x2d\x20\x23\xb5\x2d\ +\x28\xa7\x5d\x6b\x21\xc3\xb5\xc6\x84\x70\x9f\x91\xcb\x51\xec\x34\ +\xbb\xa9\x47\x1b\x2e\x95\x6c\x5c\xac\xcd\xf3\xe2\xf9\x7e\xdf\xee\ +\x68\xd6\xb8\xcf\x68\x43\xd7\x5e\x39\x8c\x1a\x9e\x6e\x99\xfc\xfd\ +\x39\x3e\xd5\xa3\x3d\x45\x9d\x4d\x2d\xe9\xe9\xc9\x71\x1c\xc5\x52\ +\x42\xef\x7f\x67\xc8\xd1\x5d\x9f\x2b\x09\x48\xd7\xb4\x45\xaf\xf6\ +\xe0\x5e\x30\x96\xea\x73\x12\x13\x7a\x27\x4e\x85\xa7\xec\xb4\x5f\ +\x87\x82\xe9\xf1\xf4\xac\x4d\xbb\x50\x11\xfd\x5f\x8e\x93\x8b\x9f\ +\xa2\x35\xaa\x2a\xa7\x2d\x7a\x2b\xcb\x5e\x3b\xff\x7b\xac\x8a\x4d\ +\x58\x1b\x93\x0d\xbc\x1e\x6d\xb9\x81\x40\xeb\xad\x64\xa8\x73\xba\ +\x44\xae\x91\x05\xd3\xd4\x6b\x73\x28\x99\x96\xed\xd1\xba\x53\xfb\ +\x7c\x6e\x94\x24\x5d\x89\x13\x0e\xcd\xdb\x52\x24\x60\x5a\x49\xf4\ +\xda\x61\x0f\x81\x4a\xc8\xda\xbc\xe8\x1b\x32\xb5\x80\x4c\x4f\xe8\ +\xb7\xea\xd4\x0e\x9f\xeb\x06\x97\x50\xdb\xd5\xdc\x57\x6b\xc8\x88\ +\x05\x4c\x2f\xe2\xe6\xe4\xcb\xa5\x92\xe4\x3b\xb8\x50\x02\x8f\x1a\ +\x32\x2d\x95\x65\xcf\x43\xe8\x42\xe9\x1e\x84\x84\x0f\xaa\xfd\xb1\ +\x65\xbf\x12\x0d\x18\xce\xca\x48\xed\x6c\x29\x69\x19\x6a\x16\x7b\ +\xd9\x45\x1c\x6c\xed\x90\x91\x70\xe4\xea\x12\x32\x1d\xcf\xe6\xe6\ +\xac\x9b\x26\x15\x2d\x1e\x30\x2d\x25\x3a\xf7\xb2\x6e\x17\x5c\x76\ +\x5b\x00\x72\x21\x10\x4a\x66\x15\x3b\x67\x9a\x1a\x32\xbd\xdb\x83\ +\x09\x72\xa2\xd6\x03\x51\xcf\x62\xd9\xd3\xe3\x30\x58\xcb\x38\x04\ +\x60\xa8\x9f\x9e\xa5\xea\x85\xdc\xe1\x89\x4f\x76\xac\xdd\x94\xd9\ +\x5b\x59\xf6\x1c\x1a\x79\xea\x45\x6c\xa2\x34\x8e\x85\x7d\x1c\x65\ +\x1c\x0a\x30\x9c\x12\xbd\xb9\x7a\xf1\xcc\x18\xe5\x66\xcd\xcb\x5d\ +\x35\x2c\x15\x32\x12\x86\x46\xae\xb6\x90\x36\xa3\xc9\x00\x5f\xd6\ +\x8a\x1f\x0e\x30\xb5\x90\x29\x75\x64\x82\x73\x69\x92\xa7\xa3\x29\ +\x94\x4c\x68\xc8\x44\x0d\x19\xea\x4e\x28\x41\xbd\xec\xdf\x43\xcf\ +\x45\x87\x0d\xb6\x87\xb0\x2a\xb5\x21\x01\x43\xad\x64\x52\x17\x32\ +\x51\x6c\xa9\x4f\x5d\xdc\x44\x71\x80\x5b\x4d\x16\xbe\x9a\xf6\x78\ +\xcd\x18\x8b\xd3\xd7\xa7\x30\xe6\xea\x60\x43\xa3\x26\x43\x42\x29\ +\xf0\xe5\x1c\x2a\x0d\x0b\x98\x12\xc8\xd4\x74\xa4\x96\x70\xa1\x84\ +\x00\xf3\x4c\xc4\x41\x7b\xfc\x1a\xc0\xcd\xb3\xbf\x7e\x0a\xc0\x3b\ +\x31\xc8\x8c\x10\xdf\x68\x0d\x99\xc6\xc3\x46\x9e\x24\x5c\xb3\x1d\ +\x1f\x9a\x73\xe8\x7b\xaa\x7a\x29\xc9\x14\x57\x0b\x97\x14\x47\xab\ +\x39\xc0\xad\xa6\x5c\x91\x8b\x7c\x05\xe0\x9c\xe3\x9d\xff\xc0\xda\ +\x4b\xd2\xd5\x4b\xef\xe4\x55\x29\x70\x61\x3c\x8d\x15\x20\x4e\x94\ +\xbe\xc1\x04\x96\xa2\x64\x5a\xab\x17\x4a\x07\xa4\x72\x28\x97\x9a\ +\xa1\x1e\x2a\x79\xe0\x02\x00\xdf\xc3\x44\xc6\x19\xfc\x95\x18\xf0\ +\x5e\x35\x60\x4a\x1b\x3c\x55\xbd\x54\x29\x2a\x22\xe9\x19\x9b\x2d\ +\x92\x02\x99\x4f\x3d\xbf\xff\x58\x68\xec\xa5\xa6\x4e\x5b\xcd\x30\ +\xb5\x3a\x34\x6e\x19\xf0\x55\xc0\x24\x36\x78\x4b\xf5\x12\x3a\x05\ +\x80\xb2\x03\x54\xee\x2d\xca\xda\xeb\x92\x63\x3f\xf7\xfc\xfe\x17\ +\x18\x23\x4b\x5e\x6f\x25\x23\x69\x73\x25\x45\x79\xa6\x02\x4c\x4e\ +\x83\x17\x3c\xad\xba\xc2\x85\x1a\x04\xb1\x32\x15\xa6\xf6\xb4\xb7\ +\x00\x5c\xdb\xc5\x5c\x4e\xff\xbd\x76\x2b\xd0\x26\xa3\xc7\x01\xb9\ +\x16\x80\xa6\x1e\xaa\xd7\x53\xc5\x45\xcb\x34\x43\x90\x37\x81\xbe\ +\x49\x87\xa0\xd7\x06\xd6\x7a\x05\xfe\x08\xd6\xe8\x54\x3b\x5c\x4a\ +\xd9\x1d\x9d\xaf\x7f\xd6\xfb\x6d\xd9\x29\xee\xa1\xa6\xfd\x6b\x0e\ +\xfb\x63\xaa\x13\x4b\xe1\xc7\x1b\x4c\x6a\xbe\x5d\xb1\x4d\xd4\x0b\ +\x73\x22\x9f\xd8\x10\x87\x72\x8c\x9f\xb8\x2f\x2a\xa9\xec\x8e\xf7\ +\xba\x3e\xdd\xa8\x95\x00\xf7\xda\xac\xde\x87\x24\x2a\x60\x1c\xf5\ +\x93\xea\xcc\x95\x71\x9a\x2e\xf9\x6b\x19\x16\xcb\x65\xd7\x4d\x6e\ +\xd9\xa5\x41\x46\xc2\x70\x49\x5a\x52\x2b\xca\xeb\xcf\x0e\x98\x03\ +\xc8\x50\xab\x97\xde\xc9\xb1\x7b\x42\xa6\xa2\xec\x67\x8e\xd7\x90\ +\x96\x7f\xb9\x25\x64\x04\x67\xcc\x33\x0a\x98\xf4\x86\x63\x71\x66\ +\x09\xb9\x83\xb9\x20\x93\x1b\xa3\xaa\x75\x62\x89\x33\x4c\xdc\x90\ +\xa1\x9a\xdd\x94\x6c\x6b\x50\x30\x51\x67\x2e\x09\x9e\x4a\x3b\xf6\ +\x96\x61\xc1\x5c\x10\x32\x14\xe5\x77\xed\x56\xee\x95\xf3\xb7\x05\ +\x64\x62\x65\x93\x34\xe1\xb2\xf0\x27\xab\x80\x49\x03\x46\xf2\x13\ +\x53\x2a\x5c\x42\xf7\xd5\x10\x32\xd4\xb3\x25\xe2\x4f\x95\xa4\x80\ +\xcc\x7e\xd9\x4a\x87\x46\xa3\xcd\xfa\xae\x46\xc1\xd8\xd3\xcc\x71\ +\xc1\xc4\xce\x92\x86\x45\x42\x95\x0c\x4b\xf9\x7b\x41\xa6\xd1\x0a\ +\xd9\x60\xd9\x04\x43\x43\xf7\x22\x95\x48\xdf\xd0\x14\x76\xa8\xc1\ +\xa5\xc3\xa5\x01\x64\xec\xd9\xea\x30\xa4\x1d\x5b\xf2\xd1\xb5\x14\ +\x90\x89\xf9\x9d\x64\x3f\x52\xc0\x14\x38\x73\x4e\x63\x73\xec\x2f\ +\x1a\x09\x32\xbe\x85\x8b\x1c\xb1\x8c\x59\x21\xe3\x50\x05\x76\xd6\ +\x85\xae\x53\x02\xa6\xc0\xd9\x0f\x20\x93\xd4\xe0\x83\x38\x05\x87\ +\x92\x71\xd5\x91\x42\x26\xdb\x3f\x67\x06\xe8\xba\x14\x4c\x02\x30\ +\xa2\x4a\x66\x94\xa1\x51\x0a\x64\x4a\xf7\x18\x2d\xcb\xef\x82\x0c\ +\xf7\xca\xd8\x91\x3b\xa3\x23\xa8\xbb\x0a\xc8\x4c\x03\x98\x82\x60\ +\x2d\x3c\x92\xdf\xce\x02\x17\x0a\xc5\x11\x2a\x7f\xee\x5a\x99\xc2\ +\xfc\xc2\xab\x98\x61\x92\x5e\xae\xd2\x87\xc7\xd4\x0a\xa6\xe4\x48\ +\xcd\x23\x00\xc7\xdb\x7c\xb2\x9f\x1a\x63\xaf\x0e\x0e\x97\x1a\xc8\ +\xe4\x9e\x82\xc0\x35\x64\x1a\x1d\x32\xa1\x29\xe9\xd9\x95\xcc\x14\ +\x80\x21\x9b\x29\x01\x7e\x73\x17\xc0\xf5\xed\xff\x9f\x06\xf0\xce\ +\xc9\xef\xaf\xcd\x02\xdc\xd4\xb8\x4c\xe9\x29\x08\x29\x43\xa6\xd2\ +\xe3\x4e\x47\xec\x8c\x29\xeb\x5d\x66\x86\xcc\x2c\x39\x79\xb3\x9c\ +\xd8\xdb\xe8\x99\xf9\x64\x7b\x94\x91\xf0\x6c\xec\x18\x20\xb2\x87\ +\x86\x31\xa0\x50\x95\x81\x62\xd8\x4a\x5d\x9f\xa5\x70\x29\x29\x57\ +\x8b\x7b\x77\xdc\x53\x11\x2b\x36\x33\xc2\xa5\xc2\x56\x91\x4f\x76\ +\xe9\x9c\x8e\x44\x56\x45\x1d\xb8\x55\x82\xa4\x99\x82\xbf\xb3\x2b\ +\x99\x29\x63\x30\x45\xea\x05\xfe\x7c\xb2\xc7\x98\xd3\x5c\x90\xa9\ +\x55\x07\x3d\x20\x53\xd3\x21\xb9\xf6\x24\x55\x6c\x05\x10\x03\x99\ +\xd5\xa7\xcc\xa4\x74\x0e\x63\x8c\xf5\xe5\x93\xbd\x89\x79\xa7\x12\ +\x7d\x9b\xda\x6a\x82\xda\xad\x86\xdd\x52\x83\xbf\xb5\x29\x18\x66\ +\x52\x68\x6b\xda\x4d\x1d\x8a\xbd\x00\x00\x7c\xf9\x64\xef\x4d\x26\ +\x5b\x3d\x40\x20\x9d\x8e\xa7\xce\xf7\x1b\xea\x8c\x54\x27\x4a\x48\ +\x80\x8b\x50\x78\x96\x9f\xe9\x35\x6a\x90\xb7\x30\xc5\x82\xf3\x6f\ +\x73\xf3\xc9\xf6\x98\xb2\xe6\x0c\xec\xb9\xb6\x01\x70\x04\x93\xb9\ +\x3b\x7a\x6e\x1b\x71\xd4\x29\x75\xde\x5c\xc7\xc6\x48\xd3\x2a\xc8\ +\xbb\x1f\xe0\x2d\xbd\xde\xaa\x14\x4c\x29\x5c\x24\x8d\x8d\x5b\x25\ +\x1a\xe2\x4c\xf7\xc0\xa9\x66\x4a\xda\x88\xea\x1e\xb8\xb6\x4c\xf4\ +\xf0\x3d\xaa\xeb\x0c\x09\x18\x0a\xf5\x52\xf2\xc4\x9b\x74\x7f\x8c\ +\x5d\x38\x33\x3b\xcc\x38\xf7\x32\x49\x49\x60\xc5\x99\x86\x13\x03\ +\xe5\x31\x5e\x85\x82\xa1\xcc\x27\xdb\x0b\x32\xdc\x43\xa3\xfd\x72\ +\xb5\xc8\xf3\xeb\x5a\xf4\x47\xa8\x9a\x92\xe2\x17\x5c\xc3\x40\xae\ +\xa1\x4b\x0f\xc8\xac\xee\xd8\x12\xa2\xd5\xa1\xb5\x53\xb1\xc3\x2b\ +\x99\x18\x60\x39\x21\x13\x8a\x53\xb4\x86\x4c\xeb\x21\x21\x45\xd3\ +\x71\xfb\x1e\xe5\xf7\x4e\xaf\x60\x1c\x8e\x43\x35\x15\x3b\x2c\x64\ +\x32\xe2\x4e\x6c\xc3\xa5\x56\x90\xe1\x6e\xa7\x4e\xc9\xb7\x87\xf1\ +\xbd\xcd\xe0\xb0\xc8\x7d\x6a\x50\x4f\xc5\x76\x69\xe8\x96\x67\x38\ +\xb5\x84\x4c\x8b\x21\x93\x27\x5d\xe5\x39\x00\x8f\x49\x1d\x1a\x79\ +\xea\x8b\xdd\xf7\xf6\x67\xac\x54\xc1\xc4\x1d\x80\x3d\x97\xec\x4a\ +\x16\xe3\xb1\xc2\xb2\xc5\x90\x69\x1b\xfc\x7d\x6e\xef\xed\x6f\x00\ +\x78\xda\x18\x73\x4e\x2a\x5c\x5a\xf9\x1e\xb5\x0f\x6f\x06\x85\x45\ +\xb4\x61\x5b\xc0\xa5\x35\x64\x7a\x6e\x12\xa4\x80\xcc\x22\x4d\x41\ +\x57\xc8\x00\xf8\xbb\x31\xe6\x27\xdb\x9f\x1f\x03\xf0\x3f\x14\x2e\ +\x28\x6b\x0d\x97\x1e\xe9\x1e\x4a\xcb\x38\xcc\x42\x3b\xc9\x80\xa1\ +\xe8\xc0\xb9\xf5\x90\xdb\x6e\x54\xf7\x56\xbb\x90\x6c\x79\xff\x09\ +\xbb\xba\xc9\x3b\xb3\x67\xf1\xda\xe3\x00\xbe\x06\xf0\x8c\xb5\xf6\ +\xbf\x12\xd5\x4b\xa8\xed\x89\x76\x98\x1f\x7c\x47\xed\xa2\xbe\x8d\ +\xc2\x65\x3c\x25\x93\xfb\x54\xa7\x04\x5f\xab\xd9\x25\x4e\x35\xe3\ +\x09\xfe\x7e\x7f\xdb\x1f\x3e\x37\xc6\x7c\x53\x2a\x5c\xb8\x7c\x8f\ +\xcb\x5f\x67\x9f\x45\x6a\xbe\xbc\x5f\x5a\x4c\x86\x43\x55\xcd\x00\ +\x19\x47\x7d\xdc\x03\xf0\x3c\x80\x2f\x25\xc7\x5d\x32\xe3\x4c\x96\ +\xa0\x5e\xe6\x06\x4c\x85\x7a\x71\x1e\xb3\x31\x3a\x64\xa4\x38\x75\ +\x2d\x64\x42\x9d\xd5\x07\x19\x8e\x59\xa6\x45\x67\xfa\x2b\x80\x1f\ +\x01\xf8\x2a\x16\xf0\xed\xd5\x0e\xb1\xeb\x96\x6c\x92\xf4\x3d\x84\ +\x28\xea\x77\xd6\x63\x4b\xba\xc1\x45\x92\x92\xe1\x8e\x09\xb5\x56\ +\x32\x9c\x43\xa6\xbd\xff\xfe\x0d\xc0\x1f\x00\x3c\x65\x8c\x39\x4f\ +\xad\x9a\x5a\xfb\x5e\xcc\xff\xd8\x87\xf2\x92\x83\xbc\x85\xea\x85\ +\x6d\x67\xb0\x94\x4e\x9e\x12\x68\x6d\xb9\xfb\x3b\x37\xf0\x1b\x0b\ +\x1c\x26\x96\x8f\x5c\x51\x38\x3a\xdb\x8f\xad\xb5\x7f\xee\x3d\x34\ +\xaa\x09\xac\xc7\xfc\x20\xf4\x3e\xc5\xae\xed\xd9\x8e\x2d\xe9\xae\ +\x5c\x24\x28\x99\xd6\xa9\x25\x4a\x95\x4c\xca\x03\x24\xf4\x37\xd4\ +\x43\x26\x47\x5d\xfd\xc9\x18\xf3\x03\xc9\x71\x97\x1a\xff\x4b\x81\ +\xcb\xb4\x43\xa4\x82\x55\xbb\x4e\xb8\x48\x70\x08\x6a\xc8\x70\xe4\ +\xd3\x95\x36\x5c\x2a\x8d\x49\x50\x40\x66\x51\x6f\x1f\x20\xe7\xd4\ +\x4f\x41\xea\xc5\xe5\x07\xbf\xdb\x1e\xc9\x73\xdf\x18\xfb\x46\x83\ +\xfb\x9f\xe5\xd8\x12\x71\xca\xa5\x87\x92\xe9\x9d\x14\x2b\x15\x32\ +\x29\x1d\x25\x07\x58\x0d\xe2\x32\xcb\x07\xd8\x70\x66\xad\x35\x6f\ +\x00\x78\x65\xfb\xff\x67\xb6\x3f\xbf\x81\xf4\xfd\x68\xd3\xc4\x60\ +\x72\xd4\x4b\x08\x2e\x42\xcb\x46\x06\x01\xc7\xa2\x35\x11\xa7\x50\ +\xe6\xc6\x50\x52\xf3\xf8\xe4\xc4\x77\x6a\x7d\xc0\x33\x61\xd0\xbc\ +\x5e\x49\xb3\xd7\x19\xf3\xc9\x96\x2d\xfb\x76\x1f\xd6\x7e\x87\x5a\ +\x35\xcd\xa1\x60\xcc\x78\x87\x2d\x32\xed\x1f\x11\x75\xc4\x2d\xe5\ +\x70\x29\xf7\xbb\x18\xd4\x4c\xb7\xdc\xb8\xd4\x43\xcd\xfb\x87\x70\ +\x59\xdf\x10\x29\x59\xbd\x18\x03\xd3\x61\x21\x9d\x50\xc8\x88\xab\ +\x87\xdc\xc5\x73\x92\x20\xe3\xd8\xe5\x7d\x00\x99\x91\x36\xb6\xee\ +\xee\xf7\x4d\xf7\xdb\x6f\x72\x0d\x8f\x44\x0e\x91\x92\x00\xb3\x80\ +\x8b\x2b\xee\x32\x42\xb4\x9f\x6b\xff\x88\xd4\xf6\x0c\x75\xfc\xdc\ +\xe1\x4f\x6a\xfb\x16\x9c\xae\x18\x82\x9b\x4d\x88\xd5\x34\x1b\x66\ +\xe6\xfa\x07\xb6\x31\x97\x57\x1e\x09\x1a\xbc\x09\x6b\x7f\xc9\x35\ +\x3c\x12\x07\x98\x52\xb8\x58\x6b\xc5\x2f\xe3\xe6\x00\xc4\x32\xfe\ +\x24\xb5\xcc\x81\xd3\x1c\xaa\x80\x51\x03\x19\xb7\xab\xa5\xf9\x50\ +\x0b\xd0\x10\x6c\x2a\x2d\xba\x47\x6a\xc0\x8c\x97\x32\x33\x61\xad\ +\xcb\x48\x6b\x15\x28\x87\x4b\x52\x57\x9b\xfa\x94\x4b\x49\xac\xa6\ +\xf6\x33\x27\x9f\xfb\x76\xf6\x67\x62\x1d\x95\x72\xc8\x34\x0b\x5c\ +\x44\x01\x26\x71\xd1\x95\x73\x95\xae\x19\x30\xd8\x5b\x0b\x99\x11\ +\xa6\xe6\x29\xe3\x22\x14\x90\x39\xfd\xdc\xaf\x82\xea\x25\x71\x1a\ +\xdd\x48\x8b\xcd\xb8\xae\xef\xba\xcf\x96\x76\x6e\x94\x4e\xc8\x79\ +\x38\x98\x14\xc8\xec\xca\x68\x8c\xb1\x21\xa7\x08\xe5\xed\x30\xc6\ +\x88\xad\x97\xe5\xd0\x68\x77\xaf\x25\xf7\x5e\xf7\xb9\x7f\x07\x86\ +\x46\xff\x2a\x6e\x37\xa2\x61\x6f\x16\xe8\x42\x40\xcb\xb9\x36\x87\ +\x7a\x11\x33\x6e\x8f\xa9\x17\xdf\xd3\x7a\xe4\xd8\x4b\x69\x4c\x26\ +\x75\xe7\xab\xf4\x7a\x20\x3a\x1d\xa2\x30\x26\xe3\x83\x8b\xdd\x7e\ +\x0f\xc9\x43\x30\xbb\xc3\xa7\xb6\x61\xed\x75\x14\x30\x67\x01\x12\ +\xdc\x02\x30\x1b\x60\x0e\x20\x02\x18\x24\x2e\xa4\xe3\x72\x92\xd9\ +\x20\xc3\x05\x98\xdc\x98\x4c\x68\xff\x4f\x6c\xf6\x8a\x02\x2a\x2d\ +\xfc\x66\x33\x0a\x5c\x72\x33\xe0\x0f\x4c\x97\x13\xa8\x00\x38\x02\ +\x70\x0c\x58\x18\xf3\xe0\xd8\x18\x7b\x14\xa9\x8b\x5e\xfb\x81\xa8\ +\xe2\x32\xa5\xf7\x5e\x5e\x6e\xbe\x73\xcc\x76\xb1\x8f\xd8\x50\x77\ +\xff\xb5\xbd\x09\x0b\x60\xf9\xfb\xe2\x6b\x94\xf6\xbf\x69\x14\x8c\ +\x17\x30\xf0\xcf\x18\x89\x53\x2f\xae\x32\x94\xeb\xec\xdd\x4f\x47\ +\x00\xee\x2e\xdf\xbe\x0c\xe0\x6e\xd8\x69\x87\x03\x6e\xed\x42\xbc\ +\xdc\xb2\x9f\xfc\x99\x1f\x2e\xdc\xd5\x26\x21\xd3\x5c\x2b\x7f\x39\ +\x37\x2a\x5c\x24\x29\x0e\xef\xef\xeb\xee\xf5\x35\xd7\x2f\xef\x02\ +\xc7\xb1\xa7\xf9\x08\x01\x5f\xdf\x3d\xef\xfb\x06\xdf\xbd\xf7\x83\ +\x4b\xce\xd0\xa8\x65\xff\xe3\xba\xae\xe9\x9c\x8c\xc9\xdb\x06\xfb\ +\x8d\xe1\xaa\x08\xd1\xea\xa5\xd0\x5b\x8d\x01\xec\xa9\xf3\x3f\x00\ +\xf0\x2d\xc7\x9f\x3d\x84\xb5\x17\x72\xea\x75\x4d\x4a\x86\x72\xa3\ +\x65\x8f\x32\xf7\x4a\x22\xce\x75\xdd\xcd\x88\x70\x99\xd1\x76\xc5\ +\x34\xa7\xc5\xbf\xe5\xfa\xbb\x63\x37\x74\x82\xce\x32\xca\x3a\x21\ +\x8a\xf6\x8d\xc5\x63\x14\x2e\x3c\xf5\x2e\x4e\xc1\x78\x9c\xde\xbb\ +\xbe\x43\xa4\x7a\x21\x54\x30\xcb\xaf\x39\x82\xb1\x77\x1d\x7f\x77\ +\x19\x27\xe9\xef\x7b\xad\xcc\x6c\xe9\x1b\x3f\x04\xf0\x47\x00\xdf\ +\x2d\xa8\xd3\x98\xdf\x48\x9b\x3d\x9d\x6d\x68\xd4\x55\xc1\xd4\xc0\ +\x65\x1d\x66\xec\xbd\x2d\x4c\x4e\x44\x0b\x1e\x6e\xff\xbd\x7c\xef\ +\xb4\x0e\x53\xa6\x2e\xe9\x54\x8c\x31\x87\x2f\xc6\x27\xea\x11\x80\ +\x7f\x2c\xe1\x12\x03\xba\xa3\xec\x2f\x6f\x3f\xf3\xa1\x31\x27\x3f\ +\x2b\x5c\x1a\x7b\x72\xe7\x34\x80\x3e\xb8\x84\x1a\x41\x1c\x78\x08\ +\x66\x91\x16\x6b\x33\xf6\x3e\x6c\xcd\xf2\xeb\x72\xa6\xef\xc9\xd4\ +\x1e\xe5\x4c\x59\x2b\x65\xe8\xf8\x8e\x1b\x00\xde\xd2\xb8\x4b\xb3\ +\xeb\x9a\x9e\x19\xd2\x6b\xe1\x32\x93\xb2\x39\x2d\xd6\x21\x5c\x5c\ +\x7d\xaa\x39\x64\x26\x01\x0c\xfb\x7d\x2b\x5c\xfa\x0f\x91\x4a\xe1\ +\xb2\x86\xa1\x91\x0b\x2e\x9e\x21\x40\xf2\x06\xc9\x51\x83\xbe\x2e\ +\x3b\x1e\xfa\x21\xb2\x2e\xb8\x34\x57\x30\xa9\x67\x46\xaf\x4d\xbd\ +\x1c\x42\xc2\x9a\xd4\x87\x6e\x33\x25\x23\x44\xc1\xec\x05\xb9\x87\ +\x52\x30\x6b\x84\x8b\x04\x05\x13\xac\x90\xb5\xa8\x97\x52\xb8\xb0\ +\x2b\x99\x65\x40\xd7\xda\xb3\x2f\x56\x6d\x6b\xbd\x70\x49\x56\x61\ +\xae\x7b\x54\xb8\x34\xd6\xe4\x7d\x0a\x9b\xad\x5e\x66\x86\x4f\xeb\ +\xd4\x99\x49\x8e\x47\xfd\xf4\x5f\x7e\x5f\x7d\xb6\xff\x21\xfc\x60\ +\xcd\x70\xe9\xa5\x60\xbc\x89\x92\x52\xe1\x32\xab\x7a\xa9\xd9\x6b\ +\x22\x3a\x26\xe3\xfa\xfe\xd0\x35\x03\x53\xe2\x23\xf9\xc5\xda\xe1\ +\xd2\x0c\x30\xbe\x43\xe9\x45\x4f\x3d\x0f\x04\x97\x21\x20\x53\x19\ +\x7b\x19\x0d\x32\x0a\x97\xf6\x0a\xa6\x38\xc5\xe3\xf0\xc1\x5d\xc7\ +\x13\x99\xeb\x34\x80\xb5\xcc\x2e\xb9\xee\x5d\xca\xfd\x77\x59\x6f\ +\xe2\x28\xbf\x84\x7e\xb2\x69\x54\xd9\xc1\x74\x97\x6b\x53\x2f\xdc\ +\xe7\x39\xd5\x42\xc6\xf8\x76\x82\x0b\x6b\x1b\xcf\x49\x9f\x5d\xc1\ +\xd2\x0b\x2e\x29\x75\x33\x1f\x60\x3c\x70\xa1\x76\x2a\x85\x0b\x2d\ +\x64\x1e\x39\x2d\xd5\xac\x11\x23\xac\xce\x26\xf3\xee\xa7\x66\x7a\ +\x74\x72\x1f\xd0\x24\xf5\x91\x4d\xab\xce\x04\x4f\x4e\x97\x98\x7a\ +\x99\x35\xb8\xdb\x62\x58\x66\xf7\xea\x3c\x05\x32\xac\x43\x26\xe6\ +\x29\xee\x9e\xbe\xd3\xa3\x93\x4b\x56\x2d\x67\xee\x93\x2d\xd1\x4c\ +\xc2\xa1\xf4\xb9\xb9\x3b\x86\x55\x30\xbb\x04\x50\xad\x8e\x78\x5d\ +\x26\x6f\xca\xbc\x6e\x77\xe7\xad\x5c\xd4\xd7\xea\xfe\x7b\xa9\x96\ +\x91\x54\xbd\x61\xaa\xf8\x28\x5c\x4a\xd4\xcb\xc8\xc3\xa3\xa6\x47\ +\xbc\xba\xf2\xa0\x10\x40\x66\xa4\x36\xe0\xbc\xff\x1e\x75\x33\x6a\ +\x7b\x6c\xb8\x9d\xfb\x28\xa1\xc2\x66\x0f\xec\x4a\x38\x3f\x3a\x67\ +\xb8\x14\x92\xf9\x23\x25\xaf\xf2\xdd\x7f\x4d\x19\x7c\x0a\x82\x71\ +\x24\xd0\xfc\x9a\x72\x15\xcc\xe2\xdc\xe8\x33\x4e\x8d\xfc\x99\xa3\ +\x19\x20\xd4\x05\x2e\x81\x0e\x54\x3a\x4c\x1b\x4d\x9a\xe7\x80\x31\ +\xf1\x80\xb3\x66\xe5\xaf\xbd\xd7\x39\x01\x13\x80\xcb\x12\x30\x6b\ +\x89\xbd\x74\x53\x2e\x91\x54\x07\xb1\x93\x23\x67\x1d\x36\xa5\x28\ +\xb0\xd4\x40\x37\x75\x99\x73\xef\x6b\x14\x23\x3b\x55\x20\x04\x97\ +\x35\x0c\x83\x84\x8d\x0f\x82\x81\xd2\x52\xd8\xf9\x52\x96\xba\x0e\ +\xb3\x97\x3c\x74\x0a\x75\xea\x96\x1d\x7d\x56\xa8\x90\x2b\x98\x83\ +\x93\x08\x7d\x4e\xaf\xea\x65\x2a\x9b\x4a\xca\x67\xc4\x65\x6a\x4e\ +\x3a\xa0\xfe\xee\xd5\x0c\x91\x8c\x31\xd6\x06\x16\xd2\x99\xdd\x69\ +\x47\x09\x15\x39\x3a\x60\xd6\x00\x97\x99\x40\xd3\x3b\x70\x3d\xb3\ +\xba\xdf\x10\x56\x92\x31\x81\xe3\x37\x2d\x0c\xfe\x09\x3c\x4a\xbc\ +\x3c\x2b\x5c\xf6\xa1\xb2\x06\xb8\xec\xda\x28\x36\x6b\x23\x6d\xaf\ +\x50\xe8\x9e\xf6\xcb\x13\x2a\x5b\x4d\x3d\x51\x7e\xef\x2a\x14\xcc\ +\x49\xc3\xb9\xc1\x12\x1a\x32\xcd\x08\x18\xb5\x74\x55\xd0\x6b\xaf\ +\xce\xda\x94\x44\x2f\x3b\x27\xf9\xe6\xb4\xc1\xc7\x56\x35\x29\x9d\ +\x9c\x7a\xfa\x77\xcd\xf1\x0e\x05\x8c\xf0\xb1\xb0\x5a\x7f\xe0\x70\ +\xfa\x81\x02\x45\x15\x8c\xda\x4a\x81\x43\x0d\x16\x85\x89\x10\xd1\ +\xc0\x1d\x83\x01\x1c\x71\x98\xc9\xf7\x1d\xa9\xa9\xa9\x31\x28\x18\ +\xdf\xfa\x2e\x03\xeb\xdf\x0c\x7b\xff\x3e\xf0\xfa\xeb\xb0\x00\x3e\ +\xbb\x78\x11\x7f\x39\x3a\xc2\x6f\x15\x2e\x6a\x6a\xaa\x60\xaa\xed\ +\xe3\x8f\x81\x67\x9f\x75\xbf\xf7\xd1\x47\xc0\xa5\x4b\xda\x42\x6a\ +\x6a\x03\x5b\xdf\x73\x91\x5e\x7d\xb5\xec\x3d\x35\x35\x35\x55\x30\ +\x51\xbb\x78\x11\x78\xf0\xc0\xfd\xde\x85\x0b\xc0\xe7\x9f\x6b\x0b\ +\xa9\xa9\xa9\x82\x29\xb4\x17\x5f\x2c\x7b\x4f\x4d\x4d\x4d\x15\x4c\ +\xd4\x34\x06\xa3\xa6\xa6\x0a\x86\xcd\x2e\x5d\xc2\xfb\xb7\x6f\xe3\ +\xbd\x2b\x57\x00\x00\x9f\x3d\xf1\x04\x70\xf3\x26\xf0\xc9\x27\x0a\ +\x17\x35\x35\x55\x30\x6a\x6a\x6a\x6a\x52\x15\x8c\x9a\x9a\x9a\x02\ +\x46\x4d\x4d\x4d\x4d\x01\xa3\xa6\xa6\xa6\x80\x51\x53\x53\x53\xc0\ +\xa8\xa9\xa9\xa9\x29\x60\xd4\xd4\xd4\x64\xda\xff\x01\x44\x08\x3a\ +\x88\x31\xa7\xcd\x9c\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\ +\x82\ +\x00\x00\x05\x7a\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\xc5\x00\x00\x00\x63\x08\x02\x00\x00\x00\xa4\x1f\x53\x54\ +\x00\x00\x00\x15\x74\x45\x58\x74\x43\x72\x65\x61\x74\x69\x6f\x6e\ +\x20\x54\x69\x6d\x65\x00\x07\xe2\x05\x02\x09\x3b\x32\xdb\x04\x31\ +\x95\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xe2\x05\x02\x0a\x00\x03\ +\x61\x2f\x83\xbe\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0e\x74\ +\x00\x00\x0e\x74\x01\x6b\x24\xb3\xd6\x00\x00\x04\xf8\x49\x44\x41\ +\x54\x78\xda\xed\xdc\x3d\x6f\xda\x5a\x1c\x06\xf0\x03\x69\x32\xdd\ +\x11\x21\x44\xd3\xc1\x6a\x93\x48\x08\x31\x64\x65\x61\x40\x4a\x76\ +\x16\xeb\x0e\x0c\x77\xcb\xe0\x85\x0f\x10\x44\x3e\x40\x16\x0f\xec\ +\x99\x58\xbc\xa7\x52\x07\x16\xd4\xa1\x2a\x43\x14\x21\xa5\x89\xc4\ +\xd2\x5a\x11\x62\xec\x56\x25\xbe\xe7\xd8\xbc\xe6\x05\x1b\x78\x5c\ +\x08\x7e\x7e\x8a\x54\x6a\x8e\xff\x3e\xc1\x0f\xe7\x85\xa4\x8d\x99\ +\xa6\x29\x88\x40\xe2\xab\xee\x00\x6d\x14\xe6\x89\x90\x98\x27\x42\ +\x62\x9e\x08\x89\x79\x22\x24\xe6\x89\x90\x98\x27\x42\x62\x9e\x08\ +\x89\x79\x22\x24\xe6\x89\x90\xd6\x34\x4f\xbb\xb7\xb7\xbb\x3f\x7e\ +\xac\xba\x17\x34\xb7\x35\xcd\x53\xfa\xee\x4e\x7e\xad\xba\x17\x34\ +\xb7\x35\xcd\x13\xbd\x51\xcc\x13\x21\x31\x4f\x84\xc4\x3c\x11\x12\ +\xf3\x44\x48\xcc\x13\x21\x31\x4f\x84\xc4\x3c\x11\x12\xf3\x44\x48\ +\xcc\x13\x21\x31\x4f\x84\xc4\x3c\x11\x12\xf3\x44\x48\xcc\x13\x21\ +\x31\x4f\x84\xc4\x3c\x11\x12\xf3\x44\x48\xcc\x13\x21\x31\x4f\x84\ +\xc4\x3c\x11\x12\xf3\x44\x48\xcc\x13\x21\x31\x4f\x84\xc4\x3c\x11\ +\x12\xf3\x44\x48\xcc\x13\x21\x31\x4f\x84\xc4\x3c\x11\x12\xf3\x44\ +\x48\xcc\x13\x21\xbd\x5b\xec\xb4\xdd\xdb\xdb\xf7\x37\x37\xe1\x75\ +\x6b\xff\xfb\xf7\x98\xe3\xc4\x1e\x1f\xc3\xbb\xc4\xaf\x83\x83\x9f\ +\x7b\x7b\xe1\xd5\x8f\xa6\x05\xf3\xf4\x18\x8b\x3d\x6e\x6f\x87\xd7\ +\x2d\x27\x1e\x77\x1c\x27\xd4\x4b\xc8\x6f\x21\xbc\xe2\x91\xb5\x60\ +\x9e\xec\x4f\x9f\xe4\x57\x78\xdd\x52\x23\x93\xe3\x7c\x3b\x3a\x5a\ +\xcd\xab\x42\x8b\xe2\xfa\x89\x90\x98\x27\x42\x62\x9e\x08\x89\x79\ +\x22\x24\xe6\x89\x90\x98\x27\x42\x62\x9e\x08\x89\x79\x22\x24\xe6\ +\x89\x90\x98\x27\x42\x62\x9e\x08\x89\x79\x22\x24\xe6\x89\x90\x98\ +\x27\x42\x62\x9e\x08\x89\x79\x22\x24\xe6\x89\x90\x98\x27\x42\x62\ +\x9e\x08\x69\xc1\xdf\x1f\x5f\x7f\x86\x61\xac\xba\x0b\x6f\x8f\x69\ +\x9a\x4b\x56\xd8\xd8\x3c\x09\xc4\xab\x13\x29\x90\x77\x20\xe7\x3b\ +\x42\x62\x9e\x08\x89\x79\x22\x24\xe6\x89\x82\xfa\x13\xdb\xf1\x6d\ +\xc3\x3c\x51\x50\x31\xc7\xf1\x6d\xb3\xc9\xfb\xbb\x10\x75\x1a\x46\ +\xbd\x25\xff\xcc\x9f\x98\x7a\x66\xe9\x66\x6f\xc4\x3b\xf1\xc7\xb7\ +\x4d\x64\xc6\xa7\x7e\xf3\xdc\x18\x38\x6f\xf6\x17\xac\x30\x38\xb3\ +\xd3\xa8\xdb\xa5\xaa\x29\xcd\x4c\xc9\xb0\x59\xb5\x64\x5f\xfa\x5d\ +\x72\x5c\x7c\xb1\x2e\x81\x0a\x2e\x2d\x22\xe3\x53\xa7\x51\x6b\x1f\ +\x56\xcd\x4a\xc2\x7d\xdc\xe8\x09\x91\x98\xbb\x46\xa2\x50\xa9\xb8\ +\x0f\xfa\x3d\x5b\xa4\x73\xbe\x05\xc6\xcd\x86\x27\xc2\x25\x42\xab\ +\xbc\xa8\x88\xe4\x49\x4a\x27\x07\x09\xc8\xe8\xba\xfb\xf6\xbd\xb8\ +\x4f\x8b\x56\xab\xab\x0e\x0d\xe6\x23\x79\xb0\x66\xb9\x07\xb4\x52\ +\xb5\x52\xf0\xc2\xe7\xcd\x58\xb2\xcd\xbf\x25\xfb\xab\x28\x57\x92\ +\x5f\xdc\x36\x75\xa3\x25\xb4\xa3\x23\xf1\xf9\xf7\xb1\x37\x4a\xc9\ +\x96\x97\xa9\xc1\x59\xee\x5f\x47\xcd\x76\x73\x1f\xb6\xf7\xfe\x2b\ +\x8b\x8b\x0b\x71\x98\xb6\xac\x96\xba\x5a\x72\xea\x52\xd9\xeb\x0b\ +\xab\xdb\x15\x35\xc3\x1a\x5d\xd8\xaf\x87\xee\x01\xd5\x46\x94\xc7\ +\x95\x27\x9e\x98\x2c\x98\xbd\x3e\x57\x6f\xa7\x51\xdf\x5e\x16\x8f\ +\x03\x26\xab\x35\xcd\xd3\xcf\xfd\x7d\x11\x60\xf5\x17\x58\x26\x97\ +\xaf\xab\x5b\x3b\xb9\x90\xe9\xda\x29\x39\x19\x25\xbc\xcc\x34\x72\ +\x66\xee\xaa\x76\x7f\x6c\x9a\xea\x69\x75\x9f\x9a\x59\x75\x1f\xea\ +\xe2\xc4\x3b\xe4\x1e\xfc\xaa\x4a\xe9\x72\xfe\x92\x77\x71\x90\xb7\ +\xdf\xc6\x55\x47\xcf\x64\x44\xe7\xaa\x95\x3f\x36\xc7\x37\x6c\xa2\ +\x99\x7b\xd7\xdd\x2b\x5a\xea\x02\x5e\x9a\xad\xf4\x89\x59\xc9\x0c\ +\x46\xcb\x42\xa1\x5c\x6a\x8f\x6a\x8e\xbc\xd0\xc3\xc1\x69\x2a\x57\ +\x8d\x8e\x59\x1c\xb5\x1c\x56\x56\xa9\x6e\x16\x33\xd3\x05\x83\x4d\ +\x7b\x9b\x9c\x27\xfb\xe3\x47\x6c\xc1\x8c\x3e\x7c\xb9\x8d\xba\x4a\ +\x95\xbc\x13\xda\x61\xd6\xbb\x7b\x32\x6c\xe2\xb2\x77\x93\xb2\xe5\ +\x60\x60\xb4\x86\x67\xe4\x7b\x7d\xd1\x16\xa5\xf2\xec\x75\xb4\x0a\ +\xaa\x0a\x94\x90\x71\xca\xe9\x3e\x9d\xd0\x4a\x45\xaf\x5a\x22\x99\ +\x16\x56\xdd\x10\x2a\xdd\xfa\xeb\x67\x3d\xef\xa1\x36\xe8\x4f\x22\ +\x7b\xa8\x59\x57\x9d\x62\xea\x69\xe5\x64\x4a\x7b\x5e\x47\x4e\x8b\ +\x66\xc1\xf7\x25\xda\xd9\xf1\xff\x38\xc0\xd7\x9a\xe6\x29\x2c\x2a\ +\x56\xb9\x86\x31\x71\x27\x26\x8d\x27\x1b\x57\xbf\x79\x19\xa0\xa2\ +\x1b\xa8\x9c\x08\x10\xa7\x27\xfd\xd0\xc7\xe9\x5e\x8b\xcd\x1f\x64\ +\x7c\x8a\xc6\xfe\x4e\x4e\x0f\x8d\xce\xf0\x71\xcf\xd6\x52\xc9\xc9\ +\x67\xe5\x54\x25\x47\x82\x83\x64\xba\x6b\x7d\xe9\x4c\x1c\x97\x83\ +\x80\x98\x3e\xf2\x92\x4c\xb1\x64\xd7\xe5\x46\xae\x38\x47\x26\xfa\ +\xcd\x86\xda\x7b\xc9\x54\x55\x4b\x9a\xdd\xf3\xe6\xa3\xee\x7d\xef\ +\xb5\xf6\xcf\x7a\xd8\xbf\x6e\x77\xf3\x39\x9f\x2b\x8e\x0a\xaa\xbd\ +\xed\x5f\xda\xec\x45\x63\x7c\x92\xc9\xb0\x6b\xc3\x1f\x9f\xab\x51\ +\x28\x21\x9a\x72\xcd\x21\x97\xab\xee\x11\x39\x42\xa8\x61\x49\xae\ +\x78\xce\xa7\x5a\xa9\x55\xc8\xe8\x88\x5c\x8f\xbf\x5a\x5c\x6b\x8b\ +\xec\x3c\x1b\xc6\x44\x52\x0c\x2f\xee\xf5\x46\xee\x01\x8f\xf3\x86\ +\x5c\xe1\x4d\x0d\x91\xb3\x7a\x28\x8f\xc8\x65\x54\xef\xf5\x4b\x4c\ +\x14\xcc\x06\xea\x14\x64\x7c\x8a\x6d\xea\x2f\x75\x18\x86\x31\xeb\ +\x5b\xf3\x76\x46\x3e\x3b\x9e\x60\x9e\x6c\xec\x50\x80\x3d\x0c\xe6\ +\xec\xec\xec\xf4\xf4\x74\xc9\x22\xd1\x98\xef\xc2\xe4\x6e\xec\xfe\ +\xde\x5d\x0f\xcf\xd6\xd6\xd6\xf2\x45\xa2\x31\xdf\x3d\x87\xfb\x24\ +\x50\x2e\x82\x42\x59\x4d\xaf\xdf\x67\x95\x41\x70\x7c\xa2\x40\x62\ +\xc1\xfe\xbb\x76\xe6\x89\x90\x98\x27\x42\xda\xe4\xf5\x13\xff\x89\ +\x4b\x70\xff\xb8\x66\x34\x78\x78\x78\x08\xf2\x81\xc2\xc6\x7e\x5e\ +\x40\x2b\xc1\xf9\x8e\x90\x98\x27\x42\x62\x9e\x08\x89\x79\x22\x24\ +\xe6\x89\x90\x98\x27\x42\x62\x9e\x08\x89\x79\x22\x24\xe6\x89\x90\ +\x98\x27\x42\xfa\x1f\x1a\xbd\xeb\xe8\xfc\x01\x30\x06\x00\x00\x00\ +\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x01\x9b\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x12\x00\x00\x00\x12\x08\x06\x00\x00\x00\x56\xce\x8e\x57\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ +\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ +\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdd\x06\x13\ +\x0c\x2c\x0e\x78\x0b\x17\x4d\x00\x00\x01\x1b\x49\x44\x41\x54\x38\ +\xcb\xa5\xd4\x3d\x2f\x83\x51\x14\x07\xf0\xdf\xf3\x54\x23\x08\x91\ +\x2e\x12\x1f\x82\xa5\x83\x45\x62\x90\x48\x2c\x06\x31\x8b\xd9\x2a\ +\x21\x16\x89\x6f\x60\x30\x88\xd8\x2d\x12\x9b\xc9\x20\xc1\xc0\x60\ +\xc2\xe0\xa5\x2a\x42\xd2\x81\xc5\xcb\x22\xa9\xe5\x56\x9e\xd4\xd3\ +\x6a\xfb\xfc\x97\x9b\x7b\xce\xb9\xff\x73\xcf\x6b\xa4\x31\x62\x14\ +\xd0\x1b\xee\x1f\x78\x45\x35\xcd\x38\x0a\x67\x55\x36\x44\x51\x13\ +\xe5\x3e\x46\xf1\x15\x1c\x76\xe3\x02\xb3\xad\x30\x0f\xe0\x0c\x77\ +\x98\x4c\xd1\x4f\xe3\x11\x47\x89\x90\xff\xa0\x1f\x37\x58\xfe\xc7\ +\x59\x8c\x35\x5c\xa1\x27\xcd\xe0\x04\x2b\x6d\xe4\x65\x1d\x07\xf5\ +\xc2\x39\x94\x13\xc9\x6f\x05\x79\x54\x30\x55\x13\x14\x70\x8a\x99\ +\x0e\xaa\x35\x8f\xc3\x90\x5b\xc3\x78\xc8\x50\xfa\x32\x86\xe2\x70\ +\x79\xca\x40\x54\xa9\x55\xe0\x13\xb9\x0c\x44\xb9\xd0\xf5\xbf\xdf\ +\xeb\x04\x79\x94\x6a\x3f\x82\x5b\x2c\x74\x40\xb4\x88\xcb\xa4\x60\ +\x02\x6f\x8d\x1a\xac\xc9\x14\xbc\xa3\x58\xaf\xd8\xc5\x46\x1b\x44\ +\xdb\xd8\x4a\x53\x74\xe1\x1c\x9b\x61\x5c\x1a\x61\x10\x3b\x38\x4e\ +\x36\x70\x9c\x30\xf8\xc6\x58\x08\xef\x05\x4b\xe8\xab\x0b\x65\x15\ +\xcf\x61\x23\x8c\xb7\xb2\x7e\x8a\x61\x8d\x94\xc2\x70\x5e\xe3\x1e\ +\x7b\x18\x49\x7b\xf0\x03\x3d\xd6\x32\x0d\x0a\x21\xca\xef\x00\x00\ +\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x0f\xff\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x01\x18\x00\x00\x00\xa0\x08\x06\x00\x00\x00\xd7\x60\x73\x20\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ +\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ +\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x01\x16\ +\x0e\x22\x36\xce\x64\x08\xbf\x00\x00\x00\x45\x69\x54\x58\x74\x43\ +\x6f\x6d\x6d\x65\x6e\x74\x00\x00\x00\x00\x00\x43\x52\x45\x41\x54\ +\x4f\x52\x3a\x20\x67\x64\x2d\x6a\x70\x65\x67\x20\x76\x31\x2e\x30\ +\x20\x28\x75\x73\x69\x6e\x67\x20\x49\x4a\x47\x20\x4a\x50\x45\x47\ +\x20\x76\x36\x32\x29\x2c\x20\x71\x75\x61\x6c\x69\x74\x79\x20\x3d\ +\x20\x39\x30\x0a\x67\x9d\x47\x40\x00\x00\x0f\x2e\x49\x44\x41\x54\ +\x78\xda\xed\x9d\x4d\xab\x5e\x57\x15\xc7\xff\xeb\x31\xb6\x98\xa4\ +\x55\x74\xe0\x20\x05\x27\xca\x0d\x94\x82\xd0\x8e\xfc\x00\xa6\x13\ +\xa1\x24\x90\x0a\xe2\xc0\xcf\xa0\xb4\x10\x70\xa2\x90\x91\x76\xe4\ +\x40\xfc\x06\x2d\xe4\xe2\x30\x26\x13\x47\x8e\x92\x81\x10\x21\xd7\ +\x20\x28\xb4\x03\xb1\x05\x21\xbd\x17\x5a\xa5\xdb\xc1\xf3\x3c\xe6\ +\xb9\x27\xfb\x9c\xb3\xdf\x5f\xd6\xfe\x2f\xb8\xe4\xe6\x9e\xfb\x72\ +\xce\x5e\x7b\xff\xf6\x6f\xed\xf3\x26\xc6\x18\x30\x18\x0c\x46\x8e\ +\xd8\xb0\x09\x18\x0c\x06\x01\xc3\x60\x30\x08\x18\x06\x83\xc1\x20\ +\x60\x18\x0c\x06\x01\xc3\x60\x30\x08\x18\x06\x83\xc1\x20\x60\x18\ +\x0c\x06\x01\xc3\x60\x30\x08\x18\x06\x83\xc1\x20\x60\x18\x0c\x06\ +\x01\xa3\x2c\xee\xdf\x07\xde\x7c\x13\x10\x01\x5e\x7e\x79\xfb\xf9\ +\xfd\xfb\x6c\x17\xc6\x30\x21\xbc\x17\x29\x53\x1c\x1f\x03\x37\x6e\ +\xd8\xb7\xdd\xb9\x03\x5c\xbf\xce\x36\x9a\x76\x46\x91\x64\xbf\x8b\ +\xfd\x9a\x80\xd1\x1d\x6f\xbc\x01\x3c\x7c\x68\xdf\xf6\xfa\xeb\xc0\ +\x83\x07\x84\x49\xe1\x60\x5f\x27\x60\xf4\xc4\x8b\x2f\x02\x9f\x7f\ +\x6e\xdf\xf6\xc2\x0b\xc0\x67\x9f\x11\x28\x19\x40\x50\xea\xef\x30\ +\xdc\x82\x6b\x30\xb9\xe2\xb5\xd7\xc2\xb6\x29\x02\xca\xe1\xc7\xd2\ +\x20\x9f\x7e\xc4\x5a\x8a\xeb\xef\x73\xdd\x47\x06\x01\xd3\x5e\xdc\ +\xba\x15\xb6\x4d\x09\x58\x5c\x01\x50\xaa\x34\xf2\x01\x0e\x83\x80\ +\x69\x3f\xae\x5f\xc7\xf7\x01\xfc\x61\xf7\xdf\xa7\x00\x70\xed\x1a\ +\x70\xef\x9e\xba\x05\xde\x25\x0b\xa8\x01\x94\x58\xe0\xd0\x6a\x12\ +\xf6\x0d\xd6\xa1\x65\xd6\x1d\x34\xb6\xf3\x9a\xa9\xf0\x78\x7a\x3b\ +\xfe\xb9\x63\x27\x60\x9a\xef\xac\x9a\xda\x79\x6e\x20\xf2\x18\xf5\ +\xc1\x25\x16\x32\x17\x88\x02\x06\x07\xdd\xfc\x31\x4d\x8f\x79\xff\ +\x7f\x4e\xcc\x04\x4c\xfb\xf6\x32\x1d\xb0\x0d\x77\xda\xb9\xf5\x15\ +\xed\xb1\x04\x1a\x42\x86\x80\xe9\xcb\x49\x45\x9a\x83\xcc\xa8\x60\ +\x71\x01\x8d\x0a\x9b\xd9\x1f\xc3\xfe\xbf\x48\x7b\x2c\x04\x4c\xc2\ +\x7a\xd5\x18\x3d\x6b\x2f\xa3\xad\x41\xf8\x80\x46\x4d\xd9\x64\x9b\ +\x3c\x20\x49\x21\xc3\xd3\xd4\x89\xe0\xb2\xb6\xad\x77\xb8\xb4\x74\ +\x9a\xb9\x05\xc8\xcc\x9d\xde\x66\x10\x30\xc5\xb5\xda\x16\x6f\x77\ +\x02\x17\x82\xc5\x0f\x34\x84\x0c\x01\x13\x32\xea\x8e\x20\x72\x07\ +\x22\xa7\x06\x72\xc7\x40\x8e\x62\x7f\xe5\x07\x07\x90\x79\xb2\xfb\ +\x5c\xaa\x1e\xa2\xa8\x3e\xb5\x5e\x72\x22\xe9\xe5\x22\xbd\xb7\x9d\ +\x8f\x2f\xa2\x5f\xb1\x13\x39\xc0\x05\x78\x6c\xd9\x72\x55\x60\x4e\ +\x2c\x3f\xb0\x3a\x40\x6d\x17\xe0\xd5\xbc\x28\x8f\x0b\xb9\x63\xb6\ +\xa3\x88\xe0\x26\x80\xf7\x53\xd1\x84\x06\x13\x14\xb7\xdd\xbf\xfe\ +\xdf\xe0\x8e\x75\xf8\xfd\x25\x67\x3f\x96\x44\x85\x4a\xa6\x03\x0b\ +\xde\xfd\x7b\x54\x73\x5f\xf7\xfb\xf5\xc1\x6e\x4a\x94\x0c\x70\xa1\ +\xc1\xb8\x65\xe2\x14\xc0\x45\xcb\x96\x33\x81\xb9\x74\xfe\x4b\x7f\ +\x06\xf0\x5d\x6f\x7b\xf1\xd9\x9e\x1b\x2e\x0c\x3f\x43\x71\x02\x0f\ +\x30\x6b\xc1\x30\x36\x0b\xd6\x63\x5a\x34\x98\xf5\xb8\xeb\xfe\xf5\ +\x1f\x2c\x26\xd1\x05\x1e\xa5\x4c\x86\x70\x59\x6e\x1b\xdb\x47\x6a\ +\x0b\x3e\x06\x1e\x8b\x88\xd1\x0a\x17\x1a\x8c\x5b\x46\x3c\xd6\x60\ +\xc4\x19\x18\x6b\x8f\x0d\xc8\x99\x7c\xc2\x25\xce\x4e\x7c\xda\x4b\ +\x44\x60\x00\xab\x05\x9f\x01\xb8\xf4\xfc\xef\x96\x92\xc7\x99\x3b\ +\xf7\x34\x98\xf5\xde\x74\x72\x75\x3b\xdb\xe0\x6c\xf7\xef\xd5\x2d\ +\x4a\x1e\x1b\xb3\x2f\x5b\x05\x73\xe7\x80\xf6\x09\xf5\x81\x46\x4e\ +\x8b\x21\x5c\xec\xa6\xb2\xb4\xa6\x12\xf3\xc8\x89\xdd\xf7\xdf\x75\ +\x55\x63\x11\x31\xfb\x8f\x56\x4a\x3c\x1a\x4c\xde\x4e\x68\x2c\x8a\ +\x62\xe6\xb4\xc5\x05\x0e\xae\x6d\x9e\xda\x64\x08\x97\xf5\x41\x96\ +\xa5\x4d\x16\x2c\x78\xbf\x06\xb3\x06\x94\x58\xb3\xa9\x75\x86\x8b\ +\x06\x13\x24\x35\xe7\x92\x6d\x7c\x3a\xab\x4f\x52\x53\x9a\xcc\xe8\ +\x70\x71\x35\x95\x5c\x16\xbc\x13\xdf\xf3\x22\x7c\xb0\xc0\x6b\x8c\ +\x91\xfd\x07\x16\xcc\xa6\x27\xb8\xd0\x60\xc2\xec\xe5\xf0\xda\x15\ +\xe3\xd2\x96\xb1\x83\x3b\xd6\x64\x46\x86\x4b\x6b\xf7\x54\xf9\xe6\ +\x62\x0e\x2a\x3e\x46\x53\x33\xff\x04\x8c\x27\x60\x2c\x56\x61\x96\ +\x92\x9e\xea\x29\x69\xa1\x90\x19\x15\x2e\x2d\xdf\xac\x19\x92\x93\ +\x50\xd0\xd4\xbe\xf8\x8f\x25\x92\xa7\xbd\xd8\xbe\x75\xae\x13\x4c\ +\xa1\x10\x73\xdf\x4a\x8a\x72\x69\x64\xb8\xb4\xf6\x4c\xe0\x90\xb2\ +\x7c\x66\x02\x33\xad\xc2\x85\x80\x89\xe8\x18\x93\xe4\x89\x4b\xc2\ +\x6d\x09\xf6\x82\x8c\xe7\xcf\x69\x7f\x26\xb0\xcb\x3a\x4b\xab\x57\ +\x26\x87\x4e\x18\x36\xd0\xd8\xd6\x67\x5a\xb9\x6d\x81\x80\x89\xb3\ +\x17\xab\xaa\xee\x12\x3e\x9b\xd8\x69\xa7\x77\xba\x90\xcb\xf2\xcc\ +\x91\xa5\x9f\x19\xed\xae\xde\xde\xef\xa7\xf2\xcd\x97\xaf\xcd\xd4\ +\x6a\x0b\x02\x26\x81\xd6\xee\xb7\xb9\x9e\x5d\x8a\xb5\x99\x35\xc8\ +\x8c\xb4\xee\xd2\x93\xb5\xa4\xb4\x59\x1f\x9b\xa9\x19\x04\x4c\x80\ +\xbd\xac\x2c\xdc\xca\xf9\xff\x9a\xf8\x4e\x26\xe2\x0d\x99\x51\xe0\ +\xd2\xfb\xf1\xa6\xd8\xdf\x99\x85\x5e\xd3\x42\x9b\x10\x30\x89\x3b\ +\xc1\xae\xd3\x3b\xaf\xc9\xac\x96\x4c\x87\x83\xc8\x52\x6a\x4d\x07\ +\xdb\x28\xeb\x2e\x9a\xee\x02\x4f\xb4\x80\x6f\xbb\x9c\xdc\xd4\x6e\ +\x13\x02\x26\xd2\x5e\x16\x12\xe8\x05\x99\x55\x9b\xa9\x7c\x73\x64\ +\xeb\x70\x61\xd8\xfb\x6d\xed\x72\x89\x80\x49\x6f\x2f\x87\x33\x6a\ +\x3c\x64\x16\xe0\xb2\xb4\x8f\x7a\x5f\x10\xa6\x13\x2e\xb1\x13\xc5\ +\xdc\x59\xcd\xda\x90\x21\x60\xf2\xd8\x8b\xb5\x3e\x76\x5d\x80\x33\ +\x78\xfe\x94\x34\x9f\xf5\x3a\xde\xe2\x75\xcc\xf7\xda\x16\x7f\x09\ +\x18\x45\xf6\x12\x9c\xec\x85\xdf\x33\x32\x64\x46\x80\x4b\x8a\xdb\ +\x40\x0e\x7f\x4f\x0b\x90\x21\x60\x10\x7c\xdd\x8b\x13\x5c\xbc\x20\ +\x63\x59\x73\x71\x81\x8c\xeb\xd7\x08\x17\xfd\xa5\x52\xd4\xe4\x46\ +\xc0\xd4\x9b\x4d\x52\x0c\xd8\xe9\x75\x0b\xe7\x92\xbd\x72\xb6\xc8\ +\xf5\xc2\x3c\x6d\x8b\xbe\x5c\xd0\x8d\x2f\xa3\x6a\x42\x66\xc3\x24\ +\xf9\xdb\x8b\x6b\x69\xe4\x92\x70\x11\x31\x70\x38\x5b\x34\x07\x3d\ +\xdb\xdf\xd7\x02\x99\x51\xe1\xe2\x92\x3f\xdf\x6b\x80\x6a\x41\x86\ +\x06\x13\x60\x2f\xa9\x2f\x8e\x92\xfd\x9a\xae\xdb\xa2\x71\x11\xd5\ +\x26\x5c\xfa\xee\xb3\xad\x40\x66\x68\xc0\xd4\xb0\x97\x55\xc8\x38\ +\x76\x26\xe3\x09\xa3\x9e\x4d\x66\x44\xb8\x2c\xe5\x2e\x06\xbe\xb9\ +\x9f\xf9\x4b\xc0\x44\x76\xe6\xa4\x9d\x7d\xfb\x40\x68\xef\x6b\x65\ +\x7c\x6a\xf2\x1e\x21\x33\xda\x5d\xe0\xa5\xcd\x2e\xe4\xfa\x2c\x02\ +\x26\x93\xbd\xb8\xd4\xc0\x41\x83\xe0\xfc\xcf\x13\x32\x0a\x4c\xab\ +\x74\xa9\x9e\x69\x2c\x10\x30\xb5\x55\x3c\x25\x5c\x60\xb9\x66\xc1\ +\x25\xe1\x4b\x0f\xb3\x5a\x7b\xcf\x74\x2f\x83\x98\xf6\xe2\x3e\x71\ +\xc4\x58\x0c\x0d\xa6\x11\x7b\x49\xd2\xe1\x97\x4f\x47\x47\x99\x8c\ +\xcf\xe2\x74\x8b\x90\x61\x69\x54\x16\xbe\x25\x4a\x25\x1a\x4c\x49\ +\x7b\x71\xbb\x79\x71\x35\xe9\x6b\x0f\xb3\xea\x11\x32\x2c\x8d\xdc\ +\xfb\x65\x2e\xf8\xe6\x80\xcc\x70\x80\x09\x3d\x73\x14\x9d\x54\xc7\ +\x6b\x5d\x52\x99\xcc\x52\xc9\xd4\xba\xc9\xd0\x5e\xca\x81\x37\x77\ +\xa9\x44\x83\xc1\xfa\x4b\xe8\xa3\xed\xc5\x03\x2e\x3e\x90\x89\x79\ +\x98\x55\x4b\x90\x61\x69\x54\x17\xbe\x39\x4b\xa5\xa1\x00\x13\x73\ +\xdd\x4b\x49\xb8\xa4\x4c\x3c\x6f\x98\xa4\xbd\xd4\x8c\xe1\x0d\xc6\ +\xc5\x5e\x82\x67\x8d\x08\xb8\xcc\x41\x26\xf4\x06\x38\x5b\xc9\xd4\ +\x82\xc5\xd0\x5e\xfc\xe0\x92\x2b\x4f\xb9\x2c\x66\x33\x50\xc2\xca\ +\xda\x4b\x02\xb8\xcc\xd4\xc9\x26\xe2\xf7\x3c\x77\x5c\x23\x3e\x11\ +\x8f\xe6\x42\x83\xe9\xdf\x5e\x32\xce\x30\x88\x78\xd6\x6a\x4b\x90\ +\xa1\xbd\x84\x19\x68\x89\x3e\x96\xca\x62\x86\x00\x4c\xcf\xf6\x92\ +\xa3\x03\xd8\x4a\x26\xad\x33\x68\xef\xf6\xd2\x3b\x78\x87\x35\x18\ +\xe3\xbf\xe0\x5a\x15\x2e\x07\x9d\x2f\xd9\x2c\xb3\x74\x4c\x25\x21\ +\x43\x7b\xf1\x83\x4b\xce\xdc\xa4\x3e\x6d\xbd\x19\x20\x69\xce\xf6\ +\x12\x9d\xb8\x8c\x70\x99\xfe\xa5\xde\x21\x43\x4b\x6a\xbf\x3d\x52\ +\x94\x49\x43\x1a\x4c\x16\x7b\x29\x07\x97\xe0\x37\x16\x84\xd4\xf8\ +\x04\x41\x3b\x7d\xb4\x47\xd3\x53\x0d\x98\x62\xf6\x52\x10\x2e\x73\ +\x3a\x9b\x62\xb6\xa9\xa4\xe4\xb4\x97\xc6\xd6\x5d\x52\x96\x49\xc3\ +\x19\x4c\x72\x7b\xa9\x04\x97\x9e\x21\x43\x2b\x6a\x17\x2e\xa9\xcb\ +\xa4\x8d\xe2\xc4\x45\x3d\xad\xae\x35\x73\x71\x7d\x1f\x76\x2a\xc8\ +\xb8\xde\x3e\xc1\xa8\x03\xda\x5e\x72\x31\x94\xc1\xf8\xce\xce\x8b\ +\xb3\x49\x65\x73\xc9\x0d\x99\xb9\xe3\x4f\xd9\xb1\x59\x1e\xb5\xdb\ +\x1e\xa9\xca\x24\x95\x80\x49\x61\x2f\xad\xc2\xc5\xf5\xc9\xf1\xad\ +\x42\x86\x16\x14\x57\x1a\xd5\x80\x50\x4c\x5f\x1a\xc6\x60\x92\xad\ +\x2d\x34\x66\x2e\x1a\x4c\x86\x70\xd1\x6b\x72\x1b\x85\xc9\xcb\x67\ +\x2f\x8d\xc3\x25\x37\x64\x52\xde\x99\xcd\xf2\xa8\x8b\x49\x39\x7a\ +\x16\xd9\x0c\xd2\x50\x43\x77\x8e\x94\x77\xc7\xf2\xf1\x0f\xb4\x97\ +\x61\x01\xe3\x6b\x2f\x5e\x0f\x52\xee\xc4\x5e\x08\x19\xc2\x85\x80\ +\x69\xd4\x5e\x34\xc1\x65\x0e\x32\xb9\xd6\x65\x96\xde\x97\xcd\x18\ +\xf7\x4d\x95\x1b\x45\x09\x8c\xb6\x17\x6d\x70\x99\xab\xa5\x6b\x2d\ +\xfe\x12\x40\x6a\xc6\x17\x0d\x26\x89\xbd\x28\x80\x4b\x6b\x90\xa1\ +\xbd\x8c\xb5\x26\xb8\x51\x92\xc0\xf4\x57\xed\x2a\x82\xcb\x21\x64\ +\x4a\x9e\xc6\x0e\xba\x80\x91\x70\x69\x7a\x62\xa2\xc1\x38\x24\x72\ +\x35\xe9\x0a\xe1\x32\xd7\x69\x72\x42\x66\x74\x9b\x19\xd9\x5c\xd4\ +\x00\x26\xb9\xbd\x28\x87\x0b\x21\xc3\x20\x60\x6a\xd9\xcb\x20\x70\ +\x21\x64\x68\x2f\x04\x4c\x69\x7b\x19\x0c\x2e\x35\x20\x43\xb8\x10\ +\x30\x63\x26\x7e\x50\xb8\xd4\x82\x8c\x66\x9b\x21\x5c\x94\x00\x66\ +\xce\x5e\x78\x8f\x4b\x3f\x90\xd1\x5e\x36\xb1\x2f\xf2\xad\x02\xdd\ +\xd9\x4b\xa9\x27\xca\x97\x2a\x97\x34\x41\xa6\xc4\xb1\xf4\xd6\x5e\ +\x9b\x4e\x13\xe9\x6d\x2f\xd6\xc4\x0c\x5e\x1a\x95\x82\xcc\x1a\x74\ +\x34\x40\x86\xa5\xd1\xc0\x06\xa3\x01\x2e\x46\xd9\xc3\xc4\xe7\xde\ +\x97\x4d\xb8\xe8\x2a\xbf\x36\x1d\x26\x33\x7a\xed\xc5\x9c\xcf\x14\ +\xa7\x99\xc2\x90\x59\x7a\x5b\x66\xef\x36\x43\x73\x19\xcc\x60\x9e\ +\x9b\x5d\x08\x97\x26\xca\x25\x2d\x90\xe1\x45\x84\x8a\x00\x13\x6b\ +\x2f\x5a\xe0\x52\xf8\xb5\xae\xc5\x20\xd3\x5b\xc9\x54\xba\x34\xaa\ +\xd1\x1e\x7c\x6d\x89\x6b\x07\xa6\xb9\x34\x0f\x99\x9e\x6c\x86\x8b\ +\xba\xca\x00\xe3\x6b\x2f\x1a\xe1\x52\xb3\x13\xa7\x80\xcc\xe1\xfe\ +\xcf\xad\xc9\xf4\x58\x32\x99\x86\xde\x2c\x51\x22\xff\x34\x98\xf9\ +\x56\xe2\x94\xd2\x90\xc9\xac\x41\xa6\xd5\x92\x89\xeb\x2e\xca\x00\ +\x93\xc2\x5e\xb4\x75\x89\x5a\x9d\xbc\x24\x64\x5a\xb4\x99\x5a\xa5\ +\x51\xaf\x50\x1b\xe3\x3a\x18\xd6\xc8\x84\x4c\xc7\x70\xa1\xc1\x34\ +\x6e\x2f\xca\x06\xb7\x0a\xc8\x2c\x0d\xd6\x39\xc8\xb4\x54\x32\xd5\ +\xca\x43\xc9\xbf\x9b\x62\xf2\xd0\xf6\xda\x12\xf5\xa5\xd1\xc8\x26\ +\x53\xd3\x66\xb8\xee\xa2\x10\x30\xb1\xd7\xbd\x88\x72\x95\x6d\xa1\ +\xd3\xfb\x42\x66\x2d\x17\x2d\x42\xa6\x76\x69\x54\x3b\xcf\x31\xcf\ +\xe5\xdd\x68\x1c\x6c\x86\xe6\xd2\x85\xc9\xb8\x00\x64\xe9\x7b\x6a\ +\x94\x4c\xa3\xac\xbb\xa4\xba\xde\x69\xd3\xc9\x01\x7a\xd9\x8b\x28\ +\xef\x10\x2e\x03\xb0\xf7\x72\x29\x74\xc0\xa7\x6e\x8f\xa5\x5b\x1a\ +\x8a\x4f\x9c\x1d\xf6\x65\x2d\xaf\x2d\xa1\x46\x74\x04\x19\x97\x81\ +\xe2\x03\xd1\x5c\x90\x61\xbf\x52\xfa\xda\x12\x2f\x7b\x59\xe8\x04\ +\x46\xf7\x5b\x01\x54\x94\x4b\xe2\x98\x3f\x17\xc8\xa4\x2c\x99\x5a\ +\x3b\x25\xdd\xdb\xd9\x23\x1d\x06\x23\xc2\xb5\x96\x06\x67\xda\x94\ +\xe5\x92\x6f\x39\x98\xc3\x66\x6a\xc1\x45\x83\x41\x6d\x1a\x1c\x2c\ +\x6e\xf6\x32\x81\x0b\x2f\x7a\xea\xcb\x64\x42\x5f\xeb\x5b\x02\x32\ +\xbd\xaf\x7b\xb4\x52\x1e\xf5\x6b\x30\x96\x0e\x33\xe2\x55\x96\x2d\ +\x2e\xf6\x86\x98\x8c\x2f\x34\x5c\xbf\x3f\xa4\x64\x6a\xf1\x7e\xa7\ +\x5e\xcb\xa3\xe6\x00\xe3\x64\x2f\xbb\x86\xa7\xbd\xf4\x69\x32\x22\ +\xf2\xf3\x83\xcd\x5f\xce\x0d\xd4\x39\x9b\x11\xb1\x7d\xf0\x56\x00\ +\xf5\x25\x52\xea\x99\x9d\x16\xd3\x9c\x6a\xff\x42\x44\x7e\xba\xfb\ +\xfa\x7f\xf6\x7d\x30\x04\x18\xe1\x90\xf9\x5a\xd3\x7d\xa8\x05\x7b\ +\x49\x51\x1e\x35\x05\x98\x18\x7b\xe1\xe9\xc4\xee\xea\xf9\x5f\x1d\ +\xe4\xfb\x7b\xf0\xbc\x2e\x32\x14\x32\xcf\x7e\xee\x97\xb6\x1e\x48\ +\x73\xc9\x10\x17\xba\xd9\x53\x96\x46\x8b\x83\xe7\x99\xfa\x4b\xf3\ +\xa7\xb0\xf7\x70\x11\x11\x63\x8c\x11\x11\xb9\x08\xe0\xcc\x67\xdf\ +\x43\x8f\x79\xfb\x73\xff\x98\x85\x0b\xf0\xf7\xe1\xd7\x5e\x52\xd9\ +\x4b\x33\x06\xb3\x6a\x2f\x0b\xb3\x14\xeb\xe6\xf5\x36\x69\xd9\x66\ +\x76\xb9\xff\x31\x80\xaf\x97\x30\x99\x6d\x7c\x6b\x06\x2e\x66\xb2\ +\x8d\x79\x53\x53\x22\xad\x99\xcb\x50\xf6\x32\x5d\x7d\x0c\x58\x67\ +\xe8\xac\x64\xfa\x2d\x80\x9f\x00\xf8\x8a\xef\x20\xeb\x61\x0d\x2a\ +\xd4\x4a\x7b\xb7\x17\x00\x90\xda\x1d\x73\xd1\x5e\x26\x2f\x47\xb3\ +\xe9\xa3\xba\x6b\x16\xe6\x06\x89\xeb\x1d\xe4\x9d\xb5\xc7\x24\xff\ +\x37\x01\xfc\x11\xc0\xbf\x7c\xf7\xdd\xe7\xb8\x9f\x7d\xeb\xd4\x5e\ +\xbc\x9a\x5a\x5d\x69\x94\x03\x30\x9b\x2e\x06\x9a\x03\x5c\x9a\x32\ +\x0e\x2e\x3a\x87\x9a\xcc\x07\x00\x7e\x06\xe0\x62\xcc\x8c\xef\xd6\ +\x2f\xec\x70\x61\x2e\x14\x95\x48\xb3\xf6\x32\x99\xb9\x9b\x9e\x95\ +\xe7\x3a\x73\xbd\x67\xe6\x76\x57\x32\x4c\x3a\xf6\x3b\x00\x7e\x24\ +\x22\x5f\xca\x98\xb4\x59\xb8\x8c\x6a\x2f\x39\xe0\xd2\xb6\xc1\xac\ +\x64\x5b\xe3\xe2\x6e\x0e\x1e\x74\x0a\x99\xdf\x01\x78\x47\x44\xbe\ +\x91\x1b\xae\xc6\x9c\xff\xa8\x09\x97\x8a\x93\x7b\xb6\xd8\xd4\x1b\ +\x4c\x2b\xf6\xa2\x75\x8d\x65\x05\x2e\x62\xd1\x75\xf1\x54\xf8\x5e\ +\x5f\xc5\x3a\x81\xcc\x6d\x00\x3f\x4c\x6d\x70\x2d\xf5\xa7\x56\x26\ +\xc9\x5c\xf6\xd2\xae\xc1\xac\x2e\xd2\xe9\x3e\x35\x2d\x30\xe7\x3e\ +\x62\x4b\xa5\x5e\xd7\x01\x5e\x05\x7e\xf3\x4f\x91\x7f\x43\xe4\x0b\ +\x88\x3c\x81\xc8\xab\x31\x90\x69\x79\xb2\xd2\x56\x1a\x55\x05\x8c\ +\xd3\xda\x0b\x78\x57\x6b\xca\x4e\xdb\xd3\x29\x5c\x63\x8c\x1c\x01\ +\x78\x04\xe0\x9b\xc0\x57\x77\x7d\xe4\xdb\x00\x1e\x41\xe4\xc8\xe7\ +\xd8\x6f\xee\xf4\xf0\x89\xc8\xf6\xf3\x46\xfa\x92\x96\x9b\x19\xd7\ +\xe2\x42\x83\xbd\xab\xaf\x99\x7a\x7a\x3a\x3d\xf0\x38\x4a\x75\xea\ +\x5e\x40\xfd\x18\x38\x06\x70\xdd\xb2\xe9\x36\x80\x1b\x4e\xa9\x39\ +\xf8\xfc\x3b\x00\xde\x6f\x28\x0f\x2d\xac\xbb\xe4\xb6\x17\xa0\xc2\ +\x75\x30\x8b\xf6\x32\x33\xe3\xfa\xbc\xc7\xb8\xf7\x35\x98\x44\xdc\ +\xed\xbf\xa4\x14\x39\x85\xfd\x74\xf5\x19\x8c\xb9\x14\xd5\xa8\x8d\ +\xdc\xc8\x58\x32\x17\x35\xe0\x52\xad\x44\x5a\x4b\x3a\x4b\xa3\xbc\ +\xf5\x7d\x27\xe5\xd2\x5d\xdb\x17\x8f\x03\xae\x91\x69\xc9\x20\x47\ +\x82\x4b\x71\x83\x71\x7d\x5a\xdd\x68\xf6\x92\xdb\x60\xba\x6c\xbf\ +\xed\x5a\xcb\xe3\xe9\x97\xaf\x02\x38\x71\x1d\x24\x0d\x19\xcc\x88\ +\x70\x69\xcb\x60\x06\xb7\x97\xa5\x43\x4d\xd5\x0c\x5d\x99\x8c\x31\ +\x27\x3b\x9e\x1c\x03\x38\x03\x70\xbc\x87\x8b\x6d\xe0\x38\x37\x1c\ +\xe1\x52\xf6\xd8\x2b\x1d\xac\xb7\xbd\xb0\x74\xea\xbf\xc3\x6b\x19\ +\x34\x84\x4b\xa7\x06\xe3\x0a\x17\xc6\x40\x26\xf3\xfc\xbe\xcb\xd2\ +\x40\x22\x5c\xda\x82\xf0\xa6\xc2\x01\x5b\xed\x85\x76\x42\xc8\x68\ +\x82\x0c\xe1\xd2\xa0\xc1\x68\x54\xfa\xdd\x01\x34\x79\xb7\xb5\x36\ +\xc8\xb4\x02\x9a\x1a\xfd\xd5\x76\xfc\x2d\x94\x8f\x9b\x12\x07\x4e\ +\x7b\xe9\xcb\x64\x3a\xbd\x77\xa9\xba\xcd\xd8\xda\xae\x14\x5c\x5c\ +\xda\xa6\x46\x5c\xc8\x7c\xe4\xd9\x07\x04\x23\x4d\x9b\x4e\x07\x46\ +\x2f\x57\xfd\x4e\x9f\xf3\x7b\xf8\x79\xd1\xeb\x3d\x2c\x7d\x3d\x77\ +\xfb\xb5\x0c\x96\x22\x06\x23\xe7\xaf\xd6\x0e\xb2\x17\x2e\xee\x96\ +\x83\x8c\xa6\x92\xa9\xa4\xcd\xd8\xac\x85\x70\xc9\x6d\x30\x9e\x6f\ +\xd1\xa3\x99\xb4\x69\x33\xfb\xcf\x69\x33\xb4\x96\xa0\xfd\xcd\xd2\ +\x20\xdb\xf7\x46\x27\xb7\x17\x42\xa8\xec\x7a\x42\xcf\x25\xea\x9c\ +\xbd\xa4\x18\x8c\x35\xda\x26\xe7\xf1\xf4\x55\x22\x6d\x2f\xf1\xbe\ +\x73\x0a\xe0\x0e\x80\x23\xda\x8b\x9a\x92\xa9\xa7\xb2\xc9\x18\x23\ +\x73\x65\x53\x4c\xe9\x34\x67\x2d\xb9\xfa\xf0\xdc\xfe\xce\x1d\x9f\ +\x6e\x83\xd9\x3e\x10\xe8\x91\x65\xcb\x55\x01\x4e\x7c\xcf\x1c\x11\ +\x42\x6d\x1b\x4d\xcf\x57\x00\xfb\x5a\x40\xc9\xe3\x8f\xdd\x57\x9d\ +\x06\xb3\x4d\xc0\xef\x67\xb6\xde\xf6\x05\x07\x17\x77\xdb\x33\x1a\ +\x5b\x8e\x7a\x37\x9a\x43\x4b\xb0\x0d\xec\xb9\x63\x4c\x0d\x97\xa5\ +\x7d\xe8\xc9\x58\x72\x1b\xcc\x17\xb0\xbf\x9d\xef\xdc\x33\x3c\x42\ +\x00\x43\x83\x69\xdb\x66\x7a\xcc\x91\x43\xa9\x24\x39\x8f\x71\xed\ +\xef\xf7\x08\x94\x7c\x06\xb3\x8d\xbf\xcd\x7c\xfd\x2e\xe1\xa2\xcb\ +\x66\x96\x8c\xa6\x37\xab\x59\x18\xc8\x66\xfa\x11\xb2\x7e\x73\x68\ +\x27\xae\xa6\xa2\x01\x2e\x85\xd7\x60\xcc\xc9\xe1\x84\x60\x06\x7b\ +\x25\xc9\x88\x46\xd3\x4b\xee\x2c\xfb\x5f\x74\xa7\xb5\xc0\x24\xbb\ +\xc1\x08\xcc\x5f\x30\x79\x86\xc7\xee\xff\x27\x06\x82\xbf\x02\xff\ +\x7f\xf0\x32\xe1\xa2\xcf\x68\xd6\xac\xa6\x15\xb3\x59\xdb\xa7\x43\ +\x8b\x48\x69\x14\xb9\x7e\xef\x30\x06\x63\x7d\xf6\x35\xfc\x9e\x2a\ +\x46\xc0\xe8\xb7\x9a\xd2\xf9\x6d\x69\x5f\x46\x8b\x0b\x2d\xef\x1c\ +\x13\xde\xb7\xd5\xb8\x0c\xf2\xd4\x67\x68\x7c\x2c\x89\xfd\x6b\x30\ +\xc0\xf0\xd4\x34\x81\x93\xb3\x1f\x10\x28\x34\x18\xc6\xa0\xc0\x49\ +\x0d\x16\xc2\xa4\x11\x69\xc8\xbd\x06\x03\x58\xd6\x61\x78\xdf\x11\ +\x83\x41\x83\xf1\x9f\x35\xec\x90\x11\x98\xf9\x87\xb9\x7f\xf2\x09\ +\xf0\xde\x7b\x30\x00\x9e\x5e\xbe\x8c\x3f\x1d\x1d\xe1\xd7\x84\x0b\ +\x83\x41\x83\x89\x8e\x8f\x3e\x02\x5e\x79\xc5\xbe\xed\xc3\x0f\x81\ +\x2b\x57\x98\x21\x06\xa3\xe3\xa8\xfb\x4c\xde\x77\xdf\x0d\xdb\xc6\ +\x60\x30\x68\x30\xab\x71\xf9\x32\x70\x7a\x6a\xdf\x76\xe9\x12\xf0\ +\xe9\xa7\xcc\x10\x83\x41\x83\x09\x8c\xb7\xde\x0a\xdb\xc6\x60\x30\ +\x68\x30\xab\xc1\x35\x18\x06\x83\x06\x93\x2d\xae\x5c\xc1\xc3\x7b\ +\xf7\xf0\xe0\xda\x35\x00\xc0\xd3\x97\x5e\x02\x6e\xdd\x02\x3e\xfe\ +\x98\x70\x61\x30\x68\x30\x0c\x06\x83\xd1\xaa\xc1\x30\x18\x0c\x02\ +\x86\xc1\x60\x30\x08\x18\x06\x83\x41\xc0\x30\x18\x0c\x02\x86\xc1\ +\x60\x30\x08\x18\x06\x83\xd1\x66\xfc\x0f\x7a\x81\xf3\xb2\x41\xec\ +\xd4\x42\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x04\xd8\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x01\x18\x00\x00\x00\xa0\x08\x06\x00\x00\x00\xd7\x60\x73\x20\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ +\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ +\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x01\x1b\ +\x0d\x1e\x28\xb7\xb4\x2a\xa7\x00\x00\x00\x45\x69\x54\x58\x74\x43\ +\x6f\x6d\x6d\x65\x6e\x74\x00\x00\x00\x00\x00\x43\x52\x45\x41\x54\ +\x4f\x52\x3a\x20\x67\x64\x2d\x6a\x70\x65\x67\x20\x76\x31\x2e\x30\ +\x20\x28\x75\x73\x69\x6e\x67\x20\x49\x4a\x47\x20\x4a\x50\x45\x47\ +\x20\x76\x36\x32\x29\x2c\x20\x71\x75\x61\x6c\x69\x74\x79\x20\x3d\ +\x20\x39\x30\x0a\x67\x9d\x47\x40\x00\x00\x04\x07\x49\x44\x41\x54\ +\x78\xda\xed\xdc\x4f\x6e\x13\x31\x14\x07\xe0\x37\xfd\xb3\x85\x25\ +\x12\xe7\xe0\x9e\xb9\x01\x27\xea\x39\x2a\xb1\x0c\x7b\x30\x1b\x2a\ +\x55\x55\x9a\xcc\x24\x63\xc7\x7e\xfe\x3e\x29\x42\x45\x11\xc9\x38\ +\xcf\x3f\xfb\x79\x1a\x22\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\xf6\xb1\x18\x02\xe8\x4a\xc9\x34\x4f\x9f\x7c\x9e\xd0\x4d\x90\xa4\ +\x23\x60\x40\x90\x68\x91\x60\xb2\x30\x49\x31\x37\x05\x0c\x08\x12\ +\x01\x03\x82\x44\xc0\x80\x30\x31\xaf\x0c\x04\x82\xc4\xfc\x11\x30\ +\x20\x48\x04\x0c\xa4\x08\x12\x73\xc2\x60\x22\x4c\xd4\xbe\x80\x01\ +\x41\x22\x60\x0c\x01\x82\x04\x01\x83\x50\x51\xb7\xc3\xf1\x5d\x24\ +\x2c\x80\x08\x18\x04\x09\x00\x58\x39\x68\xa6\xa8\xb7\x79\x3d\x18\ +\x02\x1a\x85\x0b\x02\x06\x84\x0b\x5a\x24\xc6\x0b\x17\xb5\x66\x07\ +\x03\x16\x32\x04\x0c\xc2\x05\x00\xab\x0b\xdc\xc6\xad\x68\xb4\x48\ +\x54\x0f\x17\x10\x30\x08\x17\xb4\x48\x8c\x17\x2e\xea\x09\x3b\x18\ +\x2c\x56\x08\x18\x84\x0b\x00\x58\x79\xb8\x8d\x5b\xd1\x68\x91\xa8\ +\x1e\x2e\x20\x60\x10\x2e\x68\x91\x18\x2f\x5c\xd4\x0c\x76\x30\x58\ +\x90\x10\x30\x08\x17\x26\x2f\x1a\x77\x0f\x80\x26\x01\x23\x64\x72\ +\xb3\x98\xa0\x45\xa2\x7a\xb8\x40\xb3\x80\x59\x14\xa2\x70\x81\x96\ +\x3b\x18\x05\x99\x3b\x5c\xb4\x47\x34\x0d\x18\x05\x37\x0f\x9f\x35\ +\x77\xd9\xc1\x68\x95\x84\x0b\x54\x2d\x28\xdb\x69\xa0\xea\x8a\x25\ +\x64\xc6\xe6\x56\x34\x5d\xb6\x48\x8a\x32\x57\xb8\x40\x97\x01\x73\ +\x2a\x64\x14\xae\x70\x81\xdd\x02\x46\x01\x8f\x1f\x2e\x76\xa2\x74\ +\x1d\x30\x0a\x74\x5c\x3e\x3b\x86\xd8\xc1\x68\x95\x84\x0b\x54\x2d\ +\x36\xdb\x6f\xa0\xea\x6a\xe6\x1b\xd7\x7d\x71\x2b\x9a\x14\x2d\x92\ +\x56\xa9\xef\x70\x81\x14\x01\xa3\xd0\x85\x0b\x54\x6b\x91\xce\x15\ +\xb8\x2d\xfa\xfd\xc2\xc5\xd8\x93\x6a\x07\xa3\xa0\xf3\x2e\x1e\xd0\ +\x45\x8b\xe4\x3c\x46\xb8\x40\xd5\x42\xb4\x5d\x07\xaa\x4e\x7a\xb7\ +\xae\xeb\x72\x2b\x9a\x29\x5b\x24\xad\x52\xdb\x70\x81\x29\x03\xc6\ +\xc4\x30\x86\x08\x98\x61\x5b\x30\xe1\x62\x8c\xb1\x83\xd1\x2a\x09\ +\x70\x04\x8c\x6d\xbe\x70\x81\x81\x8b\xd4\xf6\x1e\x04\x4c\xd3\x90\ +\x11\x30\xeb\xc7\xcb\x58\xa1\x45\xda\x18\x68\x5a\x25\x6d\x24\x02\ +\xc6\x44\x32\x26\x30\x46\xc0\xd8\xea\x6f\x0f\x17\x63\x86\x80\xd1\ +\x2a\x09\x64\xe8\xad\x45\x12\x32\xc2\x05\x05\xdc\x24\x54\x4c\x28\ +\x10\x30\x42\xa6\xd2\xf5\x0b\x58\xb4\x48\x5a\x25\xd7\x0a\xa3\x04\ +\xcc\x32\xe9\xc4\x13\x2e\x08\x98\x89\x5a\xb5\x7b\x87\x8b\xf6\x08\ +\x01\xd3\x30\x64\x66\x5a\xe1\x85\x0b\x02\x46\x1b\x21\x5c\x60\xf4\ +\xe2\x76\xeb\x1a\x04\x8c\x90\xb9\xf2\x7a\x04\x26\x5a\x24\xad\x92\ +\xf7\x0e\x59\x03\x26\xcb\x2a\x2f\x5c\xd0\x22\x0d\x34\x41\x47\x0a\ +\x1e\xb7\xa2\xb1\x83\xb1\x23\x10\xe6\x30\x6b\xc0\x64\xf9\x2d\x5f\ +\xe1\x82\x16\x49\xab\x04\xcc\xba\xb2\xf6\x1e\x32\x6e\x45\xc3\x60\ +\x2d\xd2\x96\xc0\xf1\x5e\x40\xc0\xe4\xd9\x6d\x95\x88\xe7\x43\x44\ +\x39\x46\xc4\x21\x22\x9e\xd5\x13\x0c\xaf\xf4\xf2\x38\x44\x94\xf2\ +\xee\x71\xe8\xe8\xbd\x79\x4c\xfd\xb0\x2b\xc8\xd0\x8e\x1c\x23\xe2\ +\xcb\xbb\x9f\x7f\x47\xc4\x57\x0b\x00\xe6\xf5\xd0\x67\x30\xdd\x0c\ +\xde\xcf\x0b\x3f\x03\xdc\x74\x06\x53\x22\x0e\x25\xe2\xf8\xff\x4f\ +\xc7\x30\x00\x30\x55\xbb\x31\xf6\x46\xc6\x78\xa2\x0e\x4f\x79\xf0\ +\xb9\x00\x02\x06\x00\xa0\xbb\x5e\x4d\xef\x0b\xf9\xea\xf0\xc9\xe7\ +\xc2\x0d\x85\x7c\xae\xa0\xcb\x0e\x8b\xda\xa5\x2f\xb8\xfa\x96\x7d\ +\xe7\x04\x0c\xd7\x84\xcb\xf2\xe1\xef\xca\x27\x93\x7b\xd9\xf1\x75\ +\x4e\x3d\x67\xed\xfb\x80\xe9\xbc\x26\xdf\xd5\x94\x9d\xff\xbd\x38\ +\x11\x26\x7b\xbe\x66\x2f\x5e\x4c\x0d\x4e\x15\xfb\xda\xe2\x7e\x4d\ +\x16\x2e\x7b\x07\xcc\x9a\xb1\xcc\x1c\x30\x6f\x21\xf3\x52\xb9\x0e\ +\x9b\xb0\x9d\xdc\x77\x72\x5d\xda\xd2\xff\x8d\x88\x5f\x83\x5c\xd7\ +\xb7\x88\x78\xbc\xa1\x9d\xb9\xf6\x7c\x64\xcd\xd9\xcd\xa5\xd7\xfc\ +\xec\xb5\xfe\x0c\x34\xfe\xef\x7d\xdf\xb1\x0e\x9b\x72\x06\xd3\x3e\ +\xcc\xdf\x56\xa6\x1f\x89\xc2\x75\xb9\x70\xcd\x25\xd6\x9d\xab\x9c\ +\x0b\x94\x8f\xe7\x2b\xd7\x4c\xa2\xc7\x41\xc6\x35\x53\x8d\xa0\xd7\ +\xde\xb5\x35\xba\xf5\xf9\x65\x45\x5b\x74\xae\x45\x2a\xea\x82\x6c\ +\x2d\x52\x86\xc2\xbe\xe6\x9a\x6b\x04\xd2\x9a\xf0\xc8\x7e\x06\x93\ +\xa6\x0e\x7d\x55\x80\xbd\xdb\x22\x10\x30\x54\x0d\x97\xb2\xe1\xef\ +\xb6\x1c\x06\x2f\x67\x9e\xe3\x3f\x5e\xef\x90\x55\x88\x3d\x5b\x9c\ +\xe5\xc2\xf3\xb6\xdc\xf5\xd9\xfa\x5b\xbc\xea\x59\xc0\xa4\x9f\x74\ +\xc6\x13\x75\xa8\x45\x02\x04\x0c\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x50\xc1\x3f\x25\x05\xa1\xfc\x8e\x95\x2f\x33\x00\x00\x00\x00\x49\ +\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x06\xaa\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x01\x18\x00\x00\x00\xa0\x08\x06\x00\x00\x00\xd7\x60\x73\x20\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ +\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ +\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x01\x18\ +\x09\x39\x05\x3d\x12\xc3\x85\x00\x00\x00\x45\x69\x54\x58\x74\x43\ +\x6f\x6d\x6d\x65\x6e\x74\x00\x00\x00\x00\x00\x43\x52\x45\x41\x54\ +\x4f\x52\x3a\x20\x67\x64\x2d\x6a\x70\x65\x67\x20\x76\x31\x2e\x30\ +\x20\x28\x75\x73\x69\x6e\x67\x20\x49\x4a\x47\x20\x4a\x50\x45\x47\ +\x20\x76\x36\x32\x29\x2c\x20\x71\x75\x61\x6c\x69\x74\x79\x20\x3d\ +\x20\x39\x30\x0a\x67\x9d\x47\x40\x00\x00\x05\xd9\x49\x44\x41\x54\ +\x78\xda\xed\xdd\x5d\x6f\x1b\x45\x14\x06\xe0\x77\x9b\xa4\x69\x4b\ +\x49\x80\xf2\x51\x21\x10\x42\xe2\xff\xa6\x42\xe2\xb6\x3f\x8c\x4a\ +\x5c\x56\x88\x1b\x08\x2a\x52\x2b\xb5\x2c\x17\xde\x12\xd7\x75\x1b\ +\x3b\xd9\x5d\xef\xcc\x3c\x8f\x64\xd1\x58\x42\xb2\xcf\xce\xbc\x73\ +\x66\xd6\x4e\x12\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa0\ +\x22\x9d\x12\xb0\x8b\xde\xc0\x41\xc0\x30\x47\xb8\x18\x3c\xec\xea\ +\x8e\x12\x70\x9d\x67\xdb\xc3\xa5\xcf\xd5\x03\x04\x0c\xb3\x37\x39\ +\xd8\x22\xc1\xfe\xe9\xd1\x7d\xf0\x69\xd0\xc1\x70\xfb\x55\x68\xf3\ +\xe9\xa3\xe1\x01\x30\x6a\x83\x73\x94\xe4\x5e\x9c\xc9\x00\x13\x38\ +\xce\xbb\x87\xbe\x42\x06\x18\xbd\x93\x59\x7f\xdc\x55\x12\x9c\xc1\ +\x30\x96\xf5\x33\x99\xb3\xb5\xae\x06\x83\x02\x46\xf3\xb6\x73\x79\ +\x65\x9c\xcd\xd6\x39\x2e\xb6\xce\x2e\x3c\x53\x78\x9d\xf7\xef\x28\ +\x19\x6b\xd3\x86\xcb\x22\x6b\x6c\x8b\xc4\x14\x8e\xd7\xfe\xfd\x32\ +\xc9\x79\x92\xef\x95\x65\xd2\x70\x81\x26\x27\xc1\xc3\x24\x8f\xe3\ +\xee\xd2\xd8\x75\x75\xd7\x0e\x92\xfc\x60\x32\x4c\x1e\x30\x60\x42\ +\xac\x3d\x4e\x95\xa4\x8d\x70\x71\x06\xc3\x1c\xde\x1e\x3e\xbe\x1e\ +\xb6\x4c\xa7\xc3\xe4\x38\x53\x9a\xbd\xc3\x05\xf8\x80\x2f\x92\x7c\ +\xb3\xb1\x02\x7f\xa6\x2c\x37\xea\x5c\x84\x0d\xec\x30\x51\xfe\x8c\ +\x4f\xfd\x0a\x17\x98\x60\xc2\xfc\x33\x84\xcb\x03\x93\x46\xb8\xc0\ +\xd8\x13\xe7\x74\xd8\x22\x99\x3c\x95\x86\x8b\x4f\x57\xb2\x84\x49\ +\x64\x5c\xee\x56\x9b\xe2\xea\xe2\x2e\x12\x4b\x5b\xe4\xce\xb2\x3a\ +\x0c\x16\x2e\x42\x17\x46\x9d\x50\x0f\x73\x75\x1e\xd3\x37\x5e\x8b\ +\x2a\xce\x5d\xa4\xe2\x72\x3d\x48\xf2\x79\x56\xe7\x14\xbf\x35\xf2\ +\x9e\xef\xe6\xdd\x6f\x61\xb7\x38\x46\x6d\x19\x99\x7d\x15\x6b\x79\ +\xf5\x3e\x6d\xf8\xbd\x17\x7f\xed\x9d\xc1\xb0\xd4\xae\xfa\xdf\xac\ +\xbe\x85\xfd\xb0\x91\x90\xd5\xb9\xc0\x8c\x7e\x4c\xf2\x55\x43\x9d\ +\x5c\x95\x9f\x75\x91\x90\xcb\x75\x9e\xd5\xef\x52\x49\xde\x3f\x97\ +\x68\x69\xcb\xd0\xc2\x98\x75\xc7\x88\x83\xad\x68\xbf\xab\xc3\x3b\ +\xbf\x48\xfc\xeb\xca\x3b\x17\x1f\x36\x64\x16\xcf\x87\xc1\xf6\x4a\ +\x29\xd2\x27\x39\x49\xf2\x49\x65\x93\x50\xb8\xc0\x42\xdc\xaf\x6c\ +\x32\x0a\x17\x58\xf8\xa4\xfc\x49\xb8\x2c\x9b\xdb\xd4\x65\x0c\x42\ +\x56\xba\x8d\x8e\xe6\x45\x81\xf5\x71\x3b\x1a\x01\xb3\x70\xdf\x25\ +\x79\x54\x4a\x8d\x36\x5e\x64\x53\xdb\x22\xc9\x49\xc9\x01\xbc\xf8\ +\xf1\xbc\xf1\x57\xd1\x9a\xbb\x1d\x6d\x8b\x44\x2d\x8b\xe3\xbd\x61\ +\xdb\xb4\xd4\x17\xab\x13\xc5\x16\xa9\xd0\x3a\x7d\x9b\xe4\xd3\xac\ +\x6e\xe9\xf7\x4b\x7b\x71\x5b\xb6\x45\xbd\x8b\xca\xa1\xfd\x25\x60\ +\x76\x76\x96\xe5\x9e\x6d\x6c\x0d\x17\x17\x15\xca\xed\xf8\xfa\xac\ +\xbe\x6a\xb1\xd8\x70\x69\x25\x60\x9c\xc1\x50\x8b\xf5\x33\x99\xc7\ +\x0b\xd8\x2e\xf5\x5b\x9e\xe8\xb6\xbd\x58\x38\x84\xe7\xb6\x48\x37\ +\x72\x9a\xe4\x78\x01\xdb\x25\x9f\xd2\x15\xa4\x8b\x6f\xf9\x5d\xa7\ +\x91\xba\x87\x99\x6b\xe8\xdb\xd1\xb6\x48\x45\x84\x7f\x27\x5c\x46\ +\x59\x38\x4f\xb2\xfa\x26\xf6\x1c\x9d\x84\x8e\x53\xb2\xd2\x50\x27\ +\x73\x67\x18\xe7\x6f\x66\x18\xf7\xbe\x06\xa0\x83\x29\x6a\x72\x38\ +\x83\xb9\xfd\x02\xfa\x60\x23\x5c\xa6\xea\x32\x84\x8b\x02\x14\xe5\ +\x65\xae\x7e\x17\xcc\xb9\x72\x8c\x1a\x00\x5f\x26\x39\x4a\xf2\x87\ +\x70\x01\xc6\x0c\x99\xe3\x8c\xff\xb7\x97\xdc\x31\x02\x92\x5c\x1d\ +\xf6\x8e\x15\x08\xc2\xc5\x16\xa9\xf8\x96\xde\x75\x9a\xae\xb6\x19\ +\xba\x9a\x37\xb6\x46\xe3\x73\xc8\x4b\xeb\x0b\xeb\xd1\x30\x0f\xf6\ +\xed\x3e\x84\x8b\x0e\x06\x3e\xea\x64\x08\x97\x97\x7b\xce\x09\xe1\ +\x22\x60\x60\x92\x4e\x44\xb8\xd8\x22\x55\x33\xf0\x1d\x1c\xce\xbf\ +\xc8\xde\xc9\xea\x2e\x93\x70\x41\xc0\x30\x6a\xbd\x1f\x65\x75\x26\ +\xf3\xa1\xba\xbb\x63\x04\xdc\xd8\xf1\x47\x42\x44\xb8\xd8\x22\xc1\ +\xad\xbc\xde\xf2\xdc\x7d\x81\x82\x2d\x12\xa3\xd6\xfe\x24\xe9\x5f\ +\x24\xbf\x5c\x26\xfd\xc5\xf0\xb3\xeb\x81\x80\x61\x14\xaf\x92\x9f\ +\xfb\xd5\xaf\xb8\xec\xfb\x21\x64\x5c\x0b\x60\x14\x97\x6b\xe1\xd2\ +\x27\xfd\xa5\x70\xb9\x11\x67\x30\xb0\xa5\x7b\x7c\xba\xf1\xc4\x59\ +\xf2\x44\x59\xb0\x45\x62\x94\xda\x9f\x0c\xdb\xa2\xa1\x93\xb9\xe8\ +\x57\x9f\xfa\x65\x4f\x3e\x24\x34\x71\x42\xdc\xa2\xe0\xbe\xec\xb8\ +\x8c\xcb\xa6\xf6\x02\xa6\x8c\x70\x51\xf4\xda\xd7\x04\x14\xef\xc0\ +\xe1\xa2\xf0\xc2\xa5\x25\x0e\x79\x77\x18\x75\x7d\x92\x5f\x33\xfb\ +\x61\x88\x33\x98\x85\x85\xcb\x01\xc7\x82\x0e\xa6\xa5\x4e\xa4\xbb\ +\xe1\xff\xb7\x67\xe1\x9d\xc1\x2c\x2c\x5c\x4c\xa0\xfd\x1d\x2b\x81\ +\xf0\x47\xdd\x6d\x91\x60\xfa\xee\x45\xb8\x08\x98\xf2\x97\xc0\x6e\ +\xf7\xc1\xef\x0c\x66\xfe\x1d\x30\x02\x66\xfe\xa0\x78\xb6\xe7\x12\ +\xd7\xe9\xbf\x4b\x0b\x97\x6e\xaa\xb1\x60\x0e\x81\x70\x41\xc0\x80\ +\x70\x11\x30\x8c\x3d\x19\x5c\x27\xe1\x52\x24\x67\x30\x58\x5c\x51\ +\x64\x18\xb1\x7b\x31\xee\x75\x30\x30\xd9\xd6\x08\x1d\x8c\x89\xe1\ +\x3a\x4d\x12\x2e\x6a\xa9\x83\x01\xe1\xa2\x83\x01\xe1\x82\x80\x41\ +\xb8\x70\x08\xbe\x4d\x5d\xc6\x44\x31\x41\x2c\xa2\x45\x72\x06\x43\ +\xcd\xdd\x8b\x70\x91\xee\x60\x6b\x24\x60\x40\xb8\x20\x60\xaa\x9c\ +\x34\xae\x93\x70\x29\x92\x33\x18\x84\x0b\x3a\x18\xd8\x21\x60\x8c\ +\x67\x01\x03\xc2\xa5\x15\x3e\x07\x53\xc6\x04\x32\x79\xae\xdf\x1a\ +\xb1\x40\xce\x60\xa8\x21\x5c\x04\xb0\x2d\x12\x08\x17\x01\x03\xc2\ +\x05\x01\xd3\xd4\x84\xea\xd4\xc2\xd8\x2d\x91\x33\x18\x2c\x8c\xb8\ +\x50\xe8\x5e\x8c\x59\x1d\x0c\x4c\xbd\x35\x42\x07\xc3\xc8\x93\xab\ +\x6b\xfc\xfd\x1b\xaf\x3a\x18\x10\x2e\xb8\x68\x08\x97\xaa\x0a\xb4\ +\xe4\xe2\xb8\x70\x08\x97\xc2\x0b\xb4\xe4\x22\xf9\x2e\x52\x19\x63\ +\xa9\xd5\x49\x26\x5c\x0a\xe7\x0c\x06\x9d\x3f\x3a\x18\xab\x77\x93\ +\x9d\x3f\x06\x31\x8c\x1e\x2e\xc6\x65\x25\xad\x9e\x0b\x59\xc6\x38\ +\xea\x1a\x99\x2b\xc6\x64\x65\x9c\xc1\x20\x5c\xd0\xc1\xa0\xd3\x47\ +\xc0\x80\x70\xe1\x7f\xee\x22\x95\x31\x09\xbb\x4a\xdf\x17\x95\x73\ +\x06\xc3\xa1\xc3\x45\xf7\x62\x8b\x04\xc2\x05\x01\x83\x70\x41\xc0\ +\xb0\xe7\xa4\xec\x2a\x79\x1f\xc6\x5d\x63\x9c\xc1\x60\x51\xc3\xc5\ +\xa6\x8a\xee\xa5\x2b\xf1\x45\x9b\x28\x3a\x98\xda\x66\xe5\x49\x9f\ +\x5c\xf4\xc9\xe5\xf0\xdf\x93\x4a\xb6\x46\xc5\xbe\x68\xf7\xd6\xad\ +\x94\xd5\x3c\x2e\x92\xbe\x5f\x7b\x5c\xd4\xf1\xbe\x8a\x1f\x48\xb4\ +\xbb\x45\xaa\xea\xfa\x5f\x26\x39\x5b\xfb\xf9\xef\x24\xe7\xc6\xd9\ +\xc1\x07\x92\x6d\x92\x2d\x52\x15\x9e\x5e\xf3\xb3\x70\x01\x5a\x3f\ +\x83\xa9\x6e\xaf\x8d\xd5\x05\x26\xd9\x26\x99\x28\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\xc0\xe8\x7c\x40\x91\x9d\xf8\x3b\x23\x08\x18\ +\x66\x09\x17\x83\x87\x5d\xf9\x36\x35\xd7\x7a\xb6\xe3\x73\x20\x60\ +\x00\x5b\x24\x96\xbf\x45\x32\x98\xd0\xc1\x60\x15\xc2\xd8\xa1\xfc\ +\xae\xc6\x60\x42\x07\x03\x08\x18\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\xa8\xda\x7f\xcd\xc0\x5b\x85\xe5\x59\x9f\x86\x00\x00\x00\ +\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x05\xec\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x01\x18\x00\x00\x00\xa0\x08\x06\x00\x00\x00\xd7\x60\x73\x20\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ +\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ +\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x01\x16\ +\x0f\x17\x2c\x90\xf5\x59\x44\x00\x00\x00\x45\x69\x54\x58\x74\x43\ +\x6f\x6d\x6d\x65\x6e\x74\x00\x00\x00\x00\x00\x43\x52\x45\x41\x54\ +\x4f\x52\x3a\x20\x67\x64\x2d\x6a\x70\x65\x67\x20\x76\x31\x2e\x30\ +\x20\x28\x75\x73\x69\x6e\x67\x20\x49\x4a\x47\x20\x4a\x50\x45\x47\ +\x20\x76\x36\x32\x29\x2c\x20\x71\x75\x61\x6c\x69\x74\x79\x20\x3d\ +\x20\x39\x30\x0a\x67\x9d\x47\x40\x00\x00\x05\x1b\x49\x44\x41\x54\ +\x78\xda\xed\xdd\xcb\x6d\x1d\x47\x10\x05\xd0\xe9\x81\x72\x61\x8c\ +\x8c\x41\xb1\x70\xaf\x28\x14\x0a\xb5\xe2\xba\xb5\xe1\x46\x80\x1e\ +\x7f\x8f\x3d\x5d\x9f\x73\x00\xc1\x80\x05\x58\x9c\xaa\xee\x3b\x55\ +\x8f\xb6\x79\x1c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc0\xf5\ +\x86\x12\xc0\xb7\x99\xee\xa2\x80\x81\x08\x81\xd2\xe2\x6e\x0a\x18\ +\xf8\xfe\x50\x19\x1b\xff\x79\x02\x06\x8a\x85\xca\x48\xfa\x67\x09\ +\x18\x08\x1a\x2c\xa3\xc1\x9f\x2f\x60\x0a\xed\xe2\x7a\x11\xbf\x9f\ +\xc3\xd7\xe4\x50\x47\x0f\x12\x3d\xca\xd7\xe3\xe1\xeb\x73\x78\x23\ +\x04\xca\x08\xfa\x67\x50\xec\xe2\x66\xf9\x7a\x1d\xd6\xaf\x5d\xf8\ +\xd1\xe8\xeb\x10\x2e\xbe\x6e\x01\xb3\xf8\x32\x0f\x5f\x63\x9b\xde\ +\x0f\xcf\x20\x60\xba\x5f\x58\x61\x23\x5c\x52\x3c\xcb\x70\xa8\xca\ +\xbd\xbd\x04\xcd\xd7\xea\x37\x0a\x9e\xed\xed\xcf\x34\x1a\x1e\xa4\ +\xca\x35\x98\xfa\x2c\x30\x23\xf5\x7e\x34\x2d\x7a\xf5\x67\x9f\x8d\ +\x7b\x4d\xa0\x90\x19\xcd\x0a\x6d\x6a\x13\x34\x42\x46\xc0\x58\x15\ +\xd4\x82\x0a\x21\x33\x9a\x5c\x28\x97\x49\x5d\xf4\x7d\x43\xcf\x87\ +\x0b\x24\x68\xd4\xa9\x55\xcf\x05\x8c\x4b\xa3\x5e\xd4\x08\x19\xdf\ +\xfb\x77\xe8\xd4\x4e\xaf\x97\x39\x15\xad\xb5\xf1\x81\xc9\x86\x9a\ +\xbd\x36\xc1\x18\xf1\xd5\x93\xdc\xab\xd2\xe9\x32\x70\xa3\x7e\xa6\ +\x19\xda\x4d\x30\x56\x22\x35\x26\xd1\x14\x73\x3a\xf8\xbc\x53\x57\ +\x93\x0c\xe5\x03\x46\xb8\x08\x19\x12\x6e\x2e\x19\x02\x46\xb8\x08\ +\x19\xae\xbb\x5f\x6d\x57\x24\xe1\xa2\xde\x58\x91\x96\xa5\xab\xc3\ +\xbe\x3f\x64\x4c\x31\x5e\x1e\x65\x02\xc6\x61\xd6\x17\x92\xf7\xf4\ +\x4c\xf2\xc0\xa6\x97\x58\x6f\x3b\x21\x43\xda\x80\x11\x2e\x42\x86\ +\x22\x6b\xd2\xd9\xbd\x00\xe8\x07\x7d\x02\xc6\x87\xba\xb9\x42\xc6\ +\x14\x53\xc7\x92\x5e\x9e\xd5\x1f\x10\x7d\xc3\x04\x63\x14\xb7\x2a\ +\x55\x4e\xe0\x87\x79\x1c\x4f\xf3\x38\x5e\x5e\xff\xfa\xa0\x97\xd7\ +\xbf\x05\xa7\xb7\xa1\xbe\x15\x0d\x97\xf9\x9f\x5f\x0f\x01\xfb\xe8\ +\xa0\xa2\x6f\xc9\x0a\xf4\x74\x23\x60\x9e\x04\x8c\x43\x8a\xfe\xdd\ +\x5b\x9c\x97\x1b\x01\xf3\xd2\x21\x60\xce\x00\x0f\x46\xad\xb0\xe1\ +\x5f\xbf\x3e\xf9\xf7\xf1\xf6\x43\x1f\x3f\x5c\x18\x9f\xc1\x38\x94\ +\xe8\xe7\xf2\x90\x89\xfa\x5d\xa4\xa5\x01\x33\x36\x3f\x54\x84\xaf\ +\x03\xfd\xd4\xbb\x45\x7d\x3b\x1d\x46\x16\xbc\xac\x4c\x31\x6c\x0d\ +\x18\x40\xc0\x5c\xfa\xd6\xa3\xd6\x14\x03\x5b\x02\xc6\xf8\xdc\x6b\ +\xaf\xa7\x71\x9f\xac\x48\x40\xc9\x80\x31\x4e\x5b\x93\x28\xde\xb7\ +\xab\x03\xc6\xd8\x6c\xfc\xc6\x04\x03\x78\x01\xe4\x0d\x18\x63\xb4\ +\x71\x1b\x13\x8c\x71\x19\x7d\xf7\x42\xb0\x22\x01\x01\x83\x7f\x47\ +\xc0\x18\x9f\xbd\x15\xb1\x22\x01\x5e\x04\x39\x02\xc6\x1e\x6e\x1c\ +\xa7\x61\x3f\x4c\x30\x40\x99\x80\xb1\x97\x1b\xbf\x89\x33\xbd\xb4\ +\xff\xd1\xb1\x80\x09\x06\x30\xbd\x08\x18\x10\x2e\xc5\x26\x18\xdf\ +\x41\xc0\x39\x88\xe5\xb2\xcf\xc6\x4c\x30\x6b\x6f\xd4\xe3\x3c\x8e\ +\xe7\xd7\x1f\x53\xf1\x3c\x8f\xe3\x51\x55\xe8\xb0\x1a\x5d\xfd\x70\ +\xb3\x61\x47\x7f\xdf\xf8\x79\x38\xbf\x9b\x1e\x70\x13\xcc\xde\xda\ +\x97\xed\x41\xbb\xc3\xf5\x3a\xb9\xcc\x37\x7e\x3d\x36\x3d\xe4\x08\ +\x17\x87\xeb\x1b\x1e\xf8\xf9\x9d\x80\x79\x16\x30\x74\x08\x97\x71\ +\xd1\x83\x96\xdc\xfd\xde\x78\xe0\xf9\x81\xc2\x8f\x66\x87\xbd\xd5\ +\x19\x08\x52\xef\x63\x77\xdd\x7d\xc8\xbb\xc6\x9f\x3b\x7f\x1f\xd2\ +\x87\x8b\x80\x59\xe7\xe7\x9d\xbf\x0f\xe9\xc3\xc5\xfe\xbd\xf6\xa1\ +\x7d\x17\xa9\xf9\x19\xd8\x54\xe3\x8e\x9f\x79\xf6\x3c\x5c\xfe\x3d\ +\x18\x67\xa0\x7b\xb8\xf8\x90\x97\xc3\x19\x28\xb3\x12\x85\xab\xb1\ +\xcf\x60\x40\xb8\x2c\xf3\x43\x9f\x40\xb0\x08\x18\x10\x2c\x47\xa6\ +\x70\x11\x30\x60\x6a\x59\xca\x0f\x5e\x43\xdf\x73\x19\x47\xa2\x0f\ +\xcb\x7d\xc8\x0b\x39\x42\x25\x65\x60\x9f\x17\x17\x07\x97\x84\x46\ +\x4c\x30\x60\x8a\x11\x30\x80\x09\x06\x30\xc5\x6c\x0b\x18\xdf\x51\ +\xe8\x45\xbf\x4d\x30\x00\xb9\x03\xc6\x77\x10\x8c\xf6\x34\x5c\x93\ +\x4c\x30\x40\xa9\x80\xb1\x97\xf7\xa0\xcf\xa6\x18\x13\x0c\x50\x23\ +\x60\xec\xe1\xde\xb6\x08\x18\xe3\x33\xfa\x6b\x4d\xb2\x22\x01\x26\ +\x18\xe3\xb2\xb7\x2c\x5d\xa7\x98\x9d\x13\x8c\x31\xda\x7a\x84\x09\ +\x06\x30\xc5\xe4\x09\x18\x63\xb3\xf5\x08\x13\x8c\x71\x1a\xfd\xc4\ +\x8a\x04\xa6\xc5\xa0\x21\x7f\x2a\x08\x0b\xa6\x17\xeb\x11\x26\x18\ +\x30\xc5\xd4\x0d\x18\x53\x8c\xe9\x05\x13\xcc\x96\x43\x4a\xce\x70\ +\x81\x50\x01\xe3\x6d\x57\x77\x4c\xc7\x56\x10\x62\x82\xb1\x2a\x59\ +\x8d\x30\xc1\x00\xa6\x98\x9c\x01\x63\x8a\x31\xbd\x60\x82\xd9\x72\ +\x78\xd1\x1f\x92\xbe\xb0\xcf\xa0\x45\x21\xe7\x61\x86\xd0\x13\x8c\ +\x55\xc9\x6a\x84\x80\x31\x8a\x0b\x17\xac\x49\x39\x03\x66\x38\xd4\ +\x29\xc2\xc5\xf4\x42\xea\xfd\xd9\x81\xd6\x0b\x92\xaf\xb4\x91\x57\ +\x24\x87\x58\x5f\x70\x58\x6a\x27\x30\xea\xaf\x87\x35\x27\x98\x8f\ +\x8c\xea\xa8\x37\x81\x65\x08\x18\x1f\xfa\xc6\x08\x17\xd3\x0b\xa5\ +\xf7\x69\x07\x5e\xad\xb7\x8d\x6d\xa3\xd6\x63\x5d\xf6\x38\x99\x56\ +\x24\x93\x8c\x70\xd9\xb6\x13\x3a\x6c\xf5\x27\x98\xe2\x2f\x18\x75\ +\x8d\x1c\x30\xa6\x98\xfa\x13\xcc\x5b\x85\xf1\x82\x11\x2e\x04\x94\ +\xf5\xff\x07\x33\xac\x4c\x4b\x57\x22\xe1\x42\xeb\x80\xb9\xf5\x96\ +\x15\x32\xf7\x87\x0b\x75\x5d\xfe\xdf\x27\x8d\xa2\x17\xc5\x65\x51\ +\x2f\xc5\x79\xff\xf1\x96\x3f\xd6\x70\x2e\xdc\x1f\x75\x6a\x7d\x06\ +\x96\xf6\x7e\xb8\x40\x82\x05\x01\x23\x60\xee\xbf\x4c\x5d\x2f\x94\ +\x5a\xb0\x2d\x64\x46\xc3\xcb\x35\x9a\x1e\x22\xc1\x82\x80\x71\xd1\ +\x3c\x2f\x02\xc6\xaa\xe0\x19\x11\x32\x02\xe6\x13\x97\x30\x6b\x3d\ +\xa6\xfe\x22\x60\x72\x5d\xcc\xe8\xb5\x99\xfa\x4a\x86\x90\x71\x10\ +\xf3\x5c\x58\xa1\x82\x80\x69\x10\x36\x57\xd5\x6d\xea\x1f\x02\x46\ +\xd8\xdc\x5b\xcf\xa9\x67\x54\x0d\x19\x87\x75\x6d\x18\xe8\x11\x02\ +\x86\x10\xc1\xa3\x17\x94\x0b\x99\x1f\xea\x2a\xa8\xc1\xc5\x00\xd2\ +\x4d\x31\xa7\x9a\x02\x00\x57\x4f\x31\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x40\x4a\x7f\x01\xe5\x23\xdb\x3f\x56\x5c\x46\x67\x00\ +\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x00\xb0\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x12\x00\x00\x00\x12\x08\x06\x00\x00\x00\x56\xce\x8e\x57\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ +\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ +\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdd\x06\x13\ +\x0b\x29\x19\x83\xe0\x70\x4a\x00\x00\x00\x30\x49\x44\x41\x54\x38\ +\xcb\x63\x60\xa0\x12\x60\x84\xd2\xff\x29\x35\x87\x89\x5a\x2e\x62\ +\xc1\xe1\x42\x62\x01\xdc\x27\x54\x73\xd1\xa8\x41\xa3\x06\x0d\x4f\ +\x83\x58\x70\xe5\x9d\x01\x73\x11\xd5\x00\x00\x03\xf8\x03\x23\xd9\ +\x76\xec\x65\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x1a\x29\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x01\x18\x00\x00\x00\xa0\x08\x06\x00\x00\x00\xd7\x60\x73\x20\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ +\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ +\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x01\x16\ +\x0f\x28\x0b\x6d\x21\xc6\x13\x00\x00\x00\x45\x69\x54\x58\x74\x43\ +\x6f\x6d\x6d\x65\x6e\x74\x00\x00\x00\x00\x00\x43\x52\x45\x41\x54\ +\x4f\x52\x3a\x20\x67\x64\x2d\x6a\x70\x65\x67\x20\x76\x31\x2e\x30\ +\x20\x28\x75\x73\x69\x6e\x67\x20\x49\x4a\x47\x20\x4a\x50\x45\x47\ +\x20\x76\x36\x32\x29\x2c\x20\x71\x75\x61\x6c\x69\x74\x79\x20\x3d\ +\x20\x39\x30\x0a\x67\x9d\x47\x40\x00\x00\x19\x58\x49\x44\x41\x54\ +\x78\xda\xed\x5d\xbd\x8b\x65\xc9\x75\xff\x55\xb3\x48\x78\x66\x25\ +\x0c\x0e\x1c\xcc\x82\xc2\xd7\xc6\x12\x36\x48\x7f\x83\x77\x14\xd8\ +\x48\x3d\xa0\x0d\x94\x3a\xb3\x73\xad\x26\x70\x62\x68\x3b\xb1\x40\ +\xb0\xfa\x1f\x2c\x41\x77\xaa\xf1\x6e\xee\x68\x17\x04\x32\x78\xda\ +\x4a\x24\xd8\x0d\x0c\x5a\x25\xab\x1e\x23\x25\xe5\xe0\xdd\xd7\x7d\ +\xdf\x7d\xf5\x71\x4e\xd5\x39\x55\x75\xef\x3b\x07\x1e\xd3\xf3\x3e\ +\xee\xbd\xf5\xf5\xab\xdf\x39\x75\x3e\x9c\xf7\x1e\x26\x26\x26\x26\ +\x1a\x72\x61\x5d\x60\x62\x62\x62\x00\x63\x62\x62\x62\x00\x63\x62\ +\x62\x62\x62\x00\x63\x62\x62\x62\x00\x63\x62\x62\x62\x00\x63\x62\ +\x62\x62\x62\x00\x63\x62\x62\x62\x00\x63\x62\x62\x62\x00\x63\x62\ +\x62\x62\x62\x00\x63\x62\x62\x62\x00\xb3\x31\xf9\xe8\x23\xe0\xf9\ +\x73\xc0\x39\xe0\xab\x5f\xdd\xff\xfd\xd1\x47\xd6\x2f\x26\x67\x23\ +\xce\x62\x91\x94\xe4\xf6\x16\x78\xf1\x22\xfc\xd9\xcd\x0d\x70\x75\ +\x75\x9e\x13\xce\x39\xc4\xe6\x9c\x73\x4e\xec\x3e\x36\xaf\x0d\x60\ +\xb6\x2d\xdf\xfa\x16\xf0\xc9\x27\xe1\xcf\xbe\xf9\x4d\xe0\xe3\x8f\ +\xcf\x12\x5c\x7a\x8a\xcd\x75\x03\x98\xed\xc8\x97\xbf\x0c\xfc\xf1\ +\x8f\xe1\xcf\xbe\xf4\x25\xe0\x0f\x7f\x30\x70\x51\x00\x82\x56\xf7\ +\x31\xa1\x89\xd9\x60\xb4\xe4\x1b\xdf\x28\xfb\x6c\x43\x80\x32\x7f\ +\xa5\x16\xf9\xf2\x55\xcb\x52\xa8\xd7\xa3\x3e\xa3\x89\x01\xcc\x78\ +\xf2\xf2\x65\xd9\x67\x1b\x01\x96\x1c\x00\xb4\x54\x9b\xb8\x80\x63\ +\x62\x00\x33\xbe\x5c\x5d\xe1\x6f\x00\xfc\xc7\xf4\xdf\x2f\x00\xe0\ +\xdd\x77\x81\x0f\x3f\xdc\x9c\x81\x37\xc5\x02\x62\x8b\xbb\x35\xc8\ +\x70\x00\xc7\x58\x8d\xe0\xdc\x30\x3d\xb4\x8d\xdd\x61\x8b\xfd\x9c\ +\x63\x2a\x25\xd7\xe8\xd9\x4f\x12\xed\x59\xf7\x78\xc6\xda\x6e\x00\ +\x33\xfc\xe2\xdb\x52\x3f\xc7\x16\x62\x69\x1b\x47\xec\x2b\xe9\x36\ +\xae\x15\x5c\x6a\x41\xc6\x54\x24\x13\xb6\x2a\x14\x53\x39\x6a\x54\ +\x16\x2a\x93\x68\xad\x46\x51\xfb\xc0\xc4\x18\xcc\x78\xec\x65\x39\ +\x51\x07\x1e\x8b\x18\xb0\x9c\x8b\x5a\xd9\xaa\xfd\x5b\x63\x30\x6c\ +\x80\x49\x79\x62\x9a\x9c\x4c\x46\x1f\xed\x63\x0d\x85\x77\x23\x0b\ +\x6b\x64\xdb\xd5\xe6\x80\x66\xd1\x1e\x07\xdf\x0f\x60\xb6\x6e\xb4\ +\xac\x45\x7b\xef\x4f\xc0\xe5\xd0\x57\x6e\x6d\x00\xd3\xdb\x06\xb1\ +\x36\x90\x59\xe5\x9a\x88\xb4\x23\x04\x32\xcd\x6d\x30\xe7\xaa\x87\ +\xa6\x9a\x3d\x5f\x13\x5b\x52\xf1\x52\x36\x09\x4d\x1b\xc8\xa8\x73\ +\x2d\x65\x9f\xa9\xe8\xf4\x1d\x9c\xbb\x81\x73\xf7\xd3\xbf\xbb\x4d\ +\xac\x97\xd2\xe3\xc4\x73\x65\x32\xf9\x39\xe4\x7c\xa4\x9f\x8e\x7e\ +\xf9\x9e\x73\xf8\xe9\x80\x0c\x66\xb4\x13\x9d\x35\xb0\xe6\xe9\x19\ +\x8b\x1f\x6e\x07\xe0\x75\xf8\xa3\x4b\x78\x7f\x77\x16\x0c\xa6\x45\ +\x04\xec\xd0\xa8\x32\xbd\x3c\xf6\x2f\x02\xb8\x24\x3b\xe6\x67\x00\ +\xde\x9b\xfe\xfe\xd5\xf4\xb7\xeb\xbc\x48\x46\x3c\x2e\xee\xc2\x64\ +\x08\x6c\xc2\x39\xe7\x0f\xaf\x5a\xc6\x7a\x5d\xf4\x51\xbd\xbc\x47\ +\x1e\x83\x06\x0c\x26\xb4\x9b\x9c\x85\x4d\x26\x31\xa9\x8f\x91\xfe\ +\x14\x5c\xa6\x3e\x3a\xb1\xc5\x84\xfa\xad\x67\x5f\xae\xc1\x70\xd9\ +\xac\x7f\xf6\x60\x12\x22\x14\x97\x2e\x4a\x34\x16\xd3\x82\xd1\x8f\ +\xce\x39\x7f\x0f\xe0\x49\xe0\xb3\x37\x00\x9e\x22\x62\xc3\x13\xea\ +\xd3\xef\x01\xc7\x4c\x5a\xb8\x6f\x59\x36\x98\xe5\x6e\x32\xb2\x9e\ +\xdc\x5a\xd3\x0c\x81\xcb\x1a\x6c\x0e\x21\xd6\x32\xe2\x66\xd1\xb0\ +\x7f\x82\xac\xe1\x36\x02\x2e\xde\x7b\xb7\x78\x85\xfb\x77\xc6\x82\ +\x1f\x5e\xd3\xef\x9f\x00\xb7\xa1\x6b\xbf\x0a\xb3\x25\xd1\x71\xff\ +\xd9\x34\x61\x9d\x92\x7a\x5e\x74\x4c\x3d\xd2\xee\xdb\x9f\xc1\xcc\ +\x07\xfd\x17\x0e\xf8\xeb\xd0\x82\x08\xaa\x4e\x39\x43\xa1\xb6\x0b\ +\xc1\x1a\xbd\x8d\xd5\xe7\x9a\x73\x41\x42\x71\x60\x13\x14\xf5\xf7\ +\x04\x84\x72\xfa\x47\x84\x35\x5d\x02\xb8\x8b\x03\xae\x93\xe8\x43\ +\xed\xb1\x67\x9f\x22\x85\x76\x92\xf3\x65\x32\x73\xe0\xf0\x0e\xf8\ +\x5b\x72\x3f\xc4\x06\xb4\xb4\x2f\x0f\xdf\xa5\xfe\x66\xad\xa1\x0c\ +\xda\x73\xed\x36\xac\xad\x60\x62\x19\x0e\x82\xa6\xb2\xdb\x43\x1b\ +\xf6\x86\xdc\xcb\xe9\xad\x37\xd3\xbf\x97\xaf\x67\xcc\x28\x66\x03\ +\x1a\x5d\x1d\x2e\xf2\xe4\x8d\xed\x22\x9b\x64\x32\x51\x4b\x3b\x16\ +\xe0\x12\xde\xd8\x96\x76\x18\x8a\x0a\xc5\xed\x47\xee\xa4\xd9\x42\ +\x9c\x94\x74\x1b\x0e\x63\x94\x3a\xd1\x71\x13\xa1\xe0\xb2\xc4\xd8\ +\xb7\x17\xac\x88\x6c\xb3\xa9\x61\x34\xad\xc7\xbe\xc8\x0f\x86\x02\ +\x2a\x9b\x61\x32\xde\x9f\x6c\x2d\xee\xc4\x89\x2e\xbe\xb1\x4d\x6a\ +\x8b\x3b\xee\x3e\x2f\xb6\x4b\xa7\xfa\x3f\xf4\xdb\xad\x04\x61\x4a\ +\xc4\x2f\xcd\xd2\x32\x3c\x5c\xec\x40\x25\x96\x6c\x02\xde\xdf\x95\ +\xd8\xa7\x52\xdf\x7f\x95\x7e\x26\xa4\x6c\x3e\x54\xe0\xa1\xf4\x91\ +\xe6\x5a\x7d\x4b\x63\xe0\xe7\x74\x7d\xed\x4c\x66\xda\x81\x76\xd8\ +\x1b\xff\x9e\x03\x78\xb2\x7b\xd4\x8d\x5d\x6a\xc1\x72\xd5\x16\x6e\ +\x3f\x86\xc0\x65\x79\xba\x37\xff\xed\xd6\x22\xbc\x53\x6d\x25\x2e\ +\x32\x2f\x69\xdb\xe0\xca\x15\x10\x1d\x9b\x1c\x93\x9d\x9d\x48\xfa\ +\x25\xc8\x44\x00\x28\xab\x5a\xfb\x11\x8c\xbc\xd4\x0e\xd8\x8c\xba\ +\x94\x3e\xb6\xbc\x63\xee\xac\xbe\x86\xd2\x72\xd4\xd1\x52\x1b\xd0\ +\x9a\x37\x02\x66\x7f\xb4\x05\x97\xd0\x78\x14\xf8\x96\x45\xda\x96\ +\x6c\x0b\x65\x83\x1b\xc6\xc8\x5b\xab\x46\xad\x70\xd2\xc6\x9c\x9d\ +\xae\x29\x83\x24\x91\x63\xb6\xa4\x1f\x53\x74\x7e\x8b\x3e\x4b\xb1\ +\x7e\x8a\xa8\x1b\x9e\xa2\x76\x48\xab\xda\xf0\xfe\x41\x99\x76\x48\ +\x7b\xc7\x73\xd2\x45\x84\x9e\x9f\x63\x00\x1e\xce\xc8\x5b\xba\x73\ +\xac\x6d\x62\x4f\xea\x51\xd4\x0f\xca\x2d\x6c\x74\xdc\x9d\x93\x33\ +\xa9\x4b\x43\x35\xb6\x9c\xf8\xaa\xd0\x9e\xe0\x7b\xa9\x44\x35\x63\ +\x42\x1d\xff\x00\xb0\xb8\x9e\x2c\xf6\xa2\x65\xe7\xad\x89\xc9\x1c\ +\x9e\x35\x76\x6c\x89\x80\x8d\x6e\xd9\xbe\x25\xb8\xd6\x18\x26\xa5\ +\xd8\xd1\xd6\x13\x26\x65\x58\x5b\x77\x70\x29\x1d\x37\x6a\x80\xe5\ +\xbc\x4d\x3b\x00\x37\x7b\xee\x74\xef\x81\x9b\xc9\x96\xd8\x74\xa3\ +\x11\x49\x38\x45\x41\xd7\xb5\x31\x99\x43\x00\x1b\xe5\xd8\x32\xd6\ +\xee\x84\xfd\xc4\x97\x8c\x01\x77\xe7\x4b\x85\x76\x6c\xdd\x0e\xb3\ +\x6c\xe7\x72\x67\xef\x05\x2e\x52\xec\x3e\xe7\x9a\x70\xe9\x9c\xef\ +\x12\x40\xd9\xca\x06\x93\x59\x60\xab\x99\xa8\xb1\x63\xcb\x25\xb8\ +\xa4\x8e\x88\x29\x40\xc0\x39\x8e\xa6\xf4\x23\xc5\x37\x66\x4b\x4c\ +\x26\xd3\x17\x43\x81\x4b\x68\xbc\x97\x35\x9a\x42\x2f\x8e\x3a\xfe\ +\x3a\x12\x7e\x00\xe5\x00\x4a\x15\x06\x93\x1b\xe0\xb5\x31\x99\x40\ +\xf8\xbd\xa3\x9e\x92\x11\x17\x76\x34\xea\x9a\x6a\xc7\xa2\x9e\xe0\ +\x71\xae\xb9\x15\x60\x49\xb0\xc5\x21\x6c\x2e\xd2\xaa\x56\xb0\x0f\ +\x12\xb6\x43\x78\xff\x74\xd5\x0c\x26\x54\x5c\x6b\x2d\x61\x05\x21\ +\x70\x29\x05\xd3\x84\xed\xc4\x1d\xcf\x05\xa2\x1d\x87\x00\xda\x4c\ +\x9b\xc4\xaa\x99\x0c\x17\x5c\xd0\x30\x23\x06\xa7\xaa\x65\xe8\x3d\ +\x81\x42\x71\x21\x3f\xbe\x94\x4d\x71\x6c\x06\x43\xd9\x69\xa9\x3b\ +\x70\xe7\x49\x9b\x75\xeb\xe7\x1a\xb2\x73\xbe\x0b\xa1\x34\x0e\x81\ +\x1d\x29\xe8\x37\x51\x9b\x42\x63\xad\x4c\x26\xc7\xd2\x52\xe0\xa2\ +\xd5\xce\x1a\xdf\x23\xae\x4a\x9d\xdd\xe4\x23\x51\x0f\x87\x00\xca\ +\x56\x4c\x4e\x94\xc1\xe4\x02\x21\x63\xef\x8f\xb2\x83\xa6\x54\x17\ +\x8e\xfa\x41\xdc\x69\xdd\xf2\xbe\x51\xb6\x11\x01\x17\x89\x7e\xa4\ +\x84\x17\xac\x0d\x5c\x42\x7d\xad\x39\xdf\x32\xae\xfd\xe2\x29\x30\ +\x48\xec\xe6\xd1\x7c\xf8\x60\x3b\x4c\x45\x67\xaf\x82\xc1\x94\xd8\ +\x06\x46\xd9\x41\xa9\x29\x15\xa8\xec\x85\x68\x2b\xf1\x11\xc0\xa9\ +\x06\xb1\xda\x53\x89\xc1\xed\x63\xc5\xf6\x25\x85\x20\x49\x16\x53\ +\x91\x66\x93\x54\xb0\x9c\xcd\xc7\x22\x3f\xac\x61\x6c\x30\xa9\x9d\ +\x22\x34\xd8\x23\x30\x19\x0a\x73\x29\x9d\x9c\x99\xd3\x25\x97\xa0\ +\xf4\x4d\x99\xc5\x1a\x4e\x98\xb8\xe0\xa2\xd9\xce\xd8\xc9\x4e\x8b\ +\x43\x93\x92\x67\x8f\x9c\x42\xfa\xd5\x01\x4c\xc9\x44\xee\x09\x32\ +\xdc\x13\x9d\x92\xdd\x87\x33\xa9\x39\xbb\xaf\xc6\xd8\x8c\x6a\xfc\ +\xad\x05\x77\x49\x90\xd1\xca\xa9\xc2\x61\x2d\x25\x86\xfd\x40\x64\ +\xff\xfa\x54\x24\x0e\xdd\xeb\x5d\x6c\x3c\xc7\x5c\x4a\x8e\xa6\xb9\ +\x79\x58\x28\xaa\x19\x85\x09\x4a\xf7\xe1\x48\xc6\x5f\x2a\xb8\xd4\ +\xaa\x18\x25\xc6\xd5\xd6\xd5\x2d\x25\x18\x5a\x2c\x57\xf4\x26\x18\ +\x0c\x75\x70\xb4\x77\xcf\x1a\x70\x91\x64\x08\xa1\x23\xeb\xd8\xd1\ +\x25\x65\x07\x93\x62\x20\xa3\x18\x7f\xb5\x6d\x43\xd4\xfe\x1a\x21\ +\x31\x7a\xad\x11\xbf\x07\x33\x55\xad\x4d\x5d\xba\xb3\x37\xd8\x21\ +\xb2\x36\x17\xaa\x71\xb0\x74\xa7\x2f\x4d\xe3\xd0\xda\xa1\xb1\xa7\ +\xf1\x97\x73\xef\xda\xf6\xb6\x34\x0c\xd7\x82\x49\x28\xec\xa3\x90\ +\x81\xa9\xb3\x98\xae\x0c\x26\xb6\x43\x6b\xa2\x2d\x85\x1a\xb6\x98\ +\x60\x8b\x6b\x90\x8c\xbd\x9c\x7b\x6b\x30\x99\x96\xbb\x5f\xeb\x45\ +\x9d\x4a\xf7\xd0\x93\xb5\x94\xda\x95\x7a\x99\x1d\x9a\x02\x0c\x77\ +\x92\xa7\x40\x46\x62\x62\x2f\xc1\xa5\xf6\x9a\xbd\xec\x14\xd4\xd3\ +\x0a\x49\x90\xe9\x69\xfc\xe5\x06\x75\x6a\xa9\x85\xbd\x4a\xe7\xa6\ +\x58\x33\xf7\xb9\x4a\x37\xb6\x55\x33\x98\x16\x76\x19\xaa\x51\x8b\ +\x93\x2f\x57\x98\x02\xbb\x29\xbc\x1e\x6f\x9c\xf3\x25\xf5\x89\x09\ +\xae\xf3\x4d\x37\x8c\xb5\x01\x77\x2d\x43\x90\x6c\x3f\x45\x25\x2f\ +\x7d\xae\xa6\x6d\x69\xad\x4f\xd6\xd4\xed\x29\xed\x20\x8e\x5b\xbe\ +\xb6\xed\xe5\xe4\xb7\xfb\x0b\x24\x53\x73\xc6\xc2\xeb\x39\xfd\x23\ +\xb9\x68\x35\x01\xa0\xc6\xc9\x50\x3b\xec\xa5\x87\x6a\x58\x6b\x77\ +\x21\xde\xc7\x6b\xd9\x61\x86\x63\x30\x54\x95\x49\x9a\xb9\x70\x06\ +\x4b\x1c\x5c\xf6\x42\x4e\xcd\x19\x7a\x06\x0e\xbd\x97\x66\x32\xda\ +\xd1\xc2\x25\x0b\x52\x72\x91\xb7\x50\x09\x4b\xc6\x4c\x4a\x1d\xd4\ +\x54\x93\x5c\xb3\xcc\x56\x82\x56\x7e\x86\x6e\x4e\x72\xc5\xef\xc6\ +\x5e\xe6\xbf\x8d\x54\x14\x44\x24\xbc\x9e\x9a\x2e\x42\x33\x19\xbb\ +\xa6\xdb\x7d\x0b\xb0\xe7\x02\x56\xab\x20\x49\xca\x66\x20\xac\xaa\ +\xab\x9d\x26\x0d\xcd\x60\x38\xb4\xbf\x86\xb9\x74\x61\x2f\xa7\xbf\ +\x7d\x15\xf9\xd9\x2b\xce\x75\x6a\x23\xbf\x6b\xc6\xa4\xd2\x46\xa6\ +\xce\x76\x4a\xae\xa9\x6d\x77\xca\x81\xb4\x46\x22\xf9\xd0\x63\xac\ +\xda\x06\x23\xb9\xdb\x70\x13\x3a\x05\xb2\xad\x57\xd9\x2d\x24\xec\ +\x49\xc1\xdf\x31\x6c\x30\x35\x9e\xc3\x2d\x76\x7e\x69\x3f\x94\x1e\ +\xec\xa5\x75\x90\x24\xc7\x1c\xa0\x64\xf7\x52\x61\x31\x4d\x19\x8c\ +\xc4\x6e\x90\x4b\x9e\x5d\xe2\x02\x1d\x02\x0f\x6e\x21\xac\xea\x85\ +\x30\xab\x4f\xfc\x58\x4e\x70\x1f\x76\x5f\x4a\x93\x6b\xb2\xf0\xd5\ +\x8c\xed\xb5\x73\xfb\x3a\x40\xf3\x57\x43\xa0\xd0\x60\x50\x9a\x6c\ +\xad\xe2\x98\x59\x45\x3b\x90\x64\x6a\xae\xa7\xbb\xb3\x42\xa8\x7c\ +\x12\x5c\xa8\x16\xfa\x98\xc5\x5e\x0d\x5c\x32\x2a\x1e\x16\x89\xaf\ +\xb8\xa7\x26\x2d\x93\x7e\x5d\x3b\x87\x97\xf1\x8e\x93\xee\xa3\xe6\ +\x0c\xac\x06\x1c\x6a\x6b\x88\x37\xb0\x03\x79\x69\x5c\x70\x3d\x76\ +\x0c\x25\x90\xc9\x32\x97\x44\xe0\x57\x16\xe1\x5b\x53\xf8\x92\x70\ +\x86\x12\xa0\x11\x07\x99\x54\x5f\x0a\x1f\x9f\x4b\x01\x4c\x8b\xb1\ +\xad\xa9\x08\xd1\x10\x64\x7c\x68\x43\x5b\x8d\x8a\xa4\xa8\x76\xf9\ +\xe3\x7e\xa2\x67\xe0\xe7\xe6\xdd\x68\x65\x1f\x48\xe5\xed\x95\xa4\ +\xc2\x6b\xaf\x5b\xd5\x79\xde\x91\x9e\x45\x12\x5c\x5a\x35\x6f\x95\ +\x36\x18\x0d\xfd\x3f\xb6\xd3\x73\xaf\x2b\xe9\xc1\x19\x4b\xd4\x2d\ +\x09\x0e\xa5\xbb\x8b\x36\xc8\xfc\x2e\xf2\xfe\x67\x83\xda\x5e\x6a\ +\xfa\x34\xd7\x6f\xa1\xf2\x35\x92\x45\xd7\xd6\x20\xab\x66\x30\x4b\ +\x83\x6e\xcc\x58\xd5\x2a\x19\x73\x10\x5c\x84\x73\xb1\x4a\x00\x41\ +\xee\xf8\xb5\xe6\xda\xdf\x8f\xbc\xff\xf7\x4a\x3b\xb3\x46\xca\x4b\ +\x89\x31\x29\xb5\xd5\x74\x8e\x7f\x72\x92\x7d\xd3\xcd\x06\x23\x6d\ +\xa3\xa8\xe9\x18\xc9\xe4\x4d\x1a\xe0\xc2\xcd\x5b\x23\xb9\xc0\x4a\ +\x6d\x22\xcf\x01\xfc\x7c\xf9\x59\xe0\xba\x35\x36\x94\x9e\xf6\x17\ +\xad\x7e\x6c\xe9\x24\xd9\xca\x0e\xb3\x4a\x06\x93\x3b\x8a\x2e\x49\ +\x69\x20\xae\xfb\x76\xf0\x90\x96\x66\x00\xa5\x39\x5f\x7f\xee\xfd\ +\xbe\xfd\xb3\xd7\xe8\xd5\x0b\x6a\x17\x52\xcc\x21\x4e\x3a\x79\xb7\ +\xe2\x44\xda\xc1\xb9\x9b\x7b\xec\x03\x6e\xe7\x75\xac\x57\xcb\x60\ +\x0a\x55\x10\x91\xe4\x4c\xaa\xec\x45\xde\x11\x2a\x58\x65\xb2\x95\ +\x9b\xbc\x64\xba\x04\xa9\xa3\xd7\x52\x06\x33\xb7\x39\x29\xbb\x4c\ +\x54\x1d\x61\xe7\x1c\xef\x44\xe7\x58\xc2\xc9\xd3\x01\x77\x35\xf7\ +\x5a\x15\x83\x91\xcc\x23\xaa\xe5\x38\xd5\x2b\x67\x88\xb4\xb3\x1c\ +\xb5\x6f\xb8\x6d\xef\xe9\x48\x47\xb5\xc9\x95\x5e\xb7\x94\x95\x0c\ +\x90\xd4\xea\x9a\xf9\xfe\x7a\x18\x0c\x75\x92\x96\x80\x0b\x87\xc1\ +\xb4\xd8\xbd\xb5\xc0\xb5\x17\x93\xe9\x99\x86\x83\xba\x93\x53\x9c\ +\x2b\xa5\xc2\x56\xa4\x12\xc1\xb7\xb0\x3d\x2d\x2e\x1a\x0d\xb4\x75\ +\xc0\xd3\xcd\x33\x18\x0d\x70\x91\xb0\x05\x8c\x72\xcc\xaa\xc1\x64\ +\x7a\x24\x8b\x96\xb4\xcd\x68\xdb\x79\x38\x41\x8a\xb5\x0c\xa7\x81\ +\xcd\xe6\x15\xf3\xfd\x75\x01\x4c\xe6\xc8\x54\x3d\x31\x71\xc9\x60\ +\x75\x00\x97\x64\xc6\x3b\x0d\x87\xb9\x14\xc8\x68\xa4\xa9\x94\x7a\ +\x7e\x62\x40\xac\x08\x70\xa5\x0c\xb9\x5c\xc3\x76\xc7\x4d\xea\x65\ +\xea\xfd\x9a\xf1\x58\xbd\x27\xaf\x16\x05\x4f\x5d\xab\x07\x73\xf1\ +\xc0\xee\x35\x80\xab\x47\x2e\x7b\x05\xe0\x75\x4f\x90\x29\x69\x3f\ +\xd5\x18\x5a\xc2\x40\xb4\xf3\xe8\x4a\x14\x7f\x8b\xd9\x81\x4a\x4f\ +\x3e\x85\x06\xf9\x24\xd0\x16\x93\x81\x77\x13\x0c\xe6\x61\x27\x00\ +\x76\x1e\xb8\x81\x73\xf7\x70\xee\xe6\x72\x71\x7a\x72\x98\x40\x35\ +\xbb\x4f\x0b\x7b\x91\x92\x90\x0c\x71\xad\x40\x46\xba\xfd\xa4\x84\ +\xef\xa1\xe8\xec\xc8\x9c\x38\x30\x8b\x9c\x61\xb7\x85\x03\xdc\x08\ +\x65\x50\x48\x20\xe3\xfd\x8b\xa7\x00\x5e\x3c\x82\x4e\xfd\x7a\x19\ +\xc6\x05\x39\x72\x54\x76\x09\xe0\x2e\x91\x10\xa7\x67\x4d\xe5\xa6\ +\x7d\x27\x94\xf1\x4e\x5b\xf5\x90\x58\xa8\xa1\xfb\xfc\x09\x80\xff\ +\x06\xf0\xb5\xc3\x77\xa6\x7f\x7f\x0d\xe0\x2f\x00\xfc\x5f\x21\xc8\ +\x72\x8d\xc4\xbd\x8f\xb3\xe7\xd7\xd0\xac\x19\xb6\xcc\x5d\x5d\x7c\ +\xaf\x03\xd2\x77\x7f\x01\x37\xa7\xee\x59\xf0\x1e\xb8\x99\x7f\x2f\ +\xd7\x86\xd1\xa4\x65\xff\x6c\x59\xde\xdf\xab\x89\x0f\xaf\x99\xea\ +\x88\xf7\x61\x22\x35\x47\x0f\x5d\xbc\x9c\x53\xa5\xf3\x76\x24\x06\ +\x53\xbc\x43\x1f\x3a\xa0\x34\x5b\xdd\x2a\x98\x4e\x26\xe3\xdd\x9a\ +\x22\xa1\x4b\xe4\x53\x00\xcf\xe6\xdd\xb1\xf8\xec\x1d\xc3\x89\xea\ +\x39\x9a\xaa\xbe\x51\x3a\x87\x2f\xc6\xc0\x16\x07\x30\x8f\xca\x6a\ +\xbd\x70\x73\x6c\x88\x88\xf6\xcd\xd4\xa8\xc9\xe0\x76\x92\xf1\xee\ +\x1c\xc0\xc5\x64\xbd\xaa\xfe\x30\x8e\x76\x3b\xc0\xbf\x0e\x7f\x7c\ +\x99\x32\x38\x95\xea\xd8\x94\xe0\xb4\x9a\x1a\x3d\xd2\x03\xb7\xdc\ +\x49\x62\x41\x90\xd4\xf6\x4a\xe5\xb5\xe1\xd8\x14\x6a\xea\x29\xbf\ +\x0f\xe0\x5f\x4e\x41\xf7\xe1\xb3\x7f\x45\xde\x11\x90\x5b\x98\xae\ +\xc5\x09\x54\x4d\xae\x21\xad\x90\x94\x58\x05\x8e\x92\xfb\x5d\x8c\ +\x00\x2e\x00\x1e\xce\xc9\xa6\xcd\xf9\x61\x93\xfe\x21\x70\x97\xca\ +\xef\x9a\x63\x1b\x5c\x14\xdf\x01\xb8\x9d\xee\xf3\x79\xe0\x59\x47\ +\x66\x0b\x9c\xbe\x90\x4a\xb4\xa5\xa5\x7e\x2e\xfb\xfa\xc7\x00\x7e\ +\x13\xf9\xee\x8f\x2b\xc7\x47\x2a\x6f\x0b\x17\x5c\x72\x00\xb7\x05\ +\x19\x21\xd8\x31\xec\x48\xe7\x1c\x7e\x83\xc7\x53\x83\xc0\xcc\x2e\ +\x56\x8f\xa2\x83\x1e\xf2\x79\x61\x2c\xe6\x16\x15\x0f\x63\x0c\x46\ +\x3a\x4f\x70\x6d\x4a\x48\xea\x02\x62\xb1\xa2\xa5\x13\x26\x13\x78\ +\x6b\xea\xa3\x6b\x80\x4b\x69\x7f\x2b\x55\xb2\xcc\xd6\x10\x5b\x1d\ +\x83\xc9\x79\xe9\x7e\x4d\x69\x37\x97\xa0\xc2\xcb\x5d\xaf\xb5\x7f\ +\x4c\x4d\x4a\xcd\x96\xbe\x32\xdc\x94\x0f\x31\x7f\x96\x87\x4d\xc5\ +\x7b\xb8\x00\xb8\x48\xf4\x79\xcf\x32\xb8\x5b\x65\x32\x17\x23\x80\ +\x0b\x2a\x0a\x3f\x69\x0f\x86\x27\xa8\x1a\x3d\x9c\xef\x34\x0b\xd9\ +\x97\xb6\xa7\x14\x64\xb2\xc0\x42\xbc\x4f\x69\x0c\xd5\x28\xc7\xfc\ +\x23\x80\xcc\x26\x2a\x3b\x4a\x81\x8b\x34\x7b\xf9\x5d\xa7\x9d\xae\ +\x02\x5c\x5c\xa4\x4f\x8b\x27\xb4\x40\xb2\x72\x16\xc8\x94\x3a\x9e\ +\x71\x23\xbb\x5b\x2f\x58\x89\x50\x80\x56\xcf\x1c\x9b\x3b\x12\xf7\ +\xbf\xe8\x09\x2e\xb5\x68\x29\x9d\xd7\xe3\xfb\xcc\x85\xb4\x05\xe7\ +\x36\x8d\x6a\x8f\x35\x09\xc9\x25\x5c\xf0\x4b\x62\x98\x46\x00\x97\ +\xd8\x98\xac\x59\x65\xba\x18\x05\x5c\x5a\x74\x62\x74\xe0\xa7\xf7\ +\x5f\x01\xf8\x36\xe3\x1a\x9c\xf2\x28\x1a\x6d\x98\x16\x64\x15\x8b\ +\x09\x2d\x80\xd6\xf6\x8c\x1a\xb0\x2e\x8d\xf3\xd1\x0a\x31\x91\x3a\ +\xea\xee\x11\x02\xa3\x91\xad\xe0\x62\x04\x70\x89\xb4\xf6\xe8\xbf\ +\x9f\x01\xf8\x21\x26\x57\x5f\xa6\xd7\x6e\xf6\xd9\x66\x7f\xe7\xf2\ +\xc9\x52\x76\xfb\x46\x93\x41\x0d\x78\xb5\x0d\xbf\x54\x95\x6a\xf9\ +\x92\x62\xb9\xcd\x22\xe0\x85\xf3\xfc\x6a\xcc\xab\xd2\x4d\x89\x7c\ +\xfd\x16\x9d\x9d\x03\x97\x92\x63\xd3\xda\xa3\x69\xae\xa3\x53\x49\ +\x96\x37\xc9\xc9\x4c\xc8\x38\x5f\xac\x7a\x4a\x82\x75\x89\x0a\xd2\ +\x62\x21\xd5\x3a\x09\xb6\x64\x2f\x2d\x81\x92\x72\x3c\x5d\x73\xbf\ +\x8b\xde\xe0\x52\x32\xd9\x25\xfc\x5e\x24\x6d\x0e\x23\x2c\x16\x41\ +\x95\x4b\x9d\x89\xf5\x48\x5f\x40\x65\x44\x52\x4c\x49\xba\x0d\x9a\ +\x91\xd3\x5a\xea\x51\x13\x80\x39\x3c\x78\xaa\x01\xda\x8b\x52\x02\ +\x5c\x24\xf3\x7f\x48\x33\x00\x29\x70\xe1\x80\x79\xed\xe2\xf0\x99\ +\x52\x26\x1a\x76\x21\xc5\x4d\xb4\xc9\xb3\xf6\xe8\x83\x55\xd4\x45\ +\xa2\xa0\x63\xce\xbf\x24\xd7\x58\x29\x63\x9a\xd4\xe2\xf7\x8d\x6b\ +\x01\x71\x8d\xbd\x39\x80\xd5\x04\x99\x52\xfb\x55\x2a\x2d\x80\xf6\ +\x86\x34\x22\x08\x54\xe6\xf4\x6d\x72\xe0\x32\x54\xca\xcc\x54\xea\ +\xc4\x9a\x38\x13\xa9\xc9\x70\xf8\x3e\x47\x95\xa8\x65\x33\x4a\x86\ +\x3d\x36\x68\x6b\xb2\xca\x58\x1f\x51\xef\x4f\x05\xf9\x25\x48\x49\ +\xcc\x8b\xde\x47\xc8\xa3\x1f\x61\x0f\x97\x93\xb7\x64\x47\x92\x74\ +\x08\xe3\xfc\x4e\x1b\x64\x62\x8b\x30\xb1\xa0\xb2\x2c\xa6\x46\x35\ +\xd4\x06\x99\x10\xe3\xcb\x79\x1c\x73\x03\x32\x53\xf7\xe4\xce\xbd\ +\xd6\xa9\x2f\xb9\xf3\xa1\xd6\xf6\x92\x3a\x4c\x59\x2d\xc0\xa4\xe8\ +\xb0\xa4\xfa\xa2\xc1\xba\x28\xf4\xbe\x95\xca\xa4\xc5\x2c\x5b\x3b\ +\xa5\xc5\x40\x86\x92\x70\x2c\xb5\x20\x6b\x7d\x98\x46\xc8\xab\x4b\ +\x1e\x97\xa9\x2c\x2c\x9c\xbb\xbf\x75\xce\xef\x1a\x32\x22\xb7\x16\ +\x6f\x54\xae\xaa\xd3\x02\x5c\x4a\x99\x01\x37\x2f\x6d\x68\x22\x65\ +\x4e\xcc\x54\x22\x63\x25\x4e\xdd\xe6\xcf\x2f\xe5\x4b\x94\x8a\x96\ +\xa6\x9c\x58\xd5\xe4\x64\x69\x09\x2e\xb1\xb1\x4f\x3e\x4b\x22\xd7\ +\xf5\x6b\x82\xcb\xc8\x26\x32\xda\x6d\x41\x0f\xad\x49\x34\xdd\x22\ +\x32\x5b\xe2\x9a\xad\x0c\xbf\x54\x75\x85\xf2\x9d\x52\x86\xb4\x26\ +\xbb\x47\xe6\xf9\x83\xd5\x28\x5e\x3f\x54\x27\xd1\x05\xcd\x55\x30\ +\x18\x6e\x1e\x8f\xd6\xec\xa5\x66\x90\xb8\x3b\x6a\x49\x99\x5d\xcd\ +\xd2\xb2\x12\x19\xf1\x4b\x03\x15\xb9\x27\x50\xb5\xac\xb8\xa7\x6a\ +\x54\x9c\xc3\x87\x98\xeb\x3a\x97\x71\xef\x2c\x19\x8c\x64\x79\x8a\ +\x5e\xf6\x0a\xa9\x7a\xcf\x8b\x6b\xba\x08\xd8\x0c\x69\x93\xa1\xa6\ +\x8e\x48\x2d\xa8\x5c\x46\xba\x56\x25\x57\x46\x5a\x0f\x25\xb9\xae\ +\xa5\xdb\x35\x3c\x83\xc9\xed\x5e\x12\x21\x04\x23\x30\x19\xce\x04\ +\xe6\xda\x61\x30\x85\x5b\x69\xfa\x8b\x48\xd8\x2e\x72\x63\x19\x7b\ +\x8f\x62\xb3\x92\x66\x48\xbd\xe6\x13\x97\x15\x53\x72\x5d\x53\xfa\ +\xee\x6c\x6d\x30\xa3\x66\x02\x93\x78\xae\x4a\x87\x2f\x77\xfc\x5f\ +\x3f\x7c\xdf\x53\x80\x81\x6b\x0c\xae\x61\x32\xb9\xc8\xf9\x15\xac\ +\x89\xc7\x9a\xb0\x07\xb5\xe8\x50\x90\x22\x00\x2e\x2a\xc0\xb8\x26\ +\x17\xec\x12\xdd\xbb\x37\x95\xad\x0d\xa6\xac\xb0\xc3\x00\xb3\x84\ +\x7c\x5a\xb1\x26\x35\x76\x27\x0a\x9b\xa8\xa9\x0a\xc1\xbd\x6e\x0c\ +\x7c\x7a\xab\x46\x85\x4c\xd1\x53\xd6\x3a\xd7\xe1\xf1\xec\x18\xcc\ +\x88\xba\x30\x77\x97\x0f\xd9\x0f\x6a\x7c\x66\xa4\x32\xde\x69\x33\ +\x19\x8a\xbd\x85\xe3\xa5\x9b\x9a\x0b\xa5\x4c\x66\x74\xbb\x0b\x15\ +\x5c\x72\xfd\xad\x15\x0f\x78\x31\x70\x27\x89\x80\xc9\x9a\x73\xad\ +\x96\xb8\xd0\xf7\xb2\x13\x54\x7a\x96\xaa\x25\x7c\x2a\x01\x19\x6e\ +\x2e\xe0\x91\xd8\xcb\x12\x5c\xbc\xf7\x47\xcc\xe5\x27\x87\x92\x3c\ +\xce\xe1\x83\x06\xcf\x6f\x7e\x30\x9d\x17\x20\x27\xe8\x30\xd7\xd6\ +\xd0\xb5\x24\x32\xde\x49\x83\x0c\xc5\xff\xa5\x34\x0c\x43\x02\x64\ +\xd6\xc2\x54\x28\xe0\x32\x6f\xcf\x07\x00\xfe\x61\xfa\xff\x9f\x4d\ +\x7f\x7f\x00\x9d\x20\xe2\xa1\x6d\x30\x52\x35\xa6\xd7\xde\x36\xee\ +\x6f\x13\xa7\x01\x4d\x6c\x31\x05\x36\x22\xd6\xf8\xe6\x6c\x58\x1c\ +\x7b\x09\xb7\xca\x63\x6f\xfb\x0b\xe3\xc4\x30\x3e\xce\xb1\xb5\xa2\ +\x78\x5a\xb6\x6e\x06\xb3\xc2\x64\xc8\x1a\xc9\xb4\x7b\x95\x4e\x29\ +\x65\x32\xa5\x25\x4a\x28\x9b\x49\x0d\x43\x19\xcd\x8b\x97\xf8\x3c\ +\x34\x70\x01\x4e\x2a\x95\xb6\x90\x8b\xf1\x30\x83\x38\x21\x9c\x83\ +\x5b\x29\xa5\xad\x49\xa6\x44\x71\x9f\x4f\x4d\x38\x6d\x35\x89\xa3\ +\x12\x6a\xd9\x77\x38\x20\x43\xa9\x73\x45\xc9\x55\xd4\x43\x1d\x8a\ +\xd8\x5b\x5c\x6c\x03\xfa\xf7\x86\x9b\xe1\xba\x19\xcc\x02\x5c\xa8\ +\x3b\xe5\xa8\x20\xca\x7d\x4e\xee\x09\xca\x28\x4c\xa6\x26\x4d\x65\ +\x09\xc8\x50\xef\x5f\x9a\xd1\x50\x73\x7e\xa5\xd4\x5d\x0e\x6b\x99\ +\x5f\xe7\x1f\x01\xfc\x84\xa1\x1e\x89\xb4\x63\xa4\x49\x49\x9a\x00\ +\x01\x70\x19\xc9\x83\x57\xdb\x0e\x53\x62\xc7\xe8\x65\x8b\x49\xa9\ +\x44\xa5\xba\xbe\x94\x87\x74\x78\x08\xca\x0c\xd3\x1a\xf3\x2c\xe0\ +\xd5\xec\x23\xcf\xe5\x24\xe6\x86\x86\xfd\x65\x95\x0c\x86\xb2\x2a\ +\x46\xae\xf3\xab\x9d\x64\x7b\xb4\x3c\x33\x29\x66\x59\xd2\xf6\xda\ +\xdf\xec\x7f\xf7\xa7\xc5\xfd\xba\x8c\xbe\xd6\x98\x67\xb3\x6b\x79\ +\x00\x51\xc6\x32\x3a\xb8\x0c\x05\x30\xa5\xd5\x04\x62\xec\x65\x4d\ +\xc5\xc4\x4b\x9e\x95\x52\xef\xb9\xa7\x2d\x26\x07\x74\x52\x79\x6f\ +\x39\x20\xf3\xf8\xbb\x7f\x4e\x6e\x5d\xb9\x24\x67\x29\x55\xaf\x22\ +\x2c\xc4\xcf\xd4\x9f\xc3\x0b\x5c\x60\x29\x49\x7b\xa1\x29\xab\x61\ +\x30\x3d\x0b\x68\x69\xeb\xd5\xdc\x45\x93\x33\x42\xf6\x06\xd6\x94\ +\x6f\xcb\xb2\x9d\x12\x4e\x88\xfc\xdf\xfd\x5d\x82\x17\xff\x3a\xfa\ +\xec\x25\x6c\x67\x9e\x4d\x6e\xfa\x77\x17\x01\x95\xd4\x33\xbb\x9c\ +\x01\xb7\x76\x7d\x68\x9d\x42\x0e\x61\x83\xe1\xf8\x77\xe4\xd8\xcb\ +\x48\x11\xb0\x25\xcf\xc1\xc9\x8d\x52\x92\xae\x42\xdb\x0e\x53\x1b\ +\x4b\x54\x63\x87\xa2\xdb\x64\x62\xe0\x72\x38\xea\xae\x67\x90\x00\ +\xf0\x4b\xe0\xaf\xbe\x0e\xfc\x62\xf9\xfe\x25\x80\xbb\x8a\xb5\xa9\ +\x11\x7b\xa7\xb5\x4e\x86\x67\x30\x5b\xf2\xb4\x2c\xda\x99\x89\x5e\ +\xbf\xb9\xff\xcf\xec\x1e\x2a\x6a\x12\xe5\x78\xb7\x06\x78\xa4\x99\ +\x8c\xc4\x38\xe5\xd8\xcd\xd7\x81\x7f\x0a\xbd\x7f\x1d\x61\x29\x13\ +\xda\xb9\x25\xb8\x50\x2a\x6b\x48\xe5\xae\x96\x5e\x57\x17\x23\x03\ +\x88\x44\x71\xb4\x11\x6c\x31\xac\x76\x38\xf7\xa0\x7c\xef\x30\x85\ +\xd9\x3b\x87\x5b\xe7\xb0\xcb\x5c\x23\xa5\x7e\x68\xb6\x3f\xc8\x5a\ +\x0e\xe0\xb8\x7c\x0d\x05\x32\xa7\xec\x45\x0a\x70\xbc\xf7\x78\x03\ +\x7c\x37\xf4\xdd\xe7\xc7\x0f\xe0\xf6\x8f\xed\xc8\xaa\x8f\x04\xa8\ +\xb4\x5a\x13\xdd\x55\xa4\x28\xc0\x20\x7e\x62\x14\x53\x9d\x58\xa9\ +\x04\x65\x1b\x11\x7a\xc8\xb2\x7b\x67\x06\x7c\x4a\xd6\xcc\x06\xb3\ +\x40\x3f\x57\xab\x4a\x51\xbd\x3f\x37\x69\x15\xb2\xcd\xf1\xd4\xd0\ +\x34\xb8\xd4\x4c\x91\x39\x2b\xbc\x01\x70\x15\xfe\xda\xad\x03\x5e\ +\xd4\xb0\xdb\x2e\x9b\xdf\xda\x00\xa6\x06\x5c\x38\x00\xa3\xda\x91\ +\xa9\xc5\x54\x92\xb1\x9e\xb2\xa3\x30\x12\x5d\x27\xfa\xab\x0a\x60\ +\x92\x7d\xcf\x04\x18\x29\x90\x29\xcb\xbd\x23\x03\x2e\x21\x75\x33\ +\x98\xce\xff\xb0\x4f\x4c\x09\x9f\x9a\x6c\x80\x9d\xc0\x65\x58\x1b\ +\x0c\x25\x04\x60\x0d\x79\x3a\xf8\xa1\xf6\x4d\x9f\xa1\xd8\x16\xa3\ +\xd1\xf7\x12\xbe\x25\x65\xc7\xd7\xc7\x2f\x2e\xa8\xa4\x4e\x81\xa6\ +\xb2\x20\x87\x84\x72\x27\xd9\xe4\x46\x51\xdb\x37\xa9\x22\x51\xdd\ +\xbd\x6b\x72\xad\x36\x41\xec\x98\x4f\x04\xfb\xd9\xe6\x7b\x6a\xbc\ +\x6f\x6e\x01\x5c\x15\x56\x2b\x08\x7c\xce\x62\x31\xe4\x3e\x2f\x60\ +\x30\x92\xe0\x55\x5b\x95\x81\xcb\x54\x16\xcf\xeb\x4a\xd7\xc0\xd6\ +\xd8\xcb\xb0\x0c\x86\x47\x71\xb7\x77\xa2\xe4\xe0\x71\x19\xf9\xec\ +\x25\xca\x02\xfe\x22\x9f\x93\x59\x4c\xab\x3e\x9f\x5f\xf7\x2f\x01\ +\xfc\x6f\xc2\x48\xcc\x65\x32\xa5\x8b\x8a\xe2\xaf\x92\x72\x80\xeb\ +\x65\x5f\xe9\x0d\x2e\xdd\x00\x86\xe1\xb3\xa1\x46\xc3\x55\x03\xd5\ +\xaa\x27\x8e\xc3\x21\x59\xf3\x52\xee\x98\x0c\x90\x63\xe3\xa0\x06\ +\x23\x7a\x2d\x7d\x6e\xf6\x5c\x3b\x00\xff\x05\xe0\xcf\x0b\xf5\xc8\ +\x43\xdb\xbe\x37\xfd\xe6\x57\xce\xed\xff\x06\x2f\xaf\x2d\x05\x54\ +\x6a\x6d\x58\xbd\xc0\x65\xb3\x2a\x52\x49\xb6\x33\xa9\x72\x0a\xda\ +\x6a\x52\x29\xb8\x70\x9c\xbf\x24\xcb\xd4\x86\xca\x9b\x84\xd4\xd3\ +\x23\x7d\x8a\xa0\xea\x50\x93\x1b\x15\x19\xa4\x2a\xae\xf1\x1e\x80\ +\x9f\x32\x93\x37\x95\xaa\x40\xa3\x31\xf0\x1e\xf7\x75\x3d\x33\xa4\ +\xd7\x82\x8b\x86\x8e\xde\x8b\x7e\x06\xf2\x74\x1f\x2d\xe9\xd3\x74\ +\x38\xfa\x20\x93\x04\x17\xce\x42\xaf\x04\x6d\x71\x90\x4a\xb7\x5f\ +\x05\x54\xce\x11\x5c\x86\xb2\xc1\x48\x17\x7c\xea\xad\xfa\x95\xaa\ +\x46\x99\x25\x5d\xa4\xee\xd5\x3a\xdc\x8d\x34\x06\xb7\x72\x63\xa5\ +\x6e\x57\x39\x77\x70\x69\x0e\x30\xb5\x6e\xff\x52\x1d\xa5\x61\x8b\ +\x69\x09\x2e\xd2\x20\xe3\xbd\x77\x3b\xec\x1d\xc3\xee\xf7\xfb\xfb\ +\x8d\x07\x76\xbd\x23\x71\x43\xf2\xb2\x72\xcc\x5c\x24\xfd\x81\x84\ +\x5d\xc5\xc0\xa5\xb3\x8a\xc4\x4d\xc9\x90\xdb\x79\xab\x33\x9e\x0b\ +\x32\x25\x29\xbb\x50\x08\x5c\xf2\xd1\x05\x95\xea\xd2\x3e\xc2\x37\ +\xe4\x13\x76\x1c\x97\xd7\x1a\x6c\x16\xcf\xba\x0c\x12\xa4\x3a\x2e\ +\xba\x0c\x62\x6b\x27\xdf\x3a\x57\x70\x69\xca\x60\xa4\x83\x16\xc5\ +\x0a\xb9\x63\x76\x2a\xc2\x38\x0a\xd5\x02\xaa\xa5\xe3\x17\xe5\x72\ +\x02\x4c\xe6\x3a\xf2\xf5\x6b\xb2\x5d\x84\x02\x16\xdc\x3e\x5e\x74\ +\xc4\x6b\x5e\x09\x97\xbd\x0a\x94\xc9\xab\x62\xe0\xb2\x11\x06\x33\ +\x1a\x7b\xc9\x2e\x18\xc5\x14\x8e\x23\xb0\xa8\xa3\xef\x02\xf7\x00\ +\x9e\x04\xbe\xf6\x06\xc0\xd3\x92\x7e\x21\xf5\x33\xc7\x51\x8f\x60\ +\xe8\xa7\x78\x24\x8f\x90\x2a\xf4\x5c\xc0\xa5\x19\x83\xa9\x05\x97\ +\xad\x4a\x4f\x3f\x9f\xc5\xfd\x5e\x45\xbe\xf6\xaa\x43\xa7\xc4\xde\ +\x7f\x48\xdc\x74\xb0\x11\x2d\xd9\x4a\x6b\xbb\x8a\x81\xcb\x20\x0c\ +\x66\x48\xf6\x22\xc0\x60\xc8\x00\x11\xca\x4b\xab\x38\x09\xb8\x4c\ +\xc6\xc7\xe3\xf2\x4e\x73\x23\x69\x33\x98\xf0\x77\x83\xcf\x97\x4a\ +\xdc\xd4\x12\x4c\x46\x58\xe4\xa3\x66\x7c\xbc\x18\x1d\x5c\xb6\xc8\ +\x3e\xb4\xeb\x39\x71\x99\x8c\xc3\x83\xe3\xf0\xed\x63\x44\x1e\x20\ +\x02\x2e\x32\x72\x4d\x7c\xf3\x90\xac\xc9\xf5\x9a\x13\x06\x2e\x2d\ +\x19\x4c\xa0\xc4\x48\x09\xc0\xf4\x4a\xb5\x20\x06\x30\x02\x9e\xbe\ +\x2d\x6d\x32\x10\xc8\x15\x93\xed\x6b\xa2\xfd\xc5\x01\x3e\x67\x20\ +\xf2\xde\xbb\xde\x8b\xac\xc7\xfd\xd7\x90\xa7\x5a\x95\xc1\x48\x83\ +\x8b\xc2\x56\xaf\x0f\x2e\xfd\x28\xd6\xd1\xf1\x09\x85\xc9\xa8\xb6\ +\x85\x79\x34\xe6\x80\x87\x13\xa0\x98\x21\xe8\x09\x70\x7b\x00\xc0\ +\xe6\x73\x27\xa3\x12\x19\xb8\x28\x33\x18\x4a\x48\x40\x49\x91\x74\ +\xcd\x4e\xe4\x3e\x8f\x67\xba\xac\x37\x63\x2f\xa7\x2c\x80\x75\xdf\ +\x65\x2a\x07\xb4\xb3\xd5\x79\xb2\x01\xe6\x60\x86\x99\xe5\x56\x69\ +\xbd\xf8\x8c\xb5\x74\x62\x30\xd4\x78\xa3\x92\x9d\x61\x95\xe2\x7d\ +\xd7\x3a\xda\x1c\x26\x93\x62\x03\x1a\x8c\x20\xe7\xb2\xef\xbd\x77\ +\x94\xc4\x4d\x2d\x9f\x5f\xa2\x4c\xc8\x1a\xee\x39\x26\x83\x59\xd8\ +\x5d\x76\x08\x7b\x5f\x8e\xc6\x5e\x28\x0c\xa5\x22\x9f\x48\xdb\x09\ +\x21\x96\x04\x2b\x1e\x69\xad\xc1\x54\x96\xc0\xa2\xb9\xd9\x95\xb6\ +\xa1\x07\xb0\x48\x3e\x7f\x6b\x79\x4b\x13\x5c\x96\xb3\xb3\xa6\x53\ +\x46\x4a\x4e\x3e\x34\xb8\x64\x98\x8c\x9b\x3d\x57\xed\x22\x2b\x08\ +\x89\x68\xee\xb2\x1f\x4b\x10\xcf\x4f\x67\xda\x0e\x58\xa4\x34\x80\ +\x6d\x01\x4c\x02\x5c\x6a\x6d\x1d\x6d\xb5\x99\xc7\x3c\x28\x35\x8b\ +\x70\x34\x70\x59\xb6\x8f\xfb\x3c\xa1\xfc\x30\x14\xa0\xe9\x01\x2a\ +\x1c\xa0\xe1\xb0\x56\xcd\x85\xae\x51\x4c\x6d\x53\x00\x93\x03\x97\ +\x35\x7b\xe7\xae\xce\x4f\x27\x56\x3a\xc4\x7b\x56\x1b\xa6\xe3\x5f\ +\x7f\x00\x8a\x03\x18\xe4\x80\x66\x14\x50\xc9\x2d\xd4\x1c\xd8\x68\ +\x2f\xf4\xad\x82\x8a\xb8\x0d\x66\x11\xd7\x12\x9f\xf4\x03\xdb\x5e\ +\xa4\x29\xf1\x56\x42\x1f\x52\xa5\x66\x63\xbe\x33\x23\x02\x4b\xed\ +\x62\xaf\x99\x8f\x9a\xd7\x3e\x0b\x80\x79\x50\x27\x92\x0c\xe7\xb8\ +\xda\xd1\xe8\x49\xbd\x6b\x00\x66\x6b\x71\x55\x19\x90\xf1\x6b\x05\ +\x95\xde\xea\xf8\x56\x41\x65\x2e\x17\x92\x9d\x94\x82\x18\x0f\x87\ +\xff\x01\x1e\x12\x2f\x8f\x0c\x2e\xa1\xfb\x72\x9e\xc3\x33\x55\x91\ +\x35\x02\x4e\x26\xc0\xf0\xa8\x24\x2a\xa5\x0c\x6a\x6b\x40\x49\x3d\ +\x53\xa8\x0c\xac\x54\x30\xaa\xc6\x75\xcf\x82\xc1\xec\x07\x2e\x0c\ +\x2c\x29\x95\x69\x54\x80\x39\x62\x66\x67\x12\xdd\x4d\x65\x31\x14\ +\xa6\xe2\x0a\x4a\x8c\xf4\x66\x28\x36\xce\xf2\xf2\xd6\xc8\x0f\x37\ +\xc2\x80\xdb\xa4\x2b\xb3\xab\x94\x96\x44\xa9\xed\xf3\x73\xb6\x77\ +\x18\xc0\x0c\xae\x0b\x9b\xa4\x81\xe4\xc0\x62\x4a\xec\x2a\x9e\x97\ +\x8d\xce\x36\x0b\x03\x18\x13\x63\x2b\xb2\x8b\x5d\x12\x58\x0c\x4c\ +\x06\x21\x0d\xda\x36\x18\x20\x60\x87\x21\x4c\x2e\x9b\x20\x26\x26\ +\xc6\x60\x4e\x70\x23\x04\x32\x0e\x3e\x1e\xa1\xff\xf9\xe7\xc0\x8f\ +\x7e\x04\x0f\xe0\x8b\xb7\xdf\xc6\x7f\xee\x76\xf8\x37\x03\x17\x13\ +\x13\x63\x30\xd5\xf2\xd9\x67\xc0\x3b\xef\x84\x3f\xfb\xf4\x53\xe0\ +\xd9\x33\x1b\x21\x13\x93\x15\x4b\xdf\xca\x8e\x3f\xf8\x41\xd9\x67\ +\x26\x26\x26\xc6\x60\xb2\xf2\xf6\xdb\xc0\xfd\x7d\xf8\xb3\xa7\x4f\ +\x81\xdf\xff\xde\x46\xc8\xc4\xc4\x18\x4c\xa1\x7c\xe7\x3b\x65\x9f\ +\x99\x98\x98\x18\x83\xc9\x8a\xd9\x60\x4c\x4c\x8c\xc1\xa8\xc9\xb3\ +\x67\xf8\xe4\xc3\x0f\xf1\xf1\xbb\xef\x02\x00\xbe\xf8\xca\x57\x80\ +\x97\x2f\x81\xdf\xfe\xd6\xc0\xc5\xc4\xc4\x18\x8c\x89\x89\x89\xc9\ +\xa8\x0c\xc6\xc4\xc4\xc4\x00\xc6\xc4\xc4\xc4\xc4\x00\xc6\xc4\xc4\ +\xc4\x00\xc6\xc4\xc4\xc4\x00\xc6\xc4\xc4\xc4\xc4\x00\xc6\xc4\xc4\ +\x64\x4c\xf9\x7f\x2d\xef\x00\x39\x64\x58\x99\x8e\x00\x00\x00\x00\ +\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x16\xa3\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x01\x18\x00\x00\x00\xa0\x08\x06\x00\x00\x00\xd7\x60\x73\x20\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ +\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ +\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x01\x16\ +\x0f\x1f\x37\xd2\x49\x1a\xa0\x00\x00\x00\x45\x69\x54\x58\x74\x43\ +\x6f\x6d\x6d\x65\x6e\x74\x00\x00\x00\x00\x00\x43\x52\x45\x41\x54\ +\x4f\x52\x3a\x20\x67\x64\x2d\x6a\x70\x65\x67\x20\x76\x31\x2e\x30\ +\x20\x28\x75\x73\x69\x6e\x67\x20\x49\x4a\x47\x20\x4a\x50\x45\x47\ +\x20\x76\x36\x32\x29\x2c\x20\x71\x75\x61\x6c\x69\x74\x79\x20\x3d\ +\x20\x39\x30\x0a\x67\x9d\x47\x40\x00\x00\x15\xd2\x49\x44\x41\x54\ +\x78\xda\xed\x5d\xbf\xaf\x5d\xc5\x11\x9e\xbd\x72\x40\x0e\x06\x45\ +\x4a\x91\xc2\x96\x28\x6d\x14\x51\xc1\xdf\x10\xd3\x44\x8a\x8c\x04\ +\x05\x6d\xba\x14\x94\x80\xdb\x28\x09\x4d\x90\x90\x9c\xff\x21\x44\ +\xe2\xb5\x24\x76\x9f\x0a\xba\x44\x09\xa1\x89\x25\xbb\x88\x84\xd3\ +\x00\x46\xa0\x48\x9b\xe2\xde\x6b\x9f\x77\xde\xfe\x9a\xd9\x99\xd9\ +\xd9\x73\x67\xa4\x2b\x1e\x3e\xf7\x9e\xb3\x3b\xbb\xfb\x9d\x6f\x66\ +\x67\x67\x42\x8c\x11\x5c\x5c\x5c\x5c\x24\x64\xe7\x2a\x70\x71\x71\ +\x71\x80\x71\x71\x71\x71\x80\x71\x71\x71\x71\x71\x80\x71\x71\x71\ +\x71\x80\x71\x71\x71\x71\x80\x71\x71\x71\x71\x71\x80\x71\x71\x71\ +\x71\x80\x71\x71\x71\x71\x80\x71\x71\x71\x71\x71\x80\x71\x71\x71\ +\x71\x80\xd9\x98\xdc\xbb\x07\xf0\xda\x6b\x00\x21\x00\xbc\xf0\xc2\ +\xfe\xef\x7b\xf7\x5c\x2f\x2e\x27\x23\xc1\xcf\x22\x09\xc9\xd9\x19\ +\xc0\xeb\xaf\xa7\xaf\x7d\xfc\x31\xc0\xad\x5b\xa7\x39\xe1\x42\x80\ +\xdc\x9c\x0b\x21\xb0\x3d\xc7\xe7\xb5\x03\xcc\xb6\xe5\xd5\x57\x01\ +\x3e\xfb\x2c\x7d\xed\x95\x57\x00\x3e\xfd\xf4\x24\xc1\x65\xa4\xf8\ +\x5c\x77\x80\xd9\x8e\x3c\xfb\x2c\xc0\xf7\xdf\xa7\xaf\x3d\xf3\x0c\ +\xc0\x77\xdf\x39\xb8\x08\x00\x81\xd6\x73\x5c\xda\xc4\x7d\x30\x52\ +\xf2\xf2\xcb\xb4\x6b\x1b\x02\x94\xe5\xa7\xb4\xc8\xd7\x9f\x5e\x96\ +\xd2\x7a\xbf\xd6\x36\xba\x38\xc0\xd8\x93\xdb\xb7\x69\xd7\x36\x02\ +\x2c\x35\x00\xd0\x34\x9b\xb0\x80\xe3\xe2\x00\x63\x5f\x6e\xdd\x82\ +\x9f\x01\xc0\x5f\x0e\xff\xfb\x15\x00\xc0\xcd\x9b\x00\x77\xef\x6e\ +\xce\xc1\x5b\x62\x01\xb9\xc5\xad\x0d\x32\x18\xc0\x71\x56\xc3\x38\ +\x37\xdc\x0e\xd5\xf1\x3b\x6c\x51\xcf\x35\xa6\x42\xb9\xc7\x48\x3d\ +\x71\xf4\x67\xee\xf1\xcc\xf5\xdd\x01\xc6\xfc\xe2\xdb\x92\x9e\x73\ +\x0b\x91\xda\x47\x8b\xba\xe2\xee\xe3\xac\xe0\xd2\x0b\x32\x6e\x22\ +\xb9\xa0\x4d\xa1\x9c\xc9\xd1\x63\xb2\xb4\x32\x09\x6d\x33\xaa\x55\ +\x07\x2e\xce\x60\xec\xb1\x97\xf5\x44\x35\x3c\x16\x39\x60\x39\x15\ +\xb3\x52\xab\xff\x5b\x63\x30\x68\x80\x29\x45\x62\xba\x20\x00\x46\ +\xc2\xe0\xdd\xc8\xc2\xb2\xec\xbb\xda\x1c\xd0\xac\xfa\x13\x20\x8e\ +\x03\x98\xad\x3b\x2d\x7b\xd1\x3e\xc6\x0e\xf6\x62\x0c\x60\x46\xfb\ +\x20\x66\x03\x99\x29\xd7\x44\xa6\x1f\x29\x90\xd1\xf2\xc1\x44\x4b\ +\x76\xb2\x35\x2a\xb9\x15\x95\x48\xf8\x59\x28\x3e\x10\xab\x73\xad\ +\xe4\x9f\x71\x21\x02\x4c\x08\x21\x3a\xc8\xd0\x17\xc9\x5a\xde\x9c\ +\xc8\xbc\x1b\xf5\x66\xb6\x0c\x32\x39\xdd\xf8\x9a\x20\x02\x4c\x8c\ +\x31\x9c\x2c\x93\x09\xe1\xc9\x27\xc2\xfe\xd3\x2b\x7f\x5a\x80\xcc\ +\x17\x87\xbf\xc3\x60\x60\xb1\xb8\x5d\x6c\x1d\x64\x52\x7a\x9a\x65\ +\xa7\xe9\xcd\xe6\xfe\x75\xcc\x2b\xa4\x0f\x26\x2e\x01\xe7\x24\x7c\ +\x32\x85\x89\x92\x76\x88\xd5\x75\x92\xd2\xdb\x48\x5d\xce\xe0\xb8\ +\x9c\x61\xae\xcd\xe6\x00\x0e\x21\xc0\x1b\x00\xf0\x11\x17\x9a\xf4\ +\x02\xcc\x49\x82\x0c\x0a\x60\xfe\x07\x00\x3f\xa8\xea\x22\xa7\xb3\ +\x11\xba\x9c\x29\x20\x70\x96\xb9\x96\xd4\x69\x6a\x1e\x19\x8b\x5a\ +\x96\xd0\xe9\x25\x0e\x7a\x78\x6c\xac\x6f\x61\xff\xbd\x6b\x91\xf4\ +\xea\x12\xfb\x9b\xd9\xa2\x8d\x47\xcf\x35\xb2\xd9\x93\xfb\x5d\x08\ +\x43\x40\x46\x93\x69\xed\x08\x83\x1c\xd6\x6c\x66\x06\x3b\x59\x47\ +\x7e\x5e\xd4\x43\xcb\x1b\x98\xaa\xcb\xe5\xc2\xdb\x22\xb8\x68\xfa\ +\x64\xd6\x69\x1c\xa4\x7c\x2a\x67\x03\xd6\x8b\xb6\x19\x47\x8a\xe4\ +\x5d\x9b\x49\xb3\x51\x58\x39\x13\x29\x34\x2f\x08\x8a\x09\xc5\x35\ +\x69\xb6\x70\x4e\x8a\xbb\x0f\x52\x89\xaa\x42\xc8\x85\xae\x01\x3c\ +\x06\x80\xe7\x94\x7d\x36\xda\x63\x4f\x3a\x8b\x94\x62\x31\x9b\x65\ +\x32\x31\xc2\x8d\xc3\xdb\xe6\xf1\xe1\xbf\x37\x0e\x50\x12\xe3\x91\ +\xe1\x06\xc8\xed\x01\xa5\x98\x45\x6d\x50\x31\x7a\x2c\xf9\x73\x6a\ +\x2c\x6a\xe6\x17\x01\xc7\xf6\x70\x8d\x9d\xa4\x92\x57\x61\xf5\x55\ +\xfa\xfe\x9f\x09\x6d\x92\x00\x51\xc9\xb5\x1a\x3a\x4e\xc0\x26\x59\ +\xcc\xd6\x98\x0c\x56\xf9\x2d\xe0\x40\x49\x65\x50\x0b\xec\x2a\x3d\ +\x37\xb5\x53\xb5\x15\x96\x89\xed\x53\x6d\x3c\x45\x74\x52\x7a\x66\ +\x66\x6c\xb8\xdb\xd5\x32\x8f\x45\xce\x96\x75\xe6\x3f\xdd\x3c\xc8\ +\xe4\xcc\x8f\x56\xf0\xe8\x5d\xd4\xd4\x1d\xa7\x21\x0b\xc9\xc8\x38\ +\x61\xf5\xa1\xa2\x0b\xc4\x2e\x12\x77\x5b\x47\x81\x8b\x28\xc0\x6c\ +\x01\x64\x6a\x20\xd2\x0a\x1e\x12\x20\x43\xf1\xe7\x6c\x15\x5c\x5a\ +\xf5\x64\x41\x07\x5c\x8c\xab\x67\xb7\x50\x53\x17\x81\x81\x7a\x6d\ +\x16\x64\x6a\x8b\xb3\x65\xb2\x70\xbd\x8d\xa8\x13\x6d\xcb\x89\xaf\ +\xac\x2d\x26\x29\x90\xe1\x1c\x7f\x6d\x5d\xec\x34\x95\x37\x93\xe3\ +\x97\xea\x34\x2c\x81\x6b\x8f\x63\x32\x35\x19\xa8\x74\x79\xcb\xa1\ +\x04\xa5\xed\x7f\x2b\xe0\x4a\x69\x07\xe5\x80\xa5\x85\x14\xa0\x3b\ +\x06\x65\x5d\xd8\x51\x5a\x4f\xe2\xad\xec\x2e\xb5\xf4\x03\xfb\x06\ +\x15\xf5\xe0\x17\xd8\xe3\x16\x41\xa6\x94\x71\x6f\xc6\xb9\xd4\x0a\ +\x34\xa9\x7e\x5b\xc9\x2f\xcc\x92\xd1\x6e\x69\x26\xc1\x6a\xbf\x76\ +\x74\x28\xbc\x84\xef\x25\xc7\xd4\xb0\x34\x16\x43\x95\xb9\xee\xbd\ +\x55\x93\x69\x26\xa7\x36\x15\xdc\x5b\x5e\x12\x25\xdf\xd3\x08\x1d\ +\xb0\xa5\xcc\xdc\x12\xc8\xb4\x0c\x0e\x86\xa1\xf5\x06\xbd\x61\x9d\ +\xbc\xd4\x7b\x6e\x05\x58\xac\xcd\x37\x29\xb6\x18\x1b\xb7\xb8\x87\ +\xf6\x5d\x02\x60\x8e\x66\x53\xcb\x5b\x74\xc6\x6c\x65\xb5\x05\xdc\ +\x1b\x89\x9b\x5d\x20\xfb\x8b\x28\x90\xe3\x8c\x18\x9e\xf1\x45\x30\ +\xaa\x9f\x98\x45\xdf\x1b\x72\x60\x15\x5c\x58\x01\x66\x2b\x20\x83\ +\x65\x2f\xbd\x0c\xa6\xd9\x6e\x5e\x81\x4b\x6e\xf1\xf4\x80\xdb\x4c\ +\x20\xd3\xb3\xdd\x2b\x16\xf3\xd1\x61\xa6\x61\xda\x37\x53\xfd\x6d\ +\xf6\xaa\x02\x33\x83\x0c\x37\x7b\xe1\x02\x99\x1c\xb8\x48\x3d\xd3\ +\x3a\xd0\x50\xdb\x2b\x35\xdf\xb8\x0b\xd0\x71\xc6\xb8\x8c\x1e\x4f\ +\x95\xba\x48\xa9\x7a\xc4\xeb\xe3\xf6\xb3\x64\x2b\xc3\x4c\x12\x4a\ +\xff\x92\xcf\x43\x9c\x5d\xa2\xb4\x7b\xa6\x1d\xa6\x1e\x30\x6c\x39\ +\xab\xc5\xc5\x78\xb1\xdb\xe2\x92\x6b\x61\xe4\x78\xee\x04\x16\x64\ +\xf2\x20\x64\x6e\x22\x5b\x01\x99\x96\x67\x53\x26\x37\xc7\xa4\xd6\ +\x88\x5d\x99\x01\x64\x38\x98\x16\x57\x3f\x73\x29\x46\x19\x7d\x9a\ +\xac\xc0\x31\x6a\x3c\xd5\x2b\x3b\x5a\x06\x19\x4e\xf6\x42\x9d\xd4\ +\x23\x63\x57\xa8\xac\x6b\x16\x70\xe1\xd2\xab\xd4\x16\x30\xc6\x9c\ +\xa2\x98\x65\x23\xc6\x33\x08\x3a\xbc\x8a\x47\x08\xb8\x6c\x57\x2d\ +\xdf\x4b\x69\xf1\x53\xcf\x05\x51\xb7\xbe\xb9\xfd\x3e\x9a\xfe\x8a\ +\xd1\xe0\xd2\xd3\x4f\x0b\xd5\x2d\x39\x02\x39\x35\xc7\x73\x68\x6d\ +\x6a\x4a\x60\xd9\x48\xa6\x25\xc9\x10\x6a\x0c\xa8\xd5\xd4\xe2\x62\ +\x20\x12\xfe\x0a\x6b\xe0\x82\xd1\x97\x85\xc0\x35\xca\x18\x8c\xae\ +\xdf\x24\x5a\x9b\xba\x85\xc5\x8c\x66\x32\xbd\x69\x17\xb8\xbd\xff\ +\x5c\x87\xd7\xb8\x18\xc8\xc8\x1d\x26\xcd\x67\xf7\x06\x2e\x6a\x82\ +\x49\x2e\x6a\xdc\xe2\xc1\xd7\xa1\x0c\x26\xf7\x86\x1e\xcd\x64\x34\ +\x26\x18\x75\xe7\xa7\xf5\xd9\x12\x4c\x46\x73\x3c\xb4\x17\x43\x0b\ +\xb3\xb4\x60\x2e\xb6\xe4\x72\xb6\x34\x96\x3b\x61\x65\x64\x77\x94\ +\x30\x4a\xd2\x4e\x21\x68\xdd\x4f\xd1\xba\x5b\xc1\x09\x32\x23\x9d\ +\xbf\x5a\xba\xad\x99\x85\xa3\x4a\xe7\x96\x58\x33\xb6\x5d\xda\x20\ +\x63\x82\xc1\x58\xf2\xcb\x44\x42\xcc\x09\x17\xb8\x71\x02\x1e\x37\ +\xc8\x70\xdf\xcb\x22\x70\xf7\x32\x04\xce\xfe\x4b\x1e\x56\xd5\x04\ +\x99\x9d\xc2\x80\xa1\x58\x8c\x96\xc9\x44\x2d\xef\xc1\xb1\x08\xd6\ +\xe7\x8b\x38\x17\xee\xec\x20\x63\x61\x6b\xbc\x16\x66\x30\xca\xf7\ +\x32\x63\x8a\x8d\x9d\xe5\xc6\x69\xfb\x65\x34\x42\xce\x73\x87\x17\ +\xb1\x0b\xb7\x16\x35\x2c\x99\xf8\x4b\x6b\x87\xc9\xca\xe9\x67\x8d\ +\x85\x4d\x19\xb3\x91\xb1\x40\xa6\x00\x86\xc2\x62\x6a\x6c\xa6\x47\ +\x21\x66\x8a\x93\x15\xde\x94\x1c\x5b\x92\x5a\x20\xc3\x35\x41\x2d\ +\xa7\x56\xd0\x32\x09\x6b\x0c\x9e\x53\x3f\x1a\x20\x63\x9a\xc1\xd4\ +\x14\xca\xa9\x10\x75\xf6\xd2\x51\xd9\xb1\x27\x6a\xd8\x2a\xc8\x58\ +\xa4\xff\x1a\xce\xed\xda\x4b\x8c\x2b\x55\xea\xe6\x4d\xa4\x1e\x16\ +\xc3\x09\x32\x5c\xd9\xc4\x24\xdf\xce\x1c\x4c\xa6\x64\xc6\x70\x83\ +\x0c\xf7\x22\xb4\xc0\x5e\x96\x6d\x90\x3a\x24\x59\xcb\xd9\xac\x01\ +\xbc\xd2\x2c\x46\x95\xc1\x70\x81\x0c\xb7\x52\x52\x1e\x7a\x6a\x6d\ +\x69\x2e\xd6\xc3\x51\xc0\xad\x27\x0b\x5f\xcf\x44\xfd\x6d\x08\xfb\ +\x3a\x40\xcb\xcf\xa4\xa6\x91\x06\x5b\xeb\xd9\x66\xb6\x0e\x32\xd3\ +\x98\x48\x1c\x4a\x69\xf1\xd0\x73\xef\x1a\xf5\x2c\x1e\x0e\x10\xd0\ +\xdc\x92\x8c\x31\xc2\x6f\x00\xe0\x76\x5a\x11\xd3\xce\x2d\x2e\x5d\ +\x62\x63\x6b\xb6\x90\x98\x3d\x0c\x49\x04\xdc\x78\x84\x80\xfb\x6d\ +\x80\xad\xd2\x98\xfb\x8e\x76\x82\x23\xae\xc3\x8c\xb5\x44\xe5\x2c\ +\x73\xa1\xa1\x4c\xaa\x65\xf6\x22\x35\xb6\x9c\x25\x6e\xb5\x32\xf2\ +\x71\x3c\x67\x5a\x06\xd3\xea\x6f\xa8\x0d\x18\x65\x4b\x58\x13\x5c\ +\x38\x27\xd4\x4c\xa9\x32\x66\x62\x39\x01\x61\x02\xf6\x82\xcb\x6c\ +\xb2\x1b\x34\x38\xdd\xbe\x18\x6e\xfa\xca\x19\xc1\x79\x21\xd6\x45\ +\xa0\x7f\x54\xd0\x91\x06\x99\xff\x66\xfe\xfd\xa1\x51\xdf\x4b\x8f\ +\x4e\x6b\x7a\x6b\x75\xe4\x62\x5f\xa2\x56\xfa\x7f\x12\x0c\xa6\x05\ +\x64\xb4\x92\x31\x27\xc1\x85\x71\xd0\xb4\xce\x16\xf5\xdc\xfb\xad\ +\xcc\xbf\xff\x12\xe6\xc8\x92\xc7\x35\x26\x1c\x79\x83\x47\x03\x2f\ +\xcb\x6e\xd9\xe0\x0e\xb0\xf9\x62\xa8\x8a\xe1\x4c\xde\x24\x09\x2e\ +\xb9\xb6\x71\x6e\x9f\x73\xf9\x78\x5e\x03\x80\x4f\xd6\xd7\x12\xf7\ +\x3d\x7e\xdf\xca\x22\x92\xf0\x93\x71\x82\x8b\x16\xe3\xe3\x7c\xce\ +\x26\x01\xa6\x15\x64\x30\x66\x95\x85\x12\x20\x12\x99\xeb\x24\x16\ +\x07\xf6\x88\x87\x05\x80\x61\xaa\x70\x4a\xee\x57\x4f\xb1\x3f\xcb\ +\x00\x33\x3a\xa3\x1d\xbb\x2f\x46\x62\x00\xac\xd5\x17\xe2\x0e\x6e\ +\xeb\x3d\x5c\xda\xda\xf7\xd5\xb5\x38\x08\x50\x22\xa7\x09\x20\x71\ +\xaf\x9c\x1e\x07\xed\xf8\xba\x0f\x86\xd3\x5f\x33\x1a\xf1\x47\xf8\ +\x64\xa8\xcc\x83\xd2\xf7\xc3\x77\xe2\x02\x64\xa2\x16\xb0\x1c\xc1\ +\x85\xf3\x65\x56\x8a\xec\x35\x73\xe6\x6d\xe0\xcb\x79\x67\x60\xa1\ +\x8b\xb1\x18\x0e\x64\xb6\x50\x31\xd0\x2a\xc8\x30\xfa\x2f\xa2\xd4\ +\xd8\xb7\xdc\x9b\xda\xf6\xdc\x21\x45\xc9\x2d\xec\xd9\x64\xb3\x0c\ +\xa6\x95\xda\xcf\xc2\x5c\x28\xb9\x78\xb9\x4d\x26\xe9\x5d\x20\x4e\ +\xa0\x91\x04\xad\x16\x60\xc0\x1e\x5a\x1d\x6d\x92\x6d\x1a\x60\x46\ +\xb1\x98\x9e\xc1\xb2\x1c\xc3\xc1\x35\xf9\x4a\x20\xc3\xd5\xff\x94\ +\x73\xbf\x77\x0e\xa4\x7e\xbf\x7e\x0e\x57\xc1\xb5\x92\xff\x0a\x7b\ +\x48\xd2\x12\x7b\xe1\x9a\x4f\x9b\x64\x30\x9c\xbb\x15\x92\xa6\x01\ +\xb7\x7d\xac\x0d\x32\x94\xfe\x67\x76\x4b\x02\x9c\xdf\xc9\x26\x31\ +\x90\xd4\x6f\x52\xf7\x96\x62\x2d\x2d\xfa\xcb\xc5\x66\x51\x0e\xc1\ +\x3a\x83\xe9\x60\x31\xb5\xcf\x28\x5a\x69\x8d\xb9\x8c\x02\x19\xee\ +\xfe\x1f\xee\x55\x06\x9a\x10\x2e\x43\x08\xef\x42\x08\x11\x42\x78\ +\x70\xf8\xfb\x72\x0e\x58\x62\x8c\xa1\x16\x6c\xa9\x11\x00\x67\xa1\ +\x0c\xca\xb0\x97\xbd\xa5\xce\x51\x80\x83\x8b\xfa\x72\xb3\x09\x29\ +\x56\x36\xe2\x2c\x54\x85\x7d\x50\xee\x11\x2b\x26\xd2\xb9\xef\x00\ +\x00\x5c\x06\x80\x7f\x00\xc0\x8b\xab\xef\xde\x07\x80\x97\x00\xe0\ +\xdb\xfc\xcb\xea\x5c\x73\x97\x8f\x69\x69\x3f\x37\x18\x30\xea\x50\ +\xb5\x3e\x13\xf5\x59\x97\xac\x23\x60\x02\x40\xe2\xfa\xff\xb9\x83\ +\xf4\x38\x19\x91\x32\x08\x45\x29\xe6\x26\xc5\x0a\x5b\x5f\x2a\x6f\ +\x27\xc0\x05\x0e\xff\xf6\x36\x00\xbc\x8f\xbf\x67\xa4\xb4\xdf\x52\ +\xa2\x73\x46\x86\x2a\x36\x41\xcc\x30\x98\xdc\xa4\x28\xd4\xb5\xbe\ +\x40\x89\x31\x34\x78\x36\xa6\x53\x7b\x93\x8c\x70\x8e\x6b\xca\x03\ +\x00\xb8\x9a\xb9\xf6\x10\x00\xae\x81\x8b\x04\xc8\xf4\x32\x18\x13\ +\x3e\x18\x26\xd3\x28\x72\xf8\x0f\x6a\x27\x57\x53\xb1\x0e\xa3\x19\ +\xcc\xd6\xc1\xc5\x65\x0c\xb8\x70\xc8\x25\xab\x9d\x5d\x46\x5d\xe6\ +\x14\x70\x60\x2d\xac\x0e\xdf\x63\xfa\x82\x54\x22\xaa\x96\x94\x09\ +\x06\x02\xef\x02\xa7\x2f\xa1\xd6\x1f\x8c\x4f\x61\xf1\xdd\xe6\x33\ +\x68\xc7\xf1\xbd\x03\x00\xbf\xcb\x7c\xe7\x4e\xfe\xa5\x73\xae\x4d\ +\xab\xb9\x72\xc1\x07\x23\xe9\x7c\xe5\xac\x6d\x3e\xe2\x90\xe8\x3a\ +\xad\xc7\x34\x0c\xa6\x74\xe0\xf1\x32\x00\xbc\xbb\xff\xd2\xb9\x5d\ +\x83\xc2\x82\x8a\xd4\x09\x72\xfc\xee\x75\x00\x38\x3b\xe4\x91\x7d\ +\x94\x50\xb2\xb5\x00\xa7\xd6\x03\xa3\xbd\x29\x46\x31\x4c\x90\x69\ +\xb7\xee\xdc\xce\xd0\x87\xb0\x77\xe8\xae\xe5\xfe\xe1\x5a\xea\x37\ +\x98\xc5\xc3\x91\xb7\x05\x0b\x2e\x35\x80\xdb\x82\x0c\xf7\xc1\x64\ +\x17\x48\x08\x97\xef\x03\x3c\x7e\x31\x3d\xa7\x5e\x82\x18\xbf\xcd\ +\x98\x08\xa1\x05\x60\x92\x83\x9e\x8a\x79\x41\x98\x3e\x92\xec\x25\ +\xf5\xd6\xa2\x9e\x46\xc7\x9e\x7c\xa6\xa4\x84\xac\x2c\xa0\xda\x2e\ +\x52\x3a\x50\x6e\xff\x72\x79\xfb\x40\x66\x1e\x1e\xc8\xcb\x87\x01\ +\xe0\x71\xaa\x49\x09\xb6\x10\x6b\x43\x1b\x85\x53\x6c\x70\xe8\x7b\ +\xc4\x2e\x12\xf5\x79\x66\x00\xe6\xc2\x44\x0b\xe1\xdd\x02\x2b\x7e\ +\x0f\x62\x7c\x7f\xa5\x84\x98\xa3\xbe\xcd\x83\x8e\x00\x18\x0e\xaa\ +\x3b\x0a\x60\xb0\xa6\x0f\x35\x56\xa4\xa0\xa3\x64\xbb\x5b\x22\x70\ +\x4b\x6d\xcb\xb0\x97\xd0\x0a\x30\xa3\xc0\x05\xab\x73\x07\x18\x1e\ +\x80\x29\x6f\x1c\xc4\x78\x2d\x33\x69\x59\x01\x66\x3d\x13\x6b\x49\ +\xc2\xa5\x13\x32\xa7\x16\x0b\xd5\x49\xd7\x52\xc6\x84\x23\x52\x37\ +\xa1\xb7\x58\xf3\xa3\x35\xf8\x66\x4a\x4c\x29\x36\xf8\xeb\x82\xd6\ +\x98\x51\x99\x60\x8d\xed\xcc\x00\x30\x9b\x3c\x2a\x40\x02\x17\xc8\ +\xe7\x93\xd5\x02\x11\x6e\xbf\x0b\xa6\x1f\x5c\x67\x8c\xb0\x29\x1f\ +\x28\xe0\x52\xf3\x29\x49\x9c\x6f\xd2\x00\x17\x6b\x3e\x19\x8e\xe7\ +\x5b\x06\x98\x3b\xb5\x6b\xdc\x09\x83\xde\x42\x2e\xa4\x2d\x84\x78\ +\x73\x31\x17\x0e\x00\x3e\x86\xf7\x53\x9f\xb3\x02\xc9\xe6\x33\x48\ +\xdc\x0b\xb9\x77\x37\x4a\xa2\x92\xe4\x30\x90\xb2\xec\xe4\x85\x52\ +\x74\x78\x8c\xdf\xa6\x28\x77\xa9\x4f\x25\xd3\xe8\x78\xa5\x96\x4f\ +\x36\xe7\x77\x91\x66\x38\x94\x2d\x5e\x4d\x2a\x8c\x5c\xb8\x11\xcb\ +\x58\x18\x4c\xb3\x6a\x60\xe6\xe8\xb4\x99\xad\xf7\xd2\x32\x91\x38\ +\xfa\x62\x97\xc1\xec\x77\x89\x5e\x02\x80\xf7\xf6\x4e\x97\xfd\x1f\ +\x3f\x04\x78\x71\x05\x2e\xdd\x4a\x58\xde\xe9\x93\x18\xf7\xc9\xba\ +\x17\x1f\xac\x13\x54\xf0\x8d\xa3\x92\x24\x5d\xfa\x90\xe4\x42\xe5\ +\xa1\x14\xb7\xb3\xfe\x74\x9a\x66\x4f\x18\x8d\xd6\xd1\x12\xee\x7a\ +\x58\x33\x32\x19\x13\x47\x05\x5a\x7c\x0a\xeb\xef\x60\xd9\x4b\x0e\ +\x14\xb0\x9e\x7b\xec\x24\x60\x66\x03\xaa\x55\x18\x14\x98\x8c\xda\ +\x42\xea\x0d\x12\xd4\x66\x2f\xd2\x6d\xd5\x7a\x9e\x39\x06\xd3\x72\ +\x26\x69\xf1\x9d\xe6\xc0\x3a\xad\xbc\x2e\x82\x8b\x45\xc5\x34\xd2\ +\x70\x32\x8e\x48\x5f\xd0\xca\x88\xb8\x0e\x1e\x72\xf7\x61\x56\x7f\ +\x9f\xb9\x8c\x76\x25\x90\x29\xd9\xf1\x50\x70\xe8\x71\x80\x0b\x67\ +\xfe\x8f\x4e\xd0\x15\xf7\xbb\x68\xe4\xf9\xad\x95\xfc\xe5\xaa\x64\ +\xa9\xf4\x52\x54\x69\xeb\xe8\x9c\xcf\xd3\x02\x4c\x0e\x64\x32\xd9\ +\xc9\x58\x4d\x3d\xa9\x9a\x42\xad\x35\xb3\x2d\x2c\x8a\x5a\x89\x0c\ +\x6e\x90\xa1\xfa\xaf\x96\x07\x4d\xd7\x1f\x69\xf3\xc0\x22\x08\x48\ +\xcd\x27\xce\xfb\x9a\x32\x91\x72\xf1\x0b\x95\x4c\x76\x81\xaa\x2c\ +\xea\xf6\x21\xc6\x94\xe8\x65\x33\x12\xec\x85\x52\xc7\x48\x72\x2b\ +\x37\xa7\xa3\xd6\xe7\xb7\x82\xfc\x1a\xa4\x38\xe6\xc5\x16\x62\x55\ +\x36\x6f\x22\xad\x41\x06\xeb\x63\xe0\x0c\x08\xc3\xfc\x4e\x1a\x64\ +\x72\xce\x6f\xcd\x4a\x08\x9a\x20\x93\x62\x7c\xb5\x1a\xda\xd8\x03\ +\x99\xa5\x67\x62\xd9\x90\x76\xea\xcb\x96\xc8\x6b\x6e\xf3\x68\xb3\ +\x85\xd7\x8e\x40\xb3\xfc\xc0\xd3\xad\xc6\x50\xf0\xc5\x34\xbd\xd9\ +\x24\x6c\xd4\x16\x7a\x6f\xd5\x64\xb2\x00\x32\x39\x30\xae\x45\x1c\ +\xa7\x16\x43\xad\x8c\x48\xcd\x2f\x67\x0d\x5c\xb4\xc6\x85\x7b\x7c\ +\xa7\x39\x2a\xc0\x95\xda\x50\x62\x22\x60\x06\xba\x95\xcd\xa4\xd8\ +\x0b\xa7\xbf\xa8\x37\xba\x54\x92\xfd\xd5\x74\xc4\x95\x91\x5f\x21\ +\xde\x47\x1c\x58\xb4\x8e\x16\x4c\x9d\xd1\x8e\xba\x5e\x32\x7e\x0a\ +\xf3\x6f\xf9\xd4\x44\x5f\xfe\x86\xeb\xac\x11\x17\xb8\x48\x4d\xe6\ +\x1a\xc8\xd4\xda\x49\x71\xf0\xb6\x32\xa4\x99\xfc\x1e\x5c\xe3\x22\ +\x11\xb6\x31\x05\xc0\x58\x66\x2f\x3d\x20\xd3\xf3\x46\xa5\x7e\x97\ +\x33\xba\x54\x1a\x64\x24\x19\x67\xcd\xb7\x63\xcd\x34\xc2\xbc\xb4\ +\x7a\x0b\x09\x9e\xa4\x89\x94\xf3\xd3\xb4\xb2\x18\xad\x89\x80\x1d\ +\xe8\x04\xc8\xb0\xb2\x17\xe9\x60\x42\x69\x90\x69\x5d\xec\x94\x7e\ +\xf6\xa6\xa2\xb0\x16\x9b\xc3\xe5\xe3\xe3\xec\x97\x79\x80\xc1\xb2\ +\x11\x0b\xf4\x95\x02\x32\x87\xdf\xa8\x67\x5c\x1b\x05\x32\x95\x7c\ +\x2e\x6a\x0b\x9a\x3a\x8f\x46\x8d\x43\x8b\xd9\x88\x5d\x0f\x92\xbb\ +\x53\xd3\xe7\x83\x19\x5d\xd7\x5a\xf2\x2d\xcf\x10\x01\xac\xb6\x38\ +\x35\x7c\x32\xd4\x05\xc4\x01\x32\x33\x1d\x34\xc4\xe8\x48\xba\x5f\ +\xa6\x01\x86\xcb\x97\x62\xe1\x6d\x53\x1b\xc8\x5c\x40\x1d\x75\x72\ +\x68\x55\x37\xe8\x35\x09\xb1\xfe\x12\x6d\x90\xb1\x60\x1a\x51\xfa\ +\x47\x39\x57\x26\x51\x22\x78\x13\x19\xed\x4a\x95\x05\x66\x60\x32\ +\x6b\xbf\x4b\x6f\xcc\x8c\x76\xe9\x14\x2a\x93\x69\xf1\xb7\x60\xa2\ +\x74\x7b\xfb\xdb\x72\xe2\x7a\xb6\x43\x87\xad\xfe\x2d\xa9\x2d\x7c\ +\xb3\x00\x33\x3b\x7b\xe1\x58\x80\x94\x10\xfa\x51\x75\x99\x7a\xcc\ +\x25\xce\xa0\xc3\xde\xfb\x94\xb6\xbe\x47\xcc\x25\xee\xf0\x82\x3f\ +\x1c\x4b\xf2\x84\x50\x4c\x19\x79\x12\x26\x12\x72\x10\xcc\xb2\x98\ +\xd2\x02\xac\xed\x1a\x51\xed\x69\xcb\x91\xa5\x2d\xf1\x2f\xd4\x63\ +\x18\x1a\x26\xd3\xa4\x2c\x1f\xee\x00\xc0\xaf\x0e\xff\xff\xe3\xc3\ +\xdf\x77\x00\xd8\xce\x65\x25\xe7\xa4\x45\x45\x76\xd4\x98\x8e\x93\ +\xf5\x0d\xb5\x25\x8d\x28\x0b\x32\x74\x81\x60\xeb\x2e\xb5\x8e\x6f\ +\xcd\x4f\xd2\x52\x8d\x53\x9b\x41\x73\xe9\x92\xe5\xb9\x39\xe0\x2d\ +\x27\x50\x3f\x61\x06\x73\x51\x61\xa6\x59\x8c\x84\x8f\x20\x65\x62\ +\x8c\x06\xd6\x1a\xfb\xc0\x44\xed\x62\x99\x8c\x74\x76\x3c\xcd\xdd\ +\x24\xee\x67\x3d\x1a\x30\x17\x2c\x66\xb4\x6b\x5b\x5c\x8b\x44\xdd\ +\xb3\x51\xda\x75\xcc\x4b\x4f\x26\x7d\xab\xd4\xbe\x60\x12\x8a\xfb\ +\x77\x18\x52\x64\x14\xc1\x6f\xb6\x4c\xff\xc7\xf6\xfe\x71\x80\x99\ +\x68\xce\x8c\x68\x02\x98\x15\xb8\xa4\xbe\x4e\x59\xbc\x8a\x7d\x2c\ +\x16\x61\xa7\xea\x6a\x26\x73\x97\x32\x99\x39\xaa\x4d\x62\xaa\x2b\ +\xb6\x80\x9b\x46\xd1\x36\xce\x3c\xc1\x4b\x3f\x8c\xb4\x79\x04\x00\ +\x70\x69\x0b\xe0\xc2\x91\xb7\xc2\x42\xdf\x39\x4e\x4b\x5b\x0c\x5f\ +\xaf\x95\xdf\xa0\xdc\xab\xb5\xaf\xeb\xe7\x3f\xf5\xd5\x24\x35\x58\ +\x04\x8f\x54\xbb\x35\xab\x2c\xf6\xae\xa5\x11\x6d\x9c\xce\x07\x13\ +\xda\x26\xa2\xc9\xe8\xde\x75\x7b\x8e\x31\x2f\x9c\x36\xb7\xe5\xd4\ +\x9c\xa5\x45\x49\x61\x3a\x54\x76\x14\xc2\x8f\xba\x18\x95\xd4\xce\ +\x15\x17\x8b\xa0\x82\xcb\xa6\x4f\x53\xb7\xb0\x97\x9c\xe2\x32\xff\ +\x6e\x16\x64\x38\x16\x4d\x43\x64\xb0\x09\xbb\x5f\xa2\x7d\x54\x90\ +\x79\xfa\xbb\x5f\x17\x5f\x5d\xad\xcc\xc8\x9a\x6f\x26\xf5\xfc\xd1\ +\x15\x48\x2f\xc1\x24\x62\x85\xf2\x71\xb2\x17\x2a\xfd\xaf\xd5\x77\ +\x1a\x6d\x32\xb5\x26\x90\x5a\x6f\x2d\x63\xda\xdb\xf7\xbb\xfb\x05\ +\x5e\xfc\xef\x6e\x13\xb0\x87\x09\x60\x7f\xcb\x95\x73\x5a\x6a\x17\ +\xd2\x4a\xe1\xb5\xa2\x82\x1a\xca\x82\x96\x26\xb3\x09\x87\x6f\x6b\ +\x3b\x6a\x03\x4d\x3d\xf9\x3a\x2a\x6f\x09\xf6\xb4\xb2\xf4\x82\xdc\ +\xff\x26\x07\x2e\xc7\xad\x6e\xde\x79\xdc\xda\xbe\x5e\xdd\x71\x99\ +\x55\x9c\x73\xc5\x3c\x83\xb1\x12\xf0\xa4\x69\x2e\x9d\x7b\x33\x2f\ +\x66\x7c\x2d\xe7\xec\xfa\x7a\xca\xb9\x39\x1a\x58\x6a\xa6\x2d\xb6\ +\x9d\x54\x26\x33\xc2\xd4\xc5\xe8\x48\xfa\x10\xa9\x06\xb8\x98\xf0\ +\xc1\xb4\xb2\x13\x6a\x71\x34\x0b\xbe\x18\x14\x8b\x3a\x82\x0a\x00\ +\x5c\x07\x80\xb3\xc3\xbf\x9d\x85\x00\xd7\x2b\xba\x58\x4f\x4a\xad\ +\xda\xc6\xc9\x85\x73\x38\xf3\x72\xe1\xc3\xf0\x86\xe6\xf0\xc9\xe4\ +\xd8\x0b\x27\xd8\xd4\xfc\x1f\xa5\x4a\x93\x2d\x35\xb9\x39\xea\x42\ +\x49\xfb\x8c\x86\x9b\x48\x59\x80\x81\xfc\x8e\x51\xce\x74\x2a\x4c\ +\x58\x59\x33\x29\xd5\x87\xa7\x6d\xc4\x3d\xbb\x32\xe0\x37\x00\xe0\ +\x9f\x48\xff\x0c\x85\x61\x74\xf9\xc5\x6a\x93\xb6\x01\xf8\x24\x69\ +\x7e\xed\xe8\x9a\xf4\x92\xb0\x90\x69\x4e\x83\xbd\x0c\x07\x98\x1e\ +\x70\xc1\x2e\x1a\x31\x90\x29\x4d\x96\x3d\x0b\x63\x05\x98\x96\x15\ +\xa0\x01\x32\xc5\x7b\x21\x01\x86\x0b\x64\x30\xe7\x96\x46\x81\xcb\ +\x88\x45\x3e\xf2\xb9\x26\xe3\x60\x5a\x8e\x00\x4c\x12\xc5\x8a\x3c\ +\xcc\xa8\x3b\x89\x2c\x15\x7a\xe7\x88\x2d\xa1\x6d\x5f\x9f\xff\x58\ +\x79\xc9\x6e\xe5\xb9\xbb\x81\x8b\xaf\x69\xc2\x70\xa1\xac\xb6\x2f\ +\x26\xa0\x4f\x4a\x1f\x7f\x57\x6e\xda\x19\x61\xb1\xb6\x54\x50\x6c\ +\x1d\xb3\xda\x39\x1d\x6e\x90\xe9\xb9\x47\xcd\xd1\x6a\xcd\x3d\x60\ +\x35\xcf\xef\xe6\x18\x0c\x8e\xe2\x6e\x6f\x47\x29\x40\x84\x1b\x99\ +\x6b\xb7\x81\x67\x57\x01\xcb\x66\xb4\x74\xbe\xbc\xef\x4f\x01\xe0\ +\x3f\x05\x27\x31\x16\x64\x2c\x81\x8b\xe6\x22\x1f\x09\xb2\x43\x00\ +\xa6\x75\x1f\x9f\x5b\x11\x5a\x2c\x26\x74\x1f\xb6\x0c\xf0\x39\x40\ +\x12\x64\x3e\x47\x32\x40\x8c\x8f\xa3\x90\xd2\xf3\xfc\xef\xa4\xec\ +\xb9\x45\xbb\xae\x03\xc0\xdf\x00\xe0\x27\x44\x3b\xf2\xd8\xb7\x37\ +\x0e\xbf\xf9\x22\x84\xfd\xdf\x46\xc0\xc5\x82\xdf\x45\xe5\x79\x16\ +\x22\x3d\x5b\x16\x45\xcb\xbf\x4b\xf8\x45\x1a\x6f\xda\x0d\x2e\x98\ +\xe0\x2f\x4c\xff\xa9\xc1\x5b\xc9\x20\xc6\x94\x03\x03\xeb\x50\x6a\ +\x1d\xaf\x8a\xf3\x9c\x7a\x8f\x37\x01\xe0\xa3\x13\x35\x8d\x46\x3c\ +\x37\x58\xca\x90\x8e\x05\x17\xaa\x92\x24\xb7\xad\xa9\xf7\x7e\xda\ +\xad\xf4\x0e\xc7\xc5\x74\x38\xf2\x20\x53\x04\x17\xcc\x42\xef\x04\ +\x6d\x76\x90\x32\xb4\x73\xba\x65\x70\x31\xe5\x83\xe1\x62\x28\x83\ +\xdf\x4c\xdd\xa6\x51\x65\x49\x37\x9b\x3f\x14\x53\xc8\x52\xb2\xeb\ +\x9c\x9c\xc1\xbc\x72\x6a\xe0\xa2\xce\x60\x5a\x27\xb6\x52\x1c\x07\ +\x77\x89\xd6\xae\xfb\x95\x62\x33\x4a\x2f\x5d\x09\x26\x73\xfc\x6e\ +\x35\xb6\x65\x00\x83\xb9\x01\x7b\x3f\x54\xd3\xd8\x1b\x62\x30\xa7\ +\x08\x2e\xa6\x18\x8c\x36\x7b\xb1\x94\xce\x81\x0a\x2e\x52\x4c\xe6\ +\xc9\x77\xd7\xbb\x37\x9a\x41\x23\x89\xfb\x1f\xc1\xa5\xa5\xaf\x59\ +\xc5\x39\xb8\xe8\xf6\x7d\x54\x67\xb1\xec\x45\x02\x7c\xb8\x58\x0c\ +\x27\x7b\xe1\xc8\x82\xcf\xc2\x64\xb8\xdf\xfe\xeb\xfb\x31\x65\xfb\ +\xb7\x6e\x42\x9f\x32\xb8\x98\x63\x30\x1a\x95\xe6\xb8\x59\x8c\x05\ +\x70\x91\x64\x32\x62\xe6\x4e\xe9\x99\x85\x43\x92\xea\x6d\x77\x70\ +\xb1\x0f\x30\xd8\x6c\x75\x5b\x4d\xc5\x20\x6d\x0e\x9a\x06\x99\x4e\ +\xdf\xcb\x6c\x20\xe3\xe0\x62\xd4\x07\xa3\xad\xb4\x1e\x16\xd3\xcc\ +\x5e\x12\x6f\x64\x29\x40\xdd\x04\xc8\x20\xfb\x79\x6c\xbb\x95\xf6\ +\x0f\x89\x37\xc9\xa4\xcb\x1c\x2d\x3b\x6d\x65\x6f\x85\xbd\xf4\x98\ +\x46\xd2\xf5\x9c\x7a\x41\x26\x84\x60\xc6\x41\x8a\x69\xfb\x68\x90\ +\x1c\xb5\xc8\x2d\xa7\x93\xdd\x09\xf7\x5c\x65\x52\x59\xf0\xc5\x58\ +\x01\x17\x0e\x90\x79\xf2\x1b\xae\x5d\x23\x41\xb0\xb2\x92\x7c\x7b\ +\xc4\x22\xb7\x98\xe4\x5b\x15\x60\xb0\x39\x5d\xac\xbd\x91\x24\xd8\ +\x8b\xa6\x2f\x23\x22\xf4\x98\x5b\xa8\x8c\x88\x27\xba\xc5\x3d\x72\ +\xee\x8c\x58\xe4\xb3\x24\xc1\x0f\x1a\x95\xe9\x72\x8a\xa0\x14\x49\ +\x17\xce\x2b\x5b\x05\x0e\x12\xb8\x1c\x73\xc6\x6a\x4d\x86\x75\xfa\ +\x45\xe4\x73\x87\x4f\xde\xce\xa0\x3e\xad\xf6\x8f\x62\x2d\x33\x00\ +\xcb\x51\x2e\x8d\x02\x17\xea\x9b\x61\x4a\x61\xcc\x6b\x43\x7a\xfc\ +\x02\x64\x5a\x12\x63\x97\x4a\x71\xa8\xb4\xbd\xf3\x19\xd2\xed\x1f\ +\x91\x88\x7e\xd6\xe4\xf7\x3b\x01\x4d\x9c\xfb\xdf\xeb\x0d\x0a\x33\ +\x54\xb0\xbd\xe8\x8b\xa1\x1f\x64\x1c\xdf\x57\x8c\xb9\x54\xa2\xf9\ +\xb3\xec\x34\x95\xda\xdf\xd3\x87\x1c\x83\x90\xb4\x04\xb4\x9f\x69\ +\x97\xc1\xac\xea\x46\xaf\x2b\xbc\x73\x05\x92\xcd\xe4\x77\xb1\x04\ +\xa4\x58\x26\x93\x33\x67\xad\xd6\x63\x6e\x6d\x3f\x65\x5c\x46\x15\ +\xbe\x9f\x8d\xb1\xc8\xf9\x60\x0a\xe0\xb2\x06\x18\x6b\xbe\x97\x16\ +\x30\xa1\x00\xcc\x30\x70\xa9\x27\x22\x67\xab\x79\x3c\xdb\xa4\x97\ +\x3e\x79\xae\xd5\xae\x59\x84\x8d\xc1\xd4\x12\x0d\xcc\x1c\x9d\x3b\ +\xc5\xae\xd1\xda\x87\x51\x70\x94\x52\xc7\xa2\xc6\x06\x66\x62\x34\ +\xb9\x45\xad\xb9\xd0\xb7\x0a\x2a\xec\x0c\xe6\xdc\x9b\xba\x34\xe9\ +\x27\x60\x2f\x25\x1f\xcc\xac\xa6\xd1\x08\x36\x30\x61\xdd\x70\x31\ +\x73\x5d\xf2\xde\x27\xc1\x60\x8e\x5e\xfb\x58\x65\xed\xa7\x71\xde\ +\xe8\x54\xce\x55\x95\x6a\x87\xcf\xc4\x6a\xb0\x4e\x5f\x6e\x47\xf7\ +\x96\xe7\xc8\x8e\x53\x49\x25\x88\x89\x10\xe0\x5f\x00\x4f\x12\x2f\ +\xb7\x0e\xde\xc0\x52\x0e\x81\xca\x5e\x62\xa7\x29\x32\x23\xd0\xd4\ +\x76\x6d\xac\x9d\x15\x2a\xb5\x69\xd9\x1f\x8e\xf2\xac\xd2\xf7\xdd\ +\xbc\x89\x74\x9e\xa5\x5c\x04\x96\x92\xc9\x64\x15\x60\x96\xa6\xd2\ +\x14\x7e\x97\x49\x59\xc1\xa8\xb3\x3a\xa7\xc6\x24\xa6\x36\x91\xa4\ +\x29\xf8\xe0\x36\x38\xb8\x30\x8c\x5d\x6b\x49\x94\xde\x71\x3f\x65\ +\x7f\x87\x03\x8c\xb2\x6d\xeb\x32\x1f\xe0\x48\xce\x03\x07\x14\x67\ +\x30\x2e\x27\x0a\x38\xdc\xc0\xe2\x60\x62\x84\x34\x48\xfb\x60\x00\ +\x12\x7e\x98\x86\xc9\xe5\x13\xc4\xc5\xc5\x19\xcc\x05\xdc\x48\xa6\ +\x5f\x85\x98\x3f\xbf\xf6\xe8\x11\xc0\x07\x1f\x40\x04\x80\xaf\xae\ +\x5c\x81\xbf\x5e\xbf\x0e\xbf\x77\x70\x71\x71\x71\x06\xd3\x2d\x0f\ +\x1f\x02\x5c\xbb\x96\xbe\xf6\xe0\x01\xc0\xd5\xab\x3e\x42\x2e\x2e\ +\x13\xcb\xd8\x9c\xbc\xef\xbc\x43\xbb\xe6\xe2\xe2\xe2\x0c\xa6\x2a\ +\x57\xae\x00\x7c\xf3\x4d\xfa\xda\x73\xcf\x01\x7c\xfd\xb5\x8f\x90\ +\x8b\x8b\x33\x18\xa2\xfc\xe2\x17\xb4\x6b\x2e\x2e\x2e\xce\x60\xaa\ +\xe2\x3e\x18\x17\x17\x67\x30\x62\x72\xf5\x2a\x7c\x76\xf7\x2e\x7c\ +\x7a\xf3\x26\x00\x00\x7c\xf5\xfc\xf3\x00\xb7\x6f\x03\x7c\xf9\xa5\ +\x83\x8b\x8b\x8b\x33\x18\x17\x17\x17\x17\xab\x0c\xc6\xc5\xc5\xc5\ +\x01\xc6\xc5\xc5\xc5\xc5\x01\xc6\xc5\xc5\xc5\x01\xc6\xc5\xc5\xc5\ +\x01\xc6\xc5\xc5\xc5\xc5\x01\xc6\xc5\xc5\xc5\xa6\xfc\x1f\x10\x8b\ +\x37\x21\x70\x93\x2c\x3e\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\ +\x60\x82\ +\x00\x00\x0c\x27\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x01\x1a\x00\x00\x00\x7a\x08\x02\x00\x00\x00\xef\x52\x0b\x03\ +\x00\x00\x00\x15\x74\x45\x58\x74\x43\x72\x65\x61\x74\x69\x6f\x6e\ +\x20\x54\x69\x6d\x65\x00\x07\xe2\x05\x02\x09\x27\x0a\x15\x71\xd4\ +\x56\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xe2\x05\x02\x09\x28\x0f\ +\x37\x82\xdf\x66\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0e\xc3\ +\x00\x00\x0e\xc3\x01\xc7\x6f\xa8\x64\x00\x00\x0b\xa5\x49\x44\x41\ +\x54\x78\xda\xed\xdd\x7b\x4c\x53\x59\x1e\x07\xf0\xcb\xfb\x8d\x8a\ +\x95\x20\xe0\x6b\x60\x70\x20\xf8\x88\x82\x3a\x92\x51\x7c\xa3\x18\ +\x5c\xd1\x8d\xc4\xf8\x08\xae\x18\x54\xd8\x3f\xfc\x63\xfe\x98\xcd\ +\x64\x62\x36\xbb\xd9\x3f\x36\x26\x3b\xf8\x4a\x70\x1c\xc5\x17\x18\ +\x59\xdf\x23\x8b\xc1\xb0\x8a\x1a\x15\xd6\x2c\x12\x1c\x0a\x06\x57\ +\x06\xc6\x41\x44\x84\x01\x29\x30\x65\x4f\xfb\x2b\x97\xf2\x68\x29\ +\x72\xda\xd3\xc7\xf7\x93\xa6\xf9\xf5\xb6\xde\x39\x87\xe1\xcb\x3d\ +\xe7\x9e\xb6\xd7\x29\x2b\x2b\x4b\x02\x00\x1e\x9c\x45\x37\x00\xc0\ +\x7e\x20\x4e\x00\xdc\x20\x4e\x00\xdc\x20\x4e\x00\xdc\x20\x4e\x00\ +\xdc\x20\x4e\x00\xdc\x20\x4e\x00\xdc\x20\x4e\x00\xdc\x20\x4e\x00\ +\xdc\x20\x4e\x00\xdc\x20\x4e\x00\xdc\x20\x4e\x00\xdc\x20\x4e\x00\ +\xdc\x20\x4e\x00\xdc\x20\x4e\x00\xdc\x20\x4e\x00\xdc\x20\x4e\x00\ +\xdc\x20\x4e\x00\xdc\x20\x4e\x00\xdc\x20\x4e\x00\xdc\x20\x4e\x00\ +\xdc\x20\x4e\x60\x0f\x42\xab\xab\x43\x95\x4a\xd1\xad\x40\x9c\xc0\ +\x2e\x04\xd7\xd4\xb0\x9b\xe8\x56\x20\x4e\x00\xfc\x20\x4e\x00\xdc\ +\x20\x4e\x00\xdc\x20\x4e\x00\xdc\x20\x4e\x00\xdc\x20\x4e\x00\xdc\ +\x20\x4e\x00\xdc\x20\x4e\x00\xdc\x98\x31\x4e\x21\x56\xb0\xac\x06\ +\x60\x49\x66\x8c\x53\x7d\x78\xb8\xe8\xde\x01\x58\x94\x2b\x97\xbd\ +\x7c\xfd\xd5\x0a\x43\x4f\xfd\xf9\xaf\x45\xa2\xfb\x08\x60\x21\x98\ +\x3b\x01\x70\x83\x38\x01\x70\xc3\x39\x4e\xf3\xe7\x7b\xad\x49\xf0\ +\x15\xdd\x29\x00\x31\xf8\xcc\x9d\x64\x65\x65\x1f\x86\x6e\xdc\xb3\ +\xe7\xcb\x8e\x0e\xdf\x0f\x1f\xd8\xcd\x4f\x7b\xa3\xc2\x57\x6f\xa3\ +\x6f\x6f\x2f\x8e\x93\x60\xf3\x38\xc7\x69\x58\x27\x4f\xfe\xc5\xcb\ +\xab\xcd\xcb\xeb\x57\x76\xef\xed\xcd\xee\x7f\xf5\xf5\x7d\x37\x69\ +\x52\x9d\xbc\x91\xdd\x77\x77\x7b\xea\xa7\x4b\x0e\x1e\xdb\xd8\xd9\ +\xe9\xc7\xee\x55\x2a\x6f\xd1\x3f\x2b\x80\x11\x8c\x35\x4e\x13\x27\ +\xfe\xfc\xf6\xed\x64\xf9\xe1\xe9\x33\xa1\xf9\x97\xde\x5f\xbb\xda\ +\xa6\xff\x9a\x9e\x1e\xb7\xb6\xb6\x00\x76\x33\xb2\x1f\x0f\x8f\x0f\ +\xfa\xe9\x62\xa9\x0b\x08\xf8\x59\x7e\xc8\xee\x3d\x3c\x3a\xb5\x79\ +\x93\xc3\xe6\x23\xd7\x72\xe4\x7a\x7a\xdc\x45\xff\x3c\xc1\xa1\x8d\ +\x29\x4e\x5f\x7c\xf1\xcf\xe0\xe0\x9a\xbc\xbc\x2f\xe5\x2d\x3b\xb7\ +\xff\xf4\x71\xbb\x52\xa9\xbc\xd8\xad\xa5\x25\xd0\xd0\x0b\x9c\x9c\ +\x7a\xb5\xd1\xd2\x85\xcd\xd3\x53\x93\x31\x7f\xff\x26\xed\xe1\x4e\ +\x17\x39\x27\x27\xc9\xc8\xa8\x92\x22\x87\x51\x25\x98\xcf\x28\xe2\ +\x14\x52\x53\x23\xaf\xcc\x46\x44\x94\xc6\xc7\xe7\x3e\x7c\xb8\xe1\ +\xde\xbd\x64\xcb\x34\xb4\xb7\xd7\xa9\xa3\xc3\x9f\xdd\x8c\x75\xc6\ +\xb5\xdb\xe8\xa8\x52\xb3\x1d\xa3\x4a\x30\x9f\xe1\xe3\xa4\x9f\x1c\ +\xb2\xe0\xd6\x2d\xda\x32\x6e\xdc\x9b\x65\xcb\xf2\xda\xda\xc6\x7f\ +\xf7\xdd\xdf\x7e\xfb\x4d\xf7\xcf\xe5\xb5\xda\xb4\xb4\xb4\x9c\x9c\ +\x1c\x95\x4a\x25\xa4\x33\x66\x18\x55\x0e\x08\x1e\x46\x95\x60\x9c\ +\x81\xa3\x53\x6f\x2f\xcb\xcf\xe3\xb5\x6b\xe9\x11\xab\xd9\x96\xfa\ +\x4f\x3f\xfd\xfc\xf3\x6b\x33\x66\x3c\x2b\x2e\x4e\x69\x68\x08\x1b\ +\xf6\xdf\x65\x67\x67\x8b\xee\xd1\x08\x46\x35\xaa\x94\x8f\x69\xa6\ +\x8f\x2a\x3b\x3b\x75\xc1\xc3\xa8\xd2\x01\x0d\x1f\x27\x96\x9c\x90\ +\xea\x6a\x4a\x14\x65\xa9\x29\x23\xe4\x0f\xcb\xbe\x2a\x2b\x5b\x79\ +\xfe\xfc\x9f\x44\xb7\xd9\xbc\xc6\x3c\xaa\x6c\xa7\x34\x9a\x38\xaa\ +\xc4\xfb\xb3\xec\x89\xc1\xb9\xd3\xe3\x75\xeb\x16\xfc\xf0\xc3\xc6\ +\x6f\xbf\x6d\x8c\x9c\x32\xe1\x1f\xbf\x4c\xe8\xfe\xe5\xec\xd9\xaf\ +\xd9\xdf\x75\x23\xfb\x52\x28\x14\x2b\x57\xae\xcc\xcd\xcd\x15\xdd\ +\x29\xb3\xe3\x35\xaa\x3c\x76\xec\x90\xe8\xae\x00\x4f\xc6\x4e\x45\ +\xb8\xb9\x77\x8d\x1f\xd7\xe8\xf3\xc9\xfb\x7f\xff\xf7\xf7\x75\x75\ +\x9f\x8d\xb8\xaf\xa6\xa6\x26\x47\xc8\x92\x89\x4c\x19\x55\x8e\xb8\ +\x13\x23\x2b\xe0\x18\x55\x5a\xa1\xe1\xe3\xe4\xe3\xd3\x92\x58\x9a\ +\x3d\x3e\xea\x4d\xc9\x1f\x7f\xe7\xfb\xf7\x96\xc9\xca\xda\xba\xb5\ +\x23\xc7\x09\x46\x85\x8d\x2a\x47\x7c\x8d\xd1\x15\xf0\xd1\x8d\x2a\ +\xc1\x02\x9c\xb2\xb2\xb2\xf4\x1f\xb3\xff\x5b\xb1\xb1\xff\x9a\xfe\ +\xbf\x67\x8d\x79\xd3\x6e\xc6\xee\xa1\x8d\x6c\xd4\xc7\xfe\x96\xca\ +\x67\x26\x0c\x99\x3b\x77\xae\xaf\xaf\x6f\x49\x49\x89\xe8\x4e\x39\ +\x96\xa1\xa3\x4a\xfd\x87\xfa\xe7\x2a\xd9\x01\x6d\x50\xf0\x6c\xfa\ +\x5c\xa5\x66\x56\xdf\x87\x4d\xf5\x25\xed\x9c\x5f\xde\x32\xe2\xaf\ +\xab\x39\xf4\xc7\xc9\xcf\xaf\x99\x05\x29\x24\x44\x59\x5a\x9a\xd0\ +\x7a\x2d\x40\xbf\x65\x92\x36\x51\xf5\x11\x11\xf8\x44\xa0\x2d\xd2\ +\x3b\x57\xd9\x6e\x28\x78\x86\xce\x55\xca\x4b\x05\xd6\x36\xaa\x64\ +\x59\x0a\x51\x2a\xe5\xdf\x52\xbf\xe6\x66\x76\xac\x6f\x0d\x08\xf0\ +\x6f\x6e\x66\xf7\xb4\xd2\xc3\xe6\xff\x96\xfe\x51\xb3\x38\xf9\xfb\ +\xbf\x8d\x89\x29\x08\x0e\x7e\xc1\x82\xf4\xe3\x8f\x0b\x0c\xbd\x94\ +\xfd\x01\x18\x94\x31\xb0\x1b\x43\xcf\x55\x0e\x5a\x2a\xb0\xb9\x51\ +\x25\x3b\x00\x08\x88\x53\x65\xe5\xc2\xa0\xa0\x5a\x16\xa4\xaa\xaa\ +\xd8\x31\xee\x6b\xfd\xfa\xf5\x15\x15\x15\x2f\x5f\xbe\xb4\x70\x1f\ +\xc0\x32\x4c\x1e\x55\xea\xd2\x65\xee\x51\x65\x46\x53\x93\xa1\xa7\ +\x0e\x2b\x14\x42\xe2\xe4\x5a\x57\xf7\x59\x51\xd1\x36\x2e\xfb\xba\ +\x71\xe3\x86\x85\x5b\x0f\x96\x64\xf2\x0a\xb8\xfe\xc1\xcd\xc4\x15\ +\xf0\x01\x6f\x68\xb6\xaa\x51\xe5\xa8\xb8\x2a\x95\x31\xa2\xdb\x00\ +\x76\xc2\xc8\x0a\x78\x68\x75\x75\x48\x55\x15\x2b\x9c\x9d\xd5\x6e\ +\x6e\x5d\xae\xae\x5d\xec\xde\xdb\xb5\x75\x9c\x5b\x13\xab\xe9\x61\ +\xdf\xbd\x4a\xad\x76\xed\xee\x76\x67\x37\x76\x28\x63\x37\x6d\xe1\ +\xa1\x2d\xdc\x58\xa1\x7d\xe8\xfa\x68\xfd\x7a\xe3\x8d\x09\x55\x2a\ +\x9d\xd4\x6a\x8e\xbd\xab\x9f\x39\xf3\xa7\x91\x26\x3b\x3c\x3f\xef\ +\x94\x9e\x9e\x7e\xe2\xc4\x89\x9e\x9e\x1e\x8e\xfb\x04\xfb\xa0\x76\ +\x72\x62\x31\xd2\x14\x92\xd4\xa3\xf6\x90\xba\x24\xcd\xcd\x00\x17\ +\x97\x6e\x37\x37\x95\x36\x5d\x2a\x16\x30\x37\xf7\x2e\x0f\xef\x76\ +\x5f\xb7\x77\x14\x36\x76\xef\xe2\xd2\xf3\x48\x1a\x21\x4e\x6a\x17\ +\x17\xfa\x2f\x72\xec\xc2\x88\xaf\xe1\x19\xa7\xe3\xc7\x8f\x73\xdc\ +\x1b\xd8\x93\x86\xf0\xf0\x06\x7e\xa7\x85\x35\x2b\xe0\x23\xad\x81\ +\x37\x84\x85\x3d\x59\xb3\xc6\xc2\xdd\xb4\xd5\x41\x2a\x38\xb2\xc1\ +\x2b\xe0\x89\x89\x52\xec\x58\x4f\xa4\x71\xc1\x2d\x4e\x41\x41\x41\ +\x9b\x37\x6f\x16\xdd\x1d\x70\x48\x37\x6f\x4a\x4f\x9e\x88\x6e\x84\ +\x06\xb7\x38\xbd\x7e\xfd\xfa\xd2\xa5\x4b\xa2\xbb\x03\xa0\xd3\x6b\ +\xc2\x54\x87\x3b\x0c\xf6\xc0\xf6\x65\x64\x48\x0a\xc5\xa0\x6d\x4f\ +\xc4\xbe\xc9\x68\x8c\xe6\xcf\x9f\xef\xee\xee\xfe\xf0\xe1\x43\xcb\ +\xf7\x01\xc0\x4a\x70\x3b\xb3\x57\x56\x56\x26\xba\x2f\xe0\xb8\xe8\ +\x43\xae\x96\x7f\x1b\xc4\x20\x18\xec\x81\x6d\xf3\xf1\xf1\x49\x4d\ +\x4d\x15\xdd\x0a\x1d\x6e\x71\xda\xb0\x61\xc3\x94\x29\x53\x44\x77\ +\x07\x1c\x4e\x7b\x7b\xfb\xf7\xdf\x7f\x2f\xba\x15\x3a\xdc\x06\x7b\ +\x57\xaf\x5e\x15\xdd\x17\x00\xc1\x30\xd8\x03\xdb\x36\x73\xe6\xcc\ +\x55\xab\x56\x89\x6e\x85\x0e\xb7\x38\xed\xdf\xbf\xdf\x49\xc4\x99\ +\x7e\x70\x70\x55\x55\x55\xb7\x6f\xdf\x16\xdd\x0a\x1d\x6e\x83\xbd\ +\x23\x47\x8e\x88\xee\x0b\x80\x60\x18\xec\x81\x6d\x5b\xb1\x62\x45\ +\x64\x64\xa4\xe8\x56\xe8\xf0\x89\x53\x48\x48\xc8\xc6\x8d\x1b\x45\ +\xf7\x05\x1c\x51\x51\x51\xd1\xf3\xe7\xcf\x45\xb7\x42\x87\x4f\x9c\ +\xea\xeb\xeb\x2f\x5f\xbe\x2c\xba\x2f\x00\x82\x61\xb0\x07\xb6\x6d\ +\xe7\xce\x9d\x7e\x7e\x7e\xa2\x5b\xa1\xc3\xe7\x54\xc4\xc2\x85\x0b\ +\xd5\x6a\xf5\x13\xeb\x78\x93\x3c\x38\x94\xd3\xa7\x4f\x8b\x6e\x42\ +\x3f\x3e\x71\x7a\xf4\xe8\x91\xe8\x8e\x00\x88\x87\xc1\x1e\xd8\x30\ +\x7f\x7f\xff\x1d\x3b\x76\x88\x6e\x45\x3f\x3e\x71\xda\xb4\x69\xd3\ +\xe4\xc9\x93\xc7\xbe\x1f\x80\x51\x69\x6d\x6d\xcd\xc9\xc9\x11\xdd\ +\x8a\x7e\x7c\x06\x7b\xf9\xf9\xf9\xa2\x3b\x02\x20\x1e\x06\x7b\x60\ +\xc3\xa2\xa2\xa2\x96\x2f\x5f\x2e\xba\x15\xfd\x38\x1c\x9d\x9c\x9d\ +\x9d\xd3\xd3\xd3\x8f\x1e\x3d\x2a\xba\x2f\x16\x92\x99\x99\x29\xba\ +\x09\x03\xf0\xfa\x3c\xb5\x2d\xaa\xd4\x12\xdd\x8a\x7e\x1c\xe2\xa4\ +\x56\xab\x1d\x27\x4b\xc4\x7a\x7e\x83\xad\x2d\xdb\x0e\x0e\x83\x3d\ +\xb0\x61\xab\x57\xaf\x8e\x88\x88\x10\xdd\x8a\x7e\x1c\xe2\x34\x75\ +\xea\xd4\xa4\xa4\x24\xd1\x1d\x01\x47\x54\x58\x58\xa8\x54\x2a\x45\ +\xb7\xa2\x1f\x87\x38\xbd\x7a\xf5\xea\xda\xb5\x6b\xac\x98\x37\x6f\ +\x1e\x6d\x09\x0c\x0c\x1c\xd3\x1e\x01\x6c\x13\xcf\xc1\x9e\xa7\xa7\ +\x27\x15\xf1\xf1\xf1\x54\xec\xd9\xa3\xbb\x1c\x68\x62\x62\x22\x15\ +\x73\xe6\xcc\xa1\x62\xe2\xc4\x89\xa2\xfb\x0e\x36\x6f\xd7\xae\x5d\ +\xde\xde\x56\x74\x8d\x36\x9e\x71\x7a\xf0\xe0\x01\x15\x17\x2f\x5e\ +\xa4\xe2\xd4\xa9\x53\x54\xc8\xa7\x5f\xfc\xfd\x75\x57\x2b\x61\xa3\ +\x5e\x2a\x76\xef\xde\x4d\x45\x42\x42\x02\x15\xd1\xd1\xd1\x54\x8c\ +\x1f\x3f\x5e\xf4\xcf\x07\xac\xda\xc9\x93\x27\x3b\x3a\x3a\x44\xb7\ +\xa2\x9f\x79\x4f\x45\x74\x75\xe9\xae\x3a\x52\x5b\x5b\x4b\xc5\xbd\ +\x7b\xf7\xa8\xb8\x70\xe1\x02\x15\x67\xcf\x9e\xa5\xa2\xa6\xa6\x86\ +\x0a\x45\xdf\x37\x7a\xae\xef\xbb\x86\x8f\xfc\xcd\x4f\xf2\xb7\x02\ +\xc8\x9f\x18\xb3\x9e\x77\x13\x03\xf0\xbc\x20\xcd\xc7\xe9\xec\xec\ +\xa4\x42\x8e\x53\x71\x71\x31\x15\x72\xd2\xe4\xc3\x1d\x9b\xa7\x51\ +\x11\x1c\x1c\x4c\x1f\x1a\x4b\x4e\x4e\xa6\xf7\x14\x6f\xdf\xbe\xfd\ +\xcc\x99\x33\xac\x58\xbe\x7c\xf9\x9d\x3b\x77\x58\x11\x11\x11\x41\ +\xf3\x54\x36\x1e\x30\xe7\xdf\xb0\xca\xdc\xcc\x63\xf7\x75\xf5\x8c\ +\x4d\xdf\x1c\x88\x57\x18\x78\x36\x6e\x6f\x56\x4a\xd4\x70\x5b\xf4\ +\x5e\xda\x40\x3b\x30\xbe\x4f\xd0\x98\x30\x61\xc2\xba\x75\xeb\xce\ +\x9d\x3b\x27\xd1\x35\xdb\x7b\x47\xba\x46\x8d\xf9\x89\x8f\x93\x29\ +\xda\xdb\xdb\xa9\xa8\xd2\x5e\xc1\x4e\xd2\x7e\x06\x93\x0a\xf9\xfd\ +\xf9\x74\x3a\x44\xd2\x5e\x7c\x80\x8a\xe9\xd3\xa7\x53\x9c\x52\x52\ +\x52\xd8\xa8\x80\x15\x5b\xb7\x6e\x3d\x7f\xfe\x3c\x2b\x96\x2c\x59\ +\x72\xf7\xee\x5d\x56\x84\x85\x85\xbd\x78\xf1\x42\xd2\x5c\xf8\xd5\ +\x43\xa5\x52\x7d\x6c\x03\x07\xc6\x42\x5f\x65\xb9\xb4\x37\x2b\x4b\ +\xf3\x5c\x53\xf1\xa1\x9c\xe2\xa6\xa8\xf8\xc6\x21\x5b\xb4\x51\x61\ +\x01\x2a\x90\xe2\x66\x98\xb2\x4f\xd0\x7a\xf7\xee\x1d\x65\x89\xa9\ +\xe7\x77\xf1\xa8\xb1\xb0\x9f\x75\xa7\xf7\xef\xdf\x53\x21\xcf\xd3\ +\x0a\x0b\x0b\xa9\xa0\x2c\x31\x05\x05\x05\x54\xb4\xb4\xb4\x50\x21\ +\xaf\x5a\xc8\x6f\x4c\xde\xb2\x65\x0b\x15\x71\x71\x71\x54\x4c\x9b\ +\x36\x8d\x0a\x17\x17\x97\x51\x37\x2b\x2a\xa5\x2f\x13\x8d\xaf\x6b\ +\x83\x03\x15\xc3\x6d\xd1\x06\xab\x20\xe8\x9b\x03\x2b\x83\x44\xff\ +\x14\x61\x4c\xec\x27\x4e\xa6\x68\x6e\x6e\xa6\xa2\xbc\xbc\x9c\x8a\ +\x5b\xb7\x6e\x51\x91\x9d\x9d\x4d\x05\x0d\x14\x19\x79\x7c\x38\x7b\ +\xf6\x6c\x2a\xe4\x13\x95\x43\xdc\x3f\x96\xa9\x71\xa8\x78\xd8\x6b\ +\x89\xb3\xb0\xb0\x27\xcb\x67\xf7\x1f\x6d\x06\x6c\xd1\x1c\xa4\xa4\ +\x1d\x43\xc6\x73\xc6\xf7\x09\xd2\xac\x59\xb3\x96\x2e\x5d\x2a\xba\ +\x15\x03\x70\xbb\x82\x86\xe3\x60\xbf\xe2\x06\x7e\x68\x9a\xf1\x5a\ +\x90\xc1\x79\xce\xd0\x67\x69\xcb\x0e\x29\xe7\x60\x7e\xad\xde\x0b\ +\x07\xcc\x95\x8c\xef\xd3\x48\x63\x40\x00\xdb\x98\x3b\xd9\x88\xc0\ +\xa0\x19\x46\x9e\x8d\x9a\x1d\x77\xac\xbc\x51\x92\x14\x83\xb6\x28\ +\x52\x0e\x64\xc5\xd3\x86\x61\x8e\x53\xc6\xf7\x09\xd6\xc5\xb1\x06\ +\x7b\x66\x51\x99\x9b\x4b\x93\xb5\xa6\x8a\xff\xd0\x5c\x48\x33\x94\ +\xeb\xdb\x56\x9c\xdb\x37\x58\xab\x2c\xbf\x3f\x23\x28\x70\x98\x2d\ +\x26\xed\x13\x86\x58\xbb\x76\x6d\x58\x58\x98\xe8\x56\x0c\x80\xa3\ +\xd3\x98\x05\x06\x35\x1c\xd4\xbd\xaf\x3b\x6e\x6f\xd6\xa0\x73\x71\ +\x8a\x40\x29\xff\x60\x66\x7e\xdf\xb3\x9a\xe3\xce\xd0\x2d\xa3\xdc\ +\xe7\x50\x09\x09\x09\x74\x96\x25\x3a\x3a\xba\xa2\xa2\x42\xd2\xae\ +\x80\xcb\xa7\x5b\xec\x95\x3c\xef\xb5\x1e\x98\x3b\x8d\x9a\x55\x4d\ +\x57\xa8\x31\xe1\xe1\xe1\xb4\x6a\x17\x1f\x1f\x4f\xab\x76\xdb\xb6\ +\x6d\xa3\x55\xbb\xd4\xd4\x54\xba\x5e\xcb\xaa\x55\xab\xe8\xbb\xbc\ +\x23\x23\x23\x69\xc9\xce\xcf\xcf\xaf\xad\xad\x4d\x74\x0f\xec\x0a\ +\x06\x7b\xf6\x60\xb4\x2b\xe0\x54\x24\x27\x27\x53\x21\x2f\x12\xc8\ +\x9f\x6c\x95\xd7\x0f\xac\xea\x1d\x71\x83\xa4\xa5\xa5\x79\x78\x78\ +\x88\x6e\xc5\x00\x88\x93\x9d\x33\x65\x05\xfc\xca\x95\x2b\x54\xe8\ +\xaf\x80\x53\x91\x92\x92\x42\xc5\xd6\xad\x5b\xa9\x90\xcf\x4d\xcb\ +\xf3\x16\x51\xbf\xd3\xd9\xd9\xd9\x63\x58\x79\x37\x0b\xc4\x09\x34\ +\xdf\x07\x44\x85\x29\x2b\xe0\xf2\xda\xdd\xa8\x56\xc0\x5d\x5d\x1d\ +\x62\x96\x8e\x38\x81\x49\xe4\x14\x3d\x7b\xf6\x8c\x8a\x51\xad\x80\ +\xcb\x9f\x1b\xd8\xbc\x79\x33\x15\x8b\x16\x2d\xa2\x22\x34\x34\xf4\ +\x23\xda\x33\x69\xd2\x24\x39\xbd\xd6\x03\x71\x02\x6e\xde\xbc\x79\ +\x43\xc5\xd3\xa7\x4f\xa9\xb8\x7e\xfd\x3a\x15\xc7\x8f\x1f\xa7\xa2\ +\xa4\xa4\x84\x8a\x9e\x9e\x1e\x2a\x62\x62\x62\xa8\xc8\xc8\xc8\xa0\ +\x42\xbe\x1a\x4b\x6c\x6c\x2c\x15\x43\xbf\xc5\x91\xfd\xb7\xf2\xf2\ +\xf2\x44\xf7\x78\x30\x87\x38\x04\x73\x87\x2f\x3c\xf9\x68\xf2\xf4\ +\xac\xb4\xb4\x94\x0a\x79\xe6\x76\xf8\xf0\x61\x2a\x1e\x3f\x7e\x4c\ +\x85\xb3\xb3\xee\xcf\xfd\xe2\xc5\x8b\xe9\xbb\x1c\xf7\xed\xdb\x47\ +\xdf\xf3\x93\x94\x94\x24\xbf\xe9\xd9\x7a\xe0\x44\x39\xd8\x12\x16\ +\x30\xb5\x5a\x2d\x69\xbf\xa1\x44\x3e\x57\x69\x3d\x30\xd8\x03\x5b\ +\x42\x59\x92\xf4\xce\xfb\x5b\x15\xc4\x09\x80\x1b\xc4\x09\x80\x1b\ +\xc4\x09\x80\x1b\xc4\x09\x80\x1b\xc4\x09\x80\x1b\xc4\x09\x80\x1b\ +\xc4\x09\x80\x1b\xc4\x09\x80\x1b\xc4\x09\x80\x1b\xc4\x09\x80\x1b\ +\xc4\x09\x80\x1b\xc4\x09\x80\x1b\xc4\x09\x80\x1b\xc4\x09\x80\x1b\ +\xc4\x09\x80\x1b\xc4\x09\x80\x1b\xc4\x09\x80\x1b\xc4\x09\x80\x9b\ +\xff\x03\x22\x1b\xf2\x8a\x03\x9b\x5a\x70\x00\x00\x00\x00\x49\x45\ +\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x0f\xd2\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x01\x18\x00\x00\x00\xa0\x08\x06\x00\x00\x00\xd7\x60\x73\x20\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ +\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ +\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x01\x1b\ +\x0d\x0c\x00\xfa\xf5\xf2\x8e\x00\x00\x00\x45\x69\x54\x58\x74\x43\ +\x6f\x6d\x6d\x65\x6e\x74\x00\x00\x00\x00\x00\x43\x52\x45\x41\x54\ +\x4f\x52\x3a\x20\x67\x64\x2d\x6a\x70\x65\x67\x20\x76\x31\x2e\x30\ +\x20\x28\x75\x73\x69\x6e\x67\x20\x49\x4a\x47\x20\x4a\x50\x45\x47\ +\x20\x76\x36\x32\x29\x2c\x20\x71\x75\x61\x6c\x69\x74\x79\x20\x3d\ +\x20\x39\x30\x0a\x67\x9d\x47\x40\x00\x00\x0f\x01\x49\x44\x41\x54\ +\x78\xda\xed\x9d\xcf\xab\x25\x47\x15\xc7\xbf\x75\x1d\x13\x9c\x99\ +\x04\x31\x1b\x61\x02\xae\xe4\x0d\x48\x40\x48\x56\xfe\x01\x4e\x36\ +\x42\x98\xc0\x64\x21\x2e\xfc\x1b\x94\x04\x06\xdc\x28\x64\xa5\x59\ +\x8b\xff\x41\x06\x66\x70\x39\xce\x6c\xb2\x72\x35\xb3\x10\x22\xe4\ +\x19\x04\x85\x64\x21\x26\xab\xc9\x3c\x48\x14\xcb\xc5\xbd\x37\xd3\ +\xaf\x5f\x75\x77\xfd\xae\x53\xa7\xbe\x07\x2e\xef\x47\xdf\x77\x5f\ +\x57\x9d\x53\x9f\xfa\x9e\xd3\xd5\xd5\xc6\x5a\x0b\x1a\x8d\x46\x2b\ +\x61\x3b\x76\x01\x8d\x46\x23\x60\x68\x34\x1a\x01\x43\xa3\xd1\x68\ +\x04\x0c\x8d\x46\x23\x60\x68\x34\x1a\x01\x43\xa3\xd1\x68\x04\x0c\ +\x8d\x46\x23\x60\x68\x34\x1a\x01\x43\xa3\xd1\x68\x04\x0c\x8d\x46\ +\x23\x60\x94\xd9\xc3\x87\xc0\xeb\xaf\x03\xc6\x00\x2f\xbe\xb8\xff\ +\xfe\xe1\x43\xf6\x0b\x6d\x18\x33\xbc\x17\xa9\x90\xdd\xbb\x07\xbc\ +\xf9\xa6\xfb\xd8\xdd\xbb\xc0\xcd\x9b\xec\xa3\x79\x30\x1a\x93\xed\ +\xb3\x18\xd7\x04\x8c\x6e\x7b\xed\x35\xe0\xf1\x63\xf7\xb1\x57\x5f\ +\x05\x1e\x3d\x22\x4c\x2a\x1b\x63\x9d\x80\xd1\x63\xcf\x3f\x0f\x7c\ +\xf5\x95\xfb\xd8\x73\xcf\x01\x5f\x7e\x49\xa0\x14\x00\x41\xad\xff\ +\x43\xf3\x33\xd6\x60\x4a\xd9\x2b\xaf\xc4\x1d\x53\x04\x94\xe9\x6b\ +\x6d\x90\xcf\x5f\xa9\x2a\xc5\xf7\xf3\x7c\xcf\x91\x46\xc0\xc8\xb3\ +\xdb\xb7\xe3\x8e\x29\x01\x8b\x2f\x00\x6a\xa5\x46\x21\xc0\xa1\x11\ +\x30\xf2\xed\xe6\x4d\xfc\x18\xc0\x9f\x0e\x3f\x3e\x01\x80\x1b\x37\ +\x80\x07\x0f\xd4\x15\x78\xd7\x54\x40\x0b\xa0\xa4\x02\x87\xaa\x26\ +\x63\x6c\x30\x0f\xad\x53\x77\xd0\xd8\xcf\x5b\x4a\x85\xed\xe9\xad\ +\xfd\x4b\x6d\x27\x60\xc4\x07\xab\xa6\x7e\x5e\x1a\x88\x6c\xa3\x3e\ +\xb8\xa4\x42\xe6\x12\x51\x40\xe3\xa0\x5b\x6e\xd3\xbc\xcd\xc7\x9f\ +\x39\x31\x13\x30\xf2\xd5\xcb\x7c\xc0\x0a\x0e\xda\xa5\xfa\x8a\x76\ +\x5b\x03\x0d\x21\x43\xc0\xf4\xa5\x49\x8d\x11\x07\x99\x51\xc1\xe2\ +\x03\x1a\x15\x6a\xe6\xd8\x86\xe3\x8f\xc8\xdb\x16\x02\x26\x63\xbe\ +\x6a\xad\x9e\xda\xcb\x68\x35\x88\x10\xd0\xa8\x49\x9b\x5c\x93\x07\ +\x4c\x56\xc8\xf0\x32\x75\x26\xb8\x6c\x1d\xeb\x1d\x2e\x92\x2e\x33\ +\x4b\x80\xcc\xd2\xe5\x6d\x1a\x01\x53\x5d\x56\xbb\xec\xad\x4e\xe0\ +\x42\xb0\x84\x81\x86\x90\x21\x60\xe2\x64\xcb\xe1\x65\xb1\x7f\xa5\ +\xda\x9d\x09\x64\x3e\x3e\x7c\x6f\x9a\x36\xd1\xa8\xbe\xb4\x5e\x73\ +\x22\xe9\x65\x91\xde\x5b\xde\xed\x4b\x88\x2b\x06\x51\x7c\x4e\xe4\ +\xce\x55\xb7\x17\xd7\xb9\x16\xe0\xb5\x5c\x94\xc7\x42\xee\x98\xfd\ +\x68\x8c\xc1\x2d\x00\xef\xe7\xa2\x09\x15\x4c\x69\xfb\x6f\x74\x60\ +\x4d\xdf\x5f\x73\xf6\x63\x4a\x54\x29\x65\x32\xe6\x04\xc6\xdc\x85\ +\x31\x4f\x0f\x5f\x4f\x24\xc0\xf0\xce\x61\x4a\x34\x05\xe0\x42\x05\ +\x93\x5d\xc1\xfc\x05\xc0\x0f\x83\xd5\x4b\xc8\xf1\xd2\x70\xa1\x85\ +\x29\x14\x2f\xf0\x00\x27\x00\x3e\x72\x1c\xba\x0e\x6b\x4f\x35\x2b\ +\x2d\x2a\x98\x10\xc7\x00\xd6\x60\xed\x1a\xde\x4f\x56\x9d\xe8\x03\ +\x8f\x5a\x4a\x86\x70\x59\xef\x1b\xd7\x2b\xc1\xde\x75\xfd\xf2\x1e\ +\xf0\x91\x31\xc6\x6a\x85\x0b\x15\x4c\x80\x82\x99\x83\xc5\x1e\x4a\ +\xbe\x17\x10\xe4\x09\x8c\xad\x6d\x03\x4a\x3a\x9f\x70\x49\x53\x27\ +\x21\xfd\x65\xf6\x51\xf2\x14\xc0\xe5\xf9\xb1\x33\x00\x57\x2e\x7e\ +\xb6\xa9\xd9\xce\xd2\xbe\xa7\x82\xd9\x8e\x26\xb8\x54\x8b\x39\xf8\ +\x66\xef\x1f\x83\xa5\x6b\x40\x47\x87\x86\x40\xa3\xa4\x8a\x21\x5c\ +\xdc\x4a\x65\xad\xa6\x92\xb2\xe5\xc4\xe1\xfd\xf7\x5d\xc7\xee\xbb\ +\xcf\xc9\x1e\x5f\x52\x52\x3c\x2a\x98\xb2\x41\x68\x1d\x12\xc5\x2e\ +\xc9\x16\x1f\x38\xf8\xf6\x79\x6e\x25\x43\xb8\x6c\x0f\xb2\x22\x7d\ +\xb2\x2f\xe8\xae\xd6\x60\xb6\x80\x92\xaa\x6c\x5a\x5d\xe1\xa2\x82\ +\x89\x12\x35\xe7\x9c\x6d\x43\x82\x35\xc4\xa9\x39\x95\xcc\xe8\x70\ +\xf1\x55\x2a\x85\x02\xe6\x14\xc0\xf5\x7d\xd9\x05\x67\x87\xaf\xe7\ +\x0a\xbc\xd6\x5a\x73\x7c\x61\x45\xd9\xf4\x04\x17\x2a\x98\x38\xf5\ +\x32\x5d\xbb\x62\x7d\xfa\x32\x75\x70\xa7\x2a\x99\x91\xe1\x22\xed\ +\x9e\xaa\x50\x5f\x2c\x41\x25\x44\xd1\xb4\xf4\x3f\x01\x13\x08\x18\ +\x87\xaa\xb0\x6b\x4e\xcf\xb5\x4b\x5a\x2c\x64\x46\x85\x8b\xe4\x9b\ +\x35\x63\x7c\x12\x0b\x9a\xd6\x8b\xff\x98\x22\x05\xaa\x17\xd7\x5b\ +\x97\x82\x60\x0e\x85\x94\xfb\x56\x72\xa4\x4b\x23\xc3\x45\xda\x9e\ +\xc0\x31\x69\xf9\xc2\x04\x66\xa5\xc2\x85\x80\x49\x08\x8c\x99\xf3\ +\x8c\x8f\xc3\x5d\x0e\x0e\x82\x4c\xe0\xdf\x69\xdf\x13\xd8\xa7\xce\ +\x22\x75\x65\x72\xec\x84\xe1\x02\x8d\xab\x3e\x23\xe5\xb6\x05\x02\ +\x26\x4d\xbd\x38\xa5\xea\xc1\xe1\x8b\x8e\x9d\x07\xbd\xd7\x42\x2e\ +\xc7\x9e\x23\x6b\x7f\x33\xda\x5d\xbd\xbd\xdf\x4f\x15\xea\xaf\x50\ +\x35\xd3\xaa\x2f\x08\x98\x0c\xb2\xf6\x78\xcc\xf7\xea\x52\xaa\x9a\ +\xd9\x82\xcc\x48\x75\x97\x9e\x54\x4b\x4e\x35\x1b\xa2\x66\x5a\x1a\ +\x01\x13\xa1\x5e\x36\x0a\xb7\xe6\xfc\x8f\x36\x3d\xc8\x8c\x09\x86\ +\xcc\x28\x70\xe9\xbd\xbd\x39\xce\x77\xa1\xd0\x6b\x25\xf4\x09\x01\ +\x93\x39\x08\x0e\x41\xef\x5d\x93\xd9\x4c\x99\xa6\x83\xc8\x91\x6a\ +\xcd\x07\xdb\x28\x75\x17\x4d\x77\x81\x67\x2a\xe0\xbb\x96\x93\xdb\ +\xd6\x7d\x42\xc0\x24\xaa\x97\x15\x07\x06\x41\x66\x53\xcd\x34\xbe\ +\x39\x52\x3a\x5c\x68\xee\xb8\x6d\x9d\x2e\x11\x30\xf9\xd5\xcb\x74\ +\x46\x4d\x87\xcc\x0a\x5c\xd6\xce\x51\xef\x03\xc2\x74\xc2\x25\x75\ +\xa2\x58\xba\xaa\xd9\x1a\x32\x04\x4c\x19\xf5\xe2\xcc\x8f\x7d\x0b\ +\x70\x16\x17\x2f\x49\x73\xaf\xd7\xf1\x8a\xd7\x29\xef\x75\x15\x7f\ +\x09\x18\x45\xea\x25\xda\xd9\x2b\x9f\x33\x32\x64\x46\x80\x4b\x8e\ +\xdb\x40\xa6\x9f\x23\x01\x32\x04\x0c\xa2\xd7\xbd\x78\xc1\x25\x08\ +\x32\x8e\x9a\x8b\x0f\x64\x7c\x7f\x47\xb8\xe8\x4f\x95\x92\x26\x37\ +\x02\xa6\xdd\x6c\x92\x63\xc0\xce\xd7\x2d\x9c\x73\xf6\xc6\xd5\x22\ +\xdf\x85\x79\xda\x8a\xbe\x2c\xe8\xa6\xa7\x51\x2d\x21\xb3\xa3\x93\ +\xc2\xd5\x8b\x6f\x6a\xe4\xe3\x70\x63\x8c\x85\xc7\xd5\xa2\x25\xe8\ +\xb9\xfe\xbf\x16\xc8\x8c\x0a\x17\x1f\xff\x85\xae\x01\x6a\x05\x19\ +\x2a\x98\x08\xf5\x92\x7b\x71\xd4\xd7\x3b\xe6\xf9\x15\x8d\xab\x48\ +\x6d\xc2\xa5\xef\x98\x95\x02\x99\xa1\x01\xd3\x42\xbd\x6c\x42\xc6\ +\x33\x98\x6c\x20\x8c\x7a\x56\x32\x23\xc2\x65\xcd\x77\x29\xf0\x2d\ +\xbd\xe7\x2f\x01\x93\x18\xcc\x59\x83\x7d\xbf\x21\x74\xf0\x5a\x99\ +\x90\x9c\xbc\x47\xc8\x8c\x76\x17\x78\x6d\x65\x17\xb3\x3e\x8b\x80\ +\x29\xa4\x5e\x7c\x72\xe0\xa8\x41\x70\xfe\xef\x09\x19\x05\x4a\xab\ +\x76\xaa\x5e\x68\x2c\x10\x30\xad\xa5\x78\x4e\xb8\xc0\xb1\x66\xc1\ +\xc7\xe1\x6b\x9b\x59\x6d\x3d\x67\xba\x97\x41\x4c\xf5\xe2\x3f\x71\ +\xa4\xa8\x18\x2a\x18\x21\xea\x25\x4b\xc0\xaf\x5f\x8e\x4e\x52\x32\ +\x21\xc5\x69\x89\x90\x61\x6a\x54\x17\xbe\x35\x52\x25\x2a\x98\x9a\ +\xea\xc5\xef\xe6\xc5\x4d\xa7\x6f\x6d\x66\xd5\x23\x64\x98\x1a\xf9\ +\xc7\x65\x29\xf8\x96\x80\xcc\x70\x80\x89\xbd\x72\x94\xec\x54\xcf\ +\xb5\x2e\xb9\x94\xcc\x5a\xca\x24\x5d\xc9\x50\xbd\xd4\x03\x6f\xe9\ +\x54\x89\x0a\x06\xdb\x0f\xa1\x4f\x56\x2f\x01\x70\x09\x81\x4c\xca\ +\x66\x56\x92\x20\xc3\xd4\xa8\x2d\x7c\x4b\xa6\x4a\x43\x01\x26\x65\ +\xdd\x4b\x4d\xb8\xe4\x74\x3c\x6f\x98\xa4\x7a\x69\x69\xc3\x2b\x18\ +\x1f\xf5\x12\x3d\x6b\x24\xc0\x65\x09\x32\xb1\x37\xc0\xb9\x52\x26\ +\x09\x2a\x86\xea\x25\x0c\x2e\xa5\xfc\x54\x4a\xc5\xec\x06\x72\x58\ +\x5d\xf5\x92\x01\x2e\x0b\x79\xb2\x4d\xf8\x9c\x0b\xed\x1a\x71\x47\ +\x3c\x2a\x17\x2a\x98\xfe\xd5\x4b\xc1\x19\x06\x09\x7b\xad\x4a\x82\ +\x0c\xd5\x4b\x9c\x02\xad\x11\x63\xb9\x54\xcc\x10\x80\xe9\x59\xbd\ +\x94\x08\x00\x57\xca\xa4\x75\x06\xed\x5d\xbd\xf4\x0e\xde\x61\x15\ +\x8c\x0d\x2f\xb8\x36\x85\xcb\x24\xf8\xb2\xcd\x32\x6b\x6d\xaa\x09\ +\x19\xaa\x97\x30\xb8\x94\xf4\x4d\xee\xcb\xd6\xbb\x01\x9c\xe6\xad\ +\x5e\x92\x1d\x57\x10\x2e\xf3\xff\xd4\x3b\x64\xa8\x92\xe4\xf7\x47\ +\x8e\x34\x69\x48\x05\x53\x44\xbd\xd4\x83\x4b\xf4\x13\x0b\x62\x72\ +\x7c\x82\x40\x4e\x8c\xf6\xa8\xf4\x54\x03\xa6\x9a\x7a\xa9\x08\x97\ +\x25\x39\x9b\x63\xb6\x69\x24\xc9\xa9\x5e\x84\xd5\x5d\x72\xa6\x49\ +\xc3\x29\x98\xec\xea\xa5\x11\x5c\x7a\x86\x0c\x55\x91\x5c\xb8\xe4\ +\x4e\x93\x76\x8a\x1d\x97\xb4\x5b\x9d\x34\xe5\xe2\xfb\x3c\xec\x5c\ +\x90\xf1\xbd\x7d\x82\xd6\x06\xb4\xbd\xf8\x62\x28\x05\x13\x3a\x3b\ +\xaf\xce\x26\x8d\x95\x4b\x69\xc8\x2c\xb5\x3f\x67\x60\x33\x3d\x92\ +\xdb\x1f\xb9\xd2\x24\x95\x80\xc9\xa1\x5e\xa4\xc2\xc5\x77\xe7\x78\ +\xa9\x90\xa1\x0a\x4a\x4b\x8d\x5a\x40\x28\x25\x96\x86\x51\x30\xd9\ +\x6a\x0b\xc2\x94\x8b\x06\x25\x43\xb8\xe8\x55\x72\x3b\x85\xce\x2b\ +\xa7\x5e\x84\xc3\xa5\x34\x64\x72\xde\x99\xcd\xf4\xa8\x8b\x49\x39\ +\x79\x16\xd9\x0d\xd2\x51\x43\x07\x47\xce\xbb\x63\xb9\xfd\x03\xd5\ +\xcb\xb0\x80\x09\x55\x2f\x41\x1b\x29\x77\xa2\x5e\x08\x19\xc2\x85\ +\x80\x11\xaa\x5e\x34\xc1\x65\x09\x32\xa5\xea\x32\x6b\xcf\xcb\xa6\ +\x8d\xfb\xa4\xca\x9d\x22\x07\x26\xab\x17\x6d\x70\x59\xca\xa5\x5b\ +\x15\x7f\x09\x20\x35\xe3\x8b\x0a\x26\x8b\x7a\x51\x00\x17\x69\x90\ +\xa1\x7a\x19\xab\x26\xb8\x53\xe2\xc0\xfc\xab\x76\x15\xc1\x65\x0a\ +\x99\x9a\x97\xb1\xa3\x16\x30\x12\x2e\xa2\x27\x26\x2a\x18\x0f\x47\ +\x6e\x3a\x5d\x21\x5c\x96\x82\xa6\x24\x64\x46\x57\x33\x23\x2b\x17\ +\x35\x80\xc9\xae\x5e\x94\xc3\x85\x90\xa1\x11\x30\xad\xd4\xcb\x20\ +\x70\x21\x64\xa8\x5e\x08\x98\xda\xea\x65\x30\xb8\xb4\x80\x0c\xe1\ +\x42\xc0\x8c\xe9\xf8\x41\xe1\xd2\x0a\x32\x9a\xd5\x0c\xe1\xa2\x04\ +\x30\x4b\xea\x85\xf7\xb8\xf4\x03\x19\xed\x69\x13\x63\x91\x4f\x15\ +\xe8\x4e\xbd\xd4\xda\x51\xbe\x56\xba\xa4\x09\x32\x35\xda\xd2\x5b\ +\x7f\xed\x3a\x75\x64\xb0\x7a\x71\x3a\x66\xf0\xd4\xa8\x16\x64\xb6\ +\xa0\xa3\x01\x32\x4c\x8d\x06\x56\x30\x1a\xe0\x62\x95\x6d\x26\xbe\ +\xf4\xbc\x6c\xc2\x45\x57\xfa\xb5\xeb\xd0\x99\xc9\xb5\x17\x7b\xde\ +\x53\x9c\x66\x2a\x43\x66\xed\x69\x99\xbd\xab\x19\x2a\x97\xc1\x14\ +\xcc\x85\xd9\x85\x70\x11\x91\x2e\x69\x81\x0c\x17\x11\x2a\x02\x4c\ +\xaa\x7a\xd1\x02\x97\xca\x8f\x75\xad\x06\x99\xde\x52\xa6\xda\xa9\ +\x51\x8b\xfe\xe0\x63\x4b\x7c\x03\x98\xca\x45\x3c\x64\x7a\x52\x33\ +\x2c\xea\x2a\x03\x4c\xa8\x7a\xd1\x08\x97\x96\x41\x9c\x03\x32\xd3\ +\xf3\x5f\xaa\xc9\xf4\x98\x32\x59\x41\x4f\x96\xa8\xe1\x7f\x2a\x98\ +\xe5\x5e\xe2\x94\x22\x48\xc9\x6c\x41\x46\x6a\xca\xc4\xba\x8b\x32\ +\xc0\xe4\x50\x2f\xda\x42\xa2\x55\x90\xd7\x84\x8c\x44\x35\xd3\x2a\ +\x35\xea\x15\x6a\x63\xac\x83\x61\x8e\x4c\xc8\x74\x0c\x17\x2a\x18\ +\xe1\xea\x45\xd9\xe0\x56\x01\x99\xb5\xc1\xba\x04\x19\x49\x29\x53\ +\x2b\x3f\xd4\xfc\xbf\x39\x26\x0f\x6d\x8f\x2d\x51\x9f\x1a\x8d\xac\ +\x64\x5a\xaa\x19\xd6\x5d\x14\x02\x26\x75\xdd\x8b\x51\x2e\x65\x25\ +\x04\x7d\x28\x64\xb6\x7c\x21\x11\x32\xad\x53\xa3\xd6\x7e\x4e\xd9\ +\x97\x77\xa7\x71\xb0\x59\x2a\x97\x2e\x94\x8c\x0f\x40\xd6\xde\xd3\ +\x22\x65\x1a\xa5\xee\x92\x6b\xbd\xd3\xae\x93\x06\x06\xa9\x17\xa3\ +\x3c\x20\x7c\x06\x60\xef\xe9\x52\xec\x80\xcf\xdd\x1f\x6b\xb7\x34\ +\x54\x9f\x38\x3b\x8c\x65\x2d\x8f\x2d\xa1\x8c\xe8\x08\x32\x3e\x03\ +\x25\x04\xa2\xa5\x20\xc3\xb8\x52\xfa\xd8\x92\x20\xf5\xb2\x12\x04\ +\x56\xf7\x53\x01\x54\xa4\x4b\xc6\xd3\x7f\x3e\x90\xc9\x99\x32\x49\ +\xbb\x24\xdd\xdb\xd5\x23\x1d\x0a\xc6\x18\xd6\x5a\x04\xce\xb4\x39\ +\xd3\xa5\xd0\x74\xb0\x84\x9a\x69\x05\x17\x0d\x0a\x6a\x27\x70\xb0\ +\xf8\xa9\x97\x19\x5c\xb8\xe8\xa9\x2f\x25\x13\xfb\x58\xdf\x1a\x90\ +\xe9\xbd\xee\x21\x25\x3d\xea\x57\xc1\x38\x02\x66\xc4\x55\x96\x12\ +\x8b\xbd\x31\x4a\x26\x14\x1a\xbe\xef\x8f\x49\x99\x24\xde\xef\xd4\ +\x6b\x7a\x24\x0e\x30\x5e\xea\xe5\xd0\xf1\x54\x2f\x7d\x2a\x19\x63\ +\xcc\xaf\x26\x87\xbf\x59\x1a\xa8\x4b\x6a\xc6\x18\xd7\x8b\xb7\x02\ +\xa8\x4f\x91\x72\xcf\xec\x54\x31\xe2\xa4\xf6\xaf\x8d\x31\xbf\x38\ +\xfc\xfe\x3f\xc7\x18\x8c\x01\x46\x3c\x64\xbe\x2d\x3a\x86\x24\xa8\ +\x97\x1c\xe9\x91\x28\xc0\xa4\xa8\x17\x5e\x4e\xec\x2e\x9f\xff\xed\ +\xc4\xdf\x3f\x42\xe0\xba\xc8\x58\xc8\x3c\xfb\xbb\xdf\xb8\x22\x90\ +\xca\xa5\x80\x5d\xea\xe6\x4c\x99\x1a\xad\x0e\x9e\x67\xd2\xdf\x88\ +\xbf\x84\x7d\x84\x8b\x31\xc6\x5a\x6b\x8d\x31\xe6\x32\x80\xb3\x90\ +\x73\x8f\x6d\xf3\xfe\xef\xfe\xb9\x08\x17\xe0\x1f\xc3\xd7\x5e\x72\ +\xa9\x17\x31\x0a\x66\x53\xbd\xac\xcc\x52\xcc\x9b\xb7\xfb\x44\xb2\ +\x9a\x39\xf8\xfe\x67\x00\xbe\x53\x43\xc9\xec\xed\x7b\x0b\x70\xb1\ +\xb3\x63\xf4\x9b\x9a\x14\x69\x4b\xb9\x0c\xa5\x5e\xe6\xd5\xc7\x88\ +\x3a\x43\x67\x29\xd3\xef\x01\xfc\x1c\xc0\xb7\x42\x07\x59\x0f\x35\ +\xa8\x58\x55\xda\xbb\x7a\x01\x00\xd3\x3a\x30\x57\xd5\xcb\xec\xe1\ +\x68\x2e\xf9\xa8\x6e\xcd\xc2\xd2\x20\xf1\xbd\x83\xbc\xb3\xfe\x98\ +\xf9\xff\x16\x80\x0f\x00\xfc\x3b\xf4\xdc\x43\xda\xfd\xec\xad\x73\ +\xf5\x12\xd4\xd5\xea\x52\xa3\x12\x80\xd9\x75\x31\xd0\x3c\xe0\x22\ +\x4a\x71\xb0\xe8\x1c\xab\x64\xee\x00\xf8\x25\x80\xcb\x29\x33\xbe\ +\x5f\x5c\xb8\xe1\x42\x5f\x28\x4a\x91\x16\xd5\xcb\x6c\xe6\x16\x3d\ +\x2b\x2f\x05\x73\xbb\x3d\x73\xbb\x4b\x19\x66\x81\xfd\x36\x80\x9f\ +\x1a\x63\xbe\x51\xd0\x69\x8b\x70\x19\x55\xbd\x94\x80\x8b\x6c\x05\ +\xb3\xe1\x6d\x8d\xc5\xdd\x12\x3c\xe8\x14\x32\x7f\x00\xf0\xb6\x31\ +\xe6\xa5\xd2\x70\xb5\xf6\xfc\xab\x25\x5c\x1a\x4e\xee\xe5\xfe\x4f\ +\xc3\x1b\xb9\xd6\xd5\xcb\x46\x8d\x45\x0c\x60\xd6\x02\x24\xe0\x9c\ +\xce\x65\x84\xb3\x8b\x29\x06\x36\x38\xf8\x7b\x05\xf0\x3c\x2e\x62\ +\xce\xdb\xf7\xb1\xb4\x02\xea\x8f\xcd\x7c\x54\x43\xbd\xc8\x55\x30\ +\x9b\x45\x3a\xdd\x97\xa6\x0d\xec\xb9\x57\x6a\xaa\xd4\x71\x1d\xc0\ +\xfe\x2b\xf1\x8a\xda\x34\x56\x24\xa7\xda\x1a\xe1\xd2\x0c\x30\x5e\ +\xb5\x17\xf0\xae\xd6\x9c\x41\xdb\xd3\x25\xdc\x69\xd0\x7f\x77\x1e\ +\x16\x81\x90\xb9\x75\xf8\x9b\x8f\x8d\xd9\x7f\x2f\x24\x96\xb4\xdc\ +\xcc\xb8\x65\x97\x04\x46\x57\x5f\x33\xf5\xfc\x72\x7a\x64\x3b\x6a\ +\x05\x75\x2f\xa0\x3e\xf4\xaa\x3d\xcc\x40\xd6\x46\x6c\xb3\x3c\x6d\ +\xe9\xf7\x01\xbc\x2f\xc8\x0f\x12\xea\x2e\xa5\xd5\x4b\x93\x1a\xcc\ +\xaa\x7a\x59\x98\x71\x45\xd6\x5e\xb2\xf7\x4b\x56\xee\xf6\x9f\x52\ +\x3e\xbb\x35\xc4\x4e\xa1\x13\xd4\x19\x89\x6b\x8a\x34\xd5\x5d\x5a\ +\xc0\xa5\x59\x8a\xb4\xe5\x74\xa6\x46\x65\xf3\xfb\xae\xd2\xa5\xc9\ +\x24\x64\x00\x7b\xaf\xeb\x49\x64\x2c\xb8\x54\x57\x30\xbe\xbb\xd5\ +\x8d\xa6\x5e\x4a\x2b\x98\x2e\xfb\xcf\xcc\xaf\xa4\x21\xfc\xea\x92\ +\x20\x05\x33\x22\x5c\x64\x29\x98\xc1\xd5\xcb\x5a\x53\x73\x75\x43\ +\x57\x4a\x66\x76\xae\xb3\x1a\x8c\xf5\x2a\x56\xba\x3a\x8e\x70\xa9\ +\xdb\xf6\x46\x8d\x0d\x56\x2f\x4c\x9d\xfa\x0f\x78\x2d\x83\x86\x70\ +\xe9\x54\xc1\xf8\xc2\x85\x36\x90\x92\xb9\x78\xee\x66\x6d\x20\x11\ +\x2e\xb2\x20\xbc\x6b\xd0\x60\xa7\x7a\xa1\x3a\x21\x64\x34\x41\x86\ +\x70\x11\xa8\x60\x34\x4a\xfa\x43\x03\x44\xde\x6d\xad\x0d\x32\x52\ +\x40\xd3\x22\x5e\x5d\xed\x97\x90\x3e\xee\x6a\x34\x9c\xea\xa5\x2f\ +\x25\xd3\xe9\x0d\x92\xcd\xd5\x8c\xab\xef\x6a\xc1\xc5\xa7\x6f\x5a\ +\xd8\xa5\xc2\x2d\x2f\x3e\x20\x68\x79\xfa\x74\x3e\x30\x7a\x59\xf5\ +\x3b\xdf\xe7\x77\xfa\x7d\xd5\xf5\x1e\x8e\x58\x2f\xdd\x7f\x92\xc1\ +\x52\x45\xc1\xcc\xd7\x2e\xc4\xa8\x17\x16\x77\xeb\x41\x46\x53\xca\ +\x54\x53\xcd\xb8\x54\x0b\xe1\x52\x5a\xc1\x04\x3e\x45\x8f\xca\x44\ +\xa6\x9a\x39\x7e\x4f\x35\x43\xd5\x12\x75\xbe\x45\x3a\x64\xff\xdc\ +\xe8\xec\xea\x85\x10\xaa\x5b\x4f\xe8\x39\x45\x5d\x52\x2f\x39\x06\ +\x63\x8b\xbe\x29\xd9\x9e\xbe\x52\x24\x63\x4e\x00\xdc\x7d\x0a\xe0\ +\x2e\x80\x13\xaa\x17\x35\x29\x53\x4f\x69\x93\xb5\xd6\x2c\xa5\x4d\ +\x29\xa9\xd3\x92\x6a\x29\x15\xc3\x4b\xe7\xbb\xd4\x3e\xdd\x0a\xc6\ +\x98\x1f\x00\xf8\xd0\x71\xe4\xba\x01\x4e\x43\xaf\x1c\x11\x42\xb2\ +\x15\x4d\xcf\x2b\x80\x43\x55\x40\xcd\xf6\xa7\x9e\xab\x4e\x05\xb3\ +\x77\xc0\x1f\x17\x8e\xbe\x1b\x0a\x0e\x16\x77\xe5\x29\x1a\x97\x8f\ +\x7a\x57\x34\x53\x95\xe0\x1a\xd8\x4b\x6d\xcc\x0d\x97\xb5\x73\xe8\ +\x49\xb1\x94\x56\x30\xff\x83\x7b\x63\xa0\x33\x58\x7b\x25\x05\x30\ +\x54\x30\xb2\xd5\x4c\x8f\x3e\xf2\x48\x95\x4c\xc9\x36\x6e\xfd\xff\ +\x1e\x81\x52\x4e\xc1\xec\xed\xef\x0b\xbf\xbf\x4f\xb8\xe8\x52\x33\ +\x6b\x8a\xa6\x37\x55\xb3\x32\x90\xed\xfc\x15\x53\xbf\x99\xaa\x13\ +\x5f\xa5\xa2\x01\x2e\x95\x6b\x30\xf6\x74\x3a\x21\xd8\xc1\x1e\x49\ +\x32\xa2\xa2\xe9\xc5\x77\x8e\xf3\xaf\x7a\xd2\x5a\x60\x52\x5c\xc1\ +\x18\xd8\xbf\x02\xb8\x0e\xe0\x1e\x80\xb3\xc3\xd7\xeb\x00\x4e\x2d\ +\x0c\xfe\x06\x7c\xbd\xf1\x32\xe1\xa2\x4f\xd1\x6c\xa9\x1a\x29\xca\ +\x66\xeb\x9c\xa6\x2a\x22\xa7\xa2\x28\xf5\xb9\xc3\x28\x18\xe7\xde\ +\xd7\x08\xdb\x55\x8c\x80\xd1\xaf\x6a\x6a\xfb\x57\xd2\xb9\x8c\x66\ +\x97\x24\x9f\x1c\x1d\xde\xb7\xaa\xf1\x19\xe4\xb9\xaf\xd0\x84\xa8\ +\x24\xc6\xd7\x60\x80\xe1\xa5\x69\x02\xa7\x64\x1c\x10\x28\x54\x30\ +\xb4\x41\x81\x93\x1b\x2c\x84\x89\x10\xd1\x50\xba\x06\x03\x38\xea\ +\x30\xbc\xef\x88\x46\xa3\x82\x09\x9f\x35\xdc\x90\x59\x7d\x78\xfb\ +\xe7\x9f\x03\xef\xbd\x07\x0b\xe0\xc9\xd5\xab\xf8\xf3\xc9\x09\x7e\ +\x47\xb8\xd0\x68\x54\x30\xc9\xf6\xe9\xa7\xc0\xcb\x2f\xbb\x8f\x7d\ +\xf2\x09\x70\xed\x1a\x3d\x44\xa3\x75\x6c\x6d\xf7\xe4\x7d\xe7\x9d\ +\xb8\x63\x34\x1a\x8d\x0a\x66\xd3\xae\x5e\x05\x9e\x3e\x75\x1f\xbb\ +\x72\x05\xf8\xe2\x0b\x7a\x88\x46\xa3\x82\x89\xb4\x37\xde\x88\x3b\ +\x46\xa3\xd1\xa8\x60\x36\x8d\x35\x18\x1a\x8d\x0a\xa6\x98\x5d\xbb\ +\x86\xc7\x0f\x1e\xe0\xd1\x8d\x1b\x00\x80\x27\x2f\xbc\x00\xdc\xbe\ +\x0d\x7c\xf6\x19\xe1\x42\xa3\x51\xc1\xd0\x68\x34\x9a\x54\x05\x43\ +\xa3\xd1\x08\x18\x1a\x8d\x46\x23\x60\x68\x34\x1a\x01\x43\xa3\xd1\ +\x08\x18\x1a\x8d\x46\x23\x60\x68\x34\x9a\x4c\xfb\x3f\x25\xe7\xf0\ +\xbf\xc2\xb2\xb9\xae\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\ +\x82\ +\x00\x00\x04\x97\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x01\x18\x00\x00\x00\xa0\x08\x06\x00\x00\x00\xd7\x60\x73\x20\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ +\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ +\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x01\x18\ +\x08\x2d\x07\xfc\x70\x1f\xcb\x00\x00\x00\x45\x69\x54\x58\x74\x43\ +\x6f\x6d\x6d\x65\x6e\x74\x00\x00\x00\x00\x00\x43\x52\x45\x41\x54\ +\x4f\x52\x3a\x20\x67\x64\x2d\x6a\x70\x65\x67\x20\x76\x31\x2e\x30\ +\x20\x28\x75\x73\x69\x6e\x67\x20\x49\x4a\x47\x20\x4a\x50\x45\x47\ +\x20\x76\x36\x32\x29\x2c\x20\x71\x75\x61\x6c\x69\x74\x79\x20\x3d\ +\x20\x39\x30\x0a\x67\x9d\x47\x40\x00\x00\x03\xc6\x49\x44\x41\x54\ +\x78\xda\xed\xdc\x5b\x6e\xdb\x40\x0c\x05\x50\xaa\xf0\x1a\xb5\xf8\ +\xd9\x0c\xf3\x93\x02\x46\xa2\xda\x1a\x5b\xa3\x6a\xcc\x73\x00\x03\ +\x45\xd1\x3a\x31\x49\x5d\x71\xd4\x47\x04\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\xd5\x64\xc4\x9a\x11\x99\x11\x2d\x23\x56\ +\x15\x01\x8e\x0e\x97\xfb\x97\x90\x01\x7a\x72\x64\xfb\xd5\x7e\x87\ +\x4b\xb6\x07\xbf\x5e\x29\xf9\xeb\xa6\x04\x65\xc3\xe4\xec\xf7\x5f\ +\x94\x1d\x8a\x6d\x27\x7b\xb6\x8f\x8e\x23\xd2\x5b\x5f\x07\xf8\xac\ +\x40\xe9\x79\xc3\x57\x1f\xf2\x0a\x9c\xc2\xac\xad\xb5\x8e\x3e\x8b\ +\xef\x11\x01\xc3\x51\x17\xec\xe2\xfb\x47\xc0\xe0\xc2\x14\x34\x02\ +\x86\x4b\x5f\x88\x8b\xcf\x88\x80\xc1\x45\xe7\x33\x0b\x18\x2e\x7f\ +\xa1\x2d\x3e\x3f\x02\x06\x17\x96\x7a\x08\x18\x1c\x0d\xd4\x06\x01\ +\xe3\x2e\x8d\x3a\x09\x18\x0e\xb9\x68\xf4\x47\xcd\x04\x0c\xee\xc6\ +\xea\x87\x80\x71\x71\xa8\x23\x02\xc6\x7a\x8f\x9a\x0a\x18\x5c\x08\ +\x6a\x8b\x80\x71\x01\xa8\xb1\x1a\x0b\x18\x83\x8f\x5a\x0b\x18\x0c\ +\xbc\x9a\x23\x60\x0c\x3a\x6a\x2f\x60\x0c\x38\x7a\x20\x60\x30\xd8\ +\x7a\x81\x80\x31\xd0\xe8\xc9\x55\xfc\x51\x02\x61\xae\x0f\x28\xf6\ +\xdc\x77\x4a\x75\xd6\x1f\x1b\x0c\xc3\xd6\x70\xf4\xcb\x06\x83\x33\ +\xbe\x5e\x61\x83\x11\xe0\xe8\x8f\x02\x3b\xd7\xa3\x6f\x36\x18\x00\ +\x1b\x8c\xbb\x20\x33\xf6\xaf\xf2\xff\x84\x65\x83\x19\x37\x43\xe8\ +\xe7\x3f\xdf\xa8\xca\xc0\x08\x18\x5b\x21\xfa\x26\x60\x1c\x8d\x38\ +\x39\x64\x6c\xa5\x02\x06\x10\x30\xb6\x17\x6c\x31\x02\x06\x18\x97\ +\x56\x7b\x7e\xbe\xca\xe7\xc7\xf6\xa2\xbf\xfa\x6b\x83\x01\x04\x8c\ +\x2d\x10\x7d\x15\x30\xbc\xb0\x3e\xa3\xdf\x08\x18\xc0\x1a\x38\xd7\ +\xdd\x4c\xfd\xf4\x1b\x1b\x0c\x20\x60\x6c\x7f\xe8\xb3\x80\x01\x10\ +\x30\x63\xcf\xe3\xe8\x3f\x02\x06\x10\x30\xce\xe5\xe8\xb7\x80\x01\ +\x10\x30\xce\xdf\x98\x03\x01\x03\x08\x18\x9c\xc7\xf5\x1d\x01\x03\ +\x08\x18\x40\xc0\xf0\xcd\x83\x3d\xcc\x83\x80\x01\x04\xcc\xbc\x3c\ +\xe8\xd3\x7f\x3a\xdc\x94\xe0\xdc\x3d\xda\x84\x62\x83\x61\xd8\x21\ +\xdd\xe1\x1d\x01\xc3\x90\x70\x11\x32\x08\x18\x7e\x86\xc1\x9a\x11\ +\xd1\xbe\x7f\xac\x22\x66\xc1\x2c\x70\xe4\x40\xe5\x8f\xd7\xba\xe3\ +\xf7\x3d\x7d\x51\x63\x16\xe0\xd1\x50\xb5\x8d\xa1\x6a\x02\xc6\x2c\ +\xec\x9d\x05\x47\x24\x00\xe6\x5a\x8b\x6d\x2f\x66\x01\x76\x0f\x56\ +\x7b\x61\xa0\x84\x8b\x59\x80\x9e\xac\x00\xb3\xd0\xc1\x33\x18\x40\ +\xc0\x00\x02\x06\x40\xc0\x00\x02\xe6\x6a\x3c\xdc\xd3\x7f\x04\x0c\ +\x20\x60\xe6\xe1\xbf\x71\xc1\x3c\x08\x18\x40\xc0\x00\x02\x86\x4d\ +\x1e\xf4\xe9\x3b\x02\x06\x10\x30\xf3\xf0\x60\x0f\x73\x20\x60\x00\ +\x01\xe3\x3c\x8e\x7e\x0b\x18\x00\x01\xe3\xfc\x8d\xfe\x0b\x18\x40\ +\xc0\xe0\x5c\x8e\x3e\x0b\x18\xc0\x99\xf2\x93\xef\x6a\x6a\xa8\xcf\ +\xd8\x60\x00\x01\x63\xf3\x43\xbf\x05\x0c\x9b\x6b\x34\xfa\x8a\x80\ +\x01\xac\x7e\x73\xdd\xe5\xd4\x52\x5f\xb1\xc1\x00\x36\x18\x77\x3b\ +\xf4\x73\x7a\x37\x25\x80\x79\x13\xf0\xea\x29\x28\xa1\xdd\xf5\x98\ +\xa8\x8f\x39\xd9\x85\xec\x19\xcc\x79\x33\x80\xbe\x95\x23\x60\x6c\ +\x82\xe8\xa7\x80\x99\x6c\x28\xdd\x0d\x1d\x8d\x10\x30\x30\xff\x6a\ +\xe5\x21\xaf\xbb\x21\xfa\x65\x83\x61\xe8\xf0\xa2\x3f\x36\x2e\x86\ +\x0c\xae\x3a\xeb\x91\x0d\x06\xc1\xad\x5f\x28\xac\xf3\x3d\xfa\x62\ +\x83\xc1\x79\x5f\x1f\x6c\x30\x38\xeb\xeb\x05\x02\xc6\x60\xa3\x07\ +\x02\x06\x03\xae\xf6\x28\xb4\x41\x57\x73\x04\x8c\x81\xe7\xd4\x5a\ +\xa7\x0b\x45\xc0\x08\x19\xce\x0a\x17\x4d\x7a\x9d\x3f\xa6\xbe\x46\ +\xb0\xfb\xa3\x53\x01\x2e\x60\x10\x32\xc2\x05\x47\xa4\xb9\x2f\x08\ +\x7d\xf9\x8f\x75\x74\x44\xb2\xc1\x54\x08\x79\xdb\x8c\x90\xb6\xc1\ +\x60\xbd\xff\xc4\x9a\x49\x2b\x01\xe3\x6e\x8c\x3a\x09\x18\x0e\xbc\ +\x78\xf4\x4b\x6d\x04\x0c\xee\xd2\xea\x81\xe6\xb8\xb0\x7c\x7e\x04\ +\x0c\xa5\x8f\x06\x8e\x43\x02\x06\x17\x9d\xcf\x88\x86\x55\x09\x9a\ +\x59\xfb\xea\xef\xb8\x09\x18\x5c\x98\xbe\x7f\x04\x8c\xb0\xb9\x6e\ +\xbf\xd3\x4c\xd6\xe1\x9f\x0a\x7c\xde\x0d\x63\x79\x72\x71\xdf\xbf\ +\x7a\x52\x61\xcd\x88\xcc\x88\x96\x11\x6b\x67\xa0\xec\xf9\x9a\xcf\ +\xbe\x77\xe0\xc2\x9b\xcd\xde\xd7\xa3\x70\xb9\x7f\xad\x47\x7f\x1d\ +\xa0\x5e\xe0\x64\xfb\x1d\x2e\xd9\x3a\xdf\x43\xc9\x6b\xba\x29\x41\ +\xd9\xa3\xd4\x56\xe8\x8c\x7c\x7f\x80\xb7\x8e\x48\x00\x6f\x85\x4c\ +\x13\x2e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1c\ +\xe9\x0b\x64\xcc\xe1\x13\xf2\xb1\xb1\xdf\x00\x00\x00\x00\x49\x45\ +\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x00\xb8\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x12\x00\x00\x00\x12\x08\x06\x00\x00\x00\x56\xce\x8e\x57\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ +\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ +\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdd\x06\x13\ +\x0c\x37\x04\x31\xe8\x35\xc9\x00\x00\x00\x38\x49\x44\x41\x54\x38\ +\xcb\x63\x60\x18\x20\xf0\x1f\x97\x04\x13\x35\x0c\x21\xd5\x25\xff\ +\x87\x8c\x21\xff\x09\x85\x11\xcc\x00\x46\x4a\xc2\x8d\x58\x97\xfc\ +\x1f\x35\x84\xf4\x64\xf0\x7f\x58\x25\x48\xfa\x18\x42\xb5\xdc\x4e\ +\x32\x00\x00\x8e\x77\x28\xdd\x40\x15\x3d\x50\x00\x00\x00\x00\x49\ +\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x01\x9d\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x12\x00\x00\x00\x12\x08\x06\x00\x00\x00\x56\xce\x8e\x57\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ +\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ +\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdd\x06\x13\ +\x0c\x0c\x34\x2b\x83\xea\x5d\x00\x00\x01\x1d\x49\x44\x41\x54\x38\ +\xcb\xa5\xd4\x2d\x4b\xc3\x51\x14\x06\xf0\xdf\xfe\x73\xe0\x26\x0e\ +\x35\xfa\x21\x4c\x0b\x16\xc1\x20\x08\x16\xd3\x9a\x20\x66\xab\xa0\ +\x58\x04\xbf\x81\xc1\x20\x62\xb7\x08\x36\x93\x8a\xa0\x22\x1a\x7d\ +\x29\xbe\xe0\x44\x14\x16\xd4\xb0\xcd\x22\xcc\x72\x07\x63\x6e\x6e\ +\xff\xed\x81\x1b\xce\xcb\x7d\xee\x39\xdc\xf3\x9c\x84\xd6\x88\x30\ +\x82\x4c\xb0\xcb\xf8\x40\x55\x0c\xcc\xe1\x0a\x8f\x38\xc2\x31\x9e\ +\x70\x81\x7c\x27\x04\x59\x5c\x06\x82\xa9\x26\xf1\x19\xbc\xe0\xa4\ +\xae\xd2\x3f\x18\xc4\x3d\x96\xdb\x3c\x16\x61\x0d\x77\x48\x37\x4b\ +\x38\xc3\x4a\x8c\xf6\xd7\x71\xd0\xe8\xcc\xa3\x80\x44\x0c\xa2\x14\ +\x8a\x98\xae\x77\x9e\x63\x56\x7c\xcc\xe3\xb0\x66\x8c\xe0\x59\xf7\ +\x28\x20\x1b\xa1\x1f\xaf\x3d\x10\x15\x91\x8e\x82\x91\xec\x81\x28\ +\x59\xfb\xca\x0a\x46\xbb\x24\x49\x61\x18\xe5\x08\x5f\x78\xc0\x42\ +\x17\x44\x8b\xb8\x45\xa9\xe6\x98\xc4\x67\xab\x01\xfb\x47\x05\x25\ +\xe4\x1a\x03\xbb\xd8\x88\x41\xb4\x8d\xad\x66\x81\xbe\x20\xd4\xcd\ +\x20\x97\x56\x18\xc2\x0e\x4e\xeb\x07\x38\xaa\x4b\xf8\xc1\x78\x68\ +\xef\x1d\x4b\x18\x68\x68\x65\x15\x6f\xf8\xc6\x44\x27\x2b\x25\x87\ +\xfd\xb0\x3a\xae\x71\x13\x36\xc2\x1e\xc6\x9a\x5d\x68\xa7\xad\x4c\ +\x38\xd5\x50\x45\xa5\x55\xe2\x2f\xb3\x70\x36\xb1\xc0\x88\x65\x3b\ +\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x06\x0e\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x01\x18\x00\x00\x00\xa0\x08\x06\x00\x00\x00\xd7\x60\x73\x20\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ +\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ +\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x01\x18\ +\x0c\x3a\x27\xc5\x94\x13\x49\x00\x00\x00\x45\x69\x54\x58\x74\x43\ +\x6f\x6d\x6d\x65\x6e\x74\x00\x00\x00\x00\x00\x43\x52\x45\x41\x54\ +\x4f\x52\x3a\x20\x67\x64\x2d\x6a\x70\x65\x67\x20\x76\x31\x2e\x30\ +\x20\x28\x75\x73\x69\x6e\x67\x20\x49\x4a\x47\x20\x4a\x50\x45\x47\ +\x20\x76\x36\x32\x29\x2c\x20\x71\x75\x61\x6c\x69\x74\x79\x20\x3d\ +\x20\x39\x30\x0a\x67\x9d\x47\x40\x00\x00\x05\x3d\x49\x44\x41\x54\ +\x78\xda\xed\xdd\x5d\x4e\x1b\x31\x14\x86\x61\x1b\x95\x5e\x76\x19\ +\xed\x8e\xd8\x16\x5d\x01\x3b\x60\x43\x61\x15\x91\xda\x4a\xbd\x72\ +\x2f\xca\x45\x08\x81\xf1\x24\x63\x8f\x7f\x9e\x57\xca\x45\x54\x11\ +\x12\x9f\xcf\xaf\xcf\x19\x60\x1a\x02\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x18\x88\x68\x09\x90\x43\ +\x12\x1c\x10\x0c\x6a\xc8\x45\x78\x90\xcb\x9d\x25\xc0\x12\x2f\xe4\ +\x02\x82\x01\x60\x44\xc2\x10\x23\x92\xe0\x40\x07\x03\xa7\x10\x00\ +\x00\x00\xa0\xfb\x1d\x00\xbf\x76\x02\x82\x41\x15\xb9\xa8\x19\x08\ +\x06\xd5\x04\xa3\x7e\x20\x18\x14\x97\x8b\x7a\x82\x60\x70\x93\x5c\ +\xe2\x0d\xf2\x51\x5b\x10\x0c\x3e\x14\x48\xdc\xb0\xd3\x51\x67\x10\ +\x0c\xb9\x5c\x5d\x17\xd7\x6d\x40\x30\xb8\x6a\x34\xda\xe2\xf5\xd4\ +\x1f\x04\x43\x30\x9b\xd7\x84\x6c\x40\x30\xe4\x52\xa5\x1e\x64\x03\ +\x82\x31\x1a\xed\xf6\xbd\xe5\x04\x04\xa3\x7b\x21\x1b\x10\x0c\xfa\ +\x90\x0b\xd9\x80\x60\x8c\x46\x5d\x08\x47\x96\x20\x14\xba\x17\xb2\ +\x01\xc1\x90\x0b\xd9\x80\x60\x30\xe8\x68\x54\x4a\x36\x32\x47\x30\ +\xd0\xbd\x90\x0d\x08\x86\x5c\xc8\x06\x04\x83\xe0\x16\x98\x6b\x85\ +\x23\x97\x04\x03\x72\x21\x1b\x28\x5e\x6b\x1b\xc9\x9a\x93\x0d\xc1\ +\x40\xf7\xd2\xb8\x6c\xac\x27\xc1\xd8\x2c\xd6\x9a\x6c\x08\x06\x46\ +\x23\xb2\x01\xc1\xe8\x5e\xb0\x5a\x38\xd6\x9e\x60\xc8\x05\xe3\xca\ +\x26\x4d\x1c\x06\xe1\x37\x1a\x91\x0d\xb9\x10\x8c\xee\x05\x3d\xca\ +\x26\x4d\x1e\x04\x1b\x80\x5c\xc8\xa6\xe0\xbe\x48\x1f\xbc\x58\x9a\ +\x24\x20\x36\x81\xd1\x48\xad\x2a\xef\x91\x99\xba\x1a\x9b\x40\xf7\ +\xa2\x6e\x15\xf7\xcb\x6c\xd7\x64\xee\x64\x8c\x5c\x4e\xf9\x31\xdf\ +\x25\x82\xd3\x47\x4e\xad\xd3\x16\x41\x71\x02\x21\xac\x08\x5c\xba\ +\x35\x7c\xad\x7c\x98\x67\x35\xfd\xac\xb6\xdd\xd7\x18\x7d\x87\xb0\ +\xfb\x0f\xf2\x5b\x4d\x87\x3f\x48\x6a\xb7\x88\x30\x1a\x69\xdf\x27\ +\xab\x77\x2d\x5c\x83\xd9\x26\x6c\x3e\x8c\xc3\x18\x04\x23\x7c\xe7\ +\x3c\x87\x10\xfe\xd8\x45\xb0\x31\xb4\xca\x50\x7b\x1d\x8c\xd1\x08\ +\x00\xc1\xe8\x00\xa1\xde\x04\xa3\x3d\x06\x08\xc6\x68\x04\xb9\x00\ +\xc1\x68\x95\x01\x1b\xc5\x68\x84\x4e\x32\x11\x85\xb8\x13\xc1\x34\ +\x78\xcf\x03\xb7\x61\x40\xf7\x87\x4e\xda\x71\xf3\xdf\xb5\xbe\x08\ +\x8d\x0d\xb9\xe4\x02\xf4\x28\x18\xa7\x14\x3a\x42\x16\x08\xa6\x48\ +\x43\x05\x80\x60\x9c\x58\xb8\x8d\xfb\x10\xc2\xaf\x10\xc2\x31\x84\ +\xf0\xf8\xfa\xbc\xd7\x03\x29\x0a\x74\x53\x77\xf7\x71\xdf\x0f\x84\ +\xbf\x67\x21\x78\x94\x93\x31\x25\x33\xdf\x5b\x40\x0b\x1c\xcf\x42\ +\x70\x5c\xce\x0b\x5a\x1f\x91\x62\x58\x7f\x93\x54\xa3\x11\x4a\xf0\ +\x74\xf6\xfc\x5b\xde\xe1\x04\x18\x8d\xb0\xcc\xfd\xeb\x58\x74\xfc\ +\x3c\x10\x32\xe3\x84\x5e\x25\x98\x4d\xd7\xc6\x6f\xe9\x4d\x97\x1b\ +\xe5\x6d\x79\x44\x1a\x5d\x2e\xfa\x68\x10\x0c\xb9\x14\x7f\x41\x92\ +\x91\x25\x82\x31\x36\xbe\x19\xb2\x0f\x1b\xa6\x47\x1f\xdd\xaf\x3d\ +\xb6\xce\xc2\x54\x9b\xc9\x89\xf3\x5e\x2e\xd7\x2c\x5a\xb2\xd8\xd3\ +\xb4\x26\xf1\xfd\x3f\x2b\xb3\x0e\xa6\x7e\x3b\x2b\x75\x20\x18\xdd\ +\x1c\xb9\xa0\xab\x83\x8b\x60\x3a\x1c\x8d\xb6\x12\x4a\x23\xbf\x2c\ +\x08\x10\x4c\x8b\x27\xcc\xa9\x1c\x5e\x56\x9a\x88\x58\xc6\x6d\x6f\ +\x2f\x64\x41\xb9\xed\x83\x3a\xdd\x0b\x8c\x46\x72\x35\x6f\x07\x23\ +\x04\x00\xc1\xec\x3b\x1a\x01\xa6\x01\x82\x11\x08\x80\x60\x8c\x46\ +\x03\x2c\xde\x43\x0a\x21\xa5\x10\x0e\x29\x84\x07\x2b\xa2\x63\xc6\ +\xdb\x42\xfb\xb3\xfa\xdb\xe5\x72\xfa\x20\x99\xbc\xac\x41\xd1\xb1\ +\xb0\x78\x87\x0b\x82\x39\x58\x99\xac\xbc\x19\x91\x8c\x46\x80\x31\ +\x09\x46\x23\x23\x92\x8e\x19\x0a\x3d\xb4\x64\x5c\xe4\x35\x26\xad\ +\x22\x0e\x5e\xe4\x99\x3e\x2f\xda\xcc\xde\xd4\x99\x1b\xf9\x1a\x4c\ +\x34\x13\xa3\xd1\x83\x8e\x60\x74\x68\x00\x08\x66\xbd\x64\x74\x31\ +\x70\xb0\x11\x8c\xb6\x15\x20\x98\x7e\x4f\x14\x92\x81\x03\x8d\x60\ +\x48\x06\x20\x18\x92\x01\x96\xb2\x46\x30\x24\x03\x18\x93\x08\xc6\ +\xe9\x02\x10\x4c\x47\x92\xd1\xc5\xc0\x41\x46\x30\xda\x58\x80\x60\ +\xfa\x3d\x61\x48\x06\x0e\x30\x82\xd9\xac\x8a\x24\x03\x98\x11\x77\ +\xf1\x92\xf5\xc1\xd6\xd9\x9a\x2a\x53\xae\xc1\xe8\x64\x60\x4c\x22\ +\x18\x1d\x1d\x40\x30\x23\x4a\x46\x17\x03\x07\xd7\x95\x7c\x51\xfb\ +\xec\xb6\x56\x77\x83\x26\x67\xac\x96\x83\xa9\x83\xc9\x3f\x71\x74\ +\x32\x28\xe5\x88\x9b\xbe\xb0\xe5\x60\x12\x0c\xc9\x00\x04\x43\x32\ +\x18\x30\x4b\x04\x03\x92\x01\x08\xc6\xe9\x83\xbe\x48\x5b\x05\x31\ +\xda\x38\xc3\x86\xc2\xfa\x41\x7e\x74\x30\x6d\x9d\x42\xd0\x09\x1b\ +\x91\x90\x1b\x10\x92\x81\x03\x8a\x60\x48\x06\x20\x18\x92\x81\x31\ +\x89\x60\x40\x32\x00\x9b\xd6\x9f\xa3\xe3\x0c\x1f\x52\x78\x64\x46\ +\x07\xb3\x8f\xa4\x75\x31\x00\xc1\x54\x3f\xf0\x81\xe9\x9a\x3f\x82\ +\x29\x17\x18\x92\xc1\xf4\x07\x12\xc1\x90\xcc\x55\x7c\xb5\xfb\xa1\ +\x55\x6b\x26\x8f\xb1\xea\x0b\xa0\x48\x31\x63\x77\x2f\x3f\x78\x07\ +\x93\xce\x1e\xdf\x75\x32\x59\xfc\x5c\x78\x0e\x9d\x0b\x2e\x08\x26\ +\xcd\xfd\xf1\xb3\x39\x9e\x7d\xe1\x51\x94\x46\xcd\xee\xf0\xdb\xc3\ +\x35\x98\x7a\x9d\x4c\x76\x80\x9e\x16\x9e\x43\xe7\xd2\x73\x3b\xdf\ +\xce\xc5\x88\x31\xb3\x1a\xad\x5b\x9f\x72\x89\xe5\xbf\xe5\x70\x65\ +\x26\x98\x1d\x32\x9b\x2e\x2c\x03\x81\x74\x7e\x2a\x10\x4c\x7d\xc1\ +\x20\x5f\x32\x0a\x21\x1b\x23\x46\x41\xae\x1b\x91\x8c\x42\x98\xc2\ +\x08\x06\xc5\x24\xa3\x10\xc3\x8b\x63\xca\x3d\xe9\x7f\x76\xac\x3b\ +\xab\xc7\xe0\x87\x14\xb3\x8a\x63\x4a\x1c\x9c\x75\x05\x33\xc5\xdc\ +\x4d\x1c\xf6\x9e\x0e\xa6\xbd\xb0\x93\xcc\x18\xdd\x86\x3a\x5a\x8c\ +\xdd\x3b\x98\x0d\xbe\x9c\x38\xec\x15\x82\x21\x18\x92\x21\x0e\x82\ +\x41\xdf\x9e\x22\x0e\xb9\x26\x18\xf4\x2d\x19\xdd\x06\x14\x6c\x20\ +\xc9\xc4\x9d\xbe\xaf\x1c\x42\x61\x49\x86\x38\xd0\x3e\x6e\xd7\xd0\ +\xdf\xe8\x94\x32\x1f\xa5\xde\x50\x4c\x21\xc4\xc3\x7f\x79\x2c\x3d\ +\xa0\x83\x41\x07\x52\xd9\x35\x0b\x49\x98\x40\x30\x24\x53\xb2\xc6\ +\x49\x98\x40\x30\x53\x49\x26\xb6\xf6\xe6\x84\x09\xe7\xf8\x53\x01\ +\xf2\x07\x8a\xe1\x22\x2f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\xb0\x07\xff\x00\xcf\xe0\xb9\x5d\xbc\xc2\x7e\ +\xd0\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x00\xe2\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x12\x00\x00\x00\x12\x08\x06\x00\x00\x00\x56\xce\x8e\x57\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ +\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ +\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdd\x06\x13\ +\x09\x32\x07\xd3\x5d\x52\xdd\x00\x00\x00\x62\x49\x44\x41\x54\x38\ +\xcb\xb5\x93\xc1\x0a\xc0\x30\x08\x43\x13\x4f\x65\xff\xff\xbf\xed\ +\xc5\x43\x61\xcc\x45\x5b\x03\x42\xa1\xfa\x08\x6d\x04\x62\x11\x80\ +\x01\x78\xfc\x2c\x69\x7a\x65\xf4\x39\x93\x81\xfd\xf6\x2a\xb0\xbd\ +\xc7\xaa\xb0\xfd\x8e\x55\xeb\x95\x77\x7c\x0d\x86\x10\x8a\xb0\x4c\ +\xbf\x0c\xeb\x75\x64\x22\x84\xaa\xb3\xca\xaf\xf1\x34\xb5\x72\x04\ +\xb2\xc9\x46\xdb\xae\x5d\xdd\xfe\x56\x0d\xaf\x50\x0b\x2c\xd6\x30\ +\xf9\x55\x1c\xba\xaf\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\ +\x82\ +\x00\x00\x01\x55\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x12\x00\x00\x00\x12\x08\x06\x00\x00\x00\x56\xce\x8e\x57\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ +\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ +\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdd\x06\x13\ +\x09\x27\x12\x89\x35\x50\x22\x00\x00\x00\xd5\x49\x44\x41\x54\x38\ +\xcb\x9d\x94\xdb\x0e\xc2\x20\x10\x44\x4f\xaf\x6a\xed\xcd\x9a\xf8\ +\xa2\x4f\xfe\xff\x87\xf8\x4f\x55\xeb\xcb\x6e\xb2\x45\x68\x69\x27\ +\x21\xc0\xb2\x0c\x33\x2c\x01\x96\x91\x02\x89\x34\x9d\x07\x13\x43\ +\xc8\x80\x13\xf0\x95\x36\x01\xc5\xca\x9e\x3f\x14\xc0\x41\x36\x4f\ +\x12\xd3\xb1\x1e\xb0\xaa\xa8\x02\x4a\xe0\x25\xf3\xc4\xe9\xdf\x32\ +\xae\x97\x94\x34\x42\x6e\x95\xb8\x98\x8c\xcd\xd6\x97\xd0\x46\x90\ +\xb8\x64\x39\x70\xb6\xd6\x0a\x60\x04\x3e\x8e\x8d\x10\x74\x7d\x94\ +\x71\x8e\x78\xcd\x22\x95\x84\x94\x35\xca\xbe\x87\x04\x7b\x15\xa9\ +\xd8\xc2\x57\xd2\x08\x24\xc0\xc5\x27\xb1\xdf\x40\x52\xc9\xe1\x33\ +\x37\xf6\x8e\xea\x08\x92\x41\xaa\xa5\x7b\x66\xef\xf1\x18\x49\x96\ +\xcb\xba\xe6\x56\x21\xcf\x4b\x64\x77\xe0\x19\x52\xe2\xc2\x2a\x1b\ +\x4c\xfc\x06\x3c\xcc\x5a\xb9\xa9\xac\xc0\xd5\x5c\xae\xc6\x36\x55\ +\xd8\x2a\xb3\x3f\x41\xb7\xe3\x99\xd0\x19\x02\xfd\x42\xbc\xf8\x01\ +\x6d\x2c\x3c\xd9\x8c\xba\x79\x52\x00\x00\x00\x00\x49\x45\x4e\x44\ +\xae\x42\x60\x82\ +\x00\x00\x06\x8b\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x01\x10\x00\x00\x00\x68\x08\x02\x00\x00\x00\xb6\x6e\x38\x5a\ +\x00\x00\x00\x15\x74\x45\x58\x74\x43\x72\x65\x61\x74\x69\x6f\x6e\ +\x20\x54\x69\x6d\x65\x00\x07\xe2\x05\x02\x09\x20\x2a\x61\x5e\x62\ +\x59\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xe2\x05\x02\x09\x22\x37\ +\xe5\x6f\x8f\x72\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0e\xc3\ +\x00\x00\x0e\xc3\x01\xc7\x6f\xa8\x64\x00\x00\x06\x09\x49\x44\x41\ +\x54\x78\xda\xed\xdd\xbf\x6b\xdb\x68\x1c\xc7\x71\x39\x5c\xa7\xbb\ +\xeb\x90\x06\x13\xd2\x2e\x86\x26\x2d\xa6\x74\xc8\x41\x39\xf0\x92\ +\x21\xd0\xa6\xab\x17\x4f\xdd\x33\x78\xc9\x3f\x60\xfc\x0f\x64\xd1\ +\x90\x3d\x93\x97\x8c\x6d\x33\x74\xf0\xe2\xe1\x5a\xe8\x60\x8a\xa1\ +\x4d\x82\x97\x26\x14\xe3\x06\xd2\x1b\x0b\xf1\x3d\x8f\x25\xeb\xe7\ +\x23\x47\xdf\xda\x3e\xa3\xf8\xfd\x22\x84\x58\xcf\x23\xf9\x79\x24\ +\x7d\xa4\xe7\x91\x43\x92\xb3\x6d\xdb\x02\x90\xce\xd2\xbc\x1b\x00\ +\x64\x09\x81\x01\x04\x08\x0c\x20\x40\x60\x00\x01\x02\x03\x08\x10\ +\x18\x40\x80\xc0\x00\x02\x04\x06\x10\x20\x30\x80\x00\x81\x01\x04\ +\x08\x0c\x20\x40\x60\x00\x01\x02\x03\x08\x10\x18\x40\x80\xc0\x00\ +\x02\x04\x06\x10\xc8\x70\x60\x1e\x9c\x9c\x3c\xf8\xf2\x65\xde\xad\ +\xc0\x62\xc9\x70\x60\xd6\x4e\x4f\xd5\xd7\xbc\x5b\x81\xc5\x92\xe1\ +\xc0\x00\xff\x3f\x02\x03\x08\x10\x18\x40\x80\xc0\x00\x02\x04\x06\ +\x10\x20\x30\x80\x00\x81\x01\x04\x08\x0c\x20\x40\x60\x00\x01\x02\ +\x03\x08\x10\x18\x40\x80\xc0\x00\x02\x04\x06\x10\x20\x30\x80\x00\ +\x81\x01\x04\x08\x0c\x20\x40\x60\x00\x01\x02\x03\x08\x10\x18\x40\ +\x80\xc0\x00\x02\x04\x06\x10\x20\x30\x80\x00\x81\x01\x04\x08\x0c\ +\x20\x40\x60\x00\x81\xdf\xe6\xdd\x00\x89\xc1\xe0\xd9\xf1\xb1\xf7\ +\xea\xfe\xc9\x89\xfa\xfe\xec\xed\x5b\x6f\xc9\xfb\x9d\x9d\x79\x37\ +\x11\xb7\x5c\x96\xee\x30\x7f\xbf\x7e\xad\xff\x36\xec\x60\xe0\x7c\ +\xfd\x58\x5e\xfe\x77\x79\xd9\x7b\xa9\xf2\xf3\xec\xcd\x9b\x79\xb7\ +\x11\xb7\x5c\x96\xee\x30\x03\xcb\x3a\xdf\xd8\xf8\xf0\xfc\xb9\xb1\ +\x54\xdf\x6a\x54\x72\x80\x59\xca\xd2\x1d\x06\x98\x3b\x02\x03\x08\ +\xcc\x70\x48\x76\xff\xf4\xd4\x99\x97\x4f\xcb\xda\xd9\xd9\x55\x3e\ +\x9f\x54\xfa\xe7\xe5\xe5\xdd\xef\xdf\x83\xcf\x00\x26\x77\xbe\xbe\ +\x7e\xfe\xf0\xe1\xec\x76\x11\x32\x67\x96\x73\x18\x67\x3a\x3e\x3d\ +\xb9\xb1\x5b\xcb\x8d\x66\xff\x53\xee\x02\x10\x30\xc3\xc0\xe8\xcb\ +\xf3\xfa\xfa\x14\x37\x98\xbb\xbe\xbe\xbe\x73\x27\xa9\xf4\xc7\xbd\ +\x7b\x3f\x96\x97\xdf\xbf\x7c\x39\xbb\x1e\x01\xcc\x61\x00\x01\x02\ +\x03\x08\x64\xe9\x73\x18\x6b\x69\xe9\xfe\xe7\xcf\x4b\x3f\x7f\x3a\ +\xaf\xee\x5e\x5e\xaa\xef\x6a\x18\xe6\xbc\x5c\x3b\x3b\xfb\xba\xb1\ +\x31\xef\x26\xe2\x96\xcb\xd2\x1d\xe6\x9f\x9d\x9d\xf3\x47\x8f\xd4\ +\x34\xc6\xf9\xfa\xfd\xea\xea\x8f\xab\x2b\xef\xe5\xd7\xc7\x8f\x3f\ +\xf0\xab\x31\x98\xb1\x4c\xdd\x61\x72\xb9\xe0\xc7\xfc\xb9\xeb\x6b\ +\x6b\x30\x48\xfa\xe0\x1f\x98\x85\x2c\xdd\x61\x80\xb9\x23\x30\x80\ +\x00\x81\x01\x04\x08\x0c\x20\x40\x60\x00\x01\x02\x03\x08\x10\x18\ +\x40\x80\xc0\x00\x02\x04\x06\x10\x20\x30\x80\x00\x81\x01\x04\x08\ +\x0c\x20\x40\x60\x00\x01\x02\x03\x08\x64\xea\xd7\xfb\xa7\xad\x5a\ +\xad\xce\xbb\x09\x73\x63\xdb\x76\xf0\x25\xbb\x22\xa5\x85\x0e\x8c\ +\x25\xdc\x59\xb7\x86\x31\x1e\xec\x8a\x34\x18\x92\x01\x02\x04\x06\ +\x10\x20\x30\x80\x00\x81\x01\x04\x08\x0c\x20\x40\x60\x00\x81\x45\ +\x7f\xac\x6c\xd2\x69\xec\xf7\xb6\xf7\xb6\x56\x42\x8b\xaa\x07\x17\ +\xe5\x9a\x5e\xa8\x7f\x6c\xe9\x65\xa5\x5d\xbb\x52\x1c\x15\xb6\xdc\ +\x8a\x85\x61\xa5\xb1\x75\xdc\x65\xf1\xb5\x02\xef\xd7\x6f\xee\xd7\ +\x8f\xba\xa1\xa2\x58\x7d\xcb\xab\xe3\x56\xf2\x57\x0a\xbd\xd1\x84\ +\xfb\x62\x4c\xc7\xa3\xbb\xcb\xdc\x80\xd1\x7a\x91\x4e\xa6\xd8\xb2\ +\x5f\x71\xfc\xfb\x9a\xb6\x99\x58\x67\x42\x04\x26\xc4\x3d\xe4\x85\ +\xf2\x76\x60\xa1\x3a\x0c\xc7\x56\xa9\xe0\x96\x1f\x58\xbb\xb6\xed\ +\x9c\xf0\xfb\xcd\xd1\xb1\x09\x1e\x4b\x53\x9d\x4e\xdb\x5d\xa2\x4b\ +\x0f\x9b\xfd\xe2\x70\xb5\xa4\x33\xba\xdf\x3c\xfc\xb8\x59\xb3\xf7\ +\x56\x86\x5b\x38\x6c\x3e\x31\xbc\x8b\x2a\xfa\xb6\xb6\x6b\xef\x15\ +\x2d\xbf\xd2\xd6\x9e\xbd\x15\x68\xf3\x6a\xe2\xbf\x06\x49\xe9\xc6\ +\x8e\x47\x76\xd7\x4a\xbc\x01\xfa\x14\x1e\x75\xdd\xbc\x65\xf5\xb3\ +\x5b\x43\x6f\xad\xd1\x89\xec\x12\x63\xe9\x4d\x87\x29\xf1\x50\x4e\ +\x8e\x21\x59\x88\x3e\xe4\xb5\x72\x21\xb0\x44\xed\xf7\xe3\xd5\xda\ +\xde\xf6\xea\xf0\x55\xef\x5b\xb7\xf4\xd4\x39\xa0\xc5\xa7\xa5\xee\ +\xb7\x9e\x61\x1b\xa6\x3a\xc5\xca\xe8\x34\x50\xa5\x6b\xf9\xf4\x57\ +\xbc\xfc\x6a\x21\xa9\xc8\xdf\x66\xac\x52\xbf\x79\xdc\x2a\xbd\x98\ +\xec\xc2\x9a\xa6\xe3\xf1\xdd\x15\x69\x40\xa7\xad\x2e\xf8\xd1\x8b\ +\x42\x78\xcb\x9d\x76\xcb\xdd\xf2\xca\x93\xcd\xc2\x45\xaf\x1f\xaa\ +\x6b\x2e\xbd\xe9\x30\x8d\x6d\xdb\x64\x32\x7c\x87\xd1\xff\x4b\x63\ +\xc6\xff\xbf\x45\xdf\x0e\xac\x57\xfa\x62\xea\x1f\x46\x7d\xd0\x8a\ +\xfa\x54\x54\xa7\xa9\xf7\x73\xeb\xa0\xea\x8d\x3a\x92\xea\x38\x97\ +\xbc\xe0\x5d\x22\xb8\x56\xf0\xe4\x56\xc7\xfa\x45\xc3\xfd\x08\x5a\ +\x17\xae\x8c\xaf\xdf\xff\xf4\xd1\xda\x7c\x15\x58\xd0\x79\x77\x64\ +\x95\x6b\x93\x8c\xc6\x52\x77\xdc\x6c\xd4\x80\x4e\xa3\xd5\x6d\xb5\ +\xaa\x47\xc3\x85\x4e\xcf\x4d\x5b\xf6\xfa\x9d\x5f\xeb\xb6\x55\x14\ +\xcd\x1b\x4e\x2e\x1d\xb7\xcd\x69\xcb\x72\x60\x66\xfe\xbf\xc1\xd4\ +\x99\xd8\xed\x76\xeb\xee\x01\xb7\xac\xfa\x7e\xb9\x56\xdb\x3c\xf4\ +\x17\x94\x76\xf5\xd1\x2b\x56\x6c\xbb\xa2\x5f\xba\x43\xa3\x4a\xad\ +\xb7\x1f\xad\x63\x8d\x06\x2c\x7a\x40\xd3\xd3\x27\x7c\x74\xad\x60\ +\x04\x46\xe3\x90\x61\xc6\xde\x75\xb6\x74\xc4\x92\xea\x77\x1a\x75\ +\x35\x7e\xdb\xf3\xd7\x76\xae\xee\xf6\x24\xb7\x97\x94\x1d\x4f\x5a\ +\xdb\x6b\x40\xcf\x1f\x46\xba\x23\xaa\xed\x5e\x74\xcb\xa5\xbf\xa6\ +\x7f\x98\xac\xda\x54\xe7\x2d\x41\x19\x0e\xcc\xec\x05\xc6\xe4\xfe\ +\x45\xcc\x72\x97\x0d\x97\x84\x67\x09\xa3\xa1\x91\xb7\x9e\xa1\x8e\ +\x1a\xd0\x1c\x84\xaf\x93\xa6\x01\xd5\x45\xf9\x55\xc5\xd9\x50\xcd\ +\xf2\xe7\x3c\xf1\xfa\xc3\x99\x42\xf8\xec\x98\xfc\xf6\x22\xef\x78\ +\x88\xb9\x01\x7a\x44\xf5\xb1\xd7\x8f\x6f\x39\xff\xae\xda\x1e\x75\ +\xbc\x77\x51\x58\x4d\x9c\x72\x24\x96\x9a\x5b\x3b\x23\xcc\x61\x7e\ +\x8d\x9a\x97\x1f\x59\x9b\x4f\x9c\xc7\x57\x8d\x8e\xb3\x4c\x5d\xe9\ +\x82\xf3\x13\xbf\x4e\xbf\xd9\x68\xba\xa3\x05\x35\x28\x2f\xe8\xd9\ +\x78\x7c\x2d\x75\xb0\xab\xc3\x65\x7a\xec\xa1\xee\x2b\xe1\x52\x73\ +\xfd\x68\x5a\xa6\x32\x7b\x49\xdb\xf1\x84\xf2\x40\x03\xd4\xd5\xa1\ +\x75\xec\xf4\x3c\xba\x73\x3c\xba\x4e\xbb\x13\xae\xe2\xed\x0a\x63\ +\xe9\x5c\x71\x87\x09\xf1\x1e\x8c\xaa\x1b\xbc\xf9\x19\x56\xfc\x21\ +\x69\x7e\xf5\xa2\xee\xfe\xca\xab\x5a\xa5\x68\xaa\xb3\x92\xb7\x8e\ +\xea\xfe\x50\x7e\xb8\x2c\xb6\x96\xaf\x58\xa9\x95\xf7\xeb\xfe\x1c\ +\xa6\x68\x7a\x17\x7d\x02\x59\xfe\x48\xc4\x7d\xd2\x7c\x38\xf9\xed\ +\x25\x81\xe9\xe9\x70\x6c\x77\xf5\x23\x0d\x28\xfa\xc3\xd3\x51\x47\ +\x62\x8a\x95\xdd\xb6\x3b\x61\x8b\xed\x89\x84\xd2\x9b\x0f\x53\xba\ +\x3a\xbf\x20\xb7\x98\xbf\xd4\xed\x50\x07\x62\x31\xbb\x1f\xef\x38\ +\xbb\x22\x25\x86\x64\x80\x00\x81\x01\x04\x08\x0c\x20\x40\x60\x00\ +\x01\x02\x03\x08\x2c\xfa\x63\xe5\x45\xfe\x6b\x29\x11\xec\x8a\x34\ +\x16\xfa\xb1\x32\x20\xc5\x90\x0c\x10\x20\x30\x80\x00\x81\x01\x04\ +\x08\x0c\x20\x40\x60\x00\x01\x02\x03\x08\x10\x18\x40\x80\xc0\x00\ +\x02\x04\x06\x10\xf8\x0f\xc1\x9c\x7c\x5d\x33\x4a\x57\x22\x00\x00\ +\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x01\x0b\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x12\x00\x00\x00\x12\x08\x06\x00\x00\x00\x56\xce\x8e\x57\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ +\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ +\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdd\x06\x13\ +\x0d\x07\x0f\x78\xbe\xb0\x85\x00\x00\x00\x8b\x49\x44\x41\x54\x38\ +\xcb\xbd\x93\xbb\x0a\x80\x30\x0c\x45\x4f\x2b\xbe\x3a\x88\x88\x83\ +\xa3\xa3\xff\xff\x65\x8e\x82\xa0\x2e\x41\x1c\x6a\x4d\x04\xbd\x50\ +\x12\xc8\xcd\x6d\x1e\x2d\xa4\xe1\x00\x0f\x04\xf1\xbf\x47\x26\xb6\ +\x05\x2a\x60\x31\xe4\x96\x52\xed\x86\x38\x00\x33\x90\x03\x9d\x52\ +\x64\x92\x22\xd6\x3b\xc2\x2e\x27\x85\x2b\xc7\x6b\x89\xa9\x98\x6a\ +\xf8\x31\x31\x4d\xb5\x8f\x62\x8f\x22\xee\x42\xb4\xc0\x49\xce\x69\ +\xfd\x1f\x6f\xcc\xd4\xda\xdb\x61\xbf\xde\x98\x79\x73\xd6\x07\x19\ +\x45\x00\x46\x65\xeb\x03\x50\xc7\x02\x3d\x50\x18\xe7\xd8\xc8\x47\ +\xff\x1f\x95\xe6\xe6\x03\x00\xf2\x27\xca\xc2\x2b\x23\x90\x00\x00\ +\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x00\xda\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x12\x00\x00\x00\x12\x08\x06\x00\x00\x00\x56\xce\x8e\x57\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ +\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ +\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdd\x06\x13\ +\x0d\x01\x01\xc9\x5c\x3a\x04\x00\x00\x00\x5a\x49\x44\x41\x54\x38\ +\xcb\x63\x60\xc0\x0f\x18\x19\x18\x18\x98\x18\x18\x18\xb8\xa0\x6c\ +\xfa\x02\x46\x6a\xda\x4a\x7f\xe7\x0f\x1a\x97\xe1\xb4\x80\x19\x4a\ +\x73\x20\x05\x36\x1b\x92\x86\xff\x48\x6a\xb0\x19\x86\x61\xf0\x7f\ +\x3c\x98\x17\x6a\x18\x8c\xcf\x02\x35\x00\xc6\x67\x60\x80\x26\x36\ +\x42\xe0\x2f\x54\xf3\x30\x02\x8c\xc3\xcb\x3b\x4c\x43\xc2\xfb\x1c\ +\x50\x8c\x17\x00\x00\x3c\xb4\x0d\x4d\x25\x0f\x12\x1c\x00\x00\x00\ +\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ +\x00\x00\x01\xb4\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x12\x00\x00\x00\x12\x08\x06\x00\x00\x00\x56\xce\x8e\x57\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ +\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ +\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdd\x06\x13\ +\x0c\x17\x1e\x59\x0e\xe8\x11\x00\x00\x01\x34\x49\x44\x41\x54\x38\ +\xcb\x9d\xd4\x3d\x2f\x04\x51\x14\x06\xe0\x67\x86\x4d\x2c\xb1\x61\ +\x4b\x3f\x42\xb5\x3f\x40\x21\x91\x68\x54\x3a\x89\xa8\xb5\x12\xa2\ +\x91\xf8\x07\x0a\x85\x88\x5e\x23\xd1\xa9\x10\x05\x11\x4a\x54\x3e\ +\x62\x45\x48\x14\x28\x2c\x85\x04\xcd\xdd\x64\xb2\xee\xec\x2c\x6f\ +\x77\xee\x79\xcf\x3b\xe7\x6b\x4e\x22\x1f\x29\xaa\xe8\x0d\x76\x03\ +\xcf\xf8\x6e\x13\xf3\xcb\x39\x85\x53\x5c\x63\x0f\xfb\xb8\xc1\x31\ +\x26\x0b\x62\x7d\xa3\x82\x93\x20\x30\x1a\xf9\xe0\x38\xee\x70\x10\ +\x32\x8d\x66\xd7\x8f\x4b\xcc\x6b\x8f\x14\x4b\x41\xa4\x1c\x23\x1c\ +\x62\xa1\xa8\x07\x99\xec\x97\xb1\xd3\xea\x98\x44\x1d\x49\x5e\xdd\ +\x91\x9e\x94\xf0\x84\xb1\xac\xf3\x08\x13\x45\x4d\x8c\xbc\x4d\x63\ +\xb7\x69\x54\x71\xdb\xc1\x34\xf3\xb2\xac\xa3\xd2\x8d\x1e\xdc\xe7\ +\x90\x92\x8c\x40\x92\xc3\x79\x42\x39\x0d\x46\x97\xff\xa3\xab\x39\ +\xca\x77\x0c\xb5\x29\x2d\x69\xc9\x2c\x8b\x12\x06\xd1\x48\xf1\x8a\ +\x2b\xcc\xe4\x88\xc4\xca\x6c\x62\x16\x17\x78\x6b\x3e\x8c\xe0\x25\ +\xb3\x60\x9d\x8c\xbf\x12\x04\x6a\xad\x84\x4d\xac\xfc\x61\x21\xd7\ +\xb1\x16\x73\x76\x07\xc2\x6a\xf8\x5d\xf2\x30\x80\x8d\xd6\xd2\xd3\ +\x0c\xe1\x33\x88\x95\xf1\x88\x39\xf4\x65\xfc\x15\x2c\xe2\x01\x1f\ +\x21\xf6\xab\x68\x63\x6b\xd8\x0e\xa7\xe3\x0c\xe7\xe1\x22\x6c\x61\ +\x38\x16\x9b\x14\xf4\xa2\x37\x73\x2e\x3e\xc2\xaa\x44\xf1\x03\x0f\ +\x82\x4d\xc9\xa9\xd9\x22\xf7\x00\x00\x00\x00\x49\x45\x4e\x44\xae\ +\x42\x60\x82\ +\x00\x00\x00\xc2\ +\x89\ +\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ +\x00\x00\x12\x00\x00\x00\x12\x08\x06\x00\x00\x00\x56\xce\x8e\x57\ +\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ +\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ +\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ +\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdd\x06\x13\ +\x0b\x2b\x0c\xdc\x0b\xf6\x23\x00\x00\x00\x42\x49\x44\x41\x54\x38\ +\xcb\x63\x60\x18\x8a\xe0\x3f\x14\xe3\x05\x4c\xd4\xb2\x8d\x91\x08\ +\xd7\x90\xa2\x9e\x28\x2f\xe1\xf5\x22\xd3\x40\x05\x30\x4e\x57\x31\ +\x0d\x64\x74\x63\x95\x67\x1a\xd0\xc4\x87\x4d\x1d\xd3\x40\xba\x06\ +\xab\x7a\x16\x22\x52\x33\x51\x80\x89\x61\xd8\x02\x00\x13\xce\x17\ +\xfb\xcd\x4f\x38\x19\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\ +\x82\ +" + +qt_resource_name = b"\ +\x00\x07\ +\x07\x3b\xe0\xb3\ +\x00\x70\ +\x00\x6c\x00\x75\x00\x67\x00\x69\x00\x6e\x00\x73\ +\x00\x03\ +\x00\x00\x77\x74\ +\x00\x71\ +\x00\x61\x00\x64\ +\x00\x05\ +\x00\x6f\xa6\x53\ +\x00\x69\ +\x00\x63\x00\x6f\x00\x6e\x00\x73\ +\x00\x0b\ +\x06\xc4\x5f\x87\ +\x00\x6c\ +\x00\x61\x00\x6d\x00\x70\x00\x5f\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x09\ +\x0c\xba\x53\xc3\ +\x00\x64\ +\x00\x73\x00\x65\x00\x74\x00\x74\x00\x69\x00\x6e\x00\x67\x00\x73\ +\x00\x18\ +\x0a\x68\xb9\x07\ +\x00\x4f\ +\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x54\x00\x6f\x00\x6f\x00\x6c\x00\x54\x00\x49\x00\x50\x00\x5f\x00\x45\x00\x58\x00\x54\ +\x00\x49\x00\x4e\x00\x54\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x16\ +\x07\x37\x37\xc7\ +\x00\x4f\ +\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x54\x00\x6f\x00\x6f\x00\x6c\x00\x54\x00\x49\x00\x50\x00\x5f\x00\x4d\x00\x49\x00\x44\ +\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x0e\ +\x0a\x3f\x8a\xe7\ +\x00\x4f\ +\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x50\x00\x45\x00\x52\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x17\ +\x0d\x56\x54\xc7\ +\x00\x4f\ +\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x54\x00\x6f\x00\x6f\x00\x6c\x00\x54\x00\x49\x00\x50\x00\x5f\x00\x50\x00\x41\x00\x52\ +\x00\x4c\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x10\ +\x0a\x81\xb0\xc7\ +\x00\x4f\ +\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x49\x00\x4e\x00\x54\x00\x41\x00\x50\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x10\ +\x02\xe5\x2c\xc7\ +\x00\x4f\ +\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x45\x00\x58\x00\x54\x00\x49\x00\x4e\x00\x54\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x16\ +\x00\x97\x06\x87\ +\x00\x4f\ +\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x54\x00\x6f\x00\x6f\x00\x6c\x00\x54\x00\x49\x00\x50\x00\x5f\x00\x54\x00\x41\x00\x4e\ +\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x0e\ +\x03\xcf\x85\xa7\ +\x00\x4f\ +\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x49\x00\x4e\x00\x53\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x16\ +\x07\x37\x08\xa7\ +\x00\x4f\ +\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x54\x00\x6f\x00\x6f\x00\x6c\x00\x54\x00\x49\x00\x50\x00\x5f\x00\x45\x00\x58\x00\x54\ +\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x0f\ +\x02\xbe\x00\x67\ +\x00\x4f\ +\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x4e\x00\x45\x00\x41\x00\x52\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x16\ +\x09\x37\x08\x27\ +\x00\x4f\ +\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x54\x00\x6f\x00\x6f\x00\x6c\x00\x54\x00\x49\x00\x50\x00\x5f\x00\x49\x00\x4e\x00\x54\ +\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x13\ +\x05\x6b\x7f\x47\ +\x00\x64\ +\x00\x79\x00\x6e\x00\x61\x00\x6d\x00\x69\x00\x63\x00\x5f\x00\x70\x00\x72\x00\x6f\x00\x6d\x00\x70\x00\x74\x00\x73\x00\x2e\x00\x70\ +\x00\x6e\x00\x67\ +\x00\x0e\ +\x06\x7f\x8b\x67\ +\x00\x4f\ +\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x54\x00\x41\x00\x4e\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x16\ +\x08\x37\x08\xa7\ +\x00\x4f\ +\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x54\x00\x6f\x00\x6f\x00\x6c\x00\x54\x00\x49\x00\x50\x00\x5f\x00\x45\x00\x4e\x00\x44\ +\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x1a\ +\x0a\x98\x67\x27\ +\x00\x4f\ +\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x54\x00\x6f\x00\x6f\x00\x6c\x00\x54\x00\x49\x00\x50\x00\x5f\x00\x50\x00\x52\x00\x4f\ +\x00\x47\x00\x52\x00\x45\x00\x53\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x16\ +\x00\xd7\x07\x67\ +\x00\x4f\ +\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x54\x00\x6f\x00\x6f\x00\x6c\x00\x54\x00\x49\x00\x50\x00\x5f\x00\x50\x00\x45\x00\x52\ +\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x16\ +\x04\x97\x09\x07\ +\x00\x4f\ +\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x54\x00\x6f\x00\x6f\x00\x6c\x00\x54\x00\x49\x00\x50\x00\x5f\x00\x43\x00\x45\x00\x4e\ +\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x0e\ +\x02\xdf\x85\x27\ +\x00\x4f\ +\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x45\x00\x4e\x00\x44\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x17\ +\x0c\x35\x59\x27\ +\x00\x4f\ +\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x54\x00\x6f\x00\x6f\x00\x6c\x00\x54\x00\x49\x00\x50\x00\x5f\x00\x4e\x00\x45\x00\x41\ +\x00\x52\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x16\ +\x09\x37\x37\x87\ +\x00\x4f\ +\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x54\x00\x6f\x00\x6f\x00\x6c\x00\x54\x00\x49\x00\x50\x00\x5f\x00\x4e\x00\x4f\x00\x44\ +\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x13\ +\x03\x08\xfa\xc7\ +\x00\x64\ +\x00\x69\x00\x6d\x00\x65\x00\x6e\x00\x73\x00\x69\x00\x6f\x00\x6e\x00\x5f\x00\x69\x00\x6e\x00\x70\x00\x75\x00\x74\x00\x2e\x00\x70\ +\x00\x6e\x00\x67\ +\x00\x1b\ +\x05\x30\x56\xa7\ +\x00\x4f\ +\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x54\x00\x6f\x00\x6f\x00\x6c\x00\x54\x00\x49\x00\x50\x00\x5f\x00\x45\x00\x4e\x00\x44\ +\x00\x5f\x00\x50\x00\x4c\x00\x49\x00\x4e\x00\x45\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x17\ +\x0f\xd6\x51\x27\ +\x00\x4f\ +\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x54\x00\x6f\x00\x6f\x00\x6c\x00\x54\x00\x49\x00\x50\x00\x5f\x00\x51\x00\x55\x00\x41\ +\x00\x44\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x0e\ +\x06\x3f\x8a\xe7\ +\x00\x4f\ +\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x50\x00\x41\x00\x52\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x0e\ +\x0a\x7f\x84\x87\ +\x00\x4f\ +\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x43\x00\x45\x00\x4e\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x1a\ +\x0d\x9e\x6d\x07\ +\x00\x4f\ +\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x54\x00\x6f\x00\x6f\x00\x6c\x00\x54\x00\x49\x00\x50\x00\x5f\x00\x43\x00\x45\x00\x4e\ +\x00\x54\x00\x52\x00\x4f\x00\x49\x00\x44\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x0e\ +\x03\xdf\x85\xa7\ +\x00\x4f\ +\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x49\x00\x4e\x00\x54\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x0f\ +\x01\x5e\x88\x67\ +\x00\x4f\ +\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x51\x00\x55\x00\x41\x00\x44\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x11\ +\x03\x92\x16\x87\ +\x00\x70\ +\x00\x6f\x00\x69\x00\x6e\x00\x74\x00\x65\x00\x72\x00\x5f\x00\x69\x00\x6e\x00\x70\x00\x75\x00\x74\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\ +\x00\x0f\ +\x07\x0e\x8f\xa7\ +\x00\x4f\ +\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x50\x00\x52\x00\x4f\x00\x47\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x0e\ +\x0d\xdf\x85\x27\ +\x00\x4f\ +\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x45\x00\x58\x00\x54\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x0e\ +\x03\xdf\x82\x07\ +\x00\x4f\ +\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x4e\x00\x4f\x00\x44\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ +\x00\x0e\ +\x09\xdf\x82\x47\ +\x00\x4f\ +\x00\x53\x00\x4e\x00\x41\x00\x50\x00\x5f\x00\x4d\x00\x45\x00\x44\x00\x50\x00\x2e\x00\x70\x00\x6e\x00\x67\ +" + +qt_resource_struct_v1 = b"\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ +\x00\x00\x00\x14\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\ +\x00\x00\x00\x20\x00\x02\x00\x00\x00\x02\x00\x00\x00\x04\ +\x00\x00\x00\x30\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ +\x00\x00\x00\x4c\x00\x02\x00\x00\x00\x21\x00\x00\x00\x06\ +\x00\x00\x01\x6e\x00\x00\x00\x00\x00\x01\x00\x00\x24\x2c\ +\x00\x00\x03\x04\x00\x00\x00\x00\x00\x01\x00\x00\x63\x9e\ +\x00\x00\x05\x2c\x00\x00\x00\x00\x00\x01\x00\x00\xcb\xb5\ +\x00\x00\x01\xf4\x00\x00\x00\x00\x00\x01\x00\x00\x31\x53\ +\x00\x00\x03\x68\x00\x00\x00\x00\x00\x01\x00\x00\x70\x3c\ +\x00\x00\x01\x48\x00\x00\x00\x00\x00\x01\x00\x00\x23\x1c\ +\x00\x00\x03\xf0\x00\x00\x00\x00\x00\x01\x00\x00\xa1\xc4\ +\x00\x00\x05\x50\x00\x00\x00\x00\x00\x01\x00\x00\xcd\x0e\ +\x00\x00\x01\xa0\x00\x00\x00\x00\x00\x01\x00\x00\x2a\x20\ +\x00\x00\x05\xbe\x00\x00\x00\x00\x00\x01\x00\x00\xd5\x8a\ +\x00\x00\x05\x0a\x00\x00\x00\x00\x00\x01\x00\x00\xca\xcf\ +\x00\x00\x03\x36\x00\x00\x00\x00\x00\x01\x00\x00\x6a\x4c\ +\x00\x00\x04\x1c\x00\x00\x00\x00\x00\x01\x00\x00\xad\xef\ +\x00\x00\x02\x4a\x00\x00\x00\x00\x00\x01\x00\x00\x47\xa2\ +\x00\x00\x04\x8c\x00\x00\x00\x00\x00\x01\x00\x00\xc2\x60\ +\x00\x00\x02\x76\x00\x00\x00\x00\x00\x01\x00\x00\x4d\x20\ +\x00\x00\x05\x78\x00\x00\x00\x00\x00\x01\x00\x00\xd3\x9d\ +\x00\x00\x01\xc2\x00\x00\x00\x00\x00\x01\x00\x00\x2b\x4b\ +\x00\x00\x00\x9a\x00\x00\x00\x00\x00\x01\x00\x00\x0a\x7b\ +\x00\x00\x02\x98\x00\x00\x00\x00\x00\x01\x00\x00\x4e\xbf\ +\x00\x00\x02\x18\x00\x00\x00\x00\x00\x01\x00\x00\x32\x8c\ +\x00\x00\x03\xbe\x00\x00\x00\x00\x00\x01\x00\x00\x8b\x1d\ +\x00\x00\x05\xe0\x00\x00\x00\x00\x00\x01\x00\x00\xd7\x42\ +\x00\x00\x00\xcc\x00\x00\x00\x00\x00\x01\x00\x00\x1a\xf7\ +\x00\x00\x00\x64\x00\x00\x00\x00\x00\x01\x00\x00\x04\x02\ +\x00\x00\x04\xae\x00\x00\x00\x00\x00\x01\x00\x00\xc3\x1c\ +\x00\x00\x01\x22\x00\x00\x00\x00\x00\x01\x00\x00\x22\x1e\ +\x00\x00\x02\xca\x00\x00\x00\x00\x00\x01\x00\x00\x5e\xc2\ +\x00\x00\x03\x8a\x00\x00\x00\x00\x00\x01\x00\x00\x70\xf0\ +\x00\x00\x00\xee\x00\x00\x00\x00\x00\x01\x00\x00\x1c\x0e\ +\x00\x00\x04\xd0\x00\x00\x00\x00\x00\x01\x00\x00\xc4\xbd\ +\x00\x00\x05\x9c\x00\x00\x00\x00\x00\x01\x00\x00\xd4\xac\ +\x00\x00\x04\x58\x00\x00\x00\x00\x00\x01\x00\x00\xbd\xc5\ +" + +qt_resource_struct_v2 = b"\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x14\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x20\x00\x02\x00\x00\x00\x02\x00\x00\x00\x04\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x30\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ +\x00\x00\x01\x6d\x25\xef\x7b\x69\ +\x00\x00\x00\x4c\x00\x02\x00\x00\x00\x21\x00\x00\x00\x06\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x01\x6e\x00\x00\x00\x00\x00\x01\x00\x00\x24\x2c\ +\x00\x00\x01\x6d\x25\xef\x7d\x21\ +\x00\x00\x03\x04\x00\x00\x00\x00\x00\x01\x00\x00\x63\x9e\ +\x00\x00\x01\x6d\x25\xef\x7d\x24\ +\x00\x00\x05\x2c\x00\x00\x00\x00\x00\x01\x00\x00\xcb\xb5\ +\x00\x00\x01\x6d\x25\xef\x7d\x47\ +\x00\x00\x01\xf4\x00\x00\x00\x00\x00\x01\x00\x00\x31\x53\ +\x00\x00\x01\x6d\x25\xef\x7d\x3e\ +\x00\x00\x03\x68\x00\x00\x00\x00\x00\x01\x00\x00\x70\x3c\ +\x00\x00\x01\x6d\x25\xef\x7d\x2f\ +\x00\x00\x01\x48\x00\x00\x00\x00\x00\x01\x00\x00\x23\x1c\ +\x00\x00\x01\x6d\x25\xef\x7d\x1e\ +\x00\x00\x03\xf0\x00\x00\x00\x00\x00\x01\x00\x00\xa1\xc4\ +\x00\x00\x01\x6d\x25\xef\x7d\x3c\ +\x00\x00\x05\x50\x00\x00\x00\x00\x00\x01\x00\x00\xcd\x0e\ +\x00\x00\x01\x6d\x25\xef\x7d\x31\ +\x00\x00\x01\xa0\x00\x00\x00\x00\x00\x01\x00\x00\x2a\x20\ +\x00\x00\x01\x6d\x25\xef\x7d\x14\ +\x00\x00\x05\xbe\x00\x00\x00\x00\x00\x01\x00\x00\xd5\x8a\ +\x00\x00\x01\x6d\x25\xef\x7d\x05\ +\x00\x00\x05\x0a\x00\x00\x00\x00\x00\x01\x00\x00\xca\xcf\ +\x00\x00\x01\x6d\x25\xef\x7d\x44\ +\x00\x00\x03\x36\x00\x00\x00\x00\x00\x01\x00\x00\x6a\x4c\ +\x00\x00\x01\x6d\x25\xef\x7d\x34\ +\x00\x00\x04\x1c\x00\x00\x00\x00\x00\x01\x00\x00\xad\xef\ +\x00\x00\x01\x6d\x25\xef\x7d\x41\ +\x00\x00\x02\x4a\x00\x00\x00\x00\x00\x01\x00\x00\x47\xa2\ +\x00\x00\x01\x6d\x25\xef\x7d\x11\ +\x00\x00\x04\x8c\x00\x00\x00\x00\x00\x01\x00\x00\xc2\x60\ +\x00\x00\x01\x6d\x25\xef\x7d\x0b\ +\x00\x00\x02\x76\x00\x00\x00\x00\x00\x01\x00\x00\x4d\x20\ +\x00\x00\x01\x6d\x25\xef\x7d\x08\ +\x00\x00\x05\x78\x00\x00\x00\x00\x00\x01\x00\x00\xd3\x9d\ +\x00\x00\x01\x6d\x25\xef\x7c\xf9\ +\x00\x00\x01\xc2\x00\x00\x00\x00\x00\x01\x00\x00\x2b\x4b\ +\x00\x00\x01\x6d\x25\xef\x7c\xff\ +\x00\x00\x00\x9a\x00\x00\x00\x00\x00\x01\x00\x00\x0a\x7b\ +\x00\x00\x01\x6d\x25\xef\x7d\x39\ +\x00\x00\x02\x98\x00\x00\x00\x00\x00\x01\x00\x00\x4e\xbf\ +\x00\x00\x01\x6d\x25\xef\x7d\x36\ +\x00\x00\x02\x18\x00\x00\x00\x00\x00\x01\x00\x00\x32\x8c\ +\x00\x00\x01\x6d\x25\xef\x7c\xf3\ +\x00\x00\x03\xbe\x00\x00\x00\x00\x00\x01\x00\x00\x8b\x1d\ +\x00\x00\x01\x6d\x25\xef\x7d\x18\ +\x00\x00\x05\xe0\x00\x00\x00\x00\x00\x01\x00\x00\xd7\x42\ +\x00\x00\x01\x6d\x25\xef\x7d\x2c\ +\x00\x00\x00\xcc\x00\x00\x00\x00\x00\x01\x00\x00\x1a\xf7\ +\x00\x00\x01\x6d\x25\xef\x7d\x0e\ +\x00\x00\x00\x64\x00\x00\x00\x00\x00\x01\x00\x00\x04\x02\ +\x00\x00\x01\x6d\x25\xef\x7d\x27\ +\x00\x00\x04\xae\x00\x00\x00\x00\x00\x01\x00\x00\xc3\x1c\ +\x00\x00\x01\x6d\x25\xef\x7d\x29\ +\x00\x00\x01\x22\x00\x00\x00\x00\x00\x01\x00\x00\x22\x1e\ +\x00\x00\x01\x6d\x25\xef\x7d\x02\ +\x00\x00\x02\xca\x00\x00\x00\x00\x00\x01\x00\x00\x5e\xc2\ +\x00\x00\x01\x6d\x25\xef\x7d\x1b\ +\x00\x00\x03\x8a\x00\x00\x00\x00\x00\x01\x00\x00\x70\xf0\ +\x00\x00\x01\x6d\x25\xef\x7c\xe9\ +\x00\x00\x00\xee\x00\x00\x00\x00\x00\x01\x00\x00\x1c\x0e\ +\x00\x00\x01\x6d\x25\xef\x7c\xfc\ +\x00\x00\x04\xd0\x00\x00\x00\x00\x00\x01\x00\x00\xc4\xbd\ +\x00\x00\x01\x6d\x25\xef\x7c\xef\ +\x00\x00\x05\x9c\x00\x00\x00\x00\x00\x01\x00\x00\xd4\xac\ +\x00\x00\x01\x6d\x25\xef\x7c\xec\ +\x00\x00\x04\x58\x00\x00\x00\x00\x00\x01\x00\x00\xbd\xc5\ +\x00\x00\x01\x6d\x25\xef\x7c\xf6\ +" + +qt_version = [int(v) for v in QtCore.qVersion().split('.')] +if qt_version < [5, 8, 0]: + rcc_version = 1 + qt_resource_struct = qt_resource_struct_v1 +else: + rcc_version = 2 + qt_resource_struct = qt_resource_struct_v2 + +def qInitResources(): + QtCore.qRegisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data) + +def qCleanupResources(): + QtCore.qUnregisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data) + +qInitResources() diff --git a/qad_dsettings_ui.py b/qad_dsettings_ui.py index e23c6a64..ec9fdfa6 100644 --- a/qad_dsettings_ui.py +++ b/qad_dsettings_ui.py @@ -1,351 +1,478 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file 'qad_dsettings.ui' -# -# Created: Tue Sep 08 15:55:18 2015 -# by: PyQt4 UI code generator 4.10.2 -# -# WARNING! All changes made in this file will be lost! - -from PyQt4 import QtCore, QtGui - -try: - _fromUtf8 = QtCore.QString.fromUtf8 -except AttributeError: - def _fromUtf8(s): - return s - -try: - _encoding = QtGui.QApplication.UnicodeUTF8 - def _translate(context, text, disambig): - return QtGui.QApplication.translate(context, text, disambig, _encoding) -except AttributeError: - def _translate(context, text, disambig): - return QtGui.QApplication.translate(context, text, disambig) - -class Ui_DSettings_Dialog(object): - def setupUi(self, DSettings_Dialog): - DSettings_Dialog.setObjectName(_fromUtf8("DSettings_Dialog")) - DSettings_Dialog.setWindowModality(QtCore.Qt.ApplicationModal) - DSettings_Dialog.resize(441, 455) - DSettings_Dialog.setMouseTracking(True) - DSettings_Dialog.setContextMenuPolicy(QtCore.Qt.DefaultContextMenu) - DSettings_Dialog.setModal(True) - self.tabWidget = QtGui.QTabWidget(DSettings_Dialog) - self.tabWidget.setGeometry(QtCore.QRect(10, 10, 421, 401)) - self.tabWidget.setObjectName(_fromUtf8("tabWidget")) - self.tab_1 = QtGui.QWidget() - self.tab_1.setObjectName(_fromUtf8("tab_1")) - self.groupBox = QtGui.QGroupBox(self.tab_1) - self.groupBox.setGeometry(QtCore.QRect(10, 40, 391, 321)) - self.groupBox.setObjectName(_fromUtf8("groupBox")) - self.layoutWidget = QtGui.QWidget(self.groupBox) - self.layoutWidget.setGeometry(QtCore.QRect(60, 290, 261, 25)) - self.layoutWidget.setObjectName(_fromUtf8("layoutWidget")) - self.horizontalLayout_2 = QtGui.QHBoxLayout(self.layoutWidget) - self.horizontalLayout_2.setMargin(0) - self.horizontalLayout_2.setObjectName(_fromUtf8("horizontalLayout_2")) - self.pushButton_SelectALL = QtGui.QPushButton(self.layoutWidget) - self.pushButton_SelectALL.setObjectName(_fromUtf8("pushButton_SelectALL")) - self.horizontalLayout_2.addWidget(self.pushButton_SelectALL) - self.pushButton_DeSelectALL = QtGui.QPushButton(self.layoutWidget) - self.pushButton_DeSelectALL.setObjectName(_fromUtf8("pushButton_DeSelectALL")) - self.horizontalLayout_2.addWidget(self.pushButton_DeSelectALL) - self.layoutWidget1 = QtGui.QWidget(self.groupBox) - self.layoutWidget1.setGeometry(QtCore.QRect(190, 20, 181, 261)) - self.layoutWidget1.setObjectName(_fromUtf8("layoutWidget1")) - self.gridLayout = QtGui.QGridLayout(self.layoutWidget1) - self.gridLayout.setMargin(0) - self.gridLayout.setObjectName(_fromUtf8("gridLayout")) - self.label_5 = QtGui.QLabel(self.layoutWidget1) - self.label_5.setText(_fromUtf8("")) - self.label_5.setPixmap(QtGui.QPixmap(_fromUtf8(":/plugins/qad/icons/dsettings/OSNAP_EXTP.png"))) - self.label_5.setObjectName(_fromUtf8("label_5")) - self.gridLayout.addWidget(self.label_5, 3, 0, 1, 1) - self.label_13 = QtGui.QLabel(self.layoutWidget1) - self.label_13.setText(_fromUtf8("")) - self.label_13.setPixmap(QtGui.QPixmap(_fromUtf8(":/plugins/qad/icons/dsettings/OSNAP_PARP.png"))) - self.label_13.setObjectName(_fromUtf8("label_13")) - self.gridLayout.addWidget(self.label_13, 4, 0, 1, 1) - self.label_14 = QtGui.QLabel(self.layoutWidget1) - self.label_14.setText(_fromUtf8("")) - self.label_14.setPixmap(QtGui.QPixmap(_fromUtf8(":/plugins/qad/icons/dsettings/OSNAP_PROGP.png"))) - self.label_14.setObjectName(_fromUtf8("label_14")) - self.gridLayout.addWidget(self.label_14, 5, 0, 1, 1) - self.label_2 = QtGui.QLabel(self.layoutWidget1) - self.label_2.setText(_fromUtf8("")) - self.label_2.setPixmap(QtGui.QPixmap(_fromUtf8(":/plugins/qad/icons/dsettings/OSNAP_EXTINT.png"))) - self.label_2.setObjectName(_fromUtf8("label_2")) - self.gridLayout.addWidget(self.label_2, 6, 0, 1, 1) - self.label_9 = QtGui.QLabel(self.layoutWidget1) - self.label_9.setText(_fromUtf8("")) - self.label_9.setPixmap(QtGui.QPixmap(_fromUtf8(":/plugins/qad/icons/dsettings/OSNAP_PERP.png"))) - self.label_9.setObjectName(_fromUtf8("label_9")) - self.gridLayout.addWidget(self.label_9, 1, 0, 1, 1) - self.checkBox_PERP = QtGui.QCheckBox(self.layoutWidget1) - self.checkBox_PERP.setTristate(False) - self.checkBox_PERP.setObjectName(_fromUtf8("checkBox_PERP")) - self.gridLayout.addWidget(self.checkBox_PERP, 1, 1, 1, 1) - self.label_10 = QtGui.QLabel(self.layoutWidget1) - self.label_10.setText(_fromUtf8("")) - self.label_10.setPixmap(QtGui.QPixmap(_fromUtf8(":/plugins/qad/icons/dsettings/OSNAP_TANP.png"))) - self.label_10.setObjectName(_fromUtf8("label_10")) - self.gridLayout.addWidget(self.label_10, 2, 0, 1, 1) - self.checkBox_TANP = QtGui.QCheckBox(self.layoutWidget1) - self.checkBox_TANP.setTristate(False) - self.checkBox_TANP.setObjectName(_fromUtf8("checkBox_TANP")) - self.gridLayout.addWidget(self.checkBox_TANP, 2, 1, 1, 1) - self.checkBox_EXTP = QtGui.QCheckBox(self.layoutWidget1) - self.checkBox_EXTP.setTristate(False) - self.checkBox_EXTP.setObjectName(_fromUtf8("checkBox_EXTP")) - self.gridLayout.addWidget(self.checkBox_EXTP, 3, 1, 1, 1) - self.checkBox_PARALP = QtGui.QCheckBox(self.layoutWidget1) - self.checkBox_PARALP.setTristate(False) - self.checkBox_PARALP.setObjectName(_fromUtf8("checkBox_PARALP")) - self.gridLayout.addWidget(self.checkBox_PARALP, 4, 1, 1, 1) - self.checkBox_PROGRESP = QtGui.QCheckBox(self.layoutWidget1) - self.checkBox_PROGRESP.setTristate(False) - self.checkBox_PROGRESP.setObjectName(_fromUtf8("checkBox_PROGRESP")) - self.gridLayout.addWidget(self.checkBox_PROGRESP, 5, 1, 1, 1) - self.checkBox_EXT_INT = QtGui.QCheckBox(self.layoutWidget1) - self.checkBox_EXT_INT.setObjectName(_fromUtf8("checkBox_EXT_INT")) - self.gridLayout.addWidget(self.checkBox_EXT_INT, 6, 1, 1, 1) - self.checkBox_QUADP = QtGui.QCheckBox(self.layoutWidget1) - self.checkBox_QUADP.setTristate(False) - self.checkBox_QUADP.setObjectName(_fromUtf8("checkBox_QUADP")) - self.gridLayout.addWidget(self.checkBox_QUADP, 0, 1, 1, 1) - self.label_3 = QtGui.QLabel(self.layoutWidget1) - self.label_3.setText(_fromUtf8("")) - self.label_3.setPixmap(QtGui.QPixmap(_fromUtf8(":/plugins/qad/icons/dsettings/OSNAP_QUADP.png"))) - self.label_3.setObjectName(_fromUtf8("label_3")) - self.gridLayout.addWidget(self.label_3, 0, 0, 1, 1) - self.lineEdit_ProgrDistance = QtGui.QLineEdit(self.groupBox) - self.lineEdit_ProgrDistance.setGeometry(QtCore.QRect(340, 210, 41, 20)) - self.lineEdit_ProgrDistance.setObjectName(_fromUtf8("lineEdit_ProgrDistance")) - self.layoutWidget2 = QtGui.QWidget(self.groupBox) - self.layoutWidget2.setGeometry(QtCore.QRect(10, 20, 154, 261)) - self.layoutWidget2.setObjectName(_fromUtf8("layoutWidget2")) - self.gridLayout_2 = QtGui.QGridLayout(self.layoutWidget2) - self.gridLayout_2.setMargin(0) - self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2")) - self.label_8 = QtGui.QLabel(self.layoutWidget2) - self.label_8.setText(_fromUtf8("")) - self.label_8.setPixmap(QtGui.QPixmap(_fromUtf8(":/plugins/qad/icons/dsettings/OSNAP_ENDP.png"))) - self.label_8.setObjectName(_fromUtf8("label_8")) - self.gridLayout_2.addWidget(self.label_8, 0, 0, 1, 1) - self.checkBox_END_PLINE = QtGui.QCheckBox(self.layoutWidget2) - self.checkBox_END_PLINE.setObjectName(_fromUtf8("checkBox_END_PLINE")) - self.gridLayout_2.addWidget(self.checkBox_END_PLINE, 0, 1, 1, 1) - self.label_ENDP = QtGui.QLabel(self.layoutWidget2) - self.label_ENDP.setText(_fromUtf8("")) - self.label_ENDP.setPixmap(QtGui.QPixmap(_fromUtf8(":/plugins/qad/icons/dsettings/OSNAP_ENDP.png"))) - self.label_ENDP.setObjectName(_fromUtf8("label_ENDP")) - self.gridLayout_2.addWidget(self.label_ENDP, 1, 0, 1, 1) - self.checkBox_ENDP = QtGui.QCheckBox(self.layoutWidget2) - self.checkBox_ENDP.setTristate(False) - self.checkBox_ENDP.setObjectName(_fromUtf8("checkBox_ENDP")) - self.gridLayout_2.addWidget(self.checkBox_ENDP, 1, 1, 1, 1) - self.label_MIDP = QtGui.QLabel(self.layoutWidget2) - self.label_MIDP.setText(_fromUtf8("")) - self.label_MIDP.setPixmap(QtGui.QPixmap(_fromUtf8(":/plugins/qad/icons/dsettings/OSNAP_MEDP.png"))) - self.label_MIDP.setObjectName(_fromUtf8("label_MIDP")) - self.gridLayout_2.addWidget(self.label_MIDP, 2, 0, 1, 1) - self.checkBox_MIDP = QtGui.QCheckBox(self.layoutWidget2) - self.checkBox_MIDP.setTristate(False) - self.checkBox_MIDP.setObjectName(_fromUtf8("checkBox_MIDP")) - self.gridLayout_2.addWidget(self.checkBox_MIDP, 2, 1, 1, 1) - self.label_4 = QtGui.QLabel(self.layoutWidget2) - self.label_4.setText(_fromUtf8("")) - self.label_4.setPixmap(QtGui.QPixmap(_fromUtf8(":/plugins/qad/icons/dsettings/OSNAP_INTP.png"))) - self.label_4.setObjectName(_fromUtf8("label_4")) - self.gridLayout_2.addWidget(self.label_4, 3, 0, 1, 1) - self.checkBox_INTP = QtGui.QCheckBox(self.layoutWidget2) - self.checkBox_INTP.setTristate(False) - self.checkBox_INTP.setObjectName(_fromUtf8("checkBox_INTP")) - self.gridLayout_2.addWidget(self.checkBox_INTP, 3, 1, 1, 1) - self.label_6 = QtGui.QLabel(self.layoutWidget2) - self.label_6.setText(_fromUtf8("")) - self.label_6.setPixmap(QtGui.QPixmap(_fromUtf8(":/plugins/qad/icons/dsettings/OSNAP_CENP.png"))) - self.label_6.setObjectName(_fromUtf8("label_6")) - self.gridLayout_2.addWidget(self.label_6, 4, 0, 1, 1) - self.checkBox_CENP = QtGui.QCheckBox(self.layoutWidget2) - self.checkBox_CENP.setTristate(False) - self.checkBox_CENP.setObjectName(_fromUtf8("checkBox_CENP")) - self.gridLayout_2.addWidget(self.checkBox_CENP, 4, 1, 1, 1) - self.label_7 = QtGui.QLabel(self.layoutWidget2) - self.label_7.setText(_fromUtf8("")) - self.label_7.setPixmap(QtGui.QPixmap(_fromUtf8(":/plugins/qad/icons/dsettings/OSNAP_NODP.png"))) - self.label_7.setObjectName(_fromUtf8("label_7")) - self.gridLayout_2.addWidget(self.label_7, 5, 0, 1, 1) - self.checkBox_NODP = QtGui.QCheckBox(self.layoutWidget2) - self.checkBox_NODP.setTristate(False) - self.checkBox_NODP.setObjectName(_fromUtf8("checkBox_NODP")) - self.gridLayout_2.addWidget(self.checkBox_NODP, 5, 1, 1, 1) - self.label_11 = QtGui.QLabel(self.layoutWidget2) - self.label_11.setText(_fromUtf8("")) - self.label_11.setPixmap(QtGui.QPixmap(_fromUtf8(":/plugins/qad/icons/dsettings/OSNAP_NEARP.png"))) - self.label_11.setObjectName(_fromUtf8("label_11")) - self.gridLayout_2.addWidget(self.label_11, 6, 0, 1, 1) - self.checkBox_NEARP = QtGui.QCheckBox(self.layoutWidget2) - self.checkBox_NEARP.setTristate(False) - self.checkBox_NEARP.setObjectName(_fromUtf8("checkBox_NEARP")) - self.gridLayout_2.addWidget(self.checkBox_NEARP, 6, 1, 1, 1) - self.checkBox_IsOsnapON = QtGui.QCheckBox(self.tab_1) - self.checkBox_IsOsnapON.setGeometry(QtCore.QRect(10, 10, 126, 17)) - self.checkBox_IsOsnapON.setObjectName(_fromUtf8("checkBox_IsOsnapON")) - self.tabWidget.addTab(self.tab_1, _fromUtf8("")) - self.tab_2 = QtGui.QWidget() - self.tab_2.setObjectName(_fromUtf8("tab_2")) - self.checkBox_PolarPickPoint = QtGui.QCheckBox(self.tab_2) - self.checkBox_PolarPickPoint.setGeometry(QtCore.QRect(10, 10, 171, 17)) - self.checkBox_PolarPickPoint.setObjectName(_fromUtf8("checkBox_PolarPickPoint")) - self.groupBox_2 = QtGui.QGroupBox(self.tab_2) - self.groupBox_2.setGeometry(QtCore.QRect(10, 40, 151, 81)) - self.groupBox_2.setObjectName(_fromUtf8("groupBox_2")) - self.label_12 = QtGui.QLabel(self.groupBox_2) - self.label_12.setGeometry(QtCore.QRect(10, 20, 121, 16)) - self.label_12.setObjectName(_fromUtf8("label_12")) - self.comboBox_increment_angle = QtGui.QComboBox(self.groupBox_2) - self.comboBox_increment_angle.setGeometry(QtCore.QRect(10, 40, 131, 22)) - self.comboBox_increment_angle.setEditable(True) - self.comboBox_increment_angle.setObjectName(_fromUtf8("comboBox_increment_angle")) - self.tabWidget.addTab(self.tab_2, _fromUtf8("")) - self.layoutWidget3 = QtGui.QWidget(DSettings_Dialog) - self.layoutWidget3.setGeometry(QtCore.QRect(190, 420, 239, 25)) - self.layoutWidget3.setObjectName(_fromUtf8("layoutWidget3")) - self.horizontalLayout = QtGui.QHBoxLayout(self.layoutWidget3) - self.horizontalLayout.setMargin(0) - self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) - self.okButton = QtGui.QPushButton(self.layoutWidget3) - self.okButton.setObjectName(_fromUtf8("okButton")) - self.horizontalLayout.addWidget(self.okButton) - self.cancelButton = QtGui.QPushButton(self.layoutWidget3) - self.cancelButton.setObjectName(_fromUtf8("cancelButton")) - self.horizontalLayout.addWidget(self.cancelButton) - self.pushButton_HELP = QtGui.QPushButton(self.layoutWidget3) - self.pushButton_HELP.setObjectName(_fromUtf8("pushButton_HELP")) - self.horizontalLayout.addWidget(self.pushButton_HELP) - - self.retranslateUi(DSettings_Dialog) - self.tabWidget.setCurrentIndex(0) - QtCore.QObject.connect(self.pushButton_DeSelectALL, QtCore.SIGNAL(_fromUtf8("pressed()")), DSettings_Dialog.ButtonDeselectALL_Pressed) - QtCore.QObject.connect(self.pushButton_SelectALL, QtCore.SIGNAL(_fromUtf8("pressed()")), DSettings_Dialog.ButtonSelectALL_Pressed) - QtCore.QObject.connect(self.pushButton_HELP, QtCore.SIGNAL(_fromUtf8("clicked()")), DSettings_Dialog.ButtonHELP_Pressed) - QtCore.QObject.connect(self.okButton, QtCore.SIGNAL(_fromUtf8("clicked()")), DSettings_Dialog.ButtonBOX_Accepted) - QtCore.QObject.connect(self.cancelButton, QtCore.SIGNAL(_fromUtf8("clicked()")), DSettings_Dialog.reject) - QtCore.QMetaObject.connectSlotsByName(DSettings_Dialog) - - def retranslateUi(self, DSettings_Dialog): - DSettings_Dialog.setWindowTitle(_translate("DSettings_Dialog", "QAD - Drawing settings", None)) - self.groupBox.setTitle(_translate("DSettings_Dialog", "Object Snap modes", None)) - self.pushButton_SelectALL.setText(_translate("DSettings_Dialog", "Select All", None)) - self.pushButton_DeSelectALL.setText(_translate("DSettings_Dialog", "Deselect All", None)) - self.checkBox_PERP.setToolTip(_translate("DSettings_Dialog", "\n" -"\n" -"

      Perpendicular OSnap: orthogonal projection of a given point on a segment.

      \n" -"

      ", None)) - self.checkBox_PERP.setText(_translate("DSettings_Dialog", "Perpendicular", None)) - self.checkBox_TANP.setToolTip(_translate("DSettings_Dialog", "\n" -"\n" -"

      tangent point on a curve of a line passing through a given point.

      \n" -"

      ", None)) - self.checkBox_TANP.setText(_translate("DSettings_Dialog", "Tangent", None)) - self.checkBox_EXTP.setToolTip(_translate("DSettings_Dialog", "\n" -"\n" -"

      Extension OSnap: point on the segment extension until the cursor position.

      \n" -"

      ", None)) - self.checkBox_EXTP.setText(_translate("DSettings_Dialog", "Extend", None)) - self.checkBox_PARALP.setToolTip(_translate("DSettings_Dialog", "\n" -"\n" -"

      Parallel OSnap: point on a line, passing through a given point, parallel to a segment.

      \n" -"

      ", None)) - self.checkBox_PARALP.setText(_translate("DSettings_Dialog", "Parallel", None)) - self.checkBox_PROGRESP.setToolTip(_translate("DSettings_Dialog", "\n" -"\n" -"

      Progressive OSnap: point at a given distance along a geometry line: from a vertex we can set a point at a distance measured along the geometry line.

      \n" -"

      ", None)) - self.checkBox_PROGRESP.setText(_translate("DSettings_Dialog", "Progressive", None)) - self.checkBox_EXT_INT.setToolTip(_translate("DSettings_Dialog", "\n" -"\n" -"

      Intersection on extension OSnap: intersection point of the extensions of two segments.

      \n" -"

      ", None)) - self.checkBox_EXT_INT.setText(_translate("DSettings_Dialog", "Intersection on extension", None)) - self.checkBox_QUADP.setToolTip(_translate("DSettings_Dialog", "\n" -"\n" -"

      Quadrant OSnap: intersections of the cartesian axis with a circumference of a circle or an arc.

      \n" -"

      ", None)) - self.checkBox_QUADP.setText(_translate("DSettings_Dialog", "Quadrant", None)) - self.checkBox_END_PLINE.setToolTip(_translate("DSettings_Dialog", "\n" -"\n" -"

      Start / End OSnap: starting and ending vertices of a linear geometry.

      \n" -"

      ", None)) - self.checkBox_END_PLINE.setText(_translate("DSettings_Dialog", "Start / End", None)) - self.checkBox_ENDP.setToolTip(_translate("DSettings_Dialog", "\n" -"\n" -"

      Start / End segment OSnap: starting and ending vertices of each segment of a geometry.

      \n" -"

      ", "aa")) - self.checkBox_ENDP.setText(_translate("DSettings_Dialog", "Segment Start / End", None)) - self.checkBox_MIDP.setToolTip(_translate("DSettings_Dialog", "\n" -"\n" -"

      Middle point OSnap: middle point of each segment of a geometry.

      \n" -"

      ", None)) - self.checkBox_MIDP.setText(_translate("DSettings_Dialog", "Middle point", None)) - self.checkBox_INTP.setToolTip(_translate("DSettings_Dialog", "\n" -"\n" -"

      Intersection OSnap: intersection between two segments.

      \n" -"

      ", None)) - self.checkBox_INTP.setText(_translate("DSettings_Dialog", "Intersection", None)) - self.checkBox_CENP.setToolTip(_translate("DSettings_Dialog", "\n" -"\n" -"

      Center OSnap: center of a circle or arc or centroid of an areal geometry.

      \n" -"

      ", None)) - self.checkBox_CENP.setText(_translate("DSettings_Dialog", "Center", None)) - self.checkBox_NODP.setToolTip(_translate("DSettings_Dialog", "\n" -"\n" -"

      Node OSnap: coordinate of a punctual geometry.

      \n" -"

      ", None)) - self.checkBox_NODP.setText(_translate("DSettings_Dialog", "Node", None)) - self.checkBox_NEARP.setToolTip(_translate("DSettings_Dialog", "\n" -"\n" -"

      Near OSnap: point of a segment close to the cursor position.

      \n" -"

      ", None)) - self.checkBox_NEARP.setText(_translate("DSettings_Dialog", "Near", None)) - self.checkBox_IsOsnapON.setText(_translate("DSettings_Dialog", "Object Snap (F3)", None)) - self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_1), _translate("DSettings_Dialog", "Object Snap", None)) - self.checkBox_PolarPickPoint.setText(_translate("DSettings_Dialog", "Polar Tracking (F10)", None)) - self.groupBox_2.setTitle(_translate("DSettings_Dialog", "Polar angle settings", None)) - self.label_12.setText(_translate("DSettings_Dialog", "Increment angle:", None)) - self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), _translate("DSettings_Dialog", "Polar Tracking", None)) - self.okButton.setText(_translate("DSettings_Dialog", "OK", None)) - self.cancelButton.setText(_translate("DSettings_Dialog", "Cancel", None)) - self.pushButton_HELP.setText(_translate("DSettings_Dialog", "?", None)) - -import qad_dsettings_rc +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file '.\qad_dsettings.ui' +# +# Created by: PyQt5 UI code generator 5.15.10 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_DSettings_Dialog(object): + def setupUi(self, DSettings_Dialog): + DSettings_Dialog.setObjectName("DSettings_Dialog") + DSettings_Dialog.setWindowModality(QtCore.Qt.ApplicationModal) + DSettings_Dialog.resize(550, 455) + DSettings_Dialog.setMinimumSize(QtCore.QSize(550, 455)) + DSettings_Dialog.setMaximumSize(QtCore.QSize(550, 455)) + DSettings_Dialog.setMouseTracking(True) + DSettings_Dialog.setContextMenuPolicy(QtCore.Qt.DefaultContextMenu) + DSettings_Dialog.setModal(True) + self.tabWidget = QtWidgets.QTabWidget(DSettings_Dialog) + self.tabWidget.setGeometry(QtCore.QRect(10, 0, 521, 401)) + self.tabWidget.setObjectName("tabWidget") + self.tab_1 = QtWidgets.QWidget() + self.tab_1.setObjectName("tab_1") + self.groupBox = QtWidgets.QGroupBox(self.tab_1) + self.groupBox.setGeometry(QtCore.QRect(10, 40, 491, 321)) + self.groupBox.setObjectName("groupBox") + self.layoutWidget = QtWidgets.QWidget(self.groupBox) + self.layoutWidget.setGeometry(QtCore.QRect(120, 290, 261, 30)) + self.layoutWidget.setObjectName("layoutWidget") + self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.layoutWidget) + self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0) + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.pushButton_SelectALL = QtWidgets.QPushButton(self.layoutWidget) + self.pushButton_SelectALL.setObjectName("pushButton_SelectALL") + self.horizontalLayout_2.addWidget(self.pushButton_SelectALL) + self.pushButton_DeSelectALL = QtWidgets.QPushButton(self.layoutWidget) + self.pushButton_DeSelectALL.setObjectName("pushButton_DeSelectALL") + self.horizontalLayout_2.addWidget(self.pushButton_DeSelectALL) + self.layoutWidget1 = QtWidgets.QWidget(self.groupBox) + self.layoutWidget1.setGeometry(QtCore.QRect(270, 20, 197, 261)) + self.layoutWidget1.setObjectName("layoutWidget1") + self.gridLayout = QtWidgets.QGridLayout(self.layoutWidget1) + self.gridLayout.setContentsMargins(0, 0, 0, 0) + self.gridLayout.setObjectName("gridLayout") + self.label_5 = QtWidgets.QLabel(self.layoutWidget1) + self.label_5.setText("") + self.label_5.setPixmap(QtGui.QPixmap(":/plugins/qad/icons/dsettings/OSNAP_EXTP.png")) + self.label_5.setObjectName("label_5") + self.gridLayout.addWidget(self.label_5, 3, 0, 1, 1) + self.label_13 = QtWidgets.QLabel(self.layoutWidget1) + self.label_13.setText("") + self.label_13.setPixmap(QtGui.QPixmap(":/plugins/qad/icons/dsettings/OSNAP_PARP.png")) + self.label_13.setObjectName("label_13") + self.gridLayout.addWidget(self.label_13, 4, 0, 1, 1) + self.label_14 = QtWidgets.QLabel(self.layoutWidget1) + self.label_14.setText("") + self.label_14.setPixmap(QtGui.QPixmap(":/plugins/qad/icons/dsettings/OSNAP_PROGP.png")) + self.label_14.setObjectName("label_14") + self.gridLayout.addWidget(self.label_14, 5, 0, 1, 1) + self.label_2 = QtWidgets.QLabel(self.layoutWidget1) + self.label_2.setText("") + self.label_2.setPixmap(QtGui.QPixmap(":/plugins/qad/icons/dsettings/OSNAP_EXTINT.png")) + self.label_2.setObjectName("label_2") + self.gridLayout.addWidget(self.label_2, 6, 0, 1, 1) + self.label_9 = QtWidgets.QLabel(self.layoutWidget1) + self.label_9.setText("") + self.label_9.setPixmap(QtGui.QPixmap(":/plugins/qad/icons/dsettings/OSNAP_PERP.png")) + self.label_9.setObjectName("label_9") + self.gridLayout.addWidget(self.label_9, 1, 0, 1, 1) + self.checkBox_PERP = QtWidgets.QCheckBox(self.layoutWidget1) + self.checkBox_PERP.setTristate(False) + self.checkBox_PERP.setObjectName("checkBox_PERP") + self.gridLayout.addWidget(self.checkBox_PERP, 1, 1, 1, 1) + self.label_10 = QtWidgets.QLabel(self.layoutWidget1) + self.label_10.setText("") + self.label_10.setPixmap(QtGui.QPixmap(":/plugins/qad/icons/dsettings/OSNAP_TANP.png")) + self.label_10.setObjectName("label_10") + self.gridLayout.addWidget(self.label_10, 2, 0, 1, 1) + self.checkBox_TANP = QtWidgets.QCheckBox(self.layoutWidget1) + self.checkBox_TANP.setTristate(False) + self.checkBox_TANP.setObjectName("checkBox_TANP") + self.gridLayout.addWidget(self.checkBox_TANP, 2, 1, 1, 1) + self.checkBox_EXTP = QtWidgets.QCheckBox(self.layoutWidget1) + self.checkBox_EXTP.setTristate(False) + self.checkBox_EXTP.setObjectName("checkBox_EXTP") + self.gridLayout.addWidget(self.checkBox_EXTP, 3, 1, 1, 1) + self.checkBox_PARALP = QtWidgets.QCheckBox(self.layoutWidget1) + self.checkBox_PARALP.setTristate(False) + self.checkBox_PARALP.setObjectName("checkBox_PARALP") + self.gridLayout.addWidget(self.checkBox_PARALP, 4, 1, 1, 1) + self.checkBox_PROGRESP = QtWidgets.QCheckBox(self.layoutWidget1) + self.checkBox_PROGRESP.setTristate(False) + self.checkBox_PROGRESP.setObjectName("checkBox_PROGRESP") + self.gridLayout.addWidget(self.checkBox_PROGRESP, 5, 1, 1, 1) + self.checkBox_EXT_INT = QtWidgets.QCheckBox(self.layoutWidget1) + self.checkBox_EXT_INT.setObjectName("checkBox_EXT_INT") + self.gridLayout.addWidget(self.checkBox_EXT_INT, 6, 1, 1, 1) + self.checkBox_QUADP = QtWidgets.QCheckBox(self.layoutWidget1) + self.checkBox_QUADP.setTristate(False) + self.checkBox_QUADP.setObjectName("checkBox_QUADP") + self.gridLayout.addWidget(self.checkBox_QUADP, 0, 1, 1, 1) + self.label_3 = QtWidgets.QLabel(self.layoutWidget1) + self.label_3.setText("") + self.label_3.setPixmap(QtGui.QPixmap(":/plugins/qad/icons/dsettings/OSNAP_QUADP.png")) + self.label_3.setObjectName("label_3") + self.gridLayout.addWidget(self.label_3, 0, 0, 1, 1) + self.lineEdit_ProgrDistance = QtWidgets.QLineEdit(self.groupBox) + self.lineEdit_ProgrDistance.setGeometry(QtCore.QRect(410, 210, 41, 20)) + self.lineEdit_ProgrDistance.setObjectName("lineEdit_ProgrDistance") + self.layoutWidget2 = QtWidgets.QWidget(self.groupBox) + self.layoutWidget2.setGeometry(QtCore.QRect(30, 20, 171, 261)) + self.layoutWidget2.setObjectName("layoutWidget2") + self.gridLayout_2 = QtWidgets.QGridLayout(self.layoutWidget2) + self.gridLayout_2.setSizeConstraint(QtWidgets.QLayout.SetMinimumSize) + self.gridLayout_2.setContentsMargins(0, 0, 0, 0) + self.gridLayout_2.setObjectName("gridLayout_2") + self.checkBox_END_PLINE = QtWidgets.QCheckBox(self.layoutWidget2) + self.checkBox_END_PLINE.setObjectName("checkBox_END_PLINE") + self.gridLayout_2.addWidget(self.checkBox_END_PLINE, 0, 1, 1, 1) + self.label_ENDP = QtWidgets.QLabel(self.layoutWidget2) + self.label_ENDP.setText("") + self.label_ENDP.setPixmap(QtGui.QPixmap(":/plugins/qad/icons/dsettings/OSNAP_ENDP.png")) + self.label_ENDP.setObjectName("label_ENDP") + self.gridLayout_2.addWidget(self.label_ENDP, 1, 0, 1, 1) + self.checkBox_ENDP = QtWidgets.QCheckBox(self.layoutWidget2) + self.checkBox_ENDP.setTristate(False) + self.checkBox_ENDP.setObjectName("checkBox_ENDP") + self.gridLayout_2.addWidget(self.checkBox_ENDP, 1, 1, 1, 1) + self.label_MIDP = QtWidgets.QLabel(self.layoutWidget2) + self.label_MIDP.setText("") + self.label_MIDP.setPixmap(QtGui.QPixmap(":/plugins/qad/icons/dsettings/OSNAP_MEDP.png")) + self.label_MIDP.setObjectName("label_MIDP") + self.gridLayout_2.addWidget(self.label_MIDP, 2, 0, 1, 1) + self.checkBox_MIDP = QtWidgets.QCheckBox(self.layoutWidget2) + self.checkBox_MIDP.setTristate(False) + self.checkBox_MIDP.setObjectName("checkBox_MIDP") + self.gridLayout_2.addWidget(self.checkBox_MIDP, 2, 1, 1, 1) + self.label_4 = QtWidgets.QLabel(self.layoutWidget2) + self.label_4.setText("") + self.label_4.setPixmap(QtGui.QPixmap(":/plugins/qad/icons/dsettings/OSNAP_INTP.png")) + self.label_4.setObjectName("label_4") + self.gridLayout_2.addWidget(self.label_4, 3, 0, 1, 1) + self.checkBox_INTP = QtWidgets.QCheckBox(self.layoutWidget2) + self.checkBox_INTP.setTristate(False) + self.checkBox_INTP.setObjectName("checkBox_INTP") + self.gridLayout_2.addWidget(self.checkBox_INTP, 3, 1, 1, 1) + self.label_6 = QtWidgets.QLabel(self.layoutWidget2) + self.label_6.setText("") + self.label_6.setPixmap(QtGui.QPixmap(":/plugins/qad/icons/dsettings/OSNAP_CENP.png")) + self.label_6.setObjectName("label_6") + self.gridLayout_2.addWidget(self.label_6, 4, 0, 1, 1) + self.checkBox_CENP = QtWidgets.QCheckBox(self.layoutWidget2) + self.checkBox_CENP.setTristate(False) + self.checkBox_CENP.setObjectName("checkBox_CENP") + self.gridLayout_2.addWidget(self.checkBox_CENP, 4, 1, 1, 1) + self.label_7 = QtWidgets.QLabel(self.layoutWidget2) + self.label_7.setText("") + self.label_7.setPixmap(QtGui.QPixmap(":/plugins/qad/icons/dsettings/OSNAP_NODP.png")) + self.label_7.setScaledContents(False) + self.label_7.setObjectName("label_7") + self.gridLayout_2.addWidget(self.label_7, 5, 0, 1, 1) + self.checkBox_NODP = QtWidgets.QCheckBox(self.layoutWidget2) + self.checkBox_NODP.setTristate(False) + self.checkBox_NODP.setObjectName("checkBox_NODP") + self.gridLayout_2.addWidget(self.checkBox_NODP, 5, 1, 1, 1) + self.label_11 = QtWidgets.QLabel(self.layoutWidget2) + self.label_11.setText("") + self.label_11.setPixmap(QtGui.QPixmap(":/plugins/qad/icons/dsettings/OSNAP_NEARP.png")) + self.label_11.setObjectName("label_11") + self.gridLayout_2.addWidget(self.label_11, 6, 0, 1, 1) + self.checkBox_NEARP = QtWidgets.QCheckBox(self.layoutWidget2) + self.checkBox_NEARP.setTristate(False) + self.checkBox_NEARP.setObjectName("checkBox_NEARP") + self.gridLayout_2.addWidget(self.checkBox_NEARP, 6, 1, 1, 1) + self.label_8 = QtWidgets.QLabel(self.layoutWidget2) + self.label_8.setText("") + self.label_8.setPixmap(QtGui.QPixmap(":/plugins/qad/icons/dsettings/OSNAP_ENDP.png")) + self.label_8.setObjectName("label_8") + self.gridLayout_2.addWidget(self.label_8, 0, 0, 1, 1) + self.checkBox_IsOsnapON = QtWidgets.QCheckBox(self.tab_1) + self.checkBox_IsOsnapON.setGeometry(QtCore.QRect(10, 10, 191, 17)) + self.checkBox_IsOsnapON.setObjectName("checkBox_IsOsnapON") + self.checkBox_ObjectSnapTracking = QtWidgets.QCheckBox(self.tab_1) + self.checkBox_ObjectSnapTracking.setGeometry(QtCore.QRect(210, 10, 251, 20)) + self.checkBox_ObjectSnapTracking.setObjectName("checkBox_ObjectSnapTracking") + self.tabWidget.addTab(self.tab_1, "") + self.tab_2 = QtWidgets.QWidget() + self.tab_2.setObjectName("tab_2") + self.checkBox_PolarPickPoint = QtWidgets.QCheckBox(self.tab_2) + self.checkBox_PolarPickPoint.setGeometry(QtCore.QRect(10, 10, 171, 17)) + self.checkBox_PolarPickPoint.setObjectName("checkBox_PolarPickPoint") + self.groupBox_PolarAngleSettings = QtWidgets.QGroupBox(self.tab_2) + self.groupBox_PolarAngleSettings.setGeometry(QtCore.QRect(10, 40, 181, 321)) + self.groupBox_PolarAngleSettings.setObjectName("groupBox_PolarAngleSettings") + self.label_12 = QtWidgets.QLabel(self.groupBox_PolarAngleSettings) + self.label_12.setGeometry(QtCore.QRect(10, 20, 121, 16)) + self.label_12.setObjectName("label_12") + self.comboBox_increment_angle = QtWidgets.QComboBox(self.groupBox_PolarAngleSettings) + self.comboBox_increment_angle.setGeometry(QtCore.QRect(10, 40, 151, 22)) + self.comboBox_increment_angle.setEditable(True) + self.comboBox_increment_angle.setObjectName("comboBox_increment_angle") + self.checkBox_AdditionaltAngles = QtWidgets.QCheckBox(self.groupBox_PolarAngleSettings) + self.checkBox_AdditionaltAngles.setGeometry(QtCore.QRect(10, 70, 131, 17)) + self.checkBox_AdditionaltAngles.setObjectName("checkBox_AdditionaltAngles") + self.listView_AdditionalAngles = QtWidgets.QListView(self.groupBox_PolarAngleSettings) + self.listView_AdditionalAngles.setGeometry(QtCore.QRect(10, 90, 81, 221)) + self.listView_AdditionalAngles.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection) + self.listView_AdditionalAngles.setObjectName("listView_AdditionalAngles") + self.pushButton_AddAngle = QtWidgets.QPushButton(self.groupBox_PolarAngleSettings) + self.pushButton_AddAngle.setGeometry(QtCore.QRect(100, 90, 71, 23)) + self.pushButton_AddAngle.setObjectName("pushButton_AddAngle") + self.pushButton_DelAngle = QtWidgets.QPushButton(self.groupBox_PolarAngleSettings) + self.pushButton_DelAngle.setGeometry(QtCore.QRect(100, 120, 71, 23)) + self.pushButton_DelAngle.setObjectName("pushButton_DelAngle") + self.groupBox_OsnapPolarOrtho = QtWidgets.QGroupBox(self.tab_2) + self.groupBox_OsnapPolarOrtho.setGeometry(QtCore.QRect(200, 40, 301, 81)) + self.groupBox_OsnapPolarOrtho.setObjectName("groupBox_OsnapPolarOrtho") + self.radioButton_OsnapOrtho = QtWidgets.QRadioButton(self.groupBox_OsnapPolarOrtho) + self.radioButton_OsnapOrtho.setGeometry(QtCore.QRect(10, 30, 271, 17)) + self.radioButton_OsnapOrtho.setObjectName("radioButton_OsnapOrtho") + self.radioButton_OsnapPolarAngle = QtWidgets.QRadioButton(self.groupBox_OsnapPolarOrtho) + self.radioButton_OsnapPolarAngle.setGeometry(QtCore.QRect(10, 50, 281, 21)) + self.radioButton_OsnapPolarAngle.setObjectName("radioButton_OsnapPolarAngle") + self.groupBox_OsnapPolarMeasurement = QtWidgets.QGroupBox(self.tab_2) + self.groupBox_OsnapPolarMeasurement.setGeometry(QtCore.QRect(200, 140, 301, 71)) + self.groupBox_OsnapPolarMeasurement.setObjectName("groupBox_OsnapPolarMeasurement") + self.radioButton_OsnapPolarAbolute = QtWidgets.QRadioButton(self.groupBox_OsnapPolarMeasurement) + self.radioButton_OsnapPolarAbolute.setGeometry(QtCore.QRect(10, 30, 271, 17)) + self.radioButton_OsnapPolarAbolute.setObjectName("radioButton_OsnapPolarAbolute") + self.radioButton_OsnapPolarRelative = QtWidgets.QRadioButton(self.groupBox_OsnapPolarMeasurement) + self.radioButton_OsnapPolarRelative.setGeometry(QtCore.QRect(10, 50, 271, 17)) + self.radioButton_OsnapPolarRelative.setObjectName("radioButton_OsnapPolarRelative") + self.tabWidget.addTab(self.tab_2, "") + self.tab_3 = QtWidgets.QWidget() + self.tab_3.setObjectName("tab_3") + self.checkBox_DI_EnableInputPointer = QtWidgets.QCheckBox(self.tab_3) + self.checkBox_DI_EnableInputPointer.setGeometry(QtCore.QRect(10, 10, 221, 17)) + self.checkBox_DI_EnableInputPointer.setObjectName("checkBox_DI_EnableInputPointer") + self.checkBox_DI_EnableDimPointer = QtWidgets.QCheckBox(self.tab_3) + self.checkBox_DI_EnableDimPointer.setGeometry(QtCore.QRect(240, 10, 261, 20)) + self.checkBox_DI_EnableDimPointer.setObjectName("checkBox_DI_EnableDimPointer") + self.groupBox_2 = QtWidgets.QGroupBox(self.tab_3) + self.groupBox_2.setGeometry(QtCore.QRect(10, 30, 221, 141)) + self.groupBox_2.setObjectName("groupBox_2") + self.pushButton_DI_PointerInputSettings = QtWidgets.QPushButton(self.groupBox_2) + self.pushButton_DI_PointerInputSettings.setGeometry(QtCore.QRect(54, 110, 111, 23)) + self.pushButton_DI_PointerInputSettings.setObjectName("pushButton_DI_PointerInputSettings") + self.label = QtWidgets.QLabel(self.groupBox_2) + self.label.setGeometry(QtCore.QRect(10, 20, 201, 81)) + self.label.setFrameShape(QtWidgets.QFrame.Box) + self.label.setText("") + self.label.setPixmap(QtGui.QPixmap(":/plugins/qad/icons/dsettings/pointer_input.png")) + self.label.setScaledContents(True) + self.label.setObjectName("label") + self.groupBox_3 = QtWidgets.QGroupBox(self.tab_3) + self.groupBox_3.setGeometry(QtCore.QRect(240, 30, 261, 141)) + self.groupBox_3.setObjectName("groupBox_3") + self.pushButton_DI_DimensionInputSettings = QtWidgets.QPushButton(self.groupBox_3) + self.pushButton_DI_DimensionInputSettings.setGeometry(QtCore.QRect(80, 110, 111, 23)) + self.pushButton_DI_DimensionInputSettings.setObjectName("pushButton_DI_DimensionInputSettings") + self.label_15 = QtWidgets.QLabel(self.groupBox_3) + self.label_15.setGeometry(QtCore.QRect(10, 20, 241, 81)) + self.label_15.setFrameShape(QtWidgets.QFrame.Box) + self.label_15.setText("") + self.label_15.setPixmap(QtGui.QPixmap(":/plugins/qad/icons/dsettings/dimension_input.png")) + self.label_15.setScaledContents(True) + self.label_15.setObjectName("label_15") + self.groupBox_4 = QtWidgets.QGroupBox(self.tab_3) + self.groupBox_4.setGeometry(QtCore.QRect(10, 180, 491, 111)) + self.groupBox_4.setObjectName("groupBox_4") + self.checkBox_DI_ShowPrompt = QtWidgets.QCheckBox(self.groupBox_4) + self.checkBox_DI_ShowPrompt.setGeometry(QtCore.QRect(219, 20, 261, 41)) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.checkBox_DI_ShowPrompt.sizePolicy().hasHeightForWidth()) + self.checkBox_DI_ShowPrompt.setSizePolicy(sizePolicy) + self.checkBox_DI_ShowPrompt.setTristate(False) + self.checkBox_DI_ShowPrompt.setObjectName("checkBox_DI_ShowPrompt") + self.label_16 = QtWidgets.QLabel(self.groupBox_4) + self.label_16.setGeometry(QtCore.QRect(10, 20, 201, 81)) + self.label_16.setFrameShape(QtWidgets.QFrame.Box) + self.label_16.setText("") + self.label_16.setPixmap(QtGui.QPixmap(":/plugins/qad/icons/dsettings/dynamic_prompts.png")) + self.label_16.setScaledContents(True) + self.label_16.setObjectName("label_16") + self.pushButton_DI_TootipAppearance = QtWidgets.QPushButton(self.tab_3) + self.pushButton_DI_TootipAppearance.setGeometry(QtCore.QRect(110, 320, 311, 23)) + self.pushButton_DI_TootipAppearance.setObjectName("pushButton_DI_TootipAppearance") + self.tabWidget.addTab(self.tab_3, "") + self.layoutWidget3 = QtWidgets.QWidget(DSettings_Dialog) + self.layoutWidget3.setGeometry(QtCore.QRect(230, 420, 295, 30)) + self.layoutWidget3.setObjectName("layoutWidget3") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.layoutWidget3) + self.horizontalLayout.setContentsMargins(0, 0, 0, 0) + self.horizontalLayout.setObjectName("horizontalLayout") + self.okButton = QtWidgets.QPushButton(self.layoutWidget3) + self.okButton.setObjectName("okButton") + self.horizontalLayout.addWidget(self.okButton) + self.cancelButton = QtWidgets.QPushButton(self.layoutWidget3) + self.cancelButton.setObjectName("cancelButton") + self.horizontalLayout.addWidget(self.cancelButton) + self.pushButton_HELP = QtWidgets.QPushButton(self.layoutWidget3) + self.pushButton_HELP.setObjectName("pushButton_HELP") + self.horizontalLayout.addWidget(self.pushButton_HELP) + + self.retranslateUi(DSettings_Dialog) + self.tabWidget.setCurrentIndex(2) + self.pushButton_DeSelectALL.pressed.connect(DSettings_Dialog.ButtonDeselectALL_Pressed) # type: ignore + self.pushButton_SelectALL.pressed.connect(DSettings_Dialog.ButtonSelectALL_Pressed) # type: ignore + self.pushButton_HELP.clicked.connect(DSettings_Dialog.ButtonHELP_Pressed) # type: ignore + self.okButton.clicked.connect(DSettings_Dialog.ButtonBOX_Accepted) # type: ignore + self.cancelButton.clicked.connect(DSettings_Dialog.reject) # type: ignore + self.pushButton_DI_PointerInputSettings.clicked.connect(DSettings_Dialog.Button_DI_PointerInputSettings_Pressed) # type: ignore + self.pushButton_DI_DimensionInputSettings.clicked.connect(DSettings_Dialog.Button_DI_DimensionInputSettings_Pressed) # type: ignore + self.pushButton_DI_TootipAppearance.clicked.connect(DSettings_Dialog.Button_DI_TootipAppearance_Pressed) # type: ignore + self.pushButton_AddAngle.clicked.connect(DSettings_Dialog.ButtonNewAngle) # type: ignore + self.pushButton_DelAngle.clicked.connect(DSettings_Dialog.ButtonDelAngle) # type: ignore + self.checkBox_AdditionaltAngles.clicked.connect(DSettings_Dialog.CheckAdditionalAngles) # type: ignore + QtCore.QMetaObject.connectSlotsByName(DSettings_Dialog) + + def retranslateUi(self, DSettings_Dialog): + _translate = QtCore.QCoreApplication.translate + DSettings_Dialog.setWindowTitle(_translate("DSettings_Dialog", "Drawing settings")) + self.groupBox.setTitle(_translate("DSettings_Dialog", "Object Snap modes")) + self.pushButton_SelectALL.setText(_translate("DSettings_Dialog", "Select All")) + self.pushButton_DeSelectALL.setText(_translate("DSettings_Dialog", "Deselect All")) + self.checkBox_PERP.setToolTip(_translate("DSettings_Dialog", "\n" +"\n" +"

      Perpendicular OSnap: orthogonal projection of a given point on a segment.

      \n" +"

      ")) + self.checkBox_PERP.setText(_translate("DSettings_Dialog", "Perpendicular")) + self.checkBox_TANP.setToolTip(_translate("DSettings_Dialog", "\n" +"\n" +"

      tangent point on a curve of a line passing through a given point.

      \n" +"

      ")) + self.checkBox_TANP.setText(_translate("DSettings_Dialog", "Tangent")) + self.checkBox_EXTP.setToolTip(_translate("DSettings_Dialog", "\n" +"\n" +"

      Extension OSnap: point on the segment extension until the cursor position.

      \n" +"

      ")) + self.checkBox_EXTP.setText(_translate("DSettings_Dialog", "Extend")) + self.checkBox_PARALP.setToolTip(_translate("DSettings_Dialog", "\n" +"\n" +"

      Parallel OSnap: point on a line, passing through a given point, parallel to a segment.

      \n" +"

      ")) + self.checkBox_PARALP.setText(_translate("DSettings_Dialog", "Parallel")) + self.checkBox_PROGRESP.setToolTip(_translate("DSettings_Dialog", "\n" +"\n" +"

      Progressive OSnap: point at a given distance along a geometry line: from a vertex we can set a point at a distance measured along the geometry line.

      \n" +"

      ")) + self.checkBox_PROGRESP.setText(_translate("DSettings_Dialog", "Progressive")) + self.checkBox_EXT_INT.setToolTip(_translate("DSettings_Dialog", "\n" +"\n" +"

      Intersection on extension OSnap: intersection point of the extensions of two segments.

      \n" +"

      ")) + self.checkBox_EXT_INT.setText(_translate("DSettings_Dialog", "Intersection on extension")) + self.checkBox_QUADP.setToolTip(_translate("DSettings_Dialog", "\n" +"\n" +"

      Quadrant OSnap: intersections of the cartesian axis with a circumference of a circle or an arc.

      \n" +"

      ")) + self.checkBox_QUADP.setText(_translate("DSettings_Dialog", "Quadrant")) + self.checkBox_END_PLINE.setToolTip(_translate("DSettings_Dialog", "\n" +"\n" +"

      Start / End OSnap: starting and ending vertices of a linear geometry.

      \n" +"

      ")) + self.checkBox_END_PLINE.setText(_translate("DSettings_Dialog", "Start / End")) + self.checkBox_ENDP.setToolTip(_translate("DSettings_Dialog", "\n" +"\n" +"

      Start / End segment OSnap: starting and ending vertices of each segment of a geometry.

      \n" +"

      ", "aa")) + self.checkBox_ENDP.setText(_translate("DSettings_Dialog", "Segment Start / End")) + self.checkBox_MIDP.setToolTip(_translate("DSettings_Dialog", "\n" +"\n" +"

      Middle point OSnap: middle point of each segment of a geometry.

      \n" +"

      ")) + self.checkBox_MIDP.setText(_translate("DSettings_Dialog", "Middle point")) + self.checkBox_INTP.setToolTip(_translate("DSettings_Dialog", "\n" +"\n" +"

      Intersection OSnap: intersection between two segments.

      \n" +"

      ")) + self.checkBox_INTP.setText(_translate("DSettings_Dialog", "Intersection")) + self.checkBox_CENP.setToolTip(_translate("DSettings_Dialog", "\n" +"\n" +"

      Center OSnap: center of a circle or arc or centroid of an areal geometry.

      \n" +"

      ")) + self.checkBox_CENP.setText(_translate("DSettings_Dialog", "Center")) + self.checkBox_NODP.setToolTip(_translate("DSettings_Dialog", "\n" +"\n" +"

      Node OSnap: coordinate of a punctual geometry.

      \n" +"

      ")) + self.checkBox_NODP.setText(_translate("DSettings_Dialog", "Node")) + self.checkBox_NEARP.setToolTip(_translate("DSettings_Dialog", "\n" +"\n" +"

      Near OSnap: point of a segment close to the cursor position.

      \n" +"

      ")) + self.checkBox_NEARP.setText(_translate("DSettings_Dialog", "Near")) + self.checkBox_IsOsnapON.setToolTip(_translate("DSettings_Dialog", "

      Turns object snap on and off. The selected object snap modes are active when the object snap is activated (system variable OSMODE).

      ")) + self.checkBox_IsOsnapON.setText(_translate("DSettings_Dialog", "Object Snap (F3)")) + self.checkBox_ObjectSnapTracking.setToolTip(_translate("DSettings_Dialog", "

      Turns object snap tracking on and off. Using the object snap tracking, the cursor can track along alignment paths that are based on object snap points. To use the object snap tracking, select one or more object snap (system variable AUTOSNAP).

      ")) + self.checkBox_ObjectSnapTracking.setText(_translate("DSettings_Dialog", "Object Snap Tracking (F11)")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_1), _translate("DSettings_Dialog", "Object Snap")) + self.checkBox_PolarPickPoint.setToolTip(_translate("DSettings_Dialog", "Turns polar tracking on and off (system variable AUTOSNAP).")) + self.checkBox_PolarPickPoint.setText(_translate("DSettings_Dialog", "Polar Tracking (F10)")) + self.groupBox_PolarAngleSettings.setTitle(_translate("DSettings_Dialog", "Polar angle settings")) + self.label_12.setText(_translate("DSettings_Dialog", "Increment angle:")) + self.checkBox_AdditionaltAngles.setToolTip(_translate("DSettings_Dialog", "Makes additional angles in the list available for polar tracking. (POLARMODE and POLARADDANG system variables). Note: Additional angles are absolute, not incremental.")) + self.checkBox_AdditionaltAngles.setText(_translate("DSettings_Dialog", "Additional angles")) + self.listView_AdditionalAngles.setToolTip(_translate("DSettings_Dialog", "

      If Additional Angles is selected, the available additional angles are listed.

      To add new angles, choose New. To remove existing angles, click Delete.

      (POLARADDANG system variable)

      ")) + self.pushButton_AddAngle.setToolTip(_translate("DSettings_Dialog", "Adds additional polar tracking alignment angles.")) + self.pushButton_AddAngle.setText(_translate("DSettings_Dialog", "New")) + self.pushButton_DelAngle.setToolTip(_translate("DSettings_Dialog", "Deletes selected additional angles.")) + self.pushButton_DelAngle.setText(_translate("DSettings_Dialog", "Delete")) + self.groupBox_OsnapPolarOrtho.setTitle(_translate("DSettings_Dialog", "Object Snap Tracking Settings")) + self.radioButton_OsnapOrtho.setToolTip(_translate("DSettings_Dialog", "Displays only orthogonal (horizontal/vertical) object snap tracking paths for acquired object snap points when object snap tracking is on (POLARMODE system variable).")) + self.radioButton_OsnapOrtho.setText(_translate("DSettings_Dialog", "Track orthogonally only")) + self.radioButton_OsnapPolarAngle.setToolTip(_translate("DSettings_Dialog", "Applies polar tracking settings to object snap tracking. When you use object snap tracking, the cursor tracks along polar alignment angles from acquired object snap points (POLARMODE system variable).")) + self.radioButton_OsnapPolarAngle.setText(_translate("DSettings_Dialog", "Track using polar angle settings")) + self.groupBox_OsnapPolarMeasurement.setTitle(_translate("DSettings_Dialog", "Polar Angle measurement")) + self.radioButton_OsnapPolarAbolute.setToolTip(_translate("DSettings_Dialog", "Bases polar tracking angles on the current user coordinate system.")) + self.radioButton_OsnapPolarAbolute.setText(_translate("DSettings_Dialog", "Absolute")) + self.radioButton_OsnapPolarRelative.setToolTip(_translate("DSettings_Dialog", "Bases polar tracking angles on the last segment drawn.")) + self.radioButton_OsnapPolarRelative.setText(_translate("DSettings_Dialog", "Relative to last segment")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), _translate("DSettings_Dialog", "Polar Tracking")) + self.checkBox_DI_EnableInputPointer.setToolTip(_translate("DSettings_Dialog", "Turns on pointer input. When pointer input and dimensional input are both turned on, dimensional input supersedes pointer input when it is available. (DYNMODE system variable)")) + self.checkBox_DI_EnableInputPointer.setText(_translate("DSettings_Dialog", "Enable Pointer Input")) + self.checkBox_DI_EnableDimPointer.setToolTip(_translate("DSettings_Dialog", "Turns on dimensional input. Dimensional input is not available for some commands that prompt for a second point. (DYNMODE system variable)")) + self.checkBox_DI_EnableDimPointer.setText(_translate("DSettings_Dialog", "Enable Dimension Input where possible")) + self.groupBox_2.setTitle(_translate("DSettings_Dialog", "Pointer Input")) + self.pushButton_DI_PointerInputSettings.setToolTip(_translate("DSettings_Dialog", "Displays the Pointer Input Settings dialog box.")) + self.pushButton_DI_PointerInputSettings.setText(_translate("DSettings_Dialog", "Settings...")) + self.groupBox_3.setTitle(_translate("DSettings_Dialog", "Dimension Input")) + self.pushButton_DI_DimensionInputSettings.setToolTip(_translate("DSettings_Dialog", "Displays the Dimension Input Settings dialog box.")) + self.pushButton_DI_DimensionInputSettings.setText(_translate("DSettings_Dialog", "Settings...")) + self.groupBox_4.setTitle(_translate("DSettings_Dialog", "Dynamic Prompts")) + self.checkBox_DI_ShowPrompt.setToolTip(_translate("DSettings_Dialog", "Displays prompts in Dynamic Input tooltips. (DYNPROMPT system variable)")) + self.checkBox_DI_ShowPrompt.setText(_translate("DSettings_Dialog", "Show command prompting and \n" +"command input near the crosshairs")) + self.pushButton_DI_TootipAppearance.setToolTip(_translate("DSettings_Dialog", "Displays the Tooltip Appearance dialog box.")) + self.pushButton_DI_TootipAppearance.setText(_translate("DSettings_Dialog", "Drafting Tooltip Appearence...")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_3), _translate("DSettings_Dialog", "Dynamic Input")) + self.okButton.setText(_translate("DSettings_Dialog", "OK")) + self.cancelButton.setText(_translate("DSettings_Dialog", "Cancel")) + self.pushButton_HELP.setText(_translate("DSettings_Dialog", "?")) +from . import qad_dsettings_rc diff --git a/qad_dynamicinput.py b/qad_dynamicinput.py new file mode 100644 index 00000000..b99a5549 --- /dev/null +++ b/qad_dynamicinput.py @@ -0,0 +1,3486 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + classe per gestire l'input dinamico + + ------------------- + begin : 2017-07-27 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from qgis.core import * +from qgis.gui import * + +from qgis.PyQt.QtCore import Qt, QTimer, QPoint +from qgis.PyQt.QtGui import QColor, QFontMetrics, QTextCursor, QIcon, QPixmap +from qgis.PyQt.QtWidgets import QTextEdit, QSizePolicy, QApplication, QLabel, QWidget + +import math + +from . import qad_utils +from .qad_arc import QadArc +from .qad_entity import QadEntity +from .qad_grip import QadGripPointTypeEnum, QadEntityGripPoint +from .qad_msg import QadMsg +from .qad_rubberband import createRubberBand +from .qad_snapper import * +from .qad_textwindow import QadCmdSuggestWindow, QadInputTypeEnum, QadInputModeEnum +from .qad_variables import * +from .qad_variables import QadVariables, QadINPUTSEARCHOPTIONSEnum +from .qad_vertexmarker import * +from .qad_dim import QadDimStyles + + +# =============================================================================== +# QadDynamicInputContextEnum class. +# =============================================================================== +class QadDynamicInputContextEnum(): + NONE = 0 + COMMAND = 1 # richiesta di un comando + EDIT = 2 # richiesta in editazione + + +# =============================================================================== +# QadDynamicInputEditEnum class. +# =============================================================================== +class QadDynamicInputEditEnum(): # vedi initGui che dichiara un vettore lungo quanto sono i valori di QadDynamicInputEditEnum + CMD_LINE_EDIT = 0 # usato per inserire un comando + PROMPT_EDIT = 1 # usato per messaggi e scelta opzioni di comando + EDIT = 2 # usato per richiesta generica di un valore (es. raggio, scala, rotazione) + EDIT_X = 3 # usato per coordinata X + EDIT_Y = 4 # usato per coordinata Y + EDIT_Z = 5 # usato per coordinata Z + # usato per distanza dal punto precedente se segmento, usato per lunghezza raggio nel punto finale della parte precedente se arco + # oppure lunghezza raggio nel punto medio se parte precedente e successiva sono lo stesso arco + EDIT_DIST_PREV_PT = 6 + # usato per distanza relativa alla posizione precedente dello stesso punto nel verso dal punto precedente se segmento + # usato per lunghezza raggio nel punto iniziale della parte precedente se arco + EDIT_REL_DIST_PREV_PT = 7 + # usato per angolo dal punto precedente se segmento, usato per angolo arco nel punto finale della parte precedente se arco + EDIT_ANG_PREV_PT = 8 + # usato per angolo relativo all'angolo dal punto precedente se segmento + # usato per angolo totale arco se parte precedente e successiva sono lo stesso arco + EDIT_REL_ANG_PREV_PT = 9 + # usato per distanza dal punto successivo se segmento, + # usato per lunghezza raggio nel punto iniziale della parte successiva se arco + EDIT_DIST_NEXT_PT = 10 + # usato per distanza relativa alla posizione precedente dello stesso punto nel verso dal punto successivo se segmento + # usato per lunghezza raggio nel punto finale della parte successiva se arco + EDIT_REL_DIST_NEXT_PT = 11 + EDIT_ANG_NEXT_PT = 12 # usato per angolo dal punto successivo, usato per angolo arco nel punto iniziale della parte successiva + EDIT_REL_ANG_NEXT_PT = 13 # usato per angolo relativo all'angolo dal punto successivo se segmento + EDIT_SYMBOL_COORD_TYPE = 14 # usato per indicare se coordinata assoluta "#" o relativa "@" + + +# ogni QadDynamicInputEdit è gestita dalle funzioni di QadDynamicEditInput: +# initGui, setFocus, setNextCurrentEdit, setPrevCurrentEdit, show, mouseMoveEvent, moveCtrls + + +# =============================================================================== +# QadDynamicEdit +# =============================================================================== +class QadDynamicEdit(QTextEdit): +# """ +# Classe che gestisce l'input dinamico in una QTextEdit +# """ + + def __init__(self, QadDynamicInputObj): + QTextEdit.__init__(self, QadDynamicInputObj.canvas) + self.QadDynamicInputObj = QadDynamicInputObj + self.plugIn = QadDynamicInputObj.plugIn + self.canvas = QadDynamicInputObj.canvas + + self.font_size = 8 + QadVariables.get(QadMsg.translate("Environment variables", "TOOLTIPSIZE")) + height = self.font_size + 15 + + self.setTextInteractionFlags(Qt.TextEditorInteraction) + self.setMinimumSize(height, height) + self.setMaximumHeight(height) + self.setUndoRedoEnabled(False) + self.setAcceptRichText(False) + self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.error = False # indica se il valore contenuto nella edit è errato + self.lockedPos = False # indica se la posizione della edit è bloccata + + + # ============================================================================ + # focusInEvent + # ============================================================================ + def focusInEvent(self, e): + pass + + + # ============================================================================ + # reset + # ============================================================================ + def reset(self): + self.showMsg("") + self.error = False + self.lockedPos = False + + + # ============================================================================ + # setColors + # ============================================================================ + def setColors(self, foregroundColor = None, backGroundColor = None, borderColor = None, \ + selectionColor = None, selectionBackGroundColor = None, opacity = 100): + # se i colori sono None allora non vengono alterati + # caso particolare per borderColor = "" non viene disegnato + # opacity = 0-100 + oldFmt = self.styleSheet().split(";") + fmt = "rgba({0},{1},{2},{3}%)" + + if foregroundColor is not None: + c = QColor(foregroundColor) + rgbStrForeColor = "color: " + fmt.format(str(c.red()), str(c.green()), str(c.blue()), str(opacity)) + ";" + else: + rgbStrForeColor = "" + for f in oldFmt: + if f.find("color:") == 0: + rgbStrForeColor = f + ";" + break + + if backGroundColor is not None: + c = QColor(backGroundColor) + rgbStrBackColor = "background-color: " + fmt.format(str(c.red()), str(c.green()), str(c.blue()), str(opacity)) + ";" + else: + rgbStrBackColor = "" + for f in oldFmt: + if f.find("background-color:") == 0: + rgbStrBackColor = f + ";" + break + + # se è in stato di errore il bordo deve essere rosso largo 2 pixel + if self.error: + c = QColor(Qt.red) + rgbStrBorderColor = "border-color: " + fmt.format(str(c.red()), str(c.green()), str(c.blue()), str(opacity)) + ";" + fmtBorder = "border:2px;border-style:solid;" + else: + if borderColor is not None: + if borderColor == "": # senza bordo + rgbStrBorderColor = "" + fmtBorder = "border:0px;border-style:solid;" + else: + c = QColor(borderColor) + rgbStrBorderColor = "border-color: " + fmt.format(str(c.red()), str(c.green()), str(c.blue()), str(opacity)) + ";" + fmtBorder = "border:1px;border-style:solid;" + else: + rgbStrBorderColor = "" + fmtBorder = "" + for f in oldFmt: + if f.find("border-color:") == 0: + rgbStrBorderColor = f + ";" + elif f.find("border:") == 0: + fmtBorder = fmtBorder + f + ";" + elif f.find("border-style:") == 0: + fmtBorder = fmtBorder + f + ";" + + if selectionColor is not None: + c = QColor(selectionColor) + rgbStrSelectionColor = "selection-color: " + fmt.format(str(c.red()), str(c.green()), str(c.blue()), str(opacity)) + ";" + else: + rgbStrSelectionColor = "" + for f in oldFmt: + if f.find("selection-color:") == 0: + rgbStrSelectionColor = f + ";" + break + + if selectionBackGroundColor is not None: + c = QColor(selectionBackGroundColor) + rgbStrSelectionBackColor = "selection-background-color: " + fmt.format(str(c.red()), str(c.green()), str(c.blue()), str(opacity)) + ";" + else: + rgbStrSelectionBackColor = "" + for f in oldFmt: + if f.find("selection-background-color:") == 0: + rgbStrSelectionBackColor = f + ";" + break + + fmt = rgbStrForeColor + \ + rgbStrBackColor + \ + fmtBorder + \ + rgbStrBorderColor + \ + rgbStrSelectionColor + \ + rgbStrSelectionBackColor + \ + "font-size: " + str(self.font_size) + "pt;" + + self.setStyleSheet(fmt) + + + def refreshWidth(self, updateCtrlsPos = True): + fm = QFontMetrics(self.currentFont()) + boundingRect = fm.boundingRect(self.toPlainText()) + width = int(boundingRect.width() * 1.2) + 5 + height = boundingRect.height() + + canvasRect = self.canvas.rect() + if width > canvasRect.width(): + width = canvasRect.width() + + self.resize(width, height) + if updateCtrlsPos: self.QadDynamicInputObj.moveCtrls() + + + # ============================================================================ + # selectAllText + # ============================================================================ + def selectAllText(self): + # seleziono tutto il testo + cursor = self.textCursor() + cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor) + cursor.movePosition(QTextCursor.Start, QTextCursor.KeepAnchor) + self.setTextCursor(cursor) + + + # ============================================================================ + # showMsg + # ============================================================================ + def showMsg(self, msg, dummy1 = False, dummy2 = False, updateCtrlsPos = True): + self.error = False + cursor = self.textCursor() + sep = msg.rfind("\n") + if sep >= 0: + newMsg = msg[sep + 1:] + cursor.movePosition(QTextCursor.Start, QTextCursor.MoveAnchor) + cursor.movePosition(QTextCursor.End, QTextCursor.KeepAnchor) + else: + newMsg = msg + cursor.movePosition(QTextCursor.Start, QTextCursor.MoveAnchor) + cursor.movePosition(QTextCursor.End, QTextCursor.KeepAnchor) + + self.setTextCursor(cursor) + self.insertPlainText(newMsg) + self.refreshWidth(updateCtrlsPos) + + + # ============================================================================ + # removeItems + # ============================================================================ + def removeItems(self): + pass + + +# =============================================================================== +# QadDynamicInputCmdLineEdit +# =============================================================================== +class QadDynamicInputCmdLineEdit(QadDynamicEdit): +# """ +# Classe che gestisce l'input dinamico della sola linea di comando +# """ + + def __init__(self, QadDynamicInputObj): + QadDynamicEdit.__init__(self, QadDynamicInputObj) + self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) + + self.historyIndex = 0 + + self.timerForCmdSuggestWindow = QTimer() + self.timerForCmdSuggestWindow.setSingleShot(True) + self.timerForCmdAutoComplete = QTimer() + self.timerForCmdAutoComplete.setSingleShot(True) + + self.infoCmds = [] + self.infoVars = [] + + self.cmdSuggestWindow = None + + foregroundColor = QColor(QadVariables.get(QadMsg.translate("Environment variables", "CMDLINEFORECOLOR"))) + backGroundColor = QColor(QadVariables.get(QadMsg.translate("Environment variables", "CMDLINEBACKCOLOR"))) + borderColor = QColor(QadVariables.get(QadMsg.translate("Environment variables", "DYNEDITBORDERCOLOR"))) + opacity = 100 - QadVariables.get(QadMsg.translate("Environment variables", "TOOLTIPTRANSPARENCY")) + self.setColors(foregroundColor, backGroundColor, borderColor, backGroundColor, foregroundColor, opacity) + self.show(False) + + + def initCmdVarsList(self): + # lista composta da elementi con: + # , , , + self.infoCmds = [] + for cmdName in self.plugIn.getCommandNames(): + cmd = self.plugIn.getCommandObj(cmdName[0]) + if cmd is not None: + self.infoCmds.append([cmdName[0], cmd.getEnglishName(), cmd.getIcon(), cmd.getNote()]) + + # Creo la finestra per il suggerimento delle variabili di ambiente + # lista composta da elementi con: + # , "", , + self.infoVars = [] + icon = QIcon(":/plugins/qad/icons/variable.svg") + for varName in QadVariables.getVarNames(): + var = QadVariables.getVariable(varName) + self.infoVars.append([varName, "", icon, var.descr]) + + + def show(self, mode): + if mode == False: + self.timerForCmdAutoComplete.stop() + self.lockedPos = False + self.showCmdSuggestWindow(False) # hide suggestion window + QTextEdit.setVisible(self, False) + else: +# if self.isVisible() == False: +# self.showMsg("") + QTextEdit.setVisible(self, True) + self.setFocus() + + + # ============================================================================ + # showMsg + # ============================================================================ + def showMsg(self, msg, dummy1 = False, dummy2 = False, updateCtrlsPos = True): + # la funzione showMsg viene usata da showMsg(self, cmd) nella classe QadCmdSuggestWindow + # la quale comunica sia con QadEdit che con QadDynamicInputCmdLineEdit + # per compatibilità con la showMsg di QadEdit devo aggiungere due parametri fittizi (dummy, dummy2) + QadDynamicEdit.showMsg(self, msg, dummy1, dummy2, updateCtrlsPos) + + + # ============================================================================ + # showEvaluateMsg + # ============================================================================ + def showEvaluateMsg(self, msg, append = False): # per compatibilità con QadCmdSuggestListView.mouseReleaseEvent + return self.QadDynamicInputObj.showEvaluateMsg(msg) + + + # ============================================================================ + # showCmdSuggestWindow + # ============================================================================ + def showCmdSuggestWindow(self, mode = True, filter = ""): + if mode == False: # se spengo la finestra + self.timerForCmdSuggestWindow.stop() + + inputSearchOptions = QadVariables.get(QadMsg.translate("Environment variables", "INPUTSEARCHOPTIONS")) + # inputSearchOptions & QadINPUTSEARCHOPTIONSEnum.ON = Turns on all automated keyboard features when typing at the Command prompt + # inputSearchOptions & QadINPUTSEARCHOPTIONSEnum.DISPLAY_LIST = Displays a list of suggestions as keystrokes are entered + if (inputSearchOptions & QadINPUTSEARCHOPTIONSEnum.ON and inputSearchOptions & QadINPUTSEARCHOPTIONSEnum.DISPLAY_LIST) and \ + mode == True: + if self.cmdSuggestWindow is None: + # Creo la finestra per il suggerimento dei comandi + self.initCmdVarsList() + self.cmdSuggestWindow = QadCmdSuggestWindow(self.canvas, self, self.infoCmds, self.infoVars) + self.cmdSuggestWindow.initGui() + + if self.cmdSuggestWindow.setFilter(filter) == 0: + self.lockedPos = False; + self.cmdSuggestWindow.show(False) + return + + dataHeight = self.cmdSuggestWindow.getDataHeight() + if dataHeight > 0: + self.cmdSuggestWindow.cmdNamesListView.setMinimumHeight(self.cmdSuggestWindow.cmdNamesListView.sizeHintForRow(0)) + + dataWidth = 200 + + # Ricavo la posizione dell'angolo in alto a sin della QTextEdit relativa al suo parent + editRect = self.geometry() + ptEditRect = QPoint(editRect.left(), editRect.top()) + if self.parentWidget(): + ptEditRect = self.parentWidget().mapToGlobal(QPoint(editRect.left(), editRect.top())) + ptUp = QApplication.desktop().mapFromGlobal(ptEditRect) + + spaceUp = ptUp.y() if ptUp.y() - dataHeight < 0 else dataHeight + + ptDown = QPoint(ptUp.x(), ptUp.y() + editRect.height()) + desktopRect = QApplication.desktop().screenGeometry() + spaceDown = desktopRect.height() - ptDown.y() if ptDown.y() + dataHeight > desktopRect.height() else dataHeight + + # verifico se c'è più spazio sopra o sotto la finestra + if spaceUp > spaceDown: + pt = QPoint(ptUp.x(), ptUp.y() - spaceUp) + dataHeight = spaceUp + else: + pt = QPoint(ptDown.x(), ptDown.y()) + dataHeight = spaceDown + + if pt.x() + dataWidth > desktopRect.width(): # se sborda oltre il limite di destra + if desktopRect.width() - dataWidth < 0: # se anche spostando la finestra a sinistra sborda a sinisitra + pt.setX(0) + dataWidth = desktopRect.width() + else: + pt.setX(desktopRect.width() - dataWidth) + + pt = QApplication.desktop().mapToGlobal(pt) + self.cmdSuggestWindow.move(pt) + self.cmdSuggestWindow.resize(dataWidth, dataHeight) + + self.cmdSuggestWindow.show(True) + self.lockedPos = True # quando la finestra dei suggerimenti è aperta la posizione si blocca + else: + if self.cmdSuggestWindow is not None: + self.lockedPos = False + self.cmdSuggestWindow.show(False) + + + def showCmdAutoComplete(self, filter = ""): + # autocompletamento + self.timerForCmdAutoComplete.stop() + + # autocompletamento + inputSearchOptions = QadVariables.get(QadMsg.translate("Environment variables", "INPUTSEARCHOPTIONS")) + filterLen = len(filter) + if filterLen < 2: + return + + # inputSearchOptions & QadINPUTSEARCHOPTIONSEnum.ON = Turns on all automated keyboard features when typing at the Command prompt + # inputSearchOptions & QadINPUTSEARCHOPTIONSEnum.AUTOCOMPLETE = Automatically appends suggestions as each keystroke is entered after the second keystroke. + if inputSearchOptions & QadINPUTSEARCHOPTIONSEnum.ON and inputSearchOptions & QadINPUTSEARCHOPTIONSEnum.AUTOCOMPLETE: + if filterLen >= 2: + cmdName, qty = self.plugIn.getMoreUsedCmd(filter) + else: + cmdName = "" + + self.appendCmdTextForAutoComplete(cmdName, filterLen) + + + def appendCmdTextForAutoComplete(self, cmdName, filterLen): + cursor = self.textCursor() + #cursor.movePosition(QTextCursor.End, QTextCursor.KeepAnchor) + self.setTextCursor(cursor) + if filterLen < len(cmdName): # se c'è qualcosa da aggiungere + self.insertPlainText(cmdName[filterLen:]) + else: + self.insertPlainText("") + cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor) + cursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, len(cmdName) - filterLen) + self.setTextCursor(cursor) + self.refreshWidth() + + + def showNextCmd(self): + # mostra il comando successivo nella lista dei comandi usati + cmdsHistory = self.plugIn.cmdsHistory + cmdsHistoryLen = len(cmdsHistory) + if self.historyIndex < cmdsHistoryLen and cmdsHistoryLen > 0: + self.historyIndex += 1 + if self.historyIndex < cmdsHistoryLen: + self.showMsg(cmdsHistory[self.historyIndex]) + + + def showPreviousCmd(self): + # mostra il comando precedente nella lista dei comandi usati + cmdsHistory = self.plugIn.cmdsHistory + cmdsHistoryLen = len(cmdsHistory) + if self.historyIndex > 0 and cmdsHistoryLen > 0: + self.historyIndex -= 1 + if self.historyIndex < cmdsHistoryLen: + self.showMsg(cmdsHistory[self.historyIndex]) + + + def showLastCmd(self): + # mostra e ritorna l'ultimo comando nella lista dei comandi usati + cmdsHistory = self.plugIn.cmdsHistory + cmdsHistoryLen = len(cmdsHistory) + if cmdsHistoryLen > 0: + self.showMsg(cmdsHistory[cmdsHistoryLen - 1]) + return cmdsHistory[cmdsHistoryLen - 1] + else: + return "" + + + # ============================================================================ + # keyPressEvent + # ============================================================================ + def keyPressEvent(self, e): + + if self.plugIn.shortCutManagement(e): # se è stata gestita una sequenza di tasti scorciatoia + return + + # if Up or Down is pressed + if self.isVisibleCmdSuggestWindow() and \ + (e.key() == Qt.Key_Down or e.key() == Qt.Key_Up or e.key() == Qt.Key_PageDown or e.key() == Qt.Key_PageUp or + e.key() == Qt.Key_End or e.key() == Qt.Key_Home): + self.cmdSuggestWindow.keyPressEvent(e) + return + else: # nascondo la finestra di suggerimento + self.lockedPos = False + self.showCmdSuggestWindow(False) + + if e.key() == Qt.Key_Escape: + cmdsHistory = self.plugIn.cmdsHistory + self.historyIndex = len(cmdsHistory) + self.QadDynamicInputObj.abort() + return + + # if Return or Space is pressed, then perform the commands + if e.key() == Qt.Key_Return or e.key() == Qt.Key_Space or e.key == Qt.Key_Enter: + self.entered() + return + # if Up or Down is pressed + elif e.key() == Qt.Key_Down: + self.showNextCmd() + return # per non far comparire la finestra di suggerimento + elif e.key() == Qt.Key_Up: + self.showPreviousCmd() + return # per non far comparire la finestra di suggerimento + else: + if (e.key() != Qt.Key_Tab and e.key() != Qt.Key_Backtab) or \ + e.text() != "": + # all other keystrokes get sent to the input line + QTextEdit.keyPressEvent(self, e) + self.refreshWidth() + + # leggo il tempo di ritardo in msec + inputSearchDelay = QadVariables.get(QadMsg.translate("Environment variables", "INPUTSEARCHDELAY")) + + # lista suggerimento dei comandi simili + currMsg = self.toPlainText() + shot1 = lambda: self.showCmdSuggestWindow(True, currMsg) + + del self.timerForCmdSuggestWindow + self.timerForCmdSuggestWindow = QTimer() + self.timerForCmdSuggestWindow.setSingleShot(True) + self.timerForCmdSuggestWindow.timeout.connect(shot1) + self.timerForCmdSuggestWindow.start(inputSearchDelay) + + if e.text().isalnum(): # autocompletamento se è stato premuto un tasto alfanumerico + shot2 = lambda: self.showCmdAutoComplete(self.toPlainText()) + del self.timerForCmdAutoComplete + self.timerForCmdAutoComplete = QTimer() + self.timerForCmdAutoComplete.setSingleShot(True) + + self.timerForCmdAutoComplete.timeout.connect(shot2) + self.timerForCmdAutoComplete.start(inputSearchDelay) + + + def entered(self): + if self.QadDynamicInputObj.refreshResult() == True: # ricalcolo il risultato + self.QadDynamicInputObj.showEvaluateMsg(self.QadDynamicInputObj.resStr) # uso il risultato in formato stringa + self.reset() + cmdsHistory = self.plugIn.cmdsHistory + self.historyIndex = len(cmdsHistory) + + + def isVisibleCmdSuggestWindow(self): + if self.cmdSuggestWindow is None: + return False + return self.cmdSuggestWindow.isVisible() + + +# =============================================================================== +# QadDynamicInputPromptEdit +# =============================================================================== +class QadDynamicInputPromptEdit(QadDynamicEdit): +# """ +# Classe che gestisce l'input dinamico del prompt dei messaggi e scelta opzioni di comando +# """ + + def __init__(self, QadDynamicInputObj): + QadDynamicEdit.__init__(self, QadDynamicInputObj) + + foregroundColor = QColor(QadVariables.get(QadMsg.translate("Environment variables", "DYNEDITFORECOLOR"))) + backGroundColor = QColor(QadVariables.get(QadMsg.translate("Environment variables", "DYNEDITBACKCOLOR"))) + borderColor = QColor(QadVariables.get(QadMsg.translate("Environment variables", "DYNEDITBORDERCOLOR"))) + opacity = 100 - QadVariables.get(QadMsg.translate("Environment variables", "TOOLTIPTRANSPARENCY")) + self.setColors(foregroundColor, backGroundColor, borderColor, backGroundColor, foregroundColor, opacity) + self.show(False) + + + def show(self, mode): + if mode == False: + QTextEdit.setVisible(self, False) + else: + QTextEdit.setVisible(self, True) + + +# # ============================================================================ +# # keyPressEvent +# # ============================================================================ +# def keyPressEvent(self, e): +# pass + + +# =============================================================================== +# QadDynamicInputEdit +# =============================================================================== +class QadDynamicInputEdit(QadDynamicEdit): +# """ +# Classe che gestisce l'input dinamico di un valore +# """ + + def __init__(self, QadDynamicInputObj): + QadDynamicEdit.__init__(self, QadDynamicInputObj) + self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) + + # il default è un numero reale non nullo + self.inputMode = QadInputModeEnum.NOT_NULL + self.inputType = QadInputTypeEnum.FLOAT + + self.lockable = True # indica se il valore della edit può essere bloccato + self.__lockedValue = False # indica se il valore contenuto nella edit è bloccato + + foregroundColor = QColor(QadVariables.get(QadMsg.translate("Environment variables", "DYNEDITFORECOLOR"))) + backGroundColor = QColor(QadVariables.get(QadMsg.translate("Environment variables", "DYNEDITBACKCOLOR"))) + borderColor = QColor(QadVariables.get(QadMsg.translate("Environment variables", "DYNEDITBORDERCOLOR"))) + opacity = 100 - QadVariables.get(QadMsg.translate("Environment variables", "TOOLTIPTRANSPARENCY")) + self.setColors(foregroundColor, backGroundColor, borderColor, backGroundColor, foregroundColor, opacity) + + # icona di lock + fm = QFontMetrics(self.currentFont()) + height = self.height() - 4 + self.LockedIcon = QLabel(self) + self.LockedIcon.resize(height, height) + self.LockedIcon.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + self.LockedIcon.setStyleSheet("border:0px;"); # senza bordo + pixmap = QPixmap(":/plugins/qad/icons/locked.svg").scaled(height, height) + self.LockedIcon.setPixmap(pixmap) + + self.lineMarkerColor = QColor(QadVariables.get(QadMsg.translate("Environment variables", "DYNTRECKINGVECTORCOLOR"))) + self.lineMarkers = [] # lista dei RubberBand visualizzati + + self.show(False) + + + # ============================================================================ + # __del__ + # ============================================================================ + def __del__(self): + self.removeItems() + + + def setLockedValue(self, mode): + # ritorna True e l'operazione ha avuto successo + if self.lockable == False: return False + if self.__lockedValue != mode: + self.__lockedValue = mode + self.LockedIcon.setVisible(self.__lockedValue) + self.refreshWidth() + + + def isLockedValue(self): + return self.__lockedValue + + + # ============================================================================ + # removeItems + # ============================================================================ + def removeItems(self): + # svuoto la linea di marker rimuovendoli dal canvas + for lineMarker in self.lineMarkers: + lineMarker.hide() + self.plugIn.canvas.scene().removeItem(lineMarker) + del self.lineMarkers[:] + + + # ============================================================================ + # reset + # ============================================================================ + def reset(self): + QadDynamicEdit.reset(self) + self.inputMode = QadInputModeEnum.NONE + self.__lockedValue = False + self.removeItems() + + + # ============================================================================ + # show + # ============================================================================ + def show(self, mode): + QTextEdit.setVisible(self, mode) + for lineMarker in self.lineMarkers: + lineMarker.setVisible(mode) + if mode == False: + self.LockedIcon.setVisible(False) + else: + if self.isLockedValue(): + self.LockedIcon.setVisible(True) + else: + self.LockedIcon.setVisible(False) + + + # ============================================================================ + # refreshWidth + # ============================================================================ + def refreshWidth(self, updateCtrlsPos = True): + height = self.height() + fm = QFontMetrics(self.currentFont()) + boundingRect = fm.boundingRect(self.toPlainText()) + width = int(boundingRect.width() * 1.2) + 5 + + dimLockedIcon = self.LockedIcon.height() + offset = 2 + if self.isLockedValue(): + width = width + dimLockedIcon + offset # per icona di lock + else: + width = width + offset # per icona di lock + + canvasRect = self.canvas.rect() + if width > canvasRect.width(): + width = canvasRect.width() + + self.resize(width, height) + self.LockedIcon.move(width - dimLockedIcon - offset, height - dimLockedIcon - 2) + if updateCtrlsPos: self.QadDynamicInputObj.moveCtrls() + + + # ============================================================================ + # keyPressEvent + # ============================================================================ + def keyPressEvent(self, e): + if self.plugIn.shortCutManagement(e): # se è stata gestita una sequenza di tasti scorciatoia + return + + if e.key() == Qt.Key_Tab: + if self.checkValid() is not None: + # mi sposto sulla edit successiva + self.QadDynamicInputObj.setNextCurrentEdit() + else: # valore errato + pass + + elif e.key() == Qt.Key_Backtab: + if self.checkValid() is not None: + self.QadDynamicInputObj.setPrevCurrentEdit() + else: # valore errato + pass + + elif e.key() == Qt.Key_Return or e.key == Qt.Key_Enter: + self.QadDynamicInputObj.keyPressEvent(e) # lo faccio gestire da QadDynamicInputObj +# if self.isLockedValue() == True: +# value = self.toPlainText() +# snapType = str2snapTypeEnum(value) +# if snapType != -1: # se é stato forzato uno snap +# self.QadDynamicInputObj.keyPressEvent(e) # lo faccio gestire da QadDynamicInputObj +# elif self.checkValid() is not None: +# self.QadDynamicInputObj.keyPressEvent(e) # lo faccio gestire da QadDynamicInputObj +# else: +# self.QadDynamicInputObj.keyPressEvent(e) # lo faccio gestire da QadDynamicInputObj + + elif (e.key() == Qt.Key_Down or e.key() == Qt.Key_Up or e.key() == Qt.Key_PageDown or e.key() == Qt.Key_PageUp or \ + e.key() == Qt.Key_End or e.key() == Qt.Key_Home): + pass # al momento si è deciso di non mostrare il menu delle opzioni del comando attivo da qui + + elif e.key() == Qt.Key_Comma: # "," + self.QadDynamicInputObj.keyPressEvent(e) # lo faccio gestire da QadDynamicInputObj + + # se non si tratta di stringa e si tratta di un carattere speciale + elif not (self.inputType & QadInputTypeEnum.STRING) and \ + (e.text() == "@" or e.text() == "#" or e.text() == "<"): + self.QadDynamicInputObj.keyPressEvent(e) # lo faccio gestire da QadDynamicInputObj + + elif e.key() == Qt.Key_Escape: + self.QadDynamicInputObj.abort() + + elif e.text() != "": + previousTxt = self.toPlainText() + QTextEdit.keyPressEvent(self, e) + if self.lockable: # se è possibile modificare lo stato di lock + currentTxt = self.toPlainText() + if currentTxt == "": + self.setLockedValue(False) + elif currentTxt != previousTxt: + self.setLockedValue(True) + else: + self.refreshWidth() + + + # ============================================================================ + # focusInEvent + # ============================================================================ + def focusInEvent(self, e): + # cambio il colore + foregroundColor = QColor(Qt.black) + backGroundColor = QColor(Qt.white) + borderColor = QColor(QadVariables.get(QadMsg.translate("Environment variables", "DYNEDITBORDERCOLOR"))) + opacity = 100 - QadVariables.get(QadMsg.translate("Environment variables", "TOOLTIPTRANSPARENCY")) + selectionColor = QColor(Qt.white) + selectionBackGroundColor = QColor(51, 153, 255) # azzurro (R=51 G=153 B=255) + self.setColors(foregroundColor, backGroundColor, borderColor, selectionColor, selectionBackGroundColor, opacity) + self.selectAllText() # seleziono tutto il testo + + + # ============================================================================ + # focusOutEvent + # ============================================================================ + def focusOutEvent(self, e): + # cambio il colore + foregroundColor = QColor(QadVariables.get(QadMsg.translate("Environment variables", "DYNEDITFORECOLOR"))) + backGroundColor = QColor(QadVariables.get(QadMsg.translate("Environment variables", "DYNEDITBACKCOLOR"))) + borderColor = QColor(QadVariables.get(QadMsg.translate("Environment variables", "DYNEDITBORDERCOLOR"))) + opacity = 100 - QadVariables.get(QadMsg.translate("Environment variables", "TOOLTIPTRANSPARENCY")) + self.setColors(foregroundColor, backGroundColor, borderColor, backGroundColor, foregroundColor, opacity) + # seleziono il testo + cursor = self.textCursor() + cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor) + cursor.movePosition(QTextCursor.End, QTextCursor.KeepAnchor) + self.setTextCursor(cursor) + + + # ============================================================================ + # checkValid + # ============================================================================ + def checkValid(self): + # ritorna None in caso di errore + value = self.toPlainText() + self.error = False + + if value == "" and (self.inputMode & QadInputModeEnum.NOT_NULL): # non permesso input nullo + self.error = True + self.setColors() # ricolora con i bordi rossi perchè error=True + self.selectAllText() # seleziono tutto il testo + return None + + if self.inputType & QadInputTypeEnum.INT or self.inputType & QadInputTypeEnum.LONG or \ + self.inputType & QadInputTypeEnum.FLOAT or self.inputType & QadInputTypeEnum.ANGLE: + if self.inputType & QadInputTypeEnum.INT: # si aspetta un numero intero + value = qad_utils.str2int(value) + if value is None: + self.error = True + self.setColors() # ricolora con i bordi rossi perchè error=True + self.selectAllText() # seleziono tutto il testo + return None + elif self.inputType & QadInputTypeEnum.LONG: # si aspetta un numero long + value = qad_utils.str2long(value) + if value is None: + self.error = True + self.setColors() # ricolora con i bordi rossi perchè error=True + self.selectAllText() # seleziono tutto il testo + return None + elif self.inputType & QadInputTypeEnum.FLOAT or self.inputType & QadInputTypeEnum.ANGLE: # si aspetta un numero reale + value = qad_utils.str2float(value) + if value is None: + self.error = True + self.setColors() # ricolora con i bordi rossi perchè error=True + self.selectAllText() # seleziono tutto il testo + return None + + # non permesso valore = 0 + # non permesso valore < 0 + # non permesso valore > 0 + if (value == 0 and (self.inputMode & QadInputModeEnum.NOT_ZERO)) or \ + (value < 0 and (self.inputMode & QadInputModeEnum.NOT_NEGATIVE)) or \ + (value > 0 and (self.inputMode & QadInputModeEnum.NOT_POSITIVE)): + self.error = True + self.setColors() # ricolora con i bordi rossi perchè error=True + self.selectAllText() # seleziono tutto il testo + return None + + if self.inputType & QadInputTypeEnum.ANGLE: # si aspetta un angolo in gradi + if value is not None: + # i gradi vanno convertiti in radianti + value = float(qad_utils.toRadians(value)) + + elif self.inputType & QadInputTypeEnum.BOOL: + value = qad_utils.str2bool(value) + if value is None: + self.error = True + self.setColors() # ricolora con i bordi rossi perchè error=True + self.selectAllText() # seleziono tutto il testo + return None + + return value + + + # ============================================================================ + # setLinesMarker + # ============================================================================ + def setLinesMarker(self, points): + """ + Crea un marcatore lineare x una lista di punti + """ + # svuoto la linea di marker rimuovendoli dal canvas + for lineMarker in self.lineMarkers: + lineMarker.hide() + self.plugIn.canvas.scene().removeItem(lineMarker) + del self.lineMarkers[:] + + lineMarker = createRubberBand(self.canvas, QgsWkbTypes.LineGeometry, True) + lineMarker.setColor(self.lineMarkerColor) + lineMarker.setLineStyle(Qt.DotLine) + if points is None: + return None + tot = len(points) + i = 0 + while i < (tot - 1): + lineMarker.addPoint(points[i], False) + i = i + 1 + lineMarker.addPoint(points[i], True) + self.lineMarkers.append(lineMarker) + + + +# =============================================================================== +class QadDynamicInput(QWidget): +# =============================================================================== +# """ +# Classe base che gestisce l'input dinamico +# """ + + + # ============================================================================ + # __init__ + # ============================================================================ + def __init__(self, plugIn): + QWidget.__init__(self, plugIn.canvas) + self.plugIn = plugIn + self.canvas = self.plugIn.canvas + self.prevPart = None # parte precedente il punto da spostare in modo grip + self.nextPart = None # parte successiva il punto da spostare in modo grip + + self.resValue = None # valore risultante + self.resStr = "" # risultato in formato stringa + + self.default = None + self.mousePos = QPoint() + self.isVisible = False + + self.initGui() + self.currentEdit = None + self.refreshOnEnvVariables() + + + # ============================================================================ + # __del__ + # ============================================================================ + def __del__(self): + self.removeItems() + + + # ============================================================================ + # getPrompt + # ============================================================================ + def getPrompt(self): + return self.edits[QadDynamicInputEditEnum.PROMPT_EDIT].toPlainText() + + + # ============================================================================ + # setPrevPart + # ============================================================================ + def setPrevPart(self, linearObject): + if linearObject is None: + if self.prevPart is not None: + del self.prevPart + self.prevPart = None + else: + if self.prevPart is None: + self.prevPart = linearObject.copy() + else: + self.prevPart.set(linearObject) + + + # ============================================================================ + # setNextPart + # ============================================================================ + def setNextPart(self, linearObject): + if linearObject is None: + if self.nextPart is not None: + del self.nextPart + self.nextPart = None + else: + if self.nextPart is None: + self.nextPart = linearObject.copy() + else: + self.nextPart.set(linearObject) + + + # ============================================================================ + # removeItems + # ============================================================================ + def removeItems(self): + self.show(False) + for edit in self.edits: + edit.removeItems() + + self.setPrevPart(None) + self.setNextPart(None) + + + # ============================================================================ + # refreshOnEnvVariables + # ============================================================================ + def refreshOnEnvVariables(self): + # DYNDIGRIP = Controlla la visualizzazione delle quote dinamiche durante la modifica dello stiramento dei grip + self.dynDiGrip = QadVariables.get(QadMsg.translate("Environment variables", "DYNDIGRIP")) + # DYNDIVIS = Controlla il numero di quote dinamiche visualizzate durante la modifica dello stiramento dei grip + self.dynDiVis = QadVariables.get(QadMsg.translate("Environment variables", "DYNDIVIS")) + # DYNMODE = Attiva e disattiva le funzioni di input dinamico + self.dynMode = QadVariables.get(QadMsg.translate("Environment variables", "DYNMODE")) + # DYNPICOORDS = Determina se l'input del puntatore utilizza un formato relativo o assoluto per le coordinate + self.dynPiCoords = QadVariables.get(QadMsg.translate("Environment variables", "DYNPICOORDS")) + # DYNPIFORMAT = Determina se l'input del puntatore utilizza un formato polare o cartesiano per le coordinate + self.dynPiFormat = QadVariables.get(QadMsg.translate("Environment variables", "DYNPIFORMAT")) + # DYNPIVIS = Controlla quando è visualizzato l'input puntatore + self.dynPiVis = QadVariables.get(QadMsg.translate("Environment variables", "DYNPIVIS")) + # DYNPROMPT = Controlla la visualizzazione dei messaggi di richiesta nelle descrizioni di input dinamico + self.dynPrompt = QadVariables.get(QadMsg.translate("Environment variables", "DYNPROMPT")) + + + # ============================================================================ + # setColors + # ============================================================================ + def setColors(self): + opacity = 100 - QadVariables.get(QadMsg.translate("Environment variables", "TOOLTIPTRANSPARENCY")) + foregroundColor = QColor(QadVariables.get(QadMsg.translate("Environment variables", "DYNEDITFORECOLOR"))) + backGroundColor = QColor(QadVariables.get(QadMsg.translate("Environment variables", "DYNEDITBACKCOLOR"))) + borderColor = QColor(QadVariables.get(QadMsg.translate("Environment variables", "DYNEDITBORDERCOLOR"))) + + for i in range(0, len(self.edits), 1): + if i == QadDynamicInputEditEnum.CMD_LINE_EDIT: # per CMD_LINE_EDIT + cmdForegroundColor = QColor(QadVariables.get(QadMsg.translate("Environment variables", "CMDLINEFORECOLOR"))) + cmdBackGroundColor = QColor(QadVariables.get(QadMsg.translate("Environment variables", "CMDLINEBACKCOLOR"))) + self.edits[QadDynamicInputEditEnum.CMD_LINE_EDIT].setColors(cmdForegroundColor, cmdBackGroundColor, None, \ + cmdBackGroundColor, cmdForegroundColor, opacity) + else: # tutti gli edit tranne CMD_LINE_EDIT + self.edits[i].setColors(foregroundColor, backGroundColor, borderColor, backGroundColor, foregroundColor, opacity) + + + def isActive(self): + # ritorna True se input dinamico è attivato + return True if self.dynMode > 0 else False + + + def isPointInputOn(self): # ritorna True se input del puntatore attivato + return True if self.dynMode & 1 else False + + + def isDimensionalInputOn(self): # ritorna True se input di quota attivato + return True if self.dynMode & 2 else False + + + def isPromptActive(self): + return True if self.isActive() and self.dynPrompt == 1 else False + + + def hasFocus(self): # ritorna True se uno widget di input ha il fuoco + for edit in self.edits: + if edit.hasFocus(): return True + return False + + + # ============================================================================ + # initGui + # ============================================================================ + def initGui(self): + # creo un array di edit + self.edits = [None] * 15 # vedi QadDynamicInputEditEnum quanti elementi ha + + # usato per inserire un comando + self.edits[QadDynamicInputEditEnum.CMD_LINE_EDIT] = QadDynamicInputCmdLineEdit(self) + # usato per messaggi e scelta opzioni di comando + self.edits[QadDynamicInputEditEnum.PROMPT_EDIT] = QadDynamicInputPromptEdit(self) + + # usato per richiesta generica (es. raggio, scala, angolo) + self.edits[QadDynamicInputEditEnum.EDIT] = QadDynamicInputEdit(self) + self.edits[QadDynamicInputEditEnum.EDIT].lockable == False # valore non bloccabile + + # usato per indicare se coordinata assoluta "#" o relativa "@" e se cartesiana o polare "<" + self.edits[QadDynamicInputEditEnum.EDIT_SYMBOL_COORD_TYPE] = QadDynamicInputEdit(self) + self.edits[QadDynamicInputEditEnum.EDIT_SYMBOL_COORD_TYPE].inputMode = QadInputModeEnum.NONE + self.edits[QadDynamicInputEditEnum.EDIT_SYMBOL_COORD_TYPE].inputType = QadInputTypeEnum.STRING + # usato per coordinata X + self.edits[QadDynamicInputEditEnum.EDIT_X] = QadDynamicInputEdit(self) + self.edits[QadDynamicInputEditEnum.EDIT_X].inputMode = QadInputModeEnum.NOT_NULL + self.edits[QadDynamicInputEditEnum.EDIT_X].inputType = QadInputTypeEnum.FLOAT + # usato per coordinata Y + self.edits[QadDynamicInputEditEnum.EDIT_Y] = QadDynamicInputEdit(self) + self.edits[QadDynamicInputEditEnum.EDIT_Y].inputMode = QadInputModeEnum.NOT_NULL + self.edits[QadDynamicInputEditEnum.EDIT_Y].inputType = QadInputTypeEnum.FLOAT + # usato per coordinata Z + self.edits[QadDynamicInputEditEnum.EDIT_Z] = QadDynamicInputEdit(self) + self.edits[QadDynamicInputEditEnum.EDIT_Z].inputMode = QadInputModeEnum.NOT_NULL + self.edits[QadDynamicInputEditEnum.EDIT_Z].inputType = QadInputTypeEnum.FLOAT + + # usato per distanza dal punto precedente + self.edits[QadDynamicInputEditEnum.EDIT_DIST_PREV_PT] = QadDynamicInputEdit(self) + self.edits[QadDynamicInputEditEnum.EDIT_DIST_PREV_PT].inputMode = QadInputModeEnum.NOT_NULL + self.edits[QadDynamicInputEditEnum.EDIT_DIST_PREV_PT].inputType = QadInputTypeEnum.FLOAT + + # usato per distanza in più o in meno rispetto la distanza dal punto precedente + self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_PREV_PT] = QadDynamicInputEdit(self) + self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_PREV_PT].inputMode = QadInputModeEnum.NOT_NULL + self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_PREV_PT].inputType = QadInputTypeEnum.FLOAT + + # usato per angolo dal punto precedente + self.edits[QadDynamicInputEditEnum.EDIT_ANG_PREV_PT] = QadDynamicInputEdit(self) + self.edits[QadDynamicInputEditEnum.EDIT_ANG_PREV_PT].inputMode = QadInputModeEnum.NOT_NULL + self.edits[QadDynamicInputEditEnum.EDIT_ANG_PREV_PT].inputType = QadInputTypeEnum.ANGLE + + # usato per angolo in più o in meno rispetto l'angolo dal punto precedente + self.edits[QadDynamicInputEditEnum.EDIT_REL_ANG_PREV_PT] = QadDynamicInputEdit(self) + self.edits[QadDynamicInputEditEnum.EDIT_REL_ANG_PREV_PT].inputMode = QadInputModeEnum.NOT_NULL + self.edits[QadDynamicInputEditEnum.EDIT_REL_ANG_PREV_PT].inputType = QadInputTypeEnum.ANGLE + + # usato per distanza dal punto successivo + self.edits[QadDynamicInputEditEnum.EDIT_DIST_NEXT_PT] = QadDynamicInputEdit(self) + self.edits[QadDynamicInputEditEnum.EDIT_DIST_NEXT_PT].inputMode = QadInputModeEnum.NOT_NULL + self.edits[QadDynamicInputEditEnum.EDIT_DIST_NEXT_PT].inputType = QadInputTypeEnum.FLOAT + + # usato per distanza in più o in meno rispetto la distanza dal punto successivo + self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_NEXT_PT] = QadDynamicInputEdit(self) + self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_NEXT_PT].inputMode = QadInputModeEnum.NOT_NULL + self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_NEXT_PT].inputType = QadInputTypeEnum.FLOAT + + # usato per angolo dal punto successivo + self.edits[QadDynamicInputEditEnum.EDIT_ANG_NEXT_PT] = QadDynamicInputEdit(self) + self.edits[QadDynamicInputEditEnum.EDIT_ANG_NEXT_PT].inputMode = QadInputModeEnum.NOT_NULL + self.edits[QadDynamicInputEditEnum.EDIT_ANG_NEXT_PT].inputType = QadInputTypeEnum.ANGLE + + # usato per angolo in più o in meno rispetto l'angolo dal punto successivo + self.edits[QadDynamicInputEditEnum.EDIT_REL_ANG_NEXT_PT] = QadDynamicInputEdit(self) + self.edits[QadDynamicInputEditEnum.EDIT_REL_ANG_NEXT_PT].inputMode = QadInputModeEnum.NOT_NULL + self.edits[QadDynamicInputEditEnum.EDIT_REL_ANG_NEXT_PT].inputType = QadInputTypeEnum.ANGLE + + + # ============================================================================ + # reset + # ============================================================================ + def reset(self, default = None): + return # da virtualizzare + + + # ============================================================================ + # getInitialNdxEdit + # ============================================================================ + # restituisce la posizione del controllo iniziale + def getInitialNdxEdit(self): + return # da virtualizzare + + + # ============================================================================ + # setInitialFocus + # ============================================================================ + def setInitialFocus(self): + for edit in self.edits: + edit.setReadOnly(True) + + self.currentEdit = self.getInitialNdxEdit() + + if self.currentEdit is not None: + widget = self.edits[self.currentEdit] + widget.setReadOnly(False) + #widget.setWindowFlags(widget.windowFlags() | Qt.WindowStaysOnTopHint) + if widget.hasFocus(): # se ha già il fuoco coloro la casella e basta + widget.focusInEvent(None) + else: + widget.setFocus() + else: + self.canvas.setFocus() + + + # ============================================================================ + # setFocus + # ============================================================================ + def setFocus(self): + if self.currentEdit is None: # se non è settato quale edit è il corrente + self.setInitialFocus() + return + + for edit in self.edits: + edit.setReadOnly(True) + + self.edits[self.currentEdit].setReadOnly(False) + self.edits[self.currentEdit].setFocus() + + + # ============================================================================ + # getNextNdxEditSequence + # ============================================================================ + # restituisce la posizione del controllo successivo usando la sequenza + def getNextNdxEditSequence(self, currentEdit): + return # da virtualizzare + + + # ============================================================================ + # setNextCurrentEdit + # ============================================================================ + def setNextCurrentEdit(self): + return # da virtualizzare + + + # ============================================================================ + # getPrevNdxEditSequence + # ============================================================================ + # restituisce la posizione del controllo precedente usando la sequenza + def getPrevNdxEditSequence(self, currentEdit): + return # da virtualizzare + + + # ============================================================================ + # setPrevCurrentEdit + # ============================================================================ + def setPrevCurrentEdit(self): + return # da virtualizzare + + + # ============================================================================ + # isCoordWidgetVisib + # ============================================================================ + def isCoordWidgetVisib(self): + # ritorna se devono essere mostrati i widget relativi alle coordinate + return # da virtualizzare + + + # ============================================================================ + # isDimensionalWidgetVisib + # ============================================================================ + def isDimensionalWidgetVisib(self): + return # da virtualizzare + + + # ============================================================================ + # anyLockedValueEdit + # ============================================================================ + def anyLockedValueEdit(self): + if self.edits[QadDynamicInputEditEnum.EDIT_X].isLockedValue(): return True + if self.edits[QadDynamicInputEditEnum.EDIT_Y].isLockedValue(): return True + if self.edits[QadDynamicInputEditEnum.EDIT_Z].isLockedValue(): return True + if self.edits[QadDynamicInputEditEnum.EDIT].isLockedValue(): return True + if self.edits[QadDynamicInputEditEnum.EDIT_DIST_PREV_PT].isLockedValue(): return True + if self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_PREV_PT].isLockedValue(): return True + if self.edits[QadDynamicInputEditEnum.EDIT_ANG_PREV_PT].isLockedValue(): return True + if self.edits[QadDynamicInputEditEnum.EDIT_REL_ANG_PREV_PT].isLockedValue(): return True + if self.edits[QadDynamicInputEditEnum.EDIT_DIST_NEXT_PT].isLockedValue(): return True + if self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_NEXT_PT].isLockedValue(): return True + if self.edits[QadDynamicInputEditEnum.EDIT_ANG_NEXT_PT].isLockedValue(): return True + if self.edits[QadDynamicInputEditEnum.EDIT_REL_ANG_NEXT_PT].isLockedValue(): return True + return False + + + # ============================================================================ + # setDefault + # ============================================================================ + def setDefault(self, default): + # da virtualizzare + return + + + # ============================================================================ + # show + # ============================================================================ + def show(self, mode, mousePos = None, prompt = None, default = None): + # da virtualizzare + return + + + # ============================================================================ + # showErr + # ============================================================================ + def showErr(self, err = ""): + # da virtualizzare + return + + + # ============================================================================ + # showInputMsg + # ============================================================================ + def showInputMsg(self, inputMsg = None, inputType = QadInputTypeEnum.COMMAND, \ + default = None, keyWords = "", inputMode = QadInputModeEnum.NONE): + # da virtualizzare + return + + + # ============================================================================ + # mouseMoveEvent + # ============================================================================ + def mouseMoveEvent(self, mousePos): + # da virtualizzare + return + + + # ============================================================================ + # moveCtrls + # ============================================================================ + def moveCtrls(self, mousePos = None): + # sposta tutti i widget visibili a seconda del contesto + # da virtualizzare + return + + + # ============================================================================ + # getPosAndLineMarkerForLine + # ============================================================================ + def getPosAndLineMarkerForLine(self, pt1, pt2, offset, editWidget): + # Restituisce la posizione di un widget edit che verrà posto nel punto medio di una linea + # avente pt1 come punto iniziale, pt2 come punto finale ma spostata di un offset. + # La funzione restituisce anche le linee da usare come marker line + # le coordinate devono essere espresse in map coordinate + angle = qad_utils.getAngleBy2Pts(pt1, pt2, 0) # senza tolleranza + if angle >= 0 and angle < math.pi: + angle = angle + math.pi / 2 + else: + angle = angle - math.pi / 2 + + pt = qad_utils.getMiddlePoint(pt1, pt2) + + editPt = qad_utils.getPolarPointByPtAngle(pt, angle, offset) + editPt = self.canvas.getCoordinateTransform().transform(editPt) # Transform the point from map (world) coordinates to device coordinates + + editWidth = editWidget.width() + editHeight = editWidget.height() + x = editPt.x() - (editWidth / 2) + y = editPt.y() - (editHeight / 2) + + x, y = self.adjustEditPosition(x, y, editWidth, editHeight) + editPt = QPoint(x, y); + + pt1Corner = qad_utils.getPolarPointByPtAngle(pt1, angle, offset) + pt2Corner = qad_utils.getPolarPointByPtAngle(pt2, angle, offset) + + return editPt, [pt1, pt1Corner, pt2Corner, pt2] + + + # ============================================================================ + # getPosAndLineMarkerForArc + # ============================================================================ + def getPosAndLineMarkerForArc(self, start, center, end, offset, editWidget, LineMarkerOnlyArc = False): + # Restituisce la posizione di un widget edit che verrà posto nel punto medio di un arco + # avente start, il punto iniziale, center come centro e end come punto finale + # La funzione restituisce anche le linee che formano l'arco da usare come marker line + # se LineMarkerOnlyArc = True allora verrà restituito solo l'arco altrimenti + # verrà aggiunta una linea che parte dal centro dell'arco + arc1 = QadArc() + if arc1.fromStartCenterEndPts(start, center, end) == False: + return self.getPosAndLineMarkerForLine(center, end, offset, editWidget) + + arc2 = QadArc() + if arc2.fromStartCenterEndPts(end, center, start) == False: + return self.getPosAndLineMarkerForLine(center, end, offset, editWidget) + + if arc1.length() <= arc2.length(): + arc1.radius = arc1.radius + offset + pos, lineMarker = self.getPosAndLineMarkerForArcObj(arc1, editWidget) + if LineMarkerOnlyArc == False and lineMarker is not None: + lineMarker.insert(0, center) + else: + arc2.radius = arc2.radius + offset + pos, lineMarker = self.getPosAndLineMarkerForArcObj(arc2, editWidget) + if LineMarkerOnlyArc == False and lineMarker is not None: + lineMarker.append(center) + + return pos, lineMarker + + + # ============================================================================ + # getPosAndLineMarkerForArcObj + # ============================================================================ + def getPosAndLineMarkerForArcObj(self, arc, editWidget): + # Restituisce la posizione di un widget edit che verrà posto nel punto medio di un arco + # La funzione restituisce anche le linee che formano l'arco da usare come marker line + editPt = arc.getMiddlePt() + editPt = self.canvas.getCoordinateTransform().transform(editPt) # Transform the point from map (world) coordinates to device coordinates + + editWidth = editWidget.width() + editHeight = editWidget.height() + x = editPt.x() - (editWidth / 2) + y = editPt.y() - (editHeight / 2) + + x, y = self.adjustEditPosition(x, y, editWidth, editHeight) + editPt = QPoint(x, y) + + return editPt, arc.asPolyline() + + + # ============================================================================ + # adjustEditPosition + # ============================================================================ + def adjustEditPosition(self, x, y, width, height): + # aggiusta la posizione di un widget edit in modo che non esca dalla finestra canvas + canvasRect = self.plugIn.canvas.rect() + offsetY = height + + if x < 0: x = 0 + else: + overflow = x + width - canvasRect.width() + if overflow > 0: x = x - overflow + + if y < 0: y = 0 + else: + overflow = y + height + offsetY - canvasRect.height() + if overflow > 0: y = y - overflow + + # per evitare che il mouse si sovrapponga sposto il widget sopra il mouse (mi tengo 5 pixel di offset intorno al widget) + if self.mousePos is not None: + if self.mousePos.x() >= x - 5 and self.mousePos.x() <= x + width + 5 and \ + self.mousePos.y() >= y - 5 and self.mousePos.y() <= y + height + 5: + if canvasRect.bottom() - self.mousePos.y() < self.mousePos.y(): + y = self.mousePos.y() - height - offsetY + else: + y = self.mousePos.y() + offsetY + + return int(x), int(y) + + + # ============================================================================ + # refreshResult + # ============================================================================ + def refreshResult(self, mousePos = None): + # calcola il risultato e restituisce True se l'operazione ha successo + # il risultato è anche impostato in self.resValue e, in formato stringa, in self.resStr + # da virtualizzare + return + + + # ============================================================================ + # showEvaluateMsg + # ============================================================================ + def showEvaluateMsg(self, msg): + if self.isActive() == False or self.isVisible == False: return + self.plugIn.showEvaluateMsg(msg) + + + # ============================================================================ + # keyPressEvent + # ============================================================================ + def keyPressEvent(self, e): + # da virtualizzare + return + + + # ============================================================================ + # abort + # ============================================================================ + def abort(self): + self.isVisible = False + for edit in self.edits: + edit.show(False) + + self.show(False) + self.plugIn.abortCommand() + self.plugIn.clearCurrentObjsSelection() + self.canvas.setFocus() + + + +# =============================================================================== +class QadDynamicCmdInput(QadDynamicInput): +# =============================================================================== +# """ +# Classe base che gestisce l'input dinamico per input di un nuovo comando +# """ + + + # ============================================================================ + # __init__ + # ============================================================================ + def __init__(self, plugIn): + QadDynamicInput.__init__(self, plugIn) + + self.resValue = None # valore risultante + self.resStr = "" # risultato in formato stringa + + self.default = None + self.mousePos = QPoint() + self.isVisible = False + + self.initGui() + self.currentEdit = None + + + # ============================================================================ + # reset + # ============================================================================ + def reset(self, default = None): + # la funzione non deve resettare self.prevPart, self.nextPart + self.currentEdit = None + + for i in range(0, len(self.edits), 1): + self.edits[i].reset() + + # aggiorno tutti i colori dei controlli + self.setColors() + # se esiste un valore di default lo imposto + if default is not None: + self.setDefault(default) + + # commentato perchè crea problemi quando si seleziona un oggetto e si va con il mouse un un punto di grip (deseleziona l'oggetto) + #self.plugIn.clearCurrentObjsSelection() + + + # ============================================================================ + # getInitialNdxEdit + # ============================================================================ + # restituisce la posizione del controllo iniziale + def getInitialNdxEdit(self): + return QadDynamicInputEditEnum.CMD_LINE_EDIT + + + # ============================================================================ + # getNextNdxEditSequence + # ============================================================================ + # restituisce la posizione del controllo successivo usando la sequenza + def getNextNdxEditSequence(self, currentEdit): + return QadDynamicInputEditEnum.CMD_LINE_EDIT + + + # ============================================================================ + # setNextCurrentEdit + # ============================================================================ + def setNextCurrentEdit(self): + if self.currentEdit is None: # se non è settato quale edit è il corrente + self.setInitialFocus() + return + + self.currentEdit = self.getNextNdxEditSequence(self.currentEdit) + + self.setFocus() + + + # ============================================================================ + # getPrevNdxEditSequence + # ============================================================================ + # restituisce la posizione del controllo precedente usando la sequenza + def getPrevNdxEditSequence(self, currentEdit): + return QadDynamicInputEditEnum.CMD_LINE_EDIT + + + # ============================================================================ + # setPrevCurrentEdit + # ============================================================================ + def setPrevCurrentEdit(self): + if self.currentEdit is None: # se non è settato quale edit è il corrente + self.setInitialFocus() + return + + self.currentEdit = self.getNextNdxEditSequence(self.currentEdit) + + self.setFocus() + + + # ============================================================================ + # isCoordWidgetVisib + # ============================================================================ + def isCoordWidgetVisib(self): + # ritorna se devono essere mostrati i widget relativi alle coordinate + # se è abilitato l'input del puntatore e la visualizzazione delle coordinate è impostata da dynPiVis = 2 (visualizza sempre) + if self.isPointInputOn() and self.dynPiVis == 2: + return True + else: + return False + + + # ============================================================================ + # isDimensionalWidgetVisib + # ============================================================================ + def isDimensionalWidgetVisib(self): + # se non è abilitato l'input di quota + if self.isDimensionalInputOn() == False: return False + # se esiste una parte precedente o una parte successiva + if self.prevPart is not None or self.nextPart is not None: + return True + else: + return False + + + # ============================================================================ + # setDefault + # ============================================================================ + def setDefault(self, default): + self.default = default + self.edits[QadDynamicInputEditEnum.CMD_LINE_EDIT].showMsg(self.default) + + + # ============================================================================ + # setPrevNextPart + # ============================================================================ + def setPrevNextPart(self, entity, gripPoint): + # entity è di tipo QadEntity + # gripPoint è di tipo QadEntityGripPoint + prevPart = None + nextPart = None + # verifico se l'entità appartiene ad uno stile di quotatura + if QadDimStyles.isDimEntity(entity): + pass + else: + qadGeom = entity.getQadGeom(gripPoint.atGeom, gripPoint.atSubGeom) + qadGeomType = qadGeom.whatIs() + if qadGeomType == "ARC" or qadGeomType == "ELLIPSE_ARC": + if gripPoint.gripType == QadGripPointTypeEnum.ARC_MID_POINT: + prevPart = qadGeom.copy() + nextPart = qadGeom.copy() + else: + if qad_utils.ptNear(qadGeom.getStartPt(), gripPoint.getPoint()): + nextPart = qadGeom.copy() + elif qad_utils.ptNear(qadGeom.getEndPt(), gripPoint.getPoint()): + prevPart = qadGeom.copy() + + elif qadGeomType == "POLYLINE": + prevPart, nextPart = qadGeom.getPrevNextLinearObjectsAtVertex(gripPoint.nVertex) + if (gripPoint.gripType == QadGripPointTypeEnum.LINE_MID_POINT or \ + gripPoint.gripType == QadGripPointTypeEnum.ARC_MID_POINT) and \ + nextPart is not None: + prevPart = nextPart.copy() + + elif qadGeomType == "CIRCLE": + if qadGeom.isPtOnCircle(gripPoint.getPoint()): + prevPart = QadLine() + prevPart.set(qadGeom.center, gripPoint.getPoint()) + + elif qadGeomType == "ELLIPSE": + if qadGeom.containsPt(gripPoint.getPoint()): + prevPart = QadLine() + prevPart.set(qadGeom.center, gripPoint.getPoint()) + + self.setPrevPart(prevPart) + self.setNextPart(nextPart) + + + # ============================================================================ + # show + # ============================================================================ + def show(self, mode, mousePos = None, prompt = None, default = None): + # se si tratta di rendere invisibile lo faccio indipendentemente dal fatto che sia attivo o meno + # (serve per gestire F12) + if mode == False: + self.isVisible = False + for edit in self.edits: + edit.show(False) + return False + + if self.isActive() == False: return False + + # se viene passata la posizione del mouse la funzione + # resetta lo stato dell'input dinamico (errori, fuoco) + if mousePos is not None: + self.reset(default) + + if prompt is not None: + self.edits[QadDynamicInputEditEnum.PROMPT_EDIT].showMsg(prompt, False, False, False) # senza aggiornare la posizione dei controlli + + self.isVisible = True + + visibList = [False] * len(self.edits) + + if self.isPromptActive() and len(self.edits[QadDynamicInputEditEnum.PROMPT_EDIT].toPlainText()) > 0: + visibList[QadDynamicInputEditEnum.PROMPT_EDIT] = True + + if len(self.edits[QadDynamicInputEditEnum.CMD_LINE_EDIT].toPlainText()) > 0: + visibList[QadDynamicInputEditEnum.CMD_LINE_EDIT] = True + else: + # se devo visualizzare i widget delle coordinate + if self.isCoordWidgetVisib(): + visibList[QadDynamicInputEditEnum.EDIT_X] = True + visibList[QadDynamicInputEditEnum.EDIT_Y] = True + # se devo mostrare i widget delle quote + if self.isDimensionalWidgetVisib(): + if self.prevPart is not None: + gType = self.prevPart.whatIs() + if gType == "LINE": + visibList[QadDynamicInputEditEnum.EDIT_DIST_PREV_PT] = True # usato per lunghezza segmento precedente + elif gType == "ARC": # arco + visibList[QadDynamicInputEditEnum.EDIT_DIST_PREV_PT] = True # usato per lunghezza raggio arco + visibList[QadDynamicInputEditEnum.EDIT_REL_DIST_PREV_PT] = True # usato per lunghezza raggio arco + visibList[QadDynamicInputEditEnum.EDIT_ANG_PREV_PT] = True # usato per angolo arco nel punto finale della parte precedente + + if self.nextPart is not None: + gType = self.nextPart.whatIs() + if gType == "LINE": + visibList[QadDynamicInputEditEnum.EDIT_DIST_NEXT_PT] = True # usato per lunghezza segmento successivo + elif gType == "ARC": # arco + # se nextPart e prevPart sono uguali + if self.prevPart is not None and self.nextPart == self.prevPart: + visibList[QadDynamicInputEditEnum.EDIT_REL_ANG_PREV_PT] = True # usato per angolo totale arco + visibList[QadDynamicInputEditEnum.EDIT_REL_DIST_PREV_PT] = False # usato per lunghezza raggio arco parte precedente + else: + visibList[QadDynamicInputEditEnum.EDIT_DIST_NEXT_PT] = True # usato per lunghezza raggio arco + visibList[QadDynamicInputEditEnum.EDIT_REL_DIST_NEXT_PT] = True # usato per lunghezza raggio arco + visibList[QadDynamicInputEditEnum.EDIT_ANG_NEXT_PT] = True # usato per angolo arco nel punto iniziale della parte successiva + + for i in range(0, len(self.edits), 1): + self.edits[i].show(visibList[i]) + + self.setFocus() + + # riposiziono i widget + if mousePos is None: + self.mouseMoveEvent(self.canvas.mouseLastXY()) + else: + self.mouseMoveEvent(mousePos) + + return self.isVisible + + + # ============================================================================ + # showErr + # ============================================================================ + def showErr(self, err = ""): + if self.isActive() == False: return + + self.edits[QadDynamicInputEditEnum.CMD_LINE_EDIT].show(False) + if self.isPromptActive(): + self.edits[QadDynamicInputEditEnum.PROMPT_EDIT].error = True + self.edits[QadDynamicInputEditEnum.PROMPT_EDIT].setColors() # ricolora con i bordi rossi perchè error=True + self.edits[QadDynamicInputEditEnum.PROMPT_EDIT].show(True) + self.edits[QadDynamicInputEditEnum.PROMPT_EDIT].showMsg(err) + + self.canvas.setFocus() + + self.moveCtrls() # per riposizionare i controlli + + + # ============================================================================ + # mouseMoveEvent + # ============================================================================ + def mouseMoveEvent(self, mousePos): + if self.isActive() == False or self.isVisible == False: return + + point = self.canvas.getCoordinateTransform().toMapCoordinates(mousePos) # posizione + + # se i widget delle coordinate sono visibili + if self.edits[QadDynamicInputEditEnum.EDIT_X].isVisible(): + self.edits[QadDynamicInputEditEnum.EDIT_X].showMsg(qad_utils.numToStringFmt(point.x()), False, False, False) # senza aggiornare la posizione dei controlli + self.edits[QadDynamicInputEditEnum.EDIT_Y].showMsg(qad_utils.numToStringFmt(point.y()), False, False, False) # senza aggiornare la posizione dei controlli + + if self.prevPart is not None: + gType = self.prevPart.whatIs() + if gType == "LINE": + if self.edits[QadDynamicInputEditEnum.EDIT_DIST_PREV_PT].isVisible(): + # usato per lunghezza segmento precedente + self.edits[QadDynamicInputEditEnum.EDIT_DIST_PREV_PT].showMsg(qad_utils.numToStringFmt(self.prevPart.length()), False, False, False) # senza aggiornare la posizione dei controlli + elif gType == "ARC": + if self.edits[QadDynamicInputEditEnum.EDIT_DIST_PREV_PT].isVisible(): + # usato per lunghezza raggio arco + self.edits[QadDynamicInputEditEnum.EDIT_DIST_PREV_PT].showMsg(qad_utils.numToStringFmt(self.prevPart.radius), False, False, False) # senza aggiornare la posizione dei controlli + + if self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_PREV_PT].isVisible(): + # usato per lunghezza raggio arco + self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_PREV_PT].showMsg(qad_utils.numToStringFmt(self.prevPart.radius), False, False, False) # senza aggiornare la posizione dei controlli + + if self.edits[QadDynamicInputEditEnum.EDIT_ANG_PREV_PT].isVisible(): + # usato per angolo arco nel punto finale della parte precedente + if self.prevPart.reversed: + angle = self.prevPart.startAngle + else: + angle = self.prevPart.endAngle + + if angle >= math.pi and angle < 2 * math.pi: + angle = 2 * math.pi - angle + msg = qad_utils.numToStringFmt(qad_utils.toDegrees(angle)) + u'\N{DEGREE SIGN}' # simbolo dei gradi + self.edits[QadDynamicInputEditEnum.EDIT_ANG_PREV_PT].showMsg(msg, False, False, False) # senza aggiornare la posizione dei controlli + + if self.nextPart is not None: + gType = self.nextPart.whatIs() + if gType == "LINE": + if self.edits[QadDynamicInputEditEnum.EDIT_DIST_NEXT_PT].isVisible(): + # usato per lunghezza segmento successivo + self.edits[QadDynamicInputEditEnum.EDIT_DIST_NEXT_PT].showMsg(qad_utils.numToStringFmt(self.nextPart.length()), False, False, False) # senza aggiornare la posizione dei controlli + elif gType == "ARC": + if self.edits[QadDynamicInputEditEnum.EDIT_DIST_NEXT_PT].isVisible(): + # usato per lunghezza raggio arco + self.edits[QadDynamicInputEditEnum.EDIT_DIST_NEXT_PT].showMsg(qad_utils.numToStringFmt(self.nextPart.radius), False, False, False) # senza aggiornare la posizione dei controlli + + if self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_NEXT_PT].isVisible(): + # usato per lunghezza raggio arco + self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_NEXT_PT].showMsg(qad_utils.numToStringFmt(self.nextPart.radius), False, False, False) # senza aggiornare la posizione dei controlli + + if self.edits[QadDynamicInputEditEnum.EDIT_ANG_NEXT_PT].isVisible(): + # usato per angolo arco nel punto iniziale della parte successiva + if self.nextPart.reversed: + angle = self.nextPart.endAngle + else: + angle = self.nextPart.startAngle + + if angle >= math.pi and angle < 2 * math.pi: + angle = 2 * math.pi - angle + msg = qad_utils.numToStringFmt(qad_utils.toDegrees(angle)) + u'\N{DEGREE SIGN}' # simbolo dei gradi + self.edits[QadDynamicInputEditEnum.EDIT_ANG_NEXT_PT].showMsg(msg, False, False, False) # senza aggiornare la posizione dei controlli + + # se nextPart e prevPart sono uguali + if self.edits[QadDynamicInputEditEnum.EDIT_REL_ANG_PREV_PT].isVisible(): + # usato per angolo totale arco + msg = qad_utils.numToStringFmt(qad_utils.toDegrees(self.nextPart.totalAngle())) + u'\N{DEGREE SIGN}' # simbolo dei gradi + self.edits[QadDynamicInputEditEnum.EDIT_REL_ANG_PREV_PT].showMsg(msg, False, False, False) # senza aggiornare la posizione dei controlli + + + if self.currentEdit is not None: + self.edits[self.currentEdit].focusInEvent(None) # riporto il fuoco sul controllo corrente + + self.moveCtrls(mousePos) + + return + + + # ============================================================================ + # moveCtrls + # ============================================================================ + def moveCtrls(self, mousePos = None): + # sposta tutti i widget visibili a seconda del contesto + if mousePos is not None: + self.mousePos.setX(mousePos.x()) + self.mousePos.setY(mousePos.y()) + + height = self.edits[QadDynamicInputEditEnum.CMD_LINE_EDIT].height() + offset = 5 + + width = 0 + if self.edits[QadDynamicInputEditEnum.PROMPT_EDIT].isVisible(): + width += self.edits[QadDynamicInputEditEnum.PROMPT_EDIT].width() + + x = self.mousePos.x() + height + y = self.mousePos.y() + height + + if self.edits[QadDynamicInputEditEnum.PROMPT_EDIT].lockedPos or \ + self.edits[QadDynamicInputEditEnum.CMD_LINE_EDIT].lockedPos: + return; + if self.edits[QadDynamicInputEditEnum.CMD_LINE_EDIT].isVisible(): + if width > 0 : width += offset + offsetX_cmdLineEdit = width + width += self.edits[QadDynamicInputEditEnum.CMD_LINE_EDIT].width() + + x, y = self.adjustEditPosition(x, y, width, height) + + if self.edits[QadDynamicInputEditEnum.CMD_LINE_EDIT].isVisible(): + self.edits[QadDynamicInputEditEnum.CMD_LINE_EDIT].move(x + offsetX_cmdLineEdit, y) + + if self.edits[QadDynamicInputEditEnum.EDIT_X].isVisible() or \ + self.edits[QadDynamicInputEditEnum.EDIT_Y].isVisible(): + + if self.edits[QadDynamicInputEditEnum.EDIT_X].isVisible(): + if width > 0 : width += offset + offsetX_editX = width + width += self.edits[QadDynamicInputEditEnum.EDIT_X].width() + if self.edits[QadDynamicInputEditEnum.EDIT_Y].isVisible(): + if width > 0 : width += offset + offsetX_editY = width + width += self.edits[QadDynamicInputEditEnum.EDIT_Y].width() + + x, y = self.adjustEditPosition(x, y, width, height) + + if self.edits[QadDynamicInputEditEnum.EDIT_X].isVisible(): self.edits[QadDynamicInputEditEnum.EDIT_X].move(x + offsetX_editX, y) + if self.edits[QadDynamicInputEditEnum.EDIT_Y].isVisible(): self.edits[QadDynamicInputEditEnum.EDIT_Y].move(x + offsetX_editY, y) + + if self.edits[QadDynamicInputEditEnum.PROMPT_EDIT].isVisible(): + x, y = self.adjustEditPosition(x, y, width, height) + self.edits[QadDynamicInputEditEnum.PROMPT_EDIT].move(x, y) + + if self.prevPart is not None: + p1 = None + offset = (height * 2) * self.canvas.mapSettings().mapUnitsPerPixel() + gType = self.prevPart.whatIs() + + if gType == "LINE": + if self.edits[QadDynamicInputEditEnum.EDIT_DIST_PREV_PT].isVisible(): + # usato per lunghezza segmento precedente + p1 = self.prevPart.getStartPt() + p2 = self.prevPart.getEndPt() + elif gType == "ARC": + center = self.prevPart.center + if self.edits[QadDynamicInputEditEnum.EDIT_ANG_PREV_PT].isVisible(): + # usato per angolo arco nel punto finale della parte precedente + p2 = self.prevPart.getEndPt() + p1 = QgsPointXY(center.x() + self.prevPart.radius, center.y()) + editPt, lineMarkers = self.getPosAndLineMarkerForArc(p1, center, p2, offset, \ + self.edits[QadDynamicInputEditEnum.EDIT_ANG_PREV_PT]) + + if editPt is not None: + self.edits[QadDynamicInputEditEnum.EDIT_ANG_PREV_PT].move(editPt.x(), editPt.y()) + del editPt + if lineMarkers is not None: # se non nullo disegno la linea di marker aggiugendo una linea + if qad_utils.ptNear(lineMarkers[0], center): + lineMarkers.append(center) + else: + lineMarkers.insert(0, center) + self.edits[QadDynamicInputEditEnum.EDIT_ANG_PREV_PT].setLinesMarker(lineMarkers) + del lineMarkers + + if self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_PREV_PT].isVisible(): + # usato per lunghezza raggio arco nel punto iniziale della parte precedente + p1 = self.prevPart.center + p2 = self.prevPart.getStartPt() + editPt, lineMarkers = self.getPosAndLineMarkerForLine(p1, p2, \ + 0, self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_PREV_PT]) + self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_PREV_PT].move(editPt.x(), editPt.y()) + del editPt + # disegno la linea di marker + self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_PREV_PT].setLinesMarker(lineMarkers) + del lineMarkers + + if self.edits[QadDynamicInputEditEnum.EDIT_DIST_PREV_PT].isVisible(): + # usato per lunghezza raggio arco nel punto finale della parte precedente + # oppure lunghezza raggio nel punto medio se parte precedente e successiva coincidono + p1 = self.prevPart.center + # se nextPart e prevPart sono uguali + if self.nextPart is not None and self.nextPart == self.prevPart: + p2 = self.prevPart.getMiddlePt() + else: + p2 = self.prevPart.getEndPt() + offset = 0 + else: + p1 = None + + + if p1 is not None: + editPt, lineMarkers = self.getPosAndLineMarkerForLine(p1, p2, \ + offset, self.edits[QadDynamicInputEditEnum.EDIT_DIST_PREV_PT]) + self.edits[QadDynamicInputEditEnum.EDIT_DIST_PREV_PT].move(editPt.x(), editPt.y()) + del editPt + # disegno la linea di marker + self.edits[QadDynamicInputEditEnum.EDIT_DIST_PREV_PT].setLinesMarker(lineMarkers) + del lineMarkers + + if self.nextPart is not None: + p1 = None + offset = (height * 2) * self.canvas.mapSettings().mapUnitsPerPixel() + gType = self.nextPart.whatIs() + + if gType == "LINE": + if self.edits[QadDynamicInputEditEnum.EDIT_DIST_NEXT_PT].isVisible(): + # usato per lunghezza segmento precedente + p1 = self.nextPart.getStartPt() + p2 = self.nextPart.getEndPt() + elif gType == "ARC": + center = self.nextPart.center + if self.edits[QadDynamicInputEditEnum.EDIT_ANG_NEXT_PT].isVisible(): + # usato per angolo arco nel punto iniziale della parte successiva + p2 = self.nextPart.getStartPt() + p1 = QgsPointXY(center.x() + self.nextPart.radius, center.y()) + editPt, lineMarkers = self.getPosAndLineMarkerForArc(p1, center, p2, offset, \ + self.edits[QadDynamicInputEditEnum.EDIT_ANG_NEXT_PT]) + + if editPt is not None: + self.edits[QadDynamicInputEditEnum.EDIT_ANG_NEXT_PT].move(editPt.x(), editPt.y()) + del editPt + if lineMarkers is not None: # se non nullo disegno la linea di marker aggiugendo una linea + if qad_utils.ptNear(lineMarkers[0], center): + lineMarkers.append(center) + else: + lineMarkers.insert(0, center) + self.edits[QadDynamicInputEditEnum.EDIT_ANG_NEXT_PT].setLinesMarker(lineMarkers) + del lineMarkers + + # se nextPart e prevPart sono uguali + if self.edits[QadDynamicInputEditEnum.EDIT_REL_ANG_PREV_PT].isVisible(): + # usato per angolo totale arco + offsetArc = height * self.canvas.mapSettings().mapUnitsPerPixel() + totalArc = QadArc(self.nextPart) + totalArc.radius = totalArc.radius + offsetArc + editPt, lineMarkers = self.getPosAndLineMarkerForArcObj(totalArc, self.edits[QadDynamicInputEditEnum.EDIT_REL_ANG_PREV_PT]) + if editPt is not None: + self.edits[QadDynamicInputEditEnum.EDIT_REL_ANG_PREV_PT].move(editPt.x(), editPt.y()) + del editPt + if lineMarkers is not None: # se non nullo disegno la linea di marker aggiugendo una linea + lineMarkers.append(center) + lineMarkers.insert(0, center) + self.edits[QadDynamicInputEditEnum.EDIT_REL_ANG_PREV_PT].setLinesMarker(lineMarkers) + del lineMarkers + + if self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_NEXT_PT].isVisible(): + # usato per lunghezza raggio arco nel punto finale della parte successiva + p1 = self.nextPart.center + p2 = self.nextPart.getEndPt() + editPt, lineMarkers = self.getPosAndLineMarkerForLine(p1, p2, \ + 0, self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_NEXT_PT]) + self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_NEXT_PT].move(editPt.x(), editPt.y()) + del editPt + # disegno la linea di marker + self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_NEXT_PT].setLinesMarker(lineMarkers) + del lineMarkers + + if self.edits[QadDynamicInputEditEnum.EDIT_DIST_NEXT_PT].isVisible(): + # usato per lunghezza raggio arco nel punto iniziale della parte successiva + p1 = self.nextPart.center + p2 = self.nextPart.getStartPt() + offset = 0 + else: + p1 = None + + if p1 is not None: + editPt, lineMarkers = self.getPosAndLineMarkerForLine(p1, p2, \ + offset, self.edits[QadDynamicInputEditEnum.EDIT_DIST_NEXT_PT]) + self.edits[QadDynamicInputEditEnum.EDIT_DIST_NEXT_PT].move(editPt.x(), editPt.y()) + del editPt + # disegno la linea di marker + self.edits[QadDynamicInputEditEnum.EDIT_DIST_NEXT_PT].setLinesMarker(lineMarkers) + del lineMarkers + + + # ============================================================================ + # refreshResult + # ============================================================================ + def refreshResult(self, mousePos = None): + # calcola il risultato e restituisce True se l'operazione ha successo + # il risultato è anche impostato in self.resValue e, in formato stringa, in self.resStr + self.resStr = self.resValue = self.edits[QadDynamicInputEditEnum.CMD_LINE_EDIT].toPlainText() + return True + + + # ============================================================================ + # keyPressEvent + # ============================================================================ + def keyPressEvent(self, e): + if self.currentEdit is None: + return + + if e.key() == Qt.Key_Return or e.key == Qt.Key_Enter: + msg = self.resStr if self.refreshResult() == True else "" # ricalcolo il risultato e lo uso in formato stringa + self.showEvaluateMsg(msg) + else: + if e.text() != "": + self.edits[self.currentEdit].keyPressEvent(e) + self.show(True) + + + +# =============================================================================== +class QadDynamicEditInput(QadDynamicInput): +# =============================================================================== +# """ +# Classe che gestisce l'input dinamico +# """ + + + # ============================================================================ + # __init__ + # ============================================================================ + def __init__(self, plugIn, context = QadDynamicInputContextEnum.NONE): + QadDynamicInput.__init__(self, plugIn) + + self.context = context + self.prevPoint = None # punto usato come punto precedente durante la digitazione dei punti di un nuovo oggetto (in map coordinate) + + self.resPt = QgsPointXY() # punto risultante + + self.inputMode = QadInputModeEnum.NONE + self.inputType = QadInputTypeEnum.NONE + self.keyWords = [] + self.englishKeyWords = [] + + self.initGui() + # flag che determina se la forzatura della visibilità dei widget per l'inserimento delle coordinate di un punto (x,y,z) + self.forcedCoordWidgetVisib = False + + + # ============================================================================ + # setPrevPoint + # ============================================================================ + def setPrevPoint(self, pt): + if pt is None: + if self.prevPoint is not None: + del self.prevPoint + self.prevPoint = None + else: + if self.prevPoint is None: + self.prevPoint = QgsPointXY(pt) + else: + self.prevPoint.setX(pt.x()) + self.prevPoint.setY(pt.y()) + + + # ============================================================================ + # removeItems + # ============================================================================ + def removeItems(self): + QadDynamicInput.removeItems(self) + self.setPrevPoint(None) + + + # ============================================================================ + # reset + # ============================================================================ + def reset(self, default = None): + # la funzione non deve resettare self.prevPoint, self.prevPart, self.nextPart + self.currentEdit = None + self.forcedCoordWidgetVisib = False + + for i in range(0, len(self.edits), 1): + self.edits[i].reset() + + self.edits[QadDynamicInputEditEnum.EDIT].inputType = self.inputType + self.edits[QadDynamicInputEditEnum.EDIT].inputMode = self.inputMode + + # aggiorno tutti i colori dei controlli + self.setColors() + # se esiste un valore di default lo imposto + if default is not None: + self.setDefault(default) + + + # ============================================================================ + # getInitialNdxEdit + # ============================================================================ + # restituisce la posizione del controllo iniziale + def getInitialNdxEdit(self): + if self.inputType & QadInputTypeEnum.STRING or self.inputType & QadInputTypeEnum.BOOL or \ + self.inputType & QadInputTypeEnum.INT or self.inputType & QadInputTypeEnum.LONG or \ + self.inputType & QadInputTypeEnum.FLOAT or self.inputType & QadInputTypeEnum.ANGLE: + return QadDynamicInputEditEnum.EDIT + + elif self.inputType & QadInputTypeEnum.POINT2D or self.inputType & QadInputTypeEnum.POINT3D: + # se devo visualizzare i widget delle coordinate + if self.isCoordWidgetVisib(): + return QadDynamicInputEditEnum.EDIT_X + # se devo mostrare i widget delle quote + elif self.isDimensionalWidgetVisib(): + # se esiste un punto precedente + if self.prevPoint is not None: # si tratta di inserimento di un nuovo punto a fine linea + return QadDynamicInputEditEnum.EDIT_DIST_PREV_PT + else: # spostamento di un punto in modalità grip + if self.dynDiVis == 0 or self.dynDiVis == 1: # solo una quota alla volta (0) o due quota alla volta (1) + if self.prevPart is not None and self.prevPart.whatIs() == "LINE": + return QadDynamicInputEditEnum.EDIT_DIST_PREV_PT + elif self.nextPart is not None and self.nextPart.whatIs() == "LINE": + return QadDynamicInputEditEnum.EDIT_DIST_NEXT_PT + elif self.dynDiVis == 2: # come definito dalla variabile dynDiGrip + if self.dynDiGrip & 1: # quota risultante (distanza dal punto precedente) + if self.prevPart is not None and self.prevPart.whatIs() == "LINE": + return QadDynamicInputEditEnum.EDIT_DIST_PREV_PT + elif self.nextPart is not None and self.nextPart.whatIs() == "LINE": + return QadDynamicInputEditEnum.EDIT_DIST_NEXT_PT + elif self.dynDiGrip & 2: # quota modifica lunghezza (distanza dalla posizione precedente dello stesso punto) + if self.prevPart is not None and self.prevPart.whatIs() == "LINE": + return QadDynamicInputEditEnum.EDIT_REL_DIST_PREV_PT + elif self.nextPart is not None and self.nextPart.whatIs() == "LINE": + return QadDynamicInputEditEnum.EDIT_REL_DIST_NEXT_PT + elif self.dynDiGrip & 4: # quota angolo assoluto + if self.prevPart is not None and self.prevPart.whatIs() == "LINE": + return QadDynamicInputEditEnum.EDIT_ANG_PREV_PT + elif self.nextPart is not None and self.nextPart.whatIs() == "LINE": + return QadDynamicInputEditEnum.EDIT_ANG_NEXT_PT + elif self.dynDiGrip & 8: # quota modifica angolo (angolo relativo all'angolo con il punto precedente) + if self.prevPart is not None and self.prevPart.whatIs() == "LINE": + return QadDynamicInputEditEnum.EDIT_REL_ANG_PREV_PT + elif self.nextPart is not None and self.nextPart.whatIs() == "LINE": + return QadDynamicInputEditEnum.EDIT_REL_ANG_NEXT_PT + + return None + + + # ============================================================================ + # getNextNdxEditSequence + # ============================================================================ + # restituisce la posizione del controllo successivo usando la sequenza + def getNextNdxEditSequence(self, currentEdit): + editSequence = [QadDynamicInputEditEnum.CMD_LINE_EDIT, \ + QadDynamicInputEditEnum.EDIT, \ + QadDynamicInputEditEnum.EDIT_X, \ + QadDynamicInputEditEnum.EDIT_Y, \ + QadDynamicInputEditEnum.EDIT_Z, \ + QadDynamicInputEditEnum.EDIT_DIST_PREV_PT, \ + QadDynamicInputEditEnum.EDIT_ANG_PREV_PT, \ + QadDynamicInputEditEnum.EDIT_REL_DIST_PREV_PT, \ + QadDynamicInputEditEnum.EDIT_REL_ANG_PREV_PT, \ + QadDynamicInputEditEnum.EDIT_DIST_NEXT_PT, \ + QadDynamicInputEditEnum.EDIT_ANG_NEXT_PT, \ + QadDynamicInputEditEnum.EDIT_REL_DIST_NEXT_PT, \ + QadDynamicInputEditEnum.EDIT_REL_ANG_NEXT_PT, \ + QadDynamicInputEditEnum.EDIT_Z] + start = i = editSequence.index(currentEdit) + maxLimit = len(editSequence) - 1 + while True: + i = 0 if i >= maxLimit else i + 1 # ciclico + if i == start: break + + # se i widgets di quota sono visibili come definito dalla variabile dynDiGrip o + # si stanno visualizzando i widgets delle coordinate + # allora i widgets sono già tutti visibili + if (self.isDimensionalWidgetVisib() == False and self.dynDiVis == 2) or \ + self.isCoordWidgetVisib(): + if self.edits[editSequence[i]].isVisible(): # controllo visibile successivo + return editSequence[i] + else: + if self.isDimensionalWidgetVisib() and self.dynDiVis != 2: + # si tratta di inserimento di un nuovo punto a fine linea + if self.prevPoint is not None: + if editSequence[i] == QadDynamicInputEditEnum.EDIT_DIST_PREV_PT or \ + editSequence[i] == QadDynamicInputEditEnum.EDIT_ANG_PREV_PT: + return editSequence[i] + else: # se si era in modalità grip + if (self.prevPart is not None and self.prevPart.whatIs() == "LINE" and \ + (editSequence[i] == QadDynamicInputEditEnum.EDIT_DIST_PREV_PT or \ + editSequence[i] == QadDynamicInputEditEnum.EDIT_REL_DIST_PREV_PT or \ + editSequence[i] == QadDynamicInputEditEnum.EDIT_ANG_PREV_PT or \ + editSequence[i] == QadDynamicInputEditEnum.EDIT_REL_ANG_PREV_PT)) or \ + (self.nextPart is not None and self.nextPart.whatIs() == "LINE" and \ + (editSequence[i] == QadDynamicInputEditEnum.EDIT_DIST_NEXT_PT or \ + editSequence[i]== QadDynamicInputEditEnum.EDIT_REL_DIST_NEXT_PT or \ + editSequence[i] == QadDynamicInputEditEnum.EDIT_ANG_NEXT_PT or \ + editSequence[i] == QadDynamicInputEditEnum.EDIT_REL_ANG_NEXT_PT)): + return editSequence[i] + + return editSequence[currentEdit] + + + # ============================================================================ + # setNextCurrentEdit + # ============================================================================ + def setNextCurrentEdit(self): + if self.currentEdit is None: # se non è settato quale edit è il corrente + self.setInitialFocus() + return + + nextEdit = self.getNextNdxEditSequence(self.currentEdit) + + # se i widgets di quota sono visibili come definito dalla variabile dynDiGrip o + # si stanno visualizzando i widgets delle coordinate + # allora i widgets sono già tutti visibili + if (self.isDimensionalWidgetVisib() == False and self.dynDiVis == 2) or \ + self.isCoordWidgetVisib(): + self.currentEdit = nextEdit + else: + if self.isDimensionalWidgetVisib() and self.dynDiVis != 2: + if self.dynDiVis == 0: # solo una quota alla volta + # spengo il controllo corrente + self.edits[self.currentEdit].show(False) + self.currentEdit = nextEdit + # visualizzo il controllo successivo + self.edits[self.currentEdit].show(True) + self.mouseMoveEvent(self.canvas.mouseLastXY()) # posiziono gli attributi che, essendo prima spenti, hanno una posizione non aggiornata + elif self.dynDiVis == 1: # solo due quota alla volta + # spengo il controllo corrente + self.edits[self.currentEdit].show(False) + self.currentEdit = nextEdit + nextEdit = self.getNextNdxEditSequence(nextEdit) + # visualizzo il controllo successivo del successivo + self.edits[nextEdit].show(True) + self.mouseMoveEvent(self.canvas.mouseLastXY()) # posiziono gli attributi che, essendo prima spenti, hanno una posizione non aggiornata + + self.setFocus() + + + # ============================================================================ + # getPrevNdxEditSequence + # ============================================================================ + # restituisce la posizione del controllo precedente usando la sequenza + def getPrevNdxEditSequence(self, currentEdit): + editSequence = [QadDynamicInputEditEnum.CMD_LINE_EDIT, \ + QadDynamicInputEditEnum.EDIT, \ + QadDynamicInputEditEnum.EDIT_X, \ + QadDynamicInputEditEnum.EDIT_Y, \ + QadDynamicInputEditEnum.EDIT_Z, \ + QadDynamicInputEditEnum.EDIT_DIST_PREV_PT, \ + QadDynamicInputEditEnum.EDIT_ANG_PREV_PT, \ + QadDynamicInputEditEnum.EDIT_REL_DIST_PREV_PT, \ + QadDynamicInputEditEnum.EDIT_REL_ANG_PREV_PT, \ + QadDynamicInputEditEnum.EDIT_DIST_NEXT_PT, \ + QadDynamicInputEditEnum.EDIT_ANG_NEXT_PT, \ + QadDynamicInputEditEnum.EDIT_REL_DIST_NEXT_PT, \ + QadDynamicInputEditEnum.EDIT_REL_ANG_NEXT_PT, \ + QadDynamicInputEditEnum.EDIT_Z] + start = i = editSequence.index(self.currentEdit) + maxLimit = len(editSequence) - 1 + while True: + i = maxLimit if i <= 0 else i - 1 # ciclico + if i == start: break + + # se i widgets di quota sono visibili come definito dalla variabile dynDiGrip o + # si stanno visualizzando i widgets delle coordinate + # allora i widgets sono già tutti visibili + if (self.isDimensionalWidgetVisib() == False and self.dynDiVis == 2) or \ + self.isCoordWidgetVisib(): + if self.edits[editSequence[i]].isVisible(): # controllo visibile successivo + return editSequence[i] + else: + if self.isDimensionalWidgetVisib() and self.dynDiVis != 2: + if self.prevPoint is not None: # si tratta di inserimento di un nuovo punto a fine linea + if editSequence[i] == QadDynamicInputEditEnum.EDIT_DIST_PREV_PT or \ + editSequence[i] == QadDynamicInputEditEnum.EDIT_ANG_PREV_PT: + return editSequence[i] + else: # se si era in modalità grip + if (self.prevPart is not None and self.prevPart.whatIs() == "LINE" and \ + (editSequence[i] == QadDynamicInputEditEnum.EDIT_DIST_PREV_PT or \ + editSequence[i] == QadDynamicInputEditEnum.EDIT_REL_DIST_PREV_PT or \ + editSequence[i] == QadDynamicInputEditEnum.EDIT_ANG_PREV_PT or \ + editSequence[i] == QadDynamicInputEditEnum.EDIT_REL_ANG_PREV_PT)) or \ + (self.nextPart is not None and self.nextPart.whatIs() == "LINE" and \ + (editSequence[i] == QadDynamicInputEditEnum.EDIT_DIST_NEXT_PT or \ + editSequence[i]== QadDynamicInputEditEnum.EDIT_REL_DIST_NEXT_PT or \ + editSequence[i] == QadDynamicInputEditEnum.EDIT_ANG_NEXT_PT or \ + editSequence[i] == QadDynamicInputEditEnum.EDIT_REL_ANG_NEXT_PT)): + return editSequence[i] + + return editSequence[currentEdit] + + + # ============================================================================ + # setPrevCurrentEdit + # ============================================================================ + def setPrevCurrentEdit(self): + if self.currentEdit is None: # se non è settato quale edit è il corrente + self.setInitialFocus() + return + + prevEdit = self.getPrevNdxEditSequence(self.currentEdit) + + # se i widgets di quota sono visibili come definito dalla variabile dynDiGrip o + # si stanno visualizzando i widgets delle coordinate + # allora i widgets sono già tutti visibili + if (self.isDimensionalWidgetVisib() == False and self.dynDiVis == 2) or \ + self.isCoordWidgetVisib(): + self.currentEdit = prevEdit + else: + if self.isDimensionalWidgetVisib() and self.dynDiVis != 2: + if self.dynDiVis == 0: # solo una quota alla volta + # spengo il controllo corrente + self.edits[self.currentEdit].show(False) + self.currentEdit = prevEdit + # visualizzo il controllo successivo + self.edits[self.currentEdit].show(True) + self.mouseMoveEvent(self.canvas.mouseLastXY()) # posiziono gli attributi che, essendo prima spenti, hanno una posizione non aggiornata + elif self.dynDiVis == 1: # solo due quota alla volta + # spengo il controllo corrente + self.edits[self.currentEdit].show(False) + self.currentEdit = prevEdit + prevEdit = self.getNextNdxEditSequence(prevEdit) + # visualizzo il controllo precedente del precedente + self.edits[prevEdit].show(True) + self.mouseMoveEvent(self.canvas.mouseLastXY()) # posiziono gli attributi che, essendo prima spenti, hanno una posizione non aggiornata + + self.setFocus() + + + # ============================================================================ + # isCoordWidgetVisib + # ============================================================================ + def isCoordWidgetVisib(self): + # ritorna se devono essere mostrati i widget relativi alle coordinate + + # se non è ammessa la restituzione di un punto + if not (self.inputType & QadInputTypeEnum.POINT2D) and not(self.inputType & QadInputTypeEnum.POINT3D): + return False + # se la visualizzazione delle coordinate è forzata + if self.forcedCoordWidgetVisib: return True + # se (non esiste nè un punto precedente, una parte precedente, una successiva oppure non è abilitato l'input di quota) ed è abilitato l'input del puntatore oppure se + # la visualizzazione delle coordinate è forzata + if (((self.prevPoint is None and self.prevPart is None and self.nextPart is None) or self.isDimensionalInputOn() == False) and \ + self.isPointInputOn()): + return True + else: + return False + + + # ============================================================================ + # isDimensionalWidgetVisib + # ============================================================================ + def isDimensionalWidgetVisib(self): + # se non è ammessa la restituzione di un punto + if not (self.inputType & QadInputTypeEnum.POINT2D) and not(self.inputType & QadInputTypeEnum.POINT3D): + return False + # se non è abilitato l'input di quota + if self.isDimensionalInputOn() == False: return False + + # la visualizzazione delle coordinate non deve essere forzata + if self.forcedCoordWidgetVisib: return False + + # se il widget di input generico deve essere invisibile + if self.inputType & QadInputTypeEnum.STRING or self.inputType & QadInputTypeEnum.BOOL or \ + self.inputType & QadInputTypeEnum.INT or self.inputType & QadInputTypeEnum.LONG or \ + self.inputType & QadInputTypeEnum.FLOAT or self.inputType & QadInputTypeEnum.ANGLE: + return False + + # se esiste un punto precedente oppure una parte precedente o una parte successiva + if self.prevPoint is not None or self.prevPart is not None or self.nextPart is not None: + return True + else: + return False + + + # ============================================================================ + # setDefault + # ============================================================================ + def setDefault(self, default): + self.default = default + + # se il risultato non dipende dalla posizione del mouse + if self.context == QadDynamicInputContextEnum.COMMAND: + self.edits[QadDynamicInputEditEnum.CMD_LINE_EDIT].showMsg(self.default) + + elif self.inputType & QadInputTypeEnum.STRING or self.inputType & QadInputTypeEnum.BOOL or \ + self.inputType & QadInputTypeEnum.INT or self.inputType & QadInputTypeEnum.LONG or \ + self.inputType & QadInputTypeEnum.FLOAT or self.inputType & QadInputTypeEnum.ANGLE: + # se si tratta di un numero + if type(self.default) == int or type(self.default) == long or type(self.default) == float: + if self.inputType & QadInputTypeEnum.ANGLE: + self.edits[QadDynamicInputEditEnum.EDIT].showMsg(qad_utils.numToStringFmt(qad_utils.toDegrees(self.default))) + else: + self.edits[QadDynamicInputEditEnum.EDIT].showMsg(qad_utils.numToStringFmt(self.default)) + # se si tratta di un punto + elif type(self.default) == QgsPointXY: + self.edits[QadDynamicInputEditEnum.EDIT].showMsg(qad_utils.pointToStringFmt(self.default)) + else: + self.edits[QadDynamicInputEditEnum.EDIT].showMsg(unicode(self.default)) + + + # ============================================================================ + # show + # ============================================================================ + def show(self, mode, mousePos = None, prompt = None, default = None): + # se si tratta di rendere invisibile lo faccio indipendentemente dal fatto che sia attivo o meno + # (serve per gestire F12) + if mode == False: + self.isVisible = False + for edit in self.edits: + edit.show(False) + return False + + if self.isActive() == False: return False + + # se viene passata la posizione del mouse la funzione + # resetta lo stato dell'input dinamico (errori, fuoco) + if mousePos is not None: + self.reset(default) + + if prompt is not None: + self.edits[QadDynamicInputEditEnum.PROMPT_EDIT].showMsg(prompt, False, False, False) # senza aggiornare la posizione dei controlli + + self.isVisible = True + + visibList = [False] * len(self.edits) + + if self.isPromptActive() and len(self.edits[QadDynamicInputEditEnum.PROMPT_EDIT].toPlainText()) > 0: + visibList[QadDynamicInputEditEnum.PROMPT_EDIT] = True + + # se richiede un numero reale o un angolo e + # la visualizzazione delle coordinate non è forzata + if (self.inputType & QadInputTypeEnum.FLOAT or self.inputType & QadInputTypeEnum.ANGLE) and \ + not self.forcedCoordWidgetVisib: + visibList[QadDynamicInputEditEnum.EDIT] = True + + # se devo visualizzare i widget delle coordinate + elif self.isCoordWidgetVisib(): + if self.edits[QadDynamicInputEditEnum.EDIT_SYMBOL_COORD_TYPE].toPlainText() != "": + visibList[QadDynamicInputEditEnum.EDIT_SYMBOL_COORD_TYPE] = True + + visibList[QadDynamicInputEditEnum.EDIT_X] = True + visibList[QadDynamicInputEditEnum.EDIT_Y] = True + if self.inputType & QadInputTypeEnum.POINT3D: + visibList[QadDynamicInputEditEnum.EDIT_Z] = True + # se devo mostrare i widget delle quote + elif self.isDimensionalWidgetVisib(): + if self.prevPoint is not None: # si tratta di inserimento di un nuovo punto a fine linea + if self.dynDiVis == 0: # solo una quota alla volta + if self.currentEdit is None: + visibList[self.getInitialNdxEdit()] = True + else: + visibList[self.currentEdit] = True + else: + visibList[QadDynamicInputEditEnum.EDIT_DIST_PREV_PT] = True + visibList[QadDynamicInputEditEnum.EDIT_ANG_PREV_PT] = True + else: # spostamento di un punto in modalità grip + if self.dynDiVis == 0: # solo una quota alla volta + if self.currentEdit is None: + first = self.getInitialNdxEdit() + else: + first = self.currentEdit + + if first is not None: + visibList[first] = True + + elif self.dynDiVis == 1: # solo due quota alla volta + if self.currentEdit is None: + first = self.getInitialNdxEdit() + else: + first = self.currentEdit + + if first is not None: + visibList[first] = True + second = self.getNextNdxEditSequence(first) + if second is not None: + visibList[second] = True + + elif self.dynDiVis == 2: # come definito dalla variabile dynDiGrip + if self.dynDiGrip & 1: # quota risultante (distanza dal punto precedente) + if self.prevPart is not None and self.prevPart.whatIs() == "LINE": + visibList[QadDynamicInputEditEnum.EDIT_DIST_PREV_PT] = True + if self.nextPart is not None and self.nextPart.whatIs() == "LINE": + visibList[QadDynamicInputEditEnum.EDIT_DIST_NEXT_PT] = True + if self.dynDiGrip & 2: # quota modifica lunghezza (distanza dalla posizione precedente dello stesso punto) + if self.prevPart is not None and self.prevPart.whatIs() == "LINE": + visibList[QadDynamicInputEditEnum.EDIT_REL_DIST_PREV_PT] = True + if self.nextPart is not None and self.nextPart.whatIs() == "LINE": + visibList[QadDynamicInputEditEnum.EDIT_REL_DIST_NEXT_PT] = True + if self.dynDiGrip & 4: # quota angolo assoluto + if self.prevPart is not None and self.prevPart.whatIs() == "LINE": + visibList[QadDynamicInputEditEnum.EDIT_ANG_PREV_PT] = True + if self.nextPart is not None and self.nextPart.whatIs() == "LINE": + visibList[QadDynamicInputEditEnum.EDIT_ANG_NEXT_PT] = True + if self.dynDiGrip & 8: # quota modifica angolo (angolo relativo all'angolo con il punto precedente) + if self.prevPart is not None and self.prevPart.whatIs() == "LINE": + visibList[QadDynamicInputEditEnum.EDIT_REL_ANG_PREV_PT] = True + if self.nextPart is not None and self.nextPart.whatIs() == "LINE": + visibList[QadDynamicInputEditEnum.EDIT_REL_ANG_NEXT_PT] = True + +# # eccezione per questo flag che viene considerato indipendentemente dal valore di self.dynDiVis +# if self.dynDiGrip & 16: # lunghezza raggio +# if self.prevPart is not None and self.prevPart.whatIs() == "ARC": +# # usato per lunghezza raggio nel punto finale della parte precedente +# # oppure lunghezza raggio nel punto medio se parte precedente e successiva sono lo stesso arco +# visibList[QadDynamicInputEditEnum.EDIT_DIST_PREV_PT] = True +# # usato per lunghezza raggio nel punto iniziale della parte precedente +# visibList[QadDynamicInputEditEnum.EDIT_REL_DIST_PREV_PT] = True +# if self.nextPart is not None and self.nextPart.whatIs() == "ARC": +# # se nextPart e prevPart sono uguali +# if self.prevPart is not None and self.nextPart == self.prevPart: +# # usato per lunghezza raggio nel punto iniziale della parte precedente +# visibList[QadDynamicInputEditEnum.EDIT_REL_DIST_PREV_PT] = False +# else: +# # usato per lunghezza raggio nel punto iniziale della parte successiva +# visibList[QadDynamicInputEditEnum.EDIT_DIST_NEXT_PT] = True +# # usato per lunghezza raggio nel punto finale della parte successiva +# visibList[QadDynamicInputEditEnum.EDIT_REL_DIST_NEXT_PT] = True + + + elif self.inputType & QadInputTypeEnum.STRING or self.inputType & QadInputTypeEnum.BOOL or \ + self.inputType & QadInputTypeEnum.INT or self.inputType & QadInputTypeEnum.LONG or \ + self.inputType & QadInputTypeEnum.FLOAT or self.inputType & QadInputTypeEnum.ANGLE: + visibList[QadDynamicInputEditEnum.EDIT] = True + + for i in range(0, len(self.edits), 1): + self.edits[i].show(visibList[i]) + + self.setFocus() + + # riposiziono i widget + if mousePos is None: + self.mouseMoveEvent(self.canvas.mouseLastXY()) + else: + self.mouseMoveEvent(mousePos) + + return self.isVisible + + + # ============================================================================ + # showErr + # ============================================================================ + def showErr(self, err = ""): + if self.isActive() == False: return + + if self.currentEdit is not None: + self.edits[self.currentEdit].error = True + self.edits[self.currentEdit].setColors() # ricolora con i bordi rossi perchè error=True + + self.moveCtrls() # per riposizionare i controlli + + + # ============================================================================ + # showInputMsg + # ============================================================================ + def showInputMsg(self, inputMsg = None, inputType = QadInputTypeEnum.COMMAND, \ + default = None, keyWords = "", inputMode = QadInputModeEnum.NONE): + if self.isActive() == False: return False + + # context va inizializzato prima dal comando + self.inputType = inputType + self.inputMode = inputMode + self.keyWords = [] + self.englishKeyWords = [] + + if (keyWords is not None) and len(keyWords) > 0: + # carattere separatore tra le parole chiave in lingua locale e quelle in inglese + localEnglishKeyWords = keyWords.split("_") + self.keyWords = localEnglishKeyWords[0].split("/") # carattere separatore delle parole chiave + if len(localEnglishKeyWords) > 1: + self.englishKeyWords = localEnglishKeyWords[1].split("/") # carattere separatore delle parole chiave + else: + del self.englishKeyWords[:] + + initial = inputMsg.find("[") + self.show(True, self.canvas.mouseLastXY(), inputMsg[0:initial], default) # resetta tutto + else: + self.show(True, self.canvas.mouseLastXY(), inputMsg, default) # resetta tutto + + + # ============================================================================ + # mouseMoveEvent + # ============================================================================ + def mouseMoveEvent(self, mousePos): + if self.isActive() == False or self.isVisible == False: return + + self.refreshResult(mousePos) + + # se i widget delle coordinate sono visibili + if self.edits[QadDynamicInputEditEnum.EDIT_X].isVisible(): + if self.prevPoint is None: # se non è settato nell'input dinamico lo cerco nel plugin (cosa che fa anche refreshResult) + if self.plugIn.lastPoint is None: + prevPt = QgsPointXY(0,0) + else: + prevPt = self.plugIn.lastPoint + else: + prevPt = self.prevPoint + + # se si tratta di coordinate relative + relative = True if self.dynPiCoords == 0 else False + # se si tratta di coordinate polari + polar = True if self.dynPiFormat == 0 else False + if self.edits[QadDynamicInputEditEnum.EDIT_SYMBOL_COORD_TYPE].isVisible(): + coordType = self.edits[QadDynamicInputEditEnum.EDIT_SYMBOL_COORD_TYPE].toPlainText() + if "@" in coordType: relative = True + elif "#" in coordType: relative = False + polar = True if "<" in coordType else False + else: # se non è esplicito che sia relativo ma lo è per via di dynPiCoords + prevPt = self.prevPoint # se non c'è il punto precedente impostato nell'input dinamico + if prevPt is None: + relative = False + polar = False + + if polar == False: # se sono coordinate cartesiane + if self.edits[QadDynamicInputEditEnum.EDIT_X].isLockedValue() == False: + # se si tratta di coordinate relative + if relative: + self.edits[QadDynamicInputEditEnum.EDIT_X].showMsg(qad_utils.numToStringFmt(self.resPt.x() - prevPt.x()), False, False, False) # senza aggiornare la posizione dei controlli + else: + self.edits[QadDynamicInputEditEnum.EDIT_X].showMsg(qad_utils.numToStringFmt(self.resPt.x()), False, False, False) # senza aggiornare la posizione dei controlli + + if self.edits[QadDynamicInputEditEnum.EDIT_Y].isLockedValue() == False: + # se si tratta di coordinate relative + if relative: + self.edits[QadDynamicInputEditEnum.EDIT_Y].showMsg(qad_utils.numToStringFmt(self.resPt.y() - prevPt.y()), False, False, False) # senza aggiornare la posizione dei controlli + else: + self.edits[QadDynamicInputEditEnum.EDIT_Y].showMsg(qad_utils.numToStringFmt(self.resPt.y()), False, False, False) # senza aggiornare la posizione dei controlli + + if self.edits[QadDynamicInputEditEnum.EDIT_Z].isVisible() and \ + self.edits[QadDynamicInputEditEnum.EDIT_Z].isLockedValue() == False: + # se si tratta di coordinate relative + if relative: + self.edits[QadDynamicInputEditEnum.EDIT_Z].showMsg(qad_utils.numToStringFmt(self.resPt.z() - prevPt.z()), False, False, False) # senza aggiornare la posizione dei controlli + else: + self.edits[QadDynamicInputEditEnum.EDIT_Z].showMsg(qad_utils.numToStringFmt(self.resPt.z()), False, False, False) # senza aggiornare la posizione dei controlli + elif prevPt is not None: # coordinate polari + # nel caso di coordinate polari EDIT_X contiene la distanza dal punto precedente o da 0,0 + if self.edits[QadDynamicInputEditEnum.EDIT_X].isLockedValue() == False: + if relative: + dist = qad_utils.getDistance(prevPt, self.resPt) + else: + dist = qad_utils.getDistance(QgsPointXY(0, 0), self.resPt) + self.edits[QadDynamicInputEditEnum.EDIT_X].showMsg(qad_utils.numToStringFmt(dist), False, False, False) # senza aggiornare la posizione dei controlli + + # nel caso di coordinate polari EDIT_Y contiene l'angolo + if self.edits[QadDynamicInputEditEnum.EDIT_Y].isLockedValue() == False: + if relative: + angle = qad_utils.getAngleBy2Pts(prevPt, self.resPt, 0) # senza tolleranza + else: + angle = qad_utils.getAngleBy2Pts(QgsPointXY(0, 0), self.resPt, 0) # senza tolleranza + + self.edits[QadDynamicInputEditEnum.EDIT_Y].showMsg(qad_utils.numToStringFmt(qad_utils.toDegrees(angle)), False, False, False) # senza aggiornare la posizione dei controlli + + # se il widget delle quote "distanza dal punto precedente" è visibile + if self.edits[QadDynamicInputEditEnum.EDIT_DIST_PREV_PT].isVisible() and \ + self.edits[QadDynamicInputEditEnum.EDIT_DIST_PREV_PT].isLockedValue() == False: + if self.prevPoint is not None: # si tratta di inserimento di un nuovo punto a fine linea + dist = qad_utils.getDistance(self.prevPoint, self.resPt) + self.edits[QadDynamicInputEditEnum.EDIT_DIST_PREV_PT].showMsg(qad_utils.numToStringFmt(dist), False, False, False) # senza aggiornare la posizione dei controlli + elif self.prevPart is not None: + gType = self.prevPart.whatIs() + + if gType == "LINE": # spostamento di un punto di un segmento in modalità grip + dist = qad_utils.getDistance(self.prevPart.getStartPt(), self.resPt) + self.edits[QadDynamicInputEditEnum.EDIT_DIST_PREV_PT].showMsg(qad_utils.numToStringFmt(dist), False, False, False) # senza aggiornare la posizione dei controlli + elif gType == "ARC": # spostamento di un punto di un arco in modalità grip + # usato per lunghezza raggio nel punto finale della parte precedente + # oppure lunghezza raggio nel punto medio se parte precedente e successiva sono lo stesso arco + self.edits[QadDynamicInputEditEnum.EDIT_DIST_PREV_PT].showMsg(qad_utils.numToStringFmt(self.prevPart.radius), False, False, False) # senza aggiornare la posizione dei controlli + + # se il widget delle quote "angolo dal punto precedente" è visibile + if self.edits[QadDynamicInputEditEnum.EDIT_ANG_PREV_PT].isVisible() and \ + self.edits[QadDynamicInputEditEnum.EDIT_ANG_PREV_PT].isLockedValue() == False: + if self.prevPoint is not None: # si tratta di inserimento di un nuovo punto a fine linea + angle = qad_utils.getAngleBy2Pts(self.prevPoint, self.resPt, 0) # senza tolleranza + if angle >= math.pi and angle < 2 * math.pi: + angle = 2 * math.pi - angle + self.edits[QadDynamicInputEditEnum.EDIT_ANG_PREV_PT].showMsg(qad_utils.numToStringFmt(qad_utils.toDegrees(angle)), False, False, False) # senza aggiornare la posizione dei controlli + elif self.prevPart is not None and self.prevPart.whatIs() == "LINE": # spostamento di un punto di un segmento in modalità grip + angle = qad_utils.getAngleBy2Pts(self.prevPart.getStartPt(), self.resPt, 0) # senza tolleranza + if angle >= math.pi and angle < 2 * math.pi: + angle = 2 * math.pi - angle + self.edits[QadDynamicInputEditEnum.EDIT_ANG_PREV_PT].showMsg(qad_utils.numToStringFmt(qad_utils.toDegrees(angle)), False, False, False) # senza aggiornare la posizione dei controlli + + # se il widget delle quote nel modo grip "distanza rispetto la posizione precedente dello stesso punto nel verso dal punto precedente" è visibile + if self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_PREV_PT].isVisible() and \ + self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_PREV_PT].isLockedValue() == False: + if self.prevPart is not None: + gType = self.prevPart.whatIs() + + if gType == "LINE": # spostamento di un punto di un segmento in modalità grip + dist = qad_utils.getDistance(self.prevPart.getEndPt(), self.resPt) + self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_PREV_PT].showMsg(qad_utils.numToStringFmt(dist), False, False, False) # senza aggiornare la posizione dei controlli + elif gType == "ARC": # spostamento di un punto di un arco in modalità grip + # usato per lunghezza raggio nel punto iniziale della parte precedente se arco + self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_PREV_PT].showMsg(qad_utils.numToStringFmt(self.prevPart.radius), False, False, False) # senza aggiornare la posizione dei controlli + + # se il widget delle quote nel modo grip "angolo relativo all'angolo dal punto precedente" è visibile + if self.edits[QadDynamicInputEditEnum.EDIT_REL_ANG_PREV_PT].isVisible() and \ + self.edits[QadDynamicInputEditEnum.EDIT_REL_ANG_PREV_PT].isLockedValue() == False: + if self.prevPart is not None and self.prevPart.whatIs() == "LINE": # spostamento di un punto di un segmento in modalità grip + pt1 = self.prevPart.getStartPt() + pt2 = self.prevPart.getEndPt() + anglePart = qad_utils.getAngleBy2Pts(pt1, pt2, 0) # senza tolleranza + angleMouse = qad_utils.getAngleBy2Pts(pt1, self.resPt, 0) # senza tolleranza + angle = qad_utils.normalizeAngle(angleMouse - anglePart) + # se il mouse forma un angolo tra 180 e 360 allora l'angolo digitato va sottratto a 360 gradi + if angle >= math.pi and angle < (2 * math.pi): + angle = (2 * math.pi) - angle + self.edits[QadDynamicInputEditEnum.EDIT_REL_ANG_PREV_PT].showMsg(qad_utils.numToStringFmt(qad_utils.toDegrees(angle)), False, False, False) # senza aggiornare la posizione dei controlli + + # se il widget delle quote nel modo grip "distanza dal punto successivo" è visibile + if self.edits[QadDynamicInputEditEnum.EDIT_DIST_NEXT_PT].isVisible() and \ + self.edits[QadDynamicInputEditEnum.EDIT_DIST_NEXT_PT].isLockedValue() == False: + if self.nextPart is not None: + gType = self.nextPart.whatIs() + if gType == "LINE": # spostamento di un punto di un segmento in modalità grip + dist = qad_utils.getDistance(self.nextPart.getEndPt(), self.resPt) + self.edits[QadDynamicInputEditEnum.EDIT_DIST_NEXT_PT].showMsg(qad_utils.numToStringFmt(dist), False, False, False) # senza aggiornare la posizione dei controlli + elif gType == "ARC": # spostamento di un punto di un arco in modalità grip + # usato per lunghezza raggio nel punto iniziale della parte successiva se arco + self.edits[QadDynamicInputEditEnum.EDIT_DIST_NEXT_PT].showMsg(qad_utils.numToStringFmt(self.nextPart.radius), False, False, False) # senza aggiornare la posizione dei controlli + + # se il widget delle quote nel modo grip "distanza rispetto la posizione precedente dello stesso punto nel verso dal punto successivo" è visibile + if self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_NEXT_PT].isVisible() and \ + self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_NEXT_PT].isLockedValue() == False: + if self.nextPart is not None: + gType = self.nextPart.whatIs() + + if gType == "LINE": # spostamento di un punto di un segmento in modalità grip + dist = qad_utils.getDistance(self.nextPart.getStartPt(), self.resPt) + self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_NEXT_PT].showMsg(qad_utils.numToStringFmt(dist), False, False, False) # senza aggiornare la posizione dei controlli + elif gType == "ARC": # spostamento di un punto di un arco in modalità grip + # usato per lunghezza raggio nel punto finale della parte successiva se arco + self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_NEXT_PT].showMsg(qad_utils.numToStringFmt(self.nextPart.radius), False, False, False) # senza aggiornare la posizione dei controlli + + # se il widget delle quote "angolo dal punto successivo" è visibile + if self.edits[QadDynamicInputEditEnum.EDIT_ANG_NEXT_PT].isVisible() and \ + self.edits[QadDynamicInputEditEnum.EDIT_ANG_NEXT_PT].isLockedValue() == False: + if self.nextPart is not None and self.nextPart.whatIs() == "LINE": # spostamento di un punto di un segmento in modalità grip + angle = qad_utils.getAngleBy2Pts(self.nextPart.getEndPt(), self.resPt, 0) # senza tolleranza + if angle >= math.pi and angle < 2 * math.pi: + angle = 2 * math.pi - angle + self.edits[QadDynamicInputEditEnum.EDIT_ANG_NEXT_PT].showMsg(qad_utils.numToStringFmt(qad_utils.toDegrees(angle)), False, False, False) # senza aggiornare la posizione dei controlli + + # se il widget delle quote nel modo grip "distanza dal punto precedente" è visibile + if self.edits[QadDynamicInputEditEnum.EDIT_REL_ANG_NEXT_PT].isVisible() and \ + self.edits[QadDynamicInputEditEnum.EDIT_REL_ANG_NEXT_PT].isLockedValue() == False: + if self.nextPart is not None and self.nextPart.whatIs() == "LINE": # spostamento di un punto di un segmento in modalità grip + pt1 = self.nextPart.getEndPt() + pt2 = self.nextPart.getStartPt() + anglePart = qad_utils.getAngleBy2Pts(pt1, pt2, 0) # senza tolleranza + angleMouse = qad_utils.getAngleBy2Pts(pt1, self.resPt, 0) # senza tolleranza + angle = qad_utils.normalizeAngle(angleMouse - anglePart) + # se il mouse forma un angolo tra 180 e 360 allora l'angolo digitato va sottratto a 360 gradi + if angle >= math.pi and angle < (2 * math.pi): + angle = (2 * math.pi) - angle + self.edits[QadDynamicInputEditEnum.EDIT_REL_ANG_NEXT_PT].showMsg(qad_utils.numToStringFmt(qad_utils.toDegrees(angle)), False, False, False) # senza aggiornare la posizione dei controlli + + + if self.edits[QadDynamicInputEditEnum.EDIT].isVisible() and \ + self.edits[QadDynamicInputEditEnum.EDIT].isLockedValue() == False and \ + self.resValue is not None: + if self.inputType & QadInputTypeEnum.ANGLE: + self.edits[QadDynamicInputEditEnum.EDIT].showMsg(qad_utils.numToStringFmt(qad_utils.toDegrees(self.resValue)), False, False, False) # senza aggiornare la posizione dei controlli + elif self.inputType & QadInputTypeEnum.FLOAT: + self.edits[QadDynamicInputEditEnum.EDIT].showMsg(qad_utils.numToStringFmt(self.resValue), False, False, False) # senza aggiornare la posizione dei controlli + elif self.inputType & QadInputTypeEnum.STRING or self.inputType & QadInputTypeEnum.BOOL or \ + self.inputType & QadInputTypeEnum.INT or self.inputType & QadInputTypeEnum.LONG: + self.edits[QadDynamicInputEditEnum.EDIT].showMsg(unicode(self.resValue), False, False, False) # senza aggiornare la posizione dei controlli + + if self.currentEdit is not None: + self.edits[self.currentEdit].focusInEvent(None) # riporto il fuoco sul controllo corrente + + self.moveCtrls(mousePos) + + return + + + # ============================================================================ + # moveCtrls + # ============================================================================ + def moveCtrls(self, mousePos = None): + # sposta tutti i widget visibili a seconda del contesto + if mousePos is not None: + self.mousePos.setX(mousePos.x()) + self.mousePos.setY(mousePos.y()) + + height = self.edits[QadDynamicInputEditEnum.PROMPT_EDIT].height() + offset = 5 + + width = 0 + if self.edits[QadDynamicInputEditEnum.PROMPT_EDIT].isVisible(): + width += self.edits[QadDynamicInputEditEnum.PROMPT_EDIT].width() + + x = self.mousePos.x() + height + y = self.mousePos.y() + height + + # se si sta richiedendo un punto tramite una qualunque sua coordinata + if self.edits[QadDynamicInputEditEnum.EDIT_X].isVisible() or \ + self.edits[QadDynamicInputEditEnum.EDIT_Y].isVisible() or \ + self.edits[QadDynamicInputEditEnum.EDIT_Z].isVisible(): + + if self.edits[QadDynamicInputEditEnum.EDIT_SYMBOL_COORD_TYPE].isVisible(): + if width > 0 : width += offset + offsetX_editSymbolCoord = width + width += self.edits[QadDynamicInputEditEnum.EDIT_SYMBOL_COORD_TYPE].width() + if self.edits[QadDynamicInputEditEnum.EDIT_X].isVisible(): + if width > 0 : width += offset + offsetX_editX = width + width += self.edits[QadDynamicInputEditEnum.EDIT_X].width() + if self.edits[QadDynamicInputEditEnum.EDIT_Y].isVisible(): + if width > 0 : width += offset + offsetX_editY = width + width += self.edits[QadDynamicInputEditEnum.EDIT_Y].width() + if self.edits[QadDynamicInputEditEnum.EDIT_Z].isVisible(): + if width > 0 : width += offset + offsetX_editZ = width + width += self.edits[QadDynamicInputEditEnum.EDIT_Z].width() + + x, y = self.adjustEditPosition(x, y, width, height) + + if self.edits[QadDynamicInputEditEnum.EDIT_SYMBOL_COORD_TYPE].isVisible(): self.edits[QadDynamicInputEditEnum.EDIT_SYMBOL_COORD_TYPE].move(x + offsetX_editSymbolCoord, y) + if self.edits[QadDynamicInputEditEnum.EDIT_X].isVisible(): self.edits[QadDynamicInputEditEnum.EDIT_X].move(x + offsetX_editX, y) + if self.edits[QadDynamicInputEditEnum.EDIT_Y].isVisible(): self.edits[QadDynamicInputEditEnum.EDIT_Y].move(x + offsetX_editY, y) + if self.edits[QadDynamicInputEditEnum.EDIT_Z].isVisible(): self.edits[QadDynamicInputEditEnum.EDIT_Z].move(x + offsetX_editZ, y) + + elif self.inputType & QadInputTypeEnum.STRING or self.inputType & QadInputTypeEnum.BOOL or \ + self.inputType & QadInputTypeEnum.INT or self.inputType & QadInputTypeEnum.LONG or \ + self.inputType & QadInputTypeEnum.FLOAT: + if self.edits[QadDynamicInputEditEnum.EDIT].isVisible(): + if width > 0 : width += offset + offsetX_edit = width + width += self.edits[QadDynamicInputEditEnum.EDIT].width() + + x, y = self.adjustEditPosition(x, y, width, height) + + if self.edits[QadDynamicInputEditEnum.EDIT].isVisible(): self.edits[QadDynamicInputEditEnum.EDIT].move(x + offsetX_edit, y) + + elif self.inputType & QadInputTypeEnum.ANGLE: + if self.edits[QadDynamicInputEditEnum.EDIT].isVisible(): + if self.prevPoint is not None: # si tratta di inserimento di un nuovo punto a fine linea + point = self.resPt # posizione + start = QgsPointXY(self.prevPoint.x() + qad_utils.getDistance(self.prevPoint, point), self.prevPoint.y()) + editPt, lineMarkers = self.getPosAndLineMarkerForArc(start, self.prevPoint, point, 0, \ + self.edits[QadDynamicInputEditEnum.EDIT]) + if editPt is not None: + self.edits[QadDynamicInputEditEnum.EDIT].move(editPt.x(), editPt.y()) + del editPt + if lineMarkers is not None: # se non nullo disegno la linea di marker + self.edits[QadDynamicInputEditEnum.EDIT].setLinesMarker(lineMarkers) + del lineMarkers + else: + if width > 0 : width += offset + offsetX_edit = width + width += self.edits[QadDynamicInputEditEnum.EDIT].width() + x, y = self.adjustEditPosition(x, y, width, height) + self.edits[QadDynamicInputEditEnum.EDIT].move(x + offsetX_edit, y) + + # se devo mostrare i widget delle quote + elif self.isDimensionalWidgetVisib(): + if self.edits[QadDynamicInputEditEnum.PROMPT_EDIT].isVisible(): + x, y = self.adjustEditPosition(x, y, width, height) + point = self.resPt # posizione + + if self.prevPoint is not None: # si tratta di inserimento di un nuovo punto a fine linea + prevPt = self.prevPoint + elif self.prevPart is not None and self.prevPart.whatIs() == "LINE": # spostamento di un punto di un segmento in modalità grip + prevPt = self.prevPart.getStartPt() + else: + prevPt = None + + if prevPt is not None: + if self.edits[QadDynamicInputEditEnum.EDIT_DIST_PREV_PT].isVisible(): + offset = (height * 2) * self.canvas.mapSettings().mapUnitsPerPixel() + editPt, lineMarkers = self.getPosAndLineMarkerForLine(prevPt, point, \ + offset, self.edits[QadDynamicInputEditEnum.EDIT_DIST_PREV_PT]) + self.edits[QadDynamicInputEditEnum.EDIT_DIST_PREV_PT].move(editPt.x(), editPt.y()) + del editPt + # disegno la linea di marker + self.edits[QadDynamicInputEditEnum.EDIT_DIST_PREV_PT].setLinesMarker(lineMarkers) + del lineMarkers + + if self.edits[QadDynamicInputEditEnum.EDIT_ANG_PREV_PT].isVisible(): + start = QgsPointXY(prevPt.x() + qad_utils.getDistance(prevPt, point), prevPt.y()) + editPt, lineMarkers = self.getPosAndLineMarkerForArc(start, prevPt, point, 0, \ + self.edits[QadDynamicInputEditEnum.EDIT_ANG_PREV_PT]) + if editPt is not None: + self.edits[QadDynamicInputEditEnum.EDIT_ANG_PREV_PT].move(editPt.x(), editPt.y()) + del editPt + if lineMarkers is not None: # se non nullo disegno la linea di marker + self.edits[QadDynamicInputEditEnum.EDIT_ANG_PREV_PT].setLinesMarker(lineMarkers) + del lineMarkers + + if self.prevPart is not None: + gType = self.prevPart.whatIs() + if gType == "LINE": # spostamento di un punto di un segmento in modalità grip + prevCurrentPt = self.prevPart.getEndPt() + angle = qad_utils.getAngleBy2Pts(prevPt, point, 0) # senza tolleranza + pt = qad_utils.getPolarPointByPtAngle(prevPt, angle, self.prevPart.length()) + if self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_PREV_PT].isVisible(): + offset = (height * 1) * self.canvas.mapSettings().mapUnitsPerPixel() + editPt, lineMarkers = self.getPosAndLineMarkerForLine(pt, point, \ + offset, self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_PREV_PT]) + self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_PREV_PT].move(editPt.x(), editPt.y()) + del editPt + # disegno la linea di marker + self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_PREV_PT].setLinesMarker(lineMarkers) + del lineMarkers + + if self.edits[QadDynamicInputEditEnum.EDIT_REL_ANG_PREV_PT].isVisible(): + editPt, lineMarkers = self.getPosAndLineMarkerForArc(prevCurrentPt, prevPt, pt, 0, \ + self.edits[QadDynamicInputEditEnum.EDIT_ANG_PREV_PT], True) # LineMarker solo arco + self.edits[QadDynamicInputEditEnum.EDIT_REL_ANG_PREV_PT].move(editPt.x(), editPt.y()) + del editPt + # disegno la linea di marker + self.edits[QadDynamicInputEditEnum.EDIT_REL_ANG_PREV_PT].setLinesMarker(lineMarkers) + del lineMarkers + elif gType == "ARC": # spostamento di un punto di un arco in modalità grip + center = self.prevPart.center + + if self.edits[QadDynamicInputEditEnum.EDIT_DIST_PREV_PT].isVisible(): + # usato per lunghezza raggio arco nel punto finale della parte precedente + # oppure lunghezza raggio nel punto medio se parte precedente e successiva coincidono + if self.nextPart is not None and self.nextPart == self.prevPart: + p2 = self.prevPart.getMiddlePt() + else: + p2 = self.prevPart.getEndPt() + + editPt, lineMarkers = self.getPosAndLineMarkerForLine(center, p2, \ + 0, self.edits[QadDynamicInputEditEnum.EDIT_DIST_PREV_PT]) + self.edits[QadDynamicInputEditEnum.EDIT_DIST_PREV_PT].move(editPt.x(), editPt.y()) + del editPt + # disegno la linea di marker + self.edits[QadDynamicInputEditEnum.EDIT_DIST_PREV_PT].setLinesMarker(lineMarkers) + del lineMarkers + + if self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_PREV_PT].isVisible(): + # usato per lunghezza raggio arco nel punto iniziale della parte precedente + p2 = self.prevPart.getStartPt() + editPt, lineMarkers = self.getPosAndLineMarkerForLine(center, p2, \ + 0, self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_PREV_PT]) + self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_PREV_PT].move(editPt.x(), editPt.y()) + del editPt + # disegno la linea di marker + self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_PREV_PT].setLinesMarker(lineMarkers) + del lineMarkers + + if self.nextPart is not None: + gType = self.nextPart.whatIs() + if gType == "LINE": # spostamento di un punto di un segmento in modalità grip + nextPt = self.nextPart.getEndPt() + if self.edits[QadDynamicInputEditEnum.EDIT_DIST_NEXT_PT].isVisible(): + offset = (height * 2) * self.canvas.mapSettings().mapUnitsPerPixel() + editPt, lineMarkers = self.getPosAndLineMarkerForLine(nextPt, point, \ + offset, self.edits[QadDynamicInputEditEnum.EDIT_DIST_PREV_PT]) + self.edits[QadDynamicInputEditEnum.EDIT_DIST_NEXT_PT].move(editPt.x(), editPt.y()) + del editPt + # disegno la linea di marker + self.edits[QadDynamicInputEditEnum.EDIT_DIST_NEXT_PT].setLinesMarker(lineMarkers) + del lineMarkers + + if self.edits[QadDynamicInputEditEnum.EDIT_ANG_NEXT_PT].isVisible(): + start = QgsPointXY(nextPt.x() + qad_utils.getDistance(nextPt, point), nextPt.y()) + editPt, lineMarkers = self.getPosAndLineMarkerForArc(start, nextPt, point, 0, \ + self.edits[QadDynamicInputEditEnum.EDIT_ANG_NEXT_PT]) + if editPt is not None: + self.edits[QadDynamicInputEditEnum.EDIT_ANG_NEXT_PT].move(editPt.x(), editPt.y()) + del editPt + if lineMarkers is not None: # se non nullo disegno la linea di marker + self.edits[QadDynamicInputEditEnum.EDIT_ANG_NEXT_PT].setLinesMarker(lineMarkers) + del lineMarkers + + prevCurrentPt = self.nextPart.getStartPt() + angle = qad_utils.getAngleBy2Pts(nextPt, point, 0) # senza tolleranza + pt = qad_utils.getPolarPointByPtAngle(nextPt, angle, self.nextPart.length()) + if self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_NEXT_PT].isVisible(): + offset = (height * 1) * self.canvas.mapSettings().mapUnitsPerPixel() + editPt, lineMarkers = self.getPosAndLineMarkerForLine(pt, point, \ + offset, self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_NEXT_PT]) + self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_NEXT_PT].move(editPt.x(), editPt.y()) + del editPt + # disegno la linea di marker + self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_NEXT_PT].setLinesMarker(lineMarkers) + del lineMarkers + + if self.edits[QadDynamicInputEditEnum.EDIT_REL_ANG_NEXT_PT].isVisible(): + editPt, lineMarkers = self.getPosAndLineMarkerForArc(prevCurrentPt, nextPt, pt, 0, \ + self.edits[QadDynamicInputEditEnum.EDIT_REL_ANG_NEXT_PT], True) # LineMarker solo arco + self.edits[QadDynamicInputEditEnum.EDIT_REL_ANG_NEXT_PT].move(editPt.x(), editPt.y()) + del editPt + # disegno la linea di marker + self.edits[QadDynamicInputEditEnum.EDIT_REL_ANG_NEXT_PT].setLinesMarker(lineMarkers) + del lineMarkers + elif gType == "ARC": # spostamento di un punto di un arco in modalità grip + center = self.nextPart.center + + if self.edits[QadDynamicInputEditEnum.EDIT_DIST_NEXT_PT].isVisible(): + # usato per lunghezza raggio arco nel punto iniziale della parte successiva + p2 = self.nextPart.getStartPt() + editPt, lineMarkers = self.getPosAndLineMarkerForLine(center, p2, \ + 0, self.edits[QadDynamicInputEditEnum.EDIT_DIST_NEXT_PT]) + self.edits[QadDynamicInputEditEnum.EDIT_DIST_NEXT_PT].move(editPt.x(), editPt.y()) + del editPt + # disegno la linea di marker + self.edits[QadDynamicInputEditEnum.EDIT_DIST_NEXT_PT].setLinesMarker(lineMarkers) + del lineMarkers + + if self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_NEXT_PT].isVisible(): + # usato per lunghezza raggio nel punto finale della parte successiva se arco + p2 = self.nextPart.getEndPt() + editPt, lineMarkers = self.getPosAndLineMarkerForLine(center, p2, \ + 0, self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_NEXT_PT]) + self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_NEXT_PT].move(editPt.x(), editPt.y()) + del editPt + # disegno la linea di marker + self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_NEXT_PT].setLinesMarker(lineMarkers) + del lineMarkers + + else: + if self.edits[QadDynamicInputEditEnum.PROMPT_EDIT].isVisible(): + x, y = self.adjustEditPosition(x, y, width, height) + + if self.edits[QadDynamicInputEditEnum.PROMPT_EDIT].isVisible(): + self.edits[QadDynamicInputEditEnum.PROMPT_EDIT].move(x, y) + + + # ============================================================================ + # refreshResult + # ============================================================================ + def refreshResult(self, mousePos = None): + # calcola il risultato e restituisce True se l'operazione ha successo + # a seconda del contesto può essere un punto -> self.resPt o un valore (numero, stringa, bool...) -> self.resValue + # il risultato è anche impostato in formato stringa in self.resStr + self.resValue = None + self.resStr = "" + + # se il risultato può essere un punto + if self.inputType & QadInputTypeEnum.POINT2D or self.inputType & QadInputTypeEnum.POINT3D: + if mousePos is not None: + point = self.canvas.getCoordinateTransform().toMapCoordinates(mousePos) # posizione + else: + point = self.canvas.getCoordinateTransform().toMapCoordinates(self.canvas.mouseLastXY()) # posizione + + # se i widget delle coordinate sono visibili si sta cercando un punto + if self.edits[QadDynamicInputEditEnum.EDIT_X].isVisible(): + # si sta cercando un punto attraverso le coordinate esplicite (relative al punto precedente o assolute) + + if self.prevPoint is None: # se non è settato nell'input dinamico lo cerco nel plugin + if self.plugIn.lastPoint is None: + prevPt = QgsPointXY(0,0) + else: + prevPt = self.plugIn.lastPoint + else: + prevPt = self.prevPoint + + # se si tratta di coordinate relative + relative = True if self.dynPiCoords == 0 else False + # se si tratta di coordinate polari + polar = True if self.dynPiFormat == 0 else False + if self.edits[QadDynamicInputEditEnum.EDIT_SYMBOL_COORD_TYPE].isVisible(): + coordType = self.edits[QadDynamicInputEditEnum.EDIT_SYMBOL_COORD_TYPE].toPlainText() + if "@" in coordType: relative = True + elif "#" in coordType: relative = False + polar = True if "<" in coordType else False + else: # se non è esplicito che sia relativo ma lo è per via di dynPiCoords + prevPt = self.prevPoint # se non c'è il punto precedente impostato nell'input dinamico + if prevPt is None: + relative = False + polar = False + + if polar == False: # coordinate cartesiane + # x + if self.edits[QadDynamicInputEditEnum.EDIT_X].isLockedValue() == False: + self.resPt.setX(point.x()) + else: + value = self.edits[QadDynamicInputEditEnum.EDIT_X].checkValid() # ritorna il valore se valido + if value is None: + self.resPt.setX(point.x()) + else: + if relative: + value = prevPt.x() + value + self.resPt.setX(value) + + # y + if self.edits[QadDynamicInputEditEnum.EDIT_Y].isLockedValue() == False: + self.resPt.setY(point.y()) + else: + value = self.edits[QadDynamicInputEditEnum.EDIT_Y].checkValid() # ritorna il valore se valido + if value is None: + self.resPt.setY(point.y()) + else: + if relative: + value = prevPt.y() + value + self.resPt.setY(value) + + if self.edits[QadDynamicInputEditEnum.EDIT_Z].isVisible(): + # z + if self.edits[QadDynamicInputEditEnum.EDIT_Z].isLockedValue() == False: + self.resPt.setZ(point.z()) + else: + value = self.edits[QadDynamicInputEditEnum.EDIT_Z].checkValid() # ritorna il valore se valido + if value is None: + self.resPt.setZ(point.z()) + else: + self.resPt.setZ(prevPt.z() + value if relative else value) + elif prevPt is not None: # coordinate polari + # nel caso di coordinate polari EDIT_X contiene la distanza dal punto precedente o da 0,0 + if self.edits[QadDynamicInputEditEnum.EDIT_X].isLockedValue(): + dist = self.edits[QadDynamicInputEditEnum.EDIT_X].checkValid() # ritorna il valore se valido + if dist is None: + if relative: + dist = qad_utils.getDistance(prevPt, point) + else: + dist = qad_utils.getDistance(QgsPointXY(0, 0), point) + else: + if relative: + dist = qad_utils.getDistance(prevPt, point) + else: + dist = qad_utils.getDistance(QgsPointXY(0, 0), point) + + # nel caso di coordinate polari EDIT_Y contiene l'angolo + if self.edits[QadDynamicInputEditEnum.EDIT_Y].isLockedValue(): + angle = self.edits[QadDynamicInputEditEnum.EDIT_Y].checkValid() # ritorna il valore se valido + if angle is None: + angle = qad_utils.getAngleBy2Pts(prevPt, point, 0) # senza tolleranza + else: + angle = qad_utils.getAngleBy2Pts(prevPt, point, 0) # senza tolleranza + + if relative: + pt = qad_utils.getPolarPointByPtAngle(prevPt, angle, dist) + else: + pt = qad_utils.getPolarPointByPtAngle(QgsPointXY(0, 0), angle, dist) + + self.resPt.setX(pt.x()) + self.resPt.setY(pt.y()) + + self.resStr = self.resPt.toString() + return True + + if self.isDimensionalWidgetVisib(): + if self.prevPoint is not None: # si tratta di inserimento di un nuovo punto a fine linea + # si sta cercando un punto attraverso la distanza e l'angolo da punto precedente + if self.edits[QadDynamicInputEditEnum.EDIT_DIST_PREV_PT].isLockedValue() == False: + dist = qad_utils.getDistance(self.prevPoint, point) + else: + dist = self.edits[QadDynamicInputEditEnum.EDIT_DIST_PREV_PT].checkValid() # ritorna il valore se valido + if dist is None: dist = qad_utils.getDistance(self.prevPoint, point) + + if self.edits[QadDynamicInputEditEnum.EDIT_ANG_PREV_PT].isLockedValue() == False: + angle = qad_utils.getAngleBy2Pts(self.prevPoint, point, 0) # senza tolleranza + else: + angle = self.edits[QadDynamicInputEditEnum.EDIT_ANG_PREV_PT].checkValid() # ritorna il valore in radianti se valido + if angle is None: + angle = qad_utils.getAngleBy2Pts(self.prevPoint, point, 0) # senza tolleranza + else: + angleMouse = qad_utils.getAngleBy2Pts(self.prevPoint, point, 0) # senza tolleranza + # se il mouse forma un angolo tra 180 e 360 allora l'angolo digitato va sottratto a 360 gradi + if angleMouse >= math.pi and angleMouse < 2 * math.pi: + angle = (2 * math.pi) - angle + + pt = qad_utils.getPolarPointByPtAngle(self.prevPoint, angle, dist) + self.resPt.setX(pt.x()) + self.resPt.setY(pt.y()) + self.resStr = self.resPt.toString() + return True + else: # spostamento di un punto in modalità grip + # se il widget delle quote "distanza dal punto precedente" è visibile + if self.edits[QadDynamicInputEditEnum.EDIT_DIST_PREV_PT].isVisible() and \ + self.edits[QadDynamicInputEditEnum.EDIT_DIST_PREV_PT].isLockedValue() and \ + self.prevPart is not None and self.prevPart.whatIs() == "LINE": + dist = self.edits[QadDynamicInputEditEnum.EDIT_DIST_PREV_PT].checkValid() # ritorna il valore se valido + if dist is not None: + pt1 = self.prevPart.getStartPt() + pt2 = self.prevPart.getEndPt() + angle = qad_utils.getAngleBy2Pts(pt1, pt2, 0) # senza tolleranza + pt = qad_utils.getPolarPointByPtAngle(pt1, angle, dist) + self.resPt.setX(pt.x()) + self.resPt.setY(pt.y()) + self.resStr = self.resPt.toString() + return True + + # se il widget delle quote nel modo grip "distanza dal punto successivo" è visibile + elif self.edits[QadDynamicInputEditEnum.EDIT_DIST_NEXT_PT].isVisible() and \ + self.edits[QadDynamicInputEditEnum.EDIT_DIST_NEXT_PT].isLockedValue() and \ + self.nextPart is not None and self.nextPart.whatIs() == "LINE": + dist = self.edits[QadDynamicInputEditEnum.EDIT_DIST_NEXT_PT].checkValid() # ritorna il valore se valido + if dist is not None: + pt1 = self.nextPart.getEndPt() + pt2 = self.nextPart.getStartPt() + angle = qad_utils.getAngleBy2Pts(pt1, pt2, 0) # senza tolleranza + pt = qad_utils.getPolarPointByPtAngle(pt1, angle, dist) + self.resPt.setX(pt.x()) + self.resPt.setY(pt.y()) + self.resStr = self.resPt.toString() + return True + + # se il widget delle quote "angolo dal punto precedente" è visibile + elif self.edits[QadDynamicInputEditEnum.EDIT_ANG_PREV_PT].isVisible() and \ + self.edits[QadDynamicInputEditEnum.EDIT_ANG_PREV_PT].isLockedValue() and \ + self.prevPart is not None and self.prevPart.whatIs() == "LINE": + angle = self.edits[QadDynamicInputEditEnum.EDIT_ANG_PREV_PT].checkValid() # ritorna il valore in radianti se valido + if angle is not None: + pt1 = self.prevPart.getStartPt() + angleMouse = qad_utils.getAngleBy2Pts(pt1, point, 0) # senza tolleranza + # se il mouse forma un angolo tra 180 e 360 allora l'angolo digitato va sottratto a 360 gradi + if angleMouse >= math.pi and angleMouse < 2 * math.pi: + angle = (2 * math.pi) - angle + pt = qad_utils.getPolarPointByPtAngle(pt1, angle, self.prevPart.length()) + self.resPt.setX(pt.x()) + self.resPt.setY(pt.y()) + self.resStr = self.resPt.toString() + return True + + # se il widget delle quote "angolo dal punto successivo" è visibile + elif self.edits[QadDynamicInputEditEnum.EDIT_ANG_NEXT_PT].isVisible() and \ + self.edits[QadDynamicInputEditEnum.EDIT_ANG_NEXT_PT].isLockedValue() and \ + self.nextPart is not None and self.nextPart.whatIs() == "LINE": + angle = self.edits[QadDynamicInputEditEnum.EDIT_ANG_NEXT_PT].checkValid() # ritorna il valore in radianti se valido + if angle is not None: + pt1 = self.nextPart.getEndPt() + angleMouse = qad_utils.getAngleBy2Pts(pt1, point, 0) # senza tolleranza + # se il mouse forma un angolo tra 180 e 360 allora l'angolo digitato va sottratto a 360 gradi + if angleMouse >= math.pi and angleMouse < 2 * math.pi: + angle = (2 * math.pi) - angle + pt = qad_utils.getPolarPointByPtAngle(pt1, angle, self.nextPart.length()) + self.resPt.setX(pt.x()) + self.resPt.setY(pt.y()) + self.resStr = self.resPt.toString() + return True + + # se il widget delle quote nel modo grip "distanza rispetto la posizione precedente dello stesso punto nel verso dal punto precedente" è visibile + elif self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_PREV_PT].isVisible() and \ + self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_PREV_PT].isLockedValue() and \ + self.prevPart is not None and self.prevPart.whatIs() == "LINE": + dist = self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_PREV_PT].checkValid() # ritorna il valore se valido + if dist is not None: + pt1 = self.prevPart.getStartPt() + pt2 = self.prevPart.getEndPt() + angle = qad_utils.getAngleBy2Pts(pt1, pt2, 0) # senza tolleranza + pt = qad_utils.getPolarPointByPtAngle(pt2, angle, dist) + self.resPt.setX(pt.x()) + self.resPt.setY(pt.y()) + self.resStr = self.resPt.toString() + return True + + # se il widget delle quote nel modo grip "distanza rispetto la posizione precedente dello stesso punto nel verso dal punto successivo" è visibile + elif self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_NEXT_PT].isVisible() and \ + self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_NEXT_PT].isLockedValue() and \ + self.nextPart is not None and self.nextPart.whatIs() == "LINE": + dist = self.edits[QadDynamicInputEditEnum.EDIT_REL_DIST_NEXT_PT].checkValid() # ritorna il valore se valido + if dist is not None: + pt1 = self.nextPart.getEndPt() + pt2 = self.nextPart.getStartPt() + angle = qad_utils.getAngleBy2Pts(pt1, pt2, 0) # senza tolleranza + pt = qad_utils.getPolarPointByPtAngle(pt2, angle, dist) + self.resPt.setX(pt.x()) + self.resPt.setY(pt.y()) + self.resStr = self.resPt.toString() + return True + + # se il widget delle quote nel modo grip "angolo relativo all'angolo dal punto precedente" è visibile + elif self.edits[QadDynamicInputEditEnum.EDIT_REL_ANG_PREV_PT].isVisible() and \ + self.edits[QadDynamicInputEditEnum.EDIT_REL_ANG_PREV_PT].isLockedValue() and \ + self.prevPart is not None and self.prevPart.whatIs() == "LINE": + angle = self.edits[QadDynamicInputEditEnum.EDIT_REL_ANG_PREV_PT].checkValid() # ritorna il valore in radianti se valido + if angle is not None: + pt1 = self.prevPart.getStartPt() + pt2 = self.prevPart.getEndPt() + anglePart = qad_utils.getAngleBy2Pts(pt1, pt2, 0) # senza tolleranza + angleMouse = qad_utils.getAngleBy2Pts(pt1, point, 0) # senza tolleranza + diffAngle = qad_utils.normalizeAngle(angleMouse - anglePart) + # se il mouse forma un angolo tra 180 e 360 allora l'angolo digitato va sottratto a 360 gradi + if diffAngle >= math.pi and diffAngle < (2 * math.pi): + pt = qad_utils.getPolarPointByPtAngle(pt1, anglePart-angle, self.prevPart.length()) + else: + pt = qad_utils.getPolarPointByPtAngle(pt1, anglePart+angle, self.prevPart.length()) + self.resPt.setX(pt.x()) + self.resPt.setY(pt.y()) + self.resStr = self.resPt.toString() + return True + + # se il widget delle quote nel modo grip "distanza dal punto precedente" è visibile + elif self.edits[QadDynamicInputEditEnum.EDIT_REL_ANG_NEXT_PT].isVisible() and \ + self.edits[QadDynamicInputEditEnum.EDIT_REL_ANG_NEXT_PT].isLockedValue() and \ + self.nextPart is not None and self.nextPart.whatIs() == "LINE": + angle = self.edits[QadDynamicInputEditEnum.EDIT_REL_ANG_NEXT_PT].checkValid() # ritorna il valore in radianti se valido + if angle is not None: + pt1 = self.nextPart.getEndPt() + pt2 = self.nextPart.getStartPt() + anglePart = qad_utils.getAngleBy2Pts(pt1, pt2, 0) # senza tolleranza + angleMouse = qad_utils.getAngleBy2Pts(pt1, point, 0) # senza tolleranza + diffAngle = qad_utils.normalizeAngle(angleMouse - anglePart) + # se il mouse forma un angolo tra 180 e 360 allora l'angolo digitato va sottratto a 360 gradi + if diffAngle >= math.pi and diffAngle < (2 * math.pi): + pt = qad_utils.getPolarPointByPtAngle(pt1, anglePart-angle, self.nextPart.length()) + else: + pt = qad_utils.getPolarPointByPtAngle(pt1, anglePart+angle, self.nextPart.length()) + self.resPt.setX(pt.x()) + self.resPt.setY(pt.y()) + self.resStr = self.resPt.toString() + return True + + else: # se nessun valore era non bloccato + self.resPt.setX(point.x()) + self.resPt.setY(point.y()) + self.resStr = self.resPt.toString() + return True + + # si sta cercando un angolo attraverso il punto precedente + if self.inputType & QadInputTypeEnum.ANGLE and self.prevPoint is not None: + if self.edits[QadDynamicInputEditEnum.EDIT].isLockedValue() == False: + self.resPt.setX(point.x()) + self.resPt.setY(point.y()) + self.resStr = self.resPt.toString() + self.resValue = qad_utils.getAngleBy2Pts(self.prevPoint, self.resPt, 0) # senza tolleranza + return True + else: + dist = qad_utils.getDistance(self.prevPoint, point) + angle = self.edits[QadDynamicInputEditEnum.EDIT].checkValid() # ritorna il valore in radianti se valido + if angle is not None: + pt = qad_utils.getPolarPointByPtAngle(self.prevPoint, angle, dist) + self.resPt.setX(pt.x()) + self.resPt.setY(pt.y()) + self.resStr = self.resPt.toString() + return True + + # si sta cercando un valore float attraverso il punto precedente + if self.inputType & QadInputTypeEnum.FLOAT and self.prevPoint is not None: + if self.edits[QadDynamicInputEditEnum.EDIT].isLockedValue() == False: + self.resPt.setX(point.x()) + self.resPt.setY(point.y()) + self.resStr = self.resPt.toString() + self.resValue = qad_utils.getDistance(self.prevPoint, self.resPt) + return True + else: + angle = qad_utils.getAngleBy2Pts(self.prevPoint, point, 0) # senza tolleranza + dist = self.edits[QadDynamicInputEditEnum.EDIT].checkValid() # ritorna il valore se valido + if dist is not None: + pt = qad_utils.getPolarPointByPtAngle(self.prevPoint, angle, dist) + self.resPt.setX(pt.x()) + self.resPt.setY(pt.y()) + self.resStr = self.resPt.toString() + return True + + return True + + + if self.inputType & QadInputTypeEnum.STRING or self.inputType & QadInputTypeEnum.INT or \ + self.inputType & QadInputTypeEnum.LONG or self.inputType & QadInputTypeEnum.FLOAT or \ + self.inputType & QadInputTypeEnum.BOOL or self.inputType & QadInputTypeEnum.ANGLE: + if self.edits[QadDynamicInputEditEnum.EDIT].isVisible(): + if self.edits[QadDynamicInputEditEnum.EDIT].isLockedValue() == True: + self.resValue = self.edits[QadDynamicInputEditEnum.EDIT].checkValid() # ritorna il valore se valido + if self.resValue is None: + self.resStr = "" + return False + else: + if self.inputType & QadInputTypeEnum.ANGLE: + self.resStr = unicode(qad_utils.toDegrees(self.resValue)) + else: + self.resStr = unicode(self.resValue) + return True + elif self.inputType & QadInputTypeEnum.ANGLE and self.prevPoint is not None: + self.resValue = qad_utils.getAngleBy2Pts(self.prevPoint, self.resPt, 0) # senza tolleranza + self.resStr = unicode(qad_utils.toDegrees(self.resValue)) + return True + else: + self.resValue = None + self.resStr = "" + + return False + + + # ============================================================================ + # keyPressEvent + # ============================================================================ + def keyPressEvent(self, e): + if self.currentEdit is None: + return + if e.key() == Qt.Key_Comma: # "," + # se il risultato può essere un punto + if self.inputType & QadInputTypeEnum.POINT2D or self.inputType & QadInputTypeEnum.POINT2D: + self.forcedCoordWidgetVisib = True # editaz forzata delle coordinate + if self.currentEdit != QadDynamicInputEditEnum.EDIT_X and \ + self.currentEdit != QadDynamicInputEditEnum.EDIT_Y and \ + self.currentEdit != QadDynamicInputEditEnum.EDIT_Z: + coord = self.edits[self.currentEdit].toPlainText() + self.currentEdit = QadDynamicInputEditEnum.EDIT_X + self.edits[QadDynamicInputEditEnum.EDIT_X].setLockedValue(True) # se è possibile modifico lo stato di lock + self.edits[QadDynamicInputEditEnum.EDIT_X].showMsg(coord) + self.show(True) + self.setNextCurrentEdit() + else: + QTextEdit.keyPressEvent(self.edits[self.currentEdit], e) + #self.edits[self.currentEdit].keyPressEvent(e) + + elif e.text() == "@" or e.text() == "#" or e.text() == "<": + # se il risultato può essere un punto + if self.inputType & QadInputTypeEnum.POINT2D or self.inputType & QadInputTypeEnum.POINT2D: + self.forcedCoordWidgetVisib = True # editaz forzata delle coordinate + value = self.edits[QadDynamicInputEditEnum.EDIT_SYMBOL_COORD_TYPE].toPlainText() + alreadyPolar = True if value.find("<") >= 0 else False + if e.text() == "@" or e.text() == "#": + value = e.text() + if alreadyPolar: value = value + "<" + else: # "<" + if value.find("@") >= 0: value = "@" + elif value.find("#") >= 0: value = "#" + if alreadyPolar == False: value = value + "<" + + self.edits[QadDynamicInputEditEnum.EDIT_SYMBOL_COORD_TYPE].showMsg(value) + if self.currentEdit != QadDynamicInputEditEnum.EDIT_X and \ + self.currentEdit != QadDynamicInputEditEnum.EDIT_Y and \ + self.currentEdit != QadDynamicInputEditEnum.EDIT_Z: + coord = self.edits[self.currentEdit].toPlainText() + self.currentEdit = QadDynamicInputEditEnum.EDIT_X + self.edits[QadDynamicInputEditEnum.EDIT_X].showMsg(coord) + self.show(True) + + elif e.key() == Qt.Key_Return or e.key == Qt.Key_Enter: + # se non c'è alcun widget con valore bloccato + if self.anyLockedValueEdit() == False: + msg = "" + # se era stato premuto @ o # + if self.edits[QadDynamicInputEditEnum.EDIT_SYMBOL_COORD_TYPE].isVisible(): + coordType = self.edits[QadDynamicInputEditEnum.EDIT_SYMBOL_COORD_TYPE].toPlainText() + if "@" in coordType: msg = "@" + elif "#" in coordType: msg = "#" + self.showEvaluateMsg(msg) + else: + if self.currentEdit is not None: + currentWidget = self.edits[self.currentEdit] + # se il contenuto del widget è stato modificato dall'utente + if currentWidget.isLockedValue() == True: + value = currentWidget.toPlainText() + # verifico se si tratta di una opzione del comando attivo + keyWord = self.evaluateKeyWords(value) + if keyWord is not None: + self.showEvaluateMsg(keyWord) + # altrimenti se ci si attendeva un punto e si tratta di un'opzione di osnap + elif (self.inputType & QadInputTypeEnum.POINT2D or self.inputType & QadInputTypeEnum.POINT3D) and \ + str2snapTypeEnum(value) != -1: + currentWidget.showMsg("") + currentWidget.setLockedValue(False) + self.showEvaluateMsg(value) + # altrimenti se ci si attendeva un punto e si tratta dell'opzione M2P "punto medio tra 2 punti" + elif (self.inputType & QadInputTypeEnum.POINT2D or self.inputType & QadInputTypeEnum.POINT3D) and \ + (value.upper() == QadMsg.translate("Snap", "M2P") or value.upper() == "_M2P"): + currentWidget.showMsg("") + currentWidget.setLockedValue(False) + self.showEvaluateMsg(value) + # altrimenti verifico la validità del valore + else: + if currentWidget.checkValid() is not None: + msg = self.resStr if self.refreshResult() == True else "" # ricalcolo il risultato e lo uso in formato stringa + self.showEvaluateMsg(msg) + else: + msg = self.resStr if self.refreshResult() == True else "" # ricalcolo il risultato e lo uso in formato stringa + self.showEvaluateMsg(msg) + else: + self.edits[self.currentEdit].keyPressEvent(e) + + + # ============================================================================ + # evaluateKeyWords + # ============================================================================ + def evaluateKeyWords(self, cmd): + # The required portion of the keyword is specified in uppercase characters, + # and the remainder of the keyword is specified in lowercase characters. + # The uppercase abbreviation can be anywhere in the keyword + if cmd[0] == "_": # versione inglese + keyWord, Msg = qad_utils.evaluateCmdKeyWords(cmd[1:], self.englishKeyWords) + if keyWord is None: return None + # cerco la corrispondente parola chiave in lingua locale + i = 0 + for k in self.englishKeyWords: + if k == keyWord: + return self.keyWords[i] + i = i + 1 + return None + else: + keyWord, Msg = qad_utils.evaluateCmdKeyWords(cmd, self.keyWords) + return keyWord diff --git a/qad_ellipse.py b/qad_ellipse.py new file mode 100644 index 00000000..09c16602 --- /dev/null +++ b/qad_ellipse.py @@ -0,0 +1,1081 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + classe per la gestione delle ellissi + + ------------------- + begin : 2018-05-15 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from qgis.PyQt.QtCore import * +from qgis.PyQt.QtGui import * +from qgis.core import * +from qgis.gui import * + +import math +import sys +from copy import deepcopy + +from . import qad_utils +from .qad_variables import QadVariables +from .qad_msg import QadMsg +from .qad_point import QadPoint + + +# =============================================================================== +# QadEllipse ellipse class +# =============================================================================== +class QadEllipse(): + + def __init__(self, ellipse = None): + if ellipse is not None: + self.set(ellipse.center, ellipse.majorAxisFinalPt, ellipse.axisRatio) + else: + self.center = None + self.majorAxisFinalPt = None # punto finale dell'asse maggiore (a dx) + self.axisRatio = 0 # rapporto tra asse minore e asse maggiore + + def whatIs(self): + # obbligatoria + return "ELLIPSE" + + + # ============================================================================ + # isClosed + # ============================================================================ + def isClosed(self): + return True + + + def set(self, center, majorAxisFinalPt = None, axisRatio = None): + if isinstance(center, QadEllipse): + ellipse = center + return self.set(ellipse.center, ellipse.majorAxisFinalPt, ellipse.axisRatio) + + if center == majorAxisFinalPt: return None + self.center = QgsPointXY(center) + self.majorAxisFinalPt = QgsPointXY(majorAxisFinalPt) + self.axisRatio = axisRatio + return self + + + def transform(self, coordTransform): + """Transform this geometry as described by CoordinateTranasform ct.""" + self.center = coordTransform.transform(self.center) + self.majorAxisFinalPt = coordTransform.transform(self.majorAxisFinalPt) + + + def transformFromCRSToCRS(self, sourceCRS, destCRS): + """Transform this geometry as described by CRS.""" + if (sourceCRS is not None) and (destCRS is not None) and sourceCRS != destCRS: + coordTransform = QgsCoordinateTransform(sourceCRS, destCRS, QgsProject.instance()) # trasformo le coord + self.center = coordTransform.transform(self.center) + self.majorAxisFinalPt = coordTransform.transform(self.majorAxisFinalPt) + + + def __eq__(self, ellipse): + # obbligatoria + """self == other""" + if ellipse.whatIs() != "ELLIPSE": return False + if self.center != ellipse.center or self.majorAxisFinalPt != ellipse.majorAxisFinalPt or self.axisRatio != ellipse.axisRatio: + return False + else: + return True + + + def __ne__(self, ellipse): + """self != other""" + return not self.__eq__(ellipse) + + + def equals(self, ellipse): + # uguali geometricamente (NON conta il verso) + return self.__eq__(ellipse) + + + def copy(self): + # obbligatoria + return QadEllipse(self) + + + def length(self): + a = qad_utils.getDistance(self.center, self.majorAxisFinalPt) # semiasse maggiore + b = a * self.axisRatio # semiasse minore + numerator = a * a + b * b + if numerator == 0: return 0 + return 2 * math.pi * math.sqrt(numerator / 2) + + + def area(self): + a = qad_utils.getDistance(self.center, self.majorAxisFinalPt) # semiasse maggiore + b = a * self.axisRatio # semiasse minore + return math.pi * a * b + + + # ============================================================================ + # getRotation + # ============================================================================ + def getRotation(self): + return qad_utils.getAngleBy2Pts(self.center, self.majorAxisFinalPt) + + + # =============================================================================== + # getBoundingBox + # =============================================================================== + def getBoundingBox(self): + """ + la funzione ritorna il rettangolo che racchiude l'ellisse. + """ + angle = self.getRotation() + a = qad_utils.getDistance(self.center, self.majorAxisFinalPt) # semiasse maggiore + b = a * self.axisRatio # semiasse minore + + pt1 = qad_utils.getPolarPointByPtAngle(self.majorAxisFinalPt, angle - math.pi / 2, b) + pt2 = qad_utils.getPolarPointByPtAngle(self.majorAxisFinalPt, angle + math.pi / 2, b) + pt3 = qad_utils.getPolarPointByPtAngle(pt1, angle + math.pi, 2 * a) + pt4 = qad_utils.getPolarPointByPtAngle(pt2, angle + math.pi, 2 * a) + + xMin = pt1.x() + yMin = pt1.y() + xMax = pt1.x() + yMax = pt1.y() + for pt in (pt2, pt3, pt4): + if pt.x() < xMin: xMin = pt.x() + if pt.y() < yMin: yMin = pt.y() + if pt.x() > xMax: xMax = pt.x() + if pt.y() > yMax: yMax = pt.y() + + return QgsRectangle(xMin, yMin, xMax, yMax) + + + # ============================================================================ + # getFocus + # ============================================================================ + def getFocus(self): + # restituisce una lista di 2 punti che sono i fuochi dell'ellisse + # http://www.softschools.com/math/calculus/finding_the_foci_of_an_ellipse/ + angle = qad_utils.getAngleBy2Pts(self.center, self.majorAxisFinalPt) + a = qad_utils.getDistance(self.center, self.majorAxisFinalPt) # semiasse maggiore + b = a * self.axisRatio # semiasse minore + numerator = a * a - b * b + if numerator == 0: return [] + c = math.sqrt(numerator) + pt1 = qad_utils.getPolarPointByPtAngle(self.center, angle, c) + pt2 = qad_utils.getPolarPointByPtAngle(self.center, angle, -c) + return [pt1, pt2] + + + # ============================================================================ + # containsPt + # ============================================================================ + def containsPt(self, point): + return True if self.whereIsPt(point) == 0 else False # -1 interno, 0 sull'ellisse, 1 esterno: + + + # ============================================================================ + # whereIsPt + # ============================================================================ + def whereIsPt(self, point): + # ritorna -1 se il punto è interno, 0 se è sull'ellisse, 1 se è esterno + foci = self.getFocus() + if len(foci) == 0: return False + dist1 = qad_utils.getDistance(foci[0], self.majorAxisFinalPt) + dist2 = qad_utils.getDistance(foci[1], self.majorAxisFinalPt) + distSumEllipse = dist1 + dist2 + dist1 = qad_utils.getDistance(foci[0], point) + dist2 = qad_utils.getDistance(foci[1], point) + distSum = dist1 + dist2 + if qad_utils.doubleNear(distSumEllipse, distSum): + return 0 + elif distSum < distSumEllipse: + return -1 + else: + return 1 + + + # ============================================================================ + # getQuadrantPoints + # ============================================================================ + def getQuadrantPoints(self): + # ritorna i punti quadranti: partendo da majorAxisFinalPt in ordine antiorario + angle = qad_utils.getAngleBy2Pts(self.center, self.majorAxisFinalPt) + a = qad_utils.getDistance(self.center, self.majorAxisFinalPt) # semiasse maggiore + b = a * self.axisRatio # semiasse minore + + pt1 = QgsPointXY(self.majorAxisFinalPt) + pt2 = qad_utils.getPolarPointByPtAngle(self.center, angle + math.pi / 2, b) + pt3 = qad_utils.getPolarPointByPtAngle(self.center, angle + math.pi, a) + pt4 = qad_utils.getPolarPointByPtAngle(self.center, angle - math.pi / 2, b) + + return [pt1, pt2, pt3, pt4] + + + # ============================================================================ + # translateAndRotatePtForNormalEllipse + # ============================================================================ + def translateAndRotatePtForNormalEllipse(self, point, inverse): + # poichè è conveniente fare i calcoli considerando una ellisse con centro in 0,0 e rotazione 0 + # traslo e ruoto il punto con cui devo fare i calcoli per adattarlo a questo tipo di ellisse + # il parametro inverse, se uguale a True, fa il calcolo inverso per riottenere il punto originale + rot = self.getRotation() + if rot > math.pi/2 and rot < math.pi*3/2: rot = rot - math.pi + if inverse == False: + myPoint = QgsPointXY(point.x() - self.center.x(), point.y() - self.center.y()) + myPoint = qad_utils.rotatePoint(myPoint, QgsPointXY(0,0), -1 * rot) + else: + myPoint = qad_utils.rotatePoint(point, QgsPointXY(0,0), rot) + myPoint = QgsPointXY(myPoint.x() + self.center.x(), myPoint.y() + self.center.y()) + + return myPoint + + + def getNormalAngleToAPointOnEllipse(self, p): + # https://www3.ul.ie/~rynnet/swconics/TC.htm + # 1. Join the point P to the two focal points + # 2. Bisect the angle formed to get the normal (the normal is a line perpendicular to the tangent at the point of contact (p.o.c.)). + + # trovo i fuochi + foci = self.getFocus() + if len(foci) == 0: return None + # punto 1 e 2 + bisectorLine = qad_utils.getBisectorInfinityLine(foci[0], p, foci[1]) + return qad_utils.getAngleBy2Pts(bisectorLine[1], bisectorLine[0]) # verso l'esterno dell'ellisse + + + def getTanDirectionOnPt(self, p): + # https://www3.ul.ie/~rynnet/swconics/TC.htm + # 1. Join the point P to the two focal points + # 2. Bisect the angle formed to get the normal (the normal is a line perpendicular to the tangent at the point of contact (p.o.c.)). + # 3. Construct the tangent perpendicular to the normal at the p.o.c. + + normal = self.getNormalAngleToAPointOnEllipse(p) + # punto 3 + angle = normal + math.pi / 2 + + return qad_utils.normalizeAngle(angle) + + + # ============================================================================ + # getAngleFromParam + # ============================================================================ + def getAngleFromParam(self, param): + """ + L'equazione parametrica per l'ellisse è x=a*cos(param), y=b*sin(param). + E' importante capire che param non è l'angolo al centro. + Questa funzione ottiene l'angolo al centro partendo da param + arctan(b/a * tan(param)) dove + a = asse maggiore + b = asse minore + """ + angle = math.atan(self.axisRatio * math.tan(param)) + myParam = param % (math.pi * 2) # modulo + if myParam > math.pi / 2 and myParam < math.pi * 3 / 2: + angle = angle + math.pi + + return angle + + + # ============================================================================ + # getParamFromAngle + # ============================================================================ + def getParamFromAngle(self, angle): + """ + L'equazione parametrica per l'ellisse è x=a*cos(param), y=b*sin(param). + E' importante capire che param non è l'angolo al centro. + Questa funzione ottiene param partendo dall'angolo al centro + arctan(a/b * tan(angle)) dove + a = asse maggiore + b = asse minore + """ + param = math.atan(1.0 / self.axisRatio * math.tan(angle)) + myAngle = angle % (math.pi * 2) # modulo + if myAngle > math.pi / 2 and myAngle < math.pi * 3 / 2: + #if myAngle > math.pi and myAngle < 2 * math.pi: + param = param + math.pi + return param + + + + # ============================================================================ + # asPolyline + # ============================================================================ + def asPolyline(self, tolerance2ApproxCurve = None, atLeastNSegment = None): + """ + ritorna una lista di punti che definisce l'ellisse + """ + if tolerance2ApproxCurve is None: + tolerance = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) + else: + tolerance = tolerance2ApproxCurve + + if atLeastNSegment is None: + _atLeastNSegment = QadVariables.get(QadMsg.translate("Environment variables", "ELLIPSEMINSEGMENTQTY"), 12) + else: + _atLeastNSegment = atLeastNSegment + + param = 0 + endParam = 2 * math.pi + pt = self.getPointAt(param) + + angleStep = 2 * math.pi / _atLeastNSegment + + points = [] + points.append(pt) + while True: + param, pt = self.getNextParamPt(param, pt, angleStep, tolerance) + if param > endParam: break + points.append(pt) + + if points[-1] != points[0]: # se l'ultimo punto non coincide con il primo + if qad_utils.ptNear(points[-1], points[0]): # se l'ultimo punto è abbastanza vicino al primo + points[-1].set(points[0].x(), points[0].y()) # sposto l'ultimo punto e lo faccio coincidere con il primo + else: + points.append(QgsPointXY(points[0])) # aggiungo l'ultimo punto coincidente al primo + + return points + + + # =============================================================================== + # asLineString + # =============================================================================== + def asLineString(self, tolerance2ApproxCurve = None, atLeastNSegment = None): + """ + la funzione ritorna l'ellisse in forma di lineString. + """ + pts = self.asPolyline(tolerance2ApproxCurve, atLeastNSegment) + if pts is None or len(pts) == 0: + return None + return QgsLineString(pts) + + + # =============================================================================== + # asAbstractGeom + # =============================================================================== + def asAbstractGeom(self, wkbType = QgsWkbTypes.LineString, tolerance2ApproxCurve = None, atLeastNSegment = None): + """ + la funzione ritorna l'ellisse in forma di QgsGeometry. + """ + flatType = QgsWkbTypes.flatType(wkbType) + + if flatType == QgsWkbTypes.CompoundCurve: + linestring = self.asLineString(tolerance2ApproxCurve, atLeastNSegment) + compoundCurve = QgsCompoundCurve() + compoundCurve.addCurve(linestring) + return compoundCurve + + elif flatType == QgsWkbTypes.MultiCurve: + linestring = self.asLineString(tolerance2ApproxCurve, atLeastNSegment) + multiCurve = QgsMultiCurve() + multiCurve.addGeometry(linestring) + return multiCurve + + elif flatType == QgsWkbTypes.CurvePolygon: + linestring = self.asLineString(tolerance2ApproxCurve, atLeastNSegment) + curvePolygon = QgsCurvePolygon() + curvePolygon.setExteriorRing(linestring) + return curvePolygon + + elif flatType == QgsWkbTypes.MultiSurface: # Geometry that is combined from several CurvePolygon is called MultiSurface + curvePolygon = self.asAbstractGeom(QgsWkbTypes.CurvePolygon, tolerance2ApproxCurve, atLeastNSegment) + multiSurface = QgsMultiSurface() + multiSurface.addGeometry(curvePolygon) + return multiSurface + + elif flatType == QgsWkbTypes.Polygon: + lineString = self.asLineString(tolerance2ApproxCurve, atLeastNSegment) + polygon = QgsPolygon() + polygon.setExteriorRing(lineString) + return polygon + + elif flatType == QgsWkbTypes.MultiPolygon: + polygon = self.asAbstractGeom(QgsWkbTypes.Polygon, tolerance2ApproxCurve, atLeastNSegment) + multiPolygon = QgsMultiPolygon() + multiPolygon.addGeometry(polygon) + return multiPolygon + + elif flatType == QgsWkbTypes.MultiLineString: + lineString = self.asLineString(tolerance2ApproxCurve, atLeastNSegment) + multiLineString = QgsMultiLineString() + multiLineString.addGeometry(lineString) + return multiLineString + + return self.asLineString(tolerance2ApproxCurve, atLeastNSegment) + + + # =============================================================================== + # asGeom + # =============================================================================== + def asGeom(self, wkbType = QgsWkbTypes.LineString, tolerance2ApproxCurve = None, atLeastNSegment = None): + """ + la funzione ritorna l'ellisse in forma di QgsGeometry. + """ + return QgsGeometry(self.asAbstractGeom(wkbType, tolerance2ApproxCurve, atLeastNSegment)); + + + # ============================================================================ + # getPointAt + # ============================================================================ + def getPointAt(self, param): + """ + L'equazione parametrica per l'ellisse è x=a*cos(param), y=b*sin(param). + E' importante capire che param non è l'angolo al centro. + Ritorna un punto dell'ellisse usando l'equazione parametrica (0 = punto finale dell'asse -> majorAxisFinalPt) + n.b. La funzione non tiene conto se si tratta di un arco di ellisse + """ + axis_a = qad_utils.getDistance(self.center, self.majorAxisFinalPt) + axis_b = axis_a * self.axisRatio + rot = qad_utils.getAngleBy2Pts(self.center, self.majorAxisFinalPt) + x = self.center.x() + \ + axis_a * math.cos(param) * math.cos(rot) - \ + axis_b * math.sin(param) * math.sin(rot) + y = self.center.y() + \ + axis_a * math.cos(param) * math.sin(rot) + \ + axis_b * math.sin(param) * math.cos(rot) + + return QgsPointXY(x, y) + + + # ============================================================================ + # getPointAtAngle + # ============================================================================ + def getPointAtAngle(self, angle): + """ + Trova il punto sull'ellisse + n.b. La funzione non tiene conto se si tratta di un arco di ellisse + """ + axis_a = qad_utils.getDistance(self.center, self.majorAxisFinalPt) + axis_b = axis_a * self.axisRatio + rot = qad_utils.getAngleBy2Pts(self.center, self.majorAxisFinalPt) + + x = axis_a * math.cos(rot) + y = axis_b * math.sin(rot) + + cos_angle = math.cos(angle) + sin_angle = math.sin(angle) + + x_new = x * cos_angle - y * sin_angle + y_new = x * sin_angle + y * cos_angle + + pt = QadPoint() + pt.setX(x_new) + pt.setY(y_new) + pt.move(self.center.x(), self.center.y()) + + return pt + + + # ============================================================================ + # getNextParamPt + # ============================================================================ + def getNextParamPt(self, param1, pt1, angleStep, tolerance): + """ + La funzione cerca l'angolo (dell'equazione parametrica) successivo all'angolo param1 + e il punto successivo a p1 (p2) in modo che il segmento p1-p2 non si distacchi + oltre la tolleranza dalla curva reale dell'ellisse. + n.b. La funzione non tiene conto se si tratta di un arco di ellisse + """ + #rot = qad_utils.getAngleBy2Pts(self.center, self.majorAxisFinalPt) + param2 = param1 + angleStep + pt2 = self.getPointAt(param2) + paramMiddle = (param1 + param2) / 2 + ptMiddleSegment = qad_utils.getMiddlePoint(pt1, pt2) + ptMiddleEllipse = self.getPointAt(paramMiddle) + error = qad_utils.getDistance(ptMiddleSegment, ptMiddleEllipse) + while error > tolerance: + angleStep = angleStep / 2 + param2 = param1 + angleStep + pt2 = self.getPointAt(param2) + paramMiddle = (param1 + param2) / 2 + ptMiddleSegment = qad_utils.getMiddlePoint(pt1, pt2) + ptMiddleEllipse = self.getPointAt(paramMiddle) + error = qad_utils.getDistance(ptMiddleSegment, ptMiddleEllipse) + + return param2, pt2 + + + # ============================================================================ + # fromPolyline + # ============================================================================ + def fromPolyline(self, points, atLeastNSegment = None): + """ + setta le caratteristiche dell'ellisse incontrata nella lista di punti. + Ritorna True se é stato trovato un'ellissa altrimenti False. + N.B. in punti NON devono essere in coordinate geografiche + """ + # se il punto iniziale e quello finale non coincidono non é un'ellisse + if points[0] != points[-1]: + return False + + totPoints = len(points) - 1 # l'ultimo dovrebbe essere uguale al primo quindi non lo conto + + if atLeastNSegment is None: + _atLeastNSegment = QadVariables.get(QadMsg.translate("Environment variables", "ELLIPSEMINSEGMENTQTY"), 12) + else: + _atLeastNSegment = atLeastNSegment + + # perché sia una ellisse ci vogliono almeno _atLeastNSegment segmenti e almeno 5 punti + if (totPoints - 1) < _atLeastNSegment or totPoints < 5: + return False + + everyNPts = int(totPoints / 5) + + # sposto i 5 punti di valutazione vicino a 0,0 per migliorare la precisione dei calcoli + dx = points[0].x() + dy = points[0].y() + first5Points = [] + first5Points.append(qad_utils.movePoint(points[0], -dx, -dy)) + first5Points.append(qad_utils.movePoint(points[everyNPts], -dx, -dy)) + first5Points.append(qad_utils.movePoint(points[everyNPts * 2], -dx, -dy)) + first5Points.append(qad_utils.movePoint(points[everyNPts * 3], -dx, -dy)) + first5Points.append(qad_utils.movePoint(points[everyNPts * 4], -dx, -dy)) + + #first5Points = [QgsPointXY(20.0, 0.0), QgsPointXY(6.18034,9.51057), QgsPointXY(-16.1803,5.87785), QgsPointXY(-16.1803,-5.87785), QgsPointXY(6.18034,-9.51057)] + + # this translation is for avoiding floating point precision issues + baryC = MathTools.barycenter(first5Points) + + pointListBC = [] + for pt in first5Points : + pointListBC.append((pt[0] - baryC[0], pt[1] - baryC[1])) + # find the center and the axes of by solving the conic equation : + # ax2 + bxy + cy2 + dx + ey + f = 0 + conic = MathTools.conicEquation(pointListBC) + [a, b, c, d, e, f] = conic + # conditions for the existence of an ellipse + if MathTools.bareissDeterminant([[a, b/2, d/2], [b/2, c, e/2], [d/2, e/2, f]]) == 0 or a*c - b*b/4 <= 0: + # Could not find the ellipse passing by these five points. + return False + cX = (b*e - 2*c*d) / (4*a*c - b*b) + cY = (d*b - 2*a*e) / (4*a*c - b*b) + center = (cX, cY) + res = MathTools.ellipseAxes(conic) + if res is None: return False + axisDir1 = res[0] + axisDir2 = res[1] + axisLen1 = MathTools.ellipseAxisLen(conic, center, axisDir1) + if axisLen1 is None: return false + axisLen2 = MathTools.ellipseAxisLen(conic, center, axisDir2) + if axisLen2 is None: return false + + if axisLen1 > axisLen2: + majorDir = axisDir1 + majorLen = axisLen1 + minorLen = axisLen2 + else: + majorDir = axisDir2 + majorLen = axisLen2 + minorLen = axisLen1 + rotAngle = math.atan2(majorDir[1], majorDir[0]) + + center = QgsPointXY(center[0], center[1]) + majorAxisFinalPt = qad_utils.rotatePoint(QgsPointXY(majorLen + center[0], center[1]), center, rotAngle) + majorAxisFinalPt.setX(majorAxisFinalPt.x() + baryC[0]) + majorAxisFinalPt.setY(majorAxisFinalPt.y() + baryC[1]) + + #majorAxisFinalPt = qad_utils.getPolarPointByPtAngle(center, rotAngle, majorLen) + #if majorAxisFinalPt.x() < center.x(): + # majorAxisFinalPt = qad_utils.getPolarPointByPtAngle(center, -rotAngle, majorLen) + axisRatio = minorLen / majorLen; + + center = QgsPointXY(center[0] + baryC[0], center[1] + baryC[1]) + + testEllipse = QadEllipse() + testEllipse.set(center, majorAxisFinalPt, axisRatio) + foci = testEllipse.getFocus() + if len(foci) == 0: return False + dist1 = qad_utils.getDistance(foci[0], testEllipse.majorAxisFinalPt) + dist2 = qad_utils.getDistance(foci[1], testEllipse.majorAxisFinalPt) + distSumEllipse = dist1 + dist2 + + # per problemi di approssimazione dei calcoli + tolerance = distSumEllipse * 1.e-2 # percentuale della somma delle distanze dai fuochi + + # sposto i punti vicino a 0,0 per migliorare la precisione dei calcoli + myPoints = [] + i = 0 + while i < totPoints: + myPoints.append(qad_utils.movePoint(points[i], -dx, -dy)) + i = i + 1 + + # se il punto finale dell'arco è a sinistra del + # segmento che unisce i punti iniziale e intermedio allora il verso è antiorario + startClockWise = False if qad_utils.leftOfLine(myPoints[2], myPoints[0], myPoints[1]) < 0 else True + angle = 0 + + # verifico che i punti siano sull'ellisse + i = 0 + while i < totPoints: + dist1 = qad_utils.getDistance(foci[0], myPoints[i]) + dist2 = qad_utils.getDistance(foci[1], myPoints[i]) + distSum = dist1 + dist2 + # calcolo la somma delle distanze dai fuochi e verifico che sia abbastanza simile a quella originale + if qad_utils.doubleNear(distSumEllipse, distSum, tolerance) == False: + return False + # calcolo il verso dell'arco e l'angolo + clockWise = False if qad_utils.leftOfLine(myPoints[i], myPoints[i - 2], myPoints[i - 1]) < 0 else True + + # il verso deve essere lo stesso di quello originale + if startClockWise != clockWise: + return False + angle = angle + qad_utils.getAngleBy3Pts(myPoints[i-1], center, myPoints[i], startClockWise) + # l'angolo inscritto non può essere > di 360 + if angle < 2 * math.pi or qad_utils.doubleNear(angle, 2 * math.pi): + i = i + 1 + else: + return False + + self.center = center + self.majorAxisFinalPt = majorAxisFinalPt + self.axisRatio = axisRatio + # traslo la geometria per riportarla alla sua posizione originale + self.move(dx, dy) + + return True + + + # ============================================================================ + # move + # ============================================================================ + def move(self, offsetX, offsetY): + self.center = qad_utils.movePoint(self.center, offsetX, offsetY) + self.majorAxisFinalPt = qad_utils.movePoint(self.majorAxisFinalPt, offsetX, offsetY) + + + # ============================================================================ + # rotate + # ============================================================================ + def rotate(self, basePt, angle): + self.center = qad_utils.rotatePoint(self.center, basePt, angle) + self.majorAxisFinalPt = qad_utils.rotatePoint(self.majorAxisFinalPt, basePt, angle) + + + # ============================================================================ + # scale + # ============================================================================ + def scale(self, basePt, scale): + self.center = qad_utils.scalePoint(self.center, basePt, scale) + self.majorAxisFinalPt = qad_utils.scalePoint(self.majorAxisFinalPt, basePt, scale) + + + # ============================================================================ + # mirror + # ============================================================================ + def mirror(self, mirrorPt, mirrorAngle): + self.center = qad_utils.mirrorPoint(self.center, mirrorPt, mirrorAngle) + self.majorAxisFinalPt = qad_utils.mirrorPoint(self.center, mirrorPt, mirrorAngle) + + + # ============================================================================ + # getNextParamPtForOffset + # ============================================================================ + def getNextParamPtForOffset(self, param1, pt1Offset, angleStep, tolerance, dist): + """ + La funzione cerca l'angolo (dell'equazione parametrica) successivo all'angolo param1 + e il punto successivo a p1 (p2) in modo che il segmento p1-p2 non si distacchi + oltre la tolleranza dalla curva do offset dell'ellisse. + n.b. La funzione non tiene conto se si tratta di un arco di ellisse + """ + rot = self.getRotation() + param2 = param1 + angleStep + pt2 = self.getPointAt(param2) + pt2Offset = qad_utils.getPolarPointByPtAngle(pt2, self.getNormalAngleToAPointOnEllipse(pt2), dist) + paramMiddle = (param1 + param2) / 2 + ptMiddleSegment = qad_utils.getMiddlePoint(pt1Offset, pt2Offset) + ptMiddle = self.getPointAt(paramMiddle) + ptMiddleOffset = qad_utils.getPolarPointByPtAngle(ptMiddle, self.getNormalAngleToAPointOnEllipse(ptMiddle), dist) + error = qad_utils.getDistance(ptMiddleSegment, ptMiddleOffset) + while error > tolerance: + angleStep = angleStep / 2 + param2 = param1 + angleStep + pt2 = self.getPointAt(param2) + pt2Offset = qad_utils.getPolarPointByPtAngle(pt2, self.getNormalAngleToAPointOnEllipse(pt2), dist) + paramMiddle = (param1 + param2) / 2 + ptMiddleSegment = qad_utils.getMiddlePoint(pt1Offset, pt2Offset) + ptMiddle = self.getPointAt(paramMiddle) + ptMiddleOffset = qad_utils.getPolarPointByPtAngle(ptMiddle, self.getNormalAngleToAPointOnEllipse(ptMiddle), dist) + error = qad_utils.getDistance(ptMiddleSegment, ptMiddleOffset) + + return param2, pt2Offset + + + # =============================================================================== + # offset + # =============================================================================== + def offset(self, offsetDist, offsetSide, tolerance2ApproxCurve = None): + """ + la funzione restituisce l'ellisse facendone l'offset. + poichè l'offset di una ellisse non è una ellisse, restituisce una lista di punti o None + secondo una distanza e un lato di offset ("internal" o "external") + """ + if offsetSide == "internal": + # offset verso l'interno dell'ellisse + dist = -offsetDist + a = qad_utils.getDistance(self.center, self.majorAxisFinalPt) # semiasse maggiore + b = a * self.axisRatio # semiasse minore + if a > b: + if b <= offsetDist: return None + else: + if a <= offsetDist: return None + else: + # offset verso l'esterno dell'ellisse + dist = offsetDist + + if tolerance2ApproxCurve is None: + tolerance = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) + else: + tolerance = tolerance2ApproxCurve + + _atLeastNSegment = QadVariables.get(QadMsg.translate("Environment variables", "ELLIPSEMINSEGMENTQTY"), 12) + + param = 0 + endParam = 2 * math.pi + pt = self.getPointAt(param) + ptOffset = qad_utils.getPolarPointByPtAngle(pt, self.getNormalAngleToAPointOnEllipse(pt), dist) + + angleStep = 2 * math.pi / _atLeastNSegment + + points = [] + points.append(ptOffset) + while True: + param, ptOffset = self.getNextParamPtForOffset(param, ptOffset, angleStep, tolerance, dist) + if param > endParam: break + points.append(ptOffset) + + lastPt = self.getPointAt(endParam) + lastPtOffset = qad_utils.getPolarPointByPtAngle(lastPt, self.getNormalAngleToAPointOnEllipse(lastPt), dist) + if qad_utils.ptNear(points[-1], lastPtOffset) == False: points.append(lastPtOffset) # ultimo elemento della lista + + if points[-1] != points[0]: + points.append(QgsPointXY(points[0])) + + return points + + + # ============================================================================ + # fromFoci + # ============================================================================ + def fromFoci(self, f1, f2, ptOnEllipse): + """ + setta le caratteristiche dell'ellisse attraverso: + i due fuochi + un punto sull'ellisse + /-ptOnEllipse-\ + / \ + | f1 f2 | + \ / + \-------------/ + """ + dist_f1f2 = qad_utils.getDistance(f1, f2) + dist_f1PtOnEllipse = qad_utils.getDistance(f1, ptOnEllipse) + dist_f2PtOnEllipse = qad_utils.getDistance(f2, ptOnEllipse) + if dist_f1f2 == 0 or dist_f1PtOnEllipse == 0 or dist_f2PtOnEllipse == 0: return None + + dist_tot = dist_f1PtOnEllipse + dist_f2PtOnEllipse + angle = qad_utils.getAngleBy2Pts(f1, f2) + ptCenter = qad_utils.getMiddlePoint(f1, f2) + + majorAxisLen = dist_tot / 2.0 # semiasse maggiore + minorAxisLen = math.sqrt((dist_tot/2.0)**2.0 - (dist_f1f2/2.0)**2.0) # semiasse minore + axisRatio = minorAxisLen / majorAxisLen + majorAxisPt = qad_utils.getPolarPointByPtAngle(ptCenter, angle, majorAxisLen) + + return self.set(ptCenter, majorAxisPt, axisRatio) + + + # ============================================================================ + # fromExtent + # ============================================================================ + def fromExtent(self, pt1, pt2, rot = 0): + """ + setta le caratteristiche dell'ellisse attraverso: + i due punti di estensione (angoli opposti) del rettangolo che racchiude l'ellisse + rotazione del rettangolo di estensione + /-------------\ pt2 + / \ + | | + \ / + pt1 \-------------/ + """ + ptCenter = qad_utils.getMiddlePoint(pt1, pt2) + halfDist = qad_utils.getDistance(pt1, pt2) / 2 + if halfDist == 0: return None + angle = qad_utils.getAngleBy2Pts(pt1, pt2) + angle = angle - rot + projPt1 = qad_utils.getPolarPointByPtAngle(pt1, angle, halfDist) + projPt2 = qad_utils.getPolarPointByPtAngle(pt1, angle, halfDist) + + majorAxisLen = abs(projPt1.x() - projPt2.x()) / 2.0 # semiasse maggiore + minorAxisLen = abs(projPt1.y() - projPt2.y()) / 2.0 # semiasse minore + axisRatio = minorAxisLen / majorAxisLen + majorAxisPt = qad_utils.getPolarPointByPtAngle(ptCenter, rot, majorAxisLen) + + return self.set(ptCenter, majorAxisPt, axisRatio) + + + # ============================================================================ + # fromCenterAxis1FinalPtAxis2FinalPt + # ============================================================================ + def fromCenterAxis1FinalPtAxis2FinalPt(self, ptCenter, axis1FinalPt, axis2FinalPt): + """ + setta le caratteristiche dell'ellisse attraverso: + il punto centrale + il punto finale dell'asse + il punto finale dell'altro asse + /--axis2FinalPt--\ + / \ + | ptCenter axis1FinalPt + \ / + \----------------/ + """ + distAxis2 = qad_utils.getDistance(ptCenter, axis2FinalPt) + return fromCenterAxis1FinalPtDistAxis2(cls, ptCenter, axis1FinalPt, distAxis2) + + + # ============================================================================ + # fromCenterAxis1FinalPtDistAxis2 + # ============================================================================ + def fromCenterAxis1FinalPtDistAxis2(self, ptCenter, axis1FinalPt, distAxis2): + """ + setta le caratteristiche dell'ellisse attraverso: + il punto centrale + il punto finale dell'asse + distanza dal centro al punto finale dell'altro asse + /-------|--------\ + / distAxis2 \ + | ptCenter axis1FinalPt + \ / + \----------------/ + """ + axis1Len = qad_utils.getDistance(ptCenter, axis1FinalPt) + if axis1Len == 0 or distAxis2 == 0: return None + axisRatio = axis1Len / distAxis2 + return self.set(ptCenter, axis1FinalPt, axisRatio) + + + # ============================================================================ + # fromCenterAxis1FinalPtAxis2FinalPt + # ============================================================================ + def fromCenterAxis1FinalPtAxis2FinalPt(self, axis1Finalpt1, axis1Finalpt2, axis2FinalPt): + """ + setta le caratteristiche dell'ellisse attraverso: + i punti finali dell'asse + il punto finale dell'altro asse + /--axis2FinalPt--\ + / \ + axis1Finalpt2 axis1Finalpt1 + \ / + \----------------/ + """ + ptCenter = qad_utils.getMiddlePoint(xis1FinalPt1, axis1FinalPt2) + axis2Len = qad_utils.getDistance(ptCenter, axis2FinalPt) + return self.fromCenterAxis1FinalPtDistAxis2(ptCenter, axis1FinalPt, axis2Len) + + + # ============================================================================ + # fromAxis1FinalPtsAxis2Len + # ============================================================================ + def fromAxis1FinalPtsAxis2Len(self, axis1FinalPt1, axis1FinalPt2, distAxis2): + """ + setta le caratteristiche dell'ellisse attraverso: + i punti finali dell'asse + distanza dal centro al punto finale dell'altro asse + /------|-------\ + / distAxis2 \ + axis1pt2 | axis1pt1 + \ / + \--------------/ + """ + if distAxis2 == 0: return None + ptCenter = qad_utils.getMiddlePoint(axis1FinalPt1, axis1FinalPt2) + dist = qad_utils.getDistance(axis1FinalPt1, axis1FinalPt2) + if dist == 0: return None + axisRatio = (2 * distAxis2) / dist + return self.set(ptCenter, axis1FinalPt1, axisRatio) + + + # ============================================================================ + # fromAxis1FinalPtsArea + # ============================================================================ + def fromAxis1FinalPtsArea(self, axis1FinalPt1, axis1FinalPt2, area): + """ + setta le caratteristiche dell'ellisse attraverso: + i punti finali dell'asse + area dell'ellisse + /--------------\ + / \ + axis1pt2 axis1pt1 + \ / + \--------------/ + """ + if area == 0: return None + ptCenter = qad_utils.getMiddlePoint(axis1FinalPt1, axis1FinalPt2) + dist = qad_utils.getDistance(axis1FinalPt1, axis1FinalPt2) / 2 + if dist == 0: return None + b = area / (math.pi * dist) + return self.fromAxis1FinalPtsAxis2Len(axis1FinalPt1, axis1FinalPt2, b) + + + +# adopted from ArcheEngine.py from ArchoCAD plugin +class MathTools(object): + """Contains static methods that are used for creating ellipses.""" + + # adopted from Inkscape's "Ellipse by 5 Points Extension" + # Copyright (c) 2012 Stuart Pernsteiner + # Algorithm from: + # Yap, Chee, "Linear Systems", Fundamental Problems of Algorithmic Algebra + # http://cs.nyu.edu/~yap/book/alge/ftpSite/l10.ps.gz + @staticmethod + def bareissDeterminant(inMatrix): + """Computes the determinant of the matrix using Bareiss algorithm.""" + + matrix = deepcopy(inMatrix) + size = len(matrix) + lastAkk = 1 + for k in range(size - 1): + if lastAkk == 0: + return 0 + for i in range(k + 1, size): + for j in range(k + 1, size): + matrix[i][j] = (matrix[i][j]*matrix[k][k] - matrix[i][k]*matrix[k][j])/lastAkk + lastAkk = matrix[k][k] + return matrix[size - 1][size - 1] + + # adopted from Inkscape's "Ellipse by 5 Points Extension" + # Copyright (c) 2012 Stuart Pernsteiner + # developed using : + # http://math.fullerton.edu/mathews/n2003/conicfit/ConicFitMod/Links/ConicFitMod_lnk_9.html + @staticmethod + def conicEquation(points): + """Computes the equation of the conic section passing through five given points.""" + + rowMajorMatrix = [] + for i in range(5): + (x, y) = points[i] + row = [x*x, x*y, y*y, x, y, 1] + rowMajorMatrix.append(row) + fullMatrix = [] + for i in range(6): + col = [] + for j in range(5): + col.append(rowMajorMatrix[j][i]) + fullMatrix.append(col) + coeffs = [] + sign = 1 + for i in range(6): + matrix = [] + for j in range(6): + if j == i: + continue + matrix.append(fullMatrix[j]) + coeffs.append(MathTools.bareissDeterminant(matrix)*sign) + sign = -sign + return coeffs + + # adopted from Inkscape's "Ellipse by 5 Points Extension" + # Copyright (c) 2012 Stuart Pernsteiner + @staticmethod + def ellipseAxes(conic): + """Compute the axis directions of the ellipse.""" + + [a, b, c, d, e, f] = conic + # Compute the eigenvalues of + # / a b/2 \ + # \ b/2 c / + # This algorithm is from + # http://www.math.harvard.edu/archive/21b_fall_04/exhibits/2dmatrices/index.html + ma = a + mb = b/2 + mc = b/2 + md = c + mDet = ma*md - mb*mc + mTrace = ma + md + + res = MathTools.solveQuadratic(1, -mTrace, mDet); + if res is None: return None + l1 = res[0] + l2 = res[1] + + if mb == 0: + return [(0, 1), (1, 0)] + else: + return [(mb, l1 - ma), (mb, l2 - ma)] + + # adopted from Inkscape's "Ellipse by 5 Points Extension" + # Copyright (c) 2012 Stuart Pernsteiner + @staticmethod + def ellipseAxisLen(conic, center, direction): + """ Compute the axis length as a multiple of the magnitude of 'direction'""" + + [a, b, c, d, e, f] = conic + (cx, cy) = center + (dx, dy) = direction + + dLen = math.sqrt(dx*dx + dy*dy) + dx /= dLen + dy /= dLen + + # Solve for t: + # a*x^2 + b*x*y + c*y^2 + d*x + e*y + f = 0 + # x = cx + t * dx + # y = cy + t * dy + # by substituting, we get qa*t^2 + qb*t + qc = 0, where: + qa = a*dx*dx + b*dx*dy + c*dy*dy + qb = a*2*cx*dx + b*(cx*dy + cy*dx) + c*2*cy*dy + d*dx + e*dy + qc = a*cx*cx + b*cx*cy + c*cy*cy + d*cx + e*cy + f + res = MathTools.solveQuadratic(qa, qb, qc) + if res is None: return None + t1 = res[0] + t2 = res[1] + + return max(t1, t2) + + @staticmethod + def solveQuadratic(a, b, c): + + if (b*b - 4*a*c) < 0: + return None + discRoot = math.sqrt(b*b - 4*a*c) + x1 = (-b + discRoot) / (2*a) + x2 = (-b - discRoot) / (2*a) + return [x1, x2] + + @staticmethod + def rotation(p, rotAngle, c): + # translation + xT = p[0] - c[0] + yT = p[1] - c[1] + # rotation over the origin + xR = xT*math.cos(rotAngle) - yT*math.sin(rotAngle) + yR = xT*math.sin(rotAngle) + yT*math.cos(rotAngle) + # translation back + newX = xR + c[0] + newY = yR + c[1] + return (newX, newY) + + @staticmethod + def barycenter(points): + + nPts = len(points) + sumX = 0 + sumY = 0 + for pt in points : + sumX += pt.x() + sumY += pt.y() + return (sumX/nPts, sumY/nPts) diff --git a/qad_ellipse_arc.py b/qad_ellipse_arc.py new file mode 100644 index 00000000..d5db36f7 --- /dev/null +++ b/qad_ellipse_arc.py @@ -0,0 +1,971 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + classe per la gestione degli archi di ellisse + + ------------------- + begin : 2019-02-18 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from qgis.PyQt.QtCore import * +from qgis.PyQt.QtGui import * +from qgis.core import * +from qgis.gui import * + +import math + +from . import qad_utils +from .qad_ellipse import QadEllipse, MathTools +from .qad_variables import QadVariables +from .qad_msg import QadMsg + + +# =============================================================================== +# QadEllipseArc arc of ellipse class +# =============================================================================== +class QadEllipseArc(QadEllipse): + + def __init__(self, ellipseArc = None): + if ellipseArc is not None: + self.set(ellipseArc.center, ellipseArc.majorAxisFinalPt, ellipseArc.axisRatio, ellipseArc.startAngle, ellipseArc.endAngle, ellipseArc.reversed) + else: + self.center = None + self.majorAxisFinalPt = None # punto finale dell'asse maggiore (a dx) + self.axisRatio = 0 # rapporto tra asse minore e asse maggiore + self.startAngle = None # angolo iniziale rispetto l'asse che va dal centro a majorAxisFinalPt + self.endAngle = None # angolo finale rispetto l'asse che va dal centro a majorAxisFinalPt + # if reversed is True the versus of the arc of ellipse is from endAngle to startAngle + self.reversed = None + + def whatIs(self): + # obbligatoria + return "ELLIPSE_ARC" + + + # ============================================================================ + # isClosed + # ============================================================================ + def isClosed(self): + return False + + + def set(self, center, majorAxisFinalPt = None, axisRatio = None, startAngle = None, endAngle = None, reversed=False): + if isinstance(center, QadEllipseArc): + ellipseArc = center + return self.set(ellipseArc.center, ellipseArc.majorAxisFinalPt, ellipseArc.axisRatio, ellipseArc.startAngle, ellipseArc.endAngle, ellipseArc.reversed) + + if center == majorAxisFinalPt: return None + self.center = QgsPointXY(center) + self.majorAxisFinalPt = QgsPointXY(majorAxisFinalPt) + self.axisRatio = axisRatio + self.reversed = reversed + if self.setArc(startAngle, endAngle) == False: return None + return self + + + def setArc(self, startAngle, endAngle): + # set controllato degli angoli per inizializzare l'arco di ellisse + _startAngle = qad_utils.normalizeAngle(startAngle) + _endAngle = qad_utils.normalizeAngle(endAngle) + if _startAngle == _endAngle: return False # ellisse completa + self.startAngle = _startAngle + self.endAngle = _endAngle + + + def __eq__(self, ellipseArc): + # obbligatoria + """self == other""" + if ellipseArc.whatIs() != "ELLIPSE_ARC": return False + if self.center != ellipseArc.center or self.majorAxisFinalPt != ellipseArc.majorAxisFinalPt or self.axisRatio != ellipseArc.axisRatio or \ + self.startAngle != ellipseArc.startAngle or self.endAngle != ellipseArc.endAngle: + return False + return True + + + def __ne__(self, ellipseArc): + """self != other""" + return not self.__eq__(ellipseArc) + + + def equals(self, ellipseArc): + # uguali geometricamente (NON conta il verso) + return self.__eq__(ellipseArc) + + + def copy(self): + # obbligatoria + return QadEllipseArc(self) + + + # =============================================================================== + # length + # =============================================================================== + def length(self): + # obbligatoria + # temporaneamente approssimo segmentando l'arco... + pts = self.asPolyline() + arcLen = 0 + i = 0 + while i < len(pts) - 1: + arcLen = arcLen + qad_utils.getDistance(pts[i], pts[i + 1]) + i = i + 1 + return arcLen + # da fare + a = qad_utils.getDistance(self.center, self.majorAxisFinalPt) # semiasse maggiore + b = a * self.axisRatio # semiasse minore + return 0 + + + # ============================================================================ + # reverse + # ============================================================================ + def reverse(self): + # inverto direzione dell'arco di ellisse (punto iniziale-finale) + self.reversed = not self.reversed + return self + + # ============================================================================ + # inverseAngles + # ============================================================================ + def inverseAngles(self): + # inverto angolo iniziale-finale + dummy = self.endAngle + self.endAngle = self.startAngle + self.startAngle = dummy + # per mantenere lo stesso punto iniziale inverto la direzione dei punti iniziale-finale + self.reverse() + + + # ============================================================================ + # getStartPt, setStartPt + # ============================================================================ + def getStartPt(self, usingReversedFlag = True): + # obbligatoria + # usingReversedFlag è usato per sapere il punto iniziale nel caso l'arco abbia una direzione (nella polyline) + # ritorna il punto iniziale + if usingReversedFlag: + param = self.getParamFromAngle(self.endAngle if self.reversed else self.startAngle) + else: + param = self.getParamFromAngle(self.startAngle) + return self.getPointAt(param) + + def setStartPt(self, pt): + # obbligatoria + return self.setStartAngleByPt(pt) + + + def setStartAngleByPt(self, pt): + # da usare per modificare un arco di ellisse già definito + angle = qad_utils.getAngleBy3Pts(self.majorAxisFinalPt, self.center, pt, False) + if self.reversed: + if angle == self.startAngle: return False + self.endAngle = angle + else: + if angle == self.endAngle: return False + self.startAngle = angle + return True + + + # ============================================================================ + # getEndPt, setEndPt + # ============================================================================ + def getEndPt(self, usingReversedFlag = True): + # obbligatoria + # usingReversedFlag è usato per sapere il punto iniziale nel caso l'arco abbia una direzione (nella polyline) + # ritorna il punto finale + if usingReversedFlag: + param = self.getParamFromAngle(self.startAngle if self.reversed else self.endAngle) + else: + param = self.getParamFromAngle(self.endAngle) + return self.getPointAt(param) + + def setEndPt(self, pt): + # obbligatoria + return self.setEndAngleByPt(pt) + + + def setEndAngleByPt(self, pt): + # da usare per modificare un arco di ellisse già definito + angle = qad_utils.getAngleBy3Pts(self.majorAxisFinalPt, self.center, pt, False) + if self.reversed: + if angle == self.endAngle: return False + self.startAngle = angle + else: + if angle == self.startAngle: return False + self.endAngle = angle + + return True + + + # ============================================================================ + # isPtOnEllipseArcOnlyByAngle + # ============================================================================ + def isPtOnEllipseArcOnlyByAngle(self, point): + # la funzione valuta se un punto è sull'arco di ellisse considerando solo gli angoli iniziale/finale + angle = qad_utils.getAngleBy3Pts(self.majorAxisFinalPt, self.center, point, False) + return qad_utils.isAngleBetweenAngles(self.startAngle, self.endAngle, angle) + + + # ============================================================================ + # containsPt + # ============================================================================ + def containsPt(self, point): + # obbligatoria + """ + la funzione ritorna true se il punto é sull'arco di ellisse (estremi compresi). + point è di tipo QgsPointXY. + """ + if self.whereIsPt(point) == 0: # -1 interno, 0 sull'ellisse, 1 esterno: + return self.isPtOnEllipseArcOnlyByAngle(point) + return False + + + # ============================================================================ + # getDistanceFromStart + # ============================================================================ + def getDistanceFromStart(self, pt): + # obbligatoria + """ + la funzione restituisce la distanza di (che deve essere sull'oggetto o sua estensione) + dal punto iniziale. + """ + if qad_utils.ptNear(pt, self.getStartPt()): return 0.0 + dummy = QadEllipseArc(self) + dummy.setEndPt(pt) + return dummy.length() + + + # ============================================================================ + # getPointFromStart + # ============================================================================ + def getPointFromStart(self, distance): + # obbligatoria + """ + la funzione restituisce un punto (e la direzione della tangente) alla distanza + (che deve essere sull'oggetto) dal punto iniziale. + """ + # temporaneamente segmento l'arco di ellisse... + from .qad_line import QadLine + if distance < 0: + return None, None + pts = self.asPolyline() + d = distance + i = 0 + while i < len(pts) - 1: + linearObject = QadLine().set(pts[i], pts[i + 1]) + l = linearObject.length() + if d > l: + d = d - l + i = i + 1 + else: + return linearObject.getPointFromStart(d) + return None, None + + # da fare + + if distance < 0: + return None, None + l = self.length() + if distance > l: + return None, None + + + # ============================================================================ + # getDistanceFromEnd + # ============================================================================ + def getDistanceFromEnd(self, pt): + # obbligatoria + """ + la funzione restituisce la distanza di (che deve essere sull'oggetto o sua estensione) + dal punto finale. + """ + return self.length() - self.getDistanceFromStart() + + + # =============================================================================== + # getPointFromEnd + # =============================================================================== + def getPointFromEnd(self, distance): + """ + la funzione restituisce un punto (e la direzione della tangente) alla distanza + (che deve essere sull'oggetto) dal punto finale. + """ + d = self.length() - distance + return self.getPointFromStart(d) + + + # ============================================================================ + # lengthen_delta + # ============================================================================ + def lengthen_delta(self, move_startPt, delta): + # obbligatoria + """ + la funzione sposta il punto iniziale (se move_startPt = True) o finale (se move_startPt = False) + di una distanza delta + """ + length = self.length() + ellipse = QadEllipse().set(self.center, self.majorAxisFinalPt, self.axisRatio) + # lunghezza arco di ellisse + delta non può essere >= alla lunghezza dell'ellisse + if length + delta >= ellipse.length() or length + delta <= 0: + return False + + dummy = self.copy() + + if move_startPt == True: + dummy.reverse() + if dummy.lengthen_delta(False, delta) == False: return False + self.setStartPt(dummy.getEndPt()) + else: + if self.reversed: + dummy.setArc(qad_utils.normalizeAngle(self.endAngle+0.001), self.endAngle) + else: + dummy.setArc(self.startAngle, qad_utils.normalizeAngle(self.startAngle-0.001)) + + distFromStart = length + delta + pt, angle = dummy.getPointFromStart(distFromStart) + if pt is not None: + self.setEndPt(pt) + return True + + # da fare + return False + + + # =============================================================================== + # getBoundingBox + # =============================================================================== + def getBoundingBox(self): + """ + la funzione ritorna il rettangolo che racchiude l'arco di ellisse. + """ + ellipseBoundingBox = QadEllipse.getBoundingBox(self) + # da fare + return ellipseBoundingBox + + + # ============================================================================ + # getQuadrantPoints + # ============================================================================ + def getQuadrantPoints(self): + Pts = QadEllipse.getQuadrantPoints(self) + # annullo i punti fuori dall'arco di ellisse ma restituisco una lista 4 per + # sapere a cosa corrisponde ciascun punto quadrante + if self.isPtOnEllipseArcOnlyByAngle(Pts[3]) == False: Pts[3] = None + if self.isPtOnEllipseArcOnlyByAngle(Pts[2]) == False: Pts[2] = None + if self.isPtOnEllipseArcOnlyByAngle(Pts[1]) == False: Pts[1] = None + if self.isPtOnEllipseArcOnlyByAngle(Pts[0]) == False: Pts[0] = None + + return Pts + + + # =============================================================================== + # getMiddleParam + # =============================================================================== + def getMiddleParam(self): + return self.getParamFromAngle(qad_utils.getMiddleAngle(self.startAngle, self.endAngle)) + + + # =============================================================================== + # getMiddlePoint + # =============================================================================== + def getMiddlePt(self): + return self.getPointAt(self.getMiddleParam()) + + + # ============================================================================ + # getTanDirectionOnStartPt, getTanDirectionOnEndPt, getTanDirectionOnMiddlePt + # ============================================================================ + # ============================================================================ + # getTanDirectionOnStartPt, getTanDirectionOnEndPt, getTanDirectionOnMiddlePt + # ============================================================================ + def getTanDirectionOnStartPt(self): + # obbligatoria + """ + la funzione ritorna la direzione della tangente al punto iniziale dell'oggetto. + """ + result = self.getTanDirectionOnPt(self.getStartPt()) + if self.reversed: + result = result + math.pi + return result + + def getTanDirectionOnEndPt(self): + # obbligatoria + """ + la funzione ritorna la direzione della tangente al punto finale dell'oggetto. + """ + result = self.getTanDirectionOnPt(self.getEndPt()) + if self.reversed: + result = result + math.pi + return result + + def getTanDirectionOnMiddlePt(self): + # obbligatoria + """ + la funzione ritorna la direzione della tangente al punto medio dell'oggetto. + """ + result = self.getTanDirectionOnPt(self.getMiddlePt()) + if self.reversed: + result = result + math.pi + return result + + + # =============================================================================== + # leftOf + # =============================================================================== + def leftOf(self, pt): + # obbligatoria + """ + la funzione ritorna una numero < 0 se il punto pt é alla sinistra dell'arco di ellisse ptStart -> ptEnd + """ + whereIs = self.whereIsPt(pt) # ritorna -1 se il punto è interno, 0 se è sull'ellisse, 1 se è esterno + + if self.whereIsPt(pt) == 1: # ritorna -1 se il punto è interno, 0 se è sull'ellisse, 1 se è esterno + # esterno all'arco + if self.reversed: # l'arco é in senso inverso + return -1 # a sinistra + else: + return 1 # a destra + else: + # interno all'arco + if self.reversed: # l'arco é in senso inverso + return 1 # a destra + else: + return -1 # a sinistra + + + # ============================================================================ + # asPolyline + # ============================================================================ + def asPolyline(self, tolerance2ApproxCurve = None, atLeastNSegment = None): + """ + ritorna una lista di punti che definisce la tangente + """ + if tolerance2ApproxCurve is None: + tolerance = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) + else: + tolerance = tolerance2ApproxCurve + + if atLeastNSegment is None: + _atLeastNSegment = QadVariables.get(QadMsg.translate("Environment variables", "ELLIPSEARCMINSEGMENTQTY"), 12) + else: + _atLeastNSegment = atLeastNSegment + + param = self.getParamFromAngle(self.startAngle) + endParam = self.getParamFromAngle(self.endAngle) + if param > endParam: + param = param - (2 * math.pi) + pt = self.getPointAt(param) + + angle = qad_utils.getAngleBy3Pts(self.getStartPt(False), self.center, self.getEndPt(False), False) + if angle == 0: return None + angleStep = angle / _atLeastNSegment + + points = [] + points.append(pt) + while True: + param, pt = self.getNextParamPt(param, pt, angleStep, tolerance) + if param > endParam: break + points.append(pt) + + lastPt = self.getPointAt(endParam) + + if points[-1] != lastPt: # se l'ultimo punto non coincide con il punto terminale dell'arco di ellisse + if qad_utils.ptNear(points[-1], lastPt): # se l'ultimo punto è abbastanza vicino al punto terminale dell'arco di ellisse + points[-1].set(lastPt.x(), lastPt.y()) # sposto l'ultimo punto e lo faccio coincidere con il punto terminale dell'arco di ellisse + else: + points.append(QgsPointXY(lastPt)) # aggiungo l'ultimo punto coincidente al punto terminale dell'arco di ellisse + + if self.reversed: points.reverse() + return points + + + # =============================================================================== + # asLineString + # =============================================================================== + def asLineString(self, tolerance2ApproxCurve = None, atLeastNSegment = None, forcedStartPt = None): + """ + la funzione ritorna l'ellisse in forma di lineString. + Quando l'arco di ellisse fa parte di una polilinea è necessario che il suo punto iniziale coincida con quello finale della parte precedente + percui il punto iniziale viene forzato. + """ + pts = self.asPolyline(tolerance2ApproxCurve, atLeastNSegment) + if pts is None or len(pts) == 0: + return None + if forcedStartPt is not None: + pts[0] = QgsPointXY(forcedStartPt) + return QgsLineString(pts) + + + # =============================================================================== + # asAbstractGeom + # =============================================================================== + def asAbstractGeom(self, wkbType = QgsWkbTypes.LineString, tolerance2ApproxCurve = None, atLeastNSegment = None): + """ + la funzione ritorna l'ellisse in forma di QgsAbstractGeometry. + """ + flatType = QgsWkbTypes.flatType(wkbType) + + if flatType == QgsWkbTypes.CompoundCurve: + linestring = self.asLineString(tolerance2ApproxCurve, atLeastNSegment) + compoundCurve = QgsCompoundCurve() + compoundCurve.addCurve(linestring) + return compoundCurve + + elif flatType == QgsWkbTypes.MultiCurve: + linestring = self.asLineString(tolerance2ApproxCurve, atLeastNSegment) + multiCurve = QgsMultiCurve() + multiCurve.addGeometry(linestring) + return multiCurve + + elif flatType == QgsWkbTypes.MultiLineString: + lineString = self.asLineString(tolerance2ApproxCurve, atLeastNSegment) + multiLineString = QgsMultiLineString() + multiLineString.addGeometry(lineString) + return multiLineString + + return self.asLineString(tolerance2ApproxCurve, atLeastNSegment) + + + # =============================================================================== + # asGeom + # =============================================================================== + def asGeom(self, wkbType = QgsWkbTypes.LineString, tolerance2ApproxCurve = None, atLeastNSegment = None): + """ + la funzione ritorna l'ellisse in forma di QgsGeometry. + """ + return QgsGeometry(self.asAbstractGeom(wkbType, tolerance2ApproxCurve, atLeastNSegment)); + + + # ============================================================================ + # fromPolyline + # ============================================================================ + def fromPolyline(self, points, startVertex, atLeastNSegment = None): + """ + Setta le caratteristiche dell'arco di ellisse incontrato nella lista di punti + partendo dalla posizione startVertex (0-indexed). + Ritorna la posizione nella lista del punto finale se é stato trovato un arco di ellisse + altrimenti None + N.B. i punti NON devono essere in coordinate geografiche + """ + # se il punto iniziale e quello finale coincidono non é un arco di ellisse + if points[startVertex] == points[-1]: return None + + if atLeastNSegment is None: + _atLeastNSegment = QadVariables.get(QadMsg.translate("Environment variables", "ELLIPSEARCMINSEGMENTQTY"), 12) + else: + _atLeastNSegment = atLeastNSegment + + totPoints = len(points) - startVertex + nSegment = totPoints - 1 + # perché sia un arco ci vogliono almeno _atLeastNSegment segmenti e almeno 5 punti + if nSegment < _atLeastNSegment or totPoints < 5: + return None + + everyNPts = int(max(_atLeastNSegment, 5) / 5) + + # sposto i 5 punti di valutazione vicino a 0,0 per migliorare la precisione dei calcoli + dx = points[startVertex].x() + dy = points[startVertex].y() + first5Points = [] + first5Points.append(qad_utils.movePoint(points[startVertex], -dx, -dy)) + first5Points.append(qad_utils.movePoint(points[startVertex + everyNPts], -dx, -dy)) + first5Points.append(qad_utils.movePoint(points[startVertex + everyNPts * 2], -dx, -dy)) + first5Points.append(qad_utils.movePoint(points[startVertex + everyNPts * 3], -dx, -dy)) + first5Points.append(qad_utils.movePoint(points[startVertex + everyNPts * 4], -dx, -dy)) + + # this translation is for avoiding floating point precision issues + baryC = MathTools.barycenter(first5Points) + + pointListBC = [] + for pt in first5Points : + pointListBC.append((pt[0] - baryC[0], pt[1] - baryC[1])) + # find the center and the axes of by solving the conic equation : + # ax2 + bxy + cy2 + dx + ey + f = 0 + conic = MathTools.conicEquation(pointListBC) + [a, b, c, d, e, f] = conic + # conditions for the existence of an ellipse + if MathTools.bareissDeterminant([[a, b/2, d/2], [b/2, c, e/2], [d/2, e/2, f]]) == 0 or a*c - b*b/4 <= 0: + # Could not find the ellipse passing by these five points. + return None + cX = (b*e - 2*c*d) / (4*a*c - b*b) + cY = (d*b - 2*a*e) / (4*a*c - b*b) + center = (cX, cY) + res = MathTools.ellipseAxes(conic) + if res is None: return None + axisDir1 = res[0] + axisDir2 = res[1] + axisLen1 = MathTools.ellipseAxisLen(conic, center, axisDir1) + if axisLen1 is None: return None + axisLen2 = MathTools.ellipseAxisLen(conic, center, axisDir2) + if axisLen2 is None: return None + + if axisLen1 > axisLen2: + majorDir = axisDir1 + majorLen = axisLen1 + minorLen = axisLen2 + else: + majorDir = axisDir2 + majorLen = axisLen2 + minorLen = axisLen1 + rotAngle = math.atan2(majorDir[1], majorDir[0]) + + center = QgsPointXY(center[0], center[1]) + majorAxisFinalPt = qad_utils.rotatePoint(QgsPointXY(majorLen + center[0], center[1]), center, rotAngle) + majorAxisFinalPt.setX(majorAxisFinalPt.x() + baryC[0]) + majorAxisFinalPt.setY(majorAxisFinalPt.y() + baryC[1]) + + axisRatio = minorLen / majorLen; + + center = QgsPointXY(center[0] + baryC[0], center[1] + baryC[1]) + + testEllipse = QadEllipse() + if testEllipse.set(center, majorAxisFinalPt, axisRatio) is None: + return None + foci = testEllipse.getFocus() + if len(foci) == 0: return None + + myPoints = [] + # sposto i punti vicino a 0,0 per migliorare la precisione dei calcoli + i = startVertex + while i < len(points): + myPoints.append(qad_utils.movePoint(points[i], -dx, -dy)) + i = i + 1 + + # se il punto medio della linea (points[1]) è a sinistra del + # segmento che unisce i punti iniziale (points[0]) e finale (points[2]) allora il verso è orario + startClockWise = False if qad_utils.leftOfLine(myPoints[2], myPoints[0], myPoints[1]) < 0 else True + angle = 0 + + # uso la distanza TOLERANCE2COINCIDENT / 2 perchè in una polilinea se ci sono 2 archi di ellisse consecutivi + # si vuole essere sicuri che il punto finale del primo arco di ellisse sia distante dal punto iniziale del + # secondo arco di ellisse non più di TOLERANCE2COINCIDENT perchè siano considerato 2 punti coincidenti + myTolerance = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2COINCIDENT")) / 2 + + # verifico che i punti siano sull'ellisse e mi fermo al primo punto fuori da essa + i = 0 + while i < totPoints: + # se TOLERANCE2COINCIDENT = 0,001 viene riconosciuto un arco di 1000 m + # se il punto calcolato non è abbastanza vicino al punto reale + # altrimenti trovo problemi con le intersezioni con gli oggetti + relativeAngle = qad_utils.getAngleBy2Pts(center, myPoints[i]) - testEllipse.getRotation() + if qad_utils.ptNear(testEllipse.getPointAt(testEllipse.getParamFromAngle(relativeAngle)), \ + myPoints[i], myTolerance) == False: + break + + # calcolo il verso dell'arco e l'angolo + if i < 2: + clockWise = False if qad_utils.leftOfLine(myPoints[i], myPoints[i + 1], myPoints[i + 2]) < 0 else True + else: + clockWise = False if qad_utils.leftOfLine(myPoints[i], myPoints[i - 2], myPoints[i - 1]) < 0 else True + # il verso deve essere lo stesso di quello originale + if startClockWise != clockWise: + break + + if i > 0: # salto il primo punto + angle = angle + qad_utils.getAngleBy3Pts(myPoints[i-1], center, myPoints[i], startClockWise) + # l'angolo incritto non può essere >= di 360 + if angle >= 2 * math.pi: + break + i = i + 1 + + # se non sono stati trovati un numero sufficiente di segmenti successivi + i = i - 1 # ultimo punto valido dell'arco + if i < _atLeastNSegment: return None + + self.center = center + self.majorAxisFinalPt = majorAxisFinalPt + self.axisRatio = axisRatio + + if startClockWise: # se è in senso orario + self.endAngle = qad_utils.getAngleBy3Pts(self.majorAxisFinalPt, self.center, myPoints[0], False) + self.startAngle = qad_utils.getAngleBy3Pts(self.majorAxisFinalPt, self.center, myPoints[i], False) + self.reversed = True + else: + self.startAngle = qad_utils.getAngleBy3Pts(self.majorAxisFinalPt, self.center, myPoints[0], False) + self.endAngle = qad_utils.getAngleBy3Pts(self.majorAxisFinalPt, self.center, myPoints[i], False) + self.reversed = False + + # traslo la geometria per riportarla alla sua posizione originale + self.move(dx, dy) + + return i + startVertex + + + # =============================================================================== + # breakOnPts + # =============================================================================== + def breakOnPts(selfs, firstPt, secondPt): + # obbligatoria + """ + la funzione spezza la geometria in un punto (se = None) o in due punti + come fa il trim. Ritorna una o due geometrie risultanti dall'operazione. + = primo punto di divisione + = secondo punto di divisione + """ + angle = qad_utils.getAngleBy3Pts(self.majorAxisFinalPt, self.center, firstPt, False) + param = self.getParamFromAngle(angle) + myFirstPt = self.getPointAt(param) + + mySecondPt = None + if secondPt is not None: + angle = qad_utils.getAngleBy3Pts(self.majorAxisFinalPt, self.center, secondPt, False) + param = self.getParamFromAngle(angle) + mySecondPt = self.getPointAt(param) + + part1 = self.getGeomBetween2Pts(self.getStartPt(), myFirstPt) + if mySecondPt is None: + part2 = self.getGeomBetween2Pts(myFirstPt, self.getEndPt()) + else: + part2 = self.getGeomBetween2Pts(mySecondPt, self.getEndPt()) + + return [part1, part2] + + + # ============================================================================ + # mirror + # ============================================================================ + def mirror(self, mirrorPt, mirrorAngle): + QadEllipse.mirror(self, mirrorPt, mirrorAngle) + self.startAngle = (2 * math.pi) - self.startAngle + self.endAngle = (2 * math.pi) - self.endAngle + + + # =============================================================================== + # offset + # =============================================================================== + def offset(self, offsetDist, offsetSide, tolerance2ApproxCurve = None): + """ + la funzione restituisce l'arco di ellisse facendone l'offset. + poichè l'offset di un arco di ellisse non è un arco di ellisse, restituisce una lista di punti o None + secondo una distanza e un lato di offset ("right" o "left" o "internal" o "external") + """ + side = "" + if offsetSide == "right": + if self.reversed: # direzione oraria + side = "internal" # offset verso l'interno dell'ellisse + else: + side = "external" # offset verso l'esterno dell'ellisse + elif offsetSide == "left": + if self.reversed: # direzione oraria + side = "external" # offset verso l'esterno dell'ellisse + else: + side = "internal" # offset verso l'interno dell'ellisse + else: + side = offsetSide + + if side == "internal": # offset verso l'interno dell'ellisse + dist = -offsetDist + a = qad_utils.getDistance(self.center, self.majorAxisFinalPt) # semiasse maggiore + b = a * self.axisRatio # semiasse minore + if a > b: + if b <= offsetDist: return None + else: + if a <= offsetDist: return None + elif side == "external": # offset verso l'esterno dell'ellisse + dist = offsetDist + + if tolerance2ApproxCurve is None: + tolerance = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) + else: + tolerance = tolerance2ApproxCurve + + _atLeastNSegment = QadVariables.get(QadMsg.translate("Environment variables", "ELLIPSEARCMINSEGMENTQTY"), 12) + + param = self.getParamFromAngle(self.startAngle) + endParam = self.getParamFromAngle(self.endAngle) + if param > endParam: + param = param - (2 * math.pi) + pt = self.getPointAt(param) + ptOffset = qad_utils.getPolarPointByPtAngle(pt, self.getNormalAngleToAPointOnEllipse(pt), dist) + + angle = qad_utils.getAngleBy3Pts(self.getStartPt(False), self.center, self.getEndPt(False), False) + angleStep = angle / _atLeastNSegment + + points = [] + points.append(ptOffset) + while True: + param, ptOffset = self.getNextParamPtForOffset(param, ptOffset, angleStep, tolerance, dist) + if param > endParam: break + points.append(ptOffset) + + lastPt = self.getPointAt(endParam) + lastPtOffset = qad_utils.getPolarPointByPtAngle(lastPt, self.getNormalAngleToAPointOnEllipse(lastPt), dist) + if qad_utils.ptNear(points[-1], lastPtOffset) == False: points.append(lastPtOffset) # ultimo elemento della lista + + if self.reversed: points.reverse() + return points + + + # ============================================================================ + # fromFoci + # ============================================================================ + def fromFoci(self, f1, f2, ptOnEllipse, startAngle, endAngle): + """ + setta le caratteristiche dell'ellisse attraverso: + i due fuochi + un punto sull'ellisse + /-ptOnEllipse-\ + / \ + | f1 f2 | + \ / + \-------------/ + """ + if QadEllipse.fromFoci(self, f1, f2, ptOnEllipse) == False: return False + self.setArc(startAngle, endAngle) + return True + + + # ============================================================================ + # fromExtent + # ============================================================================ + def fromExtent(self, pt1, pt2, rot, startAngle, endAngle): + """ + setta le caratteristiche dell'ellisse attraverso: + i due punti di estensione (angoli opposti) del rettangolo che racchiude l'ellisse + rotazione del rettangolo di estensione + /-------------\ pt2 + / \ + | | + \ / + pt1 \-------------/ + """ + if QadEllipse.fromExtent(self, pt1, pt2, rot) == False: return False + self.setArc(startAngle, endAngle) + return True + + + # ============================================================================ + # fromCenterAxis1FinalPtAxis2FinalPt + # ============================================================================ + def fromCenterAxis1FinalPtAxis2FinalPt(self, ptCenter, axis1FinalPt, axis2FinalPt, startAngle, endAngle): + """ + setta le caratteristiche dell'ellisse attraverso: + il punto centrale + il punto finale dell'asse + il punto finale dell'altro asse + /--axis2FinalPt--\ + / \ + | ptCenter axis1FinalPt + \ / + \----------------/ + """ + if QadEllipse.fromCenterAxis1FinalPtAxis2FinalPt(self, ptCenter, axis1FinalPt, axis2FinalPt) == False: return False + self.setArc(startAngle, endAngle) + return True + + + # ============================================================================ + # fromCenterAxis1FinalPtDistAxis2 + # ============================================================================ + def fromCenterAxis1FinalPtDistAxis2(self, ptCenter, axis1FinalPt, distAxis2, startAngle, endAngle): + """ + setta le caratteristiche dell'ellisse attraverso: + il punto centrale + il punto finale dell'asse + distanza dal centro al punto finale dell'altro asse + /-------|--------\ + / distAxis2 \ + | ptCenter axis1FinalPt + \ / + \----------------/ + """ + if QadEllipse.fromCenterAxis1FinalPtDistAxis2(self, ptCenter, axis1FinalPt, distAxis2) == False: return False + self.setArc(startAngle, endAngle) + return True + + + # ============================================================================ + # fromCenterAxis1FinalPtAxis2FinalPt + # ============================================================================ + def fromCenterAxis1FinalPtAxis2FinalPt(self, axis1Finalpt1, axis1Finalpt2, axis2FinalPt, startAngle, endAngle): + """ + setta le caratteristiche dell'ellisse attraverso: + i punti finali dell'asse + il punto finale dell'altro asse + /--axis2FinalPt--\ + / \ + axis1Finalpt2 axis1Finalpt1 + \ / + \----------------/ + """ + if QadEllipse.fromCenterAxis1FinalPtAxis2FinalPt(self, axis1Finalpt1, axis1Finalpt2, axis2FinalPt) == False: return False + self.setArc(startAngle, endAngle) + return True + + + # ============================================================================ + # fromAxis1FinalPtsAxis2Len + # ============================================================================ + def fromAxis1FinalPtsAxis2Len(self, axis1FinalPt1, axis1FinalPt2, distAxis2, startAngle, endAngle): + """ + setta le caratteristiche dell'ellisse attraverso: + i punti finali dell'asse + distanza dal centro al punto finale dell'altro asse + /------|-------\ + / distAxis2 \ + axis1pt2 | axis1pt1 + \ / + \--------------/ + """ + if QadEllipse.fromAxis1FinalPtsAxis2Len(self, axis1FinalPt1, axis1FinalPt2, distAxis2) == False: return False + self.setArc(startAngle, endAngle) + return True + + + # ============================================================================ + # fromAxis1FinalPtsArea + # ============================================================================ + def fromAxis1FinalPtsArea(self, axis1FinalPt1, axis1FinalPt2, area, startAngle, endAngle): + """ + setta le caratteristiche dell'ellisse attraverso: + i punti finali dell'asse + area dell'ellisse + /--------------\ + / \ + axis1pt2 axis1pt1 + \ / + \--------------/ + """ + if QadEllipse.fromAxis1FinalPtsArea(self, axis1FinalPt1, axis1FinalPt2, area) == False: return False + self.setArc(startAngle, endAngle) + return True + + + # =============================================================================== + # getGeomBetween2Pts + # =============================================================================== + def getGeomBetween2Pts(self, startPt, endPt): + """ + Ritorna una sotto geometria che parte dal punto startPt e finisce al punto endPt seguendo il tracciato della geometria. + """ + if qad_utils.ptNear(startPt, endPt): return None + if self.containsPt(startPt) == False: return None + if self.containsPt(endPt) == False: return None + + result = self.copy() + d1 = self.getDistanceFromStart(startPt) + if d1 < self.getDistanceFromStart(endPt): + result.setStartPt(startPt) + result.setEndPt(endPt) + else: + result.setStartPt(endPt) + result.setEndPt(startPt) + result.reversed = True + + return result diff --git a/qad_entity.py b/qad_entity.py index 077af9b2..385e45d3 100644 --- a/qad_entity.py +++ b/qad_entity.py @@ -1,882 +1,1176 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - classe per la gestione delle entità - - ------------------- - begin : 2013-08-22 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -from qgis.gui import * -from qgis.utils import iface -import sys - - -import qad_utils -from qad_arc import * -from qad_circle import * -import qad_layer -import qad_stretch_fun - - -#=============================================================================== -# QadEntityGeomTypeEnum class. -#=============================================================================== -class QadEntityGeomTypeEnum(): - NONE = 0 - SYMBOL = 1 - TEXT = 2 - ARC = 3 - CIRCLE = 4 - LINESTRING = 5 - - -#=============================================================================== -# QadEntity entity class -#=============================================================================== -class QadEntity(): - - def __init__(self, entity = None): - self.entityType = QadEntityGeomTypeEnum.NONE - self.qadGeom = None # in crs del canvas per lavorare con coordinate piane xy - self.dimStyle = None - self.dimId = None - - if entity is not None: - self.set(entity.layer, entity.featureId) - else: - self.layer = None - self.featureId = None - - - def whatIs(self): - return "ENTITY" - - - def isDimensionComponent(self): - # se entityType non è già stato inizializzato - if self.entityType == QadEntityGeomTypeEnum.NONE: - self.__initQadInfo() - - return (self.dimStyle is not None) and (self.dimId is not None) - - - def __fromPoyline(self, pointList): # funzione privata - # restituisce entityType, qadGeom - arc = QadArc() - startEndVertices = arc.fromPolyline(pointList, 0) - # se la polilinea è composta solo da un arco - if startEndVertices and startEndVertices[0] == 0 and startEndVertices[1] == len(pointList)-1: - return QadEntityGeomTypeEnum.ARC, arc # oggetto arco - else: - circle = QadCircle() - startEndVertices = circle.fromPolyline(pointList, 0) - # se la polilinea è composta solo da un cerchio - if startEndVertices and startEndVertices[0] == 0 and startEndVertices[1] == len(pointList)-1: - return QadEntityGeomTypeEnum.CIRCLE, circle # oggetto cerchio - else: - linearObjectList = qad_utils.QadLinearObjectList() # oggetto QadLinearObjectList - linearObjectList.fromPolyline(pointList) - return QadEntityGeomTypeEnum.LINESTRING, linearObjectList - - return QadEntityGeomTypeEnum.NONE, None - - - def __initQadInfo(self): - # inizializza entityType, qadGeom, dimStyle, dimId - if self.isInitialized() == False: - return QadEntityGeomTypeEnum.NONE - - self.dimStyle = None - self.dimId = None - - g = self.getGeometry() - if g is None: - return QadEntityGeomTypeEnum.NONE - - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - coordTransform = QgsCoordinateTransform(self.layer.crs(), iface.mapCanvas().mapRenderer().destinationCrs()) - g.transform(coordTransform) - - wkbType = g.wkbType() - if wkbType == QGis.WKBPoint: - from qad_dim import QadDimStyles # to avoid cyclic import - - # verifico se l'entità appartiene ad uno stile di quotatura - dimStyle, dimId = QadDimStyles.getDimIdByEntity(self) - if (dimStyle is not None) and (dimId is not None): - self.dimStyle = dimStyle # stile di quotatura di appartenenza - self.dimId = dimId # codice quotatura di appartenenza - - if qad_layer.isTextLayer(self.layer): - self.entityType = QadEntityGeomTypeEnum.TEXT - elif qad_layer.isSymbolLayer(self.layer): - self.entityType = QadEntityGeomTypeEnum.SYMBOL - self.qadGeom = g.asPoint() # un punto - - if wkbType == QGis.WKBMultiPoint: - if qad_layer.isTextLayer(self.layer): - self.entityType = QadEntityGeomTypeEnum.TEXT - elif qad_layer.isSymbolLayer(self.layer): - self.entityType = QadEntityGeomTypeEnum.SYMBOL - self.qadGeom = g.asMultiPoint() # lista di punti - - elif wkbType == QGis.WKBLineString: - from qad_dim import QadDimStyles # to avoid cyclic import - - # verifico se l'entità appartiene ad uno stile di quotatura - dimStyle, dimId = QadDimStyles.getDimIdByEntity(self) - if (dimStyle is not None) and (dimId is not None): - self.entityType = QadEntityGeomTypeEnum.DIMENSION_COMPONENT - self.dimStyle = dimStyle # stile di quotatura di appartenenza - self.dimId = dimId # codice quotatura di appartenenza - - self.entityType, self.qadGeom = self.__fromPoyline(g.asPolyline()) - - elif wkbType == QGis.WKBMultiLineString: - self.entityType = [] - self.qadGeom = [] - lineList = g.asMultiPolyline() # vettore di linee - for line in lineList: - entityType, qadGeom = self.__fromPoyline(g.asPolyline()) - self.entityType.append(entityType) - self.qadGeom.append(qadGeom) - - elif wkbType == QGis.WKBPolygon: - self.entityType = [] - self.qadGeom = [] - polygon = g.asPolygon() # vettore di linee - for line in polygon: - entityType, qadGeom = self.__fromPoyline(line) - self.entityType.append(entityType) - self.qadGeom.append(qadGeom) - - elif wkbType == QGis.WKBMultiPolygon: - self.entityType = [] - self.qadGeom = [] - polygonList = g.asMultiPolygon() # vettore di poligoni - for polygon in polygonList: - partialEntityType = [] - partialQadGeom = [] - for line in polygon: - entityType, qadGeom = self.__fromPoyline(line) - partialEntityType.append(entityType) - partialQadGeom.append(qadGeom) - self.entityType.append(partialEntityType) - self.qadGeom.append(partialQadGeom) - - - def getEntityType(self, atGeom = 0, atSubGeom = 0): - # se entityType non è già stato inizializzato - if self.entityType == QadEntityGeomTypeEnum.NONE: - self.__initQadInfo() - - # se entityType è stato inizializzato - if self.entityType != QadEntityGeomTypeEnum.NONE: - if type(self.entityType) == list: - if atGeom < len(self.entityType): - if type(self.entityType[atGeom]) == list: - if atSubGeom < len(self.entityType[atGeom]): - return self.entityType[atGeom][atSubGeom] - else: - return QadEntityGeomTypeEnum.NONE - else: - return QadEntityGeomTypeEnum.NONE if atSubGeom != 0 else self.entityType[atGeom] - else: - return QadEntityGeomTypeEnum.NONE - else: - return QadEntityGeomTypeEnum.NONE if atGeom != 0 else self.entityType - else: - return QadEntityGeomTypeEnum.NONE - - - def getQadGeom(self, atGeom = 0, atSubGeom = 0): - # se entityType non è già stato inizializzato - if self.qadGeom is None: - self.__initQadInfo() - - # se qadGeom è stato inizializzato - if self.qadGeom is not None: - if type(self.qadGeom) == list: - if atGeom < len(self.qadGeom): - if type(self.qadGeom[atGeom]) == list: - if atSubGeom < len(self.qadGeom[atGeom]): - return self.qadGeom[atGeom][atSubGeom] - else: - return None - else: - return None if atSubGeom != 0 else self.qadGeom[atGeom] - else: - return None - else: - return None if atGeom != 0 else self.qadGeom - else: - return None - - - def isInitialized(self): - if (self.layer is None) or (self.featureId is None): - return False - else: - return True - - - def clear(self): - self.layer = None - self.featureId = None - self.entityType = QadEntityGeomTypeEnum.NONE - self.qadGeom = None - self.dimStyle = None - self.dimId = None - - - def __eq__(self, entity): - """self == other""" - if self.isInitialized() == False or entity.isInitialized() == False : - return False - - if self.layerId() == entity.layerId() and self.featureId == entity.featureId: - return True - else: - return False - - - def __ne__(self, entity): - """self != other""" - if self.isInitialized() == False or entity.isInitialized() == False: - return True - - if self.layerId() != entity.layerId() or self.featureId != entity.featureId: - return True - else: - return False - - - def layerId(self): - if self.isInitialized() == False: - return None - return self.layer.id() - - - def set(self, layer, featureId): - self.clear() - self.layer = layer # il layer non si può copiare - self.featureId = featureId # copio l'identificativo di feature - return self - - - def getFeature(self): - if self.isInitialized() == False: - return None - - return qad_utils.getFeatureById(self.layer, self.featureId) - - - def exists(self): - if self.getFeature() is None: - return False - else: - return True - - - def getGeometry(self): - feature = self.getFeature() - if feature is None: - return None - return QgsGeometry(feature.geometry()) # fa una copia - - - def __getPtsFromQadGeom(self, qadGeom, tolerance2ApproxCurve): - if type(qadGeom) == list: # entità composta da più geometrie - res = [] - for subGeom in qadGeom: - res.append(self.__getPtsFromQadGeom(subGeom, tolerance2ApproxCurve)) - return res - else: - if type(qadGeom) == QgsPoint: - return qadGeom - else: - return qadGeom.asPolyline(tolerance2ApproxCurve) - - - def __getGeomFromQadGeom(self, qadGeom, tolerance2ApproxCurve): - Pts = self.__getPtsFromQadGeom(qadGeom, tolerance2ApproxCurve) - if Pts is None: - return None - if self.layer.geometryType() == QGis.Point: - if type(Pts) == list: - g = QgsGeometry.fromMultiPoint(Pts) - else: - g = QgsGeometry.fromPoint(Pts) - if self.layer.geometryType() == QGis.Line: - if type(Pts[0]) == list: - g = QgsGeometry.fromMultiPolyline(Pts) - else: - g = QgsGeometry.fromPolyline(Pts) - if self.layer.geometryType() == QGis.Polygon: - if type(Pts[0][0]) == list: - g = QgsGeometry.fromMultiPolygon(Pts) - else: - g = QgsGeometry.fromPolygon(Pts) - - # trasformo la geometria nel crs del layer - coordTransform = QgsCoordinateTransform(iface.mapCanvas().mapRenderer().destinationCrs(), self.layer.crs()) - g.transform(coordTransform) - return g - - -# questa funzione non ha senso perchè feature è una variabile locale temporanea a cui viene settata la geometria -# ma poi, a fine funzione, viene distrutta. -# def setGeometry(self, geom): -# feature = self.getFeature() -# if feature is None: -# return None -# return feature.setGeometry(geom) - - - def getAttribute(self, attribName): - feature = self.getFeature() - if feature is None: - return None - try: - return feature.attribute(attribName) - except: - return None - - - def getAttributes(self): - feature = self.getFeature() - if feature is None: - return None - - return feature.attributes()[:] # fa una copia - - - def selectOnLayer(self, incremental = True): - if self.isInitialized() == True: - if incremental == False: - self.layer.removeSelection() - - self.layer.select(self.featureId) - - - def deselectOnLayer(self): - if self.isInitialized() == False: - return False - - self.layer.deselect(self.featureId) - - - #=============================================================================== - # operazioni geometriche - inizio - - def gripStretch(self, basePt, ptListToStretch, offSetX, offSetY, tolerance2ApproxCurve): - # se entityType è già stato inizializzato - if self.entityType == QadEntityGeomTypeEnum.NONE: - self.__initQadInfo() - - return qad_stretch_fun.gripStretchQadGeometry(self.qadGeom, basePt, ptListToStretch, offSetX, offSetY, tolerance2ApproxCurve) - - def gripGeomStretch(self, basePt, ptListToStretch, offSetX, offSetY, tolerance2ApproxCurve): - newQadGeom = self.gripStretch(basePt, ptListToStretch, offSetX, offSetY, tolerance2ApproxCurve) - if newQadGeom is None: - return None - return self.__getGeomFromQadGeom(newQadGeom, tolerance2ApproxCurve) - - -# operazioni geometriche - fine -#=============================================================================== - - -#=============================================================================== -# QadLayerEntitySet entities of a layer class -#=============================================================================== -class QadLayerEntitySet(): - - def __init__(self, layerEntitySet = None): - if layerEntitySet is not None: - self.set(layerEntitySet.layer, layerEntitySet.featureIds) - else: - self.layer = None - self.featureIds = [] - - - def whatIs(self): - return "LAYERENTITYSET" - - - def isInitialized(self): - if self.layer is None: - return False - else: - return True - - - def isEmpty(self): - if self.isInitialized() == False: - return True - return not self.featureIds - - - def count(self): - if self.isInitialized() == False: - return 0 - return len(self.featureIds) - - - def clear(self): - if self.isInitialized() == False: - return 0 - self.layer = None - del self.featureIds[:] - - - def layerId(self): - if self.isInitialized() == False: - return None - return self.layer.id() - - - def set(self, layer, features = None): - if type(layer) == QgsVectorLayer: - self.layer = layer # il layer non si può copiare - self.featureIds = [] - if features is not None: - self.addFeatures(features) - else: # layer è una entità - return self.set(layer.layer, layer.featureIds) - - - def initByCurrentQgsSelectedFeatures(self, layer): - self.clear() - self.layer = layer - self.featureIds = self.layer.selectedFeaturesIds() - - - def getFeature(self, featureId): - if self.isInitialized() == False: - return None - return qad_utils.getFeatureById(self.layer, featureId) - - - def getGeometry(self, featureId): - feature = self.getFeature(featureId) - if feature is None: - return None - return QgsGeometry(feature.geometry()) # fa una copia - - def setGeometry(self, featureId, geom): - feature = self.getFeature(featureId) - if feature is None: - return None - return feature.setGeometry(geom) - - - def getGeometryCollection(self, destCRS = None): - result = [] - if destCRS is not None: - coordTransform = QgsCoordinateTransform(self.layer.crs(), destCRS) # trasformo la geometria - for featureId in self.featureIds: - g = self.getGeometry(featureId) - if g is not None: - if destCRS is not None: - g.transform(coordTransform) - - # Per un baco sconosciuto quando trasformo la geometria se poi ne faccio un buffer - # il calcolo dà un risultato sbagliato quando la geometria é nuova o modificata - # (in cache del layer) e il sistema di coordinate é diverso de quello della mappa corrente - wkbType = g.wkbType() - if wkbType == QGis.WKBPoint or wkbType == QGis.WKBPoint25D: - g = QgsGeometry().fromPoint(g.asPoint()) - elif wkbType == QGis.WKBMultiPoint or wkbType == QGis.WKBMultiPoint25D: - g = QgsGeometry().fromMultiPoint(g.asMultiPoint()) - elif wkbType == QGis.WKBLineString or wkbType == QGis.WKBLineString25D: - g = QgsGeometry().fromPolyline(g.asPolyline()) - elif wkbType == QGis.WKBMultiLineString or wkbType == QGis.WKBMultiLineString25D: - g = QgsGeometry().fromMultiPolyline(g.asMultiPolyline()) - elif wkbType == QGis.WKBPolygon or wkbType == QGis.WKBPolygon25D: - g = QgsGeometry().fromPolygon(g.asPolygon()) - elif wkbType == QGis.WKBMultiPolygon or wkbType == QGis.WKBMultiPolygon25D: - g = QgsGeometry().fromMultiPolygon(g.asMultiPolygon()) - - result.append(g) - return result - - - def getFeatureCollection(self): - result = [] - for featureId in self.featureIds: - f = self.getFeature(featureId) - if f is not None: - result.append(f) - return result - - - def selectOnLayer(self, incremental = True): - if self.isInitialized() == True: - if len(self.featureIds) > 0: - if incremental == False: - self.layer.removeSelection() - - self.layer.select(self.featureIds) - else: - if incremental == False: - self.layer.removeSelection() - - - def deselectOnLayer(self): - if self.isInitialized() == True: - if len(self.featureIds) > 0: - self.layer.deselect(self.featureIds) - - - def containsFeature(self, feature): - if self.isInitialized() == False: - return False - - if type(feature) == QgsFeature: - return feature.id() in self.featureIds - else: - return feature in self.featureIds - - - def containsEntity(self, entity): - if self.isInitialized() == False: - return False - - if self.layerId() != entity.layerId(): - return False - return containsFeature(entity.featureId) - - - def addFeature(self, feature): - if self.isInitialized() == False: - return False - - if type(feature) == QgsFeature: - if self.containsFeature(feature.id()) == False: - self.featureIds.append(feature.id()) - return True - else: - return False - else: - if self.containsFeature(feature) == False: - self.featureIds.append(feature) - return True - else: - return False - - - def removeFeature(self, feature): - if self.isInitialized() == False: - return False - - try: - if type(feature) == QgsFeature: - return self.featureIds.remove(feature.id()) - else: - return self.featureIds.remove(feature) - except: - return None - - - def addEntity(self, entity): - if self.isInitialized() == False: - self.set(entity.layer) - else: - if self.layerId() != entity.layerId(): - return - - self.addFeature(entity.featureId) - - - def removeEntity(self, entity): - if self.isInitialized() == False: - return False - - if self.layerId() != entity.layerId(): - return False - - return self.removeFeature(entity.featureId) - - - def addFeatures(self, features): - if self.isInitialized() == False: - return None - for feature in features: - if type(feature) == QgsFeature: - self.addFeature(feature.id()) - else: - self.addFeature(feature) # featureId - - - def addLayerEntitySet(self, layerEntitySet): - if self.isInitialized() == False: - self.set(layerEntitySet.layer) - else: - if self.layerId() != layerEntitySet.layerId(): - return - self.addFeatures(layerEntitySet.featureIds) - - - def unite(self, layerEntitySet): - if self.isInitialized() == False: - return - - if self.layerId() == layerEntitySet.layerId(): - self.featureIds = list(set.union(set(self.featureIds), set(layerEntitySet.featureIds))) - - - def intersect(self, layerEntitySet): - if self.isInitialized() == False: - return - - if self.layerId() == layerEntitySet.layerId(): - self.featureIds = list(set(self.featureIds) & set(layerEntitySet.featureIds)) - - - def subtract(self, layerEntitySet): - if self.isInitialized() == False: - return - - if self.layerId() == layerEntitySet.layerId(): - self.featureIds = list(set(self.featureIds) - set(layerEntitySet.featureIds)) - - - def getNotExistingFeaturedIds(self): - featureIds = [] - if self.isInitialized() == True: - feature = QadEntity() - for featureId in self.featureIds: - feature.set(self.layer, featureId) - if not feature.exists(): - featureIds.append(featureId) - return featureIds - - - def removeNotExisting(self): - featureIds = self.getNotExistingFeaturedIds() - self.featureIds = list(set(self.featureIds) - set(featureIds)) - - -#=============================================================================== -# QadEntitySet entities of a layers class -#=============================================================================== -class QadEntitySet(): - - def __init__(self, entitySet = None): - self.layerEntitySetList = [] - if entitySet is not None: - self.set(entitySet) - - - def whatIs(self): - return "ENTITYSET" - - - def isEmpty(self): - for layerEntitySet in self.layerEntitySetList: - if layerEntitySet.isEmpty() == False: - return False - return True - - - def count(self): - tot = 0 - for layerEntitySet in self.layerEntitySetList: - tot = tot + layerEntitySet.count() - return tot - - - def clear(self): - del self.layerEntitySetList[:] - - - def set(self, entitySet): - self.clear() - for layerEntitySet in entitySet.layerEntitySetList: - self.addLayerEntitySet(layerEntitySet) - - - def initByCurrentQgsSelectedFeatures(self, layers): - self.clear() - - for layer in layers: - if layer.selectedFeatureCount() > 0: - layerEntitySet = QadLayerEntitySet() - layerEntitySet.initByCurrentQgsSelectedFeatures(layer) - self.layerEntitySetList.append(layerEntitySet) - - - def findLayerEntitySet(self, layer): - if layer is None: - return None - if type(layer) == QgsVectorLayer: # layer - return self.findLayerEntitySet(layer.id()) - elif type(layer) == unicode: # id del layer - for layerEntitySet in self.layerEntitySetList: - if layerEntitySet.layerId() == layer: - return layerEntitySet - return None - else: # QadLayerEntitySet - return self.findLayerEntitySet(layer.layer) - - - def getLayerList(self): - layerList = [] - for layerEntitySet in self.layerEntitySetList: - layerList.append(layerEntitySet.layer) - return layerList - - - def getGeometryCollection(self, destCRS = None): - result = [] - for layerEntitySet in self.layerEntitySetList: - partial = layerEntitySet.getGeometryCollection(destCRS) - if partial is not None: - result.extend(partial) - return result - - - def getFeatureCollection(self): - result = [] - for layerEntitySet in self.layerEntitySetList: - partial = layerEntitySet.getFeatureCollection() - if partial is not None: - result.extend(partial) - return result - - - def selectOnLayer(self, incremental = True): - for layerEntitySet in self.layerEntitySetList: - layerEntitySet.selectOnLayer(incremental) - - - def deselectOnLayer(self): - for layerEntitySet in self.layerEntitySetList: - layerEntitySet.deselectOnLayer() - - - def containsEntity(self, entity): - layerEntitySet = self.findLayerEntitySet(entity.layer) - if layerEntitySet is None: - return False - return layerEntitySet.containsFeature(entity.featureId) - - - def addEntity(self, entity): - if entity is None or entity.isInitialized() == False: - return False - layerEntitySet = self.findLayerEntitySet(entity.layer) - if layerEntitySet is None: - layerEntitySet = QadLayerEntitySet() - layerEntitySet.set(entity.layer) - self.layerEntitySetList.append(layerEntitySet) - return layerEntitySet.addFeature(entity.featureId) - - - def removeEntity(self, entity): - layerEntitySet = self.findLayerEntitySet(entity.layer) - if layerEntitySet is None: - return False - return layerEntitySet.removeFeature(entity.featureId) - - - def removeLayerEntitySet(self, layer): - i = 0 - for layerEntitySet in self.layerEntitySetList: - if layerEntitySet.id() == layer.id(): - del layerEntitySet[i] - return True - i = i + 1 - return False - - - def addLayerEntitySet(self, layerEntitySet): - _layerEntitySet = self.findLayerEntitySet(layerEntitySet) - if _layerEntitySet is None: - _layerEntitySet = QadLayerEntitySet() - _layerEntitySet.set(layerEntitySet.layer) - self.layerEntitySetList.append(_layerEntitySet) - _layerEntitySet.addFeatures(layerEntitySet.featureIds) - - - def unite(self, entitySet): - if entitySet is None: - return - for layerEntitySet in entitySet.layerEntitySetList: - _layerEntitySet = self.findLayerEntitySet(layerEntitySet) - if _layerEntitySet is None: - _layerEntitySet = QadLayerEntitySet() - _layerEntitySet.set(layerEntitySet.layer) - self.layerEntitySetList.append(_layerEntitySet) - _layerEntitySet.unite(layerEntitySet) - - - def intersect(self, entitySet): - if entitySet is None: - return - for i in xrange(len(self.layerEntitySetList) - 1, -1, -1): - _layerEntitySet = self.layerEntitySetList[i] - layerEntitySet = entitySet.findLayerEntitySet(_layerEntitySet) - if layerEntitySet is None: - del self.layerEntitySetList[i] - else: - _layerEntitySet.intersect(layerEntitySet) - - - def subtract(self, entitySet): - if entitySet is None: - return - for _layerEntitySet in self.layerEntitySetList: - layerEntitySet = entitySet.findLayerEntitySet(_layerEntitySet) - if layerEntitySet is not None: - _layerEntitySet.subtract(layerEntitySet) - - - def removeNotExisting(self): - for layerEntitySet in self.layerEntitySetList: - layerEntitySet.removeNotExisting() - - - def removeNotEditable(self): - for i in xrange(len(self.layerEntitySetList) - 1, -1, -1): - layerEntitySet = self.layerEntitySetList[i] - if layerEntitySet.layer.isEditable() == False: - del self.layerEntitySetList[i] - - - def removeGeomType(self, type): - for i in xrange(len(self.layerEntitySetList) - 1, -1, -1): - layerEntitySet = self.layerEntitySetList[i] - if layerEntitySet.layer.geometryType() == type: - del self.layerEntitySetList[i] - - - def purge(self): - # rimuove i layer con zero oggetti - for i in xrange(len(self.layerEntitySetList) - 1, -1, -1): - layerEntitySet = self.layerEntitySetList[i] - if layerEntitySet.count() == 0: - del self.layerEntitySetList[i] - +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + classe per la gestione delle entità + + ------------------- + begin : 2013-08-22 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.core import * +from qgis.utils import iface +import sys + + +from . import qad_label +from . import qad_layer +from . import qad_stretch_fun +from . import qad_utils +from .qad_multi_geom import * +from .qad_variables import QadVariables + + +# =============================================================================== +# QadEntityTypeEnum class. +# =============================================================================== +class QadEntityTypeEnum(): + NONE = 0 + SYMBOL = 1 + TEXT = 2 + LINEAROBJ = 3 # linea, arco, cerchio, arco di ellisse, ellisse, polilinea (e multi) + POLYGON = 4 # poligono (e multi) + + +# =============================================================================== +# QadEntity entity class +# =============================================================================== +class QadEntity(): + + def __init__(self, entity = None): + self.entityType = QadEntityTypeEnum.NONE + + if entity is not None: + self.set(entity) + else: + self.layer = None + self.featureId = None + # geometria di qad singola (punto, linea, arco, cerchio, arco di ellisse, ellisse, polilinea, poligono) + # oppure multipla (multipunto, multi oggetto lineare, multi poligono) + self.qadGeom = None # in crs del canvas per lavorare con coordinate piane xy + self.isTextualLayer = False + self.isSymbolLayer = False + self.rotFldName = "" # campo usato per la rotazione di testi o simboli + + + def whatIs(self): + return "ENTITY" + + + # =============================================================================== + # set + # =============================================================================== + def set(self, layer_or_entity, featureId = None): + self.clear() + if isinstance(layer_or_entity, QgsVectorLayer): + self.layer = layer_or_entity # il layer non si può copiare + self.featureId = featureId # copio l'identificativo di feature + else: # layer è una entità + self.layer = layer_or_entity.layer + self.featureId = layer_or_entity.featureId + + self.entityType = layer_or_entity.entityType + self.qadGeom = None if layer_or_entity.qadGeom is None else layer_or_entity.qadGeom.copy() # copio la geometria + self.isTextualLayer = layer_or_entity.isTextualLayer + self.isSymbolLayer = layer_or_entity.isSymbolLayer + self.rotFldName = layer_or_entity.rotFldName + + return self + + + def __initQadInfo(self): + # inizializza entityType, qadGeom, dimStyle, dimId, isTextualLayer, isSymbolLayer + if self.qadGeom is not None: + return self.entityType + + if self.isInitialized() == False: + return QadEntityTypeEnum.NONE + + self.entityType = QadEntityTypeEnum.NONE + self.isTextualLayer = False + self.isSymbolLayer = False + self.rotFldName = "" # campo usato per la rotazione di testi o simboli + + layerGeomType = self.layer.geometryType() + + if layerGeomType == QgsWkbTypes.PointGeometry: + if qad_layer.isTextLayer(self.layer): + self.isTextualLayer = True + self.entityType = QadEntityTypeEnum.TEXT + # se la rotazione dipende da un solo campo + rotFldNames = qad_label.get_labelRotationFieldNames(self.layer) + if len(rotFldNames) == 1 and len(rotFldNames[0]) > 0: + self.rotFldName = rotFldNames[0] + + elif qad_layer.isSymbolLayer(self.layer): + self.isSymbolLayer = True + self.entityType = QadEntityTypeEnum.SYMBOL + self.rotFldName = qad_layer.get_symbolRotationFieldName(self.layer) + + elif layerGeomType == QgsWkbTypes.LineGeometry: + self.entityType = QadEntityTypeEnum.LINEAROBJ + elif layerGeomType == QgsWkbTypes.PolygonGeometry: + self.entityType = QadEntityTypeEnum.POLYGON + + g = self.getGeometry() + if g is None: + return QadEntityTypeEnum.NONE + + self.qadGeom = fromQgsGeomToQadGeom(g, self.layer.crs()) + if self.qadGeom is None: return QadEntityTypeEnum.NONE + + qadGeomType = self.qadGeom.whatIs() + layerGeomType = self.layer.geometryType() + + return self.entityType + + + def getEntityType(self): + return self.entityType + + + def getQadGeom(self, atGeom = None, atSubGeom = None): + self.__initQadInfo() + if atGeom is None and atSubGeom is None: + return self.qadGeom + else: + return getQadGeomAt(self.qadGeom, atGeom, atSubGeom) + + + def isInitialized(self): + if (self.layer is None) or (self.featureId is None): + return False + else: + return True + + + def clear(self): + self.layer = None + self.featureId = None + self.entityType = QadEntityTypeEnum.NONE + self.qadGeom = None + self.isTextualLayer = False + self.isSymbolLayer = False + self.rotFldName = "" + + + def __eq__(self, entity): + """self == other""" + if self.isInitialized() == False or entity.isInitialized() == False : + return False + + if self.layerId() == entity.layerId() and self.featureId == entity.featureId: + return True + else: + return False + + + def __ne__(self, entity): + """self != other""" + if self.isInitialized() == False or entity.isInitialized() == False: + return True + + if self.layerId() != entity.layerId() or self.featureId != entity.featureId: + return True + else: + return False + + + def layerId(self): + if self.isInitialized() == False: + return None + return self.layer.id() + + + def crs(self): + if self.isInitialized() == False: + return None + return self.layer.crs() + + + def copy(self): + return QadEntity(self) + + + def getFeature(self): + if self.isInitialized() == False: + return None + + return qad_utils.getFeatureById(self.layer, self.featureId) + + + def exists(self): + if self.getFeature() is None: + return False + else: + return True + + + def getGeometry(self, destCRS = None): + feature = self.getFeature() + if feature is None: + return None + g = QgsGeometry(feature.geometry()) # fa una copia + if destCRS is not None and destCRS != self.crs(): + coordTransform = QgsCoordinateTransform(self.crs(), destCRS, QgsProject.instance()) # trasformo la geometria in destCRS coordinate + g.transform(coordTransform) + + return g + + + +# questa funzione non ha senso perchè feature è una variabile locale temporanea a cui viene settata la geometria +# ma poi, a fine funzione, viene distrutta. +# def setGeometry(self, geom): +# feature = self.getFeature() +# if feature is None: +# return None +# return feature.setGeometry(geom) + + + def getAttribute(self, attribName): + feature = self.getFeature() + if feature is None: + return None + try: + return feature.attribute(attribName) + except: + return None + + + def getAttributes(self): + feature = self.getFeature() + if feature is None: + return None + + return feature.attributes()[:] # fa una copia + + + def selectOnLayer(self, incremental = True): + if self.isInitialized() == True: + if incremental == False: + self.layer.removeSelection() + + self.layer.select(self.featureId) # aaaaaaaaaaaaaaaaaaaaaaaaaa qui parte l'evento activate di qad_maptool + + + def deselectOnLayer(self): + if self.isInitialized() == False: + return False + + self.layer.deselect(self.featureId) + + +# =============================================================================== +# QadCacheEntitySetIterator +# =============================================================================== +class QadCacheEntitySetIterator(): + # classe per iterare le entità di un QadCacheEntitySet + def __init__(self, cacheEntitySet): + self.i = 0 + self.cacheEntitySet = cacheEntitySet + + def __iter__(self): + return self + + def __next__(self): + if self.i < len(self.cacheEntitySet.entityList): + ent = self.cacheEntitySet.entityList[self.i] + self.i += 1 + return ent + else: + raise StopIteration + + +# =============================================================================== +# QadCacheEntitySet +# =============================================================================== +class QadCacheEntitySet(): + """ + Classe che gestisce una memoria cache per le entità + """ + + # ============================================================================ + # __init__ + # ============================================================================ + def __init__(self): + self.entityList = [] + + + # ============================================================================ + # __del__ + # ============================================================================ + def __del__(self): + self.clear() + + + # ============================================================================ + # clear + # ============================================================================ + def clear(self): + # svuota la cache + for entity in self.entityList: del entity + del self.entityList[:] + + + # ============================================================================ + # isEmpty + # ============================================================================ + def isEmpty(self): + return True if len(self.entityList) == 0 else False + + + # ============================================================================ + # count + # ============================================================================ + def count(self): + return len(self.entityList) + + + # ============================================================================ + # getEntity + # ============================================================================ + def getEntity(self, layerId, featureId): + for entity in self.entityList: + if entity.layerId() == layerId and entity.featureId == featureId: + return entity + return None + + + # ============================================================================ + # appendEntity + # ============================================================================ + def appendEntity(self, entity): + clonedEntity = QadEntity(entity) + self.entityList.append(clonedEntity) # la copio + return clonedEntity + + + # ============================================================================ + # appendEntitySet + # ============================================================================ + def appendEntitySet(self, entitySet): + for layerEntitySet in entitySet.layerEntitySetList: + entityIterator = QadLayerEntitySetIterator(layerEntitySet) + for entity in entityIterator: + self.appendEntity(entity) + + + # ============================================================================ + # getLayerList + # ============================================================================ + def getLayerList(self): + layerList = [] + for entity in self.entityList: + found = False + for layer in layerList: + if entity.layer.id() == layer.id(): + found = True + break + if found == False: + layerList.append(entity.layer) + + return layerList + + + # ============================================================================ + # getBoundingBox + # ============================================================================ + def getBoundingBox(self): + # ritorna il rettangolo di occupazione delle entità + result = None + for entity in self.entityList: + entBouningBox = entity.getQadGeom().getBoundingBox() + if result is None: + result = entBouningBox + else: + result.combineExtentWith(entBouningBox) + return result + + +# =============================================================================== +# QadLayerEntitySetIterator +# =============================================================================== +class QadLayerEntitySetIterator(): + # classe per iterare le entità di un QadLayerEntitySet + def __init__(self, layerEntitySet): + self.i = 0 + self.layerEntitySet = layerEntitySet + self.ent = QadEntity() + + def __iter__(self): + return self + + def __next__(self): + if self.i < len(self.layerEntitySet.featureIds): + self.ent.set(self.layerEntitySet.layer, self.layerEntitySet.featureIds[self.i]) + self.i += 1 + return self.ent + else: + raise StopIteration + + def next(self): + return self.__next__() + + +# =============================================================================== +# QadLayerEntitySet entities of a layer class +# =============================================================================== +class QadLayerEntitySet(): + + def __init__(self, layerEntitySet = None): + + if layerEntitySet is not None: + self.set(layerEntitySet.layer, layerEntitySet.featureIds) + else: + self.layer = None + self.featureIds = [] + + + def whatIs(self): + return "LAYERENTITYSET" + + + def isInitialized(self): + if self.layer is None: + return False + else: + return True + + + def isEmpty(self): + if self.isInitialized() == False: + return True + return not self.featureIds + + + def count(self): + if self.isInitialized() == False: + return 0 + return len(self.featureIds) + + + def clear(self): + if self.isInitialized() == False: + return 0 + self.layer = None + del self.featureIds[:] + + + def getEntities(self): # ritorna un iterator + return QadLayerEntitySetIterator(self) + + + def layerId(self): + if self.isInitialized() == False: + return None + return self.layer.id() + + def crs(self): + if self.isInitialized() == False: + return None + return self.layer.crs() + + def set(self, layer, features = None): + if isinstance(layer, QgsVectorLayer): + self.layer = layer # il layer non si può copiare + self.featureIds = [] + if features is not None: + self.addFeatures(features) + else: # layer è una entità + return self.set(layer.layer, layer.featureIds) + + + def initByCurrentQgsSelectedFeatures(self, layer): + self.clear() + self.layer = layer + self.featureIds = self.layer.selectedFeatureIds() + + + def getFeature(self, featureId): + if self.isInitialized() == False: + return None + return qad_utils.getFeatureById(self.layer, featureId) + + + def getGeometry(self, featureId): + feature = self.getFeature(featureId) + if feature is None: + return None + return QgsGeometry(feature.geometry()) # fa una copia + + def setGeometry(self, featureId, geom): + feature = self.getFeature(featureId) + if feature is None: + return None + return feature.setGeometry(geom) + + + def getGeometryCollection(self, destCRS = None): + result = [] + if destCRS is not None: + coordTransform = QgsCoordinateTransform(self.layer.crs(), destCRS, QgsProject.instance()) # trasformo la geometria + for featureId in self.featureIds: + g = self.getGeometry(featureId) + if g is not None: + if destCRS is not None: + g.transform(coordTransform) + + # Per un baco sconosciuto quando trasformo la geometria se poi ne faccio un buffer + # il calcolo dà un risultato sbagliato quando la geometria é nuova o modificata + # (in cache del layer) e il sistema di coordinate é diverso de quello della mappa corrente + gType = g.type() + if g.isMultipart() == False: + if gType == QgsWkbTypes.PointGeometry: + g = QgsGeometry().fromPointXY(g.asPoint()) + elif gType == QgsWkbTypes.LineGeometry: + g = QgsGeometry().fromPolylineXY(g.asPolyline()) + elif gType == QgsWkbTypes.PolygonGeometry: + g = QgsGeometry().fromPolygonXY(g.asPolygon()) + else: + if gType == QgsWkbTypes.PointGeometry: + g = QgsGeometry().fromMultiPointXY(g.asMultiPoint()) + elif gType == QgsWkbTypes.LineGeometry: + g = QgsGeometry().fromMultiPolylineXY(g.asMultiPolyline()) + elif gType == QgsWkbTypes.PolygonGeometry: + g = QgsGeometry().fromMultiPolygonXY(g.asMultiPolygon()) + + result.append(g) + return result + + + def getFeatureCollection(self): + result = [] + for featureId in self.featureIds: + f = self.getFeature(featureId) + if f is not None: + result.append(f) + return result + + + def selectOnLayer(self, incremental = True): + if self.isInitialized() == True: + if len(self.featureIds) > 0: + if incremental == False: + self.layer.removeSelection() + + self.layer.select(self.featureIds) + else: + if incremental == False: + self.layer.removeSelection() + + + def deselectOnLayer(self): + if self.isInitialized() == True: + if len(self.featureIds) > 0: + self.layer.deselect(self.featureIds) + + + def containsFeature(self, feature): + if self.isInitialized() == False: + return False + + if type(feature) == QgsFeature: + return feature.id() in self.featureIds + else: + return feature in self.featureIds + + + def containsEntity(self, entity): + if self.isInitialized() == False: + return False + + if self.layerId() != entity.layerId(): + return False + return containsFeature(entity.featureId) + + + def addFeature(self, feature): + if self.isInitialized() == False: + return False + + if type(feature) == QgsFeature: + if self.containsFeature(feature.id()) == False: + self.featureIds.append(feature.id()) + return True + else: + return False + else: + if self.containsFeature(feature) == False: + self.featureIds.append(feature) + return True + else: + return False + + + def removeFeature(self, feature): + if self.isInitialized() == False: + return False + + try: + if type(feature) == QgsFeature: + return self.featureIds.remove(feature.id()) + else: + return self.featureIds.remove(feature) + except: + return None + + + def addEntity(self, entity): + if self.isInitialized() == False: + self.set(entity.layer) + else: + if self.layerId() != entity.layerId(): + return + + self.addFeature(entity.featureId) + + + def removeEntity(self, entity): + if self.isInitialized() == False: + return False + + if self.layerId() != entity.layerId(): + return False + + return self.removeFeature(entity.featureId) + + + def addFeatures(self, features): + if self.isInitialized() == False: + return None + for feature in features: + if type(feature) == QgsFeature: + self.addFeature(feature.id()) + else: + self.addFeature(feature) # featureId + + + def addLayerEntitySet(self, layerEntitySet): + if self.isInitialized() == False: + self.set(layerEntitySet.layer) + else: + if self.layerId() != layerEntitySet.layerId(): + return + self.addFeatures(layerEntitySet.featureIds) + + + def unite(self, layerEntitySet): + if self.isInitialized() == False: + return + + if self.layerId() == layerEntitySet.layerId(): + self.featureIds = list(set.union(set(self.featureIds), set(layerEntitySet.featureIds))) + + + def intersect(self, layerEntitySet): + if self.isInitialized() == False: + return + + if self.layerId() == layerEntitySet.layerId(): + self.featureIds = list(set(self.featureIds) & set(layerEntitySet.featureIds)) + + + def subtract(self, layerEntitySet): + if self.isInitialized() == False: + return + + if self.layerId() == layerEntitySet.layerId(): + self.featureIds = list(set(self.featureIds) - set(layerEntitySet.featureIds)) + + + def getNotExistingFeaturedIds(self): + featureIds = [] + if self.isInitialized() == True: + feature = QadEntity() + for featureId in self.featureIds: + feature.set(self.layer, featureId) + if not feature.exists(): + featureIds.append(featureId) + return featureIds + + + def removeNotExisting(self): + featureIds = self.getNotExistingFeaturedIds() + self.featureIds = list(set(self.featureIds) - set(featureIds)) + + + def boundingBox(self): + # ritorna il rettangolo di occupazione delle entita dl layer + result = None + geoms = self.getGeometryCollection() + for g in geoms: + if result is None: + result = g.boundingBox() + else: + result.combineExtentWith(g.boundingBox()) + return result + + +# =============================================================================== +# QadEntitySetIterator +# =============================================================================== +class QadEntitySetIterator(): + # classe per iterare le entità di un QadEntitySet + def __init__(self, entitySet): + self.i = 0 + self.entitySet = entitySet + self.layerEntitySetIterator = None + self.ent = QadEntity() + + def __iter__(self): + return self + + def __next__(self): + while self.i < len(self.entitySet.layerEntitySetList): + if self.layerEntitySetIterator is None: + self.layerEntitySetIterator = QadLayerEntitySetIterator(self.entitySet.layerEntitySetList[self.i]) + try: + return self.layerEntitySetIterator.next() + except StopIteration: + self.i += 1 + del self.layerEntitySetIterator + self.layerEntitySetIterator = None + raise StopIteration + + +# =============================================================================== +# QadEntitySet entities of a layers class +# =============================================================================== +class QadEntitySet(): + + def __init__(self, entitySet = None): + self.layerEntitySetList = [] + if entitySet is not None: + self.set(entitySet) + + + def whatIs(self): + return "ENTITYSET" + + + def isEmpty(self): + for layerEntitySet in self.layerEntitySetList: + if layerEntitySet.isEmpty() == False: + return False + return True + + + def count(self): + tot = 0 + for layerEntitySet in self.layerEntitySetList: + tot = tot + layerEntitySet.count() + return tot + + + def clear(self): + del self.layerEntitySetList[:] + + + def set(self, entitySet): + self.clear() + for layerEntitySet in entitySet.layerEntitySetList: + self.addLayerEntitySet(layerEntitySet) + + + def initByCurrentQgsSelectedFeatures(self, layers): + self.clear() + + for layer in layers: + if layer.selectedFeatureCount() > 0: + layerEntitySet = QadLayerEntitySet() + layerEntitySet.initByCurrentQgsSelectedFeatures(layer) + self.layerEntitySetList.append(layerEntitySet) + + + def findLayerEntitySet(self, layer): + if layer is None: + return None + if isinstance(layer, QgsVectorLayer): # layer + return self.findLayerEntitySet(layer.id()) + elif type(layer) == unicode: # id del layer + for layerEntitySet in self.layerEntitySetList: + if layerEntitySet.layerId() == layer: + return layerEntitySet + return None + else: # QadLayerEntitySet + return self.findLayerEntitySet(layer.layer) + + + def getLayerList(self): + layerList = [] + for layerEntitySet in self.layerEntitySetList: + layerList.append(layerEntitySet.layer) + return layerList + + + def getGeometryCollection(self, destCRS = None): + result = [] + for layerEntitySet in self.layerEntitySetList: + partial = layerEntitySet.getGeometryCollection(destCRS) + if partial is not None: + result.extend(partial) + return result + + + def getFeatureCollection(self): + result = [] + for layerEntitySet in self.layerEntitySetList: + partial = layerEntitySet.getFeatureCollection() + if partial is not None: + result.extend(partial) + return result + + + def selectOnLayer(self, incremental = True): + for layerEntitySet in self.layerEntitySetList: + layerEntitySet.selectOnLayer(incremental) + + + def deselectOnLayer(self): + for layerEntitySet in self.layerEntitySetList: + layerEntitySet.deselectOnLayer() + + + def containsEntity(self, entity): + layerEntitySet = self.findLayerEntitySet(entity.layer) + if layerEntitySet is None: + return False + return layerEntitySet.containsFeature(entity.featureId) + + + def addEntity(self, entity): + if entity is None or entity.isInitialized() == False: + return False + layerEntitySet = self.findLayerEntitySet(entity.layer) + if layerEntitySet is None: + layerEntitySet = QadLayerEntitySet() + layerEntitySet.set(entity.layer) + self.layerEntitySetList.append(layerEntitySet) + return layerEntitySet.addFeature(entity.featureId) + + + def removeEntity(self, entity): + layerEntitySet = self.findLayerEntitySet(entity.layer) + if layerEntitySet is None: + return False + return layerEntitySet.removeFeature(entity.featureId) + + + def removeLayerEntitySet(self, layer): + i = 0 + for layerEntitySet in self.layerEntitySetList: + if layerEntitySet.id() == layer.id(): + del layerEntitySet[i] + return True + i = i + 1 + return False + + + def addLayerEntitySet(self, layerEntitySet): + _layerEntitySet = self.findLayerEntitySet(layerEntitySet) + if _layerEntitySet is None: + _layerEntitySet = QadLayerEntitySet() + _layerEntitySet.set(layerEntitySet.layer) + self.layerEntitySetList.append(_layerEntitySet) + _layerEntitySet.addFeatures(layerEntitySet.featureIds) + + + def unite(self, entitySet): + if entitySet is None: + return + for layerEntitySet in entitySet.layerEntitySetList: + _layerEntitySet = self.findLayerEntitySet(layerEntitySet) + if _layerEntitySet is None: + _layerEntitySet = QadLayerEntitySet() + _layerEntitySet.set(layerEntitySet.layer) + self.layerEntitySetList.append(_layerEntitySet) + _layerEntitySet.unite(layerEntitySet) + + + def intersect(self, entitySet): + if entitySet is None: + return + for i in range(len(self.layerEntitySetList) - 1, -1, -1): + _layerEntitySet = self.layerEntitySetList[i] + layerEntitySet = entitySet.findLayerEntitySet(_layerEntitySet) + if layerEntitySet is None: + del self.layerEntitySetList[i] + else: + _layerEntitySet.intersect(layerEntitySet) + + + def subtract(self, entitySet): + if entitySet is None: + return + for _layerEntitySet in self.layerEntitySetList: + layerEntitySet = entitySet.findLayerEntitySet(_layerEntitySet) + if layerEntitySet is not None: + _layerEntitySet.subtract(layerEntitySet) + + + def removeNotExisting(self): + for layerEntitySet in self.layerEntitySetList: + layerEntitySet.removeNotExisting() + + + def removeNotEditable(self): + for i in range(len(self.layerEntitySetList) - 1, -1, -1): + layerEntitySet = self.layerEntitySetList[i] + if layerEntitySet.layer.isEditable() == False: + del self.layerEntitySetList[i] + + + def removeGeomType(self, type): + for i in range(len(self.layerEntitySetList) - 1, -1, -1): + layerEntitySet = self.layerEntitySetList[i] + if layerEntitySet.layer.geometryType() == type: + del self.layerEntitySetList[i] + + + def purge(self): + # rimuove i layer con zero oggetti + for i in range(len(self.layerEntitySetList) - 1, -1, -1): + layerEntitySet = self.layerEntitySetList[i] + if layerEntitySet.count() == 0: + del self.layerEntitySetList[i] + + + def boundingBox(self, destCRS): + # ritorna il rettangolo di occupazione del selset + result = None + for layerEntitySet in self.layerEntitySetList: + partial = layerEntitySet.boundingBox() + if partial is not None: + coordTransform = QgsCoordinateTransform(layerEntitySet.crs(), destCRS, QgsProject.instance()) # trasformo la geometria + if result is None: + result = QgsRectangle(coordTransform.transform(partial.xMinimum(), partial.yMinimum()), \ + coordTransform.transform(partial.xMaximum(), partial.yMaximum())) + else: + result.combineExtentWith(QgsRectangle(coordTransform.transform(partial.xMinimum(), partial.yMinimum()), \ + coordTransform.transform(partial.xMaximum(), partial.yMaximum()))) + return result + + +# =============================================================================== +# getSelSet +# =============================================================================== +def getSelSet(mode, mQgsMapTool, points = None, \ + layersToCheck = None, checkPointLayer = True, checkLineLayer = True, checkPolygonLayer = True, + onlyEditableLayers = False): + """ + dato un QgsMapTool, una modalità di selezione e una lista opzionale di punti (in map coordinates), + la funzione cerca le entità. + mode = "C" -> Crossing selection (inside and crossing) + "CO" -> Crossing objects (inside and crossing) + "CP" -> Crossing polygon (inside and crossing) + "F" -> Fence selection (crossing) + "W" -> Window selection (inside) + "WO" -> Window objects (inside) + "WP" -> Windows polygon (inside) + "X" -> all + layersToCheck = opzionale, lista dei layer in cui cercare + checkPointLayer = opzionale, considera i layer di tipo punto + checkLineLayer = opzionale, considera i layer di tipo linea + checkPolygonLayer = opzionale, considera i layer di tipo poligono + onlyEditableLayers = opzionale, considera i layer editabili + Restituisce un QadEntitySet in caso di successo altrimenti None + """ + + if checkPointLayer == False and checkLineLayer == False and checkPolygonLayer == False: + return None + + entity = QadEntity() + result = QadEntitySet() + feature = QgsFeature() + + #QApplication.setOverrideCursor(Qt.WaitCursor) + + if layersToCheck is None: + # Tutti i layer visibili + _layers = qad_utils.getVisibleVectorLayers(mQgsMapTool.canvas) # Tutti i layer vettoriali visibili + else: + # solo la lista passata come parametro + _layers = layersToCheck + + for layer in _layers: # ciclo sui layer + # considero solo i layer vettoriali che sono filtrati per tipo + if (layer.type() == QgsMapLayer.VectorLayer) and \ + ((layer.geometryType() == QgsWkbTypes.PointGeometry and checkPointLayer == True) or \ + (layer.geometryType() == QgsWkbTypes.LineGeometry and checkLineLayer == True) or \ + (layer.geometryType() == QgsWkbTypes.PolygonGeometry and checkPolygonLayer == True)) and \ + (onlyEditableLayers == False or layer.isEditable()): + provider = layer.dataProvider() + + if mode.upper() == "X": # take all features + # fetchAttributes, fetchGeometry, rectangle, useIntersect + for feature in layer.getFeatures(qad_utils.getFeatureRequest([], True, None, False)): + entity.set(layer, feature.id()) + result.addEntity(entity) + + elif mode.upper() == "C": # crossing selection + p1 = mQgsMapTool.toLayerCoordinates(layer, points[0]) + p2 = mQgsMapTool.toLayerCoordinates(layer, points[1]) + selectRect = QgsRectangle(p1, p2) + # Select features in rectangle + # fetchAttributes, fetchGeometry, rectangle, useIntersect + for feature in layer.getFeatures(qad_utils.getFeatureRequest([], True, selectRect, True)): + entity.set(layer, feature.id()) + result.addEntity(entity) + + elif mode.upper() == "W": # window selection + p1 = mQgsMapTool.toLayerCoordinates(layer, points[0]) + p2 = mQgsMapTool.toLayerCoordinates(layer, points[1]) + selectRect = QgsRectangle(p1, p2) + g = QgsGeometry.fromRect(selectRect) + # Select features in rectangle + # fetchAttributes, fetchGeometry, rectangle, useIntersect + for feature in layer.getFeatures(qad_utils.getFeatureRequest([], True, selectRect, True)): + # solo le feature completamente interne al rettangolo + if g.contains(feature.geometry()): + entity.set(layer, feature.id()) + result.addEntity(entity) + + elif mode.upper() == "CP": # crossing polygon + polyline = [] + for point in points: + polyline.append(mQgsMapTool.toLayerCoordinates(layer, point)) + + g = QgsGeometry.fromPolygonXY([polyline]) + # Select features in the polygon bounding box + # fetchAttributes, fetchGeometry, rectangle, useIntersect + for feature in layer.getFeatures(qad_utils.getFeatureRequest([], True, g.boundingBox(), True)): + # solo le feature intersecanti il poligono + if g.intersects(feature.geometry()): + entity.set(layer, feature.id()) + result.addEntity(entity) + + elif mode.upper() == "WP": # windows polygon + polyline = [] + for point in points: + polyline.append(mQgsMapTool.toLayerCoordinates(layer, point)) + + g = QgsGeometry.fromPolygonXY([polyline]) + # Select features in the polygon bounding box + # fetchAttributes, fetchGeometry, rectangle, useIntersect + for feature in layer.getFeatures(qad_utils.getFeatureRequest([], True, g.boundingBox(), True)): + # solo le feature completamente interne al poligono + if g.contains(feature.geometry()): + entity.set(layer, feature.id()) + result.addEntity(entity) + + elif mode.upper() == "CO": # crossing object + # points é in questo caso un QgsGeometry + g = QgsGeometry(points) + if mQgsMapTool.canvas.mapSettings().destinationCrs() != layer.crs(): + coordTransform = QgsCoordinateTransform(mQgsMapTool.canvas.mapSettings().destinationCrs(), \ + layer.crs(), \ + QgsProject.instance()) # trasformo la geometria + g.transform(coordTransform) + + # Select features in the object bounding box + if g.isMultipart() == False and g.type() == QgsWkbTypes.PointGeometry: + Tolerance = QadVariables.get(QadMsg.translate("Environment variables", "PICKBOX")) # leggo la tolleranza + ToleranceInMapUnits = QgsTolerance.toleranceInMapUnits(Tolerance, layer, \ + mQgsMapTool.canvas.mapSettings(), \ + QgsTolerance.Pixels) + + pt = g.asPoint() + # QgsRectangle (double xmin=0, double ymin=0, double xmax=0, double ymax=0) + selectRect = QgsRectangle(pt.x() - ToleranceInMapUnits, pt.y() - ToleranceInMapUnits, \ + pt.x() + ToleranceInMapUnits, pt.y() + ToleranceInMapUnits) + # fetchAttributes, fetchGeometry, rectangle, useIntersect + request = qad_utils.getFeatureRequest([], True, selectRect, True) + else: + # fetchAttributes, fetchGeometry, rectangle, useIntersect + request = qad_utils.getFeatureRequest([], True, g.boundingBox(), True) + + # fetchAttributes, fetchGeometry, rectangle, useIntersect + for feature in layer.getFeatures(request): + # solo le feature intersecanti l'oggetto + if g.intersects(feature.geometry()): + entity.set(layer, feature.id()) + result.addEntity(entity) + + elif mode.upper() == "WO": # windows object + # points é in questo caso un QgsGeometry + g = QgsGeometry(points) + if mQgsMapTool.canvas.mapSettings().destinationCrs() != layer.crs(): + coordTransform = QgsCoordinateTransform(mQgsMapTool.canvas.mapSettings().destinationCrs(), \ + layer.crs(), \ + QgsProject.instance()) # trasformo la geometria + g.transform(coordTransform) + + # Select features in the object bounding box + if g.isMultipart() == False and g.type() == QgsWkbTypes.PointGeometry: + Tolerance = QadVariables.get(QadMsg.translate("Environment variables", "PICKBOX")) # leggo la tolleranza + ToleranceInMapUnits = QgsTolerance.toleranceInMapUnits(Tolerance, layer, \ + mQgsMapTool.canvas.mapSettings(), \ + QgsTolerance.Pixels) + + pt = g.asPoint() + selectRect = QgsRectangle(pt.x() - ToleranceInMapUnits, pt.y() - ToleranceInMapUnits, \ + pt.x() + ToleranceInMapUnits, pt.y() + ToleranceInMapUnits) + # fetchAttributes, fetchGeometry, rectangle, useIntersect + request = qad_utils.getFeatureRequest([], True, selectRect, True) + else: + # fetchAttributes, fetchGeometry, rectangle, useIntersect + request = qad_utils.getFeatureRequest([], True, g.boundingBox(), True) + + gList = [] + if g.type() == QgsWkbTypes.LineGeometry: + polygon = QadPolygon() + + if g.isMultipart() == False: + # se g è una linestring allora provo a convertirla in un poligono + if polygon.fromPolygon([g.asPolyline()]) == True: + gList.append(QgsGeometry.fromPolygonXY(polygon.asPolygon())) + else: + # se g è una multilinestring allora provo a convertirla in una lista di poligoni se le linestring sono chiuse + multipolyline = g.asMultiPolyline() + for polyline in multipolyline: + if polygon.fromPolygon([polyline]) == True: + gList.append(QgsGeometry.fromPolygonXY(polygon.asPolygon())) + else: # se non è una linestring + if g.type() == QgsWkbTypes.PolygonGeometry: + gList.append(g) + + # solo le feature completamente interne all'oggetto + for feature in layer.getFeatures(request): + for g in gList: + if g.contains(feature.geometry()): + entity.set(layer, feature.id()) + result.addEntity(entity) + + elif mode.upper() == "F": # fence + polyline = [] + for point in points: + polyline.append(mQgsMapTool.toLayerCoordinates(layer, point)) + + g = QgsGeometry.fromPolylineXY(polyline) + # Select features in the polyline bounding box + # fetchAttributes, fetchGeometry, rectangle, useIntersect + for feature in layer.getFeatures(qad_utils.getFeatureRequest([], True, g.boundingBox(), True)): + # solo le feature che intersecano la polyline + if g.intersects(feature.geometry()): + entity.set(layer, feature.id()) + result.addEntity(entity) + + #QApplication.restoreOverrideCursor() + return result diff --git a/qad_entsel_cmd.py b/qad_entsel_cmd.py deleted file mode 100644 index 507586a0..00000000 --- a/qad_entsel_cmd.py +++ /dev/null @@ -1,192 +0,0 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - comando da inserire in altri comandi per la selezione di una feature - - ------------------- - begin : 2013-09-18 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -from qad_generic_cmd import QadCommandClass -from qad_msg import QadMsg -from qad_textwindow import * -from qad_entity import * -from qad_getpoint import * -import qad_utils -from qad_dim import QadDimStyles - - -#=============================================================================== -# QadEntSelClass -#=============================================================================== -class QadEntSelClass(QadCommandClass): - """ - Questa classe seleziona un'entità. Non è in grado di selezionare una quotatura ma solo un componente di una quotatura. - """ - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadEntSelClass(self.plugIn) - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - self.entity = QadEntity() - self.point = None - # opzioni per limitare gli oggetti da selezionare - self.onlyEditableLayers = False - self.checkPointLayer = True - self.checkLineLayer = True - self.checkPolygonLayer = True - self.checkDimLayers = True - self.selDimEntity = False # per restituire o meno un oggetto QadDimEntity - self.msg = QadMsg.translate("QAD", "Select object: ") - - def __del__(self): - QadCommandClass.__del__(self) - if self.entity.isInitialized(): - self.entity.deselectOnLayer() - - - #============================================================================ - # setEntity - #============================================================================ - def setEntity(self, layer, fid): - del self.entity - if self.selDimEntity: # se è possibile restituire un oggetto QadDimEntity - # verifico se l'entità appartiene ad uno stile di quotatura - self.entity = QadDimStyles.getDimEntity(layer, fid) - if self.entity is None: # se non è una quota - self.entity = QadEntity() - self.entity.set(layer, fid) - else: - self.entity = QadEntity() - self.entity.set(layer, fid) - - self.entity.selectOnLayer() - - - #============================================================================ - # getLayersToCheck - #============================================================================ - def getLayersToCheck(self): - layerList = [] - for layer in self.plugIn.canvas.layers(): # Tutti i layer visibili visibili - # considero solo i layer vettoriali che sono filtrati per tipo - if (layer.type() == QgsMapLayer.VectorLayer) and \ - ((layer.geometryType() == QGis.Point and self.checkPointLayer == True) or \ - (layer.geometryType() == QGis.Line and self.checkLineLayer == True) or \ - (layer.geometryType() == QGis.Polygon and self.checkPolygonLayer == True)) and \ - (self.onlyEditableLayers == False or layer.isEditable()): - # se devo includere i layers delle quotature - if self.checkDimLayers == True or \ - len(QadDimStyles.getDimListByLayer(layer)) == 0: - layerList.append(layer) - - return layerList - - - def run(self, msgMapTool = False, msg = None): - if self.plugIn.canvas.mapRenderer().destinationCrs().geographicFlag(): - self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) - return True # fine comando - - #========================================================================= - # RICHIESTA PUNTO o ENTITA' - if self.step == 0: # inizio del comando - # imposto il map tool - self.getPointMapTool().setSelectionMode(QadGetPointSelectionModeEnum.ENTITY_SELECTION) - # imposto i layer da controllare sul maptool - self.getPointMapTool().layersToCheck = self.getLayersToCheck() - - keyWords = QadMsg.translate("Command_ENTSEL", "Last") - - englishKeyWords = "Last" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o enter o una parola chiave - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(self.msg, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - None, \ - keyWords, QadInputModeEnum.NONE) - - self.step = 1 - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA PUNTO o ENTITA' - elif self.step == 1: # dopo aver atteso un punto si riavvia il comando - entity = None - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - if self.getPointMapTool().entity.isInitialized(): - entity = self.getPointMapTool().entity - else: # il punto arriva come parametro della funzione - value = msg - - if value is None: - return True # fine comando - - if type(value) == unicode: - if value == QadMsg.translate("Command_ENTSEL", "Last") or value == "Last": - # Seleziona l'ultima entità inserita - lastEnt = self.plugIn.getLastEntity() - if lastEnt is not None: - # controllo sul layer - if self.onlyEditableLayers == False or lastEnt.layer.isEditable() == True: - # controllo sul tipo - if (self.checkPointLayer == True and lastEnt.layer.geometryType() == QGis.Point) or \ - (self.checkLineLayer == True and lastEnt.layer.geometryType() == QGis.Line) or \ - (self.checkPolygonLayer == True and lastEnt.layer.geometryType() == QGis.Polygon): - # controllo su layer delle quotature - if self.checkDimLayers == True or lastEnt.isDimensionComponent() == False: - self.setEntity(lastEnt.layer, lastEnt.featureId) - elif type(value) == QgsPoint: - if entity is None: - # cerco se ci sono entità nel punto indicato - result = qad_utils.getEntSel(self.getPointMapTool().toCanvasCoordinates(value), - self.getPointMapTool(), \ - self.getLayersToCheck()) - if result is not None: - feature = result[0] - layer = result[1] - self.setEntity(layer, feature.id()) - else: - self.setEntity(entity.layer, entity.featureId) - - self.point = value - - return True # fine comando - diff --git a/qad_extend_cmd.py b/qad_extend_cmd.py deleted file mode 100644 index 2f9da33f..00000000 --- a/qad_extend_cmd.py +++ /dev/null @@ -1,457 +0,0 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - comando EXTEND per estendere o tagliare oggetti grafici - - ------------------- - begin : 2013-07-15 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -from qad_getpoint import * -from qad_textwindow import * -from qad_pline_cmd import QadPLINECommandClass -from qad_rectangle_cmd import QadRECTANGLECommandClass -from qad_generic_cmd import QadCommandClass -from qad_msg import QadMsg -import qad_utils -import qad_layer -from qad_ssget_cmd import QadSSGetClass -from qad_dim import QadDimStyles - - -# Classe che gestisce il comando EXTEND -class QadEXTENDCommandClass(QadCommandClass): - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadEXTENDCommandClass(self.plugIn) - - def getName(self): - return QadMsg.translate("Command_list", "EXTEND") - - def getEnglishName(self): - return "EXTEND" - - def connectQAction(self, action): - QObject.connect(action, SIGNAL("triggered()"), self.plugIn.runEXTENDCommand) - - def getIcon(self): - return QIcon(":/plugins/qad/icons/extend.png") - - def getNote(self): - # impostare le note esplicative del comando - return QadMsg.translate("Command_EXTEND", "Extends (or trims) objects to meet the edges of other objects.") - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - self.SSGetClass = QadSSGetClass(plugIn) - self.PLINECommand = None - self.RECTANGLECommand = None - self.entitySet = QadEntitySet() # entità da estendere o tagliare - self.limitEntitySet = QadEntitySet() # entità che fanno da limiti - self.edgeMode = QadVariables.get(QadMsg.translate("Environment variables", "EDGEMODE")) - self.defaultValue = None # usato per gestire il tasto dx del mouse - self.nOperationsToUndo = 0 - - def __del__(self): - QadCommandClass.__del__(self) - - def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): - if self.step == 3: # quando si é in fase di disegno linea - return self.PLINECommand.getPointMapTool(drawMode) - elif self.step == 4: # quando si é in fase di disegno rettangolo - return self.RECTANGLECommand.getPointMapTool(drawMode) - else: - return QadCommandClass.getPointMapTool(self, drawMode) - - #============================================================================ - # extendFeatures - #============================================================================ - def extendFeatures(self, geom, toExtend): - # geom è in map coordinates - tolerance2ApproxCurve = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) - LineTempLayer = None - self.plugIn.beginEditCommand("Feature extended" if toExtend else "Feature trimmed", \ - self.entitySet.getLayerList()) - - for layerEntitySet in self.entitySet.layerEntitySetList: - layer = layerEntitySet.layer - - for featureId in layerEntitySet.featureIds: - f = qad_utils.getFeatureById(layer, featureId) - if f is None: - continue - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - f_geom = self.layerToMapCoordinates(layer, f.geometry()) - - if geom.type() == QGis.Point: - # ritorna una tupla (, - # - # - # ) - dummy = qad_utils.closestSegmentWithContext(geom.asPoint(), f_geom) - if dummy[1] is not None: - intPts = [dummy[1]] - else: - intPts = qad_utils.getIntersectionPoints(geom, f_geom) - - for intPt in intPts: - if toExtend: - newGeom = qad_utils.extendQgsGeometry(self.plugIn.canvas.mapRenderer().destinationCrs(), f_geom, intPt, \ - self.limitEntitySet, self.edgeMode, \ - tolerance2ApproxCurve) - if newGeom is not None: - # aggiorno la feature con la geometria estesa - extendedFeature = QgsFeature(f) - # trasformo la geometria nel crs del layer - extendedFeature.setGeometry(self.mapToLayerCoordinates(layer, newGeom)) - # plugIn, layer, feature, refresh, check_validity - if qad_layer.updateFeatureToLayer(self.plugIn, layer, extendedFeature, False, False) == False: - self.plugIn.destroyEditCommand() - return - else: # trim - result = qad_utils.trimQgsGeometry(self.plugIn.canvas.mapRenderer().destinationCrs(), f_geom, intPt, \ - self.limitEntitySet, self.edgeMode, \ - tolerance2ApproxCurve) - if result is not None: - line1 = result[0] - line2 = result[1] - atSubGeom = result[2] - if layer.geometryType() == QGis.Line: - updGeom = qad_utils.setSubGeom(f_geom, line1, atSubGeom) - if updGeom is None: - self.plugIn.destroyEditCommand() - return - trimmedFeature1 = QgsFeature(f) - # trasformo la geometria nel crs del layer - trimmedFeature1.setGeometry(self.mapToLayerCoordinates(layer, updGeom)) - # plugIn, layer, feature, refresh, check_validity - if qad_layer.updateFeatureToLayer(self.plugIn, layer, trimmedFeature1, False, False) == False: - self.plugIn.destroyEditCommand() - return - if line2 is not None: - trimmedFeature2 = QgsFeature(f) - # trasformo la geometria nel crs del layer - trimmedFeature2.setGeometry(self.mapToLayerCoordinates(layer, line2)) - # plugIn, layer, feature, coordTransform, refresh, check_validity - if qad_layer.addFeatureToLayer(self.plugIn, layer, trimmedFeature2, None, False, False) == False: - self.plugIn.destroyEditCommand() - return - - else: - # aggiungo le linee nei layer temporanei di QAD - if LineTempLayer is None: - LineTempLayer = qad_layer.createQADTempLayer(self.plugIn, QGis.Line) - self.plugIn.addLayerToLastEditCommand("Feature trimmed", LineTempLayer) - - lineGeoms = [line1] - if line2 is not None: - lineGeoms.append(line2) - - # trasformo la geometria in quella dei layer temporanei - # plugIn, pointGeoms, lineGeoms, polygonGeoms, coord, refresh - if qad_layer.addGeometriesToQADTempLayers(self.plugIn, None, lineGeoms, None, None, False) == False: - self.plugIn.destroyEditCommand() - return - - updGeom = qad_utils.delSubGeom(f_geom, atSubGeom) - - if updGeom is None or updGeom.isGeosEmpty(): # da cancellare - # plugIn, layer, feature id, refresh - if qad_layer.deleteFeatureToLayer(self.plugIn, layer, f.id(), False) == False: - self.plugIn.destroyEditCommand() - return - else: - trimmedFeature1 = QgsFeature(f) - # trasformo la geometria nel crs del layer - trimmedFeature1.setGeometry(self.mapToLayerCoordinates(layer, updGeom)) - # plugIn, layer, feature, refresh, check_validity - if qad_layer.updateFeatureToLayer(self.plugIn, layer, trimmedFeature1, False, False) == False: - self.plugIn.destroyEditCommand() - return - - self.plugIn.endEditCommand() - self.nOperationsToUndo = self.nOperationsToUndo + 1 - - - #============================================================================ - # waitForObjectSel - #============================================================================ - def waitForObjectSel(self): - self.step = 2 - # imposto il map tool - self.getPointMapTool().setSelectionMode(QadGetPointSelectionModeEnum.ENTITY_SELECTION_DYNAMIC) - # solo layer lineari editabili che non appartengano a quote - layerList = [] - for layer in self.plugIn.canvas.layers(): - if layer.type() == QgsMapLayer.VectorLayer and layer.geometryType() == QGis.Line and \ - layer.isEditable(): - if len(QadDimStyles.getDimListByLayer(layer)) == 0: - layerList.append(layer) - - self.getPointMapTool().layersToCheck = layerList - self.getPointMapTool().setDrawMode(QadGetPointDrawModeEnum.NONE) - self.getPointMapTool().onlyEditableLayers = True - - keyWords = QadMsg.translate("Command_EXTEND", "Fence") + "/" + \ - QadMsg.translate("Command_EXTEND", "Crossing") + "/" + \ - QadMsg.translate("Command_EXTEND", "Edge") + "/" + \ - QadMsg.translate("Command_EXTEND", "Undo") - prompt = QadMsg.translate("Command_EXTEND", "Select the object to extend or shift-select to trim or [{0}]: ").format(keyWords) - - englishKeyWords = "Fence" + "/" + "Crossing" + "/" + "Edge" + "/" + "Undo" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o enter o una parola chiave - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - None, \ - keyWords, QadInputModeEnum.NONE) - - - #============================================================================ - # run - #============================================================================ - def run(self, msgMapTool = False, msg = None): - if self.plugIn.canvas.mapRenderer().destinationCrs().geographicFlag(): - self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) - return True # fine comando - - #========================================================================= - # RICHIESTA SELEZIONE OGGETTI LIMITI - if self.step == 0: # inizio del comando - CurrSettingsMsg = QadMsg.translate("QAD", "\nCurrent settings: ") - if self.edgeMode == 0: # 0 = nessuna estensione - CurrSettingsMsg = CurrSettingsMsg + QadMsg.translate("Command_EXTEND", "Edge = No extend") - else: - CurrSettingsMsg = CurrSettingsMsg + QadMsg.translate("Command_EXTEND", "Edge = Extend") - - self.showMsg(CurrSettingsMsg) - self.showMsg(QadMsg.translate("Command_EXTEND", "\nSelect extension limits...")) - - if self.SSGetClass.run(msgMapTool, msg) == True: - # selezione terminata - self.step = 1 - return self.run(msgMapTool, msg) - - #========================================================================= - # RISPOSTA ALLA SELEZIONE OGGETTI LIMITI - elif self.step == 1: - self.limitEntitySet.set(self.SSGetClass.entitySet) - - if self.limitEntitySet.count() == 0: - return True # fine comando - - # si appresta ad attendere la selezione degli oggetti da estendere/tagliare - self.waitForObjectSel() - return False - - #========================================================================= - # RISPOSTA ALLA SELEZIONE OGGETTI DA ESTENDERE - elif self.step == 2: - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - else: - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("Command_EXTEND", "Fence") or value == "Fence": - # Seleziona tutti gli oggetti che intersecano una polilinea - self.PLINECommand = QadPLINECommandClass(self.plugIn) - # se questo flag = True il comando serve all'interno di un altro comando per disegnare una linea - # che non verrà salvata su un layer - self.PLINECommand.virtualCmd = True - self.PLINECommand.run(msgMapTool, msg) - self.step = 3 - return False - elif value == QadMsg.translate("Command_EXTEND", "Crossing") or value == "Crossing": - # Seleziona tutti gli oggetti che intersecano un rettangolo - self.RECTANGLECommand = QadRECTANGLECommandClass(self.plugIn) - # se questo flag = True il comando serve all'interno di un altro comando per disegnare una linea - # che non verrà salvata su un layer - self.RECTANGLECommand.virtualCmd = True - self.RECTANGLECommand.run(msgMapTool, msg) - self.step = 4 - return False - elif value == QadMsg.translate("Command_EXTEND", "Edge") or value == "Edge": - # Per estendere un oggetto usando anche le estensioni degli oggetti di riferimento - # vedi variabile EDGEMODE - keyWords = QadMsg.translate("Command_EXTEND", "Extend") + "/" + \ - QadMsg.translate("Command_EXTEND", "No extend") - - if self.edgeMode == 0: # 0 = nessuna estensione - self.defaultValue = QadMsg.translate("Command_EXTEND", "No extend") - else: - self.defaultValue = QadMsg.translate("Command_EXTEND", "Extend") - prompt = QadMsg.translate("Command_EXTEND", "Specify an extension mode [{0}] <{1}>: ").format(keyWords, self.defaultValue) - - englishKeyWords = "Extend" + "/" + "No extend" - keyWords += "_" + englishKeyWords - # si appresta ad attendere enter o una parola chiave - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(prompt, \ - QadInputTypeEnum.KEYWORDS, \ - self.defaultValue, \ - keyWords, QadInputModeEnum.NONE) - self.step = 5 - return False - elif value == QadMsg.translate("Command_EXTEND", "Undo") or value == "Undo": - if self.nOperationsToUndo > 0: - self.nOperationsToUndo = self.nOperationsToUndo - 1 - self.plugIn.undoEditCommand() - else: - self.showMsg(QadMsg.translate("QAD", "\nThe command has been canceled.")) - elif type(value) == QgsPoint: # se é stato selezionato un punto - self.entitySet.clear() - if self.getPointMapTool().entity.isInitialized(): - self.entitySet.addEntity(self.getPointMapTool().entity) - ToExtend = True if self.getPointMapTool().shiftKey == False else False - self.extendFeatures(QgsGeometry.fromPoint(value), ToExtend) - else: - # cerco se ci sono entità nel punto indicato considerando - # solo layer lineari editabili che non appartengano a quote - layerList = [] - for layer in self.plugIn.canvas.layers(): - if layer.type() == QgsMapLayer.VectorLayer and layer.geometryType() == QGis.Line and layer.isEditable(): - if len(QadDimStyles.getDimListByLayer(layer)) == 0: - layerList.append(layer) - - result = qad_utils.getEntSel(self.getPointMapTool().toCanvasCoordinates(value), - self.getPointMapTool(), \ - layerList) - if result is not None: - feature = result[0] - layer = result[1] - point = result[2] - self.entitySet.addEntity(QadEntity().set(layer, feature.id())) - self.extendFeatures(QgsGeometry.fromPoint(point), True) - else: - return True # fine comando - - # si appresta ad attendere la selezione degli oggetti da estendere/tagliare - self.waitForObjectSel() - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA PUNTO PER MODALITA' INTERCETTA (da step = 2) - elif self.step == 3: # dopo aver atteso un punto si riavvia il comando - if self.PLINECommand.run(msgMapTool, msg) == True: - if len(self.PLINECommand.vertices) > 1: - if msgMapTool == True: # se la polilinea arriva da una selezione grafica - ToExtend = True if self.getPointMapTool().shiftKey == False else False - else: - ToExtend = True - - # cerco tutte le geometrie passanti per la polilinea considerando - # solo layer lineari editabili che non appartengano a quote - layerList = [] - for layer in self.plugIn.canvas.layers(): - if layer.type() == QgsMapLayer.VectorLayer and layer.geometryType() == QGis.Line and layer.isEditable(): - if len(QadDimStyles.getDimListByLayer(layer)) == 0: - layerList.append(layer) - - self.entitySet = qad_utils.getSelSet("F", self.getPointMapTool(), self.PLINECommand.vertices, \ - layerList) - self.extendFeatures(QgsGeometry.fromPolyline(self.PLINECommand.vertices), ToExtend) - del self.PLINECommand - self.PLINECommand = None - - # si appresta ad attendere la selezione degli oggetti da estendere/tagliare - self.waitForObjectSel() - self.getPointMapTool().refreshSnapType() # aggiorno lo snapType che può essere variato dal maptool di pline - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA PUNTO PER MODALITA' INTERSECA (da step = 2) - elif self.step == 4: # dopo aver atteso un punto si riavvia il comando - if self.RECTANGLECommand.run(msgMapTool, msg) == True: - if len(self.RECTANGLECommand.vertices) > 1: - if msgMapTool == True: # se la polilinea arriva da una selezione grafica - ToExtend = True if self.getPointMapTool().shiftKey == False else False - else: - ToExtend = True - - # cerco tutte le geometrie passanti per il rettangolo considerando - # solo layer lineari editabili che non appartengano a quote - layerList = [] - for layer in self.plugIn.canvas.layers(): - if layer.type() == QgsMapLayer.VectorLayer and layer.geometryType() == QGis.Line and layer.isEditable(): - if len(QadDimStyles.getDimListByLayer(layer)) == 0: - layerList.append(layer) - - self.entitySet = qad_utils.getSelSet("F", self.getPointMapTool(), self.RECTANGLECommand.vertices, \ - layerList) - self.extendFeatures(QgsGeometry.fromPolyline(self.RECTANGLECommand.vertices), ToExtend) - del self.RECTANGLECommand - self.RECTANGLECommand = None - - # si appresta ad attendere la selezione degli oggetti da estendere/tagliare - self.waitForObjectSel() - self.getPointMapTool().refreshSnapType() # aggiorno lo snapType che può essere variato dal maptool di rectangle - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DI TIPO DI ESTENSIONE (da step = 2) - elif self.step == 5: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - value = self.defaultValue - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - else: # il valore arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("Command_EXTEND", "No extend") or value == "No extend": - self.edgeMode = 0 - QadVariables.set(QadMsg.translate("Environment variables", "EDGEMODE"), self.edgeMode) - QadVariables.save() - # si appresta ad attendere la selezione degli oggetti da estendere/tagliare - self.waitForObjectSel() - elif value == QadMsg.translate("Command_EXTEND", "Extend") or value == "Extend": - self.edgeMode = 1 - QadVariables.set(QadMsg.translate("Environment variables", "EDGEMODE"), self.edgeMode) - QadVariables.save() - # si appresta ad attendere la selezione degli oggetti da estendere/tagliare - self.waitForObjectSel() - - return False diff --git a/qad_extend_trim_fun.py b/qad_extend_trim_fun.py new file mode 100644 index 00000000..5d9c4f8b --- /dev/null +++ b/qad_extend_trim_fun.py @@ -0,0 +1,300 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + funzioni per extend e trim + + ------------------- + begin : 2019-05-20 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.PyQt.QtCore import * +from qgis.PyQt.QtGui import * +from qgis.core import * + +from .qad_multi_geom import * +from .qad_geom_relations import * +from .qad_entity import * +from .qad_arc import * +from .qad_ellipse_arc import * + + +# =============================================================================== +# extendQadGeometry +# =============================================================================== +def extendQadGeometry(qadGeom, pt, limitEntitySet, edgeMode): + """ + la funzione estende una geometria QAD (lineare) nella parte iniziale o finale fino ad + incontrare l'oggetto più vicino nel gruppo secondo la modalità . + = geometria lineare QAD da estendere + = punto che indica la parte di quell'oggetto che deve essere estesa + = gruppo di entità che serve da limite di estensione + se = 0 si deve estendere la geometria fino ad incontrare l'oggetto più vicino + se = 1 si deve estendere la geometria fino ad incontrare l'oggetto più vicino o + anche il suo prolungamento + """ + # la funzione ritorna una lista con + # ( + # + # + # + # + # + result = getQadGeomClosestVertex(qadGeom, pt) + nearPt = result[1] + atGeom = result[2] + atSubGeom = result[3] + subGeom = getQadGeomAt(qadGeom, atGeom, atSubGeom) + + if not isLinearQadGeom(subGeom): return None + middleLength = subGeom.length() / 2 + distFromStart = subGeom.getDistanceFromStart(nearPt) + + if subGeom.whatIs() == "POLYLINE": + if subGeom.isClosed(): # non si può fare con polilinea chiusa + return None + if distFromStart > middleLength: + # parte finale + linearObjectToExtend = subGeom.getLinearObjectAt(-1).copy() + else: + # parte iniziale + linearObjectToExtend = subGeom.getLinearObjectAt(0).copy() + linearObjectToExtend.reverse() + else: + linearObjectToExtend = subGeom.copy() + if distFromStart < middleLength: + # parte iniziale + linearObjectToExtend.reverse() + + # per ciascun entità di limitEntitySet cerco i punti di intersezione + intPts = [] + entityIterator = QadEntitySetIterator(limitEntitySet) + for limitEntity in entityIterator: + intPts.extend(getIntersectionPtsExtendQadGeometry(linearObjectToExtend, limitEntity.getQadGeom(), edgeMode)) + + # cerco il punto di intersezione più vicino al punto finale di linearObject + testGeom = linearObjectToExtend.copy() + newEndPt = None + minDist = sys.float_info.max + + for intPt in intPts: + testGeom.setEndPt(intPt) + length = testGeom.length() + if length < minDist: + minDist = length + newEndPt = intPt + + if newEndPt is None: + return None + + result = subGeom.copy() + if distFromStart > middleLength: + # punto finale + result.setEndPt(newEndPt) + else: + # punto iniziale + result.setStartPt(newEndPt) + + return setQadGeomAt(qadGeom, result, atGeom, atSubGeom) + + +# =============================================================================== +# getIntersectionPtsExtendQadGeometry +# =============================================================================== +def getIntersectionPtsExtendQadGeometry(linearObject, limitGeom, edgeMode): + """ + la funzione calcola i punti di intersezione tra il prolungamento della parte lineare + oltre il punto finale fino ad incontrare la geometria secondo la modalità . + Vengono restituiti i punti che stanno oltre al punto finale di . + = geometria base QAD da estendere (linea, arco, arco di ellisse, cerchio, ellisse) + = geometria QAD da usare come limite di estensione + se = 0 si deve estendere la geometria fino ad incontrare l'oggetto più vicino + se = 1 si deve estendere la geometria fino ad incontrare l'oggetto più vicino o + anche il suo prolungamento + """ + intPts = [] + + intPts = QadIntersections.twoGeomObjectsExtensions(linearObject, limitGeom) + if edgeMode == 0: # senza estendere limitGeom + # cancello i punti di intersezione che non sono su limitGeom + for i in range(len(intPts) - 1, -1, -1): + if limitGeom.containsPt(intPts[i]) == False: del intPts[i] + + # cancello i punti di intersezione che sono su linearObject + for i in range(len(intPts) - 1, -1, -1): + if linearObject.containsPt(intPts[i]) == True: del intPts[i] + + # cancello i punti di intersezione che non sono oltre la fine di linearObject + if linearObject.whatIs() == "LINE": + angle = linearObject.getTanDirectionOnPt() + for i in range(len(intPts) - 1, -1, -1): + if qad_utils.doubleNear(angle, qad_utils.getAngleBy2Pts(linearObject.getStartPt(), intPts[i])) == False: + del intPts[i] + + return intPts + + + +# =============================================================================== +# trimQadGeometry +# =============================================================================== +def trimQadGeometry(qadGeom, pt, limitEntitySet, edgeMode): + """ + la funzione taglia la geometria QAD (lineare) in una parte i cui limiti sono le intersezioni più + vicine a pt con gli oggetti del gruppo secondo la modalità . + = geometria QAD da tagliare + = punto che indica la parte di quell'oggetto che deve essere tagliata + = gruppo di entità che serve da limite di taglio + se = 0 si deve estendere la geometria fino ad incontrare l'oggetto più vicino + se = 1 si deve estendere la geometria fino ad incontrare l'oggetto più vicino o + anche il suo prolungamento + + Ritorna una lista: + ( ) + """ + gType = qadGeom.whatIs() + if gType == "POINT" or gType == "MULTI_POINT": return None + + # la funzione ritorna una lista con + # ( + # + # + # + # + # <"a sinistra di" se il punto é alla sinista della parte con i seguenti valori: + # - < 0 = sinistra (per linea, arco o arco di ellisse) o interno (per cerchi, ellissi) + # - > 0 = destra (per linea, arco o arco di ellisse) o esterno (per cerchi, ellissi) + # ) + result = getQadGeomClosestPart(qadGeom, pt) + nearPt = result[1] + atGeom = result[2] + atSubGeom = result[3] + subGeom = getQadGeomAt(qadGeom, atGeom, atSubGeom) + + # per ciascun entità di limitEntitySet cerco i punti di intersezione + intPts = [] + entityIterator = QadEntitySetIterator(limitEntitySet) + for limitEntity in entityIterator: + intPts.extend(getIntersectionPtsTrimQadGeometry(subGeom, limitEntity.getQadGeom(), edgeMode)) + + # ordino i punti di intersezione per distanza dal punto iniziale + distFromStartList = [] + subGeomType = subGeom.whatIs() + if subGeomType == "CIRCLE" or subGeomType == "ELLIPSE": + # uso gli angoli + for intPt in intPts: + distFromStartList.append(qad_utils.getAngleBy2Pts(subGeom.center, intPt)) + else: + # uso le distanze + for intPt in intPts: + distFromStartList.append(subGeom.getDistanceFromStart(intPt)) + + intPtSortedList = [] + distFromStartSortedList = [] + minDist = sys.float_info.max + i = 0 + while i < len(distFromStartList): + insertAt = 0 + while insertAt < len(distFromStartSortedList): + if distFromStartList[i] > distFromStartSortedList[insertAt]: + insertAt = insertAt + 1 + else: + break + + intPtSortedList.insert(insertAt, intPts[i]) + distFromStartSortedList.insert(insertAt, distFromStartList[i]) + i = i + 1 + + if subGeomType == "CIRCLE" or subGeomType == "ELLIPSE": + if len(intPtSortedList) < 2: return None + distFromStart = qad_utils.getAngleBy2Pts(subGeom.center, nearPt) + if subGeomType == "ELLIPSE": + ellipseAngle = qad_utils.getAngleBy2Pts(subGeom.center, subGeom.majorAxisFinalPt) + + # cerco gli angoli che contengono il punto selezionato + i = 0 + while i < len(distFromStartSortedList) - 1: + if qad_utils.isAngleBetweenAngles(distFromStartSortedList[i], distFromStartSortedList[i + 1], distFromStart): + if subGeomType == "CIRCLE": + return [QadArc().set(subGeom.center, subGeom.radius, distFromStartSortedList[i + 1], distFromStartSortedList[i]), None, atGeom, atSubGeom] + else: + return [QadEllipseArc().set(subGeom.center, subGeom.majorAxisFinalPt, subGeom.axisRatio, distFromStartSortedList[i + 1] - ellipseAngle, distFromStartSortedList[i] - ellipseAngle), None, atGeom, atSubGeom] + i = i + 1 + + if qad_utils.isAngleBetweenAngles(distFromStartSortedList[-1], distFromStartSortedList[0], distFromStart): + if subGeomType == "CIRCLE": + return [QadArc().set(subGeom.center, subGeom.radius, distFromStartSortedList[0], distFromStartSortedList[-1]), None, atGeom, atSubGeom] + else: + return [QadEllipseArc().set(subGeom.center, subGeom.majorAxisFinalPt, subGeom.axisRatio, distFromStartSortedList[0] - ellipseAngle, distFromStartSortedList[-1] - ellipseAngle), None, atGeom, atSubGeom] + +# if qad_utils.isAngleBetweenAngles(distFromStartList[0], distFromStartList[1], distFromStart): +# firstAngle = distFromStartList[0] +# secondAngle = distFromStartList[1] +# else: +# firstAngle = distFromStartList[1] +# secondAngle = distFromStartList[0] + +# if subGeomType == "CIRCLE": +# return [QadArc().set(subGeom.center, subGeom.radius, secondAngle, firstAngle), None, atGeom, atSubGeom] +# else: +# return [QadEllipseArc().set(subGeom.center, subGeom.majorAxisFinalPt, subGeom.axisRatio, secondAngle, firstAngle), None, atGeom, atSubGeom] + + distFromStart = subGeom.getDistanceFromStart(nearPt) + + i = 0 + firstPt = subGeom.getStartPt() + while i < len(distFromStartSortedList): + if distFromStart <= distFromStartSortedList[i]: + break + firstPt = intPtSortedList[i] + i = i + 1 + + if i < len(distFromStartSortedList): + secondPt = intPtSortedList[i] + else: + secondPt = subGeom.getEndPt() + + if firstPt == subGeom.getStartPt() and secondPt == subGeom.getEndPt(): return None + + if firstPt == subGeom.getStartPt(): + return [subGeom.getGeomBetween2Pts(secondPt, subGeom.getEndPt()), None, atGeom, atSubGeom] + elif secondPt == subGeom.getEndPt(): + return [subGeom.getGeomBetween2Pts(subGeom.getStartPt(), firstPt), None, atGeom, atSubGeom] + else: + g1 = subGeom.getGeomBetween2Pts(subGeom.getStartPt(), firstPt) + g2 = subGeom.getGeomBetween2Pts(secondPt, subGeom.getEndPt()) + return [g1, g2, atGeom, atSubGeom] + + +# =============================================================================== +# getIntersectionPtsTrimQadGeometry +# =============================================================================== +def getIntersectionPtsTrimQadGeometry(qadGeom, limitGeom, edgeMode): + """ + la funzione calcola i punti di intersezione tra e la geometria secondo la modalità . + = geometria base QAD da estendere (linea, arco, arco di ellisse, cerchio, ellisse) + = geometria QAD da usare come limite di estensione + se = 0 si deve estendere la geometria fino ad incontrare l'oggetto più vicino + se = 1 si deve estendere la geometria fino ad incontrare l'oggetto più vicino o + anche il suo prolungamento + """ + if edgeMode == 0: # senza estendere limitGeom + return QadIntersections.twoGeomObjects(qadGeom, limitGeom) + else: # estendendo limitGeom + return QadIntersections.geomObjectWithGeomObjectExtensions(qadGeom, limitGeom) diff --git a/qad_fillet_cmd.py b/qad_fillet_cmd.py deleted file mode 100644 index fb06a88e..00000000 --- a/qad_fillet_cmd.py +++ /dev/null @@ -1,711 +0,0 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - comando FILLET per raccordare due oggetti grafici - - ------------------- - begin : 2014-01-30 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -from qgis.gui import * - - -from qad_generic_cmd import QadCommandClass -from qad_getdist_cmd import QadGetDistClass -from qad_snapper import * -from qad_fillet_maptool import * -from qad_msg import QadMsg -from qad_textwindow import * -import qad_utils -import qad_layer -from qad_variables import * -from qad_dim import QadDimStyles - - -# Classe che gestisce il comando FILLET -class QadFILLETCommandClass(QadCommandClass): - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadFILLETCommandClass(self.plugIn) - - def getName(self): - return QadMsg.translate("Command_list", "FILLET") - - def getEnglishName(self): - return "FILLET" - - def connectQAction(self, action): - QObject.connect(action, SIGNAL("triggered()"), self.plugIn.runFILLETCommand) - - def getIcon(self): - return QIcon(":/plugins/qad/icons/fillet.png") - - def getNote(self): - # impostare le note esplicative del comando - return QadMsg.translate("Command_FILLET", "Rounds and fillets the edges of objects.") - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - self.GetDistClass = None - - self.entity1 = QadEntity() - self.atSubGeom1 = None - self.linearObjectList1 = qad_utils.QadLinearObjectList() - self.partAt1 = 0 - self.pointAt1 = None - - self.entity2 = QadEntity() - self.atSubGeom2 = None - self.linearObjectList2 = qad_utils.QadLinearObjectList() - self.partAt2 = 0 - self.pointAt2 = None - - self.filletMode = plugIn.filletMode # modalità di raccordo; 1=Taglia-estendi, 2=Non taglia-estendi - self.radius = QadVariables.get(QadMsg.translate("Environment variables", "FILLETRAD")) - self.multi = False - self.nOperationsToUndo = 0 - - - def __del__(self): - QadCommandClass.__del__(self) - if self.GetDistClass is not None: - del self.GetDistClass - self.entity1.deselectOnLayer() - self.entity2.deselectOnLayer() - - def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): - # quando si é in fase di richiesta distanza - if self.step == 3 or self.step == 5 or self.step == 7: - return self.GetDistClass.getPointMapTool() - elif (self.plugIn is not None): - if self.PointMapTool is None: - self.PointMapTool = Qad_fillet_maptool(self.plugIn) - return self.PointMapTool - else: - return None - - - #============================================================================ - # setEntityInfo - #============================================================================ - def setEntityInfo(self, firstObj, layer, featureId, point): - """ - Setta self.entity, self.atSubGeom, self.linearObjectList, self.partAt, self.pointAt - di primo o del secondo oggetto da raccordare (vedi ) - """ - if firstObj: - e = self.entity1 - l = self.linearObjectList1 - else: - e = self.entity2 - l = self.linearObjectList2 - - e.set(layer, featureId) - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - geom = self.layerToMapCoordinates(layer, e.getGeometry()) - # ritorna una tupla (, - # - # - # ) - dummy = qad_utils.closestSegmentWithContext(point, geom) - if dummy[2] is not None: - # ritorna la sotto-geometria al vertice e la sua posizione nella geometria (0-based) - if firstObj: - subGeom, self.atSubGeom1 = qad_utils.getSubGeomAtVertex(geom, dummy[2]) - else: - subGeom, self.atSubGeom2 = qad_utils.getSubGeomAtVertex(geom, dummy[2]) - - l.fromPolyline(subGeom.asPolyline()) - e.selectOnLayer(False) # non incrementale - - # la funzione ritorna una lista con (, - # - # - # <"a sinistra di">) - dummy = l.closestPartWithContext(point) - - if firstObj: - self.partAt1 = dummy[2] - self.pointAt1 = dummy[1] - else: - self.partAt2 = dummy[2] - self.pointAt2 = dummy[1] - - return True - else: - e.deselectOnLayer() - return False - - - #============================================================================ - # filletPolyline - #============================================================================ - def filletPolyline(self): - layer = self.entity1.layer - - tolerance2ApproxCurve = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) - f = self.entity1.getFeature() - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - geom = self.layerToMapCoordinates(layer, f.getGeometry()) - - self.linearObjectList1.fillet(self.radius) - - updSubGeom = QgsGeometry.fromPolyline(self.linearObjectList1.asPolyline(tolerance2ApproxCurve)) - updGeom = qad_utils.setSubGeom(geom, updSubGeom, self.atSubGeom1) - if updGeom is None: - return False - # trasformo la geometria nel crs del layer - f.setGeometry(self.mapToLayerCoordinates(layer, updGeom)) - - self.plugIn.beginEditCommand("Feature edited", layer) - - # plugIn, layer, feature, refresh, check_validity - if qad_layer.updateFeatureToLayer(self.plugIn, layer, f, False, False) == False: - self.plugIn.destroyEditCommand() - return False - - self.plugIn.endEditCommand() - self.nOperationsToUndo = self.nOperationsToUndo + 1 - - return True - - - #============================================================================ - # fillet - #============================================================================ - def fillet(self): - tolerance2ApproxCurve = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) - - # stessa entità e stessa parte - if self.entity1.layer.id() == self.entity2.layer.id() and \ - self.entity1.featureId == self.entity2.featureId and \ - self.partAt1 == self.partAt2: - return False - - # uso il crs del canvas per lavorare con coordinate piane xy - epsg = self.plugIn.canvas.mapRenderer().destinationCrs().authid() - res = qad_utils.getFilletLinearObjectList(self.linearObjectList1, self.partAt1, self.pointAt1, \ - self.linearObjectList2, self.partAt2, self.pointAt2,\ - self.filletMode, self.radius, epsg) - if res is None: # raccordo non possibile - msg = QadMsg.translate("Command_FILLET", "\nFillet with radius <{0}> impossible.") - #showMsg - self.showMsg(msg.format(str(self.radius))) - return False - - filletLinearObjectList = res[0] - whatToDoPoly1 = res[1] - whatToDoPoly2 = res[2] - - self.plugIn.beginEditCommand("Feature edited", [self.entity1.layer, self.entity2.layer]) - - if whatToDoPoly1 == 1: # 1=modificare - f = self.entity1.getFeature() - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - geom = self.layerToMapCoordinates(self.entity1.layer, f.geometry()) - updSubGeom = QgsGeometry.fromPolyline(filletLinearObjectList.asPolyline(tolerance2ApproxCurve)) - updGeom = qad_utils.setSubGeom(geom, updSubGeom, self.atSubGeom1) - if updGeom is None: - self.plugIn.destroyEditCommand() - return False - # trasformo la geometria nel crs del layer - f.setGeometry(self.mapToLayerCoordinates(self.entity1.layer, updGeom)) - - # plugIn, layer, feature, refresh, check_validity - if qad_layer.updateFeatureToLayer(self.plugIn, self.entity1.layer, f, False, False) == False: - self.plugIn.destroyEditCommand() - return False - elif whatToDoPoly1 == 2: # 2=cancellare - # plugIn, layer, featureId, refresh - if qad_layer.deleteFeatureToLayer(self.plugIn, self.entity1.layer, \ - self.entity1.featureId, False) == False: - self.plugIn.destroyEditCommand() - return False - - if whatToDoPoly2 == 1: # 1=modificare - tolerance2ApproxCurve = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) - - f = self.entity2.getFeature() - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - geom = self.layerToMapCoordinates(self.entity2.layer, f.geometry()) - updSubGeom = QgsGeometry.fromPolyline(filletLinearObjectList.asPolyline(tolerance2ApproxCurve)) - updGeom = qad_utils.setSubGeom(geom, updSubGeom, self.atSubGeom2) - if updGeom is None: - self.plugIn.destroyEditCommand() - return False - # trasformo la geometria nel crs del layer - f.setGeometry(self.mapToLayerCoordinates(self.entity2.layer, updGeom)) - - # plugIn, layer, feature, refresh, check_validity - if qad_layer.updateFeatureToLayer(self.plugIn, self.entity2.layer, f, False, False) == False: - self.plugIn.destroyEditCommand() - return False - elif whatToDoPoly2 == 2: # 2=cancellare - # plugIn, layer, featureId, refresh - if qad_layer.deleteFeatureToLayer(self.plugIn, self.entity2.layer, \ - self.entity2.featureId, False) == False: - self.plugIn.destroyEditCommand() - return False - - if whatToDoPoly1 == 0 and whatToDoPoly2 == 0: # 0=niente - geom = QgsGeometry.fromPolyline(filletLinearObjectList.asPolyline(tolerance2ApproxCurve)) - # trasformo la geometria nel crs del layer - geom = self.mapToLayerCoordinates(self.entity1.layer, geom) - - # plugIn, layer, geom, coordTransform, refresh, check_validity - if qad_layer.addGeomToLayer(self.plugIn, self.entity1.layer, geom, None, False, False) == False: - self.plugIn.destroyEditCommand() - return False - - self.plugIn.endEditCommand() - self.nOperationsToUndo = self.nOperationsToUndo + 1 - - return True - - - #============================================================================ - # waitForFirstEntSel - #============================================================================ - def waitForFirstEntSel(self): - self.step = 1 - # imposto il map tool - self.getPointMapTool().setMode(Qad_fillet_maptool_ModeEnum.ASK_FOR_FIRST_LINESTRING) - - # l'opzione Radius viene tradotta in italiano in "RAggio" nel contesto "waitForFirstEntSel" - keyWords = QadMsg.translate("Command_FILLET", "Undo") + "/" + \ - QadMsg.translate("Command_FILLET", "Polyline") + "/" + \ - QadMsg.translate("Command_FILLET", "Radius", "waitForFirstEntSel") + "/" + \ - QadMsg.translate("Command_FILLET", "Trim") + "/" + \ - QadMsg.translate("Command_FILLET", "Multiple") - prompt = QadMsg.translate("Command_FILLET", "Select first object or [{0}]: ").format(keyWords) - - englishKeyWords = "Undo" + "/" + "Polyline" + "/" + "Radius" + "/" + "Trim" + "/" + "Multiple" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o enter o una parola chiave - # msg, inputType, default, keyWords, valore nullo non permesso - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - None, \ - keyWords, QadInputModeEnum.NOT_NULL) - - - #============================================================================ - # WaitForPolyline - #============================================================================ - def WaitForPolyline(self): - self.step = 2 - # imposto il map tool - self.getPointMapTool().setMode(Qad_fillet_maptool_ModeEnum.ASK_FOR_POLYLINE) - self.getPointMapTool().radius = self.radius - - # l'opzione Radius viene tradotta in italiano in "Raggio" nel contesto "WaitForPolyline" - keyWords = QadMsg.translate("Command_FILLET", "Radius", "WaitForPolyline") - prompt = QadMsg.translate("Command_FILLET", "Select polyline or [{0}]: ").format(keyWords) - - englishKeyWords = "Radius" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o enter o una parola chiave - # msg, inputType, default, keyWords, valore nullo non permesso - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - None, \ - keyWords, QadInputModeEnum.NOT_NULL) - - - #============================================================================ - # waitForFilletMode - #============================================================================ - def waitForFilletMode(self): - self.step = 4 - # imposto il map tool - self.getPointMapTool().setMode(Qad_fillet_maptool_ModeEnum.NONE) - - keyWords = QadMsg.translate("Command_FILLET", "Trim-extend") + "/" + \ - QadMsg.translate("Command_FILLET", "No trim-extend") - - if self.filletMode == 1: - default = QadMsg.translate("Command_FILLET", "Trim-extend") - elif self.filletMode == 2: - default = QadMsg.translate("Command_FILLET", "No trim-extend") - - prompt = QadMsg.translate("Command_FILLET", "Specify trim mode [{0}] <{1}>: ").format(keyWords, default) - - englishKeyWords = "Trim-extend" + "/" + "No trim-extend" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o enter o una parola chiave o un numero reale - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(prompt, QadInputTypeEnum.KEYWORDS, default, \ - keyWords) - - - #============================================================================ - # waitForSecondEntSel - #============================================================================ - def waitForSecondEntSel(self): - self.step = 6 - # imposto il map tool - self.getPointMapTool().filletMode = self.filletMode - self.getPointMapTool().radius = self.radius - self.getPointMapTool().setEntityInfo(self.entity1.layer, self.entity1.featureId, self.linearObjectList1, \ - self.partAt1, self.pointAt1) - self.getPointMapTool().setMode(Qad_fillet_maptool_ModeEnum.ASK_FOR_SECOND_LINESTRING) - - # l'opzione Radius viene tradotta in italiano in "RAggio" nel contesto "waitForSecondEntSel" - keyWords = QadMsg.translate("Command_FILLET", "Radius", "waitForSecondEntSel") - prompt = QadMsg.translate("Command_FILLET", "Select second object or shift-select to apply corner or [{0}]: ").format(keyWords) - - englishKeyWords = "Radius" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o enter o una parola chiave - # msg, inputType, default, keyWords, valore nullo non permesso - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - None, \ - keyWords, QadInputModeEnum.NOT_NULL) - - - def run(self, msgMapTool = False, msg = None): - if self.plugIn.canvas.mapRenderer().destinationCrs().geographicFlag(): - self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) - return True # fine comando - - if self.step == 0: - CurrSettingsMsg = QadMsg.translate("QAD", "\nCurrent settings: ") - if self.filletMode == 1: - CurrSettingsMsg = CurrSettingsMsg + QadMsg.translate("Command_FILLET", "Mode = Trim-extend") - else: - CurrSettingsMsg = CurrSettingsMsg + QadMsg.translate("Command_FILLET", "Mode = No trim-extend") - - CurrSettingsMsg = CurrSettingsMsg + QadMsg.translate("Command_FILLET", ", Radius = ") + str(self.radius) - self.showMsg(CurrSettingsMsg) - - self.waitForFirstEntSel() - return False # continua - - #========================================================================= - # RISPOSTA ALLA SELEZIONE PRIMO OGGETTO - elif self.step == 1: - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - else: - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("Command_FILLET", "Undo") or value == "Undo": - if self.nOperationsToUndo > 0: - self.nOperationsToUndo = self.nOperationsToUndo - 1 - self.plugIn.undoEditCommand() - else: - self.showMsg(QadMsg.translate("QAD", "\nThe command has been canceled.")) - - self.waitForFirstEntSel() # si appresta ad attendere la selezione del primo oggetto - elif value == QadMsg.translate("Command_FILLET", "Polyline") or value == "Polyline": - self.WaitForPolyline() - # l'opzione Radius viene tradotta in italiano in "RAggio" nel contesto "waitForFirstEntSel" - elif value == QadMsg.translate("Command_FILLET", "Radius", "waitForFirstEntSel") or value == "Radius": - if self.GetDistClass is not None: - del self.GetDistClass - self.GetDistClass = QadGetDistClass(self.plugIn) - prompt = QadMsg.translate("Command_FILLET", "Specify fillet radius <{0}>: ") - self.GetDistClass.msg = prompt.format(str(self.radius)) - self.GetDistClass.dist = self.radius - self.GetDistClass.inputMode = QadInputModeEnum.NOT_NEGATIVE - self.step = 3 - self.GetDistClass.run(msgMapTool, msg) - elif value == QadMsg.translate("Command_FILLET", "Trim") or value == "Trim": - self.waitForFilletMode() - elif value == QadMsg.translate("Command_FILLET", "Multiple") or value == "Multiple": - self.multi = True - self.waitForFirstEntSel() # si appresta ad attendere la selezione del primo oggetto - - elif type(value) == QgsPoint: # se é stato selezionato un punto - self.entity1.clear() - self.linearObjectList1.removeAll() - if self.getPointMapTool().entity.isInitialized(): - if self.setEntityInfo(True, self.getPointMapTool().entity.layer, \ - self.getPointMapTool().entity.featureId, value) == True: - self.waitForSecondEntSel() # si appresta ad attendere la selezione del secondo oggetto - return False - else: - # cerco se ci sono entità nel punto indicato considerando - # solo layer lineari o poligono editabili che non appartengano a quote - layerList = [] - for layer in self.plugIn.canvas.layers(): - if layer.type() == QgsMapLayer.VectorLayer and \ - (layer.geometryType() == QGis.Line or layer.geometryType() == QGis.Polygon) and \ - layer.isEditable(): - if len(QadDimStyles.getDimListByLayer(layer)) == 0: - layerList.append(layer) - - result = qad_utils.getEntSel(self.getPointMapTool().toCanvasCoordinates(value), - self.getPointMapTool(), \ - layerList) - if result is not None: - # result[0] = feature, result[1] = layer, result[0] = point - if self.setEntityInfo(True, result[1], result[0].id(), result[2]) == True: - self.waitForSecondEntSel() # si appresta ad attendere la selezione del secondo oggetto - return False - self.waitForFirstEntSel() # si appresta ad attendere la selezione del primo oggetto - else: - return True # fine comando - - return False - - #========================================================================= - # RISPOSTA ALLA SELEZIONE DI UNA POLILINEA (da step = 1) - elif self.step == 2: - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - else: - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == unicode: - # l'opzione Radius viene tradotta in italiano in "Raggio" nel contesto "WaitForPolyline" - if value == QadMsg.translate("Command_FILLET", "Radius", "WaitForPolyline") or value == "Radius": - if self.GetDistClass is not None: - del self.GetDistClass - self.GetDistClass = QadGetDistClass(self.plugIn) - prompt = QadMsg.translate("Command_FILLET", "Specify fillet radius <{0}>: ") - self.GetDistClass.msg = prompt.format(str(self.radius)) - self.GetDistClass.dist = self.radius - self.GetDistClass.inputMode = QadInputModeEnum.NOT_NEGATIVE - self.step = 5 - self.GetDistClass.run(msgMapTool, msg) - return False - elif type(value) == QgsPoint: # se é stato selezionato un punto - self.entity1.clear() - self.linearObjectList1.removeAll() - if self.getPointMapTool().entity.isInitialized(): - if self.setEntityInfo(True, self.getPointMapTool().entity.layer, \ - self.getPointMapTool().entity.featureId, value) == True: - if self.filletPolyline() == False or self.multi: - self.waitForFirstEntSel() # si appresta ad attendere la selezione del primo oggetto - return False - else: - return True - else: - # cerco se ci sono entità nel punto indicato considerando - # solo layer lineari o poligono editabili che non appartengano a quote - layerList = [] - for layer in self.plugIn.canvas.layers(): - if layer.type() == QgsMapLayer.VectorLayer and \ - (layer.geometryType() == QGis.Line or layer.geometryType() == QGis.Polygon) and \ - layer.isEditable(): - if len(QadDimStyles.getDimListByLayer(layer)) == 0: - layerList.append(layer) - - result = qad_utils.getEntSel(self.getPointMapTool().toCanvasCoordinates(value), - self.getPointMapTool(), \ - layerList) - if result is not None: - # result[0] = feature, result[1] = layer, result[0] = point - if self.setEntityInfo(True, result[1], result[0].id(), result[2]) == True: - if self.filletPolyline() == False or self.multi: - self.waitForFirstEntSel() # si appresta ad attendere la selezione del primo oggetto - return False - else: - return True - else: - return True # fine comando - - self.WaitForPolyline() - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DEL RAGGIO DI RACCORDO (da step = 1) - elif self.step == 3: - if self.GetDistClass.run(msgMapTool, msg) == True: - if self.GetDistClass.dist is not None: - self.radius = self.GetDistClass.dist - QadVariables.set(QadMsg.translate("Environment variables", "FILLETRAD"), self.radius) - QadVariables.save() - self.waitForFirstEntSel() # si appresta ad attendere la selezione del primo oggetto - self.getPointMapTool().refreshSnapType() # aggiorno lo snapType che può essere variato dal maptool di distanza - return False # fine comando - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DELLA MODALITA' DI TAGLIO (da step = 1) - elif self.step == 4: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - value = self.filletMode - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - else: - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("Command_FILLET", "Trim-extend") or value == "Trim-extend": - self.filletMode = 1 - elif value == QadMsg.translate("Command_FILLET", "No trim-extend") or value == "No trim-extend": - self.filletMode = 2 - self.plugIn.setFilletMode(self.filletMode) - - self.waitForFirstEntSel() # si appresta ad attendere la selezione del primo oggetto - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DEL RAGGIO DI RACCORDO (da step = 3) - elif self.step == 5: - if self.GetDistClass.run(msgMapTool, msg) == True: - if self.GetDistClass.dist is not None: - self.radius = self.GetDistClass.dist - QadVariables.set(QadMsg.translate("Environment variables", "FILLETRAD"), self.radius) - QadVariables.save() - self.WaitForPolyline() - self.getPointMapTool().refreshSnapType() # aggiorno lo snapType che può essere variato dal maptool di distanza - return False # fine comando - - #========================================================================= - # RISPOSTA ALLA SELEZIONE SECONDO OGGETTO - elif self.step == 6: - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - else: - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == unicode: - # l'opzione Radius viene tradotta in italiano in "RAggio" nel contesto "waitForSecondEntSel" - if value == QadMsg.translate("Command_FILLET", "Radius", "waitForSecondEntSel") or value == "Radius": - if self.GetDistClass is not None: - del self.GetDistClass - self.GetDistClass = QadGetDistClass(self.plugIn) - prompt = QadMsg.translate("Command_FILLET", "Specify fillet radius <{0}>: ") - self.GetDistClass.msg = prompt.format(str(self.radius)) - self.GetDistClass.dist = self.radius - self.GetDistClass.inputMode = QadInputModeEnum.NOT_NEGATIVE - self.step = 7 - self.GetDistClass.run(msgMapTool, msg) - return False - - elif type(value) == QgsPoint: # se é stato selezionato un punto - self.entity2.clear() - self.linearObjectList2.removeAll() - - if self.getPointMapTool().entity.isInitialized(): - if self.setEntityInfo(False, self.getPointMapTool().entity.layer, \ - self.getPointMapTool().entity.featureId, value) == True: - if self.getPointMapTool().shiftKey == True: - dummyRadius = self.radius - self.radius = 0 - dummyFilletMode = self.filletMode - self.filletMode = 1 # modalità di raccordo; 1=Taglia-estendi - result = self.fillet() - self.radius = dummyRadius - self.filletMode = dummyFilletMode - else: - result = self.fillet() - - if result == False: - self.waitForSecondEntSel() # si appresta ad attendere la selezione del secondo oggetto - return False - - if self.multi: - self.waitForFirstEntSel() # si appresta ad attendere la selezione del primo oggetto - return False - else: - return True - else: - # cerco se ci sono entità nel punto indicato considerando - # solo layer lineari o poligono editabili che non appartengano a quote - layerList = [] - for layer in self.plugIn.canvas.layers(): - if layer.type() == QgsMapLayer.VectorLayer and \ - (layer.geometryType() == QGis.Line or layer.geometryType() == QGis.Polygon) and \ - layer.isEditable(): - if len(QadDimStyles.getDimListByLayer(layer)) == 0: - layerList.append(layer) - - result = qad_utils.getEntSel(self.getPointMapTool().toCanvasCoordinates(value), - self.getPointMapTool(), \ - layerList) - if result is not None: - # result[0] = feature, result[1] = layer, result[0] = point - if self.setEntityInfo(False, result[1], result[0].id(), result[2]) == True: - if self.fillet() == False: - self.waitForSecondEntSel() # si appresta ad attendere la selezione del secondo oggetto - return False - - if self.multi: - self.waitForFirstEntSel() # si appresta ad attendere la selezione del primo oggetto - return False - else: - return True - else: - return True # fine comando - - self.waitForSecondEntSel() # si appresta ad attendere la selezione del secondo oggetto - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DEL RAGGIO DI RACCORDO (da step = 6) - elif self.step == 7: - if self.GetDistClass.run(msgMapTool, msg) == True: - if self.GetDistClass.dist is not None: - self.radius = self.GetDistClass.dist - QadVariables.set(QadMsg.translate("Environment variables", "FILLETRAD"), self.radius) - QadVariables.save() - self.waitForSecondEntSel() # si appresta ad attendere la selezione del secondo oggetto - self.getPointMapTool().refreshSnapType() # aggiorno lo snapType che può essere variato dal maptool di distanza - return False # fine comando \ No newline at end of file diff --git a/qad_fillet_fun.py b/qad_fillet_fun.py new file mode 100644 index 00000000..a23cc86a --- /dev/null +++ b/qad_fillet_fun.py @@ -0,0 +1,1637 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin ok + + funzioni per fillet + + ------------------- + begin : 2019-08-20 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.PyQt.QtCore import * +from qgis.PyQt.QtGui import * +from qgis.core import * + +import math + +from .qad_arc import QadArc +from .qad_multi_geom import * +from .qad_geom_relations import * +from .qad_entity import * +from .qad_offset_fun import offsetBridgeTheGapBetweenLines + + +# ============================================================================ +# isStartedPtChanged +# ============================================================================ +def isStartedPtChanged(oldQadGeom, newQadGeom, filletQadGeom, nearNewQadGeom): + """ + Funzione di ausilio alle funzioni filletQadGeometry e fillet2QadGeometries. + + Data una geometria qad e le nuove geometrie ricavate dall'operazione di raccordo, + la funzione ritorna True se è stato variato il punto iniziale della geoemtria qad e False + se ne è stato variato il punto di finale. + """ + # se la nuova geometria rispetto a oldQadGeom non è cambiata + if qad_utils.ptNear(oldQadGeom.getStartPt(), newQadGeom.getStartPt()) and \ + qad_utils.ptNear(oldQadGeom.getEndPt(), newQadGeom.getEndPt()): + if filletQadGeom is not None: # se esiste un elemento di raccordo + # verifico se il punto iniziale di oldQadGeom è collegato con questo elemento di raccordo + if qad_utils.ptNear(oldQadGeom.getStartPt(), filletQadGeom.getStartPt()) or \ + qad_utils.ptNear(oldQadGeom.getStartPt(), filletQadGeom.getEndPt()): + changedStartPt = True + else: + changedStartPt = False + else: # se non esiste un elemento di raccordo + # verifico se il punto iniziale di oldQadGeom è collegato alla nuova geometria vicina nearNewQadGeom + if qad_utils.ptNear(oldQadGeom.getStartPt(), nearNewQadGeom.getStartPt()) or \ + qad_utils.ptNear(oldQadGeom.getStartPt(), nearNewQadGeom.getEndPt()): + changedStartPt = True + else: + changedStartPt = False + else: # se la nuova geometria è cambiata verifico in quale punto + if qad_utils.ptNear(oldQadGeom.getStartPt(), newQadGeom.getStartPt()) == False: + changedStartPt = True + else: + changedStartPt = False + + return changedStartPt + + +# ============================================================================ +# filletQadGeometry +# ============================================================================ +def filletQadPolyline(qadPolyline, partAt1, pointAt1, partAt2, pointAt2, \ + filletMode, radius): + """ + Date una geometria qad, 2 parti e due 2 punti in cui bisogna fare il raccordo tra le due + parti, la funzione ritorna una polilinea risultato del raccordo. + modalità di raccordo; 1=Taglia-estendi, 2=Non taglia-estendi + raggio di raccordo + """ + if partAt1 == partAt2: return None + + basicQadGeom1 = qadPolyline.getLinearObjectAt(partAt1) + basicQadGeom2 = qadPolyline.getLinearObjectAt(partAt2) + + res = filletBridgeTheGapBetween2BasicQadGeometries(basicQadGeom1, pointAt1, basicQadGeom2, pointAt2, filletMode, radius) + + if res is None: # raccordo non possibile + return None + + filletPolyline = qadPolyline.copy() + + if res[0] is not None: + filletPolyline.remove(partAt1) + filletPolyline.insert(partAt1, res[0]) + + # se è stato variato il punto iniziale di basicQadGeom1 + if isStartedPtChanged(basicQadGeom1, res[0], res[1], res[2]): + # può essere variato solo il punto finale di partAt1 (se è minore di partAt2) + if partAt1 < partAt2: return None + else: # se è stato variato il punto finale di basicQadGeom1 + # può essere variato solo il punto iniziale di partAt1 (se è maggiore di partAt2) + if partAt1 > partAt2: return None + + if res[2] is not None: + filletPolyline.remove(partAt2) + filletPolyline.insert(partAt2, res[2]) + + # se è stato variato il punto iniziale di basicQadGeom2 + if isStartedPtChanged(basicQadGeom2, res[2], res[1], res[0]): + # può essere variato solo il punto finale di partAt2 (se è minore di partAt1) + if partAt2 < partAt1: return None + else: # se è stato variato il punto finale di basicQadGeom1 + # può essere variato solo il punto iniziale di partAt2 (se è maggiore di partAt1) + if partAt2 > partAt1: return None + + # rimuovo tutte le parti che sono fra partAt1 e partAt2 + if partAt1 < partAt2: + for i in range(partAt1 + 1, partAt2): + filletPolyline.remove(i) + if res[1] is not None: # inserisco arco di raccordo + filletPolyline.insert(partAt1 + 1, res[1]) + else: + for i in range(partAt2 + 1, partAt1): + filletPolyline.remove(i) + if res[1] is not None: # inserisco arco di raccordo + filletPolyline.insert(partAt2 + 1, res[1]) + + # verifico e correggo i versi delle parti della polilinea + filletPolyline.reverseCorrection() + + return filletPolyline + + +# ============================================================================ +# fillet2QadGeometries +# ============================================================================ +def fillet2QadGeometries(qadGeom1, atGeom1, atSubGeom1, partAt1, pointAt1, \ + qadGeom2, atGeom2, atSubGeom2, partAt2, pointAt2, \ + filletMode, radius): + """ + Date due geometrie qad, la parte e il punto in cui bisogna fare il raccordo tra le due + polilinee, la funzione ritorna una polilinea risultato del raccordo e due flag che + danno indicazioni su ciò che deve essere fatto alle polilinee originali: + (0=niente, 1=modificare, 2=cancellare) + modalità di raccordo; 1=Taglia-estendi, 2=Non taglia-estendi + raggio di raccordo + """ + gType1 = qadGeom1.whatIs() + gType2 = qadGeom2.whatIs() + + if gType1 == "POLYLINE" or gType1 == "MULTI_LINEAR_OBJ" or gType1 == "POLYGON" or gType1 == "MULTI_POLYGON": + subQadGeom1 = getQadGeomAt(qadGeom1, atGeom1, atSubGeom1) + if subQadGeom1.whatIs() == "POLYLINE": + basicQadGeom1 = subQadGeom1.getLinearObjectAt(partAt1) + else: + basicQadGeom1 = subQadGeom1 + + else: + subQadGeom1 = basicQadGeom1 = qadGeom1 + subQadGeomType1 = subQadGeom1.whatIs() + + if gType2 == "POLYLINE" or gType2 == "MULTI_LINEAR_OBJ" or gType2 == "POLYGON" or gType2 == "MULTI_POLYGON": + subQadGeom2 = getQadGeomAt(qadGeom2, atGeom2, atSubGeom2) + if subQadGeom2.whatIs() == "POLYLINE": + basicQadGeom2 = subQadGeom2.getLinearObjectAt(partAt2) + else: + basicQadGeom2 = subQadGeom2 + else: + subQadGeom2 = basicQadGeom2 = qadGeom2 + subQadGeomType2 = subQadGeom2.whatIs() + + res = filletBridgeTheGapBetween2BasicQadGeometries(basicQadGeom1, pointAt1, basicQadGeom2, pointAt2, filletMode, radius) + + if res is None: # raccordo non possibile + return None + + filletPolyline = QadPolyline() + + if res[0] is None or \ + subQadGeomType1 == "CIRCLE" or subQadGeomType1 == "ELLIPSE" or subQadGeomType1 == "POLYGON": + whatToDoGeom1 = 0 # 0=niente + else: + whatToDoGeom1 = 1 # 0=niente, 1=modificare, 2=cancellare + if subQadGeomType1 == "POLYLINE": + # se è stato variato il punto iniziale di basicQadGeom1 + if isStartedPtChanged(basicQadGeom1, res[0], res[1], res[2]): + # prendo tutte le parti successive a partAt1 + for i in range(subQadGeom1.qty() - 1, partAt1, -1): + filletPolyline.append(subQadGeom1.getLinearObjectAt(i)) + else: + # prendo tutte le parti precedenti a partAt1 + for i in range(0, partAt1): + filletPolyline.append(subQadGeom1.getLinearObjectAt(i)) + filletPolyline.append(res[0]) + + if res[1] is not None: # arco di raccordo + filletPolyline.append(res[1]) + + if res[2] is None or \ + subQadGeomType2 == "CIRCLE" or subQadGeomType2 == "ELLIPSE" or subQadGeomType2 == "POLYGON": + whatToDoGeom2 = 0 # 0=niente + elif res[2] is not None: + # 0=niente, 1=modificare, 2=cancellare + if whatToDoGeom1 == 1: # se la geometria1 è stata modificata + whatToDoGeom2 = 2 # la geometria2 si unisce alla 1 e va cancellata + else: + whatToDoGeom2 = 1 + + filletPolyline.append(res[2]) + if subQadGeomType2 == "POLYLINE": + # se è stato variato il punto iniziale di basicQadGeom2 + if isStartedPtChanged(basicQadGeom2, res[2], res[1], res[0]): + # prendo tutte le parti successive a partAt2 + for i in range(partAt2 + 1, subQadGeom2.qty()): + filletPolyline.append(subQadGeom2.getLinearObjectAt(i)) + else: + # prendo tutte le parti precedenti a partAt2 + for i in range(partAt2 - 1, -1, -1): + filletPolyline.append(subQadGeom2.getLinearObjectAt(i)) + + # verifico e correggo i versi delle parti della polilinea + filletPolyline.reverseCorrection() + + # 1=modificare + if whatToDoGeom1 == 1 and (gType1 == "MULTI_LINEAR_OBJ" or gType1 == "POLYGON" or gType1 == "MULTI_POLYGON"): + updGeom = setQadGeomAt(qadGeom1, filletPolyline, atGeom1, atSubGeom1) + return updGeom, whatToDoGeom1, whatToDoGeom2 + elif whatToDoGeom2 == 1 and (gType2 == "MULTI_LINEAR_OBJ" or gType2 == "POLYGON" or gType2 == "MULTI_POLYGON"): + updGeom = setQadGeomAt(qadGeom2, filletPolyline, atGeom2, atSubGeom2) + return updGeom, whatToDoGeom1, whatToDoGeom2 + else: + return filletPolyline, whatToDoGeom1, whatToDoGeom2 + + +# ============================================================================ +# filletBridgeTheGapBetween2BasicQadGeometries +# ============================================================================ +def filletBridgeTheGapBetween2BasicQadGeometries(qadGeom1, pointAt1, qadGeom2, pointAt2, filletMode, radius): + """ + Date due geometrie di base di qad, la parte e il punto in cui bisogna fare il raccordo tra le due + polilinee, la funzione ritorna una polilinea risultato del raccordo e due flag che + danno indicazioni su ciò che deve essere fatto alle polilinee originali: + (0=niente, 1=modificare, 2=cancellare) + modalità di raccordo; 1=Taglia-estendi, 2=Non taglia-estendi + raggio di raccordo + + Ritorna una lista di 3 elementi (None in caso di errore): + una geometria 1 che sostituisce , se = None va rimossa + un arco, se = None non c'é arco di raccordo tra le due linee + una geometria 2 che sostituisce , se = None va rimossa + + """ + gType1 = qadGeom1.whatIs() + gType2 = qadGeom2.whatIs() + + if gType1 == "CIRCLE": + if gType2 == "CIRCLE": + res = filletBridgeTheGapBetweenCircles(qadGeom1, pointAt1, qadGeom2, pointAt2, radius) + elif gType2 == "ELLIPSE": + res = filletBridgeTheGapBetweenCircleEllipse(qadGeom1, pointAt1, qadGeom2, pointAt2, radius, filletMode) + elif gType2 == "ARC": + res = filletBridgeTheGapBetweenArcCircle(qadGeom2, pointAt2, qadGeom1, pointAt1, radius, filletMode) + if res is not None: + dummy = res[0] # inverto il primo e il terzo elemento + res[0] = res[2] + res[2] = dummy + elif gType2 == "ELLIPSE_ARC": + res = filletBridgeTheGapBetweenCircleEllipsearc(qadGeom1, pointAt1, qadGeom2, pointAt2, radius, filletMode) + elif gType2 == "LINE": + res = filletBridgeTheGapBetweenCircleLine(qadGeom1, pointAt1, qadGeom2, pointAt2, radius, filletMode) + + elif gType1 == "ELLIPSE": + if gType2 == "CIRCLE": + res = filletBridgeTheGapBetweenCircleEllipse(qadGeom2, pointAt2, qadGeom1, pointAt1, radius, filletMode) + if res is not None: + dummy = res[0] # inverto il primo e il terzo elemento + res[0] = res[2] + res[2] = dummy + elif gType2 == "ELLIPSE": + res = filletBridgeTheGapBetweenEllipses(qadGeom1, pointAt1, qadGeom2, pointAt2, radius) + elif gType2 == "ARC": + res = filletBridgeTheGapBetweenArcEllipse(qadGeom2, pointAt2, qadGeom1, pointAt1, radius, filletMode) + if res is not None: + dummy = res[0] # inverto il primo e il terzo elemento + res[0] = res[2] + res[2] = dummy + elif gType2 == "ELLIPSE_ARC": + res = filletBridgeTheGapBetweenEllipseEllipsearc(qadGeom1, pointAt1, qadGeom2, pointAt2, radius, filletMode) + elif gType2 == "LINE": + res = filletBridgeTheGapBetweenEllipseLine(qadGeom1, pointAt1, qadGeom2, pointAt2, radius, filletMode) + + elif gType1 == "ARC": + if gType2 == "CIRCLE": + res = filletBridgeTheGapBetweenArcCircle(qadGeom1, pointAt1, qadGeom2, pointAt2, radius, filletMode) + elif gType2 == "ELLIPSE": + res = filletBridgeTheGapBetweenArcEllipse(qadGeom2, pointAt2, qadGeom1, pointAt1, radius, filletMode) + elif gType2 == "ARC": + res = filletBridgeTheGapBetweenArcs(qadGeom1, pointAt1, qadGeom2, pointAt2, radius, filletMode) + elif gType2 == "ELLIPSE_ARC": + res = filletBridgeTheGapBetweenArcEllipsearc(qadGeom1, pointAt1, qadGeom2, pointAt2, radius, filletMode) + elif gType2 == "LINE": + res = filletBridgeTheGapBetweenArcLine(qadGeom1, pointAt1, qadGeom2, pointAt2, radius, filletMode) + + elif gType1 == "ELLIPSE_ARC": + if gType2 == "CIRCLE": + res = filletBridgeTheGapBetweenCircleEllipsearc(qadGeom2, pointAt2, qadGeom1, pointAt1, radius, filletMode) + if res is not None: + dummy = res[0] # inverto il primo e il terzo elemento + res[0] = res[2] + res[2] = dummy + elif gType2 == "ELLIPSE": + res = filletBridgeTheGapBetweenEllipseEllipsearc(qadGeom2, pointAt2, qadGeom1, pointAt1, radius, filletMode) + if res is not None: + dummy = res[0] # inverto il primo e il terzo elemento + res[0] = res[2] + res[2] = dummy + elif gType2 == "ARC": + res = filletBridgeTheGapBetweenArcEllipsearc(qadGeom1, pointAt1, qadGeom2, pointAt2, radius, filletMode) + if res is not None: + dummy = res[0] # inverto il primo e il terzo elemento + res[0] = res[2] + res[2] = dummy + elif gType2 == "ELLIPSE_ARC": + res = filletBridgeTheGapBetweenEllipsearcs(qadGeom1, pointAt1, qadGeom2, pointAt2, radius, filletMode) + elif gType2 == "LINE": + res = filletBridgeTheGapBetweenLineEllipsearc(qadGeom2, pointAt2, qadGeom1, pointAt1, radius, filletMode) + if res is not None: + dummy = res[0] # inverto il primo e il terzo elemento + res[0] = res[2] + res[2] = dummy + + elif gType1 == "LINE": + if gType2 == "CIRCLE": + res = filletBridgeTheGapBetweenCircleLine(qadGeom1, pointAt1, qadGeom2, pointAt2, radius, filletMode) + if res is not None: + dummy = res[0] # inverto il primo e il terzo elemento + res[0] = res[2] + res[2] = dummy + elif gType2 == "ELLIPSE": + res = filletBridgeTheGapBetweenEllipseLine(qadGeom1, pointAt1, qadGeom2, pointAt2, radius, filletMode) + if res is not None: + dummy = res[0] # inverto il primo e il terzo elemento + res[0] = res[2] + res[2] = dummy + elif gType2 == "ARC": + res = filletBridgeTheGapBetweenArcLine(qadGeom2, pointAt2, qadGeom1, pointAt1, radius, filletMode) + if res is not None: + dummy = res[0] # inverto il primo e il terzo elemento + res[0] = res[2] + res[2] = dummy + elif gType2 == "ELLIPSE_ARC": + res = filletBridgeTheGapBetweenLineEllipsearc(qadGeom1, pointAt1, qadGeom2, pointAt2, radius, filletMode) + elif gType2 == "LINE": + if radius == 0: + res = filletBridgeTheGapBetweenLines(qadGeom1, pointAt1, qadGeom2, pointAt2, radius, 0) + else: + res = filletBridgeTheGapBetweenLines(qadGeom1, pointAt1, qadGeom2, pointAt2, radius, 1) + + return res + + +# =============================================================================== +# INIZIO - 2 CERCHI +# =============================================================================== + + +# =============================================================================== +# filletBridgeTheGapBetweenCircles +# =============================================================================== +def filletBridgeTheGapBetweenCircles(circle1, ptOnCircle1, circle2, ptOnCircle2, radius): + """ + la funzione raccorda due cerchi attraverso + un arco di raccordo di raggio che più si avvicinza ai punti di selezione + sui cerchi. + + Ritorna una lista di 3 elementi (None in caso di errore): + None + un arco, se = None non c'é arco di raccordo tra le due linee + None + """ + # ricavo i possibili archi di raccordo + filletArcs = getFilletArcsBetweenCircles(circle1, circle2, radius) + + # cerco l'arco valido più vicino a ptOnCircle1 e ptOnCircle2 + AvgList = [] + Avg = sys.float_info.max + + resFilletArc = QadArc() + for filletArc in filletArcs: + if circle1.isPtOnCircle(filletArc.getStartPt()): + distFromPtOnCircle1 = circle1.lengthBetween2Points(filletArc.getStartPt(), \ + ptOnCircle1, \ + filletArc.getTanDirectionOnStartPt() + math.pi) + distFromPtOnCircle2 = circle2.lengthBetween2Points(filletArc.getEndPt(), \ + ptOnCircle2, \ + filletArc.getTanDirectionOnEndPt()) + else: + distFromPtOnCircle1 = circle1.lengthBetween2Points(filletArc.getEndPt(), \ + ptOnCircle1, \ + filletArc.getTanDirectionOnEndPt()) + distFromPtOnCircle2 = circle2.lengthBetween2Points(filletArc.getStartPt(), \ + ptOnCircle2, \ + filletArc.getTanDirectionOnStartPt()+ math.pi) + + del AvgList[:] + AvgList.append(distFromPtOnCircle1) + AvgList.append(distFromPtOnCircle2) + + currAvg = qad_utils.numericListAvg(AvgList) + if currAvg < Avg: # mediamente più vicino + Avg = currAvg + resFilletArc.set(filletArc) + + if Avg == sys.float_info.max: + return None + + return [None, resFilletArc, None] + + +# =============================================================================== +# getFilletArcsBetweenCircles +# =============================================================================== +def getFilletArcsBetweenCircles(circle1, circle2, radius): + """ + la funzione raccorda due cerchi attraverso un arco di raccordo di raggio . + + Ritorna una lista dei possibili archi + """ + res = [] + + # caso 1: raccordo tra e formando un flesso con ciascuno dei cerchi + # creo un nuovo cerchio concentrico a circle1 con raggio aumentato di + newCircle1 = QadCircle(circle1) + newCircle1.radius = newCircle1.radius + radius + # creo un nuovo cerchio concentrico a circle2 con raggio aumentato di + newCircle2 = QadCircle(circle2) + newCircle2.radius = newCircle2.radius + radius + + res.extend(auxFilletArcsBetweenCircles(newCircle1, newCircle2, radius)) + + # caso 2: raccordo tra e senza formare un flesso con ciascuno dei cerchi + if radius - circle1.radius > 0 and radius - circle2.radius > 0: + # creo un nuovo cerchio concentrico a circle1 con raggio = - raggio di circle1 + newCircle1 = QadCircle(circle1) + newCircle1.radius = radius - newCircle1.radius + # creo un nuovo cerchio concentrico a circle2 con raggio = - raggio di circle2 + newCircle2 = QadCircle(circle2) + newCircle2.radius = radius - newCircle2.radius + + res.extend(auxFilletArcsBetweenCircles(newCircle1, newCircle2, radius)) + + # caso 3: raccordo tra e formando un flesso solo con circle1 + if radius - circle2.radius > 0: + # creo un nuovo cerchio concentrico a circle1 con raggio aumentato di + newCircle1 = QadCircle(circle1) + newCircle1.radius = newCircle1.radius + radius + # creo un nuovo cerchio concentrico a circle2 con raggio = - raggio di circle2 + newCircle2 = QadCircle(circle2) + newCircle2.radius = radius - newCircle2.radius + + res.extend(auxFilletArcsBetweenCircles(newCircle1, newCircle2, radius)) + + # caso 4: raccordo tra e formando un flesso solo con circle2 + if radius - circle1.radius > 0: + # creo un nuovo cerchio concentrico a circle1 con raggio aumentato di + newCircle1 = QadCircle(circle1) + newCircle1.radius = radius - newCircle1.radius + # creo un nuovo cerchio concentrico a circle2 con raggio = - raggio di circle2 + newCircle2 = QadCircle(circle2) + newCircle2.radius = newCircle2.radius + radius + + res.extend(auxFilletArcsBetweenCircles(newCircle1, newCircle2, radius)) + + # caso 5: raccordo tra e interno a formando un flesso solo con circle2 + if qad_utils.getDistance(circle1.center, circle2.center) + circle2.radius <= circle1.radius and \ + circle1.radius - radius > 0: + # creo un nuovo cerchio concentrico a circle1 con raggio diminuito di + newCircle1 = QadCircle(circle1) + newCircle1.radius = newCircle1.radius - radius + # creo un nuovo cerchio concentrico a circle2 con raggio aumentato di + newCircle2 = QadCircle(circle2) + newCircle2.radius = newCircle2.radius + radius + + res.extend(auxFilletArcsBetweenCircles(newCircle1, newCircle2, radius)) + + # caso 6: raccordo tra interno a e formando un flesso solo con circle1 + if qad_utils.getDistance(circle1.center, circle2.center) + circle1.radius <= circle2.radius and \ + circle2.radius - radius > 0: + # creo un nuovo cerchio concentrico a circle1 con raggio aumentato di + newCircle1 = QadCircle(circle1) + newCircle1.radius = newCircle1.radius + radius + # creo un nuovo cerchio concentrico a circle2 con raggio diminuito di + newCircle2 = QadCircle(circle2) + newCircle2.radius = newCircle2.radius - radius + + res.extend(auxFilletArcsBetweenCircles(newCircle1, newCircle2, radius)) + + # caso 7: raccordo tra e interno a senza formare alcun flesso + if qad_utils.getDistance(circle1.center, circle2.center) + circle2.radius <= circle1.radius and \ + circle1.radius - radius > 0 and radius - circle2.radius: + # creo un nuovo cerchio concentrico a circle1 con raggio diminuito di + newCircle1 = QadCircle(circle1) + newCircle1.radius = newCircle1.radius - radius + # creo un nuovo cerchio concentrico a circle2 con raggio = - raggio di circle2 + newCircle2 = QadCircle(circle2) + newCircle2.radius = radius - newCircle2.radius + + res.extend(auxFilletArcsBetweenCircles(newCircle1, newCircle2, radius)) + + return res + + +# =============================================================================== +# auxFilletArcsBetweenCircles +# =============================================================================== +def auxFilletArcsBetweenCircles(circle1, circle2, radius, both = True): + """ + la funzione di ausilio a getFilletArcsBetweenCircles + Ritorna una lista dei possibili archi di raccordo tra i cerchi e + """ + res = [] + # calcolo le intersezioni tra le due circonferenze + # che daranno origine ai centri degli archi di raccordo + intPts = QadIntersections.twoCircles(circle1, circle2) + + if len(intPts) > 0: + # un punto di tangenza é dato dal punto a distanza radius dal centro dell'arco di raccordo + # in direzione centro dell'arco + angle = qad_utils.getAngleBy2Pts(intPts[0], circle1.center) + tanC1Pt = qad_utils.getPolarPointByPtAngle(intPts[0], angle, radius) + # un punto di tangenza é dato dal punto a distanza radius dal centro dell'arco di raccordo + # in direzione centro dell'arco + angle = qad_utils.getAngleBy2Pts(intPts[0], circle2.center) + tanC2Pt = qad_utils.getPolarPointByPtAngle(intPts[0], angle, radius) + filletArc = QadArc() + if filletArc.fromStartCenterEndPts(tanC1Pt, intPts[0], tanC2Pt) == True: + res.append(filletArc) + if both: + # inverto angolo iniziale-finale + filletArc = QadArc(filletArc) + filletArc.inverseAngles() + res.append(filletArc) + + if len(intPts) > 1: + # un punto di tangenza é dato dal punto a distanza radius dal centro dell'arco di raccordo + # in direzione centro dell'arco + angle = qad_utils.getAngleBy2Pts(intPts[1], circle1.center) + tanC1Pt = qad_utils.getPolarPointByPtAngle(intPts[1], angle, radius) + # un punto di tangenza é dato dal punto a distanza radius dal centro dell'arco di raccordo + # in direzione centro dell'arco + angle = qad_utils.getAngleBy2Pts(intPts[1], circle2.center) + tanC2Pt = qad_utils.getPolarPointByPtAngle(intPts[1], angle, radius) + filletArc = QadArc() + if filletArc.fromStartCenterEndPts(tanC1Pt, intPts[1], tanC2Pt) == True: + res.append(filletArc) + if both: + # inverto angolo iniziale-finale + filletArc = QadArc(filletArc) + filletArc.inverseAngles() + res.append(filletArc) + + return res + + +# =============================================================================== +# FINE - 2 CERCHI +# INIZIO - CERCHIO ED ELLISSE +# =============================================================================== + + +# =============================================================================== +# filletBridgeTheGapBetweenCircleEllipse +# =============================================================================== +def filletBridgeTheGapBetweenCircleEllipse(circle, ptOnCircle, ellipse, ptOnEllipse, radius): + """ + la funzione raccorda un cerchio con una ellisse attraverso + un arco di raccordo di raggio che più si avvicinza ai punti di selezione + sulle due geometrie. + + Ritorna una lista di 3 elementi (None in caso di errore): + None + un arco, se = None non c'é arco di raccordo tra le due linee + None + """ + # da fare + return [None, None, None] + + +# =============================================================================== +# FINE - CERCHIO ED ELLISSE +# INIZIO - 2 LINEE +# =============================================================================== + + +# =============================================================================== +# filletBridgeTheGapBetweenLines +# =============================================================================== +def filletBridgeTheGapBetweenLines(line1, ptOnLine1, line2, ptOnLine2, radius, filletMode): + """ + la funzione raccorda 2 segmenti retti (QadLine) attraverso + un arco di raccordo di raggio che più si avvicinza ai punti di selezione + sul segmento 1 e sul segmento 2 . + modalità di raccordo; 1=Taglia-estendi, 2=Non taglia-estendi + + Ritorna una lista di 3 elementi (None in caso di errore): + una linea che sostituisce , se = None va rimossa + un arco, se = None non c'é arco di raccordo tra le due linee + una linea che sostituisce , se = None va rimossa + """ + if radius == 0: # Estende i segmenti + # cerco il punto di intersezione tra le due linee + ptInt = QadIntersections.twoInfinityLines(line1, line2) + if ptInt is None: # linee parallele + return None + + distBetweenLine1Pt1AndPtInt = qad_utils.getDistance(line1.getStartPt(), ptInt) + distBetweenLine1Pt2AndPtInt = qad_utils.getDistance(line1.getEndPt(), ptInt) + distBetweenLine2Pt1AndPtInt = qad_utils.getDistance(line2.getStartPt(), ptInt) + distBetweenLine2Pt2AndPtInt = qad_utils.getDistance(line2.getEndPt(), ptInt) + + if distBetweenLine1Pt1AndPtInt > distBetweenLine1Pt2AndPtInt: + # secondo punto di line1 più vicino al punto di intersezione + resLine1 = QadLine().set(line1.getStartPt(), ptInt) + else: + # primo punto di line1 più vicino al punto di intersezione + resLine1 = QadLine().set(ptInt, line1.getEndPt()) + + if distBetweenLine2Pt1AndPtInt > distBetweenLine2Pt2AndPtInt: + # secondo punto di line2 più vicino al punto di intersezione + resLine2 = QadLine().set(line2.getStartPt(), ptInt) + else: + # primo punto di line2 più vicino al punto di intersezione + resLine2 = QadLine().set(ptInt, line2.getEndPt()) + + return [resLine1, None, resLine2] + else: # Raccorda i segmenti + filletArcs = getFilletArcsBetweenLines(line1, line2, radius) + + # cerco l'arco valido più vicino a ptOnLine1 e ptOnLine2 + AvgList = [] + Avg = sys.float_info.max + + resLine1 = QadLine() + resFilletArc = QadArc() + resLine2 = QadLine() + for filletArc in filletArcs: + # ricavo il nuovo segmento in modo che sia tangente con l'arco di raccordo + newLine1, distFromPtOnLine1 = getNewLineAccordingFilletArc(line1, filletArc, ptOnLine1) + if newLine1 is None: + continue + # ricavo il nuovo segmento in modo che sia tangente con l'arco di raccordo + newLine2, distFromPtOnLine2 = getNewLineAccordingFilletArc(line2, filletArc, ptOnLine2) + if newLine2 is None: + continue + + del AvgList[:] + AvgList.append(distFromPtOnLine1) + AvgList.append(distFromPtOnLine2) + + currAvg = qad_utils.numericListAvg(AvgList) + if currAvg < Avg: # mediamente più vicino + Avg = currAvg + resLine1.set(newLine1) + resFilletArc.set(filletArc) + resLine2.set(newLine2) + + if Avg == sys.float_info.max: + return None + + if filletMode == 1: # 1=Taglia-estendi + return [resLine1, resFilletArc, resLine2] + else: + return [None, resFilletArc, None] + + +# =============================================================================== +# getFilletArcsBetweenLines +# =============================================================================== +def getFilletArcsBetweenLines(line1, line2, radius): + """ + la funzione raccorda due linee rette (QadLine) attraverso + un arco di raccordo di raggio . + + Ritorna una lista dei possibili archi + """ + res = [] + + # cerco il punto di intersezione tra le due linee + intPt = QadIntersections.twoInfinityLines(line1, line2) + if intPt is None: # linee parallele + # calcolo la proiezione perpendicolare del punto iniziale di su + ptPerp = QadPerpendicularity.fromPointToInfinityLine(line1.getStartPt(), line2) + d = qad_utils.getDistance(line1.getStartPt(), ptPerp) + # d deve essere 2 volte + if qad_utils.doubleNear(radius * 2, d): + angle = qad_utils.getAngleBy2Pts(line1.getStartPt(), ptPerp) + ptCenter = gad_utils.getPolarPointByPtAngle(line1.getStartPt(), angle, radius) + filletArc = QadArc() + if filletArc.fromStartCenterEndPts(line1.getStartPt(), ptCenter, ptPerp) == True: + res.append(filletArc) + # stesso arco con il punto iniziale e finale invertiti + filletArc = QadArc() + if filletArc.fromStartCenterEndPts(ptPerp, ptCenter, line1.getStartPt()) == True: + res.append(filletArc) + + ptPerp = qad_utils.getPolarPointByPtAngle(line1.getEndPt(), angle, d) + ptCenter = qad_utils.getPolarPointByPtAngle(line1.getEndPt(), angle, radius) + filletArc = QadArc() + if filletArc.fromStartCenterEndPts(line1.getEndPt(), ptCenter, ptPerp) == True: + res.append(filletArc) + # stesso arco con il punto iniziale e finale invertiti + filletArc = QadArc() + if filletArc.fromStartCenterEndPts(ptPerp, ptCenter, line1.getEndPt()) == True: + res.append(filletArc) + else: # linee non parallele + angleLine1 = line1.getTanDirectionOnPt() + angleLine2 = line2.getTanDirectionOnPt() + + ptLine1 = qad_utils.getPolarPointByPtAngle(intPt, angleLine1, 1) + ptLine2 = qad_utils.getPolarPointByPtAngle(intPt, angleLine2, 1) + res.extend(auxFilletArcsBetweenLines(ptLine1, ptLine2, intPt, radius)) + + ptLine2 = qad_utils.getPolarPointByPtAngle(intPt, angleLine2, -1) + res.extend(auxFilletArcsBetweenLines(ptLine1, ptLine2, intPt, radius)) + + ptLine1 = qad_utils.getPolarPointByPtAngle(intPt, angleLine1, -1) + res.extend(auxFilletArcsBetweenLines(ptLine1, ptLine2, intPt, radius)) + + ptLine2 = qad_utils.getPolarPointByPtAngle(intPt, angleLine2, 1) + res.extend(auxFilletArcsBetweenLines(ptLine1, ptLine2, intPt, radius)) + + return res + + +# =============================================================================== +# getNewLineAccordingFilletArc +# =============================================================================== +def getNewLineAccordingFilletArc(line, filletArc, ptOnLine): + """ + dato un segmento retto () e un arco che si + raccorda ad esso (), la funzione restituisce un nuovo segmento retto + modificando in modo che sia tangente all'arco di raccordo. + Inoltre, usando un punto indicato sul segmento restituisce + la distanza di quel punto dal punto di tangenza con l'arco di raccordo. + """ + newLine = QadLine() + + # determino quale punto (iniziale o finale) dell'arco di raccordo + # si interseca sul prolugamento del segmento retto + if line.isPtOnInfinityLine(filletArc.getStartPt()): + filletPtOnLine = filletArc.getStartPt() + isStartFilletPtOnLine = True + else: + filletPtOnLine = filletArc.getEndPt() + isStartFilletPtOnLine = False + + if line.containsPt(filletPtOnLine) == True: # se il punto é all'interno del segmento + newLine.set(filletPtOnLine, line.getEndPt()) + + if isStartFilletPtOnLine: # se il punto iniziale dell'arco di raccordo é sulla linea + # se il nuovo segmento non é un segmento valido + if qad_utils.ptNear(newLine.getStartPt(), newLine.getEndPt()): + # se l'arco di raccordo é tangente sul punto finale del nuovo segmento + if qad_utils.TanDirectionNear(line.getTanDirectionOnEndPt(), \ + qad_utils.normalizeAngle(filletArc.getTanDirectionOnStartPt())) == True: + newLine.set(line) # ripristino il segmento originale + else: + # se l'arco di raccordo non é tangente sul punto iniziale del nuovo segmento + if qad_utils.TanDirectionNear(newLine.getTanDirectionOnStartPt(), \ + qad_utils.normalizeAngle(filletArc.getTanDirectionOnStartPt() + math.pi)) == False: + newLine.set(line.getStartPt(), filletPtOnLine) + + # se il nuovo segmento non é un segmento valido + if qad_utils.ptNear(newLine.getStartPt(), newLine.getEndPt()) or \ + newLine.containsPt(ptOnLine) == False: + return None, None + + # calcolo la distanza dal punto ptOnLine + distFromPtOnLine = qad_utils.getDistance(ptOnLine, filletPtOnLine) + else: # se il punto finale dell'arco di raccordo é sulla linea + # se il nuovo segmento non é un segmento valido + if qad_utils.ptNear(newLine.getStartPt(), newLine.getEndPt()): + # se l'arco di raccordo é tangente sul punto finale del nuovo segmento + if qad_utils.TanDirectionNear(line.getTanDirectionOnEndPt(), \ + qad_utils.normalizeAngle(filletArc.getTanDirectionOnEndPt() + math.pi)) == True: + newLine.set(line) # ripristino il segmento originale + else: + # se l'arco di raccordo non é tangente sul punto iniziale del nuovo segmento + if qad_utils.TanDirectionNear(newLine.getTanDirectionOnStartPt(), \ + filletArc.getTanDirectionOnEndPt()) == False: + newLine.set(line.getStartPt(), filletPtOnLine) + + # se il nuovo segmento non é un segmento valido + if qad_utils.ptNear(newLine.getStartPt(), newLine.getEndPt()) or \ + newLine.containsPt(ptOnLine) == False: + return None, None + + # calcolo la distanza dal punto ptOnLine + distFromPtOnLine = qad_utils.getDistance(ptOnLine, filletPtOnLine) + + return newLine, distFromPtOnLine + else: # se il punto é all'esterno del segmento + if qad_utils.getDistance(line.getStartPt(), filletPtOnLine) < qad_utils.getDistance(line.getEndPt(), filletPtOnLine): + newLine.set(filletPtOnLine, line.getEndPt()) + else: + newLine.set(line.getStartPt(), filletPtOnLine) + + return getNewLineAccordingFilletArc(newLine, filletArc, ptOnLine) + + +# =============================================================================== +# auxFilletArcsBetweenLines +# =============================================================================== +def auxFilletArcsBetweenLines(ptLine1, ptLine2, intPt, radius, both = True): + """ + la funzione di ausilio a getFilletArcsBetweenLines + Ritorna una lista dei possibili archi di raccordo tra la + linea 1 che va da fino al punto di intersezione con la linea 2 + e + linea2 che va da fino al punto di intersezione con la linea 1 + """ + res = [] + + angleLine1 = qad_utils.getAngleBy2Pts(intPt, ptLine1) + angleLine2 = qad_utils.getAngleBy2Pts(intPt, ptLine2) + + line = QadLine().set(ptLine1, ptLine2) + bisectorInfinityLinePts = qad_utils.getBisectorInfinityLine(ptLine1, intPt, ptLine2, True) + bisectorLine = QadLine().set(bisectorInfinityLinePts[0], bisectorInfinityLinePts[1]) + # cerco il punto di intersezione tra la bisettrice e + # la retta che congiunge i punti più distanti delle due linee + pt = QadIntersections.twoInfinityLines(bisectorLine, line) + angleBisectorLine = qad_utils.getAngleBy2Pts(intPt, pt) + + # calcolo l'angolo (valore assoluto) tra un lato e la bisettrice + alfa = angleLine1 - angleBisectorLine + if alfa < 0: + alfa = angleBisectorLine - angleLine1 + if alfa > math.pi: + alfa = (2 * math.pi) - alfa + + # calcolo l'angolo del triangolo rettangolo sapendo che la somma degli angoli interni = 180 + # - alfa - 90 gradi (angolo retto) + distFromIntPt = math.tan(math.pi - alfa - (math.pi / 2)) * radius + pt1Proj = qad_utils.getPolarPointByPtAngle(intPt, angleLine1, distFromIntPt) + pt2Proj = qad_utils.getPolarPointByPtAngle(intPt, angleLine2, distFromIntPt) + # Pitagora + distFromIntPt = math.sqrt((distFromIntPt * distFromIntPt) + (radius * radius)) + secondPt = qad_utils.getPolarPointByPtAngle(intPt, angleBisectorLine, distFromIntPt - radius) + filletArc = QadArc() + if filletArc.fromStartSecondEndPts(pt1Proj, secondPt, pt2Proj) == True: + res.append(filletArc) + if both: + # inverto angolo iniziale-finale + filletArc = QadArc(filletArc) + filletArc.inverseAngles() + res.append(filletArc) + + return res + + +# =============================================================================== +# FINE - 2 LINEE +# INIZIO - ARCO E CERCHIO +# =============================================================================== + + +# =============================================================================== +# filletBridgeTheGapBetweenArcCircle +# =============================================================================== +def filletBridgeTheGapBetweenArcCircle(arc, ptOnArc, circle, ptOnCircle, radius, filletMode): + """ + la funzione raccorda un arco e un cerchio attraverso + un arco di raccordo di raggio che più si avvicinza ai punti di selezione + sull'arco e sul cerchio . + modalità di raccordo; 1=Taglia-estendi, 2=Non taglia-estendi + + Ritorna una lista di 3 elementi (None in caso di errore): + una arco che sostituisce + un arco, se = None non c'é arco di raccordo tra le due linee + None + """ + # ricavo i possibili archi di raccordo + filletArcs = getFilletArcsBetweenArcCircle(arc, circle, radius) + + # cerco l'arco valido più vicino a ptOnArc e ptOnCircle + AvgList = [] + Avg = sys.float_info.max + + resFilletArc = QadArc() + resArc = QadArc() + for filletArc in filletArcs: + # ricavo il nuovo arco in modo che sia tangente con l'arco di raccordo + newArc, distFromPtOnArc = getNewArcAccordingFilletArc(arc, filletArc, ptOnArc) + if newArc is None: + continue + + # calcolo la distanza dal punto ptOnCircle + if circle.isPtOnCircle(filletArc.getStartPt()): # se il punto iniziale dell'arco di raccordo é sul cerchio + distFromPtOnCircle = circle.lengthBetween2Points(filletArc.getStartPt(), \ + ptOnCircle, \ + filletArc.getTanDirectionOnStartPt() + math.pi) + else: # se il punto finale dell'arco di raccordo é sul cerchio + distFromPtOnCircle = circle.lengthBetween2Points(filletArc.getEndPt(), \ + ptOnCircle, \ + filletArc.getTanDirectionOnEndPt()) + + del AvgList[:] + AvgList.append(distFromPtOnArc) + AvgList.append(distFromPtOnCircle) + + currAvg = qad_utils.qad_utils.numericListAvg(AvgList) + if currAvg < Avg: # mediamente più vicino + Avg = currAvg + resArc.set(newArc) + resFilletArc.set(filletArc) + + if Avg == sys.float_info.max: + return None + + if filletMode == 1: # 1=Taglia-estendi + return [resArc, resFilletArc, None] + else: + return [None, resFilletArc, None] + + +# =============================================================================== +# getFilletArcsBetweenArcCircle +# =============================================================================== +def getFilletArcsBetweenArcCircle(arc, circle, radius): + """ + la funzione raccorda un arco e un cerchio attraverso + un arco di raccordo di raggio . + + Ritorna una lista dei possibili archi + """ + circle1 = QadCircle() + circle1.set(arc.center, arc.radius) + + return getFilletArcsBetweenCircles(circle1, circle, radius) + + +# =============================================================================== +# getNewArcAccordingFilletArc +# =============================================================================== +def getNewArcAccordingFilletArc(arc, filletArc, ptOnArc): + """ + dato un arco () e un altro arco che si raccorda ad esso (), + la funzione restituisce un nuovo arco modificando in modo che sia + tangente all'arco di raccordo. Inoltre, usando un punto indicato sull'arco + restituisce la distanza di quel punto dal punto di tangenza con l'arco + di raccordo usando la direzione della tangente dell'arco di raccordo. + """ + circle = QadCircle() + circle.set(arc.center, arc.radius) + + newArc = QadArc(arc) + + # determino quale punto (iniziale o finale) dell'arco di raccordo + # si interseca sul prolugamento dell'arco + if circle.isPtOnCircle(filletArc.getStartPt()): + filletPtOnArc = filletArc.getStartPt() + isStartFilletPtOnArc = True + else: + filletPtOnArc = filletArc.getEndPt() + isStartFilletPtOnArc = False + + # verifico che l'arco di raccordo sia tangente con l'arco + newArc.setStartAngleByPt(filletPtOnArc) + + if isStartFilletPtOnArc: # se il punto iniziale dell'arco di raccordo é sull'arco + # se il nuovo arco non é un arco valido + if qad_utils.doubleNear(newArc.startAngle, newArc.endAngle): + # se l'arco di raccordo é tangente sul punto finale dell'arco + if qad_utils.TanDirectionNear(arc.getTanDirectionOnEndPt(), \ + qad_utils.normalizeAngle(filletArc.getTanDirectionOnStartPt())) == True: + newArc.startAngle = arc.startAngle # ripristino l'arco originale + else: + # se l'arco di raccordo non é tangente sul punto iniziale del nuovo arco + if qad_utils.TanDirectionNear(newArc.getTanDirectionOnStartPt(), \ + qad_utils.normalizeAngle(filletArc.getTanDirectionOnStartPt() + math.pi)) == False: + newArc.startAngle = arc.startAngle # ripristino l'arco originale + newArc.setEndAngleByPt(filletPtOnArc) + + # se il nuovo arco non é un arco valido + if qad_utils.doubleNear(newArc.startAngle, newArc.endAngle): + return None, None + + # calcolo la distanza dal punto ptOnArc + distFromPtOnArc = circle.lengthBetween2Points(filletArc.getStartPt(), \ + ptOnArc, \ + filletArc.getTanDirectionOnStartPt() + math.pi) + else: # se il punto finale dell'arco di raccordo é sull'arco + # se il nuovo arco non é un arco valido + if qad_utils.doubleNear(newArc.startAngle, newArc.endAngle): + # se l'arco di raccordo é tangente sul punto finale dell'arco + if qad_utils.TanDirectionNear(arc.getTanDirectionOnEndPt(), \ + qad_utils.normalizeAngle(filletArc.getTanDirectionOnEndPt() + math.pi)) == True: + newArc.startAngle = arc.startAngle # ripristino l'arco originale + else: + # se l'arco di raccordo non é tangente sul punto iniziale del nuovo arco + if qad_utils.TanDirectionNear(newArc.getTanDirectionOnStartPt(), \ + filletArc.getTanDirectionOnEndPt()) == False: + newArc.startAngle = arc.startAngle # ripristino l'arco originale + newArc.setEndAngleByPt(filletPtOnArc) + + # se il nuovo arco non é un arco valido + if qad_utils.doubleNear(newArc.startAngle, newArc.endAngle): + return None, None + + # calcolo la distanza dal punto ptOnArc + distFromPtOnArc = circle.lengthBetween2Points(filletArc.getEndPt(), \ + ptOnArc, \ + filletArc.getTanDirectionOnEndPt()) + + return newArc, distFromPtOnArc + + +# =============================================================================== +# FINE - ARCO E CERCHIO +# INIZIO - CERCHIO E ARCO DI ELLISSE +# =============================================================================== + + +# =============================================================================== +# filletBridgeTheGapBetweenCircleEllipsearc +# =============================================================================== +def filletBridgeTheGapBetweenCircleEllipsearc(circle, ptOnCircle, ellipseArc, ptOnEllipseArc, radius): + """ + la funzione raccorda un cerchio ed un arco ellisse attraverso + un arco di raccordo di raggio che più si avvicinza ai punti di selezione + sulle 2 geometrie. + + Ritorna una lista di 3 elementi (None in caso di errore): + None + un arco, se = None non c'é arco di raccordo tra le due linee + None + """ + # da fare + return [None, None, None] + + +# =============================================================================== +# FINE - CERCHIO E ARCO DI ELLISSE +# INIZIO - CERCHIO E LINEA +# =============================================================================== + + +# =============================================================================== +# filletBridgeTheGapBetweenCircleLine +# =============================================================================== +def filletBridgeTheGapBetweenCircleLine(circle, ptOnCircle, line, ptOnLine, radius, filletMode): + """ + la funzione raccorda un cerchio e un segmento retto (QadLine) attraverso + un arco di raccordo di raggio che più si avvicinza ai punti di selezione + sul cerchio e sul segmento retto . + modalità di raccordo; 1=Taglia-estendi, 2=Non taglia-estendi + + Ritorna una lista di 3 elementi (None in caso di errore): + None + un arco, se = None non c'é arco di raccordo tra le due linee + una linea che sostituisce se filleMode = 1 (Taglia-estendi) altrimenti None + """ + # ricavo i possibili archi di raccordo + filletArcs = getFilletArcsBetweenCircleLine(circle, line, radius) + + # cerco l'arco valido più vicino a ptOnArc e ptOnLine + AvgList = [] + Avg = sys.float_info.max + + resFilletArc = QadArc() + resLine = QadLine() + for filletArc in filletArcs: + # ricavo il nuovo segmento in modo che sia tangente con l'arco di raccordo + newLine, distFromPtOnLine = getNewLineAccordingFilletArc(line, filletArc, ptOnLine) + if newLine is None: + continue + + if circle.isPtOnCircle(filletArc.getStartPt()): + distFromPtOnCircle = circle.lengthBetween2Points(filletArc.getStartPt(), \ + ptOnCircle, \ + filletArc.getTanDirectionOnStartPt() + math.pi) + else: + distFromPtOnCircle = circle.lengthBetween2Points(filletArc.getEndPt(), \ + ptOnCircle, \ + filletArc.getTanDirectionOnEndPt()) + + del AvgList[:] + AvgList.append(distFromPtOnLine) + AvgList.append(distFromPtOnCircle) + + currAvg = qad_utils.numericListAvg(AvgList) + if currAvg < Avg: # mediamente piùvicino + Avg = currAvg + resLine.set(newLine) + resFilletArc.set(filletArc) + + if Avg == sys.float_info.max: + return None + + if filletMode == 1: # 1=Taglia-estendi + return [None, resFilletArc, resLine] + else: + return [None, resFilletArc, None] + + +# =============================================================================== +# auxFilletArcsBetweenCircleLine +# =============================================================================== +def auxFilletArcsBetweenCircleLine(circle, line, origCircle, origLine, both = True): + """ + la funzione di ausilio a getFilletArcsBetweenArcLine + Ritorna una lista dei possibili archi di raccordo tra e + """ + res = [] + # calcolo le intersezioni tra la circonferenza del cerchio e la retta parallela a + # che daranno origine ai centri degli archi di raccordo + intPts = QadIntersections.infinityLineWithCircle(line, circle) + if len(intPts) > 0: + # un punto di tangenza é dato dal punto a distanza radius dal centro di + # in direzione centro dell'arco di raccordo + angle = qad_utils.getAngleBy2Pts(origCircle.center, intPts[0]) + tanCirclePt = qad_utils.getPolarPointByPtAngle(origCircle.center, angle, origCircle.radius) + # un punto di tangenza é la proiezione perpendicolare del centro dell'arco di raccordo + # con + ptPerp = QadPerpendicularity.fromPointToInfinityLine(intPts[0], origLine) + filletArc = QadArc() + if filletArc.fromStartCenterEndPts(tanCirclePt, \ + intPts[0], \ + ptPerp) == True: + res.append(filletArc) + if both: + # inverto angolo iniziale-finale + filletArc = QadArc(filletArc) + filletArc.inverseAngles() + res.append(filletArc) + + if len(intPts) > 1: # # due centri per i due archi di raccordo + # un punto di tangenza é dato dal punto a distanza arc.radius dal centro di + # in direzione centro dell'arco di raccordo + angle = qad_utils.getAngleBy2Pts(origCircle.center, intPts[1]) + tanCirclePt = qad_utils.getPolarPointByPtAngle(origCircle.center, angle, origCircle.radius) + # un punto di tangenza é la proiezione perpendicolare del centro dell'arco di raccordo + # con + ptPerp = QadPerpendicularity.fromPointToInfinityLine(intPts[1], origLine) + filletArc = QadArc() + if filletArc.fromStartCenterEndPts(tanCirclePt, \ + intPts[1], \ + ptPerp) == True: + res.append(filletArc) + if both: + # inverto angolo iniziale-finale + filletArc = QadArc(filletArc) + filletArc.inverseAngles() + res.append(filletArc) + + return res + + +# =============================================================================== +# getFilletArcsBetweenCircleLine +# =============================================================================== +def getFilletArcsBetweenCircleLine(circle, line, radius): + """ + la funzione raccorda un cerchio e una linea retta (QadLine) attraverso + un arco di raccordo di raggio . + + Ritorna una lista dei possibili archi + """ + res = [] + + offsetCircle = circle.copy() + + intPts = QadIntersections.infinityLineWithCircle(line, circle) + if len(intPts) == 0: + # se il cerchio e la retta generata dall'estensione di line + # non hanno punti in comune + leftOfLine = line.leftOf(circle.center) + # creo una retta parallela a ad una distanza verso il centro di + linePar = QadLine() + angle = line.getTanDirectionOnStartPt() + if leftOfLine < 0: # a sinistra + linePar.set(qad_utils.getPolarPointByPtAngle(line.getStartPt(), angle + math.pi / 2, radius), \ + qad_utils.getPolarPointByPtAngle(line.getEndPt(), angle + math.pi / 2, radius)) + else :# a destra + linePar.set(qad_utils.getPolarPointByPtAngle(line.getStartPt(), angle - math.pi / 2, radius), \ + qad_utils.getPolarPointByPtAngle(line.getEndPt(), angle - math.pi / 2, radius)) + + # Calcolo la distanza dal centro di a + ptPerp = QadPerpendicularity.fromPointToInfinityLine(circle.center, line) + d = qad_utils.getDistance(circle.center, ptPerp) + # deve essere >= (d - raggio cerchio) / 2 + if radius >= (d - circle.radius) / 2: + + # caso 1: raccordo tra e formando un flesso con + + # creo un cerchio con raggio aumentato di + offsetCircle.radius = circle.radius + radius + res.extend(auxFilletArcsBetweenCircleLine(offsetCircle, linePar, circle, line)) + + # caso 2: raccordo tra e senza formare un flesso con + + # deve essere > raggio cerchio + if radius > circle.radius: + # creo un cerchio con raggio = - circle.radius + offsetCircle.radius = radius - circle.radius + res.extend(auxFilletArcsBetweenCircleLine(offsetCircle, linePar, circle, line)) + else: + # se il cerchio e la retta generata dall'estensione di line + # hanno punti in comune + # creo una retta parallela a ad una distanza verso sinistra + linePar = QadLine() + angle = line.getTanDirectionOnStartPt() + linePar.set(qad_utils.getPolarPointByPtAngle(line.getStartPt(), angle + math.pi / 2, radius), \ + qad_utils.getPolarPointByPtAngle(line.getEndPt(), angle + math.pi / 2, radius)) + + # creo un cerchio con raggio aumentato di + offsetCircle.radius = circle.radius + radius + res.extend(auxFilletArcsBetweenCircleLine(offsetCircle, linePar, circle, line)) + + if circle.radius > radius: + # creo un cerchio con raggio diminuito di + offsetCircle.radius = circle.radius - radius + res.extend(auxFilletArcsBetweenCircleLine(offsetCircle, linePar, circle, line)) + + # creo una retta parallela a ad una distanza verso destra + linePar.set(qad_utils.getPolarPointByPtAngle(line.getStartPt(), angle - math.pi / 2, radius), \ + qad_utils.getPolarPointByPtAngle(line.getEndPt(), angle - math.pi / 2, radius)) + + # creo un cerchio con raggio aumentato di + offsetCircle.radius = circle.radius + radius + res.extend(auxFilletArcsBetweenCircleLine(offsetCircle, linePar, circle, line)) + # calcolo le intersezioni tra la circonferenza del cerchio e la retta parallela a + + if circle.radius > radius: + # creo un cerchio con raggio diminuito di + offsetCircle.radius = circle.radius - radius + res.extend(auxFilletArcsBetweenCircleLine(offsetCircle, linePar, circle, line)) + + return res + + +# =============================================================================== +# FINE - CERCHIO E LINEA +# INIZIO - 2 ELLISSI +# =============================================================================== + + +# =============================================================================== +# filletBridgeTheGapBetweenEllipses +# =============================================================================== +def filletBridgeTheGapBetweenEllipses(ellipse1, ptOnEllipse1, ellipse2, ptOnEllipse2, radius): + """ + la funzione raccorda due ellissi attraverso + un arco di raccordo di raggio che più si avvicinza ai punti di selezione + sulle ellissi. + + Ritorna una lista di 3 elementi (None in caso di errore): + None + un arco, se = None non c'é arco di raccordo tra le due linee + None + """ + # da fare + return [None, None, None] + + +# =============================================================================== +# FINE - 2 ELLISSI +# INIZIO - ARCO ED ELLISSE +# =============================================================================== + + +# =============================================================================== +# filletBridgeTheGapBetweenArcEllipse +# =============================================================================== +def filletBridgeTheGapBetweenArcEllipse(arc, ptOnArc, ellipse, ptOnEllipse, radius): + """ + la funzione raccorda un arco con una ellisse attraverso + un arco di raccordo di raggio che più si avvicinza ai punti di selezione + sulle geometrie. + + Ritorna una lista di 3 elementi (None in caso di errore): + None + un arco, se = None non c'é arco di raccordo tra le due linee + None + """ + # da fare + return [None, None, None] + + +# =============================================================================== +# FINE - ARCO ED ELLISSE +# INIZIO - ELLISSE ED ARCO DI ELLISSE +# =============================================================================== + + +# =============================================================================== +# filletBridgeTheGapBetweenEllipseEllipsearc +# =============================================================================== +def filletBridgeTheGapBetweenEllipseEllipsearc(ellipse, ptOnEllipse, ellipseArc, ptOnEllipseArc, radius): + """ + la funzione raccorda una ellisse con un arco di ellisse attraverso + un arco di raccordo di raggio che più si avvicinza ai punti di selezione + sulle geometrie. + + Ritorna una lista di 3 elementi (None in caso di errore): + None + un arco, se = None non c'é arco di raccordo tra le due linee + None + """ + # da fare + return [None, None, None] + + +# =============================================================================== +# FINE - ELLISSE ED ARCO DI ELLISSE +# INIZIO - ELLISSE E LINEA +# =============================================================================== + + +# =============================================================================== +# filletBridgeTheGapBetweenEllipseLine +# =============================================================================== +def filletBridgeTheGapBetweenEllipseLine(ellipse, ptOnEllipse, line, ptOnLine, radius): + """ + la funzione raccorda una ellisse con una linea attraverso + un arco di raccordo di raggio che più si avvicinza ai punti di selezione + sulle geometrie. + + Ritorna una lista di 3 elementi (None in caso di errore): + None + un arco, se = None non c'é arco di raccordo tra le due linee + None + """ + # da fare + return [None, None, None] + + +# =============================================================================== +# FINE - ELLISSE E LINEA +# INIZIO - 2 ARCHI +# =============================================================================== + + +# =============================================================================== +# filletBridgeTheGapBetweenArcs +# =============================================================================== +def filletBridgeTheGapBetweenArcs(arc1, ptOnArc1, arc2, ptOnArc2, radius, filletMode): + """ + la funzione raccorda due archi attraverso + un arco di raccordo di raggio che più si avvicinza ai punti di selezione + sull'arco1 e sull'arco2 . + modalità di raccordo; 1=Taglia-estendi, 2=Non taglia-estendi + + Ritorna una lista di 3 elementi (None in caso di errore): + una arco che sostituisce + un arco, se = None non c'é arco di raccordo tra le due linee + una arco che sostituisce + """ + # ricavo i possibili archi di raccordo + filletArcs = getFilletArcsBetweenArcs(arc1, arc2, radius) + + # cerco l'arco valido più vicino a ptOnArc1 e ptOnArc2 + AvgList = [] + Avg = sys.float_info.max + + resFilletArc = QadArc() + resArc1 = QadArc() + resArc2 = QadArc() + for filletArc in filletArcs: + # ricavo il nuovo arco1 in modo che sia tangente con l'arco di raccordo + newArc1, distFromPtOnArc1 = getNewArcAccordingFilletArc(arc1.getArc(), filletArc, ptOnArc1) + if newArc1 is None: + continue + # ricavo il nuovo arco in modo che sia tangente con l'arco di raccordo + newArc2, distFromPtOnArc2 = getNewArcAccordingFilletArc(arc2.getArc(), filletArc, ptOnArc2) + if newArc2 is None: + continue + + del AvgList[:] + AvgList.append(distFromPtOnArc1) + AvgList.append(distFromPtOnArc2) + + currAvg = qad_utils.numericListAvg(AvgList) + if currAvg < Avg: # mediamente più vicino + Avg = currAvg + resArc1.set(newArc1) + resFilletArc.set(filletArc) + resArc2.set(newArc2) + + if Avg == sys.float_info.max: + return None + + if filletMode == 1: # 1=Taglia-estendi + return [resArc1, resFilletArc, resArc2] + else: + return [None, resFilletArc, None] + + +# =============================================================================== +# getFilletArcsBetweenArcs +# =============================================================================== +def getFilletArcsBetweenArcs(arc1, arc2, radius): + """ + la funzione raccorda due archi attraverso un arco di raccordo di raggio . + + Ritorna una lista dei possibili archi + """ + circle1 = QadCircle() + circle1.set(arc1.center, arc1.radius) + circle2 = QadCircle() + circle2.set(arc2.center, arc2.radius) + + return getFilletArcsBetweenCircles(circle1, circle2, radius) + + +# =============================================================================== +# FINE - 2 ARCHI +# INIZIO - ARCO ED ARCO DI ELLISSE +# =============================================================================== + + +# =============================================================================== +# filletBridgeTheGapBetweenArcEllipsearc +# =============================================================================== +def filletBridgeTheGapBetweenArcEllipsearc(arc, ptOnArc, ellipseArc, ptOnEllipseArc, radius): + """ + la funzione raccorda una acro con un rco di ellisse + un arco di raccordo di raggio che più si avvicinza ai punti di selezione + sulle geometrie. + + Ritorna una lista di 3 elementi (None in caso di errore): + None + un arco, se = None non c'é arco di raccordo tra le due linee + None + """ + # da fare + return [None, None, None] + + +# =============================================================================== +# FINE - ARCO ED ARCO DI ELLISSE +# INIZIO - ARCO E LINEA +# =============================================================================== + + +# =============================================================================== +# filletBridgeTheGapBetweenArcLine +# =============================================================================== +def filletBridgeTheGapBetweenArcLine(arc, ptOnArc, line, ptOnLine, radius, filletMode): + """ + la funzione raccorda un arco e un segmento retto attraverso + un arco di raccordo di raggio che piùsi avvicinza ai punti di selezione + sull'arco e sul segmento retto . + modalità di raccordo; 1=Taglia-estendi, 2=Non taglia-estendi + + Ritorna una lista di 3 elementi (None in caso di errore): + una arco che sostituisce + un arco, se = None non c'é arco di raccordo tra le due linee + una linea che sostituisce + """ + # ricavo i possibili archi di raccordo + filletArcs = getFilletArcsBetweenArcLine(arc, line, radius) + + # cerco l'arco valido più vicino a ptOnArc e ptOnLine + AvgList = [] + Avg = sys.float_info.max + + resArc = QadArc() + resFilletArc = QadArc() + resLine = QadLine() + for filletArc in filletArcs: + # ricavo il nuovo segmento in modo che sia tangente con l'arco di raccordo + newLine, distFromPtOnLine = getNewLineAccordingFilletArc(line, filletArc, ptOnLine) + if newLine is None: + continue + + # ricavo il nuovo arco in modo che sia tangente con l'arco di raccordo + newArc, distFromPtOnArc = getNewArcAccordingFilletArc(arc, filletArc, ptOnArc) + if newArc is None: + continue + + del AvgList[:] + AvgList.append(distFromPtOnLine) + AvgList.append(distFromPtOnArc) + + currAvg = qad_utils.numericListAvg(AvgList) + if currAvg < Avg: # mediamente più vicino + Avg = currAvg + resLine.set(newLine) + resFilletArc.set(filletArc) + resArc.set(newArc) + + if Avg == sys.float_info.max: + return None + + if filletMode == 1: # 1=Taglia-estendi + return [resArc, resFilletArc, resLine] + else: + return [None, resFilletArc, None] + + +# =============================================================================== +# getFilletArcsBetweenArcLine +# =============================================================================== +def getFilletArcsBetweenArcLine(arc, line, radius): + """ + la funzione raccorda un arco e una linea retta attraverso + un arco di raccordo di raggio . + + Ritorna una lista dei possibili archi + """ + circle = QadCircle() + circle.set(arc.center, arc.radius) + + return getFilletArcsBetweenCircleLine(circle, line, radius) + + +# =============================================================================== +# FINE - ARCO E LINEA +# INIZIO - 2 ARCHI DI ELLISSE +# =============================================================================== + + +# =============================================================================== +# filletBridgeTheGapBetweenEllipsearcs +# =============================================================================== +def filletBridgeTheGapBetweenEllipsearcs(ellipseArc1, ptOnEllipseArc1, ellipseArc2, ptOnEllipseArc2, radius): + """ + la funzione raccorda due archi di ellisse attraverso + un arco di raccordo di raggio che più si avvicinza ai punti di selezione + sugli archi di ellisse. + + Ritorna una lista di 3 elementi (None in caso di errore): + una arco di ellisse che sostituisce + un arco, se = None non c'é arco di raccordo tra le due linee + una arco di ellisse che sostituisce + """ + # da fare + return [None, None, None] + + +# =============================================================================== +# FINE - 2 ARCHI DI ELLISSE +# INIZIO - LINEA E ARCO DI ELLISSE +# =============================================================================== + + +# =============================================================================== +# filletBridgeTheGapBetweenLineEllipsearc +# =============================================================================== +def filletBridgeTheGapBetweenLineEllipsearc(line, ptOnLine, ellipseArc, ptOnEllipseArc, radius): + """ + la funzione raccorda una linea ed un arco di ellisse attraverso + un arco di raccordo di raggio che più si avvicinza ai punti di selezione + sulle geometrie. + + Ritorna una lista di 3 elementi (None in caso di errore): + una linea che sostituisce + un arco, se = None non c'é arco di raccordo tra le due geometrie + una arco di ellisse che sostituisce + """ + # da fare + return [None, None, None] + + +# =============================================================================== +# FINE - LINEA E ARCO DI ELLISSE +# INIZIO - POLILINEA +# =============================================================================== + + +# ============================================================================ +# filletAllPartsQadPolyline +# ============================================================================ +def filletAllPartsQadPolyline(polyline, radius): + """ + la funzione raccorda ogni segmento al successivo con un raggio di curvatura noto, + la nuova polilinea avrà i vertici cambiati. + """ + if radius <= 0: return + newPolyline = QadPolyline() + + part = polyline.getLinearObjectAt(0) + i = 1 + tot = polyline.qty() + while i <= tot - 1: + nextPart = polyline.getLinearObjectAt(i) + if part.whatIs() == "LINE" and nextPart.whatIs() == "LINE": + # Ritorna una lista di 3 elementi (None in caso di errore): + # - una linea che sostituisce , se = None va rimossa + # - un arco, se = None non c'é arco di raccordo tra le due linee + # - una linea che sostituisce , se = None va rimossa + res = offsetBridgeTheGapBetweenLines(part, nextPart, radius, 1) + if res is None: + return + if res[0] is not None: + part = res[0] + newPolyline.append(part) + if res[1] is not None: + part = res[1] + newPolyline.append(part) + if res[2] is not None: + part = res[2] + i = i + 1 + + if polyline.isClosed(): + nextPart = newPolyline.getLinearObjectAt(0) + if part.whatIs() == "LINE" and nextPart.whatIs() == "LINE": + + # Ritorna una lista di 3 elementi (None in caso di errore): + # - una linea che sostituisce , se = None va rimossa + # - un arco, se = None non c'é arco di raccordo tra le due linee + # - una linea che sostituisce , se = None va rimossa + res = offsetBridgeTheGapBetweenLines(part, nextPart, radius, 1) + if res is None: + return + if res[0] is not None: + newPolyline.append(res[0]) + if res[1] is not None: + newPolyline.append(res[1]) + if res[2] is not None: + nextPart.set(res[2]) + else: + newPolyline.append(part) + + polyline.set(newPolyline) + + return True diff --git a/qad_fillet_maptool.py b/qad_fillet_maptool.py deleted file mode 100644 index 69f96a51..00000000 --- a/qad_fillet_maptool.py +++ /dev/null @@ -1,236 +0,0 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - classe per gestire il map tool in ambito del comando fillet - - ------------------- - begin : 2014-01-31 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -from qgis.gui import * -import math - - -import qad_utils -from qad_snapper import * -from qad_variables import * -from qad_getpoint import * -from qad_rubberband import QadRubberBand -from qad_dim import QadDimStyles - - -#=============================================================================== -# Qad_fillet_maptool_ModeEnum class. -#=============================================================================== -class Qad_fillet_maptool_ModeEnum(): - # si richiede la selezione del primo oggetto - ASK_FOR_FIRST_LINESTRING = 1 - # si richiede la selezione del secondo oggetto - ASK_FOR_SECOND_LINESTRING = 2 - # non si richiede niente - NONE = 3 - # si richiede la selezione della polilinea - ASK_FOR_POLYLINE = 4 - - -#=============================================================================== -# Qad_fillet_maptool class -#=============================================================================== -class Qad_fillet_maptool(QadGetPoint): - - def __init__(self, plugIn): - QadGetPoint.__init__(self, plugIn) - - self.filletMode = 1 # modalità di raccordo; 1=Taglia-estendi, 2=Non taglia-estendi - self.radius = 0.0 - - self.layer = None - self.linearObjectList = qad_utils.QadLinearObjectList() - self.partAt1 = 0 - self.vertexAt1 = 0 - - self.tolerance2ApproxCurve = None - - self.__rubberBand = QadRubberBand(self.canvas) - - - def hidePointMapToolMarkers(self): - QadGetPoint.hidePointMapToolMarkers(self) - self.__rubberBand.hide() - - def showPointMapToolMarkers(self): - QadGetPoint.showPointMapToolMarkers(self) - self.__rubberBand.show() - - def clear(self): - QadGetPoint.clear(self) - self.__rubberBand.reset() - self.mode = None - - def setEntityInfo(self, layer, featureId, linearObjectList, partAt, pointAt): - """ - Setta self.entity, self.atSubGeom, self.linearObjectList, self.partAt, self.pointAt - di primo o del secondo oggetto da raccordare (vedi ) - """ - self.layer = layer - self.featureId = featureId - self.linearObjectList.set(linearObjectList) - self.partAt = partAt - self.pointAt = pointAt - - self.tolerance2ApproxCurve = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) - - def canvasMoveEvent(self, event): - QadGetPoint.canvasMoveEvent(self, event) - - self.__rubberBand.reset() - tmpLinearObjectList = None - - # si richiede la selezione del secondo oggetto - if self.mode == Qad_fillet_maptool_ModeEnum.ASK_FOR_SECOND_LINESTRING: - if self.tmpEntity.isInitialized(): - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - geom = self.layerToMapCoordinates(self.tmpEntity.layer, self.tmpEntity.getGeometry()) - - # ritorna una tupla (, - # - # - # ) - dummy = qad_utils.closestSegmentWithContext(self.tmpPoint, geom) - if dummy[2] is not None: - # ritorna la sotto-geometria al vertice e la sua posizione nella geometria (0-based) - subGeom, atSubGeom = qad_utils.getSubGeomAtVertex(geom, dummy[2]) - tmpLinearObjectList = qad_utils.QadLinearObjectList() - tmpLinearObjectList.fromPolyline(subGeom.asPolyline()) - - # la funzione ritorna una lista con (, - # - # - # <"a sinistra di">) - dummy = tmpLinearObjectList.closestPartWithContext(self.tmpPoint) - tmpPartAt = dummy[2] - tmpPointAt = dummy[1] - - # stessa entità e stessa parte - if self.layer.id() == self.tmpEntity.layer.id() and \ - self.featureId == self.tmpEntity.featureId and \ - self.partAt == tmpPartAt: - return - - # uso il crs del canvas per lavorare con coordinate piane xy - epsg = self.canvas.mapRenderer().destinationCrs().authid() - - if self.tmpShiftKey == True: # tasto shift premuto durante il movimento del mouse - # filletMode = 1 # modalità di raccordo; 1=Taglia-estendi - # raggio = 0 - res = qad_utils.getFilletLinearObjectList(self.linearObjectList, self.partAt, self.pointAt, \ - tmpLinearObjectList, tmpPartAt, tmpPointAt,\ - 1, 0, epsg) - else: - res = qad_utils.getFilletLinearObjectList(self.linearObjectList, self.partAt, self.pointAt, \ - tmpLinearObjectList, tmpPartAt, tmpPointAt,\ - self.filletMode, self.radius, epsg) - if res is None: # raccordo non possibile - return - tmpLinearObjectList = res[0] - - # si richiede la selezione della polilinea - elif self.mode == Qad_fillet_maptool_ModeEnum.ASK_FOR_POLYLINE: - if self.tmpEntity.isInitialized(): - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - geom = self.layerToMapCoordinates(self.tmpEntity.layer, self.tmpEntity.getGeometry()) - # ritorna una tupla (, - # - # - # ) - dummy = qad_utils.closestSegmentWithContext(self.tmpPoint, geom) - if dummy[2] is not None: - # ritorna la sotto-geometria al vertice e la sua posizione nella geometria (0-based) - subGeom, atSubGeom = qad_utils.getSubGeomAtVertex(geom, dummy[2]) - tmpLinearObjectList = qad_utils.QadLinearObjectList() - tmpLinearObjectList.fromPolyline(subGeom.asPolyline()) - tmpLinearObjectList.fillet(self.radius) - - if tmpLinearObjectList is not None: - pts = tmpLinearObjectList.asPolyline(self.tolerance2ApproxCurve) - if self.layer.geometryType() == QGis.Polygon: - geom = QgsGeometry.fromPolygon([pts]) - else: - geom = QgsGeometry.fromPolyline(pts) - - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - geom = self.layerToMapCoordinates(self.layer, geom) - self.__rubberBand.addGeometry(geom, self.layer) - - - def activate(self): - QadGetPoint.activate(self) - self.__rubberBand.show() - - def deactivate(self): - try: # necessario perché se si chiude QGIS parte questo evento nonostante non ci sia più l'oggetto maptool ! - QadGetPoint.deactivate(self) - self.__rubberBand.hide() - except: - pass - - def setMode(self, mode): - self.mode = mode - - # si richiede la selezione del primo oggetto - # si richiede la selezione del secondo oggetto - if self.mode == Qad_fillet_maptool_ModeEnum.ASK_FOR_FIRST_LINESTRING or \ - self.mode == Qad_fillet_maptool_ModeEnum.ASK_FOR_SECOND_LINESTRING: - - if self.mode == Qad_fillet_maptool_ModeEnum.ASK_FOR_FIRST_LINESTRING: - self.setSelectionMode(QadGetPointSelectionModeEnum.ENTITY_SELECTION) - else: - self.setSelectionMode(QadGetPointSelectionModeEnum.ENTITY_SELECTION_DYNAMIC) - - # solo layer lineari editabili che non appartengano a quote - layerList = [] - for layer in self.plugIn.canvas.layers(): - if layer.type() == QgsMapLayer.VectorLayer and layer.geometryType() == QGis.Line and layer.isEditable(): - if len(QadDimStyles.getDimListByLayer(layer)) == 0: - layerList.append(layer) - - self.layersToCheck = layerList - self.setSnapType(QadSnapTypeEnum.DISABLE) - # non si richiede niente - elif self.mode == Qad_fillet_maptool_ModeEnum.NONE: - self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) - self.setDrawMode(QadGetPointDrawModeEnum.NONE) - # si richiede la selezione della polilinea - elif self.mode == Qad_fillet_maptool_ModeEnum.ASK_FOR_POLYLINE: - self.setSelectionMode(QadGetPointSelectionModeEnum.ENTITY_SELECTION_DYNAMIC) - - # solo layer lineari o poligono editabili che non appartengano a quote - layerList = [] - for layer in self.plugIn.canvas.layers(): - if layer.type() == QgsMapLayer.VectorLayer and \ - (layer.geometryType() == QGis.Line or layer.geometryType() == QGis.Polygon) and \ - layer.isEditable(): - if len(QadDimStyles.getDimListByLayer(layer)) == 0: - layerList.append(layer) - - self.layersToCheck = layerList - self.setSnapType(QadSnapTypeEnum.DISABLE) diff --git a/qad_generic_cmd.py b/qad_generic_cmd.py deleted file mode 100644 index 311502f8..00000000 --- a/qad_generic_cmd.py +++ /dev/null @@ -1,237 +0,0 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - classe base per un comando - - ------------------- - begin : 2013-05-22 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -from qad_msg import QadMsg -from qad_textwindow import * -from qad_getpoint import * - - -# Classe che gestisce un comando generico -class QadCommandClass(): - def showMsg(self, msg, displayPromptAfterMsg = False): - if self.plugIn is not None: - self.plugIn.showMsg(msg, displayPromptAfterMsg) - - def showErr(self, err): - if self.plugIn is not None: - self.plugIn.showErr(err) - - def showInputMsg(self, inputMsg, inputType, default = None, keyWords = "", \ - inputMode = QadInputModeEnum.NONE): - if self.plugIn is not None: - self.plugIn.showInputMsg(inputMsg, inputType, default, keyWords, inputMode) - - def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): - if (self.plugIn is not None): - if self.PointMapTool is None: - self.PointMapTool = QadGetPoint(self.plugIn, drawMode) # per selezione di un punto - return self.PointMapTool - else: - return None - - def hidePointMapToolMarkers(self): - if self.PointMapTool is not None: - self.PointMapTool.hidePointMapToolMarkers() - - def setMapTool(self, mapTool): - if self.plugIn is not None: - # setto il maptool per l'input via finestra grafica - self.plugIn.canvas.setMapTool(mapTool) - self.plugIn.mainAction.setChecked(True) - - def waitForPoint(self, msg = QadMsg.translate("QAD", "Specify point: "), \ - default = None, inputMode = QadInputModeEnum.NONE): - self.setMapTool(self.getPointMapTool()) - # setto l'input via finestra di testo - self.showInputMsg(msg, QadInputTypeEnum.POINT2D, default, "", inputMode) - - def waitForString(self, msg, default = None, inputMode = QadInputModeEnum.NONE): - self.setMapTool(self.getPointMapTool()) - # setto l'input via finestra di testo - self.showInputMsg(msg, QadInputTypeEnum.STRING, default, "", inputMode) - - def waitForInt(self, msg, default = None, inputMode = QadInputModeEnum.NOT_NULL): - self.setMapTool(self.getPointMapTool()) - # setto l'input via finestra di testo - self.showInputMsg(msg, QadInputTypeEnum.INT, default, "", inputMode) - - def waitForlong(self, msg, default = None, inputMode = QadInputModeEnum.NONE): - self.setMapTool(self.getPointMapTool()) - # setto l'input via finestra di testo - self.showInputMsg(msg, QadInputTypeEnum.LONG, default, "", inputMode) - - def waitForFloat(self, msg, default = None, inputMode = QadInputModeEnum.NONE): - self.setMapTool(self.getPointMapTool()) - # setto l'input via finestra di testo - self.showInputMsg(msg, QadInputTypeEnum.FLOAT, default, "", inputMode) - - def waitForBool(self, msg, default = None, inputMode = QadInputModeEnum.NONE): - self.setMapTool(self.getPointMapTool()) - # setto l'input via finestra di testo - self.showInputMsg(msg, QadInputTypeEnum.BOOL, default, "", inputMode) - - def waitForSelSet(self, msg = QadMsg.translate("QAD", "Select objects: ")): - self.getPointMapTool().setDrawMode(QadGetPointDrawModeEnum.ELASTIC_RECTANGLE) - self.setMapTool(self.getPointMapTool()) - # setto l'input via finestra di testo - self.showInputMsg(msg, QadInputTypeEnum.POINT2D) - - def waitFor(self, msg, inputType, default = None, keyWords = "", \ - inputMode = QadInputModeEnum.NONE): - self.setMapTool(self.getPointMapTool()) - # setto l'input via finestra di testo - self.showInputMsg(msg, inputType, default, keyWords, inputMode) - - def getCurrMsgFromTxtWindow(self): - if self.plugIn is not None: - return self.plugIn.getCurrMsgFromTxtWindow() - else: - return None - - def showEvaluateMsg(self, msg = None): - if self.plugIn is not None: - self.plugIn.showEvaluateMsg(msg) - - def runCommandAbortingTheCurrent(self): - self.plugIn.runCommandAbortingTheCurrent(self.getName()) - - def getToolTipText(self): - text = self.getName() - if self.getNote() > 0: - text = text + "\n\n" + self.getNote() - return text - - #============================================================================ - # funzioni da sovrascrivere con le classi ereditate da questa - #============================================================================ - def getName(self): - """ impostare il nome del comando in maiuscolo """ - return "" - - def getEnglishName(self): - """ impostare il nome del comando in inglese maiuscolo """ - return "" - - def connectQAction(self, action): - pass - #QObject.connect(action, SIGNAL("triggered()"), self.plugIn.runPLINECommand) - - def getIcon(self): - # impostare l'icona del comando (es. QIcon(":/plugins/qad/icons/pline.png")) - # ricordarsi di inserire l'icona in resources.qrc e di ricompilare le risorse - return None - - def getNote(self): - """ impostare le note esplicative del comando """ - return "" - - def __init__(self, plugIn): - self.plugIn = plugIn - self.PointMapTool = None - self.step = 0 - self.isValidPreviousInput = True - - # inizializzare tutti i maptool necessari al comando - # esempio di struttura di un comando che richiede - # 1) un punto - # self.mapTool = QadGetPoint(self.plugIn) # per selezione di un punto - - def __del__(self): - """ distruttore """ - self.hidePointMapToolMarkers() - if self.PointMapTool: - self.PointMapTool.removeItems() - del self.PointMapTool - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return None - - def run(self, msgMapTool = False, msg = None): - """ - Esegue il comando. - - msgMapTool; se True significa che arriva un valore da MapTool del comando - se false significa che il valore é nel parametro msg - - msg; valore in input al comando (usato quando msgMapTool = False) - - ritorna True se il comando é terminato altrimenti False - """ - # esempio di struttura di un comando che richiede - # 1) un punto - if self.step == 0: # inizio del comando - self.waitForPoint() # si appresta ad attendere un punto - self.step = self.step + 1 - return False - elif self.step == 1: # dopo aver atteso un punto si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - pt = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - pt = msg - - return True - - def mapToLayerCoordinates(self, layer, point_geom): - # transform point o geometry coordinates from output CRS to layer's CRS - if self.plugIn is None: - return None - if type(point_geom) == QgsPoint: - return self.plugIn.canvas.mapRenderer().mapToLayerCoordinates(layer, point_geom) - elif type(point_geom) == QgsGeometry: - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - coordTransform = QgsCoordinateTransform(self.plugIn.canvas.mapRenderer().destinationCrs(), layer.crs()) - g = QgsGeometry(point_geom) - g.transform(coordTransform) - return g - else: - return None - - def layerToMapCoordinates(self, layer, point_geom): - # transform point o geometry coordinates from layer's CRS to output CRS - if self.plugIn is None: - return None - if type(point_geom) == QgsPoint: - return self.plugIn.canvas.mapRenderer().layerToMapCoordinates(layer, point_geom) - elif type(point_geom) == QgsGeometry: - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - coordTransform = QgsCoordinateTransform(layer.crs(), self.plugIn.canvas.mapRenderer().destinationCrs()) - g = QgsGeometry(point_geom) - g.transform(coordTransform) - return g - else: - return None diff --git a/qad_geom_relations.py b/qad_geom_relations.py new file mode 100644 index 00000000..277444d7 --- /dev/null +++ b/qad_geom_relations.py @@ -0,0 +1,5011 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + classe per la gestione delle relazioni (intersezioni, tangenza, + perpendicolarità, minima distanza) tra oggetti geometrici di base: + linea , arco, arco di ellisse, cerchio, ellisse + + ------------------- + begin : 2019-02-28 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from qgis.PyQt.QtCore import * +from qgis.PyQt.QtGui import * +from qgis.core import * +from qgis.gui import * + +import math +import sys + +try: + import numpy as np +except: + raise Exception("Need to have numpy installed") + +try: + from scipy.optimize import fsolve, minimize + NO_SCIPY = False +except: + NO_SCIPY = True + # raise Exception("Need to have scipy installed") + +from .qad_line import QadLine +from .qad_circle import QadCircle +from .qad_arc import QadArc +from .qad_ellipse import QadEllipse +from .qad_ellipse_arc import QadEllipseArc + +from . import qad_utils +from .qad_multi_geom import * + + +# =============================================================================== +# QadIntersections class +# rappresenta una classe che calcola le intersezioni tra oggetti di base: linea, cerchio, arco, ellisse, arco di ellisse +# =============================================================================== +class QadIntersections(): + + def __init__(self): + pass + + # =============================================================================== + # metodi per le linee infinite - inizio + # =============================================================================== + + # =============================================================================== + # twoInfinityLines + # =============================================================================== + @staticmethod + def twoInfinityLines(line1, line2): + """ + La funzione ritorna il punto di intersezione tra la linea1 e la linea2 considerate linee infinite. + La funzione ritorna None se le linee non hanno intersezione. + """ + return qad_utils.getIntersectionPointOn2InfinityLines(line1.pt1, line1.pt2, line2.pt1, line2.pt2) + + + # =============================================================================== + # infinityLineWithLine + # =============================================================================== + @staticmethod + def infinityLineWithLine(infinityLine, line): + """ + La funzione ritorna il punto di intersezione tra una linea infinita e un segmento . + La funzione ritorna None se non c'è intersezione. + """ + ptInt = QadIntersections.twoInfinityLines(infinityLine, line) + if ptInt is None: return None + if line.containsPt(ptInt) != True: + return None + return ptInt + + + # =============================================================================== + # infinityLineWithCircle + # =============================================================================== + @staticmethod + def infinityLineWithCircle(infinityLine, circle): + """ + La funzione ritorna i punti di intersezione tra una linea infinita ed un cerchio. + """ + # sposto le geometrie vicino a 0,0 per migliorare la precisione dei calcoli + dx = circle.center.x() + dy = circle.center.y() + myInfinityLine = infinityLine.copy() + myInfinityLine.move(-dx, -dy) + myCircle = circle.copy() + myCircle.move(-dx, -dy) + + if qad_utils.ptNear(myInfinityLine.pt1, myInfinityLine.pt2): return [] + + x2_self = myCircle.center.x() * myCircle.center.x() # X del centro del cerchio al quadrato + y2_self = myCircle.center.y() * myCircle.center.y() # Y del centro del cerchio al quadrato + radius2_self = myCircle.radius * myCircle.radius # raggio del cerchio al quadrato + + diffX = myInfinityLine.pt2.x() - myInfinityLine.pt1.x() + # se diffX è così vicino a zero + if qad_utils.doubleNear(diffX, 0.0): # se myInfinityLine è una retta verticale + B = -2 * myCircle.center.y() + C = x2_self + y2_self + (myInfinityLine.pt1.x() * myInfinityLine.pt1.x()) - (2* myInfinityLine.pt1.x() * myCircle.center.x()) - radius2_self + D = (B * B) - (4 * C) + # se D è così vicino a zero + if qad_utils.doubleNear(D, 0.0): + D = 0 + elif D < 0: # non si può fare la radice quadrata di un numero negativo + return [] + E = math.sqrt(D) + + y1 = (-B + E) / 2 + x1 = myInfinityLine.pt1.x() + + y2 = (-B - E) / 2 + x2 = myInfinityLine.pt1.x() + else: + m = (myInfinityLine.pt2.y() - myInfinityLine.pt1.y()) / diffX + q = myInfinityLine.pt1.y() - (m * myInfinityLine.pt1.x()) + A = 1 + (m * m) + B = (2 * m * q) - (2 * myCircle.center.x()) - (2 * m * myCircle.center.y()) + C = x2_self + (q * q) + y2_self - (2 * q * myCircle.center.y()) - radius2_self + + D = (B * B) - 4 * A * C + # se D è così vicino a zero + if qad_utils.doubleNear(D, 0.0): + D = 0 + elif D < 0: # non si può fare la radice quadrata di un numero negativo + return [] + E = math.sqrt(D) + + x1 = (-B + E) / (2 * A) + y1 = myInfinityLine.pt1.y() + m * x1 - m * myInfinityLine.pt1.x() + + x2 = (-B - E) / (2 * A) + y2 = myInfinityLine.pt1.y() + m * x2 - m * myInfinityLine.pt1.x() + + # traslo i punti per riportarli alla loro posizione originale + result = [] + result.append(QgsPointXY(x1 + dx, y1 + dy)) + if x1 != x2 or y1 != y2: # i punti non sono coincidenti + result.append(QgsPointXY(x2 + dx, y2 + dy)) + + return result + + + # =============================================================================== + # infinityLineWithArc + # =============================================================================== + @staticmethod + def infinityLineWithArc(infinityLine, arc): + """ + La funzione ritorna i punti di intersezione tra una linea infinita ed un cerchio. + """ + result = [] + circle = QadCircle() + circle.set(arc.center, arc.radius) + intPtList = QadIntersections.infinityLineWithCircle(infinityLine, circle) + for intPt in intPtList: + if arc.isPtOnArcOnlyByAngle(intPt): + result.append(intPt) + return result + + + # =============================================================================== + # infinityLineWithEllipse + # =============================================================================== + @staticmethod + def infinityLineWithEllipse(infinityLine, ellipse): + """ + La funzione ritorna i punti di intersezione tra una linea infinita e un'ellisse. + """ + # http://www.ambrsoft.com/TrigoCalc/Circles2/Ellipse/EllipseLine.htm + # la formula dell'ellisse è: + # (x - h)^2 / a^2 + (y - k)^2 / b^2 = 1 + # la formula della linea è: + # y = mx + c + # se h=0 e k=0 e c<>0 (ellisse orizzontale con centro in 0,0; linea che non passa da 0,0) + + # deltaForX = a * b * sqrt(a^2 * m^2 + b^2 - c^2) + # deltaForY = a * b * m * sqrt(a^2 * m^2 + b^2 - c^2) + # denom = a^2 * m^2 + b^2 + + # x1 = (-a^2 * m * c + deltaForX) / denom + # y1 = (b^2 * c + deltaForY) / denom + # x2 = (-a^2 * m * c - deltaForX) / denom + # y1 = (b^2 * c - deltaForY) / denom + + result = [] + # traslo e ruoto la linea per confrontarla con l'ellisse con centro in 0,0 e con rotazione = 0 + myP1 = ellipse.translateAndRotatePtForNormalEllipse(infinityLine.pt1, False) + myP2 = ellipse.translateAndRotatePtForNormalEllipse(infinityLine.pt2, False) + + a = qad_utils.getDistance(ellipse.center, ellipse.majorAxisFinalPt) # semiasse maggiore + b = a * ellipse.axisRatio # semiasse minore + + # a lo chiamo m e b lo chiamo c nell'equazione della retta + m, c = qad_utils.get_A_B_LineEquation(myP1.x(), myP1.y(), myP2.x(), myP2.y()) + + dummy = a*a * m*m + b*b - c*c + if dummy < 0: # non si può fare la radice quadrata di un numero negativo + return result + + deltaForX = a * b * math.sqrt(dummy) + deltaForY = a * b * m * math.sqrt(dummy) + denom = a*a * m*m + b*b + if denom == 0: return result + + x1 = (-(a*a) * m * c + deltaForX) / denom + y1 = (b*b * c + deltaForY) / denom + x2 = (-(a*a) * m * c - deltaForX) / denom + y2 = (b*b * c - deltaForY) / denom + + # traslo e ruoto il punto per riportarlo nella posizione originale (con il centro e la rotazione dell'ellisse originale) + myP1.set(x1, y1) + myP1 = ellipse.translateAndRotatePtForNormalEllipse(myP1, True) + result.append(myP1) + + # traslo e ruoto il punto per riportarlo nella posizione originale (con il centro e la rotazione dell'ellisse originale) + myP2.set(x2, y2) + myP2 = ellipse.translateAndRotatePtForNormalEllipse(myP2, True) + result.append(myP2) + + return result + + + # =============================================================================== + # infinityLineWithEllipseArc + # =============================================================================== + @staticmethod + def infinityLineWithEllipseArc(infinityLine, ellipseArc): + """ + La funzione ritorna i punti di intersezione tra una linea infinita ed un arco di ellisse. + """ + result = [] + ellipse = QadEllipse() + ellipse.set(ellipseArc.center, ellipseArc.majorAxisFinalPt, ellipseArc.axisRatio) + intPtList = QadIntersections.infinityLineWithEllipse(infinityLine, ellipse) + for intPt in intPtList: + if ellipseArc.isPtOnEllipseArcOnlyByAngle(intPt): + result.append(intPt) + + return result + + + # =============================================================================== + # metodi per le linee infinite - fine + # metodi per i segmenti - inizio + # =============================================================================== + + + # =============================================================================== + # twoLines + # =============================================================================== + @staticmethod + def twoLines(line1, line2): + """ + La funzione ritorna il punto di intersezione tra 2 segmenti. + La funzione ritorna None se i segmenti non hanno intersezione. + """ + intPt = QadIntersections.twoInfinityLines(line1, line2) + if intPt is None: return None + if line1.containsPt(intPt) == False or line2.containsPt(intPt) == False: + return None + return intPt + + + # =============================================================================== + # lineWithCircle + # =============================================================================== + @staticmethod + def lineWithCircle(line, circle): + """ + La funzione ritorna i punti di intersezione tra un segmento ed un cerchio. + """ + result = [] + intPtList = QadIntersections.infinityLineWithCircle(line, circle) + for intPt in intPtList: + if line.containsPt(intPt): + result.append(intPt) + return result + + + # =============================================================================== + # lineWithArc + # =============================================================================== + @staticmethod + def lineWithArc(line, arc): + """ + La funzione ritorna i punti di intersezione tra un segmento ed un arco. + """ + result = [] + circle = QadCircle() + circle.set(arc.center, arc.radius) + intPtList = QadIntersections.lineWithCircle(line, circle) + for intPt in intPtList: + if arc.isPtOnArcOnlyByAngle(intPt): + result.append(intPt) + return result + + + # =============================================================================== + # lineWithEllipse + # =============================================================================== + @staticmethod + def lineWithEllipse(line, ellipse): + """ + La funzione ritorna i punti di intersezione tra un segmento e un'ellisse. + """ + result = [] + intPtList = QadIntersections.infinityLineWithEllipse(line, ellipse) + for intPt in intPtList: + if line.containsPt(intPt): + result.append(intPt) + return result + + + # =============================================================================== + # lineWithEllipseArc + # =============================================================================== + @staticmethod + def lineWithEllipseArc(line, ellipseArc): + """ + La funzione ritorna i punti di intersezione tra un segmento e un arco di ellisse. + """ + result = [] + ellipse = QadEllipse() + ellipse.set(ellipseArc.center, ellipseArc.majorAxisFinalPt, ellipseArc.axisRatio) + intPtList = QadIntersections.lineWithEllipse(line, ellipse) + for intPt in intPtList: + if ellipseArc.isPtOnEllipseArcOnlyByAngle(intPt): + result.append(intPt) + return result + + + # =============================================================================== + # metodi per i segmenti - fine + # metodi per i cerchi - inizio + # =============================================================================== + + + # =============================================================================== + # twoCircles + # =============================================================================== + @staticmethod + def twoCircles(circle1, circle2): + """ + La funzione ritorna i punti di intersezione tra 2 cerchi. + """ + result = [] + # sposto le geometrie vicino a 0,0 per migliorare la precisione dei calcoli + dx = circle1.center.x() + dy = circle1.center.y() + myCircle1 = circle1.copy() + myCircle1.move(-dx, -dy) + myCircle2 = circle2.copy() + myCircle2.move(-dx, -dy) + + # se i punti sono così vicini da essere considerati uguali + if qad_utils.ptNear(myCircle1.center, myCircle2.center): # stesso centro + return [] + distFromCenters = qad_utils.getDistance(myCircle1.center, myCircle2.center) + distFromCirc = distFromCenters - myCircle1.radius - myCircle2.radius + + # se è così vicino allo zero da considerarlo = 0 + if qad_utils.doubleNear(distFromCirc, 0): + angle = qad_utils.getAngleBy2Pts(myCircle1.center, myCircle2.center) + pt = qad_utils.getPolarPointByPtAngle(myCircle1.center, angle, myCircle1.radius) + # traslo il punto per riportarlo alla sua posizione originale + pt.set(pt.x() + dx, pt.y() + dy) + result.append(pt) + return result + + if distFromCirc > 0: # i cerchi sono troppo distanti + return [] + + x2_myCircle1 = myCircle1.center.x() * myCircle1.center.x() # X del centro del cerchio al quadrato + x2_circle = myCircle2.center.x() * myCircle2.center.x() # Y del centro del cerchio al quadrato + radius2_myCircle1 = myCircle1.radius * myCircle1.radius # raggio del cerchio al quadrato + radius2_circle = myCircle2.radius * myCircle2.radius # raggio del cerchio al quadrato + + if qad_utils.doubleNear(myCircle1.center.y(), myCircle2.center.y()): + x1 = x2_circle - x2_myCircle1 + radius2_myCircle1 - radius2_circle + x1 = x1 / (2 * (myCircle2.center.x() - myCircle1.center.x())) + x2 = x1 + D = radius2_myCircle1 - ((x1 - myCircle1.center.x()) * (x1 - myCircle1.center.x())) + # se D è così vicino a zero + if qad_utils.doubleNear(D, 0.0): + D = 0 + elif D < 0: # non si può fare la radice quadrata di un numero negativo + return [] + E = math.sqrt(D) + + y1 = myCircle1.center.y() + E + y2 = myCircle1.center.y() - E + else: + y2_myCircle1 = myCircle1.center.y() * myCircle1.center.y() # Y del centro del cerchio al quadrato + y2_circle = myCircle2.center.y() * myCircle2.center.y() # Y del centro del cerchio al quadrato + + a = (myCircle1.center.x() - myCircle2.center.x()) / (myCircle2.center.y() - myCircle1.center.y()) + b = x2_circle - x2_myCircle1 + y2_circle - y2_myCircle1 + radius2_myCircle1 - radius2_circle + b = b / (2 * (myCircle2.center.y() - myCircle1.center.y())) + + A = 1 + (a * a) + B = (2 * a * b) - (2 * myCircle1.center.x()) - (2 * a * myCircle1.center.y()) + C = (b * b) - (2 * myCircle1.center.y() * b) + x2_myCircle1 + y2_myCircle1 - radius2_myCircle1 + D = (B * B) - (4 * A * C) + # se D è così vicino a zero + if qad_utils.doubleNear(D, 0.0): + D = 0 + elif D < 0: # non si può fare la radice quadrata di un numero negativo + return [] + E = math.sqrt(D) + + x1 = (-B + E) / (2 * A) + y1 = a * x1 + b + + x2 = (-B - E) / (2 * A) + y2 = a * x2 + b + + # traslo i punti per riportarli alla loro posizione originale + result.append(QgsPointXY(x1 + dx, y1 + dy)) + if x1 != x2 or y1 != y2: # i punti non sono coincidenti + result.append(QgsPointXY(x2 + dx, y2 + dy)) + + return result + + + # =============================================================================== + # circleWithArc + # =============================================================================== + @staticmethod + def circleWithArc(circle, arc): + """ + La funzione ritorna i punti di intersezione tra un cerchio ed un arco. + """ + result = [] + circle1 = QadCircle() + circle1.set(arc.center, arc.radius) + intPtList = QadIntersections.twoCircles(circle, circle1) + for intPt in intPtList: + if arc.isPtOnArcOnlyByAngle(intPt): + result.append(intPt) + return result + + + # =============================================================================== + # generate_initial_guesses + # Funzione per generare i guess iniziali per i calcoli di intersezione cerchio con ellisse (chatgpt) + # =============================================================================== + def generate_initial_guesses(h, k, a, b, theta, num_points=20): + t = np.linspace(0, 2 * np.pi, num_points) + x_ellipse = a * np.cos(t) + y_ellipse = b * np.sin(t) + + initial_guesses = [] + for x_e, y_e in zip(x_ellipse, y_ellipse): + x_rotated = h + (x_e * np.cos(theta) - y_e * np.sin(theta)) + y_rotated = k + (x_e * np.sin(theta) + y_e * np.cos(theta)) + initial_guesses.append((x_rotated, y_rotated)) + + return initial_guesses + + + # =============================================================================== + # rotate + # Funzione per la trasformazione di coordinate per i calcoli di intersezione cerchio con ellisse (chatgpt) + # =============================================================================== + def rotate(x, y, theta): + x_prime = x * np.cos(theta) + y * np.sin(theta) + y_prime = -x * np.sin(theta) + y * np.cos(theta) + return x_prime, y_prime + + +# ============================================================================ + # getEquationForIntCicleEllipse + # Definisce un sistema di equazioni non lineari per i calcoli di intersezione cerchio con ellisse (chatgpt) + # x e y: centro + # a e b: i semiassi dell'ellisse + # theta: l'angolo di rotazione dell'ellisse + # h e k: le traslazioni dell'ellisse rispetto all'origine. + # ============================================================================ + @staticmethod + def getEquationForIntCicleEllipse(xy, circle, ellipse): + x, y = xy + + circle_eq = (x - circle.center.x())**2 + (y - circle.center.y())**2 - circle.radius**2 + + return [circle_eq, QadIntersections.getEquationForEllipse(xy, ellipse)] + + + # =============================================================================== + # circleWithEllipse + # =============================================================================== + @staticmethod + def circleWithEllipse(circle, ellipse): + """ + La funzione ritorna i punti di intersezione tra un cerchio ed una ellisse. + """ + if NO_SCIPY == True: return [] + + # Calcola la lunghezza dell'asse maggiore (a) + a = qad_utils.getDistance(ellipse.center, ellipse.majorAxisFinalPt) + # Calcola la lunghezza dell'asse minore (b) + b = a * ellipse.axisRatio + # theta: l'angolo di rotazione dell'ellisse. + theta = ellipse.getRotation() + # h e k: le traslazioni dell'ellisse rispetto all'origine, coordinate centro + h = ellipse.center.x() + k = ellipse.center.y() + + args = (circle, ellipse) + + # Generiamo i guess iniziali + initial_guesses = QadIntersections.generate_initial_guesses(h, k, a, b, theta) + + # Risoluzione del sistema di equazioni + intersections = [] + for guess in initial_guesses: + sol = fsolve(QadIntersections.getEquationForIntCicleEllipse, guess, args) + if not any(np.isclose(sol, x).all() for x in intersections): + intersections.append(sol) + + result = [] + for intersection in intersections: + result.append(QgsPointXY(intersection[0], intersection[1])) + + return result + +# # http://it.scienza.matematica.narkive.com/cTzzSW1r/intersezione-tra-ellisse-e-circonferenza +# result = [] +# +# # traslo e ruoto il centro del cerchio per confrontarlo con l'ellisse con centro in 0,0 e con rotazione = 0 +# myCircle = QadCircle(circle) +# myCircle.center = ellipse.translateAndRotatePtForNormalEllipse(circle.center, False) +# +# a = qad_utils.getDistance(ellipse.center, ellipse.majorAxisFinalPt) # semiasse maggiore +# b = a * ellipse.axisRatio # semiasse minore +# +# a2 = a * a # a al quadrato +# a4 = a2 * a2 # a alla quarta +# b2 = b * b # b al quadrato +# c2 = (a / b) * (a / b) +# c4 = c2 * c2 +# r = myCircle.radius +# r2 = r * r +# xc = myCircle.center.x() # x del centro del cerchio +# xc2 = xc * xc # x cerchio al quadrato +# yc = myCircle.center.y() # y del centro del cerchio +# yc2 = yc * yc # y cerchio al quadrato +# a2_b2 = a2 - b2 +# +# # [a^4+(p^2+q^2-r^2)^2-2a^2(p^2-q^2+r^2] + +# # y [4q(r^2-a^2-p^2-q^2)] + +# # y^2 [2a^2-2a^2c^2+2p^2+2c^2p^2+6q^2-2c^2q^2-2r^2+2c^2r^2] + +# # y^3 [4c^2q-4q]+ +# # y^4 [1-2c^2+c^4] = 0 +# +# z0 = a4 + (xc2 + yc2 - r2) * (xc2 + yc2 - r2) - (2 * a2) * (xc2 - yc2 + r2) +# z1 = (4 * yc2) * (r2 - a2 - xc2 - yc2) +# z2 = (2 * a2) - (2 * a2 * c2) + 2 *xc2 + (2 * c2 * xc2) + (6 * yc2) - (2 *c2 * yc2) - (2 * r2) + (2 * c2 * r2) +# z3 = (4 * c2 * yc) - (4 * yc) +# z4 = 1 - (2 * c2) + c4 +# +# y_result = np.roots([z4, z3, z2, z1, z0]) +# for y in y_result: +# y = float(y) +# n = (1.0 - y * y / b2) * a2 # data la Y calcolo la X +# if qad_utils.doubleNear(n, 0): n = 0 # per problemi di precisione di calcolo (es. se x = 10 , n = -1.11022302463e-14 !) +# if n >= 0: +# x = math.sqrt(n) +# p = QgsPointXY(x, y) +# # verifico se il punto va bene +# dist = qad_utils.getDistance(p, myCircle.center) +# # se la distanza coincide con il raggio del cerchio +# if qad_utils.doubleNear(dist, myCircle.radius, 1.e-1): # lo so che fa schifo ma l'approssimazione dei calcoli... +# # traslo e ruoto il punto per riportarlo nella posizione originale (con il centro e la rotazione dell'ellisse originale) +# p = ellipse.translateAndRotatePtForNormalEllipse(p, True) +# qad_utils.appendUniquePointToList(result, p) +# +# # verifico l'altra coordinata x +# p = QgsPointXY(-x, y) +# # verifico se il punto va bene +# dist = qad_utils.getDistance(p, myCircle.center) +# # se la distanza coincide con il raggio del cerchio +# if qad_utils.doubleNear(dist, myCircle.radius, 1.e-1): # lo so che fa schifo ma l'approssimazione dei calcoli... +# # traslo e ruoto il punto per riportarlo nella posizione originale (con il centro e la rotazione dell'ellisse originale) +# p = ellipse.translateAndRotatePtForNormalEllipse(p, True) +# qad_utils.appendUniquePointToList(result, p) +# +# return result + + + # =============================================================================== + # circleWithEllipseArc + # =============================================================================== + @staticmethod + def circleWithEllipseArc(circle, ellipseArc): + """ + La funzione ritorna i punti di intersezione tra un cerchio ed un arco di ellisse. + """ + result = [] + ellipse = QadEllipse() + ellipse.set(ellipseArc.center, ellipseArc.majorAxisFinalPt, ellipseArc.axisRatio) + intPtList = QadIntersections.circleWithEllipse(circle, ellipse) + for intPt in intPtList: + if ellipseArc.isPtOnEllipseArcOnlyByAngle(intPt): + result.append(intPt) + return result + + + # =============================================================================== + # metodi per i cerchi - fine + # metodi per gli archi - inizio + # =============================================================================== + + + # =============================================================================== + # twoArcs + # =============================================================================== + @staticmethod + def twoArcs(arc1, arc2): + """ + La funzione ritorna i punti di intersezione tra 2 archi. + """ + result = [] + circle = QadCircle() + circle.set(arc1.center, arc1.radius) + intPtList = QadIntersections.circleWithArc(circle, arc2) + for intPt in intPtList: + if arc1.isPtOnArcOnlyByAngle(intPt): + result.append(intPt) + return result + + + # =============================================================================== + # arcWithEllipseArc + # =============================================================================== + @staticmethod + def arcWithEllipseArc(arc, ellipseArc): + """ + La funzione ritorna i punti di intersezione tra un arco ed un arco di ellisse. + """ + result = [] + circle = QadCircle() + circle.set(arc.center, arc.radius) + intPtList = QadIntersections.circleWithEllipseArc(circle, ellipseArc) + for intPt in intPtList: + if arc.isPtOnArcOnlyByAngle(intPt): + result.append(intPt) + return result + + + # =============================================================================== + # metodi per gli archi - fine + # metodi per le ellissi - inizio + # =============================================================================== + + + # ============================================================================ + # getEquationForEllipse + # Definisce una funzione che rappresenta un'ellisse ruotata e traslata + # x e y: centro + # a e b: i semiassi dell'ellisse + # theta: l'angolo di rotazione dell'ellisse + # h e k: le traslazioni dell'ellisse rispetto all'origine. + # ============================================================================ + @staticmethod + def getEquationForEllipse(xy, ellipse): + x, y = xy + # Calcola la lunghezza dell'asse maggiore (a) + a = qad_utils.getDistance(ellipse.center, ellipse.majorAxisFinalPt) + # Calcola la lunghezza dell'asse minore (b) + b = a * ellipse.axisRatio + # theta: l'angolo di rotazione dell'ellisse. + theta = ellipse.getRotation() + # h e k: le traslazioni dell'ellisse rispetto all'origine, coordinate centro + h = ellipse.center.x() + k = ellipse.center.y() + + cos_t = np.cos(theta) + sin_t = np.sin(theta) + term1 = ((x - h) * cos_t + (y - k) * sin_t) ** 2 / a ** 2 + term2 = ((x - h) * sin_t - (y - k) * cos_t) ** 2 / b ** 2 + return term1 + term2 - 1 + + + @staticmethod + def sistema(xy, ellipse1, ellipse2): + return [QadIntersections.getEquationForEllipse(xy, ellipse1), QadIntersections.getEquationForEllipse(xy, ellipse2)] + + + # =============================================================================== + # twoEllipses + # =============================================================================== + @staticmethod + def twoEllipses(ellipse1, ellipse2): + """ + La funzione ritorna i punti di intersezione tra 2 ellissi (chatGPT) + """ + if NO_SCIPY == True: return [] + + # test +# l1 = QadLine() +# l1.set(QgsPointXY(1, 2), QgsPointXY(3, 4)) +# +# center = QgsPointXY(0, 0) +# a = 2 +# b = 1 +# axisRatio = b / a +# theta = math.pi / 4 +# ellipse1 = QadEllipse() +# majorAxisFinalPt = qad_utils.getPolarPointByPtAngle(center, theta, a) +# ellipse1.set(center, majorAxisFinalPt, axisRatio) +# QadMinDistance.fromLineToEllipse(l1, ellipse1) + +# +# +# center = QgsPointXY(-1, -2) +# a = 4 +# b = 2 +# axisRatio = b / a +# theta = math.pi / 4 +# ellipse2 = QadEllipse() +# majorAxisFinalPt = qad_utils.getPolarPointByPtAngle(center, theta, a) +# ellipse2.set(center, majorAxisFinalPt, axisRatio) + + + args = (ellipse1, ellipse2) + # Genera stime iniziali in una griglia intorno ai centri delle ellissi + x_vals = np.linspace(-10, 10, 10) + y_vals = np.linspace(-10, 10, 10) + stima_iniziali = np.array(np.meshgrid(x_vals, y_vals)).T.reshape(-1, 2) + + # Trova le intersezioni utilizzando fsolve da diverse stime iniziali + soluzioni = [] + for stima in stima_iniziali: + soluzione = fsolve(QadIntersections.sistema, stima, args) + if QadIntersections.sistema(soluzione, ellipse1, ellipse2)[0] < 1e-6 and QadIntersections.sistema(soluzione, ellipse1, ellipse2)[1] < 1e-6: + soluzioni.append(soluzione) + + # Rimuovi soluzioni duplicate (vicine) + tolleranza = 1e-4 + soluzioni_uniche = [] + for sol in soluzioni: + if not any(np.linalg.norm(sol - s) < tolleranza for s in soluzioni_uniche): + soluzioni_uniche.append(sol) + + result = [] + for sol in soluzioni_uniche: + result.append(QgsPointXY(sol[0], sol[1])) + + return result + + + # =============================================================================== + # ellipseWithArc + # =============================================================================== + @staticmethod + def ellipseWithArc(ellipse, arc): + """ + La funzione ritorna i punti di intersezione tra un'ellisse ed un arco. + """ + result = [] + circle = QadCircle() + circle.set(arc.center, arc.radius) + intPtList = QadIntersections.circleWithEllipse(circle, ellipse) + for intPt in intPtList: + if arc.isPtOnArcOnlyByAngle(intPt): + result.append(intPt) + return result + + + # =============================================================================== + # ellipseWithEllipseArc + # =============================================================================== + @staticmethod + def ellipseWithEllipseArc(ellipse, ellipseArc): + """ + La funzione ritorna i punti di intersezione tra un'ellisse ed un arco di ellisse. + """ + result = [] + ellipse1 = QadEllipse() + ellipse1.set(ellipseArc.center, ellipseArc.majorAxisFinalPt, ellipseArc.axisRatio) + intPtList = QadIntersections.twoEllipses(ellipse, ellipse1) + for intPt in intPtList: + if ellipseArc.isPtOnEllipseArcOnlyByAngle(intPt): + result.append(intPt) + return result + + + # =============================================================================== + # metodi per le ellissi - fine + # metodi per gli archi di ellisse - inizio + # =============================================================================== + + + # =============================================================================== + # twoEllipseArcs + # =============================================================================== + @staticmethod + def twoEllipseArcs(EllipseArc1, EllipseArc2): + """ + La funzione ritorna i punti di intersezione tra 2 archi di ellisse. + """ + result = [] + ellipse1 = QadEllipse() + ellipse1.set(EllipseArc1.center, EllipseArc1.majorAxisFinalPt, EllipseArc1.axisRatio) + intPtList = QadIntersections.ellipseWithEllipseArc(ellipse1, EllipseArc2) + for intPt in intPtList: + if EllipseArc1.isPtOnEllipseArcOnlyByAngle(intPt): + result.append(intPt) + return result + + + # =============================================================================== + # metodi per gli archi di ellisse - fine + # metodi per gli oggetti geometrici di base - inizio + # =============================================================================== + + + # ============================================================================ + # twoBasicGeomObjects + # ============================================================================ + @staticmethod + def twoBasicGeomObjects(object1, object2): + """ + la funzione calcola i punti di intersezione tra 2 oggetti geometrici di base: + linea, arco, arco di ellisse, cerchio, ellisse. + """ + if object1.whatIs() == "LINE": + if object2.whatIs() == "LINE": + result = QadIntersections.twoLines(object1, object2) + return [result] if result is not None else [] + elif object2.whatIs() == "CIRCLE": + return QadIntersections.lineWithCircle(object1, object2) + elif object2.whatIs() == "ARC": + return QadIntersections.lineWithArc(object1, object2) + elif object2.whatIs() == "ELLIPSE": + return QadIntersections.lineWithEllipse(object1, object2) + elif object2.whatIs() == "ELLIPSE_ARC": + return QadIntersections.lineWithEllipseArc(object1, object2) + + elif object1.whatIs() == "CIRCLE": + if object2.whatIs() == "LINE": + return QadIntersections.lineWithCircle(object2, object1) + elif object2.whatIs() == "CIRCLE": + return QadIntersections.twoCircles(object1, object2) + elif object2.whatIs() == "ARC": + return QadIntersections.circleWithArc(object1, object2) + elif object2.whatIs() == "ELLIPSE": + return QadIntersections.circleWithEllipse(object1, object2) + elif object2.whatIs() == "ELLIPSE_ARC": + return QadIntersections.circleWithEllipseArc(object1, object2) + + elif object1.whatIs() == "ARC": + if object2.whatIs() == "LINE": + return QadIntersections.lineWithArc(object2, object1) + elif object2.whatIs() == "CIRCLE": + return QadIntersections.circleWithArc(object2, object1) + elif object2.whatIs() == "ARC": + return QadIntersections.twoArcs(object1, object2) + elif object2.whatIs() == "ELLIPSE": + return QadIntersections.ellipseWithArc(object2, object1) + elif object2.whatIs() == "ELLIPSE_ARC": + return QadIntersections.arcWithEllipseArc(object1, object2) + + elif object1.whatIs() == "ELLIPSE": + if object2.whatIs() == "LINE": + return QadIntersections.lineWithEllipse(object2, object1) + elif object2.whatIs() == "CIRCLE": + return QadIntersections.circleWithEllipse(object2, object1) + elif object2.whatIs() == "ARC": + return QadIntersections.ellipseWithArc(object1, object2) + elif object2.whatIs() == "ELLIPSE": + return QadIntersections.twoEllipses(object1, object2) + elif object2.whatIs() == "ELLIPSE_ARC": + return QadIntersections.ellipseWithEllipseArc(object1, object2) + + elif object1.whatIs() == "ELLIPSE_ARC": + if object2.whatIs() == "LINE": + return QadIntersections.lineWithEllipseArc(object2, object1) + elif object2.whatIs() == "CIRCLE": + return QadIntersections.circleWithEllipseArc(object2, object1) + elif object2.whatIs() == "ARC": + return QadIntersections.arcWithEllipseArc(object2, object1) + elif object2.whatIs() == "ELLIPSE": + return QadIntersections.ellipseWithEllipseArc(object2, object1) + elif object2.whatIs() == "ELLIPSE_ARC": + return QadIntersections.twoEllipseArcs(object1, object2) + + return [] + + + # ============================================================================ + # twoBasicGeomObjectExtensions + # ============================================================================ + @staticmethod + def twoBasicGeomObjectExtensions(object1, object2): + """ + la funzione calcola i punti di intersezione tra le estensioni di 2 oggetti geometrici di base: + linea (diventa linea infinita), arco (diventa cerchio), arco di ellisse (diventa ellisse), cerchio, ellisse. + """ + if object1.whatIs() == "LINE": + if object2.whatIs() == "LINE": + result = QadIntersections.twoInfinityLines(object1, object2) + return [result] if result is not None else () + elif object2.whatIs() == "CIRCLE": + return QadIntersections.infinityLineWithCircle(object1, object2) + elif object2.whatIs() == "ARC": + circle = QadCircle() + circle.set(object2.center, object2.radius) + return QadIntersections.infinityLineWithCircle(object1, circle) + elif object2.whatIs() == "ELLIPSE": + return QadIntersections.infinityLineWithEllipse(object1, object2) + elif object2.whatIs() == "ELLIPSE_ARC": + ellipse = QadEllipse() + ellipse.set(object2.center, object2.majorAxisFinalPt, object2.axisRatio) + return QadIntersections.infinityLineWithEllipse(object1, ellipse) + + elif object1.whatIs() == "CIRCLE": + if object2.whatIs() == "LINE": + return QadIntersections.infinityLineWithCircle(object2, object1) + elif object2.whatIs() == "CIRCLE": + return QadIntersections.twoCircles(object1, object2) + elif object2.whatIs() == "ARC": + circle = QadCircle() + circle.set(object2.center, object2.radius) + return QadIntersections.twoCircles(object1, circle) + elif object2.whatIs() == "ELLIPSE": + return QadIntersections.circleWithEllipse(object1, object2) + elif object2.whatIs() == "ELLIPSE_ARC": + ellipse = QadEllipse() + ellipse.set(object2.center, object2.majorAxisFinalPt, object2.axisRatio) + return QadIntersections.circleWithEllipse(object1, ellipse) + + elif object1.whatIs() == "ARC": + circle = QadCircle() + circle.set(object1.center, object1.radius) + return QadIntersections.twoBasicGeomObjectExtensions(circle, object2) + + elif object1.whatIs() == "ELLIPSE": + if object2.whatIs() == "LINE": + return QadIntersections.infinityLineWithEllipse(object2, object1) + elif object2.whatIs() == "CIRCLE": + return QadIntersections.circleWithEllipse(object2, object1) + elif object2.whatIs() == "ARC": + circle = QadCircle() + circle.set(object2.center, object2.radius) + return QadIntersections.circleWithEllipse(circle, object1) + elif object2.whatIs() == "ELLIPSE": + return QadIntersections.twoEllipses(object1, object2) + elif object2.whatIs() == "ELLIPSE_ARC": + ellipse = QadEllipse() + ellipse.set(object2.center, object2.majorAxisFinalPt, object2.axisRatio) + return QadIntersections.twoEllipses(object1, ellipse) + + elif object1.whatIs() == "ELLIPSE_ARC": + ellipse = QadEllipse() + ellipse.set(object1.center, object1.majorAxisFinalPt, object1.axisRatio) + return QadIntersections.twoBasicGeomObjectExtensions(ellipse, object2) + + return [] + + + # ============================================================================ + # basicGeomObjectWithBasicGeomObjectExtensions + # ============================================================================ + @staticmethod + def basicGeomObjectWithBasicGeomObjectExtensions(object1, object2): + """ + la funzione calcola i punti di intersezione tra un oggetto geometrico di base (object1) e + le estensioni di oggetti geometrico (object2) di base: + linea (diventa linea infinita), arco (diventa cerchio), arco di ellisse (diventa ellisse), cerchio, ellisse. + """ + if object1.whatIs() == "LINE": + if object2.whatIs() == "LINE": + result = QadIntersections.infinityLineWithLine(object2, object1) + return [result] if result is not None else () + elif object2.whatIs() == "CIRCLE": + return QadIntersections.lineWithCircle(object1, object2) + elif object2.whatIs() == "ARC": + circle = QadCircle() + circle.set(object2.center, object2.radius) + return QadIntersections.lineWithCircle(object1, circle) + elif object2.whatIs() == "ELLIPSE": + return QadIntersections.lineWithEllipse(object1, object2) + elif object2.whatIs() == "ELLIPSE_ARC": + ellipse = QadEllipse() + ellipse.set(object2.center, object2.majorAxisFinalPt, object2.axisRatio) + return QadIntersections.lineWithEllipse(object1, ellipse) + + elif object1.whatIs() == "CIRCLE": + return QadIntersections.twoBasicGeomObjectExtensions(object1, object2) + + elif object1.whatIs() == "ARC": + if object2.whatIs() == "LINE": + return QadIntersections.infinityLineWithArc(object2, object1) + elif object2.whatIs() == "CIRCLE": + return QadIntersections.circleWithArc(object2, object1) + elif object2.whatIs() == "ARC": + circle = QadCircle() + circle.set(object2.center, object2.radius) + return QadIntersections.circleWithArc(circle, object1) + elif object2.whatIs() == "ELLIPSE": + return QadIntersections.ellipseWithArc(object2, object1) + elif object2.whatIs() == "ELLIPSE_ARC": + ellipse = QadEllipse() + ellipse.set(object2.center, object2.majorAxisFinalPt, object2.axisRatio) + return QadIntersections.ellipseWithArc(ellipse, object1) + + elif object1.whatIs() == "ELLIPSE": + return QadIntersections.twoBasicGeomObjectExtensions(object1, object2) + + elif object1.whatIs() == "ELLIPSE_ARC": + if object2.whatIs() == "LINE": + return QadIntersections.infinityLineWithEllipseArc(object2, object1) + elif object2.whatIs() == "CIRCLE": + return QadIntersections.circleWithEllipseArc(object2, object1) + elif object2.whatIs() == "ARC": + circle = QadCircle() + circle.set(object2.center, object2.radius) + return QadIntersections.circleWithEllipseArc(circle, object1) + elif object2.whatIs() == "ELLIPSE": + return QadIntersections.ellipseWithEllipseArc(object2, object1) + elif object2.whatIs() == "ELLIPSE_ARC": + ellipse = QadEllipse() + ellipse.set(object2.center, object2.majorAxisFinalPt, object2.axisRatio) + return QadIntersections.ellipseWithEllipseArc(ellipse, object1) + + return [] + + + # ============================================================================ + # twoGeomObjects + # ============================================================================ + @staticmethod + def twoGeomObjects(object1, object2, object2GeomBoundingBoxCache = None): + """ + la funzione calcola i punti di intersezione tra 2 oggetti geometrici + """ + geomType1 = object1.whatIs() + result = [] + + if object2GeomBoundingBoxCache is None: + object2GeomBoundingBoxCache = QadGeomBoundingBoxCache(object2) + + if geomType1 == "MULTI_POINT": + for geomAt in range(0, object1.qty()): + pt = object1.getPointAt(geomAt) + result.extend(QadIntersections.twoGeomObjects(pt, object2, object2GeomBoundingBoxCache)) + + elif geomType1 == "MULTI_LINEAR_OBJ": + for geomAt in range(0, object1.qty()): + linearObj = object1.getLinearObjectAt(geomAt) + result.extend(QadIntersections.twoGeomObjects(linearObj, object2, object2GeomBoundingBoxCache)) + + elif geomType1 == "POLYLINE": + for geomAt in range(0, object1.qty()): + linearObj = object1.getLinearObjectAt(geomAt) + result.extend(QadIntersections.twoGeomObjects(linearObj, object2, object2GeomBoundingBoxCache)) + + elif geomType1 == "POLYGON": + for geomAt in range(0, object1.qty()): + closedObj = object1.getClosedObjectAt(geomAt) + result.extend(QadIntersections.twoGeomObjects(closedObj, object2, object2GeomBoundingBoxCache)) + + elif geomType1 == "MULTI_POLYGON": + for geomAt in range(0, object1.qty()): + polygon = object1.getPolygonAt(geomAt) + result.extend(QadIntersections.twoGeomObjects(polygon, object2, object2GeomBoundingBoxCache)) + + # oggetto 1 è una geometria base + elif object1.whatIs() == "POINT" or object1.whatIs() == "LINE" or object1.whatIs() == "CIRCLE" or \ + object1.whatIs() == "ARC" or object1.whatIs() == "ELLIPSE" or object1.whatIs() == "ELLIPSE_ARC": + geomType2 = object2.whatIs() + + if object2GeomBoundingBoxCache is not None and object2GeomBoundingBoxCache.cacheLayer is not None: + # leggo solo le parti che si intersecano con il bounding box di object1 + boundingBox = object1.getBoundingBox() + geomSubgeomPartAtList = object2GeomBoundingBoxCache.getIntersectionWithBoundingBox(boundingBox) + for geomSubgeomPartAt in geomSubgeomPartAtList: + part = getQadGeomPartAt(object2, geomSubgeomPartAt[0], geomSubgeomPartAt[1], geomSubgeomPartAt[2]) + result.extend(QadIntersections.twoBasicGeomObjects(object1, part)) + + elif geomType2 == "MULTI_POINT": + for geomAt in range(0, object2.qty()): + result.extend(QadIntersections.twoBasicGeomObjects(object1, object2.getPointAt(geomAt))) + + elif geomType2 == "MULTI_LINEAR_OBJ": + for geomAt in range(0, object2.qty()): + result.extend(QadIntersections.twoGeomObjects(object1, object2.getLinearObjectAt(geomAt))) + + elif geomType2 == "POLYLINE": + for geomAt in range(0, object2.qty()): + result.extend(QadIntersections.twoGeomObjects(object1, object2.getLinearObjectAt(geomAt))) + + elif geomType2 == "POLYGON": + for geomAt in range(0, object2.qty()): + result.extend(QadIntersections.twoGeomObjects(object1, object2.getClosedObjectAt(geomAt))) + + elif geomType2 == "MULTI_POLYGON": + for geomAt in range(0, object2.qty()): + result.extend(QadIntersections.twoGeomObjects(object1, object2.getPolygonAt(geomAt))) + + # oggetto 1 è una geometria base + elif object2.whatIs() == "POINT" or object2.whatIs() == "LINE" or object2.whatIs() == "CIRCLE" or \ + object2.whatIs() == "ARC" or object2.whatIs() == "ELLIPSE" or object2.whatIs() == "ELLIPSE_ARC": + result = QadIntersections.twoBasicGeomObjects(object1, object2) + + return result + + + # ============================================================================ + # twoGeomObjectsExtensions + # ============================================================================ + @staticmethod + def twoGeomObjectsExtensions(object1, object2): + """ + la funzione calcola i punti di intersezione tra tra le estensioni di 2 oggetti geometrici: + linea (diventa linea infinita), arco (diventa cerchio), arco di ellisse (diventa ellisse). + """ + geomType1 = object1.whatIs() + result = [] + + if geomType1 == "MULTI_POINT": + for geomAt in range(0, object1.qty()): + pt = object1.getPointAt(geomAt) + result.extend(QadIntersections.twoGeomObjectsExtensions(pt, object2)) + + elif geomType1 == "MULTI_LINEAR_OBJ": + for geomAt in range(0, object1.qty()): + linearObj = object1.getLinearObjectAt(geomAt) + result.extend(QadIntersections.twoGeomObjectsExtensions(linearObj, object2)) + + elif geomType1 == "POLYLINE": + if object1.qty() > 0: # prima parte della polilinea + linearObj = object1.getLinearObjectAt(0) + pts = QadIntersections.twoGeomObjectsExtensions(linearObj, object2) + if linearObj.whatIs() == "LINE": + reversedLine = linearObj.copy() + appendPtOnTheSameTanDirectionOnly(reversedLine.reverse(), pts, result) + else: + result.extend(pts) + + for geomAt in range(1, object1.qty()-1): + result.extend(QadIntersections.geomObjectWithGeomObjectExtensions(object1.getLinearObjectAt(geomAt), object2)) + + if object1.qty() > 1: # ultima parte della polilinea + linearObj = object1.getLinearObjectAt(-1) + pts = QadIntersections.twoGeomObjectsExtensions(linearObj, object2) + if linearObj.whatIs() == "LINE": + appendPtOnTheSameTanDirectionOnly(linearObj, pts, result) + else: + result.extend(pts) + + elif geomType1 == "POLYGON": + for subGeomAt in range(0, object1.qty()): + closedObj = object1.getClosedObjectAt(geomAt) + result.extend(QadIntersections.twoGeomObjectsExtensions(closedObj, object2)) + + elif geomType1 == "MULTI_POLYGON": + for geomAt in range(0, object1.qty()): + polygon = object1.getPolygonAt(geomAt) + result.extend(QadIntersections.twoGeomObjectsExtensions(polygon, object2)) + + # oggetto 1 è una geometria base + elif object1.whatIs() == "POINT" or object1.whatIs() == "LINE" or object1.whatIs() == "CIRCLE" or \ + object1.whatIs() == "ARC" or object1.whatIs() == "ELLIPSE" or object1.whatIs() == "ELLIPSE_ARC": + geomType2 = object2.whatIs() + + if geomType2 == "MULTI_POINT": + for geomAt in range(0, object2.qty()): + result.extend(QadIntersections.twoBasicGeomObjectExtensions(object1, object2.getPointAt(geomAt))) + + elif geomType2 == "MULTI_LINEAR_OBJ": + for geomAt in range(0, object2.qty()): + result.extend(QadIntersections.twoGeomObjectsExtensions(object1, object2.getLinearObjectAt(geomAt))) + + elif geomType2 == "POLYLINE": + if object2.qty() > 0: # prima parte della polilinea + linearObj = object2.getLinearObjectAt(0) + pts = QadIntersections.twoGeomObjectsExtensions(object1, linearObj) + if linearObj.whatIs() == "LINE": + reversedLine = linearObj.copy() + appendPtOnTheSameTanDirectionOnly(reversedLine.reverse(), pts, result) + else: + result.extend(pts) + + for geomAt in range(1, object2.qty()-1): + result.extend(QadIntersections.geomObjectWithGeomObjectExtensions(object2.getLinearObjectAt(geomAt), object1)) + + if object2.qty() > 1: # ultima parte della polilinea + linearObj = object2.getLinearObjectAt(-1) + pts = QadIntersections.twoGeomObjectsExtensions(object1, linearObj) + if linearObj.whatIs() == "LINE": + appendPtOnTheSameTanDirectionOnly(linearObj, pts, result) + else: + result.extend(pts) + + elif geomType2 == "POLYGON": + for geomAt in range(0, object2.qty()): + result.extend(QadIntersections.twoGeomObjectsExtensions(object1, object2.getClosedObjectAt(geomAt))) + + elif geomType2 == "MULTI_POLYGON": + for geomAt in range(0, object2.qty()): + result.extend(QadIntersections.twoGeomObjectsExtensions(object1, object2.getPolygonAt(geomAt))) + + # oggetto 2 è una geometria base + elif object2.whatIs() == "POINT" or object2.whatIs() == "LINE" or object2.whatIs() == "CIRCLE" or \ + object2.whatIs() == "ARC" or object2.whatIs() == "ELLIPSE" or object2.whatIs() == "ELLIPSE_ARC": + result = QadIntersections.twoBasicGeomObjectExtensions(object1, object2) + + return result + + + # ============================================================================ + # geomObjectWithGeomObjectExtensions + # ============================================================================ + @staticmethod + def geomObjectWithGeomObjectExtensions(object1, object2): + """ + la funzione calcola i punti di intersezione tra un oggetto geometrico (object1) e + le estensioni di un oggetto geometrico (object2) + """ + geomType1 = object1.whatIs() + result = [] + + if geomType1 == "MULTI_POINT": + for geomAt in range(0, object1.qty()): + pt = object1.getPointAt(geomAt) + result.extend(QadIntersections.geomObjectWithGeomObjectExtensions(pt, object2)) + + elif geomType1 == "MULTI_LINEAR_OBJ": + for geomAt in range(0, object1.qty()): + linearObj = object1.getLinearObjectAt(geomAt) + result.extend(QadIntersections.geomObjectWithGeomObjectExtensions(linearObj, object2)) + + elif geomType1 == "POLYLINE": + if object1.qty() > 0: # prima parte della polilinea + linearObj = object1.getLinearObjectAt(0) + pts = QadIntersections.geomObjectWithGeomObjectExtensions(linearObj, object2) + if linearObj.whatIs() == "LINE": + reversedLine = linearObj.copy() + appendPtOnTheSameTanDirectionOnly(reversedLine.reverse(), pts, result) + else: + result.extend(pts) + + for geomAt in range(1, object1.qty()-1): + result.extend(QadIntersections.twoGeomObjects(object1.getLinearObjectAt(geomAt), object2)) + + if object1.qty() > 1: # ultima parte della polilinea + linearObj = object1.getLinearObjectAt(-1) + pts = QadIntersections.geomObjectWithGeomObjectExtensions(linearObj, object2) + if linearObj.whatIs() == "LINE": + appendPtOnTheSameTanDirectionOnly(linearObj, pts, result) + else: + result.extend(pts) + + elif geomType1 == "POLYGON": + for subGeomAt in range(0, object1.qty()): + closedObj = object1.getClosedObjectAt(geomAt) + result.extend(QadIntersections.geomObjectWithGeomObjectExtensions(closedObj, object2)) + + elif geomType1 == "MULTI_POLYGON": + for geomAt in range(0, object1.qty()): + polygon = object1.getPolygonAt(geomAt) + result.extend(QadIntersections.geomObjectWithGeomObjectExtensions(polygon, object2)) + + # oggetto 1 è una geometria base + elif object1.whatIs() == "POINT" or object1.whatIs() == "LINE" or object1.whatIs() == "CIRCLE" or \ + object1.whatIs() == "ARC" or object1.whatIs() == "ELLIPSE" or object1.whatIs() == "ELLIPSE_ARC": + geomType2 = object2.whatIs() + + if geomType2 == "MULTI_POINT": + for geomAt in range(0, object2.qty()): + result.extend(QadIntersections.geomObjectWithGeomObjectExtensions(object1, object2.getPointAt(geomAt))) + + elif geomType2 == "MULTI_LINEAR_OBJ": + for geomAt in range(0, object2.qty()): + result.extend(QadIntersections.geomObjectWithGeomObjectExtensions(object1, object2.getLinearObjectAt(geomAt))) + + elif geomType2 == "POLYLINE": + if object2.qty() > 0: # prima parte della polilinea + linearObj = object2.getLinearObjectAt(0) + pts = QadIntersections.geomObjectWithGeomObjectExtensions(object1, linearObj) + if linearObj.whatIs() == "LINE": + reversedLine = linearObj.copy() + appendPtOnTheSameTanDirectionOnly(reversedLine.reverse(), pts, result) + else: + result.extend(pts) + + for geomAt in range(1, object2.qty()-1): + result.extend(QadIntersections.twoGeomObjects(object1, object2.getLinearObjectAt(geomAt))) + + if object2.qty() > 1: # ultima parte della polilinea + linearObj = object2.getLinearObjectAt(-1) + pts = QadIntersections.geomObjectWithGeomObjectExtensions(object1, linearObj) + if linearObj.whatIs() == "LINE": + appendPtOnTheSameTanDirectionOnly(linearObj, pts, result) + else: + result.extend(pts) + + elif geomType2 == "POLYGON": + for geomAt in range(0, object2.qty()): + result.extend(QadIntersections.geomObjectWithGeomObjectExtensions(object1, object2.getClosedObjectAt(geomAt))) + + elif geomType2 == "MULTI_POLYGON": + for geomAt in range(0, object2.qty()): + result.extend(QadIntersections.geomObjectWithGeomObjectExtensions(object1, object2.getPolygonAt(geomAt))) + + # oggetto 2 è una geometria base + elif object2.whatIs() == "POINT" or object2.whatIs() == "LINE" or object2.whatIs() == "CIRCLE" or \ + object2.whatIs() == "ARC" or object2.whatIs() == "ELLIPSE" or object2.whatIs() == "ELLIPSE_ARC": + result = QadIntersections.basicGeomObjectWithBasicGeomObjectExtensions(object1, object2) + + return result + + + # =============================================================================== + # getOrderedPolylineIntersectionPtsWithBasicGeom + # =============================================================================== + @staticmethod + def getOrderedPolylineIntersectionPtsWithBasicGeom(polyline, linearObject, orderByStartPtOfLinearObject = False): + """ + La funzione restituisce diverse liste: + - la prima é una lista di punti di intersezione tra la parte e la polilinea. + La lista è ordinata per distanza dal punto iniziale di se = True + altrimenti è ordinata per distanza dal punto iniziale della polilinea + - la seconda é una lista che contiene, rispettivamente per ogni punto di intersezione, + il numero della parte (0-based) della polilinea in cui si trova quel punto. + - la terza é una lista che contiene, rispettivamente per ogni punto di intersezione, + la distanza dal punto iniziale di se = True o + dal punto iniziale della polilinea se = False + """ + gType = linearObject.whatIs() + if polyline.whatIs() != "POLYLINE" or \ + (gType != "LINE" and gType != "ARC" and gType != "ELLIPSE_ARC"): + return [], [], [] + + intPtSortedList = [] # lista di ((punto, distanza dall'inizio di linearObject) ...) + partNumber = -1 + if orderByStartPtOfLinearObject == False: + distFromStartPrevParts = 0 + + # per ogni parte della lista + i = 0 + while i < polyline.qty(): + linearObject2 = polyline.getLinearObjectAt(i) + partNumber = partNumber + 1 + partialIntPtList = QadIntersections.twoBasicGeomObjects(linearObject, linearObject2) + + for partialIntPt in partialIntPtList: + # escludo i punti che sono già in intPtSortedList + found = False + for intPt in intPtSortedList: + if qad_utils.ptNear(intPt[0], partialIntPt): + found = True + break + + if found == False: + if orderByStartPtOfLinearObject: + # inserisco il punto ordinato per distanza dall'inizio di linearObject + distFromStart = linearObject.getDistanceFromStart(partialIntPt) + else: + distFromStart = distFromStartPrevParts + linearObject2.getDistanceFromStart(partialIntPt) + + insertAt = 0 + for intPt in intPtSortedList: + if intPt[1] < distFromStart: + insertAt = insertAt + 1 + else: + break + intPtSortedList.insert(insertAt, [partialIntPt, distFromStart, partNumber]) + + if orderByStartPtOfLinearObject == False: + distFromStartPrevParts = distFromStartPrevParts + linearObject2.length() + i = i + 1 + + resultIntPt = [] + resultPartNumber = [] + resultDistanceFromStart = [] + for intPt in intPtSortedList: + resultIntPt.append(intPt[0]) + resultPartNumber.append(intPt[2]) + resultDistanceFromStart.append(intPt[1]) + + return resultIntPt, resultPartNumber, resultDistanceFromStart + + + # =============================================================================== + # getOrderedPolylineIntersectionPtsWithPolyline + # =============================================================================== + @staticmethod + def getOrderedPolylineIntersectionPtsWithPolyline(polyline1, polyline2): + """ + la funzione restituisce diverse liste: + - la prima é una lista di punti di intersezione tra le 2 polilinee + ordinata per distanza dal punto iniziale di . + - la seconda é una lista che contiene, rispettivamente per ogni punto di intersezione, + il numero della parte della (0-based) in cui si trova quel punto. + - la terza é una lista che contiene, rispettivamente per ogni punto di intersezione, + la distanza dal punto iniziale della polilinea2. + """ + if polyline1.whatIs() != "POLYLINE" or polyline2.whatIs() != "POLYLINE": + return [], [], [] + + resultIntPt = [] + resultPartNumber = [] + resultDistanceFromStart = [] + + # per ogni parte della lista + i = 0 + while i < polyline1.qty(): + linearObject1 = polyline1.getLinearObjectAt(i) + # lista di punti di intersezione ordinata per distanza dal punto iniziale di + partialResult = QadIntersections.getOrderedPolylineIntersectionPtsWithBasicGeom(polyline2, linearObject1, orderByStartPtOfLinearObject = True) + resultIntPt.extend(partialResult[0]) + resultPartNumber.extend(partialResult[2]) + resultDistanceFromStart.extend(partialResult[1]) + i = i + 1 + + return resultIntPt, resultPartNumber, resultDistanceFromStart + + +# =============================================================================== +# QadPerpendicularity class +# rappresenta una classe che calcola la perpendicolarità tra oggetti di base: punto, linea, arco, arco di ellisse, cerchio, ellisse +# =============================================================================== +class QadPerpendicularity(): + + def __init__(self): + pass + + + # =============================================================================== + # metodi per le linee infinite - inizio + # =============================================================================== + + # =============================================================================== + # fromPointToInfinityLine + # =============================================================================== + @staticmethod + def fromPointToInfinityLine(pt, line): + """ + la funzione ritorna la proiezione perpendicolare di punto su una linea infinita + """ + return qad_utils.getPerpendicularPointOnInfinityLine(line.pt1, line.pt2, pt) + + + # =============================================================================== + # metodi per le linee infinite - fine + # metodi per i segmenti - inizio + # =============================================================================== + + + # =============================================================================== + # fromPointToLine + # =============================================================================== + @staticmethod + def fromPointToLine(pt, line): + """ + la funzione ritorna la proiezione perpendicolare di punto su un segmento + """ + perpPt = QadPerpendicularity.fromPointToInfinityLine(pt, line) + if line.containsPt(perpPt): + return perpPt + return None + + + # =============================================================================== + # getInfinityLinePerpOnMiddle + # =============================================================================== + @staticmethod + def getInfinityLinePerpOnMiddleLine(line): + """ + la funzione trova una linea perpendicolare e passante per il punto medio della linea. + """ + ptMiddle = line.getMiddlePt() + dist = qad_utils.getDistance(line.pt1, ptMiddle) + if dist == 0: + return None + angle = qad_utils.getAngleBy2Pts(line.pt1, line.pt2) + math.pi / 2 + pt2Middle = qad_utils.getPolarPointByPtAngle(ptMiddle, angle, dist) + line = QadLine() + line.set(ptMiddle, pt2Middle) + return line + + + # =============================================================================== + # metodi per i segmenti - fine + # metodi per i cerchi - inizio + # =============================================================================== + + + # =============================================================================== + # fromPointToCircle + # =============================================================================== + @staticmethod + def fromPointToCircle(pt, circle): + """ + la funzione ritorna le proiezioni perpendicolari di punto su un cerchio + """ + angle = qad_utils.getAngleBy2Pts(circle.center, pt) + pt1 = qad_utils.getPolarPointByPtAngle(circle.center, angle, circle.radius) + pt2 = qad_utils.getPolarPointByPtAngle(circle.center, angle + math.pi, circle.radius) + return [pt1, pt2] + + + # =============================================================================== + # metodi per i cerchi - fine + # metodi per gli archi - inizio + # =============================================================================== + + + # ============================================================================ + # fromPointToArc + # ============================================================================ + @staticmethod + def fromPointToArc(pt, arc): + """ + la funzione ritorna la proiezione perpendicolare di punto su un arco + """ + result = [] + circle = QadCircle() + circle.set(arc.center, arc.radius) + perpPtList = QadPerpendicularity.fromPointToCircle(pt, circle) + for perpPt in perpPtList: + if arc.isPtOnArcOnlyByAngle(perpPt): + result.append(perpPt) + return result + + + # =============================================================================== + # metodi per gli archi - fine + # metodi per le ellissi - inizio + # =============================================================================== + + + # ============================================================================ + # fromPointToEllipse + # ============================================================================ + @staticmethod + def fromPointToEllipse(pt, ellipse): + """ + la funzione ritorna la proiezione perpendicolare di punto su un'ellisse (fino a 4 punti) + """ + # https://www.mathpages.com/home/kmath505/kmath505.htm (per punti esterni all'ellise) + # https://math.stackexchange.com/questions/609351/number-of-normals-from-a-point-to-an-ellipse (per punti interni all'ellisse) + result = [] + + # ritorna -1 se il punto è interno, 0 se è sull'ellisse, 1 se è esterno + whereIsPt = ellipse.whereIsPt(pt) + if whereIsPt == 0: # pt è sull'ellisse + result.append(QgsPointXY(pt.x(), pt.y())) + return result + + # traslo e ruoto il punto per confrontarlo con l'ellisse con centro in 0,0 e con rotazione = 0 + myPoint = ellipse.translateAndRotatePtForNormalEllipse(pt, False) + + a = qad_utils.getDistance(ellipse.center, ellipse.majorAxisFinalPt) # semiasse maggiore + b = a * ellipse.axisRatio # semiasse minore + e = QadEllipse(ellipse) + e.center.set(0.0, 0.0) + e.majorAxisFinalPt.set(a, 0.0) + + a2 = a * a # a al quadrato + b2 = b * b # b al quadrato + xp = myPoint.x() # x del punto + xp2 = xp * xp # xp al quadrato + yp = myPoint.y() + yp2 = yp * yp # yp al quadrato + a2_b2 = a2 - b2 + + c4 = a2_b2 * a2_b2 + c3 = -2 * a2 * xp * a2_b2 + c2 = a2 * (a2 * xp2 + b2 * yp2 - (a2_b2 * a2_b2)) + c1 = 2 * a2 * a2 * xp * a2_b2 + c0 = -1 * (a2 * a2 * a2) * xp2 + + x_result = np.roots([c4, c3, c2, c1, c0]) + for x in x_result: + n = (1.0 - x * x / a2) * b2 # data la X calcolo la Y + if qad_utils.doubleNear(n, 0): n = 0 # per problemi di precisione di calcolo (es. se x = 10 , n = -1.11022302463e-14 !) + if n >= 0: + y = math.sqrt(n) + p = QgsPointXY(x, y) + # verifico se il punto va bene + # calcolo la tangente su quel punto + t = e.getTanDirectionOnPt(p) + # se è perpendicolare con il segmento che unisce il punto trovato con quello fornito (myPoint) + angSegment = qad_utils.normalizeAngle(qad_utils.getAngleBy2Pts(p, myPoint) + math.pi / 2) + if qad_utils.doubleNear(t, angSegment) or qad_utils.doubleNear(qad_utils.normalizeAngle(t + math.pi), angSegment): + # traslo e ruoto il punto per riportarlo nella posizione originale (con il centro e la rotazione dell'ellisse originale) + p = ellipse.translateAndRotatePtForNormalEllipse(p, True) + qad_utils.appendUniquePointToList(result, p) + + # verifico l'altra coordinata y + p = QgsPointXY(x, -y) + # verifico se il punto va bene + # calcolo la tangente su quel punto + t = e.getTanDirectionOnPt(p) + # se è perpendicolare con il segmento che unisce il punto trovato con quello fornito (myPoint) + angSegment = qad_utils.normalizeAngle(qad_utils.getAngleBy2Pts(p, myPoint) + math.pi / 2) + if qad_utils.doubleNear(t, angSegment) or qad_utils.doubleNear(qad_utils.normalizeAngle(t + math.pi), angSegment): + # traslo e ruoto il punto per riportarlo nella posizione originale (con il centro e la rotazione dell'ellisse originale) + p = ellipse.translateAndRotatePtForNormalEllipse(p, True) + qad_utils.appendUniquePointToList(result, p) + + return result + + + # =============================================================================== + # metodi per le ellissi - fine + # metodi per gli archi di ellisse - inizio + # =============================================================================== + + + # ============================================================================ + # fromPointToEllipseArc + # ============================================================================ + @staticmethod + def fromPointToEllipseArc(pt, ellipseArc): + """ + la funzione ritorna la proiezione perpendicolare di punto su un arco di ellisse + """ + result = [] + ellipse = QadEllipse() + ellipse.set(ellipseArc.center, ellipseArc.majorAxisFinalPt, ellipseArc.axisRatio) + perpPtList = QadPerpendicularity.fromPointToEllipse(pt, ellipse) + for perpPt in perpPtList: + if ellipseArc.isPtOnEllipseArcOnlyByAngle(perpPt): + result.append(perpPt) + return result + + + # ============================================================================ + # fromPointToBasicGeomObject + # ============================================================================ + @staticmethod + def fromPointToBasicGeomObject(pt, object): + """ + la funzione ritorna la proiezione perpendicolare di punto su un oggetto geometrico di base: + linea, arco, arco di ellisse, cerchio, ellisse. + """ + if object.whatIs() == "LINE": + res = QadPerpendicularity.fromPointToLine(pt, object) + return [] if res is None else [res] + elif object.whatIs() == "CIRCLE": + return QadPerpendicularity.fromPointToCircle(pt, object) + elif object.whatIs() == "ARC": + return QadPerpendicularity.fromPointToArc(pt, object) + elif object.whatIs() == "ELLIPSE": + return QadPerpendicularity.fromPointToEllipse(pt, object) + elif object.whatIs() == "ELLIPSE_ARC": + return QadPerpendicularity.fromPointToEllipseArc(pt, object) + + return [] + + + # ============================================================================ + # fromPointToBasicGeomObjectExtensions + # ============================================================================ + @staticmethod + def fromPointToBasicGeomObjectExtensions(pt, object): + """ + la funzione ritorna le proiezioni perpendicolari di punto su una estensione di un oggetto geometrico di base: + linea (diventa linea infinita), arco (diventa cerchio), arco di ellisse (diventa ellisse), cerchio, ellisse. + """ + if object.whatIs() == "LINE": + res = QadPerpendicularity.fromPointToInfinityLine(pt, object) + return [] if res is None else [res] + elif object.whatIs() == "CIRCLE": + return QadPerpendicularity.fromPointToCircle(pt, object) + elif object.whatIs() == "ARC": + circle = QadCircle() + circle.set(object.center, object.radius) + return QadPerpendicularity.fromPointToCircle(pt, circle) + elif object.whatIs() == "ELLIPSE": + return QadPerpendicularity.fromPointToEllipse(pt, object) + elif object.whatIs() == "ELLIPSE_ARC": + ellipse = QadEllipse() + ellipse.set(object.center, object.majorAxisFinalPt, object.axisRatio) + return QadPerpendicularity.fromPointToEllipse(pt, ellipse) + + return [] + + + # ============================================================================ + # fromPointToGeomObject + # ============================================================================ + @staticmethod + def fromPointToGeomObject(pt, object): + """ + la funzione ritorna la proiezione perpendicolare di punto su un oggetto geometrico + """ + geomType = object.whatIs() + result = [] + + if geomType == "MULTI_LINEAR_OBJ": + for geomAt in range(0, object.qty()): + linearObj = object.getLinearObjectAt(geomAt) + result.extend(QadPerpendicularity.fromPointToBasicGeomObject(pt, linearObj)) + + elif geomType == "POLYLINE": + for geomAt in range(0, object.qty()): + linearObj = object.getLinearObjectAt(geomAt) + result.extend(QadPerpendicularity.fromPointToBasicGeomObject(pt, linearObj)) + + elif geomType == "POLYGON": + for subGeomAt in range(0, object.qty()): + closedObj = object.getClosedObjectAt(geomAt) + result.extend(QadPerpendicularity.fromPointToGeomObject(pt, closedObj)) + + elif geomType == "MULTI_POLYGON": + for geomAt in range(0, object.qty()): + polygon = object.getPolygonAt(geomAt) + result.extend(QadPerpendicularity.fromPointToGeomObject(pt, polygon)) + + # oggetto è una geometria base + else: + result.extend(QadPerpendicularity.fromPointToBasicGeomObject(pt, object)) + + return result + + +# =============================================================================== +# QadMinDistance class +# rappresenta una classe che calcola la minima distanza tra oggetti di base: punto, linea, arco, arco di ellisse, cerchio, ellisse +# =============================================================================== +class QadMinDistance(): + + def __init__(self): + pass + + + # =============================================================================== + # metodi per le linee infinite - inizio + # =============================================================================== + + + # =============================================================================== + # fromInfinityLineToPoint + # =============================================================================== + @staticmethod + def fromInfinityLineToPoint(infinityLine, pt): + """ + la funzione ritorna la distanza minima e il punto di distanza minima tra una linea infinita ed un punto + () + """ + if infinityLine.isPtOnInfinityLine(pt) == True: + return [0, pt] + perpPt = QadPerpendicularity.fromPointToInfinityLine(pt, infinityLine) + return [qad_utils.getDistance(perpPt, pt), perpPt] + + + # =============================================================================== + # fromInfinityLineToLine + # =============================================================================== + @staticmethod + def fromInfinityLineToLine(infinityLine, line): + """ + la funzione ritorna la distanza minima e i punti di distanza minima tra una linea infinita ed un segmento + () + """ + intPt = QadIntersections.infinityLineWithLine(infinityLine, line) + if intPt is not None: + return [0, intPt, intPt] + + # ritorna una lista: () + dist, ptLine = QadMinDistance.fromInfinityLineToPoint(infinityLine, line.pt1) + bestResult = [dist, ptLine, line.pt1] + + dist, ptLine = QadMinDistance.fromInfinityLineToPoint(infinityLine, line.pt2) + if bestResult[0] > dist: + bestResult = [dist, ptLine, line.pt2] + + return bestResult[0], bestResult[1], bestResult[2] + + + # =============================================================================== + # fromInfinityLineToCircle + # =============================================================================== + @staticmethod + def fromInfinityLineToCircle(infinityLine, circle): + """ + la funzione ritorna la distanza minima e i punti di distanza minima tra una linea infinita ed un cerchio + () + """ + intPts = QadIntersections.infinityLineWithCircle(infinityLine, circle) + if len(intPts) > 0: + return [0, intPts[0], intPts[0]] + + perpPt = QadPerpendicularity.fromPointToInfinityLine(circle.center, infinityLine) + angle = qad_utils.getAngleBy2Pts(circle.center, perpPt) + ptOnCircle = qad_utils.getPolarPointByPtAngle(circle.center, angle, circle.radius) + + return [qad_utils.getDistance(perpPt, ptOnCircle), perpPt, ptOnCircle] + + + # =============================================================================== + # fromInfinityLineToArc + # =============================================================================== + @staticmethod + def fromInfinityLineToArc(infinityLine, arc): + """ + la funzione ritorna la distanza minima e i punti di distanza minima tra una linea infinita ed un arco + () + """ + circle = QadCircle() + circle.set(arc.center, arc.radius) + result = QadMinDistance.fromInfinityLineToCircle(infinityLine, circle) + ptArc = result[2] + if arc.isPtOnArcOnlyByAngle(ptArc): + return result + + d1 = qad_utils.getDistance(arc.gtStartPt(), ptOnCircle) + res1 = QadMinDistance.fromInfinityLineToPoint(infinityLine, arc.getStartPt()) + res2 = QadMinDistance.fromInfinityLineToPoint(infinityLine, arc.getEndPt()) + if res1[0] < res2[0]: + return [res1[0], res1[1], arc.getStartPt()] + else: + return [res2[0], res2[1], arc.getEndPt()] + + + @staticmethod + def distanza_infinityLine_ellipse(params, px, py, dx, dy, a, b, theta, h, k): + t, phi = params + # Parametri della retta: punto sulla retta (px, py) e vettore direzione (dx, dy) + # Parametri dell'ellisse: semiassi a, b, angolo di rotazione theta, centro (h, k) + # px, py, a, b, theta, h, k = line_ellipse + # Punto sulla retta + x_retta = px + t * dx + y_retta = py + t * dy + # Punto sull'ellisse (parametrizzato da phi) + x_ellisse = h + a * np.cos(phi) * np.cos(theta) - b * np.sin(phi) * np.sin(theta) + y_ellisse = k + a * np.cos(phi) * np.sin(theta) + b * np.sin(phi) * np.cos(theta) + # Distanza quadratica + distanza2 = (x_retta - x_ellisse)**2 + (y_retta - y_ellisse)**2 + return distanza2 + + + # =============================================================================== + # fromInfinityLineToEllipse chatgpt + # =============================================================================== + @staticmethod + def fromInfinityLineToEllipse(line, ellipse): + """ + la funzione ritorna la distanza minima e i punti di distanza minima tra una linea infinita ed un'ellisse + () + """ + if NO_SCIPY == True: return [] + + # test +# line = QadLine() +# line.set(QgsPointXY(1,1), QgsPointXY(2, 1)) +# +# ellipse = QadEllipse() +# center = QgsPointXY(2, 1) +# angle = np.pi / 6 # 30 gradi +# a = 5 +# b = 3 +# majorAxisFinalPt = qad_utils.getPolarPointByPtAngle(center, angle, a) +# ellipse.set(center, majorAxisFinalPt, b / a) + + + + # Parametri della retta: punto sulla retta (px, py) e vettore direzione (dx, dy) + px, py = line.getStartPt().x(), line.getStartPt().y() + dx, dy = line.getEndPt().x() - px, line.getEndPt().y() - py + + # Calcola la lunghezza dell'asse maggiore (a) + a = qad_utils.getDistance(ellipse.center, ellipse.majorAxisFinalPt) + # Calcola la lunghezza dell'asse minore (b) + b = a * ellipse.axisRatio + # theta: l'angolo di rotazione dell'ellisse. + theta = ellipse.getRotation() + # h e k: le traslazioni dell'ellisse rispetto all'origine, coordinate centro + h = ellipse.center.x() + k = ellipse.center.y() + + args = (px, py, dx, dy, a, b, theta, h, k) + + # Stima iniziale per t e phi + stima_iniziale = [0, 0] + + # Minimizzazione della funzione di distanza quadratica + risultato = minimize(QadMinDistance.distanza_infinityLine_ellipse, stima_iniziale, args, method='Nelder-Mead') + + # Estrazione dei risultati + t_min, phi_min = risultato.x + + # Calcolo dei punti di minima distanza + x_retta_min = px + t_min * dx + y_retta_min = py + t_min * dy + x_ellisse_min = h + a * np.cos(phi_min) * np.cos(theta) - b * np.sin(phi_min) * np.sin(theta) + y_ellisse_min = k + a * np.cos(phi_min) * np.sin(theta) + b * np.sin(phi_min) * np.cos(theta) + + ptRetta = QgsPointXY(x_retta_min, y_retta_min) + ptEllisse = QgsPointXY(x_ellisse_min, y_ellisse_min) + + return qad_utils.getDistance(ptRetta, ptEllisse), ptRetta, ptEllisse + + + + # =============================================================================== + # fromInfinityLineToEllipseArc + # =============================================================================== + @staticmethod + def fromInfinityLineToEllipseArc(line, ellipseArc): + """ + la funzione ritorna la distanza minima e i punti di distanza minima tra una linea infinita ed un arco di ellisse + () + """ + ellipse = QadEllipse() + ellipse.set(ellipseArc.center, ellipseArc.majorAxisFinalPt, ellipseArc.axisRatio) + result = QadMinDistance.fromInfinityLineToEllipse(infinityLine, ellipse) + ptArc = result[2] + if ellipseArc.isPtOnEllipseArcOnlyByAngle(ptArc): + return result + + d1 = qad_utils.getDistance(arc.gtStartPt(), ptOnCircle) + res1 = QadMinDistance.fromInfinityLineToPoint(infinityLine, ellipseArc.getStartPt()) + res2 = QadMinDistance.fromInfinityLineToPoint(infinityLine, ellipseArc.getEndPt()) + if res1[0] < res2[0]: + return [res1[0], res1[1], ellipseArc.getStartPt()] + else: + return [res2[0], res2[1], ellipseArc.getEndPt()] + + + # =============================================================================== + # metodi per le linee infinite - fine + # metodi per i segmenti - inizio + # =============================================================================== + + + # =============================================================================== + # fromLineToPoint + # =============================================================================== + @staticmethod + def fromLineToPoint(line, pt): + """ + la funzione ritorna la distanza minima e i punti di distanza minima tra un segmento ed un punto + () + """ + if line.containsPt(pt) == True: + return [0, pt] + perpPt = QadPerpendicularity.fromPointToInfinityLine(pt, line) + if perpPt is not None: + if line.containsPt(perpPt) == True: + return [qad_utils.getDistance(perpPt, pt), perpPt] + + distFromP1 = qad_utils.getDistance(line.pt1, pt) + distFromP2 = qad_utils.getDistance(line.pt2, pt) + if distFromP1 < distFromP2: + return [distFromP1, line.pt1] + else: + return [distFromP2, line.pt2] + + + # =============================================================================== + # fromTwoLines + # =============================================================================== + @staticmethod + def fromTwoLines(line1, line2): + """ + la funzione ritorna la distanza minima e i punti di distanza minima tra 2 segmenti + () + """ + intPt = QadIntersections.twoLines(line1, line2) + if intPt is not None: + return [0, intPt, intPt] + + # ritorna una lista: () + result = QadMinDistance.fromLineToPoint(line2, line1.pt1) + dist = result[0] + ptLine = result[1] + bestResult = [dist, line1.pt1, ptLine] + + result = QadMinDistance.fromLineToPoint(line2, line1.pt2) + dist = result[0] + ptLine = result[1] + if bestResult[0] > dist: + bestResult = [dist, line1.pt2, ptLine] + + result = QadMinDistance.fromLineToPoint(line1, line2.pt1) + dist = result[0] + ptLine = result[1] + if bestResult[0] > dist: + bestResult = [dist, ptLine, line2.pt1] + + result = QadMinDistance.fromLineToPoint(line1, line2.pt2) + dist = result[0] + ptLine = result[1] + if bestResult[0] > dist: + bestResult = [dist, ptLine, line2.pt2] + + return [bestResult[0], bestResult[1], bestResult[2]] + + + # =============================================================================== + # fromLineToCircle + # =============================================================================== + @staticmethod + def fromLineToCircle(line, circle): + """ + la funzione ritorna la distanza minima e i punti di distanza minima tra un segmento ed un cerchio + () + """ + intPts = QadIntersections.lineWithCircle(line, circle) + if len(intPts) > 0: + return [0, intPts[0], intPts[0]] + + d1 = qad_utils.getDistance(line.getStartPt(), circle.center) + if d1 < circle.radius: # linea interna al cerchio + d2 = qad_utils.getDistance(line.getEndPt(), circle.center) + if d1 > d2: + ptLine = line.getStartPt() + else: + ptLine = line.getEndPt() + else: # linea esterna al cerchio + result = QadMinDistance.fromInfinityLineToCircle(line, circle) + if line.containsPt(result[1]): + return result + else: + d2 = qad_utils.getDistance(line.getEndPt(), circle.center) + if d1 < d2: + ptLine = line.getStartPt() + else: + ptLine = line.getEndPt() + + angle = qad_utils.getAngleBy2Pts(circle.center, ptLine) + ptOnCircle = qad_utils.getPolarPointByPtAngle(circle.center, angle, circle.radius) + + return [qad_utils.getDistance(ptLine, ptOnCircle), ptLine, ptOnCircle] + + + # =============================================================================== + # fromLineToArc + # =============================================================================== + @staticmethod + def fromLineToArc(line, arc): + """ + la funzione ritorna la distanza minima e i punti di distanza minima tra un segmento ed un arco + () + """ + intPtList = QadIntersections.lineWithArc(line, arc) + if len(intPtList) > 0: + return [0, intPtList[0], intPtList[0]] + + p1Line = line.getStartPt() + p2Line = line.getEndPt() + resultP1 = QadMinDistance.fromArcToPoint(arc, p1Line) # ritorna () + resultP2 = QadMinDistance.fromArcToPoint(arc, p2Line) # ritorna () + + # se il segmento é interno al cerchio orginato dall'estensione dell'arco + if qad_utils.getDistance(p1Line, arc.center) < arc.radius and \ + qad_utils.getDistance(p2Line, arc.center) < arc.radius: + if resultP1[0] < resultP2[0]: # se il punto iniziale della linea è più vicino all'arco + return [resultP1[0], p1Line, resultP1[1]] + else: + return [resultP2[0], p2Line, resultP2[1]] + + else: # se il segmento é esterno al cerchio orginato dall'estensione dell'arco + perpPt = QadPerpendicularity.fromPointToLine(arc.center, line) + if perpPt is not None: + angle = qad_utils.getAngleBy2Pts(arc.center, perpPt) + # se il punto di perpendicolare al segmento é compreso tra gli angoli dell'arco + if arc.isAngleBetweenAngles(angle): + ptOnArc = qad_utils.getPolarPointByPtAngle(arc.center, angle, arc.radius) + return [qad_utils.getDistance(perpPt, ptOnArc), perpPt, ptOnArc] + + bestResult = resultP1 # () + bestResult.insert(1, p1Line) # () + resultP2.insert(1, p2Line) + if bestResult[0] > resultP2[0]: + bestResult = resultP2 + + ptStart = arc.getStartPt() + ptEnd = arc.getEndPt() + + resultStartPt = QadMinDistance.fromLineToPoint(line, ptStart) # ( + resultStartPt.append(ptStart) # + if bestResult[0] > resultStartPt[0]: + bestResult = resultStartPt + + resultEndPt = QadMinDistance.fromLineToPoint(line, ptEnd) # ( + resultEndPt.append(ptEnd) # + if bestResult[0] > resultEndPt[0]: + bestResult = resultEndPt + + return bestResult # () + + + @staticmethod + def distanza_line_ellipse(params, line, ellipse): + t, theta = params + x_segment = (1 - t) * line.getStartPt().x() + t * line.getEndPt().x() + y_segment = (1 - t) * line.getStartPt().y() + t * line.getEndPt().y() + + pt_ellipse = ellipse.getPointAtAngle(theta) + x_ellipse = pt_ellipse.x() + y_ellipse = pt_ellipse.y() + + return np.sqrt((x_segment - x_ellipse) ** 2 + (y_segment - y_ellipse) ** 2) + + + # =============================================================================== + # fromLineToEllipse chatgpt + # =============================================================================== + @staticmethod + def fromLineToEllipse(line, ellipse): + """ + la funzione ritorna la distanza minima e i punti di distanza minima tra un segmento ed un'ellisse + () + """ + if NO_SCIPY == True: return [] + + initial_guess = [0.5, 0] + bounds = [(0, 1), (0, 2 * np.pi)] + + args = (line, ellipse) + + result = minimize(QadMinDistance.distanza_line_ellipse, initial_guess, args, bounds=bounds) + + if result.success: + t_opt, theta_opt = result.x + x_segment_opt = (1 - t_opt) * line.getStartPt().x() + t_opt * line.getEndPt().x() + y_segment_opt = (1 - t_opt) * line.getStartPt().y() + t_opt * line.getEndPt().y() + + pt_ellipse = ellipse.getPointAtAngle(theta_opt) + x_ellipse_opt = pt_ellipse.x() + y_ellipse_opt = pt_ellipse.y() + + min_distance = result.fun + + return [min_distance, QgsPointXY(x_segment_opt, y_segment_opt), QgsPointXY(x_ellipse_opt, y_ellipse_opt)] + else: + raise [] + + + # =============================================================================== + # fromLineToEllipseArc + # =============================================================================== + @staticmethod + def fromLineToEllipseArc(line, ellipseArc): + """ + la funzione ritorna la distanza minima e i punti di distanza minima tra un segmento ed un arco di ellisse + () + """ + + pass # da fare + + + # =============================================================================== + # metodi per i segmenti - fine + # metodi per i cerchi - inizio + # =============================================================================== + + + # =============================================================================== + # fromCircleToPoint + # =============================================================================== + @staticmethod + def fromCircleToPoint(circle, pt): + """ + la funzione ritorna la distanza minima e i punti di distanza minima tra un cerchio ed un punto + () + """ + angle = qad_utils.getAngleBy2Pts(circle.center, pt) + ptOnCircle = qad_utils.getPolarPointByPtAngle(circle.center, angle, circle.radius) + + return [qad_utils.getDistance(pt, ptOnCircle), ptOnCircle] + + + # =============================================================================== + # twoCircles + # =============================================================================== + @staticmethod + def fromTwoCircles(circle1, circle2): + """ + la funzione ritorna la distanza minima e i punti di distanza minima tra 2 cerchi + () + """ + intersections = QadIntersections.twoCircles(circle1, circle2) + if len(intersections) > 0: + return [0.0, intersections[0], intersections[0]] + + angle = qad_utils.getAngleBy2Pts(circle1.center, circle2.center) + pt1Circle1 = qad_utils.getPolarPointByPtAngle(circle1.center, angle, circle1.radius) + pt2Circle1 = qad_utils.getPolarPointByPtAngle(circle1.center, angle, -circle1.radius) + pt1Circle2 = qad_utils.getPolarPointByPtAngle(circle2.center, angle, circle2.radius) + pt2Circle2 = qad_utils.getPolarPointByPtAngle(circle2.center, angle, -circle2.radius) + + minDistance = qad_utils.getDistance(pt1Circle1, pt1Circle2) + ptMinDistanceCircle1 = pt1Circle1 + ptMinDistanceCircle2 = pt1Circle2 + + dist = qad_utils.getDistance(pt1Circle1, pt2Circle2) + if dist < minDistance: + minDistance = dist + ptMinDistanceCircle1 = pt1Circle1 + ptMinDistanceCircle2 = pt2Circle2 + + dist = qad_utils.getDistance(pt2Circle1, pt1Circle2) + if dist < minDistance: + minDistance = dist + ptMinDistanceCircle1 = pt2Circle1 + ptMinDistanceCircle2 = pt1Circle2 + + dist = qad_utils.getDistance(pt2Circle1, pt2Circle2) + if dist < minDistance: + minDistance = dist + ptMinDistanceCircle1 = pt2Circle1 + ptMinDistanceCircle2 = pt2Circle2 + + return [minDistance, ptMinDistanceCircle1, ptMinDistanceCircle2] + + + # =============================================================================== + # fromCircleToArc + # =============================================================================== + @staticmethod + def fromCircleToArc(circle, arc): + """ + la funzione ritorna la distanza minima e i punti di distanza minima tra un cerchio ed un arco + () + """ + intersections = QadIntersections.circleWithArc(circle, arc) + if len(intersections) > 0: + return [0.0, intersections[0], intersections[0]] + + pt1Arc = arc.getStartPt() + pt2Arc = arc.getEndPt() + + angle = qad_utils.getAngleBy2Pts(circle.center, pt1Arc) + ptCircle = qad_utils.getPolarPointByPtAngle(circle.center, angle, circle.radius) + minDistance = qad_utils.getDistance(ptCircle, pt1Arc) + ptMinDistanceCircle = ptCircle + ptMinDistanceArc = pt1Arc + + angle = qad_utils.getAngleBy2Pts(circle.center, pt2Arc) + ptCircle = qad_utils.getPolarPointByPtAngle(circle.center, angle, circle.radius) + dist = qad_utils.getDistance(ptCircle, pt2Arc) + if dist < minDistance: + minDistance = dist + ptMinDistanceCircle = ptCircle + ptMinDistanceArc = pt2Arc + + line = QadLine() + line.set(circle.center, arc.center) + intersections = QadIntersections.infinityLineWithArc(line, arc) + if len(intersections) > 0: + angle = qad_utils.getAngleBy2Pts(circle.center, arc.center) + ptCircle = qad_utils.getPolarPointByPtAngle(circle.center, angle, circle.radius) + dist = qad_utils.getDistance(ptCircle, intersections[0]) + if dist < minDistance: + minDistance = dist + ptMinDistanceCircle = ptCircle + ptMinDistanceArc = intersections[0] + + return [minDistance, ptMinDistanceCircle, ptMinDistanceArc] + + + # =============================================================================== + # fromCircleToEllipse + # =============================================================================== + @staticmethod + def fromCircleToEllipse(circle, ellipse): + """ + la funzione ritorna la distanza minima e i punti di distanza minima tra un cerchio ed un'ellisse + () + """ + pass # da fare + + + # =============================================================================== + # fromCircleToEllipseArc + # =============================================================================== + @staticmethod + def fromCircleToEllipseArc(circle, ellipseArc): + """ + la funzione ritorna la distanza minima e i punti di distanza minima tra un cerchio ed un arco di ellisse + () + """ + pass # da fare + + + # =============================================================================== + # metodi per i cerchi - fine + # metodi per gli archi - inizio + # =============================================================================== + + + # =============================================================================== + # fromArcToPoint + # =============================================================================== + @staticmethod + def fromArcToPoint(arc, pt): + """ + la funzione ritorna la distanza minima e i punti di distanza minima tra un arco ed un punto + () + """ + if arc.isPtOnArcOnlyByAngle(pt): + circle = QadCircle() + circle.set(arc.center, arc.radius) + return QadMinDistance.fromCircleToPoint(circle, pt) + else: + p1 = arc.getStartPt() + p2 = arc.getEndPt() + d1 = qad_utils.getDistance(p1, pt) + d2 = qad_utils.getDistance(p2, pt) + if d1 < d2: + return [d1, p1] + else: + return [d2, p2] + + angle = qad_utils.getAngleBy2Pts(circle.center, pt) + ptOnCircle = qad_utils.getPolarPointByPtAngle(circle.center, angle, circle.radius) + + return [qad_utils.getDistance(pt, ptOnCircle), ptOnCircle] + + + # =============================================================================== + # fromTwoArcs + # =============================================================================== + @staticmethod + def fromTwoArcs(arc1, arc2): + """ + la funzione ritorna la distanza minima e i punti di distanza minima tra 2 archi + () + """ + intPtList = QadIntersections.twoArcs(arc1, arc2) + if len(intPtList) > 0: + return [0, intPtList[0], intPtList[0]] + + StartPtArc1 = arc1.getStartPt() + EndPtArc1 = arc1.getEndPt() + StartPtArc2 = arc2.getStartPt() + EndPtArc2 = arc2.getEndPt() + + # calcolo la minima distanza tra gli estremi di un arco e l'altro arco e + # scelgo la migliore tra le quattro distanze + # ritorna una lista: () + dummy = QadMinDistance.fromArcToPoint(arc2, StartPtArc1) + bestResult = [dummy[0], StartPtArc1, dummy[1]] + + dummy = QadMinDistance.fromArcToPoint(arc2, EndPtArc1) + resultArc2_EndPtArc1 = [dummy[0], EndPtArc1, dummy[1]] + if bestResult[0] > resultArc2_EndPtArc1[0]: + bestResult = resultArc2_EndPtArc1 + + dummy = QadMinDistance.fromArcToPoint(arc1, StartPtArc2) + resultArc1_StartPtArc2 = [dummy[0], dummy[1], StartPtArc2] + if bestResult[0] > resultArc1_StartPtArc2[0]: + bestResult = resultArc1_StartPtArc2 + + dummy = QadMinDistance.fromArcToPoint(arc1, EndPtArc2) + resultArc1_EndPtArc2 = [dummy[0], dummy[1], EndPtArc2] + if bestResult[0] > resultArc1_EndPtArc2[0]: + bestResult = resultArc1_EndPtArc2 + + # il cerchio1 e il cerchio 2 sono derivati rispettivamente dall'estensione dell'arco1 e arco2. + circle1 = QadCircle() + circle1.set(arc1.center, arc1.radius) + circle2 = QadCircle() + circle2.set(arc2.center, arc2.radius) + distanceBetweenCenters = qad_utils.getDistance(circle1.center, circle2.center) + + # considero i seguenti 2 casi: + # i cerchi sono esterni + if distanceBetweenCenters - circle1.radius - circle2.radius > 0: + # creo un segmento che unisce i due centri e lo interseco con l'arco 1 + l = QadLine() + l.set(arc1.center, arc2.center) + intPtListArc1 = QadIntersections.lineWithArc(l, arc1) + if len(intPtListArc1) > 0: + intPtArc1 = intPtListArc1[0] + + # creo un segmento che unisce i due centri e lo interseco con l'arco 2 + intPtListArc2 = QadIntersections.lineWithArc(l, arc2) + if len(intPtListArc2) > 0: + intPtArc2 = intPtListArc2[0] + + distanceIntPts = qad_utils.getDistance(intPtArc1, intPtArc2) + if bestResult[0] > distanceIntPts: + bestResult = [distanceIntPts, intPtArc1, intPtArc2] + # il cerchio1 é interno al cerchio2 oppure + # il cerchio2 é interno al cerchio1 + elif distanceBetweenCenters + circle1.radius < circle2.radius or \ + distanceBetweenCenters + circle2.radius < circle1.radius: + # creo un segmento che unisce i due centri e lo interseco con l'arco 2 + l = QadLine() + l.set(arc1.center, arc2.center) + intPtListArc2 = QadIntersections.infinityLineWithArc(l, arc2) + if len(intPtListArc2) > 0: + # creo un segmento che unisce i due centri e lo interseco con l'arco 1 + intPtListArc1 = QadIntersections.infinityLineWithArc(l, arc1) + + for intPtArc2 in intPtListArc2: + for intPtArc1 in intPtListArc1: + distanceIntPts = qad_utils.getDistance(intPtArc2, intPtArc1) + if bestResult[0] > distanceIntPts: + bestResult = [distanceIntPts, intPtArc1, intPtArc2] + + return bestResult + + + # =============================================================================== + # fromArcToEllipseArc + # =============================================================================== + @staticmethod + def fromArcToEllipseArc(arc, ellipseArc): + """ + la funzione ritorna la distanza minima e i punti di distanza minima tra un arco ed un arco di ellisse + () + """ + pass # da fare + + + # =============================================================================== + # metodi per gli archi - fine + # metodi per le ellissi - inizio + # =============================================================================== + + + # =============================================================================== + # fromEllipseToPoint + # =============================================================================== + @staticmethod + def fromEllipseToPoint(ellipse, pt): + """ + la funzione ritorna la distanza minima e i punti di distanza minima tra un'ellisse ed un punto + () + """ + perpPts = QadPerpendicularity.fromPointToEllipse(pt, ellipse) + dist = sys.float_info.max + for perpPt in perpPts: + d = qad_utils.getDistance(pt, perpPt) + if d < dist: + dist = d + bestPt = perpPt + + return [dist, bestPt] + + + # =============================================================================== + # fromTwoEllipses + # =============================================================================== + @staticmethod + def fromTwoEllipses(ellipse1, ellipse2): + """ + la funzione ritorna la distanza minima e i punti di distanza minima tra 2 ellissi + () + """ + pass # da fare + + + # =============================================================================== + # fromEllipseToArc + # =============================================================================== + @staticmethod + def fromEllipseToArc(ellipse, arc): + """ + la funzione ritorna la distanza minima e i punti di distanza minima tra un'ellisse ed un arco + () + """ + pass # da fare + + + # =============================================================================== + # fromEllipseToEllipseArc + # =============================================================================== + @staticmethod + def fromEllipseToEllipseArc(ellipse, ellipseArc): + """ + la funzione ritorna la distanza minima e i punti di distanza minima tra un'ellisse ed un arco di ellisse + () + """ + pass # da fare + + + # =============================================================================== + # metodi per le ellissi - fine + # metodi per gli archi di ellisse - inizio + # =============================================================================== + + + # =============================================================================== + # fromEllipseArcToPoint + # =============================================================================== + @staticmethod + def fromEllipseArcToPoint(ellipseArc, pt): + """ + la funzione ritorna la distanza minima e i punti di distanza minima tra un arco di ellisse ed un punto + () + """ + perpPts = QadPerpendicularity.fromPointToEllipseArc(pt, ellipseArc) + dist = sys.float_info.max + for perpPt in perpPts: + d = qad_utils.getDistance(pt, perpPt) + if d < dist: + dist = d + bestPt = perpPt + + if dist < sys.float_info.max: return [dist, bestPt] + + d1 = qad_utils.getDistance(pt, ellipseArc.getStartPt()) + d2 = qad_utils.getDistance(pt, ellipseArc.getEndPt()) + + if d1 < d2: + return [d1, ellipseArc.getStartPt()] + else: + return [d2, ellipseArc.getEndPt()] + + + # =============================================================================== + # fromTwoEllipseArcs + # =============================================================================== + @staticmethod + def fromTwoEllipseArcs(ellipseArc1, ellipseArc2): + """ + la funzione ritorna la distanza minima e i punti di distanza minima tra 2 archi di ellisse + () + """ + pass # da fare + + + # ============================================================================ + # fromPointToBasicGeomObject + # ============================================================================ + @staticmethod + def fromPointToBasicGeomObject(pt, object): + """ + la funzione ritorna la distanza minima e il punto di distanza minima tra un oggetto geometrico di base ed un punto + () + """ + if object.whatIs() == "POINT": + return (object.distance(pt), object) + elif object.whatIs() == "LINE": + return QadMinDistance.fromLineToPoint(object, pt) + elif object.whatIs() == "CIRCLE": + return QadMinDistance.fromCircleToPoint(object, pt) + elif object.whatIs() == "ARC": + return QadMinDistance.fromArcToPoint(object, pt) + elif object.whatIs() == "ELLIPSE": + return QadMinDistance.fromEllipseToPoint(object, pt) + elif object.whatIs() == "ELLIPSE_ARC": + return QadMinDistance.fromEllipseArcToPoint(object, pt) + + return [] + + + # ============================================================================ + # fromPointToBasicGeomObjectExtensions + # ============================================================================ + @staticmethod + def fromPointToBasicGeomObjectExtensions(pt, object): + """ + la funzione ritorna la distanza minima e il punto di distanza minima tra una estensione di oggetto geometrico di base ed un punto + linea (diventa linea infinita), arco (diventa cerchio), arco di ellisse (diventa ellisse), cerchio, ellisse. + """ + if object.whatIs() == "LINE": + return QadMinDistance.fromInfinityLineToPoint(object, pt) + elif object.whatIs() == "CIRCLE": + return QadMinDistance.fromCircleToPoint(object, pt) + elif object.whatIs() == "ARC": + circle = QadCircle() + circle.set(object.center, object.radius) + return QadMinDistance.fromCircleToPoint(circle, pt) + elif object.whatIs() == "ELLIPSE": + return QadMinDistance.fromEllipseToPoint(object, pt) + elif object.whatIs() == "ELLIPSE_ARC": + ellipse = QadEllipse() + ellipse.set(object.center, object.majorAxisFinalPt, object.axisRatio) + return QadMinDistance.fromEllipseToPoint(object, pt) + + return [] + + + # ============================================================================ + # fromTwoBasicGeomObjects + # ============================================================================ + @staticmethod + def fromTwoBasicGeomObjects(object1, object2): + """ + la funzione ritorna + + + + dei 2 oggetti geometrici di base: linea, arco, arco di ellisse, cerchio, ellisse. + """ + if object1.whatIs() == "LINE": + if object2.whatIs() == "LINE": + # + return QadMinDistance.fromTwoLines(object1, object2) + elif object2.whatIs() == "CIRCLE": + return QadMinDistance.fromLineToCircle(object1, object2) + elif object2.whatIs() == "ARC": + return QadMinDistance.fromLineToArc(object1, object2) + elif object2.whatIs() == "ELLIPSE": + return QadMinDistance.fromLineToEllipse(object1, object2) + elif object2.whatIs() == "ELLIPSE_ARC": + return QadMinDistance.fromLineToEllipseArc(object1, object2) + + elif object1.whatIs() == "CIRCLE": + if object2.whatIs() == "LINE": + return QadMinDistance.fromLineToCircle(object2, object1) + elif object2.whatIs() == "CIRCLE": + return QadMinDistance.fromTwoCircles(object1, object2) + elif object2.whatIs() == "ARC": + return QadMinDistance.fromCircleToArc(object1, object2) + elif object2.whatIs() == "ELLIPSE": + return QadMinDistance.fromCircleToEllipse(object1, object2) + elif object2.whatIs() == "ELLIPSE_ARC": + return QadMinDistance.fromCircleToEllipseArc(object1, object2) + + elif object1.whatIs() == "ARC": + if object2.whatIs() == "LINE": + return QadMinDistance.fromLineToArc(object2, object1) + elif object2.whatIs() == "CIRCLE": + return QadMinDistance.fromCircleToArc(object2, object1) + elif object2.whatIs() == "ARC": + return QadMinDistance.fromTwoArcs(object1, object2) + elif object2.whatIs() == "ELLIPSE": + return QadMinDistance.fromEllipseToArc(object2, object1) + elif object2.whatIs() == "ELLIPSE_ARC": + return QadMinDistance.fromArcToEllipseArc(object1, object2) + + elif object1.whatIs() == "ELLIPSE": + if object2.whatIs() == "LINE": + return QadMinDistance.fromLineToEllipse(object2, object1) + elif object2.whatIs() == "CIRCLE": + return QadMinDistance.fromCircleToEllipse(object2, object1) + elif object2.whatIs() == "ARC": + return QadMinDistance.fromEllipseToArc(object1, object2) + elif object2.whatIs() == "ELLIPSE": + return QadMinDistance.fromTwoEllipses(object1, object2) + elif object2.whatIs() == "ELLIPSE_ARC": + return QadMinDistance.fromEllipseToEllipseArc(object1, object2) + + elif object1.whatIs() == "ELLIPSE_ARC": + if object2.whatIs() == "LINE": + return QadMinDistance.fromLineToEllipseArc(object2, object1) + elif object2.whatIs() == "CIRCLE": + return QadMinDistance.fromCircleToEllipseArc(object2, object1) + elif object2.whatIs() == "ARC": + return QadMinDistance.fromArcToEllipseArc(object2, object1) + elif object2.whatIs() == "ELLIPSE": + return QadMinDistance.fromEllipseToEllipseArc(object2, object1) + elif object2.whatIs() == "ELLIPSE_ARC": + return QadMinDistance.fromTwoEllipseArcs(object1, object2) + + return [] + + + # ============================================================================ + # fromTwoGeomObjects + # ============================================================================ + @staticmethod + def fromTwoGeomObjects(object1, object2): + """ + la funzione ritorna + + + + + + + + + + dei 2 oggetti geometrici. + """ + minDistancePts = None + geomType1 = object1.whatIs() + + if geomType1 == "MULTI_LINEAR_OBJ": + for geomAt in range(0, object1.qty()): + partialMinDistancePts = QadMinDistance.fromTwoGeomObjects(object.getLinearObjectAt(geomAt), object2) + if minDistancePts is None: + minDistancePts = partialMinDistancePts + minDistancePts[2] = geomAt + elif partialMinDistancePts[0] < minDistancePts[0]: + minDistancePts = partialMinDistancePts + minDistancePts[2] = geomAt + + elif geomType1 == "POLYLINE": + for partAt in range(0, object1.qty()): + partialMinDistancePts = QadMinDistance.fromTwoGeomObjects(object.getLinearObjectAt(partAt), object2) + if minDistancePts is None: + minDistancePts = partialMinDistancePts + minDistancePts[4] = partAt + elif partialMinDistancePts[0] < minDistancePts[0]: + minDistancePts = partialMinDistancePts + minDistancePts[4] = partAt + + elif geomType1 == "POLYGON": + for subGeomAt in range(0, object1.qty()): + partialMinDistancePts = QadMinDistance.fromTwoGeomObjects(object.getClosedObjectAt(geomAt), object2) + if minDistancePts is None: + minDistancePts = partialMinDistancePts + minDistancePts[3] = subGeomAt + elif partialMinDistancePts[0] < minDistancePts[0]: + minDistancePts = partialMinDistancePts + minDistancePts[3] = subGeomAt + + elif geomType1 == "MULTI_POLYGON": + for geomAt in range(0, object1.qty()): + partialMinDistancePts = QadMinDistance.fromTwoGeomObjects(object.getPolygonAt(geomAt), object2) + if minDistancePts is None: + minDistancePts = partialMinDistancePts + minDistancePts[2] = geomAt + elif partialMinDistancePts[0] < minDistancePts[0]: + minDistancePts = partialMinDistancePts + minDistancePts[2] = geomAt + + elif geomType1 == "MULTI_POINT": + for geomAt in range(0, object1.qty()): + partialMinDistancePts = QadMinDistance.fromTwoGeomObjects(object.getPointAt(geomAt), object2) + if minDistancePts is None: + minDistancePts = partialMinDistancePts + minDistancePts[2] = geomAt + elif partialMinDistancePts[0] < minDistancePts[0]: + minDistancePts = partialMinDistancePts + minDistancePts[2] = geomAt + + # oggetto1 è una geometria base + else: + geomType2 = object2.whatIs() + + if geomType2 == "MULTI_LINEAR_OBJ": + for geomAt in range(0, object2.qty()): + partialMinDistancePts = QadMinDistance.fromTwoGeomObjects(object1, object2.getLinearObjectAt(geomAt)) + if minDistancePts is None: + minDistancePts = partialMinDistancePts + minDistancePts[6] = geomAt + elif partialMinDistancePts[0] < minDistancePts[0]: + minDistancePts = partialMinDistancePts + minDistancePts[6] = geomAt + + elif geomType2 == "POLYLINE": + for partAt in range(0, object2.qty()): + partialMinDistancePts = QadMinDistance.fromTwoGeomObjects(object1, object2.getLinearObjectAt(partAt)) + if minDistancePts is None: + minDistancePts = partialMinDistancePts + minDistancePts[8] = partAt + elif partialMinDistancePts[0] < minDistancePts[0]: + minDistancePts = partialMinDistancePts + minDistancePts[8] = partAt + + elif geomType2 == "POLYGON": + for subGeomAt in range(0, object2.qty()): + partialMinDistancePts = QadMinDistance.fromTwoGeomObjects(object1, object2.getClosedObjectAt(geomAt)) + if minDistancePts is None: + minDistancePts = partialMinDistancePts + minDistancePts[7] = subGeomAt + elif partialMinDistancePts[0] < minDistancePts[0]: + minDistancePts = partialMinDistancePts + minDistancePts[7] = subGeomAt + + elif geomType2 == "MULTI_POLYGON": + for geomAt in range(0, object2.qty()): + partialMinDistancePts = QadMinDistance.fromTwoGeomObjects(object1, object2.getPolygonAt(geomAt)) + if minDistancePts is None: + minDistancePts = partialMinDistancePts + minDistancePts[6] = geomAt + elif partialMinDistancePts[0] < minDistancePts[0]: + minDistancePts = partialMinDistancePts + minDistancePts[6] = geomAt + + elif geomType2 == "MULTI_POINT": + for geomAt in range(0, object2.qty()): + partialMinDistancePts = QadMinDistance.fromTwoGeomObjects(object1, object2.getPointAt(geomAt)) + if minDistancePts is None: + minDistancePts = partialMinDistancePts + minDistancePts[6] = geomAt + elif partialMinDistancePts[0] < minDistancePts[0]: + minDistancePts = partialMinDistancePts + minDistancePts[6] = geomAt + + # oggetto2 è una geometria base (e anche oggetto1 era una geometria base) + else: + dummy = QadMinDistance.fromTwoBasicGeomObjects(object1, object2) + minDistancePts = [dummy[0], dummy[1], 0, 0, 0, dummy[2], 0, 0, 0,] + + return minDistancePts + + + # ============================================================================ + # twoBasicGeomObjectExtensions + # ============================================================================ + @staticmethod + def fromTwoBasicGeomObjectExtensions(object1, object2): + """ + la funzione ritorna la distanza minima e i punti di distanza minima delle estensioni di 2 oggetti geometrici di base: + linea (diventa linea infinita), arco (diventa cerchio), arco di ellisse (diventa ellisse), cerchio, ellisse. + """ + if object1.whatIs() == "LINE": + if object2.whatIs() == "CIRCLE": + return QadMinDistance.fromInfinityLineToCircle(object1, object2) + elif object2.whatIs() == "ARC": + circle = QadCircle() + circle.set(object2.center, object2.radius) + return QadMinDistance.fromInfinityLineToArc(object1, object2) + elif object2.whatIs() == "ELLIPSE": + return QadMinDistance.fromInfinityLineToEllipse(object1, object2) + elif object2.whatIs() == "ELLIPSE_ARC": + return QadMinDistance.fromInfinityLineToEllipseArc(object1, object2) + + elif object1.whatIs() == "CIRCLE": + if object2.whatIs() == "LINE": + return QadMinDistance.fromInfinityLineToCircle(object2, object1) + elif object2.whatIs() == "CIRCLE": + return QadMinDistance.fromTwoCircles(object1, object2) + elif object2.whatIs() == "ARC": + circle = QadCircle() + circle.set(object2.center, object2.radius) + return QadMinDistance.fromCircleToArc(object1, circle) + elif object2.whatIs() == "ELLIPSE": + return QadMinDistance.fromCircleToEllipse(object1, object2) + elif object2.whatIs() == "ELLIPSE_ARC": + ellipse = QadEllipse() + ellipse.set(object2.center, object2.majorAxisFinalPt, object2.axisRatio) + return QadMinDistance.fromCircleToEllipse(object1, ellipse) + + elif object1.whatIs() == "ARC": + circle1 = QadCircle() + circle1.set(object1.center, object1.radius) + if object2.whatIs() == "LINE": + return QadMinDistance.fromInfinityLineToCircle(object2, circle1) + elif object2.whatIs() == "CIRCLE": + return QadMinDistance.fromTwoCircles(object2, circle1) + elif object2.whatIs() == "ARC": + circle2 = QadCircle() + circle2.set(object2.center, object2.radius) + return QadMinDistance.fromTwoCircles(circle1, circle2) + elif object2.whatIs() == "ELLIPSE": + return QadMinDistance.fromCircleToEllipse(circle1, object2) + elif object2.whatIs() == "ELLIPSE_ARC": + ellipse = QadEllipse() + ellipse.set(object2.center, object2.majorAxisFinalPt, object2.axisRatio) + return QadMinDistance.fromCircleToEllipse(circle1, ellipse) + + elif object1.whatIs() == "ELLIPSE": + if object2.whatIs() == "LINE": + return QadMinDistance.fromInfinityLineToEllipse(object2, object1) + elif object2.whatIs() == "CIRCLE": + return QadMinDistance.fromCircleToEllipse(object2, object1) + elif object2.whatIs() == "ARC": + circle = QadCircle() + circle.set(object2.center, object2.radius) + return QadMinDistance.fromCircleToEllipse(circle, object1) + elif object2.whatIs() == "ELLIPSE": + return QadMinDistance.fromTwoEllipses(object1, object2) + elif object2.whatIs() == "ELLIPSE_ARC": + ellipse = QadEllipse() + ellipse.set(object2.center, object2.majorAxisFinalPt, object2.axisRatio) + return QadMinDistance.fromEllipseToEllipseArc(object1, ellipse) + + elif object1.whatIs() == "ELLIPSE_ARC": + ellipse1 = QadEllipse() + ellipse1.set(object1.center, object1.majorAxisFinalPt, object1.axisRatio) + if object2.whatIs() == "LINE": + return QadMinDistance.fromInfinityLineToEllipse(object2, ellipse1) + elif object2.whatIs() == "CIRCLE": + return QadMinDistance.fromCircleToEllipse(object2, ellipse1) + elif object2.whatIs() == "ARC": + circle = QadCircle() + circle.set(object.center, object.radius) + return QadMinDistance.fromCircleToEllipse(circle, ellipse1) + elif object2.whatIs() == "ELLIPSE": + return QadMinDistance.fromTwoEllipses(ellipse1, object2) + elif object2.whatIs() == "ELLIPSE_ARC": + ellipse2 = QadEllipse() + ellipse2.set(object2.center, object2.majorAxisFinalPt, object2.axisRatio) + return QadMinDistance.fromTwoEllipseArcs(ellipse1, ellipse2) + + return [] + + +# =============================================================================== +# QadTangency class +# rappresenta una classe che calcola la tangenza tra oggetti di base: punto, linea, cerchio, arco, ellisse, arco di ellisse +# =============================================================================== +class QadTangency(): + + def __init__(self): + pass + + + # =============================================================================== + # metodi per i cerchi - inizio + # =============================================================================== + + + # =============================================================================== + # fromPointToCircle + # =============================================================================== + @staticmethod + def fromPointToCircle(point, circle): + """ + la funzione ritorna una lista punti di tangenza sul cerchio di linee passanti per un punto + """ + dist = circle.center.distance(point) + if dist < circle.radius: return [] + + angleOffSet = math.asin(circle.radius / dist) + angleOffSet = (math.pi / 2) - angleOffSet + angle = qad_utils.getAngleBy2Pts(circle.center, point) + + pt1 = qad_utils.getPolarPointByPtAngle(circle.center, angle + angleOffSet, circle.radius) + pt2 = qad_utils.getPolarPointByPtAngle(circle.center, angle - angleOffSet, circle.radius) + return [pt1, pt2] + + + # ============================================================================ + # twoCircles + # ============================================================================ + @staticmethod + def twoCircles(circle1, circle2): + """ + la funzione ritorna una lista di linee che sono le tangenti ai due cerchi + """ + x1 = circle1.center[0] + y1 = circle1.center[1] + r1 = circle1.radius + x2 = circle2.center[0] + y2 = circle2.center[1] + r2 = circle2.radius + + d_sq = (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) + if (d_sq <= (r1-r2)*(r1-r2)): + return [] + + d = math.sqrt(d_sq); + vx = (x2 - x1) / d; + vy = (y2 - y1) / d; + + tangents = [] + # Let A, B be the centers, and C, D be points at which the tangent + # touches first and second circle, and n be the normal vector to it. + # + # We have the system: + # n * n = 1 (n is a unit vector) + # C = A + r1 * n + # D = B +/- r2 * n + # n * CD = 0 (common orthogonality) + # + # n * CD = n * (AB +/- r2*n - r1*n) = AB*n - (r1 -/+ r2) = 0, <=> + # AB * n = (r1 -/+ r2), <=> + # v * n = (r1 -/+ r2) / d, where v = AB/|AB| = AB/d + # This is a linear equation in unknown vector n. + sign1 = +1 + while sign1 >= -1: + c = (r1 - sign1 * r2) / d; + + # Now we're just intersecting a line with a circle: v*n=c, n*n=1 + + if (c*c > 1.0): + sign1 = sign1 - 2 + continue + + h = math.sqrt(max(0.0, 1.0 - c*c)); + + sign2 = +1 + while sign2 >= -1: + nx = vx * c - sign2 * h * vy; + ny = vy * c + sign2 * h * vx; + + tangent = QadLine() + tangent.set(QgsPointXY(x1 + r1 * nx, y1 + r1 * ny), \ + QgsPointXY(x2 + sign1 * r2 * nx, y2 + sign1 * r2 * ny)) + tangents.append(tangent) + sign2 = sign2 - 2 + + sign1 = sign1 - 2 + + return tangents + + + # ============================================================================ + # fromCircleToArc + # ============================================================================ + @staticmethod + def fromCircleToArc(circle1, arc2): + """ + la funzione ritorna una lista di linee che sono le tangenti al cerchio e all'arco + """ + result = [] + circle2 = QadCircle() + circle2.set(arc2.center, arc2.radius) + lines = QadTangency.twoCircles(circle1, circle2) + for line in lines: + if arc2.isPtOnArcOnlyByAngle(line.getEndPt()): + result.append(line) + return result + + + # ============================================================================ + # fromCircleToEllipse + # ============================================================================ + @staticmethod + def fromCircleToEllipse(circle1, ellipse2): + """ + la funzione ritorna una lista di linee che sono le tangenti al cerchio e all'ellisse + """ + return [] + + + # ============================================================================ + # fromCircleToEllipseArc + # ============================================================================ + @staticmethod + def fromCircleToEllipseArc(circle1, ellipseArc2): + """ + la funzione ritorna una lista di linee che sono le tangenti al cerchio e all'arco di ellisse + """ + result = [] + ellipse2 = QadEllipse() + ellipse2.set(ellipseArc2.center, ellipseArc2.majorAxisFinalPt, ellipseArc2.axisRatio) + lines = QadTangency.fromCircleToEllipse(circle1, ellipse2) + for line in lines: + if ellipseArc2.isPtOnEllipseArcOnlyByAngle(line.getEndPt()): + result.append(line) + return result + + + # =============================================================================== + # metodi per i cerchi - fine + # metodi per gli archi - inizio + # =============================================================================== + + + # ============================================================================ + # fromPointToArc + # ============================================================================ + @staticmethod + def fromPointToArc(pt, arc): + """ + la funzione ritorna una lista punti di tangenza sull'arco di linee passanti per un punto + """ + result = [] + circle = QadCircle() + circle.set(arc.center, arc.radius) + tangPtList = QadTangency.fromPointToCircle(pt, circle) + for tangPt in tangPtList: + if arc.isPtOnArcOnlyByAngle(tangPt): + result.append(tangPt) + return result + + + # ============================================================================ + # twoArcs + # ============================================================================ + @staticmethod + def twoArcs(arc1, arc2): + """ + la funzione ritorna una lista di linee che sono le tangenti ai due archi + """ + result = [] + circle1 = QadCircle() + circle1.set(arc1.center, arc1.radius) + lines = QadTangency.fromCircleToArc(circle1, arc2) + for line in lines: + if arc1.isPtOnArcOnlyByAngle(line.getStartPt()): + result.append(line) + return result + + + # ============================================================================ + # fromArcToEllipse + # ============================================================================ + @staticmethod + def fromArcToEllipse(arc1, ellipse2): + """ + la funzione ritorna una lista di linee che sono le tangenti all'arco e all'ellisse + """ + result = [] + circle1 = QadCircle() + circle1.set(arc1.center, arc1.radius) + lines = QadTangency.fromCircleToEllipse(circle1, ellipse2) + for line in lines: + if arc1.isPtOnArcOnlyByAngle(line.getStartPt()): + result.append(tangPt) + return result + + + # ============================================================================ + # fromArcToEllipseArc + # ============================================================================ + @staticmethod + def fromArcToEllipseArc(arc1, ellipseArc2): + """ + la funzione ritorna una lista di linee che sono le tangenti all'arco e all'arco di ellisse + """ + result = [] + circle1 = QadCircle() + circle1.set(arc1.center, arc1.radius) + lines = QadTangency.fromCircleToEllipseArc(circle1, ellipseArc2) + for line in lines: + if arc1.isPtOnArcOnlyByAngle(line.getStartPt()): + result.append(line) + return result + + + # =============================================================================== + # metodi per gli archi - fine + # metodi per le ellissi - inizio + # =============================================================================== + + + # ============================================================================ + # fromPointToEllipse + # ============================================================================ + @staticmethod + def fromPointToEllipse(pt, ellipse): + """ + la funzione ritorna una lista punti di tangenza sull'ellisse di linee passanti per un punto + """ + # https://www3.ul.ie/~rynnet/swconics/TC.htm + # 1. With the radius set to the major axis, scribe an arc from F. + # 2. From P scribe an arc with radius set to F1 + # 3. Where this arc intersects the previous arc drawn, draw the lines back to the focal point F. + # 4. The points where these lines intersect the curve will be the points of contact of the tangents from point P. + + result = [] + line = QadLine() + # trovo i fuochi + foci = ellipse.getFocus() + if len(foci) == 0: return result + a = qad_utils.getDistance(ellipse.center, ellipse.majorAxisFinalPt) * 2 # asse maggiore + b = a * ellipse.axisRatio # asse minore + # punto 1 e 2 + focus = foci[0] # provo prima con il primo fuoco + circle1 = QadCircle() + circle1.set(focus, a) + circle2 = QadCircle() + circle2.set(point, qad_utils.getDistance(foci[1], point)) + intPts = QadIntersections.twoCircles(circle1, circle2) + if len(intPts) == 0: # se non hanno intersezioni provo l'altro fuoco + focus = foci[1] + circle1.set(focus, a) + circle2.set(point, qad_utils.getDistance(foci[0], point)) + intPts = QadIntersections.twoCircles(circle1, circle2) + if len(intPts) == 0: # se non hanno intersezioni ciao + return result + + if len(intPts) == 1: + line.set(focus, intPts[0]) + tgPt1 = QadIntersections.lineWithEllipse(line, ellipse) + if len(tgPt1) == 0: return result + result.append(tgPt1[0]) + else: + line.set(focus, intPts[0]) + tgPt1 = QadIntersections.lineWithEllipse(line, ellipse) + line.set(focus, intPts[1]) + tgPt2 = QadIntersections.lineWithEllipse(line, ellipse) + if len(tgPt1) == 0 or len(tgPt2) == 0: return result + result.append(tgPt1[0]) + result.append(tgPt2[0]) + + return result + + + # ============================================================================ + # twoEllipses + # ============================================================================ + @staticmethod + def twoEllipses(ellipse1, ellipse2): + """ + la funzione ritorna una lista di linee che sono le tangenti a due ellissi + """ + # da fare + return [] + + + # ============================================================================ + # fromEllipseToEllipseArc + # ============================================================================ + @staticmethod + def fromEllipseToEllipseArc(ellipse1, ellipseArc2): + """ + la funzione ritorna una lista di linee che sono le tangenti all'ellisse e all'arco di ellisse + """ + result = [] + ellipse2 = QadEllipse() + ellipse2.set(ellipseArc2.center, ellipseArc2.majorAxisFinalPt, ellipseArc2.axisRatio) + lines = QadTangency.twoEllipses(ellipse1, ellipse2) + for line in lines: + if ellipseArc2.isPtOnEllipseArcOnlyByAngle(line.getEndPt()): + result.append(line) + return result + + + # =============================================================================== + # metodi per le ellissi - fine + # metodi per gli archi di ellisse - inizio + # =============================================================================== + + + # ============================================================================ + # fromPointToEllipseArc + # ============================================================================ + @staticmethod + def fromPointToEllipseArc(pt, ellipseArc): + """ + la funzione ritorna una lista punti di tangenza sull'arco di ellisse di linee passanti per un punto + """ + result = [] + ellipse = QadEllipse() + ellipse.set(ellipseArc.center, ellipseArc.majorAxisFinalPt, ellipseArc.axisRatio) + tangPtList = QadTangency.fromPointToEllipse(pt, ellipse) + for tangPt in tangPtList: + if ellipseArc.isPtOnEllipseArcOnlyByAngle(tangPt): + result.append(tangPt) + return result + + + # ============================================================================ + # twoEllipseArcs + # ============================================================================ + @staticmethod + def twoEllipseArcs(ellipseArc1, ellipseArc2): + """ + la funzione ritorna una lista di linee che sono le tangenti a due archi di ellissi + """ + result = [] + ellipse1 = QadEllipse() + ellipse1.set(ellipseArc1.center, ellipseArc1.majorAxisFinalPt, ellipseArc1.axisRatio) + lines = QadTangency.fromEllipseToEllipseArc(ellipse1, ellipseArc2) + for line in lines: + if ellipseArc1.isPtOnEllipseArcOnlyByAngle(line.getStartPt()): + result.append(line) + return result + + + # =============================================================================== + # metodi per gli archi di ellisse - fine + # metodi per gli oggetti geometrici di base - inizio + # =============================================================================== + + + # ============================================================================ + # fromPointToBasicGeomObject + # ============================================================================ + @staticmethod + def fromPointToBasicGeomObject(pt, object): + """ + la funzione ritorna una lista punti di tangenza sull'oggetto passanti per un punto + la funzione ritorna i punti di tangenza su un oggetto geometrico di base: + linea, arco, arco di ellisse, cerchio, ellisse. + """ + if object.whatIs() == "CIRCLE": + return QadTangency.fromPointToCircle(pt, object) + elif object.whatIs() == "ARC": + return QadTangency.fromPointToArc(pt, object) + elif object.whatIs() == "ELLIPSE": + return QadTangency.fromPointToEllipse(pt, object) + elif object.whatIs() == "ELLIPSE_ARC": + return QadTangency.fromPointToEllipseArc(pt, object) + + return [] + + + # ============================================================================ + # fromPointToBasicGeomObjectExtensions + # ============================================================================ + @staticmethod + def fromPointToBasicGeomObjectExtensions(pt, object): + """ + la funzione ritorna una lista punti di tangenza su una estensione di un oggetto geometrico di base passanti per un punto + arco (diventa cerchio), arco di ellisse (diventa ellisse), cerchio, ellisse. + """ + if object.whatIs() == "CIRCLE": + return QadTangency.fromPointToCircle(pt, object) + elif object.whatIs() == "ARC": + circle = QadCircle() + circle.set(object.center, object.radius) + return QadTangency.fromPointToCircle(pt, circle) + elif object.whatIs() == "ELLIPSE": + return QadTangency.fromPointToEllipse(pt, object) + elif object.whatIs() == "ELLIPSE_ARC": + ellipse = QadEllipse() + ellipse.set(object.center, object.majorAxisFinalPt, object.axisRatio) + return QadTangency.fromPointToEllipse(pt, ellipse) + + return [] + + + # ============================================================================ + # fromPointToGeomObject + # ============================================================================ + @staticmethod + def fromPointToGeomObject(pt, object): + """ + la funzione ritorna una lista punti di tangenza sull'oggetto passanti per un punto + """ + geomType = object.whatIs() + result = [] + + if geomType == "MULTI_LINEAR_OBJ": + for geomAt in range(0, object.qty()): + linearObj = object.getLinearObjectAt(geomAt) + result.extend(QadTangency.fromPointToBasicGeomObject(pt, linearObj)) + + elif geomType == "POLYLINE": + for geomAt in range(0, object.qty()): + linearObj = object.getLinearObjectAt(geomAt) + result.extend(QadTangency.fromPointToBasicGeomObject(pt, linearObj)) + + elif geomType == "POLYGON": + for subGeomAt in range(0, object.qty()): + closedObj = object.getClosedObjectAt(geomAt) + result.extend(QadTangency.fromPointToGeomObject(pt, closedObj)) + + elif geomType == "MULTI_POLYGON": + for geomAt in range(0, object.qty()): + polygon = object.getPolygonAt(geomAt) + result.extend(QadTangency.fromPointToGeomObject(pt, polygon)) + + # oggetto è una geometria base + else: + result.extend(QadTangency.fromPointToBasicGeomObject(pt, object)) + + return result + + + # ============================================================================ + # bestTwoBasicGeomObjects + # ============================================================================ + @staticmethod + def bestTwoBasicGeomObjects(object1, tanPt1, object2, tanPt2): + """ + Trova la linea tangente a un oggetto geometrico di base e tangente ad un altro oggetto geometrico di base + (che ha i punti inziale/finale che rispettivamente sono più vicini ai punti tanPt1 e tanPt2): + arco, arco di ellisse, cerchio, ellisse. + tanPt1 = punto di selezione geometria 1 di tangenza + tanPt2 = punto di selezione geometria 2 di tangenza + """ + if object1.whatIs() == "CIRCLE": + if object2.whatIs() == "CIRCLE": + return getLineWithStartEndPtsClosestToPts(QadTangency.twoCircles(object1, object2), tanPt1, tanPt2) + elif object2.whatIs() == "ARC": + return getLineWithStartEndPtsClosestToPts(QadTangency.fromCircleToArc(object1, object2), tanPt1, tanPt2) + elif object2.whatIs() == "ELLIPSE": + return getLineWithStartEndPtsClosestToPts(QadTangency.fromCircleToEllipse(object1, object2), tanPt1, tanPt2) + elif object2.whatIs() == "ELLIPSE_ARC": + return getLineWithStartEndPtsClosestToPts(QadTangency.fromCircleToEllipseArc(object1, object2), tanPt1, tanPt2) + + elif object1.whatIs() == "ARC": + if object2.whatIs() == "CIRCLE": + lines = QadTangency.fromCircleToArc(object2, object1) + for line in lines: line.reverse() + return getLineWithStartEndPtsClosestToPts(lines, tanPt1, tanPt2) + elif object2.whatIs() == "ARC": + return getLineWithStartEndPtsClosestToPts(QadTangency.twoArcs(object1, object2), tanPt1, tanPt2) + elif object2.whatIs() == "ELLIPSE": + return getLineWithStartEndPtsClosestToPts(QadTangency.fromArcToEllipse(object1, sobject2), tanPt1, tanPt2) + elif object2.whatIs() == "ELLIPSE_ARC": + return getLineWithStartEndPtsClosestToPts(QadTangency.fromArcToEllipseArc(object1, object2), tanPt1, tanPt2) + + elif object1.whatIs() == "ELLIPSE": + if object2.whatIs() == "CIRCLE": + lines = QadTangency.fromCircleToEllipse(object2, object1) + for line in lines: line.reverse() + return getLineWithStartEndPtsClosestToPts(lines, tanPt1, tanPt2) + elif object2.whatIs() == "ARC": + lines = QadTangency.fromArcToEllipse(object2, object1) + for line in lines: line.reverse() + return getLineWithStartEndPtsClosestToPts(lines, tanPt1, tanPt2) + elif object2.whatIs() == "ELLIPSE": + return getLineWithStartEndPtsClosestToPts(QadTangency.twoEllipses(object1, object2), tanPt1, tanPt2) + elif object2.whatIs() == "ELLIPSE_ARC": + return getLineWithStartEndPtsClosestToPts(QadTangency.fromEllipseToEllipseArc(object1, object2), tanPt1, tanPt2) + + elif object1.whatIs() == "ELLIPSE_ARC": + if object2.whatIs() == "CIRCLE": + lines = QadTangency.fromCircleToEllipseArc(object2, object1) + for line in lines: line.reverse() + return getLineWithStartEndPtsClosestToPts(lines, tanPt1, tanPt2) + elif object2.whatIs() == "ARC": + lines = QadTangency.fromArcToEllipseArc(object2, object1) + for line in lines: line.reverse() + return getLineWithStartEndPtsClosestToPts(lines, tanPt1, tanPt2) + elif object2.whatIs() == "ELLIPSE": + lines = QadTangency.fromEllipseToEllipseArc(object2, object1) + for line in lines: line.reverse() + return getLineWithStartEndPtsClosestToPts(lines, tanPt1, tanPt2) + elif object2.whatIs() == "ELLIPSE_ARC": + return getLineWithStartEndPtsClosestToPts(QadTangency.twoEllipseArcs(object1, object2), tanPt1, tanPt2) + + return None + + + # ============================================================================ + # bestTwoBasicGeomObjectExtensions + # ============================================================================ + @staticmethod + def bestTwoBasicGeomObjectExtensions(object1, tanPt1, object2, tanPt2): + """ + Trova la linea tangente all'estensione di un oggetto geometrico di base e tangente ad un'estensione + di un altro oggetto geometrico di base (che ha i punti inziale/finale che rispettivamente sono + più vicini ai punti tanPt1 e tanPt2): + arco, arco di ellisse, cerchio, ellisse. + tanPt1 = punto di selezione geometria 1 di tangenza + tanPt2 = punto di selezione geometria 2 di tangenza + """ + if object1.whatIs() == "CIRCLE": + if object2.whatIs() == "CIRCLE": + return getLineWithStartEndPtsClosestToPts(QadTangency.twoCircles(object1, object2), tanPt1, tanPt2) + elif object2.whatIs() == "ARC": + circle2 = QadCircle() + circle2.set(object2.center, object2.radius) + return getLineWithStartEndPtsClosestToPts(QadTangency.twoCircles(object1, circle2), tanPt1, tanPt2) + elif object2.whatIs() == "ELLIPSE": + return getLineWithStartEndPtsClosestToPts(QadTangency.circleWithEllipse(object1, object2), tanPt1, tanPt2) + elif object2.whatIs() == "ELLIPSE_ARC": + ellipse2 = QadEllipse() + ellipse2.set(object2.center, object2.majorAxisFinalPt, object2.axisRatio) + return getLineWithStartEndPtsClosestToPts(QadTangency.circleWithEllipse(object1, ellipse2), tanPt1, tanPt2) + + elif object1.whatIs() == "ARC": + circle1 = QadCircle() + circle1.set(object1.center, object1.radius) + if object2.whatIs() == "CIRCLE": + return getLineWithStartEndPtsClosestToPts(QadTangency.twoCircles(circle1, object2), tanPt1, tanPt2) + elif object2.whatIs() == "ARC": + circle2 = QadCircle() + circle2.set(object2.center, object2.radius) + return getLineWithStartEndPtsClosestToPts(QadTangency.twoCircles(circle1, circle2), tanPt1, tanPt2) + elif object2.whatIs() == "ELLIPSE": + return getLineWithStartEndPtsClosestToPts(QadTangency.circleWithEllipse(circle1, object2), tanPt1, tanPt2) + elif object2.whatIs() == "ELLIPSE_ARC": + ellipse2 = QadEllipse() + ellipse2.set(object2.center, object2.majorAxisFinalPt, object2.axisRatio) + return getLineWithStartEndPtsClosestToPts(QadTangency.circleWithEllipse(circle1, ellipse2), tanPt1, tanPt2) + + elif object1.whatIs() == "ELLIPSE": + if object2.whatIs() == "CIRCLE": + lines = QadTangency.circleWithEllipse(object2, object1) + for line in lines: line.reverse() + return getLineWithStartEndPtsClosestToPts(lines, tanPt1, tanPt2) + elif object2.whatIs() == "ARC": + circle2 = QadCircle() + circle2.set(object2.center, object2.radius) + lines = QadTangency.circleWithEllipse(circle2, object1) + for line in lines: line.reverse() + return getLineWithStartEndPtsClosestToPts(lines, tanPt1, tanPt2) + elif object2.whatIs() == "ELLIPSE": + return getLineWithStartEndPtsClosestToPts(QadTangency.twoEllipses(object1, object2), tanPt1, tanPt2) + elif object2.whatIs() == "ELLIPSE_ARC": + ellipse2 = QadEllipse() + ellipse2.set(object2.center, object2.majorAxisFinalPt, object2.axisRatio) + lines = QadTangency.ellipseWithEllipseArc(object1, ellipse2) + for line in lines: line.reverse() + return getLineWithStartEndPtsClosestToPts(lines, tanPt1, tanPt2) + + elif object1.whatIs() == "ELLIPSE_ARC": + ellipse1 = QadEllipse() + ellipse1.set(object1.center, object1.majorAxisFinalPt, object1.axisRatio) + if object2.whatIs() == "CIRCLE": + lines = QadTangency.circleWithEllipse(object2, ellipse1) + for line in lines: line.reverse() + return getLineWithStartEndPtsClosestToPts(lines, tanPt1, tanPt2) + elif object2.whatIs() == "ARC": + circle2 = QadCircle() + circle2.set(object2.center, object2.radius) + lines = QadTangency.circleWithEllipse(circle2, ellipse1) + for line in lines: line.reverse() + return getLineWithStartEndPtsClosestToPts(lines, tanPt1, tanPt2) + elif object2.whatIs() == "ELLIPSE": + return getLineWithStartEndPtsClosestToPts(QadTangency.twoEllipses(ellipse1, object2), tanPt1, tanPt2) + elif object2.whatIs() == "ELLIPSE_ARC": + ellipse2 = QadEllipse() + ellipse2.set(object2.center, object2.majorAxisFinalPt, object2.axisRatio) + return getLineWithStartEndPtsClosestToPts(QadTangency.twoEllipseArcs(ellipse1, ellipse2), tanPt1, tanPt2) + + return None + + +# =============================================================================== +# QadTangPerp class +# rappresenta una classe che calcola le linee tangenti ad un oggetto e perpendicolari ad un altro oggetto +# =============================================================================== +class QadTangPerp(): + + def __init__(self): + pass + + + # =============================================================================== + # metodi per i cerchi - inizio + # =============================================================================== + + + # =============================================================================== + # circleWithInfinityLine + # =============================================================================== + @staticmethod + def circleWithInfinityLine(circle1, line2): + """ + Trova la linee tangenti a un cerchio e perpendicolari ad una linea infinita + """ + lines = [] + # linee tangenti ad un cerchio e perpendicolari ad una linea + angle = line2.getTanDirectionOnStartPt() + pt1 = qad_utils.getPolarPointByPtAngle(circle1.center, angle, circle1.radius) + pt2 = QadPerpendicularity.fromPointToInfinityLine(pt1, line2) + if pt2 is not None: + line = QadLine() + line.set(pt1, pt2) # primo punto tangente e secondo punto perpendicolare + lines.append(line) + + pt1 = qad_utils.getPolarPointByPtAngle(circle1.center, angle, -1 * circle1.radius) + pt2 = QadPerpendicularity.fromPointToInfinityLine(pt1, line2) + if pt2 is not None: + line = QadLine() + line.set(pt1, pt2) # primo punto tangente e secondo punto perpendicolare + lines.append(line) + + return lines + + + # =============================================================================== + # circleWithLine + # =============================================================================== + @staticmethod + def circleWithLine(circle1, line2): + """ + Trova le linee tangenti a un cerchio e perpendicolari ad una linea + """ + lines = QadTangPerp.circleWithInfinityLine(circle1, line2) + + if len(lines) == 2: + if line2.containsPt(lines[1].getEndPt()) == False: del(lines[1]) + + if len(lines) == 1: + if line2.containsPt(lines[0].getEndPt()) == False: del(lines[0]) + + return lines + + + # =============================================================================== + # twoCircles + # =============================================================================== + @staticmethod + def twoCircles(circle1, circle2): + """ + Trova le linee tangenti a un cerchio e perpendicolari ad un altro cerchio + """ + lines = [] + points = QadTangency.fromPointToCircle(circle2.center, circle1) + for point in points: + angle = qad_utils.getAngleBy2Pts(circle2.center, point) + pt1 = qad_utils.getPolarPointByPtAngle(circle2.center, angle, circle2.radius) + line = QadLine() + line.set(point, pt1) # primo punto tangente e secondo punto perpendicolare + lines.append(line) + pt1 = qad_utils.getPolarPointByPtAngle(circle2.center, angle, -1 * circle2.radius) + line = QadLine() + line.set(point, pt1) # primo punto tangente e secondo punto perpendicolare + lines.append(line) + + return lines + + + # =============================================================================== + # circleWithArc + # =============================================================================== + @staticmethod + def circleWithArc(circle1, arc2): + """ + Trova la linee tangenti a un cerchio e perpendicolari ad un arco + """ + circle2 = QadCircle() + circle2.set(arc2.center, arc2.radius) + + lines = QadTangPerp.twoCircles(circle1, circle2) + + if len(lines) == 2: + if arc2.containsPt(lines[1].getEndPt()) == False: del(lines[1]) + + if len(lines) == 1: + if arc2.containsPt(lines[0].getEndPt()) == False: del(lines[0]) + + return lines + + + # =============================================================================== + # circleWithEllipse + # =============================================================================== + @staticmethod + def circleWithEllipse(circle1, ellipse2): + """ + Trova le linee tangenti a un cerchio e perpendicolari ad un'ellisse + """ + # da fare + return [] + + + # =============================================================================== + # circleWithEllipseArc + # =============================================================================== + @staticmethod + def circleWithEllipseArc(circle1, ellipseArc2): + """ + Trova la linee tangenti a un cerchio e perpendicolari ad un arc di ellisse + """ + # da fare + return [] + + + # =============================================================================== + # metodi per i cerchi - fine + # metodi per gli archi - inizio + # =============================================================================== + + + # =============================================================================== + # arcWithInfinityLine + # =============================================================================== + @staticmethod + def arcWithInfinityLine(arc1, line2): + """ + Trova la linee tangenti a un arco e perpendicolari ad una linea infinita + """ + circle1 = QadCircle() + circle1.set(arc1.center, arc1.radius) + + lines = QadTangPerp.circleWithInfinityLine(circle1, line2) + + if len(lines) == 2: + if arc1.containsPt(lines[1].getStartPt()) == False: del(lines[1]) + + if len(lines) == 1: + if arc1.containsPt(lines[0].getStartPt()) == False: del(lines[0]) + + return lines + + + # =============================================================================== + # arcWithLine + # =============================================================================== + @staticmethod + def arcWithLine(arc1, line2): + """ + Trova la linee tangenti a un arco e perpendicolari ad una linea + """ + lines = QadTangPerp.arcWithInfinityLines(arc1, line2) + + if len(lines) == 2: + if line2.containsPt(lines[1].getEndPt()) == False: del(lines[1]) + + if len(lines) == 1: + if line2.containsPt(lines[0].getEndPt()) == False: del(lines[0]) + + return lines + + + # =============================================================================== + # arcWithCircle + # =============================================================================== + @staticmethod + def arcWithCircle(arc1, circle2): + """ + Trova le linee tangenti a un arco e perpendicolari ad un cerchio + """ + circle1 = QadCircle() + circle1.set(arc1.center, arc1.radius) + + lines = QadTangPerp.twoCircles(circle1, circle2) + + if len(lines) == 2: + if arc1.containsPt(lines[1].getStartPt()) == False: del(lines[1]) + + if len(lines) == 1: + if arc1.containsPt(lines[0].getStartPt()) == False: del(lines[0]) + + return lines + + + # =============================================================================== + # twoArcs + # =============================================================================== + @staticmethod + def twoArcs(arc1, arc2): + """ + Trova le linee tangenti a un arco e perpendicolari ad un altro arco + """ + circle2 = QadCircle() + circle2.set(arc2.center, arc2.radius) + + lines = QadTangPerp.arcWithCircle(arc1, circle2) + + if len(lines) == 2: + if arc2.containsPt(lines[1].getEndPt()) == False: del(lines[1]) + + if len(lines) == 1: + if arc2.containsPt(lines[0].getEndPt()) == False: del(lines[0]) + + return lines + + + # =============================================================================== + # arcWithEllipse + # =============================================================================== + @staticmethod + def arcWithEllipse(arc1, ellipse2): + """ + Trova le linee tangenti a un arco e perpendicolari ad una ellisse + """ + circle1 = QadCircle() + circle1.set(arc1.center, arc1.radius) + + lines = QadTangPerp.circleWithEllipse(circle1, ellipse2) + + if len(lines) == 2: + if arc1.containsPt(lines[1].getStartPt()) == False: del(lines[1]) + + if len(lines) == 1: + if arc1.containsPt(lines[0].getStartPt()) == False: del(lines[1]) + + return lines + + + # =============================================================================== + # arcWithEllipseArc + # =============================================================================== + @staticmethod + def arcWithEllipseArc(arc1, ellipseArc2): + """ + Trova le linee tangenti a un arco e perpendicolari ad un arco di ellisse + """ + ellipse2 = QadEllipse() + ellipse2.set(ellipseArc2.center, ellipseArc2.majorAxisFinalPt, ellipseArc2.axisRatio) + + lines = QadTangPerp.arcWithEllipse(arc1, ellipse2) + + if len(lines) == 2: + if ellipseArc2.isPtOnEllipseArcOnlyByAngle(lines[1].getEndPt()) == False: del(lines[1]) + + if len(lines) == 1: + if arc1.isPtOnEllipseArcOnlyByAngle(lines[0].getEndPt()) == False: del(lines[0]) + + return lines + + + # =============================================================================== + # metodi per gli archi - fine + # metodi per le ellissi - inizio + # =============================================================================== + + + # =============================================================================== + # ellipseWithInfinityLine + # =============================================================================== + @staticmethod + def ellipseWithInfinityLine(ellipse1, line2): + """ + Trova le linee tangenti a un'ellisse e perpendicolari ad una linea infinita + """ + # da fare + return [] + + + # =============================================================================== + # ellipseWithLine + # =============================================================================== + @staticmethod + def ellipseWithLine(ellipse1, line2): + """ + Trova le linee tangenti a un'ellisse e perpendicolari ad una linea + """ + lines = QadTangPerp.ellipseWithInfinityLines(ellipse1, line2) + + if len(lines) == 2: + if line2.containsPt(lines[1].getEndPt()) == False: del(lines[1]) + + if len(lines) == 1: + if line2.containsPt(lines[0].getEndPt()) == False: del(lines[0]) + + return lines + + + # =============================================================================== + # ellipseWithCircle + # =============================================================================== + @staticmethod + def ellipseWithCircle(ellipse1, circle2): + """ + Trova le linee tangenti a un'ellisse e perpendicolari ad un cerchio + """ + # da fare + return [] + + + # =============================================================================== + # ellipseWithArc + # =============================================================================== + @staticmethod + def ellipseWithArc(ellipse1, arc2): + """ + Trova le linee tangenti a un'ellisse e perpendicolari ad un arco + """ + circle2 = QadCircle() + circle2.set(arc2.center, arc2.radius) + + lines = QadTangPerp.ellipseWithCircle(ellipse1, tanPt1, circle2, perPt2) + + if len(lines) == 2: + if arc2.containsPt(lines[1].getEndPt()) == False: del(lines[1]) + + if len(lines) == 1: + if arc2.containsPt(lines[0].getEndPt()) == False: del(lines[0]) + + return lines + + + # =============================================================================== + # twoEllipses + # =============================================================================== + @staticmethod + def twoEllipses(ellipse1, ellipse2): + """ + Trova le linee tangenti a un'ellisse e perpendicolari ad un altra ellisse + """ + # da fare + return [] + + + # =============================================================================== + # ellipseWithEllipseArc + # =============================================================================== + @staticmethod + def ellipseWithEllipseArc(ellipse1, ellipseArc2): + """ + Trova le linee tangenti a un'ellisse e perpendicolari ad un arco di ellisse + """ + ellipse2 = QadEllipse() + ellipse2.set(ellipseArc2.center, ellipseArc2.majorAxisFinalPt, ellipseArc2.axisRatio) + + lines = QadTangPerp.twoEllipses(ellipse1, ellipse2) + + if len(lines) == 2: + if ellipseArc2.isPtOnEllipseArcOnlyByAngle(lines[1].getEndPt()) == False: del(lines[1]) + + if len(lines) == 1: + if ellipseArc2.isPtOnEllipseArcOnlyByAngle(lines[0].getEndPt()) == False: del(lines[0]) + + return lines + + + # =============================================================================== + # metodi per le ellissi - fine + # metodi per gli archi di ellisse - inizio + # =============================================================================== + + + # =============================================================================== + # ellipseArcWithInfinityLine + # =============================================================================== + @staticmethod + def ellipseArcWithInfinityLine(ellipseArc1, line2): + """ + Trova le linee tangenti a un arco di ellisse e perpendicolari ad una linea infinita + """ + ellipse1 = QadEllipse() + ellipse1.set(ellipseArc1.center, ellipseArc1.majorAxisFinalPt, ellipseArc1.axisRatio) + + lines = QadTangPerp.ellipseWithInfinityLine(ellipse1, line2) + + if len(lines) == 2: + if ellipseArc1.isPtOnEllipseArcOnlyByAngle(lines[1].getStartPt()) == False: del(lines[1]) + + if len(lines) == 1: + if ellipseArc1.isPtOnEllipseArcOnlyByAngle(lines[0].getStartPt()) == False: del(lines[0]) + + return lines + + + # =============================================================================== + # ellipseArcWithLine + # =============================================================================== + @staticmethod + def ellipseArcWithLine(ellipseArc1, line2): + """ + Trova le linee tangenti a un arco di ellisse e perpendicolari ad una linea + """ + lines = QadTangPerp.ellipseArcWithInfinityLine(ellipseArc1, line2) + + if len(lines) == 2: + if line2.containsPt(lines[1].getEndPt()) == False: del(lines[1]) + + if len(lines) == 1: + if line2.containsPt(lines[0].getEndPt()) == False: del(lines[0]) + + return lines + + + # =============================================================================== + # ellipseArcWithCircle + # =============================================================================== + @staticmethod + def ellipseArcWithCircle(ellipseArc1, circle2): + """ + Trova le linee tangenti a un arco di ellisse e perpendicolari ad un cerchio + """ + ellipse1 = QadEllipse() + ellipse1.set(ellipseArc1.center, ellipseArc1.majorAxisFinalPt, ellipseArc1.axisRatio) + + lines = QadTangPerp.ellipseWithCircle(ellipse1, line2) + + if len(lines) == 2: + if ellipseArc1.isPtOnEllipseArcOnlyByAngle(lines[1].getStartPt()) == False: del(lines[1]) + + if len(lines) == 1: + if ellipseArc1.isPtOnEllipseArcOnlyByAngle(lines[0].getStartPt()) == False: del(lines[0]) + + return lines + + + # =============================================================================== + # ellipseArcWithArc + # =============================================================================== + @staticmethod + def ellipseArcWithArc(ellipseArc1, arc2): + """ + Trova le linee tangenti a un arco di ellisse e perpendicolari ad un arco + """ + ellipse1 = QadEllipse() + ellipse1.set(ellipseArc1.center, ellipseArc1.majorAxisFinalPt, ellipseArc1.axisRatio) + + lines = QadTangPerp.ellipseWithArc(ellipse1, arc2) + + if len(lines) == 2: + if ellipseArc1.isPtOnEllipseArcOnlyByAngle(lines[1].getStartPt()) == False: del(lines[1]) + + if len(lines) == 1: + if ellipseArc1.isPtOnEllipseArcOnlyByAngle(lines[0].getStartPt()) == False: del(lines[0]) + + return lines + + + # =============================================================================== + # ellipseArcWithEllipse + # =============================================================================== + @staticmethod + def ellipseArcWithEllipse(ellipseArc1, ellipse2): + """ + Trova le linee tangenti a un arco di ellisse e perpendicolari ad un'ellisse + """ + ellipse1 = QadEllipse() + ellipse1.set(ellipseArc1.center, ellipseArc1.majorAxisFinalPt, ellipseArc1.axisRatio) + + lines = QadTangPerp.twoEllipses(ellipse1, ellipse2) + + if len(lines) == 2: + if ellipseArc1.isPtOnEllipseArcOnlyByAngle(lines[1].getStartPt()) == False: del(lines[1]) + + if len(lines) == 1: + if ellipseArc1.isPtOnEllipseArcOnlyByAngle(lines[0].getStartPt()) == False: del(lines[0]) + + return lines + + + # =============================================================================== + # twoEllipseArcs + # =============================================================================== + @staticmethod + def twoEllipseArcs(ellipseArc1, ellipseArc2): + """ + Trova le linee tangenti a un arco di ellisse e perpendicolari ad un altro arco di ellisse + """ + ellipse1 = QadEllipse() + ellipse1.set(ellipseArc1.center, ellipseArc1.majorAxisFinalPt, ellipseArc1.axisRatio) + + lines = QadTangPerp.ellipseWithEllipseArc(ellipse1, ellipseArc2) + + if len(lines) == 2: + if ellipseArc2.isPtOnEllipseArcOnlyByAngle(lines[1].getEndPt()) == False: del(lines[1]) + + if len(lines) == 1: + if ellipseArc2.isPtOnEllipseArcOnlyByAngle(lines[0].getEndPt()) == False: del(lines[0]) + + return lines + + + # ============================================================================ + # bestTwoBasicGeomObjects + # ============================================================================ + @staticmethod + def bestTwoBasicGeomObjects(object1, tanPt1, object2, perPt2): + """ + Trova la linea tangente a un oggetto geometrico di base e perpendicolarie ad un altro oggetto geometrico di base + (che ha i punti inziale/finale che rispettivamente sono più vicini ai punti tanPt1 e perPt2): + linea, arco, arco di ellisse, cerchio, ellisse. + tanPt1 = punto di selezione geometria di tangenza + perPt2 = punto di selezione geometria di perpendicolarità + """ + if object1.whatIs() == "CIRCLE": + if object2.whatIs() == "LINE": + return getLineWithStartEndPtsClosestToPts(QadTangPerp.circleWithLine(object1, object2), tanPt1, perPt2) + elif object2.whatIs() == "CIRCLE": + return getLineWithStartEndPtsClosestToPts(QadTangPerp.twoCircles(object1, object2), tanPt1, perPt2) + elif object2.whatIs() == "ARC": + return getLineWithStartEndPtsClosestToPts(QadTangPerp.circleWithArc(object1, object2), tanPt1, perPt2) + elif object2.whatIs() == "ELLIPSE": + return getLineWithStartEndPtsClosestToPts(QadTangPerp.circleWithEllipse(object1, object2), tanPt1, perPt2) + elif object2.whatIs() == "ELLIPSE_ARC": + return getLineWithStartEndPtsClosestToPts(QadTangPerp.circleWithEllipseArc(object1, object2), tanPt1, perPt2) + + elif object1.whatIs() == "ARC": + if object2.whatIs() == "LINE": + return getLineWithStartEndPtsClosestToPts(QadTangPerp.arcWithLine(object1, object2), tanPt1, perPt2) + elif object2.whatIs() == "CIRCLE": + return getLineWithStartEndPtsClosestToPts(QadTangPerp.arcWithCircle(object1, object2), tanPt1, perPt2) + elif object2.whatIs() == "ARC": + return getLineWithStartEndPtsClosestToPts(QadTangPerp.twoArcs(object1, object2), tanPt1, perPt2) + elif object2.whatIs() == "ELLIPSE": + return getLineWithStartEndPtsClosestToPts(QadTangPerp.arcWithEllipse(object1, object2), tanPt1, perPt2) + elif object2.whatIs() == "ELLIPSE_ARC": + return getLineWithStartEndPtsClosestToPts(QadTangPerp.arcWithEllipseArc(object1, object2), tanPt1, perPt2) + + elif object1.whatIs() == "ELLIPSE": + if object2.whatIs() == "LINE": + return getLineWithStartEndPtsClosestToPts(QadTangPerp.ellipseWithLine(object1, object2), tanPt1, perPt2) + elif object2.whatIs() == "CIRCLE": + return getLineWithStartEndPtsClosestToPts(QadTangPerp.ellipseWithCircle(object1, object2), tanPt1, perPt2) + elif object2.whatIs() == "ARC": + return getLineWithStartEndPtsClosestToPts(QadTangPerp.ellipseWithArc(object1, object2), tanPt1, perPt2) + elif object2.whatIs() == "ELLIPSE": + return getLineWithStartEndPtsClosestToPts(QadTangPerp.twoEllipses(object1, object2), tanPt1, perPt2) + elif object2.whatIs() == "ELLIPSE_ARC": + return getLineWithStartEndPtsClosestToPts(QadTangPerp.ellipseWithEllipseArc(object1, object2), tanPt1, perPt2) + + elif object1.whatIs() == "ELLIPSE_ARC": + if object2.whatIs() == "LINE": + return getLineWithStartEndPtsClosestToPts(QadTangPerp.ellipseArcWithLine(object1, object2), tanPt1, perPt2) + elif object2.whatIs() == "CIRCLE": + return getLineWithStartEndPtsClosestToPts(QadTangPerp.ellipseArcWithCircle(object1, object2), tanPt1, perPt2) + elif object2.whatIs() == "ARC": + return getLineWithStartEndPtsClosestToPts(QadTangPerp.ellipseArcWithArc(object1, object2), tanPt1, perPt2) + elif object2.whatIs() == "ELLIPSE": + return getLineWithStartEndPtsClosestToPts(QadTangPerp.ellipseArcWithEllipse(object1, object2), tanPt1, perPt2) + elif object2.whatIs() == "ELLIPSE_ARC": + return getLineWithStartEndPtsClosestToPts(QadTangPerp.twoEllipseArcs(object1, object2), tanPt1, perPt2) + + return None + + + # ============================================================================ + # bestTwoBasicGeomObjectExtensions + # ============================================================================ + @staticmethod + def bestTwoBasicGeomObjectExtensions(object1, tanPt1, object2, perPt2): + """ + Trova le linee tangenti all'estensione di un oggetto geometrico di base e perpendicolari ad un'estensione + di un altro oggetto geometrico di base (che ha i punti inziale/finale che rispettivamente + sono più vicini ai punti tanPt1 e perPt2): + linea (diventa linea infinita), arco (diventa cerchio), arco di ellisse (diventa ellisse), cerchio, ellisse. + tanPt1 = punto di selezione geometria di tangenza + perPt2 = punto di selezione geometria di perpendicolarità + """ + if object1.whatIs() == "CIRCLE": + if object2.whatIs() == "LINE": + return getLineWithStartEndPtsClosestToPts(QadTangPerp.circleWithInfinityLine(object2, object1), tanPt1, perPt2) + elif object2.whatIs() == "CIRCLE": + return getLineWithStartEndPtsClosestToPts(QadTangPerp.twoCircles(object1, object2), tanPt1, perPt2) + elif object2.whatIs() == "ARC": + circle = QadCircle() + circle.set(object2.center, object2.radius) + return getLineWithStartEndPtsClosestToPts(QadTangPerp.twoCircles(object1, circle), tanPt1, perPt2) + elif object2.whatIs() == "ELLIPSE": + return getLineWithStartEndPtsClosestToPts(QadTangPerp.circleWithEllipse(object1, object2), tanPt1, perPt2) + elif object2.whatIs() == "ELLIPSE_ARC": + ellipse = QadEllipse() + ellipse.set(object2.center, object2.majorAxisFinalPt, object2.axisRatio) + return getLineWithStartEndPtsClosestToPts(QadTangPerp.circleWithEllipse(object1, ellipse), tanPt1, perPt2) + + elif object1.whatIs() == "ARC": + circle1 = QadCircle() + circle1.set(object1.center, object1.radius) + return getLineWithStartEndPtsClosestToPts(QadTangPerp.fromTwoBasicGeomObjectExtensions(circle1, object2), tanPt1, perPt2) + + elif object1.whatIs() == "ELLIPSE": + if object2.whatIs() == "LINE": + return getLineWithStartEndPtsClosestToPts(QadTangPerp.ellipseWithInfinityLine(object1, object2), tanPt1, perPt2) + elif object2.whatIs() == "CIRCLE": + return getLineWithStartEndPtsClosestToPts(QadTangPerp.ellipseWithCircle(object1, object2), tanPt1, perPt2) + elif object2.whatIs() == "ARC": + circle = QadCircle() + circle.set(object2.center, object2.radius) + return getLineWithStartEndPtsClosestToPts(QadTangPerp.ellipseWithCircle(object1, circle), tanPt1, perPt2) + elif object2.whatIs() == "ELLIPSE": + return getLineWithStartEndPtsClosestToPts(QadTangPerp.twoEllipses(object1, object2), tanPt1, perPt2) + elif object2.whatIs() == "ELLIPSE_ARC": + ellipse = QadEllipse() + ellipse.set(object2.center, object2.majorAxisFinalPt, object2.axisRatio) + return getLineWithStartEndPtsClosestToPts(QadTangPerp.twoEllipses(object1, ellipse), tanPt1, perPt2) + + elif object1.whatIs() == "ELLIPSE_ARC": + ellipse1 = QadEllipse() + ellipse1.set(object1.center, object1.majorAxisFinalPt, object1.axisRatio) + if object2.whatIs() == "LINE": + return getLineWithStartEndPtsClosestToPts(QadTangPerp.ellipseWithInfinityLine(ellipse1, object2), tanPt1, perPt2) + elif object2.whatIs() == "CIRCLE": + return getLineWithStartEndPtsClosestToPts(QadTangPerp.ellipseWithCircle(ellipse1, object2), tanPt1, perPt2) + elif object2.whatIs() == "ARC": + circle = QadCircle() + circle.set(object.center, object.radius) + return getLineWithStartEndPtsClosestToPts(QadTangPerp.ellipseWithCircle(ellipse1, circle), tanPt1, perPt2) + elif object2.whatIs() == "ELLIPSE": + return getLineWithStartEndPtsClosestToPts(QadTangPerp.twoEllipses(ellipse1, object2), tanPt1, perPt2) + elif object2.whatIs() == "ELLIPSE_ARC": + ellipse2 = QadEllipse() + ellipse2.set(object2.center, object2.majorAxisFinalPt, object2.axisRatio) + return getLineWithStartEndPtsClosestToPts(QadTangPerp.twoEllipses(ellipse1, ellipse2), tanPt1, perPt2) + + return None + + +# =============================================================================== +# QadPerpPerp class +# rappresenta una classe che calcola le linee perpendicolari ad un oggetto e perpendicolari ad un altro oggetto +# =============================================================================== +class QadPerpPerp(): + + def __init__(self): + pass + + + # =============================================================================== + # metodi per le linee infinite - inizio + # =============================================================================== + + + # =============================================================================== + # infinityLineWithCircle + # =============================================================================== + @staticmethod + def infinityLineWithCircle(infinityLine1, circle2): + """ + La funzione ritorna la linea perpendicolare tra la linea1 considerata linea infinita ed un cerchio. + """ + # linea perpendicolare ad una linea e ad un cerchio + ptPer1 = QadPerpendicularity.fromPointToInfinityLine(circle2.center, infinityLine1) + angle = qad_utils.getAngleBy2Pts(circle2.center, ptPer1) + ptPer2 = qad_utils.getPolarPointByPtAngle(circle2.center, angle, circle2.radius) + line = QadLine() + line.set(ptPer1, ptPer2) + return line + + + # =============================================================================== + # infinityLineWithArc + # =============================================================================== + @staticmethod + def infinityLineWithArc(infinityLine1, arc2): + """ + La funzione ritorna la linea perpendicolare alla linea1 considerata linea infinita ed un arco. + """ + circle = QadCircle() + circle.set(arc2.center, arc2.radius) + line = QadPerpPerp.infinityLineWithCircle(infinityLine1, circle) + if line is None: return None + if arc2.isPtOnArcOnlyByAngle(line.getEndPt()): return line + return None + + + # =============================================================================== + # infinityLineWithEllipse + # =============================================================================== + @staticmethod + def infinityLineWithEllipse(infinityLine1, ellipse2): + """ + La funzione ritorna le linee perpendicolari alla linea1 considerata linea infinita ed un'ellisse. + (fino a 4 linee) + """ + # da fare + return [] + + + # =============================================================================== + # infinityLineWithEllipseArc + # =============================================================================== + @staticmethod + def infinityLineWithEllipseArc(infinityLine1, ellipseArc2): + """ + La funzione ritorna le linee perpendicolari alla linea 1 considerata linea infinita ed un arco di ellisse. + (fino a 4 linee) + """ + ellipse = QadEllipse() + ellipse.set(ellipseArc2.center, ellipseArc2.majorAxisFinalPt, ellipseArc2.axisRatio) + lines = QadPerpPerp.infinityLineWithEllipse(infinityLine1, ellipse) + + if len(lines) == 4: + if ellipseArc2.isPtOnEllipseArcOnlyByAngle(lines[3].getEndPt()) == False: del(lines[3]) + if len(lines) == 3: + if ellipseArc2.isPtOnEllipseArcOnlyByAngle(lines[2].getEndPt()) == False: del(lines[2]) + if len(lines) == 2: + if ellipseArc2.isPtOnEllipseArcOnlyByAngle(lines[1].getEndPt()) == False: del(lines[1]) + if len(lines) == 1: + if ellipseArc2.isPtOnEllipseArcOnlyByAngle(lines[0].getEndPt()) == False: del(lines[0]) + + return lines + + + # =============================================================================== + # metodi per le linee infinite - fine + # metodi per i segmenti - inizio + # =============================================================================== + + # =============================================================================== + # lineWithLine + # =============================================================================== + @staticmethod + def lineWithLine(line1, perPt1, line2, perPt2): + """ + La funzione ritorna la linea perpendicolare ad un segmento ad un segmento. + """ + ang1 = qad_utils.normalizeAngle(qad_utils.getAngleBy2Pts(line1.getStartPt(), line1.getEndPt()), math.pi) + ang2 = qad_utils.normalizeAngle(qad_utils.getAngleBy2Pts(line2.getStartPt(), line2.getEndPt()), math.pi) + if qad_utils.TanDirectionNear(ang1, ang2) == True: + result = QadLine() + result.set(perPt1, perPt2) + return result + + return None + + # =============================================================================== + # lineWithCircle + # =============================================================================== + @staticmethod + def lineWithCircle(line1, circle2): + """ + La funzione ritorna la linea perpendicolare ad un segmento ed un cerchio. + """ + line = QadPerpPerp.infinityLineWithCircle(line1, circle2) + if line is None: return None + if line1.containsPt(line.getStartPt()): return line + return None + + + # =============================================================================== + # lineWithArc + # =============================================================================== + @staticmethod + def lineWithArc(line1, arc2): + """ + La funzione ritorna la linea perpendicolare ad un segmento ed un arco. + """ + circle = QadCircle() + circle.set(arc2.center, arc2.radius) + line = QadPerpPerp.lineWithCircle(line1, circle) + if line is None: return None + if arc2.isPtOnArcOnlyByAngle(line.getEndPt()): return line + return None + + + # =============================================================================== + # lineWithEllipse + # =============================================================================== + @staticmethod + def lineWithEllipse(line1, ellipse2): + """ + La funzione ritorna le linee perpendicolari ad un segmento ed un'ellisse. + (fino a 4 linee) + """ + lines = QadPerpPerp.infinityLineWithEllipse(line1, ellipse2) + + if len(lines) == 4: + if line1.containsPt(lines[3].getStartPt()) == False: del(lines[3]) + if len(lines) == 3: + if line1.containsPt(lines[2].getStartPt()) == False: del(lines[2]) + if len(lines) == 2: + if line1.containsPt(lines[1].getStartPt()) == False: del(lines[1]) + if len(lines) == 1: + if line1.containsPt(lines[0].getStartPt()) == False: del(lines[0]) + + return lines + + + # =============================================================================== + # lineWithEllipseArc + # =============================================================================== + @staticmethod + def lineWithEllipseArc(line1, ellipseArc2): + """ + La funzione ritorna le linee perpendicolari ad un segmento ed un arco di ellisse. + (fino a 4 linee) + """ + lines = QadPerpPerp.infinityLineWithEllipseArc(line1, ellipseArc) + + if len(lines) == 4: + if line1.containsPt(lines[3].getStartPt()) == False: del(lines[3]) + if len(lines) == 3: + if line1.containsPt(lines[2].getStartPt()) == False: del(lines[2]) + if len(lines) == 2: + if line1.containsPt(lines[1].getStartPt()) == False: del(lines[1]) + if len(lines) == 1: + if line1.containsPt(lines[0].getStartPt()) == False: del(lines[0]) + + return lines + + + # =============================================================================== + # metodi per i segmenti - fine + # metodi per i cerchi - inizio + # =============================================================================== + + + # =============================================================================== + # twoCircles + # =============================================================================== + @staticmethod + def twoCircles(circle1, circle2): + """ + La funzione ritorna la linea perpendicolare tra due cerchi + """ + angle = qad_utils.getAngleBy2Pts(circle1.center, circle2.center) + ptPer1 = qad_utils.getPolarPointByPtAngle(circle1.center, angle, circle1.radius) + ptPer2 = qad_utils.getPolarPointByPtAngle(circle2.center, angle, -circle2.radius) + line = QadLine() + line.set(ptPer1, ptPer2) + return line + + + # =============================================================================== + # circleWithArc + # =============================================================================== + @staticmethod + def circleWithArc(circle1, arc2): + """ + La funzione ritorna la linea perpendicolare ad un cerchio ed un arco + """ + circle = QadCircle() + circle.set(arc2.center, arc2.radius) + line = QadPerpPerp.twoCircles(circle1, circle) + if line is None: return None + if arc2.isPtOnArcOnlyByAngle(line.getEndPt()): return line + return None + + + # =============================================================================== + # circleWithEllipse + # =============================================================================== + @staticmethod + def circleWithEllipse(circle1, ellipse2): + """ + La funzione ritorna le linee perpendicolari tra un cerchio ed un'ellisse + (fino a 4 linee) + """ + perpPts = QadPerpendicularity.fromPointToEllipse(circle1.center, ellipse2) + lines = [] + for perpPt2 in perpPts: + angle = qad_utils.getAngleBy2Pts(circle1.center, perpPt2) + perPt1 = qad_utils.getPolarPointByPtAngle(circle1.center, angle, circle1.radius) + line = QadLine() + line.set(perPt1, perpPt2) + lines.append(line) + return lines + + + # =============================================================================== + # circleWithEllipseArc + # =============================================================================== + @staticmethod + def circleWithEllipseArc(circle1, ellipseArc2): + """ + La funzione ritorna le linee perpendicolari ad un cerchio ed un'ellisse + (fino a 4 linee) + """ + ellipse = QadEllipse() + ellipse.set(ellipseArc2.center, ellipseArc2.majorAxisFinalPt, ellipseArc2.axisRatio) + lines = QadPerpPerp.circleWithEllipse(circle1, ellipse) + + if len(lines) == 4: + if ellipseArc2.isPtOnEllipseArcOnlyByAngle(lines[3].getEndPt()) == False: del(lines[3]) + if len(lines) == 3: + if ellipseArc2.isPtOnEllipseArcOnlyByAngle(lines[2].getEndPt()) == False: del(lines[2]) + if len(lines) == 2: + if ellipseArc2.isPtOnEllipseArcOnlyByAngle(lines[1].getEndPt()) == False: del(lines[1]) + if len(lines) == 1: + if ellipseArc2.isPtOnEllipseArcOnlyByAngle(lines[0].getEndPt()) == False: del(lines[0]) + + return lines + + + # =============================================================================== + # metodi per i cerchi - fine + # metodi per gli archi - inizio + # =============================================================================== + + + # =============================================================================== + # twoArcs + # =============================================================================== + @staticmethod + def twoArcs(arc1, arc2): + """ + La funzione ritorna le linee perpendicolari a due archi + """ + circle1 = QadCircle() + circle1.set(arc1.center, arc1.radius) + line = QadPerpPerp.circleWithArc(circle1, arc2) + if line is None: return None + if arc1.isPtOnArcOnlyByAngle(line.getStartPt()): return line + return None + + + # =============================================================================== + # arcWithEllipse + # =============================================================================== + @staticmethod + def arcWithEllipse(arc1, ellipse2): + """ + La funzione ritorna le linee perpendicolari ad un arco ed un'ellisse + (fino a 4 linee) + """ + circle = QadCircle() + circle.set(arc1.center, arc1.radius) + lines = QadPerpPerp.circleWithEllipse(circle, ellipse2) + + if len(lines) == 4: + if arc1.isPtOnArcOnlyByAngle(lines[3].getStartPt()) == False: del(lines[3]) + if len(lines) == 3: + if arc1.isPtOnArcOnlyByAngle(lines[2].getStartPt()) == False: del(lines[2]) + if len(lines) == 2: + if arc1.isPtOnArcOnlyByAngle(lines[1].getStartPt()) == False: del(lines[1]) + if len(lines) == 1: + if arc1.isPtOnArcOnlyByAngle(lines[0].getStartPt()) == False: del(lines[0]) + + return lines + + + # =============================================================================== + # arcWithEllipseArc + # =============================================================================== + @staticmethod + def arcWithEllipseArc(arc1, ellipseArc2): + """ + La funzione ritorna le linee perpendicolari ad un arco ed un arco di ellisse + (fino a 4 linee) + """ + circle1 = QadCircle() + circle1.set(arc1.center, arc1.radius) + lines = QadPerpPerp.circleWithEllipseArc(circle1, ellipseArc2) + + if len(lines) == 4: + if arc1.isPtOnArcOnlyByAngle(lines[3].getStartPt()) == False: del(lines[3]) + if len(lines) == 3: + if arc1.isPtOnArcOnlyByAngle(lines[2].getStartPt()) == False: del(lines[2]) + if len(lines) == 2: + if arc1.isPtOnArcOnlyByAngle(lines[1].getStartPt()) == False: del(lines[1]) + if len(lines) == 1: + if arc1.isPtOnArcOnlyByAngle(lines[0].getStartPt()) == False: del(lines[0]) + + return lines + + + # =============================================================================== + # metodi per gli archi - fine + # =============================================================================== + + + # ============================================================================ + # bestTwoBasicGeomObjects + # ============================================================================ + @staticmethod + def bestTwoBasicGeomObjects(object1, perPt1, object2, perPt2): + """ + Trova la linea perpendicolare a un oggetto geometrico di base e perpendicolare ad un altro oggetto geometrico di base + (che ha i punti inziale/finale che rispettivamente sono più vicini ai punti tanPt1 e perPt2): + linea, arco, arco di ellisse, cerchio, ellisse. + perPt1 = punto di selezione geometria 1 di perpendicolarità + perPt2 = punto di selezione geometria 2 di perpendicolarità + """ + if object1.whatIs() == "LINE": + if object2.whatIs() == "LINE": + return QadPerpPerp.lineWithLine(object1, perPt1, object2, perPt2) + elif object2.whatIs() == "CIRCLE": + return QadPerpPerp.lineWithCircle(object1, object2) + elif object2.whatIs() == "ARC": + result = QadPerpPerp.lineWithArc(object1, object2) + if result is not None: + pts = QadIntersections.infinityLineWithArc(result, object2) + if len(pts) == 2: + if qad_utils.getDistance(perPt2, pts[0]) <= qad_utils.getDistance(perPt2, pts[1]): + result.setEndPt(pts[0]) + else: + result.setEndPt(pts[1]) + return result + elif object2.whatIs() == "ELLIPSE": + return getLineWithStartEndPtsClosestToPts(QadPerpPerp.lineWithEllipse(object1, object2), perPt1, perPt2) + elif object2.whatIs() == "ELLIPSE_ARC": + return getLineWithStartEndPtsClosestToPts(QadPerpPerp.lineWithEllipseArc(object1, object2), perPt1, perPt2) + + if object1.whatIs() == "CIRCLE": + if object2.whatIs() == "LINE": + result = QadPerpPerp.lineWithCircle(object2, object1) + if result is not None: result.reverse() + return result + elif object2.whatIs() == "CIRCLE": + return QadPerpPerp.twoCircles(object1, object2) + elif object2.whatIs() == "ARC": + return QadPerpPerp.circleWithArc(object1, object2) + elif object2.whatIs() == "ELLIPSE": + return getLineWithStartEndPtsClosestToPts(QadPerpPerp.circleWithEllipse(object1, object2), perPt1, perPt2) + elif object2.whatIs() == "ELLIPSE_ARC": + return getLineWithStartEndPtsClosestToPts(QadPerpPerp.circleWithEllipseArc(object1, object2), perPt1, perPt2) + + elif object1.whatIs() == "ARC": + if object2.whatIs() == "LINE": + result = QadPerpPerp.lineWithArc(object2, object1) + if result is not None: + pts = QadIntersections.infinityLineWithArc(result, object1) + if len(pts) == 2: + if qad_utils.getDistance(perPt1, pts[0]) <= qad_utils.getDistance(perPt1, pts[1]): + result.setEndPt(pts[0]) + else: + result.setEndPt(pts[1]) + + if result is not None: result.reverse() + return result + elif object2.whatIs() == "CIRCLE": + result = QadPerpPerp.lineWithCircle(object2, object1) + if result is not None: result.reverse() + return result + elif object2.whatIs() == "ARC": + return QadPerpPerp.twoArcs(object1, object2) + elif object2.whatIs() == "ELLIPSE": + return getLineWithStartEndPtsClosestToPts(QadPerpPerp.arcWithEllipse(object1, object2), perPt1, perPt2) + elif object2.whatIs() == "ELLIPSE_ARC": + return getLineWithStartEndPtsClosestToPts(QadPerpPerp.arcWithEllipseArc(object1, object2), perPt1, perPt2) + + elif object1.whatIs() == "ELLIPSE": + if object2.whatIs() == "LINE": + lines = QadPerpPerp.lineWithEllipse(object2, object1) + for line in lines: line.reverse() + return getLineWithStartEndPtsClosestToPts(lines, perPt1, perPt2) + elif object2.whatIs() == "CIRCLE": + lines = QadPerpPerp.circleWithEllipse(object2, object1) + for line in lines: line.reverse() + return getLineWithStartEndPtsClosestToPts(lines, perPt1, perPt2) + elif object2.whatIs() == "ARC": + lines = QadPerpPerp.arcWithEllipse(object2, object1) + for line in lines: line.reverse() + return getLineWithStartEndPtsClosestToPts(lines, perPt1, perPt2) + + elif object1.whatIs() == "ELLIPSE_ARC": + if object2.whatIs() == "LINE": + lines = QadPerpPerp.lineWithEllipseArc(object2, object1) + for line in lines: line.reverse() + return getLineWithStartEndPtsClosestToPts(lines, perPt1, perPt2) + elif object2.whatIs() == "CIRCLE": + QadPerpPerp.circleWithEllipseArc(object2, object1) + for line in lines: line.reverse() + return getLineWithStartEndPtsClosestToPts(lines, perPt1, perPt2) + elif object2.whatIs() == "ARC": + lines = QadPerpPerp.arcWithEllipseArc(object2, object1) + for line in lines: line.reverse() + return getLineWithStartEndPtsClosestToPts(lines, perPt1, perPt2) + + return None + + + # ============================================================================ + # bestTwoBasicGeomObjectExtensions + # ============================================================================ + @staticmethod + def bestTwoBasicGeomObjectExtensions(object1, tanPt1, object2, perPt2): + """ + Trova la linea perpendicolare all'estensione di un oggetto geometrico di base e perpendicolare ad un'estensione + di un altro oggetto geometrico di base (che ha i punti inziale/finale che rispettivamente sono + più vicini ai punti perPt1 e perPt2): + linea (diventa linea infinita), arco (diventa cerchio), arco di ellisse (diventa ellisse), cerchio, ellisse. + perPt1 = punto di selezione geometria 1 di perpendicolarità + perPt2 = punto di selezione geometria 2 di perpendicolarità + """ + if object1.whatIs() == "LINE": + if object2.whatIs() == "CIRCLE": + return QadPerpPerp.infinityLineWithCircle(object1, object2) + elif object2.whatIs() == "ARC": + circle2 = QadCircle() + circle2.set(object2.center, object2.radius) + return QadPerpPerp.infinityLineWithCircle(object1, circle2) + elif object2.whatIs() == "ELLIPSE": + return getLineWithStartEndPtsClosestToPts(QadPerpPerp.infinityLineWithEllipse(object1, object2), perPt1, perPt2) + elif object2.whatIs() == "ELLIPSE_ARC": + ellipse2 = QadEllipse() + ellipse2.set(object2.center, object2.majorAxisFinalPt, object2.axisRatio) + return getLineWithStartEndPtsClosestToPts(QadPerpPerp.infinityLineWithEllipse(object1, ellipse2), perPt1, perPt2) + + if object1.whatIs() == "CIRCLE": + if object2.whatIs() == "LINE": + result = QadPerpPerp.infinityLineWithCircle(object2, object1) + if result is not None: result.reverse() + return result + elif object2.whatIs() == "CIRCLE": + return QadPerpPerp.twoCircles(object1, object2) + elif object2.whatIs() == "ARC": + circle2 = QadCircle() + circle2.set(object2.center, object2.radius) + return QadPerpPerp.twoCircles(object1, circle2) + elif object2.whatIs() == "ELLIPSE": + return getLineWithStartEndPtsClosestToPts(QadPerpPerp.circleWithEllipse(object1, object2), perPt1, perPt2) + elif object2.whatIs() == "ELLIPSE_ARC": + ellipse2 = QadEllipse() + ellipse2.set(object2.center, object2.majorAxisFinalPt, object2.axisRatio) + return getLineWithStartEndPtsClosestToPts(QadPerpPerp.circleWithEllipse(object1, ellipse2), perPt1, perPt2) + + elif object1.whatIs() == "ARC": + circle1 = QadCircle() + circle1.set(object1.center, object1.radius) + if object2.whatIs() == "LINE": + result = QadPerpPerp.infinityLineWithCircle(object2, circle1) + if result is not None: result.reverse() + return result + elif object2.whatIs() == "CIRCLE": + return QadPerpPerp.twoCircles(circle1, object2) + elif object2.whatIs() == "ARC": + circle2 = QadCircle() + circle2.set(object2.center, object2.radius) + return QadPerpPerp.twoCircles(circle1, circle2) + elif object2.whatIs() == "ELLIPSE": + return getLineWithStartEndPtsClosestToPts(QadPerpPerp.circleWithEllipse(circle1, object2), perPt1, perPt2) + elif object2.whatIs() == "ELLIPSE_ARC": + ellipse2 = QadEllipse() + ellipse2.set(object2.center, object2.majorAxisFinalPt, object2.axisRatio) + return getLineWithStartEndPtsClosestToPts(QadPerpPerp.circleWithEllipse(circle1, ellipse2), perPt1, perPt2) + + elif object1.whatIs() == "ELLIPSE": + if object2.whatIs() == "LINE": + lines = QadPerpPerp.infinityLineWithEllipse(object2, object1) + for line in lines: line.reverse() + return getLineWithStartEndPtsClosestToPts(lines, perPt1, perPt2) + elif object2.whatIs() == "CIRCLE": + lines = QadPerpPerp.circleWithEllipse(object2, object1) + for line in lines: line.reverse() + return getLineWithStartEndPtsClosestToPts(lines, perPt1, perPt2) + elif object2.whatIs() == "ARC": + circle2 = QadCircle() + circle2.set(object2.center, object2.radius) + lines = QadPerpPerp.circleWithEllipse(circle2, object1) + for line in lines: line.reverse() + return getLineWithStartEndPtsClosestToPts(lines, perPt1, perPt2) + + elif object1.whatIs() == "ELLIPSE_ARC": + ellipse1 = QadEllipse() + ellipse1.set(object1.center, object1.majorAxisFinalPt, object1.axisRatio) + if object2.whatIs() == "LINE": + lines = QadPerpPerp.infinityLineWithEllipse(object2, ellipse1) + for line in lines: line.reverse() + return getLineWithStartEndPtsClosestToPts(lines, perPt1, perPt2) + elif object2.whatIs() == "CIRCLE": + QadPerpPerp.circleWithEllipse(object2, ellipse1) + for line in lines: line.reverse() + return getLineWithStartEndPtsClosestToPts(lines, perPt1, perPt2) + elif object2.whatIs() == "ARC": + circle2 = QadCircle() + circle2.set(object2.center, object2.radius) + lines = QadPerpPerp.circleWithEllipse(circle2, ellipse1) + for line in lines: line.reverse() + return getLineWithStartEndPtsClosestToPts(lines, perPt1, perPt2) + + return None + + +# =============================================================================== +# getLineWithStartEndPtsClosestToPts +# =============================================================================== +def getLineWithStartEndPtsClosestToPts(lines, pt1, pt2): + """ + Data ua lista di linee ritorna la linea che ha punto iniziale e finale rispettivamente + più vicini a pt1 e pt2 (la funzione usa la media delle distanze). + """ + if len(lines) == 0: + return None + + Avg = sys.float_info.max + for line in lines: + d1 = qad_utils.getDistance(line.getStartPt(), pt1) + d2 = qad_utils.getDistance(line.getEndPt(), pt2) + currAvg = (d1 + d2) / 2.0 + if currAvg < Avg: # mediamente più vicino + Avg = currAvg + result = line + + return result + + +# =============================================================================== +# getQadGeomClosestPart +# =============================================================================== +def getQadGeomClosestPart(qadGeom, pt): + """ + la funzione ritorna una lista con + ( + + + + + <"a sinistra di" se il punto é alla sinista della parte con i seguenti valori: + - < 0 = sinistra (per linea, arco o arco di ellisse) o interno (per cerchi, ellissi) + - > 0 = destra (per linea, arco o arco di ellisse) o esterno (per cerchi, ellissi) + ) + """ + geomType = qadGeom.whatIs() + if geomType == "POINT" or geomType == "LINE" or geomType == "ARC" or \ + geomType == "CIRCLE" or geomType == "ELLIPSE_ARC" or geomType == "ELLIPSE": + # la funzione ritorna una lista con () + result = QadMinDistance.fromPointToBasicGeomObject(pt, qadGeom) + dist = result[0] + minDistPoint = result[1] + + if geomType == "LINE" or geomType == "ARC" or geomType == "ELLIPSE_ARC": + # < 0 "a sinistra di" se il punto é alla sinista della parte (< 0 -> sinistra, > 0 -> destra) + leftOf = qadGeom.leftOf(pt) + elif geomType == "CIRCLE" or geomType == "ELLIPSE_ARC" or geomType == "ELLIPSE": # cerchio o ellisse + leftOf = qadGeom.whereIsPt(pt) # -1 interno, 0 sulla circonferenza, 1 esterno + else: + leftOf = None + + return (dist, minDistPoint, 0, 0, 0, leftOf) + + elif qadGeom.whatIs() == "POLYLINE": + dist = sys.float_info.max + i = 0 + while i < qadGeom.qty(): + result = getQadGeomClosestPart(qadGeom.getLinearObjectAt(i), pt) + if result[0] < dist: + dist = result[0] + minDistPoint = result[1] + partIndex = i + leftOf = result[5] + i = i + 1 + + return (dist, minDistPoint, 0, 0, partIndex, leftOf) + + elif qadGeom.whatIs() == "POLYGON": + dist = sys.float_info.max + i = 0 + while i < qadGeom.qty(): + result = getQadGeomClosestPart(qadGeom.getClosedObjectAt(i), pt) + if result[0] < dist: + dist = result[0] + minDistPoint = result[1] + partIndex = result[4] + leftOf = result[5] + subGeomIndex = i + i = i + 1 + + return (dist, minDistPoint, 0, subGeomIndex, partIndex, leftOf) + + elif qadGeom.whatIs() == "MULTI_POINT": + dist = sys.float_info.max + i = 0 + while i < qadGeom.qty(): + result = getQadGeomClosestPart(qadGeom.getPointAt(i), pt) + if result[0] < dist: + dist = result[0] + minDistPoint = result[1] + geomIndex = i + i = i + 1 + + return (dist, minDistPoint, geomIndex, 0, 0, None) + + elif qadGeom.whatIs() == "MULTI_LINEAR_OBJ": + dist = sys.float_info.max + i = 0 + while i < qadGeom.qty(): + result = getQadGeomClosestPart(qadGeom.getLinearObjectAt(i), pt) + if result[0] < dist: + dist = result[0] + minDistPoint = result[1] + geomIndex = i + partIndex = result[4] + leftOf = result[5] + i = i + 1 + + return (dist, minDistPoint, geomIndex, 0, partIndex, leftOf) + + elif qadGeom.whatIs() == "MULTI_POLYGON": + dist = sys.float_info.max + i = 0 + while i < qadGeom.qty(): + result = getQadGeomClosestPart(qadGeom.getPolygonAt(i), pt) + if result[0] < dist: + dist = result[0] + minDistPoint = result[1] + geomIndex = i + subGeomIndex = result[3] + partIndex = result[4] + leftOf = result[5] + i = i + 1 + + return (dist, minDistPoint, geomIndex, subGeomIndex, partIndex, leftOf) + + else: + return (None, None, None, None, None, None) + + +# =============================================================================== +# getQadGeomClosestVertex +# =============================================================================== +def getQadGeomClosestVertex(qadGeom, pt): + """ + la funzione ritorna una lista con + ( + + + + + + ) + """ + geomType = qadGeom.whatIs() + if geomType == "POINT": + return (qad_utils.getDistance(qadGeom, pt), qadGeom, 0, 0, 0, 0) + + elif geomType == "LINE" or geomType == "ARC" or geomType == "ELLIPSE_ARC": + startPt = qadGeom.getStartPt() + endPt = qadGeom.getEndPt() + d1 = qad_utils.getDistance(startPt, pt) + d2 = qad_utils.getDistance(endPt, pt) + if d1 < d2: + return (d1, startPt, 0, 0, 0, 0) + else: + return (d2, endPt, 0, 0, 0, 1) + + elif qadGeom.whatIs() == "POLYLINE": + dist = sys.float_info.max + i = 0 + while i < qadGeom.qty(): + result = getQadGeomClosestVertex(qadGeom.getLinearObjectAt(i), pt) + if result[0] < dist: + dist = result[0] + minDistPoint = result[1] + partIndex = i + vertexIndex = partIndex + result[5] + i = i + 1 + + return (dist, minDistPoint, 0, 0, partIndex, vertexIndex) + + elif qadGeom.whatIs() == "POLYGON": + dist = sys.float_info.max + i = 0 + while i < qadGeom.qty(): + result = getQadGeomClosestVertex(qadGeom.getClosedObjectAt(i), pt) + if result[0] < dist: + dist = result[0] + minDistPoint = result[1] + partIndex = result[4] + vertexIndex = result[5] + subGeomIndex = i + i = i + 1 + + return (dist, minDistPoint, 0, subGeomIndex, partIndex, vertexIndex) + + elif qadGeom.whatIs() == "MULTI_POINT": + dist = sys.float_info.max + i = 0 + while i < qadGeom.qty(): + result = getQadGeomClosestVertex(qadGeom.getPointAt(i), pt) + if result[0] < dist: + dist = result[0] + minDistPoint = result[1] + geomIndex = i + i = i + 1 + + return (dist, minDistPoint, geomIndex, 0, 0, 0) + + elif qadGeom.whatIs() == "MULTI_LINEAR_OBJ": + dist = sys.float_info.max + i = 0 + while i < qadGeom.qty(): + result = getQadGeomClosestVertex(qadGeom.getLinearObjectAt(i), pt) + if result[0] < dist: + dist = result[0] + minDistPoint = result[1] + geomIndex = i + partIndex = result[4] + vertexIndex = result[5] + i = i + 1 + + return (dist, minDistPoint, geomIndex, 0, partIndex, vertexIndex) + + elif qadGeom.whatIs() == "MULTI_POLYGON": + dist = sys.float_info.max + i = 0 + while i < qadGeom.qty(): + result = getQadGeomClosestVertex(qadGeom.getPolygonAt(i), pt) + if result[0] < dist: + dist = result[0] + minDistPoint = result[1] + geomIndex = i + subGeomIndex = result[3] + partIndex = result[4] + vertexIndex = result[5] + i = i + 1 + + return (dist, minDistPoint, geomIndex, subGeomIndex, partIndex, vertexIndex) + + else: + # la funzione ritorna una lista con () + result = QadMinDistance.fromPointToBasicGeomObject(pt, qadGeom) + dist = result[0] + minDistPoint = result[1] + + return (dist, minDistPoint, 0, 0, None, None) + + +# =============================================================================== +# getGeomBetween2Pts +# =============================================================================== +def getQadGeomBetween2Pts(qadGeom, startPt, endPt): + """ + Ritorna una sotto geometria che parte dal punto startPt e finisce al punto endPt seguendo il tracciato della geometria. + """ + # la funzione ritorna una lista con + # ( + # + # + # + # se geometria chiusa è tipo polyline la lista contiene anche + # + # <"a sinistra di" se il punto é alla sinista della parte (< 0 -> sinistra, > 0 -> destra) + dummy = getQadGeomClosestPart(qadGeom, startPt) + ptEnd = dummy[1] + # ritorna la sotto-geometria + g = getQadGeomAt(qadGeom, dummy[2], dummy[3]) + + geomType = g.whatIs() + if geomType == "LINE" or geomType == "ARC" or geomType == "ELLIPSE_ARC": + return g.getGeomBetween2Pts(startPt, endPt) + + elif qadGeom.whatIs() == "POLYLINE": + return g.getGeomBetween2Pts(startPt, endPt) + + elif qadGeom.whatIs() == "CIRCLE": + angle1 = qad_utils.getAngleBy2Pts(g.center, startPt) + angle2 = qad_utils.getAngleBy2Pts(g.center, endPt) + + arc1 = QadArc() + arc1.set(g.center, g.radius, angle1, angle2) + arc2 = QadArc() + arc2.set(g.center, g.radius, angle2, angle1) + + if arc1.length() < arc2.length(): + if qad_utils.ptNear(arc1.getStartPt(), startPt) == False: arc1.reversed = True + return arc1 + else: + if qad_utils.ptNear(arc2.getStartPt(), startPt) == False: arc2.reversed = True + return arc2 + + elif qadGeom.whatIs() == "ELLIPSE": + angle1 = qad_utils.getAngleBy2Pts(g.center, startPt) + angle2 = qad_utils.getAngleBy2Pts(g.center, endPt) + + arc1 = QadEllipseArc() + arc1.set(g.center, g.majorAxisFinalPt, g.axisRatio, angle1, angle2) + arc2 = QadEllipseArc() + arc2.set(g.center, g.majorAxisFinalPt, g.axisRatio, angle2, angle1) + + if arc1.length() < arc2.length(): + if qad_utils.ptNear(arc1.getStartPt(), startPt) == False: arc1.reversed = True + return arc1 + else: + if qad_utils.ptNear(arc2.getStartPt(), startPt) == False: arc2.reversed = True + return arc2 + + +# =============================================================================== +# appendPtOnTheSameTanDirectionOnly +# =============================================================================== +def appendPtOnTheSameTanDirectionOnly(line, pts, resultList): + """ + Aggiunge i punti della lista pts solo se sono nella stessa direzione della tangente della linea. + Serve, ad esempio, per aggiungere i punti di intersezione sulla estensione della prima ed ultima linea diuna polilinea. + """ + angle = line.getTanDirectionOnPt() + for pt in pts: + if qad_utils.ptNear(pt, line.pt1) or \ + qad_utils.doubleNear(angle, qad_utils.getAngleBy2Pts(line.pt1, pt)): + resultList.append(pt) + + + diff --git a/qad_getpoint.py b/qad_getpoint.py index 3cb90909..b90f526c 100644 --- a/qad_getpoint.py +++ b/qad_getpoint.py @@ -1,947 +1,1499 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - classe per gestire il map tool di richiesta di un punto - - ------------------- - begin : 2013-05-22 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -from qgis.gui import * -import math - - -import qad_utils -from qad_snapper import * -from qad_snappointsdisplaymanager import * -from qad_entity import * -from qad_variables import * -from qad_rubberband import * - - -#=============================================================================== -# QadGetPointSelectionModeEnum class. -#=============================================================================== -class QadGetPointSelectionModeEnum(): - POINT_SELECTION = 0 # selezione di un punto - ENTITY_SELECTION = 1 # selezione di una entità in modo statico (cerca l'entità solo con l'evento click) - ENTITYSET_SELECTION = 2 # selezione di un gruppo di entità - ENTITY_SELECTION_DYNAMIC = 3 # selezione di una entità in modo dinamico (cerca l'entità con l'evento click e - # con l'evento mouse move) - - -#=============================================================================== -# QadGetPointDrawModeEnum class. -#=============================================================================== -class QadGetPointDrawModeEnum(): - NONE = 0 # nessuno - ELASTIC_LINE = 1 # linea elastica dal punto __startPoint - ELASTIC_RECTANGLE = 2 # rettangolo elastico dal punto __startPoint - - -from qad_dsettings_dlg import QadDSETTINGSDialog - - -#=============================================================================== -# QadGetPoint get point class -#=============================================================================== -class QadGetPoint(QgsMapTool): - - def __init__(self, plugIn, drawMode = QadGetPointDrawModeEnum.NONE): - QgsMapTool.__init__(self, plugIn.iface.mapCanvas()) - self.iface = plugIn.iface - self.canvas = plugIn.iface.mapCanvas() - self.plugIn = plugIn - - # cursore - self.__csrRubberBand = None - - self.__QadSnapper = None - self.__QadSnapPointsDisplayManager = None - self.__oldSnapType = None - self.__oldSnapProgrDist = None - self.__geometryTypesAccordingToSnapType = (False, False, False) - self.__startPoint = None - self.tmpGeometries = [] # lista di geometria non ancora esistenti ma da contare per i punti di osnap (in map coordinates) - # opzioni per limitare l'oggetto da selezionare - self.onlyEditableLayers = False - self.checkPointLayer = True - self.checkLineLayer = True - self.checkPolygonLayer = True - self.layersToCheck = None - - self.__RubberBand = None - self.__prevGeom = None - - self.__stopTimer = True - - # setto la modalità di selezione - self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) - - self.setDrawMode(drawMode) - - self.__QadSnapper = QadSnapper() - self.__QadSnapper.setSnapPointCRS(self.canvas.mapRenderer().destinationCrs()) - self.__QadSnapper.setSnapLayers(self.canvas.layers()) - self.__QadSnapper.setProgressDistance(QadVariables.get(QadMsg.translate("Environment variables", "OSPROGRDISTANCE"))) - self.setSnapType(QadVariables.get(QadMsg.translate("Environment variables", "OSMODE"))) - - self.setOrthoMode() # setto secondo le variabili d'ambiente - self.setAutoSnap() # setto secondo le variabili d'ambiente - - # leggo la tolleranza in unità di mappa - ToleranceInMapUnits = QadVariables.get(QadMsg.translate("Environment variables", "PICKBOX")) * self.canvas.mapRenderer().mapUnitsPerPixel() - self.__QadSnapper.setDistToExcludeNea(ToleranceInMapUnits) - self.__QadSnapper.setToleranceExtParLines(ToleranceInMapUnits) - - self.__QadSnapPointsDisplayManager = QadSnapPointsDisplayManager(self.canvas) - self.__QadSnapPointsDisplayManager.setIconSize(QadVariables.get(QadMsg.translate("Environment variables", "OSSIZE"))) - self.__QadSnapPointsDisplayManager.setColor(QColor(QadVariables.get(QadMsg.translate("Environment variables", "OSCOLOR")))) - - # output - self.rightButton = False - # tasto shift - self.shiftKey = False - self.tmpShiftKey = False - # tasto ctrl - self.ctrlKey = False - self.tmpCtrlKey = False - - self.point = None # punto selezionato dal click - self.tmpPoint = None # punto selezionato dal movimento del mouse - - self.entity = QadEntity() # entità selezionata dal click - self.tmpEntity = QadEntity() # entità selezionata dal movimento del mouse - - self.snapTypeOnSelection = None # snap attivo al momento del click - - def __del__(self): - self.removeItems() - - - def removeItems(self): - if self.__csrRubberBand is not None: - self.__csrRubberBand.removeItems() # prima lo stacco dal canvas altrimenti non si rimuove perchè usato da canvas - del self.__csrRubberBand - self.__csrRubberBand = None - - if self.__RubberBand is not None: - self.canvas.scene().removeItem(self.__RubberBand) # prima lo stacco dal canvas altrimenti non si rimuove perchè usato da canvas - del self.__RubberBand - self.__RubberBand = None - - del self.__QadSnapper - self.__QadSnapper = None - - self.__QadSnapPointsDisplayManager.removeItems() # prima lo stacco dal canvas altrimenti non si rimuove perchè usato da canvas - del self.__QadSnapPointsDisplayManager - self.__QadSnapPointsDisplayManager = None - - - def setDrawMode(self, drawMode): - self.__drawMode = drawMode - if self.__RubberBand is not None: - self.__RubberBand.hide() - self.canvas.scene().removeItem(self.__RubberBand) # prima lo stacco dal canvas altrimenti non si rimuove perchè usato da canvas - del self.__RubberBand - self.__RubberBand = None - - if self.__drawMode == QadGetPointDrawModeEnum.ELASTIC_LINE: - self.refreshOrthoMode() # setto il default - self.__RubberBand = createRubberBand(self.canvas, QGis.Line) - self.__RubberBand.setLineStyle(Qt.DotLine) - elif self.__drawMode == QadGetPointDrawModeEnum.ELASTIC_RECTANGLE: - self.rectangleCrossingSelectionColor = getColorForCrossingSelectionArea() - self.rectangleWindowSelectionColor = getColorForWindowSelectionArea() - - self.__RubberBand = createRubberBand(self.canvas, QGis.Polygon, False, None, self.rectangleCrossingSelectionColor) - self.__RubberBand.setLineStyle(Qt.DotLine) - - - def getDrawMode(self): - return self.__drawMode - - - def setSelectionMode(self, selectionMode): - self.__selectionMode = selectionMode - # setto il tipo di cursore - if selectionMode == QadGetPointSelectionModeEnum.POINT_SELECTION: - self.setCursorType(QadCursorTypeEnum.CROSS) # una croce usata per selezionare un punto - elif selectionMode == QadGetPointSelectionModeEnum.ENTITY_SELECTION or \ - selectionMode == QadGetPointSelectionModeEnum.ENTITY_SELECTION_DYNAMIC: - self.entity.clear() # entità selezionata - self.setCursorType(QadCursorTypeEnum.BOX) # un quadratino usato per selezionare entità - elif selectionMode == QadGetPointSelectionModeEnum.ENTITYSET_SELECTION: - self.setCursorType(QadCursorTypeEnum.CROSS) # una croce usata per selezionare un punto - - def getSelectionMode(self): - return self.__selectionMode - - - def hidePointMapToolMarkers(self): - self.__QadSnapPointsDisplayManager.hide() - if self.__RubberBand is not None: - self.__RubberBand.hide() - - def showPointMapToolMarkers(self): - if self.__RubberBand is not None: - self.__RubberBand.show() - - def getPointMapToolMarkersCount(self): - if self.__RubberBand is None: - return 0 - else: - return self.__RubberBand.numberOfVertices() - - def clear(self): - self.hidePointMapToolMarkers() - if self.__RubberBand is not None: - self.canvas.scene().removeItem(self.__RubberBand) # prima lo stacco dal canvas altrimenti non si rimuove perchè usato da canvas - del self.__RubberBand - self.__RubberBand = None - - self.__QadSnapper.removeReferenceLines() - self.__QadSnapper.setStartPoint(None) - - self.point = None # punto selezionato dal click - self.tmpPoint = None # punto selezionato dal movimento del mouse - - self.entity.clear() # entità selezionata dal click - self.tmpEntity.clear() # entità selezionata dal movimento del mouse - - self.snapTypeOnSelection = None # snap attivo al momento del click - - self.shiftKey = False - self.tmpShiftKey = False # tasto shift premuto durante il movimento del mouse - - self.ctrlKey = False - self.tmpCtrlKey = False # tasto ctrl premuto durante il movimento del mouse - - self.rightButton = False - # opzioni per limitare l'oggetto da selezionare - self.onlyEditableLayers = False - self.checkPointLayer = True # usato solo per ENTITY_SELECTION - self.checkLineLayer = True # usato solo per ENTITY_SELECTION - self.checkPolygonLayer = True # usato solo per ENTITY_SELECTION - self.layersToCheck = None - - self.__oldSnapType = None - self.__oldSnapProgrDist = None - self.__startPoint = None - self.clearTmpGeometries() - - - #============================================================================ - # tmpGeometries - #============================================================================ - def clearTmpGeometries(self): - del self.tmpGeometries[:] # svuoto la lista - self.__QadSnapper.clearTmpGeometries() - - def setTmpGeometry(self, geom, CRS = None): - self.clearTmpGeometries() - self.appendTmpGeometry(geom, CRS) - - def appendTmpGeometry(self, geom, CRS = None): - if geom is None: - return - if CRS is not None and CRS != self.canvas.mapRenderer().destinationCrs(): - g = QgsGeometry(geom) - coordTransform = QgsCoordinateTransform(CRS, self.canvas.mapRenderer().destinationCrs()) # trasformo la geometria - g.transform(coordTransform) - self.tmpGeometries.append(g) - else: - self.tmpGeometries.append(geom) - - self.__QadSnapper.appendTmpGeometry(geom) - - - def setTmpGeometries(self, geoms, CRS = None): - self.clearTmpGeometries() - for g in geoms: - self.appendTmpGeometry(g, CRS) - - - #============================================================================ - # SnapType - #============================================================================ - def setSnapType(self, snapType = None): - if snapType is None: - self.__QadSnapper.setSnapType(QadVariables.get(QadMsg.translate("Environment variables", "OSMODE"))) - else: - self.__QadSnapper.setSnapType(snapType) - - self.__geometryTypesAccordingToSnapType = self.__QadSnapper.getGeometryTypesAccordingToSnapType() - - def getSnapType(self): - return self.__QadSnapper.getSnapType() - - def forceSnapTypeOnce(self, snapType = None, snapParams = None): - self.__oldSnapType = self.__QadSnapper.getSnapType() - self.__oldSnapProgrDist = self.__QadSnapper.getProgressDistance() - - # se si vuole impostare lo snap perpendicolare e - # non é stato impostato un punto di partenza - if snapType == QadSnapTypeEnum.PER and self.__startPoint is None: - # imposto lo snap perpendicolare differito - self.setSnapType(QadSnapTypeEnum.PER_DEF) - return - # se si vuole impostare lo snap tangente e - # non é stato impostato un punto di partenza - if snapType == QadSnapTypeEnum.TAN and self.__startPoint is None: - # imposto lo snap tangente differito - self.setSnapType(QadSnapTypeEnum.TAN_DEF) - return - - if snapParams is not None: - for param in snapParams: - if param[0] == QadSnapTypeEnum.PR: - # se si vuole impostare una distanza lo snap progressivo - self.__QadSnapper.setProgressDistance(param[1]) - - self.setSnapType(snapType) - - def refreshSnapType(self): - self.__oldSnapType = None - self.__oldSnapProgrDist = None - self.__QadSnapper.setProgressDistance(QadVariables.get(QadMsg.translate("Environment variables", "OSPROGRDISTANCE"))) - self.setSnapType(QadVariables.get(QadMsg.translate("Environment variables", "OSMODE"))) - - - #============================================================================ - # OrthoMode - #============================================================================ - def setOrthoMode(self, orthoMode = None): - if orthoMode is None: - self.__OrthoMode = QadVariables.get(QadMsg.translate("Environment variables", "ORTHOMODE")) - else: - self.__OrthoMode = orthoMode - - def getOrthoCoord(self, point): - if math.fabs(point.x() - self.__startPoint.x()) < \ - math.fabs(point.y() - self.__startPoint.y()): - return QgsPoint(self.__startPoint.x(), point.y()) - else: - return QgsPoint(point.x(), self.__startPoint.y()) - - def refreshOrthoMode(self): - self.setOrthoMode() - - - #============================================================================ - # AutoSnap - #============================================================================ - def setAutoSnap(self, autoSnap = None): - if autoSnap is None: - self.__AutoSnap = QadVariables.get(QadMsg.translate("Environment variables", "AUTOSNAP")) - self.__PolarAng = math.radians(QadVariables.get(QadMsg.translate("Environment variables", "POLARANG"))) - else: - self.__AutoSnap = autoSnap - - if (self.__AutoSnap & 8) == False: # puntamento polare non attivato - self.__PolarAng = None - - def refreshAutoSnap(self): - self.setAutoSnap() - - - #============================================================================ - # CursorType - #============================================================================ - def setCursorType(self, cursorType): - if self.__csrRubberBand is not None: - self.__csrRubberBand.removeItems() # prima lo stacco dal canvas altrimenti non si rimuove perchè usato da canvas - del self.__csrRubberBand - self.__csrRubberBand = QadCursorRubberBand(self.canvas, cursorType) - self.__cursor = QCursor(Qt.BlankCursor) - self.__cursorType = cursorType - - def getCursorType(self): - return self.__cursorType - - - #============================================================================ - # Elastic - #============================================================================ - def moveElastic(self, point): - numberOfVertices = self.__RubberBand.numberOfVertices() - if numberOfVertices > 0: - if numberOfVertices == 2: - # per un baco non ancora capito: se la linea ha solo 2 vertici e - # hanno la stessa x o y (linea orizzontale o verticale) - # la linea non viene disegnata perciò sposto un pochino la x o la y - adjustedPoint = qad_utils.getAdjustedRubberBandVertex(self.__RubberBand.getPoint(0, 0), point) - self.__RubberBand.movePoint(numberOfVertices - 1, adjustedPoint) - else: - p1 = self.__RubberBand.getPoint(0, 0) - - if point.x() > p1.x(): # se il punto è a destra di p1 (punto iniziale) - self.__RubberBand.setFillColor(self.rectangleWindowSelectionColor) - else: - self.__RubberBand.setFillColor(self.rectangleCrossingSelectionColor) - - adjustedPoint = qad_utils.getAdjustedRubberBandVertex(p1, point) - self.__RubberBand.movePoint(numberOfVertices - 3, QgsPoint(p1.x(), adjustedPoint.y())) - self.__RubberBand.movePoint(numberOfVertices - 2, adjustedPoint) - self.__RubberBand.movePoint(numberOfVertices - 1, QgsPoint(adjustedPoint.x(), p1.y())) - - - #============================================================================ - # setStartPoint - #============================================================================ - def setStartPoint(self, startPoint): - self.__startPoint = startPoint - self.__QadSnapper.setStartPoint(startPoint) - - if self.getDrawMode() == QadGetPointDrawModeEnum.ELASTIC_LINE: - # previsto uso della linea elastica - self.__RubberBand.reset(QGis.Line) - #numberOfVertices = self.__RubberBand.numberOfVertices() - #if numberOfVertices == 2: - # self.__RubberBand.removeLastPoint() - # self.__RubberBand.removeLastPoint() - self.__RubberBand.addPoint(startPoint, False) - - point = self.toMapCoordinates(self.canvas.mouseLastXY()) # posizione - # per un baco non ancora capito: se la linea ha solo 2 vertici e - # hanno la stessa x o y (linea orizzontale o verticale) - # la linea non viene disegnata perciò sposto un pochino la x o la y - point = qad_utils.getAdjustedRubberBandVertex(startPoint, point) - - self.__RubberBand.addPoint(point, True) - elif self.getDrawMode() == QadGetPointDrawModeEnum.ELASTIC_RECTANGLE: - # previsto uso del rettangolo elastico - point = self.toMapCoordinates(self.canvas.mouseLastXY()) - - self.__RubberBand.reset(QGis.Polygon) - self.__RubberBand.addPoint(startPoint, False) - - # per un baco non ancora capito: se la linea ha solo 2 vertici e - # hanno la stessa x o y (linea orizzontale o verticale) - # la linea non viene disegnata perciò sposto un pochino la x o la y - point = qad_utils.getAdjustedRubberBandVertex(startPoint, point) - - self.__RubberBand.addPoint(QgsPoint(startPoint.x(), point.y()), False) - self.__RubberBand.addPoint(point, False) - self.__RubberBand.addPoint(QgsPoint(point.x(), startPoint.y()), True) - - self.__QadSnapPointsDisplayManager.setStartPoint(startPoint) - - - def toggleReferenceLines(self, geom, point, crs): - if self.__stopTimer == False and (geom is not None) and (point is not None): - if self.__QadSnapper is not None: - self.__QadSnapper.toggleReferenceLines(geom, point, crs) - self.__QadSnapper.toggleIntExtLine(geom, point, crs) - - - def canvasMoveEvent(self, event): - self.tmpPoint = self.toMapCoordinates(event.pos()) - self.tmpEntity.clear() - - self.__csrRubberBand.moveEvent(self.tmpPoint) - - # se l'obiettivo é selezionare un'entità in modo dinamico - if self.getSelectionMode() == QadGetPointSelectionModeEnum.ENTITY_SELECTION_DYNAMIC: - result = qad_utils.getEntSel(event.pos(), self, \ - self.layersToCheck, \ - self.checkPointLayer, \ - self.checkLineLayer, \ - self.checkPolygonLayer, \ - True, self.onlyEditableLayers) - # se l'obiettivo é selezionare un punto - elif self.getSelectionMode() == QadGetPointSelectionModeEnum.POINT_SELECTION: - result = qad_utils.getEntSel(event.pos(), self, \ - None, \ - self.__geometryTypesAccordingToSnapType[0], \ - self.__geometryTypesAccordingToSnapType[1], \ - self.__geometryTypesAccordingToSnapType[2], \ - True, \ - self.onlyEditableLayers) - else: - result = None - - if result is not None: - feature = result[0] - layer = result[1] - self.tmpEntity.set(layer, feature.id()) - geometry = feature.geometry() - point = self.toLayerCoordinates(layer, event.pos()) - - # se é stata selezionata una geometria diversa da quella selezionata precedentemente - if (self.__prevGeom is None) or not self.__prevGeom.equals(geometry): - self.__prevGeom = QgsGeometry(geometry) - runToggleReferenceLines = lambda: self.toggleReferenceLines(self.__prevGeom, point, layer.crs()) - self.__stopTimer = False - QTimer.singleShot(500, runToggleReferenceLines) - - oSnapPoints = self.__QadSnapper.getSnapPoint(geometry, point, \ - layer.crs(), \ - None, \ - self.__PolarAng) - - # se l'obiettivo é selezionare un punto - elif self.getSelectionMode() == QadGetPointSelectionModeEnum.POINT_SELECTION: - # se non é stata trovato alcun oggetto allora verifico se una geometria di tmpGeometries rientra nel pickbox - tmpGeometry = qad_utils.getGeomInPickBox(event.pos(), - self, \ - self.tmpGeometries, \ - None, \ - self.__geometryTypesAccordingToSnapType[0], \ - self.__geometryTypesAccordingToSnapType[1], \ - self.__geometryTypesAccordingToSnapType[2], \ - True) - if tmpGeometry is not None: - # se é stata selezionata una geometria diversa da quella selezionata precedentemente - if (self.__prevGeom is None) or not self.__prevGeom.equals(tmpGeometry): - self.__prevGeom = QgsGeometry(tmpGeometry) - runToggleReferenceLines = lambda: self.toggleReferenceLines(self.__prevGeom, self.tmpPoint, \ - self.canvas.mapRenderer().destinationCrs()) - self.__stopTimer = False - QTimer.singleShot(500, runToggleReferenceLines) - - self.__QadSnapper.clearCacheSnapPoints() # pulisco la cache perché tmpGeometry può essere variato - oSnapPoints = self.__QadSnapper.getSnapPoint(tmpGeometry, self.tmpPoint, \ - self.canvas.mapRenderer().destinationCrs(), \ - None, \ - self.__PolarAng, - True) - else: - oSnapPoints = self.__QadSnapper.getSnapPoint(None, self.tmpPoint, \ - self.canvas.mapRenderer().destinationCrs(), \ - None, \ - self.__PolarAng) - - self.__prevGeom = None - self.__stopTimer = True - - oSnapPoint = None - - # se l'obiettivo é selezionare un punto - if self.getSelectionMode() == QadGetPointSelectionModeEnum.POINT_SELECTION: - # visualizzo il punto di snap - self.__QadSnapPointsDisplayManager.show(oSnapPoints, \ - self.__QadSnapper.getExtLines(), \ - self.__QadSnapper.getExtArcs(), \ - self.__QadSnapper.getParLines(), \ - self.__QadSnapper.getIntExtLine(), \ - self.__QadSnapper.getIntExtArc()) - - self.point = None - self.tmpPoint = None - # memorizzo il punto di snap in point (prendo il primo valido) - for item in oSnapPoints.items(): - points = item[1] - if points is not None: - self.tmpPoint = points[0] - oSnapPoint = points[0] - break - - if self.tmpPoint is None: - self.tmpPoint = self.toMapCoordinates(event.pos()) - - # tasto shift premuto durante il movimento del mouse - self.tmpShiftKey = True if event.modifiers() & Qt.ShiftModifier else False - - # tasto ctrl premuto durante il movimento del mouse - self.tmpCtrlKey = True if event.modifiers() & Qt.ControlModifier else False - - # se l'obiettivo é selezionare un punto - if self.getSelectionMode() == QadGetPointSelectionModeEnum.POINT_SELECTION: - if oSnapPoint is None: - if self.__startPoint is not None: # c'é un punto di partenza - if self.tmpShiftKey == False: # se non è premuto shift - if self.__OrthoMode == 1: # orto attivato - self.tmpPoint = self.getOrthoCoord(self.tmpPoint) - else: # se non è premuto shift devo fare il toggle di ortho - if self.__OrthoMode == 0: # se orto disattivato lo attivo temporaneamente - self.tmpPoint = self.getOrthoCoord(self.tmpPoint) - - if self.getDrawMode() != QadGetPointDrawModeEnum.NONE: - # previsto uso della linea elastica o rettangolo elastico - self.moveElastic(self.tmpPoint) - - - def canvasPressEvent(self, event): - # volevo mettere questo evento nel canvasReleaseEvent - # ma il tasto destro non genera quel tipo di evento - if event.button() == Qt.RightButton: - # Se é stato premuto il tasto CTRL (o META) - if ((event.modifiers() & Qt.ControlModifier) or (event.modifiers() & Qt.MetaModifier)): - self.displayPopupMenu(event.pos()) - else: - # self.clear() da rivedere - self.rightButton = True - elif event.button() == Qt.LeftButton: - if self.getSelectionMode() == QadGetPointSelectionModeEnum.ENTITY_SELECTION_DYNAMIC or \ - self.getSelectionMode() == QadGetPointSelectionModeEnum.ENTITY_SELECTION: - self.tmpPoint = self.toMapCoordinates(event.pos()) - result = qad_utils.getEntSel(event.pos(), self, \ - self.layersToCheck, \ - self.checkPointLayer, \ - self.checkLineLayer, \ - self.checkPolygonLayer, \ - True, self.onlyEditableLayers) - if result is not None: - feature = result[0] - layer = result[1] - self.tmpEntity.set(layer, feature.id()) - - self.__QadSnapper.removeReferenceLines() - self.__QadSnapPointsDisplayManager.hide() - - self.__setPoint(event) - - self.rightButton = False - - if self.__oldSnapType is not None: - self.setSnapType(self.__oldSnapType) # riporto il valore precedente - self.__QadSnapper.setProgressDistance(self.__oldSnapProgrDist) - - # tasto shift premuto durante il click del mouse - self.shiftKey = True if event.modifiers() & Qt.ShiftModifier else False - - # tasto ctrl premuto durante il click del mouse - self.ctrlKey = True if event.modifiers() & Qt.ControlModifier else False - - self.plugIn.QadCommands.continueCommandFromMapTool() - #self.plugIn.setStandardMapTool() - - def canvasReleaseEvent(self, event): - # se l'obiettivo é selezionare un rettangolo - if self.getDrawMode() == QadGetPointDrawModeEnum.ELASTIC_RECTANGLE: - if event.button() == Qt.LeftButton: - p1 = self.__RubberBand.getPoint(0, 0) - # se il mouse é in una posizione diversa dal punto iniziale del rettangolo - if p1 != self.toMapCoordinates(event.pos()): - self.__QadSnapper.removeReferenceLines() - self.__QadSnapPointsDisplayManager.hide() - - self.__setPoint(event) - - self.rightButton = False - - if self.__oldSnapType is not None: - self.setSnapType(self.__oldSnapType) # riporto il valore precedente - self.__QadSnapper.setProgressDistance(self.__oldSnapProgrDist) - - # tasto shift premuto durante il click del mouse - self.shiftKey = True if event.modifiers() & Qt.ShiftModifier else False - - # tasto ctrl premuto durante il click del mouse - self.ctrlKey = True if event.modifiers() & Qt.ControlModifier else False - - self.plugIn.QadCommands.continueCommandFromMapTool() - #self.plugIn.setStandardMapTool() - - def __setPoint(self, event): - # se non era mai stato mosso il mouse - if self.tmpPoint is None: - self.canvasMoveEvent(event) - - self.point = self.tmpPoint - self.plugIn.setLastPoint(self.point) - self.snapTypeOnSelection = self.getSnapType() # snap attivo al momento del click - self.entity.set(self.tmpEntity.layer, self.tmpEntity.featureId) - - def keyPressEvent(self, event): - self.plugIn.keyPressEvent(event) - - def activate(self): - if self.__csrRubberBand is not None: - # posizione corrente del mouse - self.__csrRubberBand.moveEvent(self.toMapCoordinates(self.canvas.mouseLastXY())) - self.__csrRubberBand.show() - - self.point = None - self.tmpPoint = None - - self.entity = QadEntity() # entità selezionata dal click - self.tmpEntity = QadEntity() # entità selezionata dal movimento del mouse - - self.snapTypeOnSelection = None # snap attivo al momento del click - - self.shiftKey = False - self.tmpShiftKey = False # tasto shift premuto durante il movimento del mouse - - self.ctrlKey = False - self.tmpCtrlKey = False # tasto ctrl premuto durante il movimento del mouse - - self.rightButton = False - self.canvas.setCursor(self.__cursor) - self.showPointMapToolMarkers() - - def deactivate(self): - try: # necessario perché se si chiude QGIS parte questo evento nonostante non ci sia più l'oggetto maptool ! - if self.__csrRubberBand is not None: - self.__csrRubberBand.hide() - self.hidePointMapToolMarkers() - except: - pass - - def isTransient(self): - return False - - def isEditTool(self): - return True - - - #============================================================================ - # dispalyPopupMenu - #============================================================================ - def displayPopupMenu(self, pos): - popupMenu = QMenu(self.canvas) - - msg = QadMsg.translate("DSettings_Dialog", "Start / End") - icon = QIcon(":/plugins/qad/icons/osnap_endLine.png") - if icon is None: - addEndLineSnapTypeAction = QAction(msg, popupMenu) - else: - addEndLineSnapTypeAction = QAction(icon, msg, popupMenu) - QObject.connect(addEndLineSnapTypeAction, SIGNAL("triggered()"), self.addEndLineSnapTypeByPopupMenu) - popupMenu.addAction(addEndLineSnapTypeAction) - - msg = QadMsg.translate("DSettings_Dialog", "Segment Start / End") - icon = QIcon(":/plugins/qad/icons/osnap_end.png") - if icon is None: - addEndSnapTypeAction = QAction(msg, popupMenu) - else: - addEndSnapTypeAction = QAction(icon, msg, popupMenu) - QObject.connect(addEndSnapTypeAction, SIGNAL("triggered()"), self.addEndSnapTypeByPopupMenu) - popupMenu.addAction(addEndSnapTypeAction) - - msg = QadMsg.translate("DSettings_Dialog", "Middle point") - icon = QIcon(":/plugins/qad/icons/osnap_mid.png") - if icon is None: - addMidSnapTypeAction = QAction(msg, popupMenu) - else: - addMidSnapTypeAction = QAction(icon, msg, popupMenu) - QObject.connect(addMidSnapTypeAction, SIGNAL("triggered()"), self.addMidSnapTypeByPopupMenu) - popupMenu.addAction(addMidSnapTypeAction) - - msg = QadMsg.translate("DSettings_Dialog", "Intersection") - icon = QIcon(":/plugins/qad/icons/osnap_int.png") - if icon is None: - addIntSnapTypeAction = QAction(msg, popupMenu) - else: - addIntSnapTypeAction = QAction(icon, msg, popupMenu) - QObject.connect(addIntSnapTypeAction, SIGNAL("triggered()"), self.addIntSnapTypeByPopupMenu) - popupMenu.addAction(addIntSnapTypeAction) - - msg = QadMsg.translate("DSettings_Dialog", "Intersection on extension") - icon = QIcon(":/plugins/qad/icons/osnap_extInt.png") - if icon is None: - addExtIntSnapTypeAction = QAction(msg, popupMenu) - else: - addExtIntSnapTypeAction = QAction(icon, msg, popupMenu) - QObject.connect(addExtIntSnapTypeAction, SIGNAL("triggered()"), self.addExtIntSnapTypeByPopupMenu) - popupMenu.addAction(addExtIntSnapTypeAction) - - msg = QadMsg.translate("DSettings_Dialog", "Extend") - icon = QIcon(":/plugins/qad/icons/osnap_ext.png") - if icon is None: - addExtSnapTypeAction = QAction(msg, popupMenu) - else: - addExtSnapTypeAction = QAction(icon, msg, popupMenu) - QObject.connect(addExtSnapTypeAction, SIGNAL("triggered()"), self.addExtSnapTypeByPopupMenu) - popupMenu.addAction(addExtSnapTypeAction) - - popupMenu.addSeparator() - - msg = QadMsg.translate("DSettings_Dialog", "Center") - icon = QIcon(":/plugins/qad/icons/osnap_cen.png") - if icon is None: - addCenSnapTypeAction = QAction(msg, popupMenu) - else: - addCenSnapTypeAction = QAction(icon, msg, popupMenu) - QObject.connect(addCenSnapTypeAction, SIGNAL("triggered()"), self.addCenSnapTypeByPopupMenu) - popupMenu.addAction(addCenSnapTypeAction) - - msg = QadMsg.translate("DSettings_Dialog", "Quadrant") - icon = QIcon(":/plugins/qad/icons/osnap_qua.png") - if icon is None: - addQuaSnapTypeAction = QAction(msg, popupMenu) - else: - addQuaSnapTypeAction = QAction(icon, msg, popupMenu) - QObject.connect(addQuaSnapTypeAction, SIGNAL("triggered()"), self.addQuaSnapTypeByPopupMenu) - popupMenu.addAction(addQuaSnapTypeAction) - - msg = QadMsg.translate("DSettings_Dialog", "Tangent") - icon = QIcon(":/plugins/qad/icons/osnap_tan.png") - if icon is None: - addTanSnapTypeAction = QAction(msg, popupMenu) - else: - addTanSnapTypeAction = QAction(icon, msg, popupMenu) - QObject.connect(addTanSnapTypeAction, SIGNAL("triggered()"), self.addTanSnapTypeByPopupMenu) - popupMenu.addAction(addTanSnapTypeAction) - - popupMenu.addSeparator() - - msg = QadMsg.translate("DSettings_Dialog", "Perpendicular") - icon = QIcon(":/plugins/qad/icons/osnap_per.png") - if icon is None: - addPerSnapTypeAction = QAction(msg, popupMenu) - else: - addPerSnapTypeAction = QAction(icon, msg, popupMenu) - QObject.connect(addPerSnapTypeAction, SIGNAL("triggered()"), self.addPerSnapTypeByPopupMenu) - popupMenu.addAction(addPerSnapTypeAction) - - msg = QadMsg.translate("DSettings_Dialog", "Parallel") - icon = QIcon(":/plugins/qad/icons/osnap_par.png") - if icon is None: - addParSnapTypeAction = QAction(msg, popupMenu) - else: - addParSnapTypeAction = QAction(icon, msg, popupMenu) - QObject.connect(addParSnapTypeAction, SIGNAL("triggered()"), self.addParSnapTypeByPopupMenu) - popupMenu.addAction(addParSnapTypeAction) - - msg = QadMsg.translate("DSettings_Dialog", "Node") - icon = QIcon(":/plugins/qad/icons/osnap_nod.png") - if icon is None: - addNodSnapTypeAction = QAction(msg, popupMenu) - else: - addNodSnapTypeAction = QAction(icon, msg, popupMenu) - QObject.connect(addNodSnapTypeAction, SIGNAL("triggered()"), self.addNodSnapTypeByPopupMenu) - popupMenu.addAction(addNodSnapTypeAction) - - msg = QadMsg.translate("DSettings_Dialog", "Near") - icon = QIcon(":/plugins/qad/icons/osnap_nea.png") - if icon is None: - addNeaSnapTypeAction = QAction(msg, popupMenu) - else: - addNeaSnapTypeAction = QAction(icon, msg, popupMenu) - QObject.connect(addNeaSnapTypeAction, SIGNAL("triggered()"), self.addNeaSnapTypeByPopupMenu) - popupMenu.addAction(addNeaSnapTypeAction) - - msg = QadMsg.translate("DSettings_Dialog", "Progressive") - icon = QIcon(":/plugins/qad/icons/osnap_pr.png") - if icon is None: - addPrSnapTypeAction = QAction(msg, popupMenu) - else: - addPrSnapTypeAction = QAction(icon, msg, popupMenu) - QObject.connect(addPrSnapTypeAction, SIGNAL("triggered()"), self.addPrSnapTypeByPopupMenu) - popupMenu.addAction(addPrSnapTypeAction) - - msg = QadMsg.translate("DSettings_Dialog", "None") - icon = QIcon(":/plugins/qad/icons/osnap_disable.png") - if icon is None: - setSnapTypeToDisableAction = QAction(msg, popupMenu) - else: - setSnapTypeToDisableAction = QAction(icon, msg, popupMenu) - QObject.connect(setSnapTypeToDisableAction, SIGNAL("triggered()"), self.setSnapTypeToDisableByPopupMenu) - popupMenu.addAction(setSnapTypeToDisableAction) - - popupMenu.addSeparator() - - msg = QadMsg.translate("DSettings_Dialog", "Object snap settings...") - icon = QIcon(":/plugins/qad/icons/dsettings.png") - if icon is None: - DSettingsAction = QAction(msg, popupMenu) - else: - DSettingsAction = QAction(icon, msg, popupMenu) - QObject.connect(DSettingsAction, SIGNAL("triggered()"), self.showDSettingsByPopUpMenu) - popupMenu.addAction(DSettingsAction) - - popupMenu.popup(self.canvas.mapToGlobal(pos)) - - - #============================================================================ - # addSnapTypeByPopupMenu - #============================================================================ - def addSnapTypeByPopupMenu(self, _snapType): - value = QadVariables.get(QadMsg.translate("Environment variables", "OSMODE")) - if value & QadSnapTypeEnum.DISABLE: - value = value - QadSnapTypeEnum.DISABLE - QadVariables.set(QadMsg.translate("Environment variables", "OSMODE"), value | _snapType) - QadVariables.save() - self.refreshSnapType() - - def addEndLineSnapTypeByPopupMenu(self): - self.addSnapTypeByPopupMenu(QadSnapTypeEnum.END_PLINE) - def addEndSnapTypeByPopupMenu(self): - self.addSnapTypeByPopupMenu(QadSnapTypeEnum.END) - def addMidSnapTypeByPopupMenu(self): - self.addSnapTypeByPopupMenu(QadSnapTypeEnum.MID) - def addIntSnapTypeByPopupMenu(self): - self.addSnapTypeByPopupMenu(QadSnapTypeEnum.INT) - def addExtIntSnapTypeByPopupMenu(self): - self.addSnapTypeByPopupMenu(QadSnapTypeEnum.EXT_INT) - def addExtSnapTypeByPopupMenu(self): - self.addSnapTypeByPopupMenu(QadSnapTypeEnum.EXT) - def addCenSnapTypeByPopupMenu(self): - self.addSnapTypeByPopupMenu(QadSnapTypeEnum.CEN) - def addQuaSnapTypeByPopupMenu(self): - self.addSnapTypeByPopupMenu(QadSnapTypeEnum.QUA) - def addTanSnapTypeByPopupMenu(self): - self.addSnapTypeByPopupMenu(QadSnapTypeEnum.TAN) - def addPerSnapTypeByPopupMenu(self): - self.addSnapTypeByPopupMenu(QadSnapTypeEnum.PER) - def addParSnapTypeByPopupMenu(self): - self.addSnapTypeByPopupMenu(QadSnapTypeEnum.PAR) - def addNodSnapTypeByPopupMenu(self): - self.addSnapTypeByPopupMenu(QadSnapTypeEnum.NOD) - def addNeaSnapTypeByPopupMenu(self): - self.addSnapTypeByPopupMenu(QadSnapTypeEnum.NEA) - def addPrSnapTypeByPopupMenu(self): - self.addSnapTypeByPopupMenu(QadSnapTypeEnum.PR) - - def setSnapTypeToDisableByPopupMenu(self): - value = QadVariables.get(QadMsg.translate("Environment variables", "OSMODE")) - QadVariables.set(QadMsg.translate("Environment variables", "OSMODE"), value | QadSnapTypeEnum.DISABLE) - QadVariables.save() - self.refreshSnapType() - - def showDSettingsByPopUpMenu(self): - d = QadDSETTINGSDialog(self.plugIn) - d.exec_() - self.refreshSnapType() - - - def mapToLayerCoordinates(self, layer, point_geom): - # transform point o geometry coordinates from output CRS to layer's CRS - if self.canvas is None: - return None - if type(point_geom) == QgsPoint: - return self.canvas.mapRenderer().mapToLayerCoordinates(layer, point_geom) - elif type(point_geom) == QgsGeometry: - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - coordTransform = QgsCoordinateTransform(self.canvas.mapRenderer().destinationCrs(), layer.crs()) - g = QgsGeometry(point_geom) - g.transform(coordTransform) - return g - else: - return None - - def layerToMapCoordinates(self, layer, point_geom): - # transform point o geometry coordinates from layer's CRS to output CRS - if self.canvas is None: - return None - if type(point_geom) == QgsPoint: - return self.canvas.mapRenderer().layerToMapCoordinates(layer, point_geom) - elif type(point_geom) == QgsGeometry: - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - coordTransform = QgsCoordinateTransform(layer.crs(), self.canvas.mapRenderer().destinationCrs()) - g = QgsGeometry(point_geom) - g.transform(coordTransform) - return g - else: - return None +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + classe per gestire il map tool di richiesta di un punto + + ------------------- + begin : 2013-05-22 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from qgis.PyQt.QtCore import Qt, QTimer, QEvent +from qgis.PyQt.QtGui import QColor, QCursor, QIcon, QKeyEvent +from qgis.PyQt.QtWidgets import QAction, QMenu +from qgis.core import QgsWkbTypes, QgsGeometry, QgsCoordinateTransform, QgsPointXY, QgsProject +from qgis.gui import QgsMapTool + +import math +import time # profiling +import datetime + + +from . import qad_utils +from .qad_snapper import QadSnapper, QadSnapModeEnum, QadSnapTypeEnum, snapTypeEnum2Str +from .qad_snappointsdisplaymanager import QadSnapPointsDisplayManager +from .qad_entity import QadEntity +from .qad_variables import QadVariables, QadAUTOSNAPEnum, QadPOLARMODEnum, POLARADDANG_to_list +from .qad_rubberband import createRubberBand, getColorForCrossingSelectionArea, \ + getColorForWindowSelectionArea, QadCursorTypeEnum, QadCursorRubberBand +from .qad_cacheareas import QadLayerCacheGeomsDict +from .qad_textwindow import QadInputTypeEnum +from .qad_dynamicinput import QadDynamicEditInput, QadDynamicInputContextEnum +from .qad_msg import QadMsg + + +# =============================================================================== +# QadGetPointSelectionModeEnum class. +# =============================================================================== +class QadGetPointSelectionModeEnum(): + NONE = 0 # nessuna selezione (usato quando in un comando si chiede solo la scelta di opzioni) + POINT_SELECTION = 1 # selezione di un punto + ENTITY_SELECTION = 2 # selezione di una entità in modo statico (cerca l'entità solo con l'evento click) + ENTITYSET_SELECTION = 3 # selezione di un gruppo di entità + ENTITY_SELECTION_DYNAMIC = 4 # selezione di una entità in modo dinamico (cerca l'entità con l'evento click e + # con l'evento mouse move) + + +# =============================================================================== +# QadGetPointDrawModeEnum class. +# =============================================================================== +class QadGetPointDrawModeEnum(): + NONE = 0 # nessuno + ELASTIC_LINE = 1 # linea elastica dal punto __startPoint + ELASTIC_RECTANGLE = 2 # rettangolo elastico dal punto __startPoint + + +from .qad_dsettings_dlg import QadDSETTINGSDialog, QadDSETTINGSTabIndexEnum + + +# =============================================================================== +# QadGetPoint get point class +# =============================================================================== +class QadGetPoint(QgsMapTool): + + def __init__(self, plugIn, drawMode = QadGetPointDrawModeEnum.NONE): + QgsMapTool.__init__(self, plugIn.iface.mapCanvas()) + self.iface = plugIn.iface + self.canvas = plugIn.iface.mapCanvas() + self.plugIn = plugIn + + # cursore + self.__csrRubberBand = None + + self.__QadSnapper = None + self.__QadSnapPointsDisplayManager = None + self.__oldSnapType = None + self.__oldSnapProgrDist = None + self.__geometryTypesAccordingToSnapType = (False, False, False) + self.__startPoint = None + self.tmpGeometries = [] # lista di geometria non ancora esistenti ma da contare per i punti di osnap (in map coordinates) + # opzioni per limitare l'oggetto da selezionare + self.onlyEditableLayers = False + self.checkPointLayer = True + self.checkLineLayer = True + self.checkPolygonLayer = True + self.layersToCheck = None + + self.__RubberBand = None + self.__prevGeom = None + + self.__stopTimer = True + + # ottimizzazione per la ricerca degli oggetti + # cache per selezione oggetti + self.layerCacheGeomsDict = QadLayerCacheGeomsDict(self.canvas) + self.lastLayerFound = None # layer ultimo oggetto trovato + + # setto la modalità di selezione + self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) + + self.setDrawMode(drawMode) + + self.__QadSnapper = QadSnapper() + self.__QadSnapper.setSnapMode(QadSnapModeEnum.ONE_RESULT) # Viene restituito solo il punto più vicino + # Tutti i layer vettoriali visibili secondo le impostazioni QGIS + # (solo layer corrente, un set di layer, tutti i layer) + self.setSnapLayersFromQgis() + self.canvas.snappingUtils().configChanged.connect(self.setSnapLayersFromQgis) # update snap layers whenever QGIS snap settings change + + + self.__QadSnapper.setProgressDistance(QadVariables.get(QadMsg.translate("Environment variables", "OSPROGRDISTANCE"))) + self.setSnapType(QadVariables.get(QadMsg.translate("Environment variables", "OSMODE"))) + + self.setOrthoMode() # setto secondo le variabili d'ambiente + self.setAutoSnap() # setto secondo le variabili d'ambiente + + # leggo la tolleranza in unità di mappa + ToleranceInMapUnits = QadVariables.get(QadMsg.translate("Environment variables", "PICKBOX")) * self.canvas.mapSettings().mapUnitsPerPixel() + self.__QadSnapper.setDistToExcludeNea(ToleranceInMapUnits) + self.__QadSnapper.setToleranceExtParLines(ToleranceInMapUnits) + + self.__QadSnapPointsDisplayManager = QadSnapPointsDisplayManager(self.canvas) + self.__QadSnapPointsDisplayManager.setIconSize(QadVariables.get(QadMsg.translate("Environment variables", "AUTOSNAPSIZE"))) + self.__QadSnapPointsDisplayManager.setColor(QColor(QadVariables.get(QadMsg.translate("Environment variables", "AUTOSNAPCOLOR")))) + + # output + self.rightButton = False + # tasto shift + self.shiftKey = False + self.tmpShiftKey = False + # tasto ctrl + self.ctrlKey = False + self.tmpCtrlKey = False + + self.point = None # punto selezionato dal click + self.tmpPoint = None # punto selezionato dal movimento del mouse + + self.entity = QadEntity() # entità selezionata dal click + self.tmpEntity = QadEntity() # entità selezionata dal movimento del mouse + + self.snapTypeOnSelection = None # snap attivo al momento del click + + # profiling + self.tempo_tot = 0 + self.tempo1 = 0 + self.tempo2 = 0 + + self.startDateTimeForRightClick = 0 + + # input dinamico + self.dynamicEditInput = QadDynamicEditInput(plugIn, QadDynamicInputContextEnum.NONE) + + # gestione di punto medio tra 2 punto (M2P) + self.M2P_Mode = False # se la modalità M2P è attivata o meno + self.M2p_pt1 = None # primo punto + + + def __del__(self): + self.removeItems() + self.canvas.snappingUtils().configChanged.disconnect(self.setSnapLayersFromQgis) # update snap layers whenever QGIS snap settings change + + + def removeItems(self): + if self.__csrRubberBand is not None: + self.__csrRubberBand.removeItems() # prima lo stacco dal canvas altrimenti non si rimuove perchè usato da canvas + del self.__csrRubberBand + self.__csrRubberBand = None + + if self.__RubberBand is not None: + self.canvas.scene().removeItem(self.__RubberBand) # prima lo stacco dal canvas altrimenti non si rimuove perchè usato da canvas + del self.__RubberBand + self.__RubberBand = None + + if self.__QadSnapper is not None: + del self.__QadSnapper + self.__QadSnapper = None + + if self.__QadSnapPointsDisplayManager is not None: + self.__QadSnapPointsDisplayManager.removeItems() # prima lo stacco dal canvas altrimenti non si rimuove perchè usato da canvas + del self.__QadSnapPointsDisplayManager + self.__QadSnapPointsDisplayManager = None + + if self.layerCacheGeomsDict is not None: # prima lo stacco dal canvas altrimenti non si rimuove perchè usato da canvas (eventi) + del self.layerCacheGeomsDict + self.layerCacheGeomsDict = None + + if self.dynamicEditInput is not None: + self.dynamicEditInput.removeItems() + del self.dynamicEditInput + self.dynamicEditInput = None + + + # ============================================================================ + # getDynamicInput + # ============================================================================ + def getDynamicInput(self): + return self.dynamicEditInput + + + # ============================================================================ + # setDrawMode + # ============================================================================ + def setDrawMode(self, drawMode): + self.__drawMode = drawMode + if self.__RubberBand is not None: + self.__RubberBand.hide() + self.canvas.scene().removeItem(self.__RubberBand) # prima lo stacco dal canvas altrimenti non si rimuove perchè usato da canvas + del self.__RubberBand + self.__RubberBand = None + + if self.__drawMode == QadGetPointDrawModeEnum.ELASTIC_LINE: + self.refreshOrthoMode() # setto il default + self.__RubberBand = createRubberBand(self.canvas, QgsWkbTypes.LineGeometry) + self.__RubberBand.setLineStyle(Qt.DotLine) + elif self.__drawMode == QadGetPointDrawModeEnum.ELASTIC_RECTANGLE: + self.rectangleCrossingSelectionColor = getColorForCrossingSelectionArea() + self.rectangleWindowSelectionColor = getColorForWindowSelectionArea() + + self.__RubberBand = createRubberBand(self.canvas, QgsWkbTypes.PolygonGeometry, False, None, self.rectangleCrossingSelectionColor) + self.__RubberBand.setLineStyle(Qt.DotLine) + + + # ============================================================================ + # getDrawMode + # ============================================================================ + def getDrawMode(self): + return self.__drawMode + + + # ============================================================================ + # setSelectionMode + # ============================================================================ + def setSelectionMode(self, selectionMode): + self.__selectionMode = selectionMode + # setto il tipo di cursore + if selectionMode == QadGetPointSelectionModeEnum.POINT_SELECTION: + if QadVariables.get(QadMsg.translate("Environment variables", "APBOX")) == 0: + self.setCursorType(QadCursorTypeEnum.CROSS) # una croce usata per selezionare un punto + else: + self.setCursorType(QadCursorTypeEnum.CROSS | QadCursorTypeEnum.APERTURE) # una croce + un quadratino usati per selezionare un punto + elif selectionMode == QadGetPointSelectionModeEnum.ENTITY_SELECTION or \ + selectionMode == QadGetPointSelectionModeEnum.ENTITY_SELECTION_DYNAMIC: + self.entity.clear() # entità selezionata + self.setCursorType(QadCursorTypeEnum.BOX) # un quadratino usato per selezionare entità + elif selectionMode == QadGetPointSelectionModeEnum.ENTITYSET_SELECTION: + if QadVariables.get(QadMsg.translate("Environment variables", "APBOX")) == 0: + self.setCursorType(QadCursorTypeEnum.CROSS) # una croce usata per selezionare un punto + else: + self.setCursorType(QadCursorTypeEnum.CROSS | QadCursorTypeEnum.APERTURE) # una croce + un quadratino usati per selezionare un punto + elif selectionMode == QadGetPointSelectionModeEnum.NONE: + self.setCursorType(QadCursorTypeEnum.NONE) # nessun cursore + + + # ============================================================================ + # getSelectionMode + # ============================================================================ + def getSelectionMode(self): + return self.__selectionMode + + + # ============================================================================ + # hidePointMapToolMarkers + # ============================================================================ + def hidePointMapToolMarkers(self): + if self.__QadSnapPointsDisplayManager is not None: + self.__QadSnapPointsDisplayManager.hide() + if self.__RubberBand is not None: + self.__RubberBand.hide() + + + # ============================================================================ + # showPointMapToolMarkers + # ============================================================================ + def showPointMapToolMarkers(self): + if self.__RubberBand is not None: + self.__RubberBand.show() + + + # ============================================================================ + # getPointMapToolMarkersCount + # ============================================================================ + def getPointMapToolMarkersCount(self): + if self.__RubberBand is None: + return 0 + else: + return self.__RubberBand.numberOfVertices() + + + # ============================================================================ + # clear + # ============================================================================ + def clear(self): + self.hidePointMapToolMarkers() + if self.__RubberBand is not None: + self.canvas.scene().removeItem(self.__RubberBand) # prima lo stacco dal canvas altrimenti non si rimuove perchè usato da canvas + del self.__RubberBand + self.__RubberBand = None + + self.__QadSnapper.removeReferenceLines() + self.__QadSnapper.setStartPoint(None) + + self.point = None # punto selezionato dal click + self.tmpPoint = None # punto selezionato dal movimento del mouse + + self.entity.clear() # entità selezionata dal click + self.tmpEntity.clear() # entità selezionata dal movimento del mouse + + self.snapTypeOnSelection = None # snap attivo al momento del click + + self.shiftKey = False + self.tmpShiftKey = False # tasto shift premuto durante il movimento del mouse + + self.ctrlKey = False + self.tmpCtrlKey = False # tasto ctrl premuto durante il movimento del mouse + + self.rightButton = False + # opzioni per limitare l'oggetto da selezionare + self.onlyEditableLayers = False + self.checkPointLayer = True # usato solo per ENTITY_SELECTION + self.checkLineLayer = True # usato solo per ENTITY_SELECTION + self.checkPolygonLayer = True # usato solo per ENTITY_SELECTION + self.layersToCheck = None + + self.__oldSnapType = None + self.__oldSnapProgrDist = None + self.__startPoint = None + self.clearTmpGeometries() + + + # ============================================================================ + # cache + # ============================================================================ + def updateLayerCacheOnMapCanvasExtent(self): + if self.layerCacheGeomsDict is not None: + del self.layerCacheGeomsDict + # ottimizzazione per la ricerca degli oggetti + self.layerCacheGeomsDict = QadLayerCacheGeomsDict(self.canvas) + + # se l'obiettivo é selezionare un'entità in modo dinamico + if self.getSelectionMode() == QadGetPointSelectionModeEnum.ENTITY_SELECTION_DYNAMIC: + if self.layerCacheGeomsDict.refreshOnMapCanvasExtent(self.layersToCheck, \ + self.checkPointLayer, \ + self.checkLineLayer, \ + self.checkPolygonLayer, \ + self.onlyEditableLayers) == False: + del self.layerCacheGeomsDict + self.layerCacheGeomsDict = None + + # se l'obiettivo é selezionare un punto + elif self.getSelectionMode() == QadGetPointSelectionModeEnum.POINT_SELECTION: + if self.layerCacheGeomsDict.refreshOnMapCanvasExtent(None, \ + self.__geometryTypesAccordingToSnapType[0], \ + self.__geometryTypesAccordingToSnapType[1], \ + self.__geometryTypesAccordingToSnapType[2], \ + False) == False: + del self.layerCacheGeomsDict + self.layerCacheGeomsDict = None + + + # ============================================================================ + # tmpGeometries + # ============================================================================ + def clearTmpGeometries(self): + del self.tmpGeometries[:] # svuoto la lista + self.__QadSnapper.clearTmpGeometries() + + + # ============================================================================ + # setTmpGeometry + # ============================================================================ + def setTmpGeometry(self, geom, CRS = None): + self.clearTmpGeometries() + self.appendTmpGeometry(geom, CRS) + + + # ============================================================================ + # appendTmpGeometry + # ============================================================================ + def appendTmpGeometry(self, geom, CRS = None): + if geom is None: + return + if CRS is not None and CRS != self.canvas.mapSettings().destinationCrs(): + g = QgsGeometry(geom) + coordTransform = QgsCoordinateTransform(CRS, \ + self.canvas.mapSettings().destinationCrs(), \ + QgsProject.instance()) # trasformo la geometria + g.transform(coordTransform) + self.tmpGeometries.append(g) + else: + self.tmpGeometries.append(geom) + + self.__QadSnapper.appendTmpGeometry(geom) + + + # ============================================================================ + # setTmpGeometries + # ============================================================================ + def setTmpGeometries(self, geoms, CRS = None): + self.clearTmpGeometries() + for g in geoms: + self.appendTmpGeometry(g, CRS) + + + # ============================================================================ + # SnapType + # ============================================================================ + def setSnapLayersFromQgis(self): + """ + Sets the layers to be snapped to from QGIS's settings + """ + # Tutti i layer vettoriali visibili secondo le impostazioni QGIS + # (solo layer corrente, un set di layer, tutti i layer) + if self.__QadSnapper is not None: + self.__QadSnapper.setSnapLayers(qad_utils.getSnappableVectorLayers(self.canvas)) + + + # ============================================================================ + # SnapType + # ============================================================================ + def setSnapType(self, snapType = None): + if snapType is None: + self.__QadSnapper.setSnapType(QadVariables.get(QadMsg.translate("Environment variables", "OSMODE"))) + else: + self.__QadSnapper.setSnapType(snapType) + + self.__geometryTypesAccordingToSnapType = self.__QadSnapper.getGeometryTypesAccordingToSnapType() + self.updateLayerCacheOnMapCanvasExtent() + + + # ============================================================================ + # getSnapType + # ============================================================================ + def getSnapType(self): + return self.__QadSnapper.getSnapType() + + + # ============================================================================ + # forceSnapTypeOnce + # ============================================================================ + def forceSnapTypeOnce(self, snapType = None, snapParams = None): + self.__oldSnapType = self.__QadSnapper.getSnapType() + self.__oldSnapProgrDist = self.__QadSnapper.getProgressDistance() + + # se si vuole impostare lo snap perpendicolare e + # non é stato impostato un punto di partenza + if snapType == QadSnapTypeEnum.PER and self.__startPoint is None: + # imposto lo snap perpendicolare differito + self.setSnapType(QadSnapTypeEnum.PER_DEF) + return + # se si vuole impostare lo snap tangente e + # non é stato impostato un punto di partenza + if snapType == QadSnapTypeEnum.TAN and self.__startPoint is None: + # imposto lo snap tangente differito + self.setSnapType(QadSnapTypeEnum.TAN_DEF) + return + + if snapParams is not None: + for param in snapParams: + if param[0] == QadSnapTypeEnum.PR: + # se si vuole impostare una distanza lo snap progressivo + self.__QadSnapper.setProgressDistance(param[1]) + + self.setSnapType(snapType) + + + # ============================================================================ + # forceM2P + # ============================================================================ + def forceM2P(self): + self.M2P_Mode = True + self.plugIn.showMsg("\n" + QadMsg.translate("Snap", "First point of mid: ")) + + + # ============================================================================ + # refreshSnapType + # ============================================================================ + def refreshSnapType(self): + self.__oldSnapType = None + self.__oldSnapProgrDist = None + self.__QadSnapper.setProgressDistance(QadVariables.get(QadMsg.translate("Environment variables", "OSPROGRDISTANCE"))) + self.setSnapType(QadVariables.get(QadMsg.translate("Environment variables", "OSMODE"))) + + + # ============================================================================ + # OrthoMode + # ============================================================================ + def setOrthoMode(self, orthoMode = None): + if orthoMode is None: + self.__OrthoMode = QadVariables.get(QadMsg.translate("Environment variables", "ORTHOMODE")) + else: + self.__OrthoMode = orthoMode + + + # ============================================================================ + # getOrthoCoord + # ============================================================================ + def getOrthoCoord(self, point): + if math.fabs(point.x() - self.__startPoint.x()) < \ + math.fabs(point.y() - self.__startPoint.y()): + return QgsPointXY(self.__startPoint.x(), point.y()) + else: + return QgsPointXY(point.x(), self.__startPoint.y()) + + + # ============================================================================ + # refreshOrthoMode + # ============================================================================ + def refreshOrthoMode(self): + self.setOrthoMode() + + + # ============================================================================ + # AutoSnap + # ============================================================================ + def setAutoSnap(self, autoSnap = None): + # setta le variabili: + # self.__AutoSnap, self.__PolarAng, self.__PolarMode, self.__PolarAngOffset, self.__snapMarkerSizeInMapUnits, self.__PolarAddAngles + # self.__QadSnapper viene svuotato dai punti polari se "Object Snap Tracking off" + + if autoSnap is None: + self.__AutoSnap = QadVariables.get(QadMsg.translate("Environment variables", "AUTOSNAP")) + else: + self.__AutoSnap = autoSnap + + if (self.__AutoSnap & QadAUTOSNAPEnum.POLAR_TRACKING) == False: # puntamento polare non attivato + self.__PolarAng = None + self.__PolarMode = None + self.__PolarAngOffset = None + self.__PolarAddAngles = None + else: + self.__PolarAng = math.radians(QadVariables.get(QadMsg.translate("Environment variables", "POLARANG"))) + self.__PolarMode = QadVariables.get(QadMsg.translate("Environment variables", "POLARMODE")) + self.__PolarAngOffset = self.plugIn.lastSegmentAng + if self.__PolarMode & QadPOLARMODEnum.ADDITIONAL_ANGLES: + dummy = QadVariables.get(QadMsg.translate("Environment variables", "POLARADDANG")) + self.__PolarAddAngles = POLARADDANG_to_list(dummy, True) # es. "1;2.3" genera la lista in ordine crescente convertendo in radianti + else: + self.__PolarAddAngles = None + + + if (self.__AutoSnap & QadAUTOSNAPEnum.OBJ_SNAP_TRACKING) == False: # Object Snap Tracking off + if self.__QadSnapper is not None: + self.__QadSnapper.removeOSnapPointsForPolar() + + # calcolo la dimensione dei simboli di snap in map unit + self.__snapMarkerSizeInMapUnits = QadVariables.get(QadMsg.translate("Environment variables", "AUTOSNAPSIZE")) * \ + self.canvas.mapSettings().mapUnitsPerPixel() + + + def refreshAutoSnap(self): + self.setAutoSnap() + + + # ============================================================================ + # Dynamic Input + # ============================================================================ + def refreshDynamicInput(self): + self.dynamicEditInput.refreshOnEnvVariables() + + + # ============================================================================ + # AutoSnap + # ============================================================================ + def setPolarAngOffset(self, polarAngOffset): + self.__PolarAngOffset = polarAngOffset # per gestire l'angolo relativo all'ultimo segmento + + + # ============================================================================ + # getRealPolarAng + # ============================================================================ + def getRealPolarAng(self): + # ritorna l'angolo polare che veramente deve essere usato tenendo conto delle variabili di sistema + if self.__AutoSnap is None: return None + if (self.__AutoSnap & QadAUTOSNAPEnum.POLAR_TRACKING) == False: return None # puntamento polare non attivato + + # il comportamento di QAD è uguale sia per i punti della linea che si sta disegnando che per i punti di osanp + if (self.__PolarMode & QadPOLARMODEnum.POLAR_TRACKING): # usa POLARANG + return self.__PolarAng + else: + return math.pi / 2 # 90 gradi (ortogonale) + + + # ============================================================================ + # getRealPolarAddAngles + # ============================================================================ + def getRealPolarAddAngles(self): + # ritorna la lista degli angoli polari aggiuntivi che veramente deve essere usato tenendo conto delle variabili di sistema + if self.__AutoSnap is None: return None + if (self.__AutoSnap & QadAUTOSNAPEnum.POLAR_TRACKING) == False: return None # puntamento polare non attivato + + # il comportamento di QAD è uguale sia per i punti della linea che si sta disegnando che per i punti di osanp + if (self.__PolarMode & QadPOLARMODEnum.POLAR_TRACKING): # usa POLARANG + return self.__PolarAng + else: + return math.pi / 2 # 90 gradi (ortogonale) + + + # ============================================================================ + # getRealPolarAngOffset + # ============================================================================ + def getRealPolarAngOffset(self): + # ritorna l'angolo polare di offset che veramente deve essere usato tenendo conto delle variabili di sistema + if self.__AutoSnap is None: return None + if (self.__AutoSnap & QadAUTOSNAPEnum.POLAR_TRACKING) == False: return None # puntamento polare non attivato + + if (self.__PolarMode is not None and self.__PolarMode & QadPOLARMODEnum.MEASURE_RELATIVE_ANGLE): # (relativo al coeff angolare dell'ultimo segmento) + return self.__PolarAngOffset + else: + return 0 # 0 gradi (assoluto) + + + # ============================================================================ + # setCursorType + # ============================================================================ + def setCursorType(self, cursorType): + if self.__csrRubberBand is not None: + self.__csrRubberBand.removeItems() # prima lo stacco dal canvas altrimenti non si rimuove perchè usato da canvas + del self.__csrRubberBand + self.__csrRubberBand = QadCursorRubberBand(self.canvas, cursorType) + + if cursorType == QadCursorTypeEnum.NONE: + self.__cursor = QCursor(Qt.ArrowCursor) + else: + self.__cursor = QCursor(Qt.BlankCursor) + self.__cursorType = cursorType + + + # ============================================================================ + # getCursorType + # ============================================================================ + def getCursorType(self): + return self.__cursorType + + + # ============================================================================ + # moveElastic + # ============================================================================ + def moveElastic(self, point): + numberOfVertices = self.__RubberBand.numberOfVertices() + if numberOfVertices > 0: + if numberOfVertices == 2: + # per un baco non ancora capito: se la linea ha solo 2 vertici e + # hanno la stessa x o y (linea orizzontale o verticale) + # la linea non viene disegnata perciò sposto un pochino la x o la y + adjustedPoint = qad_utils.getAdjustedRubberBandVertex(self.__RubberBand.getPoint(0, 0), point) + self.__RubberBand.movePoint(numberOfVertices - 1, adjustedPoint) + else: + p1 = self.__RubberBand.getPoint(0, 0) + + # se l'obiettivo é selezionare un gruppo di selezione + if self.getSelectionMode() == QadGetPointSelectionModeEnum.ENTITYSET_SELECTION: + if point.x() > p1.x(): # se il punto è a destra di p1 (punto iniziale) + self.__RubberBand.setFillColor(self.rectangleWindowSelectionColor) + else: + self.__RubberBand.setFillColor(self.rectangleCrossingSelectionColor) + + adjustedPoint = qad_utils.getAdjustedRubberBandVertex(p1, point) + self.__RubberBand.movePoint(numberOfVertices - 3, QgsPointXY(p1.x(), adjustedPoint.y())) + self.__RubberBand.movePoint(numberOfVertices - 2, adjustedPoint) + self.__RubberBand.movePoint(numberOfVertices - 1, QgsPointXY(adjustedPoint.x(), p1.y())) + + + # ============================================================================ + # getStartPoint + # ============================================================================ + def getStartPoint(self): + return None if self.__startPoint is None else QgsPointXY(self.__startPoint) # alloca + + + # ============================================================================ + # setStartPoint + # ============================================================================ + def setStartPoint(self, startPoint): + self.__startPoint = startPoint + self.__QadSnapper.setStartPoint(startPoint) + + if self.getDrawMode() == QadGetPointDrawModeEnum.ELASTIC_LINE: + # previsto uso della linea elastica + self.__RubberBand.reset(QgsWkbTypes.LineGeometry) + #numberOfVertices = self.__RubberBand.numberOfVertices() + #if numberOfVertices == 2: + # self.__RubberBand.removeLastPoint() + # self.__RubberBand.removeLastPoint() + self.__RubberBand.addPoint(startPoint, False) + + point = self.toMapCoordinates(self.canvas.mouseLastXY()) # posizione + # per un baco non ancora capito: se la linea ha solo 2 vertici e + # hanno la stessa x o y (linea orizzontale o verticale) + # la linea non viene disegnata perciò sposto un pochino la x o la y + point = qad_utils.getAdjustedRubberBandVertex(startPoint, point) + + self.__RubberBand.addPoint(point, True) + + # input dinamico + self.dynamicEditInput.setPrevPoint(startPoint) + if self.dynamicEditInput.isActive() and self.dynamicEditInput.isVisible: + self.dynamicEditInput.show(True, self.canvas.mouseLastXY()) # visualizzo e resetto input dinamico + elif self.getDrawMode() == QadGetPointDrawModeEnum.ELASTIC_RECTANGLE: + # previsto uso del rettangolo elastico + point = self.toMapCoordinates(self.canvas.mouseLastXY()) + + self.__RubberBand.reset(QgsWkbTypes.PolygonGeometry) + self.__RubberBand.addPoint(startPoint, False) + + # per un baco non ancora capito: se la linea ha solo 2 vertici e + # hanno la stessa x o y (linea orizzontale o verticale) + # la linea non viene disegnata perciò sposto un pochino la x o la y + point = qad_utils.getAdjustedRubberBandVertex(startPoint, point) + + self.__RubberBand.addPoint(QgsPointXY(startPoint.x(), point.y()), False) + self.__RubberBand.addPoint(point, False) + self.__RubberBand.addPoint(QgsPointXY(point.x(), startPoint.y()), True) + + # input dinamico + self.dynamicEditInput.setPrevPoint(None) + else: + #input dinamico + self.dynamicEditInput.setPrevPoint(None) + + + self.__QadSnapPointsDisplayManager.setStartPoint(startPoint) + + + # ============================================================================ + # toggleReferenceLines + # ============================================================================ + def toggleReferenceLines(self, geom, oSnapPointsForPolar = None, shiftKey = None): + if self.__stopTimer == False and (geom is not None): + if self.__QadSnapper is not None: + if self.__AutoSnap & QadAUTOSNAPEnum.OBJ_SNAP_TRACKING: # se abilitato l'utilizzo del modo i punti di snap per l'uso polare + if self.__PolarMode is not None and self.__PolarMode & QadPOLARMODEnum.SHIFT_TO_ACQUIRE: # acquisisce i punti di snap per l'uso polare solo se premuto shift + useOSnapPointsForPolar = True if shiftKey else False + else: # acquisisce i punti di snap per l'uso polare automaticamente + useOSnapPointsForPolar = True + else: # se NON abilitato l'utilizzo del modo i punti di snap per l'uso polare + useOSnapPointsForPolar = False + + # prendo la posizione attuale del mouse perchè per attivare o disattivare i punti di snap per l'uso polare + # devo essere dentro il simbolo di snap invece questa funzione viene attivata non appena sono in prossimità della geometria + # (vedi variabile di sistema APERTURE) e quindi quando il mouse può essere ancora lontano dal punto di snap + point = self.toMapCoordinates(self.canvas.mouseLastXY()) + if useOSnapPointsForPolar: + self.__QadSnapper.toggleReferenceLines(geom, point, oSnapPointsForPolar, self.__snapMarkerSizeInMapUnits) + else: + self.__QadSnapper.toggleReferenceLines(geom, point) + + self.__QadSnapper.toggleIntExtLinearObj(geom, point) + + + # ============================================================================ + # magneticCursor + # ============================================================================ + def magneticCursor(self, oSnapPoints): + if len(oSnapPoints) > 0: + for item in oSnapPoints.items(): + for pt in item[1]: + # il punto deve essere dentro il punto di snap che ha dimensioni snapMarkerSizeInMapUnits + if self.tmpPoint.x() >= pt.x() - self.__snapMarkerSizeInMapUnits and \ + self.tmpPoint.x() <= pt.x() + self.__snapMarkerSizeInMapUnits and \ + self.tmpPoint.y() >= pt.y() - self.__snapMarkerSizeInMapUnits and \ + self.tmpPoint.y() <= pt.y() + self.__snapMarkerSizeInMapUnits: + self.tmpPoint.set(pt.x(), pt.y()) + if self.__csrRubberBand is not None: + self.__csrRubberBand.moveEvent(self.tmpPoint) + + + # ============================================================================ + # canvasMoveEvent + # ============================================================================ + def canvasMoveEvent(self, event): + self.tmpPoint = self.toMapCoordinates(event.pos()) + if self.__csrRubberBand is not None: + self.__csrRubberBand.moveEvent(self.tmpPoint) + + # tasto shift premuto durante il movimento del mouse + self.tmpShiftKey = True if event.modifiers() & Qt.ShiftModifier else False + # tasto ctrl premuto durante il movimento del mouse + self.tmpCtrlKey = True if event.modifiers() & Qt.ControlModifier else False + + # se l'obiettivo é selezionare un punto + if self.getSelectionMode() == QadGetPointSelectionModeEnum.POINT_SELECTION or \ + self.getSelectionMode() == QadGetPointSelectionModeEnum.ENTITYSET_SELECTION: + return self.canvasMoveEventOnPointSel(event) + elif self.getSelectionMode() == QadGetPointSelectionModeEnum.NONE: + self.dynamicEditInput.mouseMoveEvent(event.pos()) + # se l'obiettivo é selezionare una o più entità + else: + return self.canvasMoveEventOnEntitySel(event) + + + # ============================================================================ + # canvasMoveEventOnEntitySel + # ============================================================================ + def canvasMoveEventOnEntitySel(self, event): + self.dynamicEditInput.mouseMoveEvent(event.pos()) + # start = time.time() # test + self.tmpEntity.clear() + + # start1 = time.time() # test + + # se l'obiettivo é selezionare un'entità in modo dinamico + if self.getSelectionMode() == QadGetPointSelectionModeEnum.ENTITY_SELECTION_DYNAMIC: + result = qad_utils.getEntSel(event.pos(), self, \ + QadVariables.get(QadMsg.translate("Environment variables", "PICKBOX")), \ + self.layersToCheck, \ + self.checkPointLayer, \ + self.checkLineLayer, \ + self.checkPolygonLayer, \ + True, self.onlyEditableLayers, \ + self.lastLayerFound, self.layerCacheGeomsDict) + else: + result = None + + #self.tempo1 += ((time.time() - start1) * 1000) # test + + # se è stata trovata una geometria + if result is not None: + feature = result[0] + layer = result[1] + self.lastLayerFound = layer + self.tmpEntity.set(layer, feature.id()) + + if self.getDrawMode() != QadGetPointDrawModeEnum.NONE: + # previsto uso della linea elastica o rettangolo elastico + self.moveElastic(self.tmpPoint) + + # self.tempo_tot += ((time.time() - start) * 1000) # test + + + # ============================================================================ + # canvasMoveEventOnPointSel + # ============================================================================ + def canvasMoveEventOnPointSel(self, event): + self.dynamicEditInput.mouseMoveEvent(event.pos()) + + # start = time.time() # test + result = qad_utils.getEntSel(event.pos(), self, \ + QadVariables.get(QadMsg.translate("Environment variables", "APERTURE")), \ + None, \ + self.__geometryTypesAccordingToSnapType[0], \ + self.__geometryTypesAccordingToSnapType[1], \ + self.__geometryTypesAccordingToSnapType[2], \ + True, False, \ + self.lastLayerFound, self.layerCacheGeomsDict, True) + + #self.tempo1 += ((time.time() - start1) * 1000) # test + + # se è stata trovata una geometria + if result is not None: + feature = result[0] + layer = result[1] + self.lastLayerFound = layer + if self.layerCacheGeomsDict is not None: + self.tmpEntity.set(layer, feature.attribute("index")) # leggendo la feature dalla cache in index trovo il codice della feature reale + else: + self.tmpEntity.set(layer, feature.id()) # leggendo la feature direttamente dalla classe + + geometry = self.tmpEntity.getGeometry(self.canvas.mapSettings().destinationCrs()) # trasformo la geometria in map coordinate + point = self.toMapCoordinates(event.pos()) # trasformo il punto da screen coordinate a map coordinate + + oSnapPoints = self.__QadSnapper.getSnapPoint(self.tmpEntity, point, \ + None, \ + self.getRealPolarAng(), \ + self.getRealPolarAngOffset(), \ + self.__PolarAddAngles) + + if self.__AutoSnap & QadAUTOSNAPEnum.MAGNET: # Turns on the AutoSnap magnet + self.magneticCursor(oSnapPoints) + + # se é stata selezionata una geometria diversa da quella selezionata precedentemente + if (self.__prevGeom is None) or not self.__prevGeom.equals(geometry): + self.__prevGeom = QgsGeometry(geometry) + runToggleReferenceLines = lambda: self.toggleReferenceLines(self.__prevGeom, oSnapPoints, self.tmpShiftKey) + self.__stopTimer = False + QTimer.singleShot(500, runToggleReferenceLines) + else: # se NON è stata trovata una geometria + # start1 = time.time() # test + + # se non é stata trovato alcun oggetto allora verifico se una geometria di tmpGeometries rientra nella casella aperture + boxSize = QadVariables.get(QadMsg.translate("Environment variables", "APERTURE")) # leggo la dimensione del quadrato (in pixel) + tmpGeometry = qad_utils.getGeomInBox(event.pos(), + self, \ + self.tmpGeometries, \ + boxSize, \ + None, \ + self.__geometryTypesAccordingToSnapType[0], \ + self.__geometryTypesAccordingToSnapType[1], \ + self.__geometryTypesAccordingToSnapType[2], \ + True) + + #self.tempo2 += ((time.time() - start1) * 1000) # test + + if tmpGeometry is not None: + oSnapPoints = self.__QadSnapper.getSnapPoint(tmpGeometry, self.tmpPoint, \ + None, \ + self.getRealPolarAng(), \ + self.getRealPolarAngOffset(), \ + self.__PolarAddAngles, \ + True) + + if self.__AutoSnap & QadAUTOSNAPEnum.MAGNET: # Turns on the AutoSnap magnet + self.magneticCursor(oSnapPoints) + + # se é stata selezionata una geometria diversa da quella selezionata precedentemente + if (self.__prevGeom is None) or not self.__prevGeom.equals(tmpGeometry): + self.__prevGeom = QgsGeometry(tmpGeometry) + runToggleReferenceLines = lambda: self.toggleReferenceLines(self.__prevGeom, \ + oSnapPoints, self.tmpShiftKey) + self.__stopTimer = False + QTimer.singleShot(500, runToggleReferenceLines) + else: # se NON è stata trovata una geometria temporanea (la stessa che si sta disegnando) + oSnapPoints = self.__QadSnapper.getSnapPoint(None, self.tmpPoint, \ + None, \ + self.getRealPolarAng(), \ + self.getRealPolarAngOffset(), \ + self.__PolarAddAngles) + + if self.__AutoSnap & QadAUTOSNAPEnum.MAGNET: # Turns on the AutoSnap magnet + self.magneticCursor(oSnapPoints) + + self.__prevGeom = None + self.__stopTimer = True + + oSnapPoint = None + + # visualizzo il punto di snap + self.__QadSnapPointsDisplayManager.show(oSnapPoints, \ + self.__QadSnapper.getExtLinearObjs(), \ + self.__QadSnapper.getParLines(), \ + self.__QadSnapper.getIntExtLinearObjs(), \ + self.__QadSnapper.getOSnapPointsForPolar(), \ + self.__QadSnapper.getOSnapLinesForPolar()) + + self.point = None + self.tmpPoint = None + # memorizzo il punto di snap in point (prendo il primo valido) + for item in oSnapPoints.items(): + points = item[1] + if points is not None: + self.tmpPoint = points[0] + oSnapPoint = points[0] + break + + # se non è stato trovato alcun punto di osnap + if self.tmpPoint is None: + # se si sta usando input dinamico che restituisce un risultato puntuale + if self.dynamicEditInput.isActive() and self.dynamicEditInput.isVisible and \ + (self.dynamicEditInput.inputType & QadInputTypeEnum.POINT2D or self.dynamicEditInput.inputType & QadInputTypeEnum.POINT3D) and \ + self.dynamicEditInput.refreshResult(event.pos()) == True: + self.tmpPoint = QgsPointXY(self.dynamicEditInput.resPt) + else: # prendo il punto direttamente dal mouse + self.tmpPoint = self.toMapCoordinates(event.pos()) + + if oSnapPoint is None: # se non c'è un punto di osnap + if self.__startPoint is not None: # se c'é un punto di partenza + if self.tmpShiftKey == False: # se non è premuto shift + if self.__OrthoMode == 1: # orto attivato + self.tmpPoint = self.getOrthoCoord(self.tmpPoint) + else: # se non è premuto shift devo fare il toggle di ortho + if self.__OrthoMode == 0: # se orto disattivato lo attivo temporaneamente + self.tmpPoint = self.getOrthoCoord(self.tmpPoint) + + if self.getDrawMode() != QadGetPointDrawModeEnum.NONE: + # previsto uso della linea elastica o rettangolo elastico + self.moveElastic(self.tmpPoint) + + # self.tempo_tot += ((time.time() - start) * 1000) # test + + + # ============================================================================ + # canvasPressEvent + # ============================================================================ + def canvasPressEvent(self, event): + # tasto shift premuto durante il click del mouse + self.shiftKey = True if event.modifiers() & Qt.ShiftModifier else False + + # tasto ctrl premuto durante il click del mouse + self.ctrlKey = True if event.modifiers() & Qt.ControlModifier else False + + # volevo mettere questo evento nel canvasReleaseEvent + # ma il tasto destro non genera quel tipo di evento + if event.button() == Qt.RightButton: + self.startDateTimeForRightClick = datetime.datetime.now() + self.rightButton = True + return # esco qui per non contiuare il comando dal maptool + + if event.button() == Qt.LeftButton: + self.rightButton = False + + if self.getSelectionMode() == QadGetPointSelectionModeEnum.ENTITY_SELECTION_DYNAMIC or \ + self.getSelectionMode() == QadGetPointSelectionModeEnum.ENTITY_SELECTION: + self.tmpPoint = self.toMapCoordinates(event.pos()) + result = qad_utils.getEntSel(event.pos(), self, \ + QadVariables.get(QadMsg.translate("Environment variables", "PICKBOX")), \ + self.layersToCheck, \ + self.checkPointLayer, \ + self.checkLineLayer, \ + self.checkPolygonLayer, \ + True, self.onlyEditableLayers, \ + self.lastLayerFound) # non uso self.layerCacheGeomsDict perchè se ho gli snap disattivati self.layerCacheGeomsDict è vuota + if result is not None: + feature = result[0] + layer = result[1] + self.tmpEntity.set(layer, feature.id()) + + self.__QadSnapper.removeReferenceLines() + self.__QadSnapPointsDisplayManager.hide() + + self.__setPoint(event) + + if self.__oldSnapType is not None: + self.setSnapType(self.__oldSnapType) # riporto il valore precedente + self.__QadSnapper.setProgressDistance(self.__oldSnapProgrDist) + + if self.M2P_Mode == True: # modo "punto medio tra 2 punti" + if self.M2p_pt1 is None: + self.M2p_pt1 = self.point + self.plugIn.showMsg(QadMsg.translate("Snap", "Second point of mid: ")) + else: + self.M2P_Mode = False + self.point = qad_utils.getMiddlePoint(self.M2p_pt1, self.point) + self.M2p_pt1 = None + self.plugIn.setLastPoint(self.point) + self.plugIn.QadCommands.continueCommandFromMapTool() + else: + self.plugIn.QadCommands.continueCommandFromMapTool() + #self.plugIn.setStandardMapTool() + + + # ============================================================================ + # canvasReleaseEvent + # ============================================================================ + def canvasReleaseEvent(self, event): + if event.button() == Qt.RightButton: + self.rightButton = True + # Se é stato premuto il tasto CTRL (o META) + if ((event.modifiers() & Qt.ControlModifier) or (event.modifiers() & Qt.MetaModifier)): + self.displayOsnapPopupMenu(event.pos()) + return # esco qui per non contiuare il comando dal maptool + + actualCommand = self.plugIn.QadCommands.actualCommand + if actualCommand is not None: + contextualMenu = actualCommand.getCurrentContextualMenu() + else: + contextualMenu = None + + shortCutMenu = QadVariables.get(QadMsg.translate("Environment variables", "SHORTCUTMENU")) + if shortCutMenu == 0 or contextualMenu is None: + # equivale a premere INVIO + return self.plugIn.showEvaluateMsg(None) + + # 16 = Enables the display of a shortcut menu when the right button on the pointing device is held down long enough + if shortCutMenu & 16: + now = datetime.datetime.now() + value = QadVariables.get(QadMsg.translate("Environment variables", "SHORTCUTMENUDURATION")) + shortCutMenuDuration = datetime.timedelta(0, 0, 0, value) + # se supera il numero di millisecondi impostato da SHORTCUTMENUDURATION + if now - self.startDateTimeForRightClick > shortCutMenuDuration: + contextualMenu.popup(self.canvas.mapToGlobal(event.pos())) + return # esco qui per non contiuare il comando dal maptool + else: + return self.plugIn.showEvaluateMsg(None) + else: + # 4 = Enables Command mode shortcut menus whenever a command is active. + if shortCutMenu & 4: + contextualMenu.popup(self.canvas.mapToGlobal(event.pos())) + return # esco qui per non contiuare il comando dal maptool + else: + # 8 = Enables Command mode shortcut menus only when command options are currently available at the Command prompt. + if shortCutMenu & 8 and contextualMenu is not None and len(contextualMenu.localKeyWords)>0: + contextualMenu.popup(self.canvas.mapToGlobal(event.pos())) + else: + # equivale a premere INVIO + return self.plugIn.showEvaluateMsg(None) + + # se l'obiettivo é selezionare un rettangolo + if self.getDrawMode() == QadGetPointDrawModeEnum.ELASTIC_RECTANGLE: + if event.button() == Qt.LeftButton: + p1 = self.__RubberBand.getPoint(0, 0) + # se il mouse é in una posizione diversa dal punto iniziale del rettangolo + if p1 != self.toMapCoordinates(event.pos()): + self.__QadSnapper.removeReferenceLines() + self.__QadSnapPointsDisplayManager.hide() + + self.__setPoint(event) + + self.rightButton = False + + if self.__oldSnapType is not None: + self.setSnapType(self.__oldSnapType) # riporto il valore precedente + self.__QadSnapper.setProgressDistance(self.__oldSnapProgrDist) + + # tasto shift premuto durante il click del mouse + self.shiftKey = True if event.modifiers() & Qt.ShiftModifier else False + + # tasto ctrl premuto durante il click del mouse + self.ctrlKey = True if event.modifiers() & Qt.ControlModifier else False + + self.plugIn.QadCommands.continueCommandFromMapTool() + #self.plugIn.setStandardMapTool() + + + # ============================================================================ + # __setPoint + # ============================================================================ + def __setPoint(self, event): + # se non era mai stato mosso il mouse + if self.tmpPoint is None: + self.canvasMoveEvent(event) + + self.point = self.tmpPoint + self.plugIn.setLastPoint(self.point) + self.snapTypeOnSelection = self.getSnapType() # snap attivo al momento del click + if self.tmpEntity.isInitialized(): + self.entity.set(self.tmpEntity.layer, self.tmpEntity.featureId) + else: + self.entity.clear() + + + # ============================================================================ + # keyPressEvent + # ============================================================================ + def keyPressEvent(self, e): + myEvent = e + # ALTGR non si può usare perchè è usato per indicare le coordinate +# # if Key_AltGr is pressed, then perform the as return +# if e.key() == Qt.Key_AltGr: +# myEvent = QKeyEvent(QEvent.KeyPress, Qt.Key_Return, Qt.NoModifier) +# else: +# myEvent = e + + self.plugIn.keyPressEvent(myEvent) + + + # ============================================================================ + # activate + # ============================================================================ + def activate(self): + self.canvas.setToolTip("") + + if self.__csrRubberBand is not None: + # posizione corrente del mouse + self.__csrRubberBand.moveEvent(self.toMapCoordinates(self.canvas.mouseLastXY())) + self.__csrRubberBand.show() + + self.point = None + self.tmpPoint = None + + self.entity = QadEntity() # entità selezionata dal click + self.tmpEntity = QadEntity() # entità selezionata dal movimento del mouse + + self.snapTypeOnSelection = None # snap attivo al momento del click + + self.shiftKey = False + self.tmpShiftKey = False # tasto shift premuto durante il movimento del mouse + + self.ctrlKey = False + self.tmpCtrlKey = False # tasto ctrl premuto durante il movimento del mouse + + self.rightButton = False + self.canvas.setCursor(self.__cursor) + self.showPointMapToolMarkers() + self.plugIn.disableShortcut() + + self.dynamicEditInput.show(True) + + + # ============================================================================ + # deactivate + # ============================================================================ + def deactivate(self): + try: # necessario perché se si chiude QGIS parte questo evento nonostante non ci sia più l'oggetto maptool ! + if self.__csrRubberBand is not None: + self.__csrRubberBand.hide() + self.hidePointMapToolMarkers() + self.plugIn.enableShortcut() + + self.dynamicEditInput.show(False) + except: + pass + + + # ============================================================================ + # isTransient + # ============================================================================ + def isTransient(self): # Check whether this MapTool performs a zoom or pan operation + return False + + + # ============================================================================ + # isEditTool + # ============================================================================ + def isEditTool(self): + # benchè questo tool faccia editing ritorno False perchè ogni volta che seleziono una feature + # con la funzione QgsVectorLayer::select(QgsFeatureId featureId) + # parte in sequenza la chiamata a isEditTool del tool corrente che se ritorna true viene disattivato + # e poi riattivato QadMapTool che riprende il comando interrotto creando casino + #return True # 2016 + return False + + + # ============================================================================ + # displayOsnapPopupMenu + # ============================================================================ + def displayOsnapPopupMenu(self, pos): + popupMenu = QMenu(self.canvas) + + msg = QadMsg.translate("Snap", "Midpoint between 2 points") + icon = QIcon(":/plugins/qad/icons/osnap_mid2p.svg") + if icon is None: + addEndLineSnapTypeAction = QAction(msg, popupMenu) + else: + addEndLineSnapTypeAction = QAction(icon, msg, popupMenu) + addEndLineSnapTypeAction.triggered.connect(self.addM2PActionByPopupMenu) + popupMenu.addAction(addEndLineSnapTypeAction) + + msg = QadMsg.translate("DSettings_Dialog", "Start / End") + icon = QIcon(":/plugins/qad/icons/osnap_endLine.svg") + if icon is None: + addEndLineSnapTypeAction = QAction(msg, popupMenu) + else: + addEndLineSnapTypeAction = QAction(icon, msg, popupMenu) + addEndLineSnapTypeAction.triggered.connect(self.addEndLineSnapTypeByPopupMenu) + popupMenu.addAction(addEndLineSnapTypeAction) + + msg = QadMsg.translate("DSettings_Dialog", "Segment Start / End") + icon = QIcon(":/plugins/qad/icons/osnap_end.svg") + if icon is None: + addEndSnapTypeAction = QAction(msg, popupMenu) + else: + addEndSnapTypeAction = QAction(icon, msg, popupMenu) + addEndSnapTypeAction.triggered.connect(self.addEndSnapTypeByPopupMenu) + popupMenu.addAction(addEndSnapTypeAction) + + msg = QadMsg.translate("DSettings_Dialog", "Middle point") + icon = QIcon(":/plugins/qad/icons/osnap_mid.svg") + if icon is None: + addMidSnapTypeAction = QAction(msg, popupMenu) + else: + addMidSnapTypeAction = QAction(icon, msg, popupMenu) + addMidSnapTypeAction.triggered.connect(self.addMidSnapTypeByPopupMenu) + popupMenu.addAction(addMidSnapTypeAction) + + msg = QadMsg.translate("DSettings_Dialog", "Intersection") + icon = QIcon(":/plugins/qad/icons/osnap_int.svg") + if icon is None: + addIntSnapTypeAction = QAction(msg, popupMenu) + else: + addIntSnapTypeAction = QAction(icon, msg, popupMenu) + addIntSnapTypeAction.triggered.connect(self.addIntSnapTypeByPopupMenu) + popupMenu.addAction(addIntSnapTypeAction) + + msg = QadMsg.translate("DSettings_Dialog", "Intersection on extension") + icon = QIcon(":/plugins/qad/icons/osnap_extInt.svg") + if icon is None: + addExtIntSnapTypeAction = QAction(msg, popupMenu) + else: + addExtIntSnapTypeAction = QAction(icon, msg, popupMenu) + addExtIntSnapTypeAction.triggered.connect(self.addExtIntSnapTypeByPopupMenu) + popupMenu.addAction(addExtIntSnapTypeAction) + + msg = QadMsg.translate("DSettings_Dialog", "Extend") + icon = QIcon(":/plugins/qad/icons/osnap_ext.svg") + if icon is None: + addExtSnapTypeAction = QAction(msg, popupMenu) + else: + addExtSnapTypeAction = QAction(icon, msg, popupMenu) + addExtSnapTypeAction.triggered.connect(self.addExtSnapTypeByPopupMenu) + popupMenu.addAction(addExtSnapTypeAction) + + popupMenu.addSeparator() + + msg = QadMsg.translate("DSettings_Dialog", "Center") + icon = QIcon(":/plugins/qad/icons/osnap_cen.svg") + if icon is None: + addCenSnapTypeAction = QAction(msg, popupMenu) + else: + addCenSnapTypeAction = QAction(icon, msg, popupMenu) + addCenSnapTypeAction.triggered.connect(self.addCenSnapTypeByPopupMenu) + popupMenu.addAction(addCenSnapTypeAction) + + msg = QadMsg.translate("DSettings_Dialog", "Quadrant") + icon = QIcon(":/plugins/qad/icons/osnap_qua.svg") + if icon is None: + addQuaSnapTypeAction = QAction(msg, popupMenu) + else: + addQuaSnapTypeAction = QAction(icon, msg, popupMenu) + addQuaSnapTypeAction.triggered.connect(self.addQuaSnapTypeByPopupMenu) + popupMenu.addAction(addQuaSnapTypeAction) + + msg = QadMsg.translate("DSettings_Dialog", "Tangent") + icon = QIcon(":/plugins/qad/icons/osnap_tan.svg") + if icon is None: + addTanSnapTypeAction = QAction(msg, popupMenu) + else: + addTanSnapTypeAction = QAction(icon, msg, popupMenu) + addTanSnapTypeAction.triggered.connect(self.addTanSnapTypeByPopupMenu) + popupMenu.addAction(addTanSnapTypeAction) + + popupMenu.addSeparator() + + msg = QadMsg.translate("DSettings_Dialog", "Perpendicular") + icon = QIcon(":/plugins/qad/icons/osnap_per.svg") + if icon is None: + addPerSnapTypeAction = QAction(msg, popupMenu) + else: + addPerSnapTypeAction = QAction(icon, msg, popupMenu) + addPerSnapTypeAction.triggered.connect(self.addPerSnapTypeByPopupMenu) + popupMenu.addAction(addPerSnapTypeAction) + + msg = QadMsg.translate("DSettings_Dialog", "Parallel") + icon = QIcon(":/plugins/qad/icons/osnap_par.svg") + if icon is None: + addParSnapTypeAction = QAction(msg, popupMenu) + else: + addParSnapTypeAction = QAction(icon, msg, popupMenu) + addParSnapTypeAction.triggered.connect(self.addParSnapTypeByPopupMenu) + popupMenu.addAction(addParSnapTypeAction) + + msg = QadMsg.translate("DSettings_Dialog", "Node") + icon = QIcon(":/plugins/qad/icons/osnap_nod.svg") + if icon is None: + addNodSnapTypeAction = QAction(msg, popupMenu) + else: + addNodSnapTypeAction = QAction(icon, msg, popupMenu) + addNodSnapTypeAction.triggered.connect(self.addNodSnapTypeByPopupMenu) + popupMenu.addAction(addNodSnapTypeAction) + + msg = QadMsg.translate("DSettings_Dialog", "Near") + icon = QIcon(":/plugins/qad/icons/osnap_nea.svg") + if icon is None: + addNeaSnapTypeAction = QAction(msg, popupMenu) + else: + addNeaSnapTypeAction = QAction(icon, msg, popupMenu) + addNeaSnapTypeAction.triggered.connect(self.addNeaSnapTypeByPopupMenu) + popupMenu.addAction(addNeaSnapTypeAction) + + msg = QadMsg.translate("DSettings_Dialog", "Progressive") + icon = QIcon(":/plugins/qad/icons/osnap_pr.svg") + if icon is None: + addPrSnapTypeAction = QAction(msg, popupMenu) + else: + addPrSnapTypeAction = QAction(icon, msg, popupMenu) + addPrSnapTypeAction.triggered.connect(self.addPrSnapTypeByPopupMenu) + popupMenu.addAction(addPrSnapTypeAction) + + msg = QadMsg.translate("DSettings_Dialog", "None") + icon = QIcon(":/plugins/qad/icons/osnap_disable.svg") + if icon is None: + setSnapTypeToDisableAction = QAction(msg, popupMenu) + else: + setSnapTypeToDisableAction = QAction(icon, msg, popupMenu) + setSnapTypeToDisableAction.triggered.connect(self.setSnapTypeToDisableByPopupMenu) + popupMenu.addAction(setSnapTypeToDisableAction) + + popupMenu.addSeparator() + + msg = QadMsg.translate("DSettings_Dialog", "Object snap settings...") + icon = QIcon(":/plugins/qad/icons/dsettings.svg") + if icon is None: + DSettingsAction = QAction(msg, popupMenu) + else: + DSettingsAction = QAction(icon, msg, popupMenu) + DSettingsAction.triggered.connect(self.showDSettingsByPopUpMenu) + popupMenu.addAction(DSettingsAction) + + popupMenu.popup(self.canvas.mapToGlobal(pos)) + + + # ============================================================================ + # addSnapTypeByPopupMenu + # ============================================================================ + def addSnapTypeByPopupMenu(self, _snapType): + # la funzione deve impostare lo snap ad oggetto solo temporaneamente + str = snapTypeEnum2Str(_snapType) + self.plugIn.showEvaluateMsg(str) + return +# value = QadVariables.get(QadMsg.translate("Environment variables", "OSMODE")) +# if value & QadSnapTypeEnum.DISABLE: +# value = value - QadSnapTypeEnum.DISABLE +# QadVariables.set(QadMsg.translate("Environment variables", "OSMODE"), value | _snapType) +# QadVariables.save() +# self.refreshSnapType() + + + def addM2PActionByPopupMenu(self): + self.plugIn.showEvaluateMsg("_M2P") + def addEndLineSnapTypeByPopupMenu(self): + self.addSnapTypeByPopupMenu(QadSnapTypeEnum.END_PLINE) + def addEndSnapTypeByPopupMenu(self): + self.addSnapTypeByPopupMenu(QadSnapTypeEnum.END) + def addMidSnapTypeByPopupMenu(self): + self.addSnapTypeByPopupMenu(QadSnapTypeEnum.MID) + def addIntSnapTypeByPopupMenu(self): + self.addSnapTypeByPopupMenu(QadSnapTypeEnum.INT) + def addExtIntSnapTypeByPopupMenu(self): + self.addSnapTypeByPopupMenu(QadSnapTypeEnum.EXT_INT) + def addExtSnapTypeByPopupMenu(self): + self.addSnapTypeByPopupMenu(QadSnapTypeEnum.EXT) + def addCenSnapTypeByPopupMenu(self): + self.addSnapTypeByPopupMenu(QadSnapTypeEnum.CEN) + def addQuaSnapTypeByPopupMenu(self): + self.addSnapTypeByPopupMenu(QadSnapTypeEnum.QUA) + def addTanSnapTypeByPopupMenu(self): + self.addSnapTypeByPopupMenu(QadSnapTypeEnum.TAN) + def addPerSnapTypeByPopupMenu(self): + self.addSnapTypeByPopupMenu(QadSnapTypeEnum.PER) + def addParSnapTypeByPopupMenu(self): + self.addSnapTypeByPopupMenu(QadSnapTypeEnum.PAR) + def addNodSnapTypeByPopupMenu(self): + self.addSnapTypeByPopupMenu(QadSnapTypeEnum.NOD) + def addNeaSnapTypeByPopupMenu(self): + self.addSnapTypeByPopupMenu(QadSnapTypeEnum.NEA) + def addPrSnapTypeByPopupMenu(self): + self.addSnapTypeByPopupMenu(QadSnapTypeEnum.PR) + + + # ============================================================================ + # setSnapTypeToDisableByPopupMenu + # ============================================================================ + def setSnapTypeToDisableByPopupMenu(self): + value = QadVariables.get(QadMsg.translate("Environment variables", "OSMODE")) + QadVariables.set(QadMsg.translate("Environment variables", "OSMODE"), value | QadSnapTypeEnum.DISABLE) + QadVariables.save() + self.refreshSnapType() + + + # ============================================================================ + # showDSettingsByPopUpMenu + # ============================================================================ + def showDSettingsByPopUpMenu(self): + d = QadDSETTINGSDialog(self.plugIn, QadDSETTINGSTabIndexEnum.OBJECT_SNAP) + d.exec_() + self.refreshSnapType() + self.refreshAutoSnap() + self.setPolarAngOffset(self.plugIn.lastSegmentAng) + self.refreshDynamicInput() + + + # ============================================================================ + # mapToLayerCoordinates + # ============================================================================ + def mapToLayerCoordinates(self, layer, point_geom): + # transform point o geometry coordinates from output CRS to layer's CRS + if self.canvas is None: + return None + if type(point_geom) == QgsPointXY: + return self.canvas.mapSettings().mapToLayerCoordinates(layer, point_geom) + elif type(point_geom) == QgsGeometry: + fromCrs = self.canvas.mapSettings().destinationCrs() + toCrs = layer.crs() + + if fromCrs == toCrs: + return QgsGeometry(point_geom) + + # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy + coordTransform = QgsCoordinateTransform(self.canvas.mapSettings().destinationCrs(), \ + layer.crs(), \ + QgsProject.instance()) + g = QgsGeometry(point_geom) + g.transform(coordTransform) + return g + else: + return None + + + # ============================================================================ + # layerToMapCoordinates + # ============================================================================ + def layerToMapCoordinates(self, layer, point_geom): + # transform point o geometry coordinates from layer's CRS to output CRS + if self.canvas is None: + return None + if type(point_geom) == QgsPointXY: + return self.canvas.mapSettings().layerToMapCoordinates(layer, point_geom) + elif type(point_geom) == QgsGeometry: + # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy + coordTransform = QgsCoordinateTransform(layer.crs(), \ + self.canvas.mapSettings().destinationCrs(), \ + QgsProject.instance()) + g = QgsGeometry(point_geom) + g.transform(coordTransform) + return g + else: + return None diff --git a/qad_grip.py b/qad_grip.py index 4cd38f4a..71f90a9e 100644 --- a/qad_grip.py +++ b/qad_grip.py @@ -1,664 +1,839 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - classe per gestire i grip - - ------------------- - begin : 2015-09-29 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -from qgis.gui import * - - -from qad_arc import * -from qad_circle import * -from qad_variables import * -from qad_entity import * -from qad_dim import * - - -#=============================================================================== -# QadGripStatusEnum class. -#=============================================================================== -class QadGripStatusEnum(): - NONE = 0 # nessuno - UNSELECTED = 1 # grip non selezionato - SELECTED = 2 # grip selezionato - HOVER = 3 # grip non selezionati quando il cursore si ferma su di essi - - -#=============================================================================== -# QadGripIconTypeEnum class. -#=============================================================================== -class QadGripIconTypeEnum(): - NONE = 0 # nessuno - BOX = 1 # un quadrato - CIRCLE = 2 # cerchio - RECTANGLE = 3 # rettangolo - - -#=============================================================================== -# QadGripMarker class. -#=============================================================================== -class QadGripMarker(QgsMapCanvasItem): - """ - Classe che gestisce i marcatori dei grip - """ - - - #============================================================================ - # __init__ - #============================================================================ - def __init__(self, mapCanvas): - QgsMapCanvasItem.__init__(self, mapCanvas) - self.canvas = mapCanvas - self.iconType = QadGripIconTypeEnum.BOX # icon to be shown - self.iconSize = QadVariables.get(QadMsg.translate("Environment variables", "GRIPSIZE")) - self.borderColor = QadVariables.get(QadMsg.translate("Environment variables", "GRIPCONTOUR")) # color of the border - self.center = QgsPoint(0, 0) # coordinates of the point in the center - self.setGrip(QadGripStatusEnum.UNSELECTED, QadGripIconTypeEnum.BOX) - - - def __del__(self): - self.removeItem() - - - def removeItem(self): - self.canvas.scene().removeItem(self) - - - def setCenter(self, point): - # point è in map coordinates - self.center = point - pt = self.toCanvasCoordinates(self.center) - self.setPos(pt) - - - def setGrip(self, status, iconType, rot = None): - # rot in radians counterclockwise (0 = horizontal) - if status == QadGripStatusEnum.UNSELECTED: - self.fillColor = QadVariables.get(QadMsg.translate("Environment variables", "GRIPCOLOR")) - elif status == QadGripStatusEnum.SELECTED: - self.fillColor = QadVariables.get(QadMsg.translate("Environment variables", "GRIPHOT")) - elif status == QadGripStatusEnum.HOVER: - self.fillColor = QadVariables.get(QadMsg.translate("Environment variables", "GRIPHOVER")) - - self.status = status - self.__iconType = iconType - if rot is not None: - self.__rot = -qad_utils.toDegrees(rot) # trasformo in gradi in senso orario - - - def paint(self, painter, option, widget): - """ - painter é un QPainter - """ - - s = self.iconSize / 2 - - pen = QPen(QColor(self.borderColor)) - pen.setWidth(1) - painter.setPen(pen) - painter.rotate(self.__rot) - - if self.__iconType == QadGripIconTypeEnum.NONE: - pass - elif self.__iconType == QadGripIconTypeEnum.BOX: - # un quadrato - painter.fillRect(-s, -s, self.iconSize, self.iconSize, QBrush(QColor(self.fillColor))); - painter.drawRect(-s, -s, self.iconSize, self.iconSize) - elif self.__iconType == QadGripIconTypeEnum.CIRCLE: - # cerchio - painter.setBrush(QBrush(QColor(self.fillColor))) - painter.drawEllipse(QPointF(0, 0), s, s) - elif self.__iconType == QadGripIconTypeEnum.RECTANGLE: - # un rettangolo - painter.fillRect(-s, -s / 2, self.iconSize, self.iconSize / 2, QBrush(QColor(self.fillColor))); - painter.drawRect(-s, -s / 2, self.iconSize, self.iconSize / 2) - - - def boundingRect(self): - if self.__rot != 0: - width = qad_utils.getDistance(QgsPoint(0,0), QgsPoint(self.iconSize, self.iconSize)) - height = width - else: - width = self.iconSize - height = self.iconSize - - return QRectF(-width/2, -height/2, width, height) - - - def updatePosition(self): - self.setCenter(self.center) - - -#=============================================================================== -# QadGripPointTypeEnum class. -#=============================================================================== -class QadGripPointTypeEnum(): - NONE = 0 # nessuno - VERTEX = 1 # vertice di una geometria - LINE_MID_POINT = 2 # punto medio di un segmento - CENTER = 3 # centro di un cerchio o di un arco - QUA_POINT = 4 # punto quadrante - ARC_MID_POINT = 5 # punto medio di un arco - END_VERTEX = 6 # vertice iniziale e finale di una geometria - - -#=============================================================================== -# QadEntityGripPoint class. -#=============================================================================== -class QadEntityGripPoint(): - """ - Classe che gestisce un punto di grip per una entità - """ - - - #============================================================================ - # __init__ - #============================================================================ - def __init__(self, mapCanvas, point, type, atGeom = 0, atSubGeom = 0, nVertex = 0, rot = 0.0): - self.atGeom = atGeom # numero di geometria (0-index) - self.atSubGeom = atSubGeom # numero di sotto-geometria (0-index) - self.nVertex = nVertex # numero di vertice della QadLinearObjectList della geometria e sotto-geometria (0-index) - - self.gripType = type - - self.gripMarker = QadGripMarker(mapCanvas) - self.gripMarker.setGrip(QadGripStatusEnum.UNSELECTED, self.gripType2IconType(self.gripType), rot) - self.gripMarker.setCenter(point) - - def __del__(self): - self.removeItem() - del self.gripMarker - - def removeItem(self): - self.gripMarker.removeItem() - - def getPoint(self): - return self.gripMarker.center - - def isIntersecting(self, point): - # point è in map coordinate - ToleranceInMapUnits = self.gripMarker.iconSize * self.gripMarker.canvas.mapRenderer().mapUnitsPerPixel() - if point.x() >= self.getPoint().x() - ToleranceInMapUnits and \ - point.x() <= self.getPoint().x() + ToleranceInMapUnits and \ - point.y() >= self.getPoint().y() - ToleranceInMapUnits and \ - point.y() <= self.getPoint().y() + ToleranceInMapUnits: - return True - else: - return False - - def select(self): # seleziona un grip - self.gripMarker.setGrip(QadGripStatusEnum.SELECTED, self.gripType2IconType(self.gripType)) - self.gripMarker.show() - - def unselect(self): # deseleziona un grip - self.gripMarker.setGrip(QadGripStatusEnum.UNSELECTED, self.gripType2IconType(self.gripType)) - self.gripMarker.show() - - def hover(self): # grip non selezionato quando il cursore si ferma su di esso - if self.getStatus() == QadGripStatusEnum.UNSELECTED: - self.gripMarker.setGrip(QadGripStatusEnum.HOVER, self.gripType2IconType(self.gripType)) - self.gripMarker.show() - - def getStatus(self): - return self.gripMarker.status - - def gripType2IconType(self, gripType): - if gripType == QadGripPointTypeEnum.VERTEX or gripType == QadGripPointTypeEnum.END_VERTEX: - return QadGripIconTypeEnum.BOX - elif gripType == QadGripPointTypeEnum.LINE_MID_POINT or gripType == QadGripPointTypeEnum.ARC_MID_POINT: - return QadGripIconTypeEnum.RECTANGLE - elif gripType == QadGripPointTypeEnum.CENTER: - return QadGripIconTypeEnum.CIRCLE - elif gripType == QadGripPointTypeEnum.QUA_POINT: - return QadGripIconTypeEnum.BOX - else: - return None - - -#=============================================================================== -# QadEntityGripPoints class. -#=============================================================================== -class QadEntityGripPoints(QgsMapCanvasItem): - """ - Classe che gestisce i punti di grip per una entità - """ - - - #============================================================================ - # __init__ - #============================================================================ - def __init__(self, plugIn, entity = None, grips = 2): - self.plugIn = plugIn - self.mapCanvas = plugIn.canvas - self.gripPoints = [] # lista dei punti di grip in map coordinate - if entity is not None: - self.entity = QadEntity(entity) - self.gripPoints = self.initGripPoints(grips) - - - def __del__(self): - self.removeItems() - - - def set(self, layer, featureId, grips = 2): - self.entity = QadEntity() - self.entity.set(layer, featureId) - self.gripPoints = self.initGripPoints(grips) - - - def removeItems(self): - for gripPoint in self.gripPoints: - gripPoint.removeItem() - del self.gripPoints[:] - - - def selectIntersectingGripPoints(self, point): - # seleziona i grip che intersecano un punto in map coordinate - res = 0 - for gripPoint in self.gripPoints: - if gripPoint.isIntersecting(point): - gripPoint.select() - res = res + 1 - return res - - - def unselectIntersectingGripPoints(self, point): - # deseleziona i grip che intersecano un punto in map coordinate - res = 0 - for gripPoint in self.gripPoints: - if gripPoint.isIntersecting(point): - gripPoint.unselect() - res = res + 1 - return res - - - def toggleSelectIntersectingGripPoints(self, point): - # seleziona i grip deselezionati e deseleziona i grip selezionati - # che intersecano un punto in map coordinate - for gripPoint in self.gripPoints: - if gripPoint.isIntersecting(point): - if gripPoint.getStatus() == QadGripStatusEnum.SELECTED: - gripPoint.unselect() - else: - gripPoint.select() - - - def hoverIntersectingGripPoints(self, point): - # seleziono in modo hover i grip che intersecano un punto (in map coordinate) - # non selezionati quando il cursore si ferma su di esso - res = 0 - for gripPoint in self.gripPoints: - if gripPoint.isIntersecting(point): - gripPoint.hover() - res = res + 1 - else: - status = gripPoint.getStatus() - if status == QadGripStatusEnum.SELECTED: - gripPoint.select() - else: - gripPoint.unselect() - return res - - - def isIntersecting(self, point): - # ritorna il primo punto di grip che interseca point (in map coordinate) - for gripPoint in self.gripPoints: - if gripPoint.isIntersecting(point): - return gripPoint - return None - - - def getSelectedGripPoints(self): - # restituisce una lista di punti in cui i grip sono selezionati - result = [] - - for gripPoint in self.gripPoints: - if gripPoint.getStatus() == QadGripStatusEnum.SELECTED: - result.append(gripPoint) - - return result - - - def initGripPoints(self, grips = 2): - # restituisce una lista di QadEntityGripPoint - atGeom = 0 - atSubGeom = 0 - result = [] - - g = self.entity.getGeometry() - if g is None: - return result - - # verifico se l'entità appartiene ad uno stile di quotatura - dimEntity = QadDimStyles.getDimEntity(self.entity) - if dimEntity is not None: - return self.getGripPointsFromDimComponent(dimEntity, self.entity) - - wkbType = g.wkbType() - if wkbType == QGis.WKBPoint: - # converto il punto dal layer coordinate in map coordinates - pt = self.mapCanvas.mapSettings().layerToMapCoordinates(self.entity.layer, g.asPoint()) - gp = QadEntityGripPoint(self.mapCanvas, pt, QadGripPointTypeEnum.VERTEX) - result.append(gp) - - elif wkbType == QGis.WKBMultiPoint: - pointList = g.asMultiPoint() # vettore di punti - atGeom = 0 - for point in pointList: - # converto il punto dal layer coordinate in map coordinates - pt = self.mapCanvas.mapSettings().layerToMapCoordinates(self.entity.layer, point) - gp = QadEntityGripPoint(self.mapCanvas, pt, QadGripPointTypeEnum.VERTEX, atGeom) - atGeom = atGeom + 1 - result.append(gp) - - elif wkbType == QGis.WKBLineString: - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - coordTransform = QgsCoordinateTransform(self.entity.layer.crs(), self.mapCanvas.mapRenderer().destinationCrs()) - g.transform(coordTransform) - result = self.getGripPointsFromPolyline(g.asPolyline(), 0, 0, grips) - - elif wkbType == QGis.WKBMultiLineString: - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - coordTransform = QgsCoordinateTransform(self.entity.layer.crs(), self.mapCanvas.mapRenderer().destinationCrs()) - g.transform(coordTransform) - - lineList = g.asMultiPolyline() # vettore di linee - atGeom = 0 - for line in lineList: - result.extend(self.getGripPointsFromPolyline(line, atGeom, 0, grips)) - atGeom = atGeom + 1 - - elif wkbType == QGis.WKBPolygon: - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - coordTransform = QgsCoordinateTransform(self.entity.layer.crs(), self.mapCanvas.mapRenderer().destinationCrs()) - g.transform(coordTransform) - - lineList = g.asPolygon() # vettore di linee - atGeom = 0 - for line in lineList: - result.extend(self.getGripPointsFromPolyline(line, atGeom, 0, grips)) - atGeom = atGeom + 1 - # aggiungo il centroide - pt = QgsGeometry().fromPolygon([line]).centroid().asPoint() - gp = QadEntityGripPoint(self.mapCanvas, pt, QadGripPointTypeEnum.CENTER, atGeom, 0) - result.append(gp) - - elif wkbType == QGis.WKBMultiPolygon: - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - coordTransform = QgsCoordinateTransform(self.entity.layer.crs(), self.mapCanvas.mapRenderer().destinationCrs()) - g.transform(coordTransform) - - polygonList = g.asMultiPolygon() # vettore di poligoni - atGeom = 0 - for polygon in polygonList: - atSubGeom = 0 - result1 = [] - for line in polygon: - result.extend(self.getGripPointsFromPolyline(line, atGeom, atSubGeom, grips)) - # aggiungo il centroide - pt = QgsGeometry.fromPolygon([line]).centroid().asPoint() - gp = QadEntityGripPoint(self.mapCanvas, pt, QadGripPointTypeEnum.CENTER, atGeom, atSubGeom) - result.append(gp) - - atSubGeom = atSubGeom + 1 - atGeom = atGeom + 1 - - return result - - - def getGripPointsFromPolyline(self, pointList, atGeom = 0, atSubGeom = 0, grips = 2): - arc = QadArc() - startEndVertices = arc.fromPolyline(pointList, 0) - # se la polilinea è composta solo da un arco - if startEndVertices and startEndVertices[0] == 0 and startEndVertices[1] == len(pointList)-1: - return self.getGripPointsFromQadArc(arc, atGeom, atSubGeom, grips) - else: - circle = QadCircle() - startEndVertices = circle.fromPolyline(pointList, 0) - # se la polilinea è composta solo da un cerchio - if startEndVertices and startEndVertices[0] == 0 and startEndVertices[1] == len(pointList)-1: - return self.getGripPointsFromQadCircle(circle, atGeom, atSubGeom) - else: - linearObjectList = qad_utils.QadLinearObjectList() - linearObjectList.fromPolyline(pointList) - return self.getGripPointsFromQadLinearObjectList(linearObjectList, atGeom, atSubGeom, grips) - - - def getGripPointsFromQadLinearObjectList(self, linearObjectList, atGeom = 0, atSubGeom = 0, grips = 2): - """ - Ottiene una lista di punti di grip da una QadLinearObjectList in map coordinate (vertici e punti medi con rotaz) - grips = 1 Displays grips - grips = 2 Displays additional midpoint grips on polyline segments - """ - result = [] - - isClosed = linearObjectList.isClosed() - nVertex = 0 - while nVertex < linearObjectList.qty(): - linearObject = linearObjectList.getLinearObjectAt(nVertex) - startPt = linearObject.getStartPt() - if isClosed == False and nVertex == 0: - gp = QadEntityGripPoint(self.mapCanvas, startPt, QadGripPointTypeEnum.END_VERTEX, atGeom, atSubGeom, nVertex) - else: - gp = QadEntityGripPoint(self.mapCanvas, startPt, QadGripPointTypeEnum.VERTEX, atGeom, atSubGeom, nVertex) - result.append(gp) - if grips == 2: - middlePt = linearObject.getMiddlePt() - rot = linearObject.getTanDirectionOnMiddlePt() - if linearObject.isSegment(): # linea - gp = QadEntityGripPoint(self.mapCanvas, middlePt, QadGripPointTypeEnum.LINE_MID_POINT, atGeom, atSubGeom, nVertex, rot) - else: # arco - gp = QadEntityGripPoint(self.mapCanvas, middlePt, QadGripPointTypeEnum.ARC_MID_POINT, atGeom, atSubGeom, nVertex, rot) - result.append(gp) - nVertex = nVertex + 1 - - # solo se la polilinea è aperta - if isClosed == False: - linearObject = linearObjectList.getLinearObjectAt(-1) # ultima parte - endPt = linearObject.getEndPt() - gp = QadEntityGripPoint(self.mapCanvas, endPt, QadGripPointTypeEnum.END_VERTEX, atGeom, atSubGeom, nVertex) - - result.append(gp) - - return result - - - def getGripPointsFromQadCircle(self, circle, atGeom = 0, atSubGeom = 0): - """ - Ottiene una lista di punti di grip da un QadCircle in map coordinate (centro e punti quadrante) - """ - result = [] - gp = QadEntityGripPoint(self.mapCanvas, circle.center, QadGripPointTypeEnum.CENTER, atGeom, atSubGeom, -1) - result.append(gp) - qua_points = circle.getQuadrantPoints() - for pt in qua_points: - gp = QadEntityGripPoint(self.mapCanvas, pt, QadGripPointTypeEnum.QUA_POINT, atGeom, atSubGeom, -1) - result.append(gp) - - return result - - - def getGripPointsFromQadArc(self, arc, atGeom = 0, atSubGeom = 0, grips = 2): - """ - Ottiene una lista di punti di grip da un QadArc in map coordinate (punto centrale, iniziale, finale, medio) - """ - result = [] - gp = QadEntityGripPoint(self.mapCanvas, arc.center, QadGripPointTypeEnum.CENTER, atGeom, atSubGeom, -1) - result.append(gp) - - startPt = arc.getStartPt() - gp = QadEntityGripPoint(self.mapCanvas, startPt, QadGripPointTypeEnum.END_VERTEX, atGeom, atSubGeom, 0) - result.append(gp) - - endPt = arc.getEndPt() - gp = QadEntityGripPoint(self.mapCanvas, endPt, QadGripPointTypeEnum.END_VERTEX, atGeom, atSubGeom, 1) - result.append(gp) - - if grips == 2: - middlePt = arc.getMiddlePt() - gp = QadEntityGripPoint(self.mapCanvas, middlePt, QadGripPointTypeEnum.ARC_MID_POINT, atGeom, atSubGeom, 0) - result.append(gp) - - return result - - - def getGripPointsFromDimComponent(self, dimEntity, component): - """ - Ottiene una lista di punti di grip del componente di una quotatura - """ - result = [] - dimComponent = dimEntity.getDimComponentByEntity(component) - if dimComponent is None: - return result - elif dimComponent == QadDimComponentEnum.TEXT_PT or \ - dimComponent == QadDimComponentEnum.DIM_PT1 or \ - dimComponent == QadDimComponentEnum.DIM_PT2: - g = component.getGeometry() - if g.wkbType() == QGis.WKBPoint: - # converto il punto dal layer coordinate in map coordinates - pt = self.mapCanvas.mapSettings().layerToMapCoordinates(self.entity.layer, g.asPoint()) - gp = QadEntityGripPoint(self.mapCanvas, pt, QadGripPointTypeEnum.VERTEX) - result.append(gp) - - return result - - -#=============================================================================== -# QadEntitySetGripPoints class. -#=============================================================================== -class QadEntitySetGripPoints(QgsMapCanvasItem): - """ - Classe che gestisce i punti di grip per una gruppo di selezione di entità - """ - - - #============================================================================ - # __init__ - #============================================================================ - def __init__(self, plugIn): - self.plugIn = plugIn - self.mapCanvas = plugIn.canvas - self.entityGripPoints = [] - - - def __del__(self): - self.removeItems() - - - def removeItems(self): - for entityGripPoint in self.entityGripPoints: - entityGripPoint.removeItems() - del self.entityGripPoints[:] - - - def set(self, entitySet, grips = 2): - """ - grips = 0 Hides grips - grips = 1 Displays grips - grips = 2 Displays additional midpoint grips on polyline segments - """ - self.removeItems() - - if grips == 0: # nasconde i grip - return - - # for each layer - for layerEntitySet in entitySet.layerEntitySetList: - for featureId in layerEntitySet.featureIds: - entityGripPoints = QadEntityGripPoints(self.plugIn) - entityGripPoints.set(layerEntitySet.layer, featureId, grips) - self.entityGripPoints.append(entityGripPoints) - - - def addEntity(self, entity, grips = 2): - """ - grips = 0 Hides grips - grips = 1 Displays grips - grips = 2 Displays additional midpoint grips on polyline segments - """ - if grips == 0: # nasconde i grip - return - if self.containsEntity(entity) == False: - entityGripPoints = QadEntityGripPoints(self.plugIn) - entityGripPoints.set(entity.layer, entity.featureId, grips) - self.entityGripPoints.append(entityGripPoints) - - - def hoverIntersectingGripPoints(self, point): - res = 0 - for entityGripPoint in self.entityGripPoints: - res = res + entityGripPoint.hoverIntersectingGripPoints(point) - return res - - - def selectIntersectingGripPoints(self, point): - res = 0 - for entityGripPoint in self.entityGripPoints: - res = res + entityGripPoint.selectIntersectingGripPoints(point) - return res - - - def unselectIntersectingGripPoints(self, point): - res = 0 - for entityGripPoint in self.entityGripPoints: - res = res + entityGripPoint.unselectIntersectingGripPoints(point) - return res - - - def toggleSelectIntersectingGripPoints(self, point): - for entityGripPoint in self.entityGripPoints: - entityGripPoint.toggleSelectIntersectingGripPoints(point) - - - def isIntersecting(self, point): - for entityGripPoint in self.entityGripPoints: - res = entityGripPoint.isIntersecting(point) - if res is not None: - return entityGripPoint - return None - - - def getSelectedEntityGripPoints(self): - # ritorna una lista delle entityGripPoint con dei grip point selezionati - # la funzione non fa copie delle entityGripPoint - result = [] - for entityGripPoint in self.entityGripPoints: - for gripPoint in entityGripPoint.gripPoints: - if gripPoint.getStatus() == QadGripStatusEnum.SELECTED: - result.append(entityGripPoint) - - return result - - - def containsEntity(self, entity): - for entityGripPoint in self.entityGripPoints: - if entityGripPoint.entity == entity: - return True - return False - - - def count(self): - return len(self.entityGripPoints) - +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + classe per gestire i grip + + ------------------- + begin : 2015-09-29 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + +from qgis.PyQt.QtCore import QPointF, QRectF +from qgis.PyQt.QtGui import QPen, QColor, QBrush +from qgis.core import QgsPointXY, QgsWkbTypes, QgsGeometry +from qgis.gui import QgsMapCanvasItem + +from .qad_multi_geom import * +from .qad_variables import QadVariables +from .qad_entity import QadEntity, QadEntityTypeEnum +from .qad_dim import QadDimStyles, QadDimComponentEnum +from .qad_msg import QadMsg + + +# =============================================================================== +# QadGripStatusEnum class. +# =============================================================================== +class QadGripStatusEnum(): + NONE = 0 # nessuno + UNSELECTED = 1 # grip non selezionato + SELECTED = 2 # grip selezionato + HOVER = 3 # grip non selezionati quando il cursore si ferma su di essi + + +# =============================================================================== +# QadGripIconTypeEnum class. +# =============================================================================== +class QadGripIconTypeEnum(): + NONE = 0 # nessuno + BOX = 1 # un quadrato + CIRCLE = 2 # cerchio + RECTANGLE = 3 # rettangolo + + +# =============================================================================== +# QadGripMarker class. +# =============================================================================== +class QadGripMarker(QgsMapCanvasItem): + """ + Classe che gestisce i marcatori dei grip + """ + + + # ============================================================================ + # __init__ + # ============================================================================ + def __init__(self, mapCanvas): + QgsMapCanvasItem.__init__(self, mapCanvas) + self.canvas = mapCanvas + self.iconType = QadGripIconTypeEnum.BOX # icon to be shown + self.iconSize = QadVariables.get(QadMsg.translate("Environment variables", "GRIPSIZE")) + self.borderColor = QadVariables.get(QadMsg.translate("Environment variables", "GRIPCONTOUR")) # color of the border + self.center = QgsPointXY(0, 0) # coordinates of the point in the center + self.setGrip(QadGripStatusEnum.UNSELECTED, QadGripIconTypeEnum.BOX) + + + def __del__(self): + self.removeItem() + + + def removeItem(self): + self.canvas.scene().removeItem(self) + + + def setCenter(self, point): + # point è in map coordinates + self.center = point + pt = self.toCanvasCoordinates(self.center) + self.setPos(pt) + + + def setGrip(self, status, iconType, rot = None): + # rot in radians counterclockwise (0 = horizontal) + if status == QadGripStatusEnum.UNSELECTED: + self.fillColor = QadVariables.get(QadMsg.translate("Environment variables", "GRIPCOLOR")) + elif status == QadGripStatusEnum.SELECTED: + self.fillColor = QadVariables.get(QadMsg.translate("Environment variables", "GRIPHOT")) + elif status == QadGripStatusEnum.HOVER: + self.fillColor = QadVariables.get(QadMsg.translate("Environment variables", "GRIPHOVER")) + + self.status = status + self.__iconType = iconType + if rot is not None: + self.__rot = -qad_utils.toDegrees(rot) # trasformo in gradi in senso orario + + + def paint(self, painter, option, widget): + """ + painter é un QPainter + """ + pen = QPen(QColor(self.borderColor)) + pen.setWidth(1) + painter.setPen(pen) + painter.rotate(self.__rot) + + if self.__iconType == QadGripIconTypeEnum.NONE: + pass + elif self.__iconType == QadGripIconTypeEnum.BOX: + # un quadrato + painter.fillRect(-self.iconSize, -self.iconSize, self.iconSize * 2, self.iconSize * 2, QBrush(QColor(self.fillColor))); + painter.drawRect(-self.iconSize, -self.iconSize, self.iconSize * 2, self.iconSize * 2) + elif self.__iconType == QadGripIconTypeEnum.CIRCLE: + # cerchio + painter.setBrush(QBrush(QColor(self.fillColor))) + painter.drawEllipse(QPointF(0, 0), self.iconSize, self.iconSize) + elif self.__iconType == QadGripIconTypeEnum.RECTANGLE: + # un rettangolo + painter.fillRect(-self.iconSize, -int(self.iconSize / 2), self.iconSize * 2, self.iconSize, QBrush(QColor(self.fillColor))); + painter.drawRect(-self.iconSize, -int(self.iconSize / 2), self.iconSize * 2, self.iconSize) + + + def boundingRect(self): + if self.__rot != 0: + width = qad_utils.getDistance(QgsPointXY(0,0), QgsPointXY(self.iconSize, self.iconSize)) + height = width + else: + width = self.iconSize + height = self.iconSize + + return QRectF(-width/2, -height/2, width, height) + + + def updatePosition(self): + self.setCenter(self.center) + + +# =============================================================================== +# QadGripPointTypeEnum class. +# =============================================================================== +class QadGripPointTypeEnum(): + NONE = 0 # nessuno + VERTEX = 1 # vertice di una geometria + LINE_MID_POINT = 2 # punto medio di un segmento + CENTER = 3 # centro di un cerchio, di un arco, di un ellisse o di un arco di ellisse + QUA_POINT = 4 # punto quadrante + ARC_MID_POINT = 5 # punto medio di un arco o fi un arco di ellisse + END_VERTEX = 6 # vertice iniziale e finale di una geometria + + +# =============================================================================== +# QadEntityGripPoint class. +# =============================================================================== +class QadEntityGripPoint(): + """ + Classe che gestisce un punto di grip per una entità + """ + + + # ============================================================================ + # __init__ + # ============================================================================ + def __init__(self, mapCanvas, point, type, atGeom = 0, atSubGeom = 0, nVertex = 0, rot = 0.0): + self.atGeom = atGeom # numero di geometria (0-index) + self.atSubGeom = atSubGeom # numero di sotto-geometria (0-index) + self.nVertex = nVertex # numero di vertice della polilinea della geometria e sotto-geometria (0-index) + + self.gripType = type + + self.gripMarker = QadGripMarker(mapCanvas) + self.gripMarker.setGrip(QadGripStatusEnum.UNSELECTED, self.gripType2IconType(self.gripType), rot) + self.gripMarker.setCenter(point) + + def __del__(self): + self.removeItem() + del self.gripMarker + + def removeItem(self): + self.gripMarker.removeItem() + + def getPoint(self): + return self.gripMarker.center + + def isIntersecting(self, point): + # point è in map coordinate + ToleranceInMapUnits = self.gripMarker.iconSize * self.gripMarker.canvas.mapSettings().mapUnitsPerPixel() + if point.x() >= self.getPoint().x() - ToleranceInMapUnits and \ + point.x() <= self.getPoint().x() + ToleranceInMapUnits and \ + point.y() >= self.getPoint().y() - ToleranceInMapUnits and \ + point.y() <= self.getPoint().y() + ToleranceInMapUnits: + return True + else: + return False + + def select(self): # seleziona un grip + self.gripMarker.setGrip(QadGripStatusEnum.SELECTED, self.gripType2IconType(self.gripType)) + self.gripMarker.show() + + def unselect(self): # deseleziona un grip + self.gripMarker.setGrip(QadGripStatusEnum.UNSELECTED, self.gripType2IconType(self.gripType)) + self.gripMarker.show() + + def hover(self): # grip non selezionato quando il cursore si ferma su di esso + if self.getStatus() == QadGripStatusEnum.UNSELECTED: + self.gripMarker.setGrip(QadGripStatusEnum.HOVER, self.gripType2IconType(self.gripType)) + self.gripMarker.show() + + def getStatus(self): + return self.gripMarker.status + + def gripType2IconType(self, gripType): + if gripType == QadGripPointTypeEnum.VERTEX or gripType == QadGripPointTypeEnum.END_VERTEX: + return QadGripIconTypeEnum.BOX + elif gripType == QadGripPointTypeEnum.LINE_MID_POINT or gripType == QadGripPointTypeEnum.ARC_MID_POINT: + return QadGripIconTypeEnum.RECTANGLE + elif gripType == QadGripPointTypeEnum.CENTER: + return QadGripIconTypeEnum.CIRCLE + elif gripType == QadGripPointTypeEnum.QUA_POINT: + return QadGripIconTypeEnum.BOX + else: + return None + + +# =============================================================================== +# QadEntityGripPoints class. +# =============================================================================== +class QadEntityGripPoints(QgsMapCanvasItem): + """ + Classe che gestisce i punti di grip per una entità + """ + + + # ============================================================================ + # __init__ + # ============================================================================ + def __init__(self, plugIn, entity = None, grips = 2): + self.plugIn = plugIn + self.mapCanvas = plugIn.canvas + self.gripPoints = [] # lista dei punti di grip in map coordinate + self.iHoverGripPoints = [] # lista delle posizioni in gripPoints del punti hover + # Build the spatial index for faster lookup. + self.index = QgsSpatialIndex() + if entity is not None: + self.entity = QadEntity(entity) + self.gripPoints = self.initGripPoints(grips) + self.index = self.initIndexGripPoints() + + + def __del__(self): + self.removeItems() + if self.index is not None: + del self.index + + + def set(self, layer, featureId, grips = 2): + self.entity = QadEntity() + self.entity.set(layer, featureId) + self.gripPoints = self.initGripPoints(grips) + self.index = self.initIndexGripPoints() + + + def removeItems(self): + for gripPoint in self.gripPoints: + gripPoint.removeItem() + del self.gripPoints[:] + del self.iHoverGripPoints[:] + del self.index + self.index = QgsSpatialIndex() + + + def selectIntersectingGripPoints(self, point): + # seleziona i grip che intersecano un punto in map coordinate + res = 0 + + # Get the i-pos of all the features in the index that are within + # the bounding box of the current feature because these are the ones + # that will be touching. + ToleranceInMapUnits = QadVariables.get(QadMsg.translate("Environment variables", "GRIPSIZE")) * self.mapCanvas.mapSettings().mapUnitsPerPixel() + rect = QgsRectangle(point.x() - ToleranceInMapUnits, point.y() - ToleranceInMapUnits, \ + point.x() + ToleranceInMapUnits, point.y() + ToleranceInMapUnits) + + iList = self.index.intersects(rect) + for i in iList: + self.gripPoints[i].select() + res = res + 1 + +# for gripPoint in self.gripPoints: +# if gripPoint.isIntersecting(point): +# gripPoint.select() +# res = res + 1 + + return res + + + def unselectIntersectingGripPoints(self, point): + # deseleziona i grip che intersecano un punto in map coordinate + res = 0 + + # Get the i-pos of all the features in the index that are within + # the bounding box of the current feature because these are the ones + # that will be touching. + ToleranceInMapUnits = QadVariables.get(QadMsg.translate("Environment variables", "GRIPSIZE")) * self.mapCanvas.mapSettings().mapUnitsPerPixel() + rect = QgsRectangle(point.x() - ToleranceInMapUnits, point.y() - ToleranceInMapUnits, \ + point.x() + ToleranceInMapUnits, point.y() + ToleranceInMapUnits) + + iList = self.index.intersects(rect) + for i in iList: + self.gripPoints[i].unselect() + res = res + 1 + +# for gripPoint in self.gripPoints: +# if gripPoint.isIntersecting(point): +# gripPoint.unselect() +# res = res + 1 + return res + + + def toggleSelectIntersectingGripPoints(self, point): + # seleziona i grip deselezionati e deseleziona i grip selezionati + # che intersecano un punto in map coordinate + + # Get the i-pos of all the features in the index that are within + # the bounding box of the current feature because these are the ones + # that will be touching. + ToleranceInMapUnits = QadVariables.get(QadMsg.translate("Environment variables", "GRIPSIZE")) * self.mapCanvas.mapSettings().mapUnitsPerPixel() + rect = QgsRectangle(point.x() - ToleranceInMapUnits, point.y() - ToleranceInMapUnits, \ + point.x() + ToleranceInMapUnits, point.y() + ToleranceInMapUnits) + + iList = self.index.intersects(rect) + for i in iList: + if self.gripPoints[i].getStatus() == QadGripStatusEnum.SELECTED: + self.gripPoints[i].unselect() + else: + self.gripPoints[i].select() + +# for gripPoint in self.gripPoints: +# if gripPoint.isIntersecting(point): +# if gripPoint.getStatus() == QadGripStatusEnum.SELECTED: +# gripPoint.unselect() +# else: +# gripPoint.select() + + + def hoverIntersectingGripPoints(self, point): + # seleziono in modo hover i grip che intersecano un punto (in map coordinate) + # non selezionati quando il cursore si ferma su di esso + + for i in self.iHoverGripPoints: + status = self.gripPoints[i].getStatus() + if status == QadGripStatusEnum.SELECTED: + self.gripPoints[i].select() + else: + self.gripPoints[i].unselect() + +# for gripPoint in self.gripPoints: +# status = gripPoint.getStatus() +# if status == QadGripStatusEnum.SELECTED: +# gripPoint.select() +# else: +# gripPoint.unselect() + + res = 0 + del self.iHoverGripPoints[:] + + # Get the i-pos of all the features in the index that are within + # the bounding box of the current feature because these are the ones + # that will be touching. + ToleranceInMapUnits = QadVariables.get(QadMsg.translate("Environment variables", "GRIPSIZE")) * self.mapCanvas.mapSettings().mapUnitsPerPixel() + rect = QgsRectangle(point.x() - ToleranceInMapUnits, point.y() - ToleranceInMapUnits, \ + point.x() + ToleranceInMapUnits, point.y() + ToleranceInMapUnits) + + iList = self.index.intersects(rect) + for i in iList: + self.gripPoints[i].hover() + self.iHoverGripPoints.append(i) + res = res + 1 + +# for gripPoint in self.gripPoints: +# if gripPoint.isIntersecting(point): +# gripPoint.hover() +# res = res + 1 +# else: +# status = gripPoint.getStatus() +# if status == QadGripStatusEnum.SELECTED: +# gripPoint.select() +# else: +# gripPoint.unselect() + return res + + + def isIntersecting(self, point): + # ritorna il primo punto di grip (QadEntityGripPoint) che interseca point (in map coordinate) + + # Get the i-pos of all the features in the index that are within + # the bounding box of the current feature because these are the ones + # that will be touching. + ToleranceInMapUnits = QadVariables.get(QadMsg.translate("Environment variables", "GRIPSIZE")) * self.mapCanvas.mapSettings().mapUnitsPerPixel() + rect = QgsRectangle(point.x() - ToleranceInMapUnits, point.y() - ToleranceInMapUnits, \ + point.x() + ToleranceInMapUnits, point.y() + ToleranceInMapUnits) + + iList = self.index.intersects(rect) + for i in iList: + return self.gripPoints[i] + + +# for gripPoint in self.gripPoints: +# if gripPoint.isIntersecting(point): +# return gripPoint + return None + + + def getSelectedGripPoints(self): + # restituisce una lista di punti in cui i grip sono selezionati + result = [] + + for gripPoint in self.gripPoints: + if gripPoint.getStatus() == QadGripStatusEnum.SELECTED: + result.append(gripPoint) + + return result + + + def initGripPoints(self, grips = 2): + # restituisce una lista di QadEntityGripPoint + atGeom = 0 + atSubGeom = 0 + result = [] + + g = self.entity.getQadGeom() + if g is None: + return result + + # verifico se l'entità appartiene ad uno stile di quotatura + dimEntity = QadDimStyles.getDimEntity(self.entity) + if dimEntity is not None: + return self.getGripPointsFromDimComponent(dimEntity, self.entity) + + gType = g.whatIs() + if gType == "POINT": + # converto il punto dal layer coordinate in map coordinates + gp = QadEntityGripPoint(self.mapCanvas, g, QadGripPointTypeEnum.VERTEX) + result.append(gp) + + elif gType == "MULTI_POINT": + for atGeom in range(0, g.qty()): + gp = QadEntityGripPoint(self.mapCanvas, g.getPointAt(atGeom), QadGripPointTypeEnum.VERTEX, atGeom) + result.append(gp) + + elif gType == "ARC": + result = self.getGripPointsFromQadArc(g, 0, 0, grips) + + elif gType == "CIRCLE": + result = self.getGripPointsFromQadCircle(g, 0, 0) + + elif gType == "ELLIPSE": + result = self.getGripPointsFromQadEllipse(g, 0, 0) + + elif gType == "ELLIPSE_ARC": + result = self.getGripPointsFromQadEllipseArc(g, 0, 0, grips) + + elif gType == "LINE": + result = self.getGripPointsFromQadLine(g, 0, 0, grips) + + elif gType == "POLYLINE": + result = self.getGripPointsFromPolyline(g, 0, 0, grips) + + elif gType == "MULTI_LINEAR_OBJ": + for atGeom in range(0, g.qty()): + subGeom = g.getLinearObjectAt(atGeom) + subGeomType = subGeom.whatIs() + if subGeomType == "ARC": + result.extend(self.getGripPointsFromQadArc(subGeom, atGeom, 0, grips)) + elif subGeomType == "CIRCLE": + result.extend(self.getGripPointsFromQadCircle(subGeom, atGeom, 0)) + elif subGeomType == "ELLIPSE": + result.extend(self.getGripPointsFromQadEllipse(subGeom, atGeom, 0)) + elif subGeomType == "ELLIPSE_ARC": + result.extend(self.getGripPointsFromQadEllipseArc(subGeom, atGeom, 0, grips)) + elif subGeomType == "LINE": + result.extend(self.getGripPointsFromQadLine(subGeom, atGeom, 0, grips)) + elif subGeomType == "POLYLINE": + result.extend(self.getGripPointsFromPolyline(subGeom, atGeom, 0, grips)) + + elif gType == "POLYGON": + for atSubGeom in range(0, g.qty()): + closedObj = g.getClosedObjectAt(atSubGeom) + subGeomType = closedObj.whatIs() + if subGeomType == "ARC": + result.extend(self.getGripPointsFromQadArc(closedObj, 0, atSubGeom, grips)) + elif subGeomType == "CIRCLE": + result.extend(self.getGripPointsFromQadCircle(closedObj, 0, atSubGeom)) + elif subGeomType == "ELLIPSE": + result.extend(self.getGripPointsFromQadEllipse(closedObj, 0, atSubGeom)) + elif subGeomType == "ELLIPSE_ARC": + result.extend(self.getGripPointsFromQadEllipseArc(closedObj, 0, atSubGeom, grips)) + elif subGeomType == "LINE": + result.extend(self.getGripPointsFromQadLine(closedObj, 0, atSubGeom, grips)) + elif subGeomType == "POLYLINE": + result.extend(self.getGripPointsFromPolyline(closedObj, 0, atSubGeom, grips)) + # aggiungo il centroide + gp = QadEntityGripPoint(self.mapCanvas, closedObj.getCentroid(), QadGripPointTypeEnum.CENTER, 0, atSubGeom) + result.append(gp) + + elif gType == "MULTI_POLYGON": + for atGeom in range(0, g.qty()): + polygon = g.getPolygonAt(atGeom) + for atSubGeom in range(0, polygon.qty()): + closedObj = polygon.getClosedObjectAt(atSubGeom) + subGeomType = closedObj.whatIs() + if subGeomType == "ARC": + result.extend(self.getGripPointsFromQadArc(closedObj, atGeom, atSubGeom, grips)) + elif subGeomType == "CIRCLE": + result.extend(self.getGripPointsFromQadCircle(closedObj, atGeom, atSubGeom)) + elif subGeomType == "ELLIPSE": + result.extend(self.getGripPointsFromQadEllipse(closedObj, atGeom, atSubGeom)) + elif subGeomType == "ELLIPSE_ARC": + result.extend(self.getGripPointsFromQadEllipseArc(closedObj, atGeom, atSubGeom, grips)) + elif subGeomType == "LINE": + result.extend(self.getGripPointsFromQadLine(closedObj, atGeom, atSubGeom, grips)) + elif subGeomType == "POLYLINE": + result.extend(self.getGripPointsFromPolyline(closedObj, atGeom, atSubGeom, grips)) + # aggiungo il centroide + gp = QadEntityGripPoint(self.mapCanvas, closedObj.getCentroid(), QadGripPointTypeEnum.CENTER, atGeom, atSubGeom) + result.append(gp) + + atSubGeom = atSubGeom + 1 + atGeom = atGeom + 1 + + return result + + + def initIndexGripPoints(self): + # inizializza l'indice spaziale della lista dei punti di grip + index = QgsSpatialIndex() + f = QgsFeature(QgsFields(), 0) + i = 0 + for gp in self.gripPoints: + f.setId(i) + f.setGeometry(QgsGeometry.fromPointXY(gp.getPoint())) + index.addFeature(f) + i = i + 1 + + return index + + + def getGripPointsFromPolyline(self, polyline, atGeom = 0, atSubGeom = 0, grips = 2): + """ + Ottiene una lista di punti di grip da una QadPolyline in map coordinate (vertici e punti medi con rotaz) + grips = 1 Displays grips + grips = 2 Displays additional midpoint grips on polyline segments + """ + result = [] + + isClosed = polyline.isClosed() + nVertex = 0 + while nVertex < polyline.qty(): + linearObject = polyline.getLinearObjectAt(nVertex) + startPt = linearObject.getStartPt() + if isClosed == False and nVertex == 0: + gp = QadEntityGripPoint(self.mapCanvas, startPt, QadGripPointTypeEnum.END_VERTEX, atGeom, atSubGeom, nVertex) + else: + gp = QadEntityGripPoint(self.mapCanvas, startPt, QadGripPointTypeEnum.VERTEX, atGeom, atSubGeom, nVertex) + result.append(gp) + + if grips == 2: # Displays additional midpoint grips on polyline segments + middlePt = linearObject.getMiddlePt() + rot = linearObject.getTanDirectionOnPt(middlePt) + linearObjectType = linearObject.whatIs() + if linearObjectType == "LINE": + gp = QadEntityGripPoint(self.mapCanvas, middlePt, QadGripPointTypeEnum.LINE_MID_POINT, atGeom, atSubGeom, nVertex, rot) + elif linearObjectType == "ARC" or linearObjectType == "ELLIPSE_ARC": + gp = QadEntityGripPoint(self.mapCanvas, middlePt, QadGripPointTypeEnum.ARC_MID_POINT, atGeom, atSubGeom, nVertex, rot) + + result.append(gp) + nVertex = nVertex + 1 + + # solo se la polilinea è aperta + if isClosed == False: + linearObject = polyline.getLinearObjectAt(-1) # ultima parte + endPt = linearObject.getEndPt() + gp = QadEntityGripPoint(self.mapCanvas, endPt, QadGripPointTypeEnum.END_VERTEX, atGeom, atSubGeom, nVertex) + result.append(gp) + + return result + + + def getGripPointsFromQadLine(self, line, atGeom = 0, atSubGeom = 0, grips = 2): + """ + Ottiene una lista di punti di grip da un QadLine in map coordinate (iniziale, finale, medio) + """ + result = [] + startPt = line.getStartPt() + gp = QadEntityGripPoint(self.mapCanvas, startPt, QadGripPointTypeEnum.END_VERTEX, atGeom, atSubGeom, 0) + result.append(gp) + + if grips == 2: # Displays additional midpoint grips on polyline segments + middlePt = line.getMiddlePt() + rot = line.getTanDirectionOnMiddlePt() + gp = QadEntityGripPoint(self.mapCanvas, middlePt, QadGripPointTypeEnum.LINE_MID_POINT, atGeom, atSubGeom, 0, rot) + result.append(gp) + + + endPt = line.getEndPt() + gp = QadEntityGripPoint(self.mapCanvas, endPt, QadGripPointTypeEnum.END_VERTEX, atGeom, atSubGeom, 1) + result.append(gp) + + return result + + + def getGripPointsFromQadCircle(self, circle, atGeom = 0, atSubGeom = 0): + """ + Ottiene una lista di punti di grip da un QadCircle in map coordinate (centro e punti quadrante) + """ + result = [] + gp = QadEntityGripPoint(self.mapCanvas, circle.center, QadGripPointTypeEnum.CENTER, atGeom, atSubGeom, -1) + result.append(gp) + qua_points = circle.getQuadrantPoints() + for pt in qua_points: + gp = QadEntityGripPoint(self.mapCanvas, pt, QadGripPointTypeEnum.QUA_POINT, atGeom, atSubGeom, -1) + result.append(gp) + + return result + + + def getGripPointsFromQadEllipse(self, ellipse, atGeom = 0, atSubGeom = 0): + """ + Ottiene una lista di punti di grip da un QadEllipse in map coordinate (centro e punti quadrante) + """ + result = [] + gp = QadEntityGripPoint(self.mapCanvas, ellipse.center, QadGripPointTypeEnum.CENTER, atGeom, atSubGeom, -1) + result.append(gp) + qua_points = ellipse.getQuadrantPoints() + for pt in qua_points: + gp = QadEntityGripPoint(self.mapCanvas, pt, QadGripPointTypeEnum.QUA_POINT, atGeom, atSubGeom, -1) + result.append(gp) + + return result + + + def getGripPointsFromQadArc(self, arc, atGeom = 0, atSubGeom = 0, grips = 2): + """ + Ottiene una lista di punti di grip da un QadArc in map coordinate (punto centrale, iniziale, finale, medio) + """ + result = [] + gp = QadEntityGripPoint(self.mapCanvas, arc.center, QadGripPointTypeEnum.CENTER, atGeom, atSubGeom, -1) + result.append(gp) + + startPt = arc.getStartPt() + gp = QadEntityGripPoint(self.mapCanvas, startPt, QadGripPointTypeEnum.END_VERTEX, atGeom, atSubGeom, 0) + result.append(gp) + + endPt = arc.getEndPt() + gp = QadEntityGripPoint(self.mapCanvas, endPt, QadGripPointTypeEnum.END_VERTEX, atGeom, atSubGeom, 1) + result.append(gp) + + if grips == 2: # Displays additional midpoint grips on polyline segments + middlePt = arc.getMiddlePt() + rot = arc.getTanDirectionOnMiddlePt() + gp = QadEntityGripPoint(self.mapCanvas, middlePt, QadGripPointTypeEnum.ARC_MID_POINT, atGeom, atSubGeom, 0, rot) + result.append(gp) + + return result + + + def getGripPointsFromQadEllipseArc(self, ellipseArc, atGeom = 0, atSubGeom = 0, grips = 2): + """ + Ottiene una lista di punti di grip da un QadEllipseArc in map coordinate (punto centrale, iniziale, finale e punti quadrante) + """ + result = [] + gp = QadEntityGripPoint(self.mapCanvas, ellipseArc.center, QadGripPointTypeEnum.CENTER, atGeom, atSubGeom, -1) + result.append(gp) + + startPt = ellipseArc.getStartPt() + gp = QadEntityGripPoint(self.mapCanvas, startPt, QadGripPointTypeEnum.END_VERTEX, atGeom, atSubGeom, 0) + result.append(gp) + + endPt = ellipseArc.getEndPt() + gp = QadEntityGripPoint(self.mapCanvas, endPt, QadGripPointTypeEnum.END_VERTEX, atGeom, atSubGeom, 1) + result.append(gp) + + qua_points = ellipseArc.getQuadrantPoints() + for pt in qua_points: + if pt is not None: + gp = QadEntityGripPoint(self.mapCanvas, pt, QadGripPointTypeEnum.QUA_POINT, atGeom, atSubGeom, -1) + result.append(gp) + + return result + + + def getGripPointsFromDimComponent(self, dimEntity, component): + """ + Ottiene una lista di punti di grip del componente di una quotatura + component = QadEntity + """ + result = [] + dimComponent = dimEntity.getDimComponentByEntity(component) + if dimComponent is None: return result + + if dimComponent == QadDimComponentEnum.TEXT_PT or \ + dimComponent == QadDimComponentEnum.DIM_PT1 or \ + dimComponent == QadDimComponentEnum.DIM_PT2: + g = component.getQadGeom() + gType = g.whatIs() + if gType == "POINT": + gp = QadEntityGripPoint(self.mapCanvas, g, QadGripPointTypeEnum.VERTEX) + result.append(gp) + + return result + + +# =============================================================================== +# QadEntitySetGripPoints class. +# =============================================================================== +class QadEntitySetGripPoints(QgsMapCanvasItem): + """ + Classe che gestisce i punti di grip per una gruppo di selezione di entità + """ + + + # ============================================================================ + # __init__ + # ============================================================================ + def __init__(self, plugIn): + self.plugIn = plugIn + self.mapCanvas = plugIn.canvas + self.entityGripPoints = [] + + + def __del__(self): + self.removeItems() + + + def removeItems(self): + for entityGripPoint in self.entityGripPoints: + entityGripPoint.removeItems() + del self.entityGripPoints[:] + + + def set(self, entitySet, grips = 2): + """ + grips = 0 Hides grips + grips = 1 Displays grips + grips = 2 Displays additional midpoint grips on polyline segments + """ + self.removeItems() + + if grips == 0: # nasconde i grip + return + + # for each layer + for layerEntitySet in entitySet.layerEntitySetList: + for featureId in layerEntitySet.featureIds: + entityGripPoints = QadEntityGripPoints(self.plugIn) + entityGripPoints.set(layerEntitySet.layer, featureId, grips) + self.entityGripPoints.append(entityGripPoints) + + + def addEntity(self, entity, grips = 2): + """ + grips = 0 Hides grips + grips = 1 Displays grips + grips = 2 Displays additional midpoint grips on polyline segments + """ + if grips == 0: # nasconde i grip + return + if self.containsEntity(entity) == False: + entityGripPoints = QadEntityGripPoints(self.plugIn) + entityGripPoints.set(entity.layer, entity.featureId, grips) + self.entityGripPoints.append(entityGripPoints) + + + def hoverIntersectingGripPoints(self, point): + res = 0 + for entityGripPoint in self.entityGripPoints: + res = res + entityGripPoint.hoverIntersectingGripPoints(point) + return res + + + def selectIntersectingGripPoints(self, point): + res = 0 + for entityGripPoint in self.entityGripPoints: + res = res + entityGripPoint.selectIntersectingGripPoints(point) + return res + + + def unselectIntersectingGripPoints(self, point): + res = 0 + for entityGripPoint in self.entityGripPoints: + res = res + entityGripPoint.unselectIntersectingGripPoints(point) + return res + + + def toggleSelectIntersectingGripPoints(self, point): + for entityGripPoint in self.entityGripPoints: + entityGripPoint.toggleSelectIntersectingGripPoints(point) + + + def isIntersecting(self, point): + # ritorna 2 valori: QadEntityGripPoints, QadEntityGripPoint + for entityGripPoints in self.entityGripPoints: + res = entityGripPoints.isIntersecting(point) + if res is not None: + return entityGripPoints, res + return None, None + + + def getSelectedEntityGripPoints(self): + # ritorna una lista delle entityGripPoint con dei grip point selezionati + # la funzione non fa copie delle entityGripPoint + result = [] + for entityGripPoint in self.entityGripPoints: + for gripPoint in entityGripPoint.gripPoints: + if gripPoint.getStatus() == QadGripStatusEnum.SELECTED: + result.append(entityGripPoint) + + return result + + + def containsEntity(self, entity): + for entityGripPoint in self.entityGripPoints: + if entityGripPoint.entity == entity: + return True + return False + + + def count(self): + return len(self.entityGripPoints) + diff --git a/qad_gripcolor.ui b/qad_gripcolor.ui new file mode 100644 index 00000000..72ccd513 --- /dev/null +++ b/qad_gripcolor.ui @@ -0,0 +1,217 @@ + + + GripColor_Dialog + + + + 0 + 0 + 400 + 208 + + + + + 400 + 208 + + + + + 400 + 208 + + + + Grip colors + + + + + 60 + 170 + 331 + 32 + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Help|QDialogButtonBox::Ok + + + + + + 10 + 10 + 381 + 141 + + + + Settings + + + + + 10 + 30 + 171 + 21 + + + + Unselected grip color: + + + + + + 200 + 30 + 171 + 21 + + + + Hover grip color: + + + + + + 10 + 80 + 171 + 21 + + + + Selected grip color: + + + + + + 200 + 80 + 171 + 21 + + + + Grip contour color: + + + + + + 10 + 50 + 171 + 23 + + + + + + + + + + 10 + 100 + 171 + 23 + + + + + + + + + + 200 + 50 + 171 + 23 + + + + + + + + + + 200 + 100 + 171 + 23 + + + + + + + + + + + + buttonBox + rejected() + GripColor_Dialog + reject() + + + 316 + 260 + + + 286 + 207 + + + + + buttonBox + helpRequested() + GripColor_Dialog + ButtonHELP_Pressed() + + + 142 + 183 + + + 135 + 207 + + + + + buttonBox + accepted() + GripColor_Dialog + ButtonBOX_Accepted() + + + 168 + 185 + + + 123 + 207 + + + + + + ButtonBOX_Accepted() + ButtonHELP_Pressed() + + diff --git a/qad_gripcolor_dlg.py b/qad_gripcolor_dlg.py new file mode 100644 index 00000000..a8055c22 --- /dev/null +++ b/qad_gripcolor_dlg.py @@ -0,0 +1,111 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + Gestione dei colori dei grip di QAD + + ------------------- + begin : 2016-17-02 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.PyQt.QtWidgets import QDialog +from qgis.PyQt.QtCore import * +from qgis.PyQt.QtGui import * +from qgis.core import * +from qgis.utils import * +from qgis.gui import * + + +from .qad_gripcolor_ui import Ui_GripColor_Dialog + +from .qad_msg import QadMsg, qadShowPluginPDFHelp +from . import qad_utils + + +####################################################################################### +# Classe che gestisce l'interfaccia grafica per i colori dei grip +class QadGripColorDialog(QDialog, QObject, Ui_GripColor_Dialog): + def __init__(self, plugIn, parent, gripColor, gripHot, gripHover, gripContour): + self.plugIn = plugIn + self.iface = self.plugIn.iface.mainWindow() + + QDialog.__init__(self, parent) + + self.gripColor = gripColor + self.gripHot = gripHot + self.gripHover = gripHover + self.gripContour = gripContour + + self.setupUi(self) + self.setWindowTitle(QadMsg.getQADTitle() + " - " + self.windowTitle()) + + # Inizializzazione dei colori + self.init_colors() + + + def setupUi(self, Dialog): + Ui_GripColor_Dialog.setupUi(self, self) + # aggiungo il bottone di qgis QgsColorButton chiamato unselectedGripColor + # che eredita la posizione di unselectedGripColorDummy (che viene nascosto) + self.unselectedGripColorDummy.setHidden(True) + self.unselectedGripColor = QgsColorButton(self.unselectedGripColorDummy.parent()) + self.unselectedGripColor.setGeometry(self.unselectedGripColorDummy.geometry()) + self.unselectedGripColor.setObjectName("unselectedGripColor") + # aggiungo il bottone di qgis QgsColorButton chiamato selectedGripColor + # che eredita la posizione di selectedGripColorDummy (che viene nascosto) + self.selectedGripColorDummy.setHidden(True) + self.selectedGripColor = QgsColorButton(self.selectedGripColorDummy.parent()) + self.selectedGripColor.setGeometry(self.selectedGripColorDummy.geometry()) + self.selectedGripColor.setObjectName("selectedGripColor") + # aggiungo il bottone di qgis QgsColorButton chiamato hoverGripColor + # che eredita la posizione di hoverGripColorDummy (che viene nascosto) + self.hoverGripColorDummy.setHidden(True) + self.hoverGripColor = QgsColorButton(self.hoverGripColorDummy.parent()) + self.hoverGripColor.setGeometry(self.hoverGripColorDummy.geometry()) + self.hoverGripColor.setObjectName("hoverGripColor") + # aggiungo il bottone di qgis QgsColorButton chiamato contourGripColor + # che eredita la posizione di contourGripColorDummy (che viene nascosto) + self.contourGripColorDummy.setHidden(True) + self.contourGripColor = QgsColorButton(self.contourGripColorDummy.parent()) + self.contourGripColor.setGeometry(self.contourGripColorDummy.geometry()) + self.contourGripColor.setObjectName("contourGripColor") + + + # ============================================================================ + # init_colors + # ============================================================================ + def init_colors(self): + # Inizializzazione dei colori + self.unselectedGripColor.setColor(QColor(self.gripColor)) + self.selectedGripColor.setColor(QColor(self.gripHot)) + self.hoverGripColor.setColor(QColor(self.gripHover)) + self.contourGripColor.setColor(QColor(self.gripContour)) + + + def ButtonBOX_Accepted(self): + self.gripColor = self.unselectedGripColor.color().name() + self.gripHot = self.selectedGripColor.color().name() + self.gripHover = self.hoverGripColor.color().name() + self.gripContour = self.contourGripColor.color().name() + + QDialog.accept(self) + + + def ButtonHELP_Pressed(self): + qadShowPluginPDFHelp(QadMsg.translate("Help", "")) diff --git a/qad_gripcolor_ui.py b/qad_gripcolor_ui.py new file mode 100644 index 00000000..6c2a0f1c --- /dev/null +++ b/qad_gripcolor_ui.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file '.\qad_gripcolor.ui' +# +# Created by: PyQt5 UI code generator 5.15.4 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_GripColor_Dialog(object): + def setupUi(self, GripColor_Dialog): + GripColor_Dialog.setObjectName("GripColor_Dialog") + GripColor_Dialog.resize(400, 208) + GripColor_Dialog.setMinimumSize(QtCore.QSize(400, 208)) + GripColor_Dialog.setMaximumSize(QtCore.QSize(400, 208)) + self.buttonBox = QtWidgets.QDialogButtonBox(GripColor_Dialog) + self.buttonBox.setGeometry(QtCore.QRect(60, 170, 331, 32)) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Help|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.groupBox = QtWidgets.QGroupBox(GripColor_Dialog) + self.groupBox.setGeometry(QtCore.QRect(10, 10, 381, 141)) + self.groupBox.setObjectName("groupBox") + self.label = QtWidgets.QLabel(self.groupBox) + self.label.setGeometry(QtCore.QRect(10, 30, 171, 21)) + self.label.setObjectName("label") + self.label_2 = QtWidgets.QLabel(self.groupBox) + self.label_2.setGeometry(QtCore.QRect(200, 30, 171, 21)) + self.label_2.setObjectName("label_2") + self.label_3 = QtWidgets.QLabel(self.groupBox) + self.label_3.setGeometry(QtCore.QRect(10, 80, 171, 21)) + self.label_3.setObjectName("label_3") + self.label_4 = QtWidgets.QLabel(self.groupBox) + self.label_4.setGeometry(QtCore.QRect(200, 80, 171, 21)) + self.label_4.setObjectName("label_4") + self.unselectedGripColorDummy = QtWidgets.QPushButton(self.groupBox) + self.unselectedGripColorDummy.setGeometry(QtCore.QRect(10, 50, 171, 23)) + self.unselectedGripColorDummy.setText("") + self.unselectedGripColorDummy.setObjectName("unselectedGripColorDummy") + self.selectedGripColorDummy = QtWidgets.QPushButton(self.groupBox) + self.selectedGripColorDummy.setGeometry(QtCore.QRect(10, 100, 171, 23)) + self.selectedGripColorDummy.setText("") + self.selectedGripColorDummy.setObjectName("selectedGripColorDummy") + self.hoverGripColorDummy = QtWidgets.QPushButton(self.groupBox) + self.hoverGripColorDummy.setGeometry(QtCore.QRect(200, 50, 171, 23)) + self.hoverGripColorDummy.setText("") + self.hoverGripColorDummy.setObjectName("hoverGripColorDummy") + self.contourGripColorDummy = QtWidgets.QPushButton(self.groupBox) + self.contourGripColorDummy.setGeometry(QtCore.QRect(200, 100, 171, 23)) + self.contourGripColorDummy.setText("") + self.contourGripColorDummy.setObjectName("contourGripColorDummy") + + self.retranslateUi(GripColor_Dialog) + self.buttonBox.rejected.connect(GripColor_Dialog.reject) + self.buttonBox.helpRequested.connect(GripColor_Dialog.ButtonHELP_Pressed) + self.buttonBox.accepted.connect(GripColor_Dialog.ButtonBOX_Accepted) + QtCore.QMetaObject.connectSlotsByName(GripColor_Dialog) + + def retranslateUi(self, GripColor_Dialog): + _translate = QtCore.QCoreApplication.translate + GripColor_Dialog.setWindowTitle(_translate("GripColor_Dialog", "Grip colors")) + self.groupBox.setTitle(_translate("GripColor_Dialog", "Settings")) + self.label.setText(_translate("GripColor_Dialog", "Unselected grip color:")) + self.label_2.setText(_translate("GripColor_Dialog", "Hover grip color:")) + self.label_3.setText(_translate("GripColor_Dialog", "Selected grip color:")) + self.label_4.setText(_translate("GripColor_Dialog", "Grip contour color:")) diff --git a/qad_help_cmd.py b/qad_help_cmd.py deleted file mode 100644 index 33056804..00000000 --- a/qad_help_cmd.py +++ /dev/null @@ -1,64 +0,0 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - comando HELP che apre la guida di QAD - - ------------------- - begin : 2015-08-31 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -from qad_generic_cmd import QadCommandClass -from qad_msg import QadMsg, qadShowPluginHelp - - -# Classe che gestisce il comando HELP -class QadHELPCommandClass(QadCommandClass): - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadHELPCommandClass(self.plugIn) - - def getName(self): - return QadMsg.translate("Command_list", "HELP") - - def getEnglishName(self): - return "HELP" - - def connectQAction(self, action): - QObject.connect(action, SIGNAL("triggered()"), self.plugIn.runHELPCommand) - - def getIcon(self): - return QIcon(":/plugins/qad/icons/help.png") - - def getNote(self): - # impostare le note esplicative del comando - return QadMsg.translate("Command_HELP", "The QAD manual will be showed.") - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - - def run(self, msgMapTool = False, msg = None): - qadShowPluginHelp() - return True diff --git a/qad_highlight.py b/qad_highlight.py index fab3787c..3fafdee3 100644 --- a/qad_highlight.py +++ b/qad_highlight.py @@ -1,114 +1,111 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - Classe per gestire l'evidenziazione delle geometrie - - ------------------- - begin : 2015-12-12 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -from qgis.gui import * - - -from qad_variables import * - - -#=============================================================================== -# getQGISColorForHighlight -#=============================================================================== -def getQGISColorForHighlight(): - """ - La funzione legge il colore impostato da QGIS per il rubber band di tipo . - Se = True, il rubber band sarà impostato con più trasparenza - """ - settings = QSettings() - color = QColor(int(settings.value( "/qgis/digitizing/line_color_red", 1)), \ - int(settings.value( "/qgis/digitizing/line_color_green", 1)), \ - int(settings.value( "/qgis/digitizing/line_color_blue", 1))) - alpha = float(int(settings.value( "/qgis/digitizing/line_color_alpha", 200)) / 255.0) - - color.setAlphaF(alpha) - return color - - -#=============================================================================== -# createHighlight -#=============================================================================== -def createHighlight(mapCanvas, geometry_feature, layer, borderColor = None, fillColor = None): - """ - la funzione crea un rubber band di tipo con le impostazioni di QGIS. - Se = True, il rubber band sarà impostato con più trasparenza e tipolinea punteggiato - """ - settings = QSettings() - width = int(settings.value( "/qgis/digitizing/line_width", 1)) - - hl = QgsHighlight(mapCanvas, geometry_feature, layer) - - if borderColor is None: - borderColor = getQGISColorForHighlight() - hl.setColor(borderColor) - - if fillColor is None: - hl.setFillColor(borderColor) - else: - hl.setFillColor(fillColor) - - return hl - - -# Classe che gestisce l'evidenziazione delle geometrie -class QadHighlight(): - def __init__(self, mapCanvas, borderColor = None, fillColor = None): - self.mapCanvas = mapCanvas - self.__highlight = [] - - def __del__(self): - self.reset() - - for highlight in self.__highlight: - self.mapCanvas.scene().removeItem(highlight) - - del self.__highlight[:] - - def hide(self): - for highlight in self.__highlight: - highlight.hide() - - def show(self): - for highlight in self.__highlight: - highlight.show() - - def addGeometry(self, geom, layer, borderColor = None, fillColor = None): - highlight = createHighlight(self.mapCanvas, geom, layer, borderColor, fillColor) - highlight.show() - self.__highlight.append(highlight) - - def addGeometries(self, geoms, layer, borderColor = None, fillColor = None): - for g in geoms: - self.addGeometry(g, layer, borderColor, fillColor) - - def reset(self): - self.hide() - for highlight in self.__highlight: - self.mapCanvas.scene().removeItem(highlight) - del self.__highlight[:] +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + Classe per gestire l'evidenziazione delle geometrie + + ------------------- + begin : 2015-12-12 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.PyQt.QtCore import * +from qgis.PyQt.QtGui import * +from qgis.core import * +from qgis.gui import * + + +# =============================================================================== +# getQGISColorForHighlight +# =============================================================================== +def getQGISColorForHighlight(): + """ + La funzione legge il colore impostato da QGIS per il rubber band di tipo . + Se = True, il rubber band sarà impostato con più trasparenza + """ + settings = QSettings() + color = QColor(int(settings.value( "/qgis/digitizing/line_color_red", 1)), \ + int(settings.value( "/qgis/digitizing/line_color_green", 1)), \ + int(settings.value( "/qgis/digitizing/line_color_blue", 1))) + alpha = float(int(settings.value( "/qgis/digitizing/line_color_alpha", 200)) / 255.0) + + color.setAlphaF(alpha) + return color + + +# =============================================================================== +# createHighlight +# =============================================================================== +def createHighlight(mapCanvas, geometry_feature, layer, borderColor = None, fillColor = None): + """ + la funzione crea un rubber band di tipo con le impostazioni di QGIS. + Se = True, il rubber band sarà impostato con più trasparenza e tipolinea punteggiato + """ + settings = QSettings() + width = int(settings.value( "/qgis/digitizing/line_width", 1)) + + hl = QgsHighlight(mapCanvas, geometry_feature, layer) + + if borderColor is None: + borderColor = getQGISColorForHighlight() + hl.setColor(borderColor) + + if fillColor is None: + hl.setFillColor(borderColor) + else: + hl.setFillColor(fillColor) + + return hl + + +# Classe che gestisce l'evidenziazione delle geometrie +class QadHighlight(): + def __init__(self, mapCanvas, borderColor = None, fillColor = None): + self.mapCanvas = mapCanvas + self.__highlight = [] + + def __del__(self): + self.reset() + + for highlight in self.__highlight: + self.mapCanvas.scene().removeItem(highlight) + + del self.__highlight[:] + + def hide(self): + for highlight in self.__highlight: + highlight.hide() + + def show(self): + for highlight in self.__highlight: + highlight.show() + + def addGeometry(self, geom, layer, borderColor = None, fillColor = None): + highlight = createHighlight(self.mapCanvas, geom, layer, borderColor, fillColor) + highlight.show() + self.__highlight.append(highlight) + + def addGeometries(self, geoms, layer, borderColor = None, fillColor = None): + for g in geoms: + self.addGeometry(g, layer, borderColor, fillColor) + + def reset(self): + self.hide() + for highlight in self.__highlight: + self.mapCanvas.scene().removeItem(highlight) + del self.__highlight[:] diff --git a/qad_join_fun.py b/qad_join_fun.py new file mode 100644 index 00000000..0201f942 --- /dev/null +++ b/qad_join_fun.py @@ -0,0 +1,455 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + funzioni per join tra elementi lineari + + ------------------- + begin : 2019-09-04 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.PyQt.QtCore import * +from qgis.PyQt.QtGui import * +from qgis.core import * +from qgis.gui import * +import qgis.utils + + +from .qad_msg import QadMsg +from .qad_variables import QadVariables +from .qad_geom_relations import * +from .qad_layer import createMemoryLayer +from .qad_polyline import * + + +# ============================================================================ +# join2polyline +# ============================================================================ +def join2polyline(polyline, polylineToJoinTo, toleranceDist = None, mode = 1): + """ + la funzione unisce la polilinea con un'altra polilinea secondo la modalità . + In caso di successo ritorna True altrimenti False. + = polilinea da unire (sarà modificata) + = polilinea con cui unirsi + = distanza di tolleranza perché 2 punti siano considerati coincidenti + = Imposta il metodo di unione (usato se toleranceDist > 0): + 1 -> Estendi; Consente di unire polilinee selezionate estendendo o tagliando + i segmenti nei punti finali più vicini. + 2 -> Aggiungi; Consente di unire polilinee selezionate aggiungendo un segmento + retto tra i punti finali più vicini. + 3 -> Entrambi;Consente di unire polilinee selezionate estendendo o tagliando, se possibile. + In caso contrario, consente di unire polilinee selezionate aggiungendo + un segmento retto tra i punti finali più vicini. + """ + if toleranceDist is None: + myToleranceDist = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2COINCIDENT")) + else: + myToleranceDist = toleranceDist + + # cerco il punto più vicino al punto iniziale della polilinea + ptToJoin = polyline.getStartPt() + isStartPt = True + minDist = sys.float_info.max + # considero il punto iniziale della polilinea a cui unirsi + dist = qad_utils.getDistance(ptToJoin, polylineToJoinTo.getStartPt()) + if dist < minDist: + isStartPtToJoinTo = True + minDist = dist + # considero il punto finale della polilinea a cui unirsi + dist = qad_utils.getDistance(ptToJoin, polylineToJoinTo.getEndPt()) + if dist < minDist: + isStartPtToJoinTo = False + minDist = dist + + # cerco il punto più vicino al punto finale della polilinea + ptToJoin = polyline.getEndPt() + # considero il punto iniziale della polilinea a cui unirsi + dist = qad_utils.getDistance(ptToJoin, polylineToJoinTo.getStartPt()) + if dist < minDist: + isStartPt = False + isStartPtToJoinTo = True + minDist = dist + # considero il punto finale della polilinea a cui unirsi + dist = qad_utils.getDistance(ptToJoin, polylineToJoinTo.getEndPt()) + if dist < minDist: + isStartPt = False + isStartPtToJoinTo = False + minDist = dist + + if minDist <= myToleranceDist: # trovato un punto + # se il punto iniziale della polilinea da unire é uguale a quello iniziale della polilinea a cui unirsi + if isStartPt == True and isStartPtToJoinTo == True: + part1 = polyline.getLinearObjectAt(0).copy() + part1.reverse() + part2 = polylineToJoinTo.getLinearObjectAt(0).copy() + part2.reverse() + + res = joinEndPtsLinearParts(part1, part2, mode) + if res is not None: + # elimino la prima parte + polyline.remove(0) + res.reverse() + polyline.insertPolyline(0, res) + + # aggiungo le parti di tranne la prima + i = 1 + tot = polylineToJoinTo.qty() + while i < tot: + polyline.insert(0, polylineToJoinTo.getLinearObjectAt(i).copy().reverse()) + i = i + 1 + return True + + # se il punto iniziale della polilinea da unire é uguale a quello finale della polilinea a cui unirsi + elif isStartPt == True and isStartPtToJoinTo == False: + part1 = polyline.getLinearObjectAt(0).copy() + part1.reverse() + part2 = polylineToJoinTo.getLinearObjectAt(-1) + + res = joinEndPtsLinearParts(part1, part2, mode) + if res is not None: + # elimino la prima parte + polyline.remove(0) + res.reverse() + polyline.insertPolyline(0, res) + + # aggiungo le parti di tranne l'ultima + i = polylineToJoinTo.qty() - 2 + while i >= 0: + polyline.insert(0, polylineToJoinTo.getLinearObjectAt(i)) + i = i - 1 + return True + + # se il punto finale della polilinea da unire é uguale a quello iniziale della polilinea a cui unirsi + elif isStartPt == False and isStartPtToJoinTo == True: + part1 = polyline.getLinearObjectAt(-1) + part2 = polylineToJoinTo.getLinearObjectAt(0).copy() + part2.reverse() + + res = joinEndPtsLinearParts(part1, part2, mode) + if res is not None: + # elimino l'ultima parte + polyline.remove(-1) + polyline.appendPolyline(res) + + # aggiungo le parti di tranne la prima + i = 1 + tot = polylineToJoinTo.qty() + while i < tot: + polyline.append(polylineToJoinTo.getLinearObjectAt(i)) + i = i + 1 + return True + + # se il punto finale della polilinea da unire é uguale a quello finale della polilinea a cui unirsi + elif isStartPt == False and isStartPtToJoinTo == False: + part1 = polyline.getLinearObjectAt(-1) + part2 = polylineToJoinTo.getLinearObjectAt(-1) + + res = joinEndPtsLinearParts(part1, part2, mode) + if res is not None: + # elimino l'ultima parte + polyline.remove(-1) + polyline.appendPolyline(res) + + # aggiungo le parti di tranne l'ultima + i = polylineToJoinTo.qty() - 2 + while i >= 0: + polyline.append(polylineToJoinTo.getLinearObjectAt(i).reverse()) + i = i - 1 + return True + + return False + + +# =============================================================================== +# joinEndPtsLinearParts +# =============================================================================== +def joinEndPtsLinearParts(part1, part2, mode): + """ + la funzione effettua il join (unione) tra 2 parti lineari di base considerando il punto finale di part1 + e il punto iniziale di part2. + La funzione riceve: + = prima parte lineare + = seconda parte parte lineare + = Imposta il metodo di unione: + 1 -> Estendi; Consente di unire polilinee selezionate estendendo o tagliando + i segmenti nei punti finali più vicini. + 2 -> Aggiungi; Consente di unire polilinee selezionate aggiungendo un segmento + retto tra i punti finali più vicini. + 3 -> Entrambi; Consente di unire polilinee selezionate estendendo o tagliando, se possibile. + In caso contrario, consente di unire polilinee selezionate aggiungendo + un segmento retto tra i punti finali più vicini. + La funzione restituisce una QadPolyline che comprende: + part1 (eventualmente modificata nel punto finale) + + eventuale segmento + + part2 (eventualmente modificata nel punto finale) + oppure restituisce None se non é possibile l'unione delle parti + """ + polyline = QadPolyline() + endPt1 = part1.getEndPt() + endPt2 = part2.getEndPt() + + if qad_utils.ptNear(endPt1, endPt2): # le 2 parti sono già unite + polyline.append(part1.copy()) + p = part2.copy() + p.reverse() + polyline.append(p) + return polyline + + if mode == 1: # Estendi/Taglia + IntPtList = QadIntersections.twoBasicGeomObjects(part1, part2) + if len(IntPtList) > 0: # Taglia + polyline.append(part1.copy()) + polyline.getLinearObjectAt(-1).setEndPt(IntPtList[0]) + p = part2.copy() + p.reverse() + polyline.append(p) + polyline.getLinearObjectAt(-1).setStartPt(IntPtList[0]) + return polyline + else: # estendi + IntPtList = QadIntersections.twoBasicGeomObjectExtensions(part1, part2) + # considero solo i punti oltre l'inizio delle parti + for i in range(len(IntPtList) - 1, -1, -1): + if part1.getDistanceFromStart(IntPtList[i]) < 0 or \ + part2.getDistanceFromStart(IntPtList[i]) < 0: + del IntPtList[i] + + if len(IntPtList) > 0: + IntPt = IntPtList[0] + polyline.append(part1.copy()) + polyline.getLinearObjectAt(-1).setEndPt(IntPtList[0]) + p = part2.copy() + p.reverse() + polyline.append(p) + polyline.getLinearObjectAt(-1).setStartPt(IntPtList[0]) + return polyline + + if mode == 2 or mode == 3: # Aggiungi + polyline.append(part1.copy()) + polyline.append([endPt1, endPt2]) + p = part2.copy() + p.reverse() + polyline.append(p) + return polyline + + return None + + +# ============================================================================ +# joinFeatureInVectorLayer +# ============================================================================ +def joinFeatureInVectorLayer(featureIdToJoin, vectorLayer, tolerance2ApproxCurve, toleranceDist = None, \ + mode = 2): + """ + la funzione effettua il join (unione) di una polilinea con un gruppo di altre polilinee. + Non sono ammesse geometrie multiLineString. + Il layer deve essere in modifica (startEditing) e in una transazione (beginEditCommand) + La funzione riceve: + = un ID della feature da unire + = un QgsVectorLayer che deve contenere le feature da unire + (si usano gli indici spaziali del vettore x essere più veloci). + = distanza di tolleranza perché 2 punti siano considerati coincidenti + = tolleranza di approssimazione per le curve (usato se toleranceDist > 0) + = Imposta il metodo di unione (usato se toleranceDist > 0): + 1 -> Estendi; Consente di unire polilinee selezionate estendendo o tagliando + i segmenti nei punti finali più vicini. + 2 -> Aggiungi; Consente di unire polilinee selezionate aggiungendo un segmento + retto tra i punti finali più vicini. + 3 -> Entrambi;Consente di unire polilinee selezionate estendendo o tagliando, se possibile. + In caso contrario, consente di unire polilinee selezionate aggiungendo + un segmento retto tra i punti finali più vicini. + La funzione modifica il modificando la feature da unire e cancellando + quelle unite a featureIdToJoin . Ritorna la lista di features cancellate. + """ + if toleranceDist is None: + myToleranceDist = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2COINCIDENT")) + else: + myToleranceDist = toleranceDist + + featureToJoin = qad_utils.getFeatureById(vectorLayer, featureIdToJoin) + if featureToJoin is None: + return [] + + g = QgsGeometry(featureToJoin.geometry()) + polyline = QadPolyline() + polyline.fromPolyline(g.asPolyline()) + + polylineToJoinTo = QadPolyline() + + deleteFeatures = [] + feature = QgsFeature() + + # Unisco usando il punto iniziale finché trovo feature da unire + ptToJoin = polyline.getStartPt() + found = True + while found == True: + found = False + if ptToJoin is None: # test + fermati = True + # cerco le features nel punto iniziale usando un micro rettangolo secondo + selectRect = QgsRectangle(ptToJoin.x() - myToleranceDist, ptToJoin.y() - myToleranceDist, \ + ptToJoin.x() + myToleranceDist, ptToJoin.y() + myToleranceDist) + # cerco il punto più vicino al punto iniziale della polilinea + minDist = sys.float_info.max + # fetchAttributes, fetchGeometry, rectangle, useIntersect + for feature in vectorLayer.getFeatures(qad_utils.getFeatureRequest([], True, selectRect, True)): + if feature.id() != featureIdToJoin: # salto la feature da unire + polylineToJoinTo.fromPolyline(feature.geometry().asPolyline()) + + if join2polyline(polyline, polylineToJoinTo, myToleranceDist, mode) == True: + found = True + + deleteFeatures.append(QgsFeature(feature)) + if vectorLayer.deleteFeature(feature.id()) == False: + return [] + + ptToJoin = polyline.getStartPt() + pts = polyline.asPolyline(tolerance2ApproxCurve) + featureToJoin.setGeometry(QgsGeometry.fromPolylineXY(pts)) + if vectorLayer.updateFeature(featureToJoin) == False: + return [] + break + + # Unisco usando il punto finale finché trovo feature da unire + ptToJoin = polyline.getEndPt() + found = True + while found == True: + found = False + # cerco le features nel punto finale usando un micro rettangolo secondo + selectRect = QgsRectangle(ptToJoin.x() - myToleranceDist, ptToJoin.y() - myToleranceDist, \ + ptToJoin.x() + myToleranceDist, ptToJoin.y() + myToleranceDist) + # fetchAttributes, fetchGeometry, rectangle, useIntersect + for feature in vectorLayer.getFeatures(qad_utils.getFeatureRequest([], True, selectRect, True)): + if feature.id() != featureIdToJoin: # salto la feature da unire + polylineToJoinTo.fromPolyline(feature.geometry().asPolyline()) + + if join2polyline(polyline, polylineToJoinTo, myToleranceDist, mode) == True: + found = True + + deleteFeatures.append(QgsFeature(feature)) + if vectorLayer.deleteFeature(feature.id()) == False: + return [] + + ptToJoin = polyline.getEndPt() + pts = polyline.asPolyline(tolerance2ApproxCurve) + featureToJoin.setGeometry(QgsGeometry.fromPolylineXY(pts)) + if vectorLayer.updateFeature(featureToJoin) == False: + return [] + break + + return deleteFeatures + + +# ============================================================================ +# polylineAsQgsFeatureList +# ============================================================================ +def polylineAsQgsFeatureList(polyline, polylineMode): + """ + la funzione restituisce una lista di feature. + Se polylineMode = True allora la lista degli oggetti lineari sarà considerata un'unica polilinea + """ + fList = [] + if polylineMode == False: + for linearObject in polyline.defList: + f = QgsFeature() + f.setGeometry(QgsGeometry.fromPolylineXY(linearObject.asPolyline())) + fList.append(f) + else: + f = QgsFeature() + f.setGeometry(QgsGeometry.fromPolylineXY(polyline.asPolyline())) + fList.append(f) + + return fList + + +# ============================================================================ +# appendPolylineToTempQgsVectorLayer +# ============================================================================ +def appendPolylineToTempQgsVectorLayer(polyline, vectorLayer, polylineMode, updateExtents = True): + """ + la funzione inserisce gli oggetti lineari di una polyline in un QgsVectorLayer temporaneo già creato. + Se polylineMode = True allora la lista degli oggetti lineari sarà considerata un'unica polilinea + Ritorna la lista dei corrispettivi id di feature oppure None in caso di errore + """ + fList = polylineAsQgsFeatureList(polyline, polylineMode) + + idList = [] + result = True + if vectorLayer.startEditing() == False: + return None + + vectorLayer.beginEditCommand("Feature added") + + for f in fList: + if vectorLayer.addFeature(f): + idList.append(f.id()) + else: + result = False + break + + if result == True: + vectorLayer.endEditCommand(); + if updateExtents: + vectorLayer.updateExtents() + return idList + else: + vectorLayer.destroyEditCommand() + return None + + +# ============================================================================ +# selfJoinPolyline +# ============================================================================ +def selfJoinPolyline(polyline): + """ + la funzione viene usata quando la polilinea contiene parti lineari non connesse tra loro come una vera polyline. + Restituisce una lista QadPolyline che contiene le polilinee + generate dall'unione degli oggetti lineari. + """ + crs = qgis.utils.iface.mapCanvas().mapSettings().destinationCrs() + # creo un layer temporaneo in memoria + vectorLayer = createMemoryLayer("QAD_SelfJoinLines", "LineString", crs) + provider = vectorLayer.dataProvider() + + # unisco le parti della polilinea + # inserisco nel layer i vari oggetti lineari + idList = appendPolylineToTempQgsVectorLayer(polyline, vectorLayer, False) + if idList is None: + return [] + if provider.capabilities() & QgsVectorDataProvider.CreateSpatialIndex: + provider.createSpatialIndex() + + vectorLayer.beginEditCommand("selfJoin") + + for featureIdToJoin in idList: + # featureIdToJoin, vectorLayer, tolerance2ApproxCurve, tomyToleranceDist + joinFeatureInVectorLayer(featureIdToJoin, vectorLayer, QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE"))) + + vectorLayer.endEditCommand() + vectorLayer.commitChanges() + + result = [] + feature = QgsFeature() + + # fetchAttributes, fetchGeometry, rectangle, useIntersect + for feature in vectorLayer.getFeatures(qad_utils.getFeatureRequest([], True, None, False)): + polyline = QadPolyline() + polyline.fromPolyline(feature.geometry().asPolyline()) + result.append(polyline) + + return result diff --git a/qad_label.py b/qad_label.py index 5c8b1d54..8466bb09 100644 --- a/qad_label.py +++ b/qad_label.py @@ -1,496 +1,516 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - funzioni per le etichette - - ------------------- - begin : 2014-04-24 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -from qgis.gui import * - - -#=============================================================================== -# get_tokenListFromLblFieldName ausilio di getTokenListFromLblFieldName -#=============================================================================== -def getToken(expr, start, endChar = None): - """ - ritorna una parola dentro la stringa expr che inizia nella posizione start e che termina con il carattere - endChar. Se endChar <> None allora due endChar consecutivi valgono uno es. 'a''a' = a'a - """ - token = "" - tot = len(expr) - i = start - if endChar is None: - separators = "()+-*/%^=><|, \"'" - while i < tot: - ch = expr[i] - if separators.find(ch) >= 0: - return token, i - token = token + ch - i = i + 1 - else: - while i < tot: - ch = expr[i] - if ch != endChar: - token = token + ch - elif i + 1 < tot: # se c'é un carattere successivo - if expr[i + 1] == endChar: # se il carattere successivo = endChar - token = token + ch - i = i + 1 - else: - return token, i - i = i + 1 - - return token, i - - -#=============================================================================== -# getTokenListFromLblFieldName ausilio di get_labelFieldNames -#=============================================================================== -def getTokenListFromLblFieldName(expr): - """ - ritorna una lista di token escluse le stringhe, dall'espressione passata come parametro - """ - result = [] - i = 0 - tot = len(expr) - while i < tot: - ch = expr[i] - if ch == "\"": # se inizia un nome di campo - token, i = getToken(expr, i + 1, "\"") - if len(token) > 0: - result.append(token) - elif ch == "'": # se inizia una stringa - token, i = getToken(expr, i + 1, "'") - else: - token, i = getToken(expr, i) - if len(token) > 0: - result.append(token) - - i = i + 1 - - return result - - -#=============================================================================== -# get_activeDataDefinedPropertyFieldNames -#=============================================================================== -def get_activeDataDefinedPropertyFieldNames(layer, dataDefinedProperty): - """ - ritorna la lista dei nomi dei campi che determinano il valore della proprietà attiva - """ - result = [] - - if (dataDefinedProperty is not None) and dataDefinedProperty.isActive(): - if dataDefinedProperty.useExpression(): - # estraggo i token - tokenList = getTokenListFromLblFieldName(dataDefinedProperty.expressionString()) - - fields = layer.label().fields() - for field in fields: - if field.name() in tokenList: - if field.name() not in result: # evito duplicati - result.append(field.name()) - else: - result.append(dataDefinedProperty.field()) - - return result - - -#=============================================================================== -# get_scaleFieldName -#=============================================================================== -def get_labelFieldNames(layer): - """ - ritorna la lista dei campi che concorrono a formare il testo dell'etichetta - """ - result = [] - - if layer.type() == QgsMapLayer.VectorLayer: - palyr = QgsPalLayerSettings() - palyr.readFromLayer(layer) - if palyr.enabled: - lblFieldName = palyr.fieldName - if palyr.isExpression: # Is this label made from a expression string eg FieldName || 'mm'. - # estraggo i token - tokenList = getTokenListFromLblFieldName(lblFieldName) - - fields = layer.label().fields() - for field in fields: - if field.name() in tokenList: - if field.name() not in result: # evito duplicati - result.append(field.name()) - else: - result.append(lblFieldName) - - return result - - -#=============================================================================== -# get_labelRotationFieldNames -#=============================================================================== -def get_labelRotationFieldNames(layer): - """ - ritorna la lista dei campi che concorrono a formare la rotazione dell'etichetta - """ - if layer.type() == QgsMapLayer.VectorLayer: - palyr = QgsPalLayerSettings() - palyr.readFromLayer(layer) - dataDefined = palyr.dataDefinedProperty(QgsPalLayerSettings.Rotation) - return get_activeDataDefinedPropertyFieldNames(layer, dataDefined) - - return [] - - -#=============================================================================== -# get_labelSizeFieldNames -#=============================================================================== -def get_labelSizeFieldNames(layer): - """ - ritorna la lista dei campi che concorrono a formare la dimensione del testo dell'etichetta - """ - if layer.type() == QgsMapLayer.VectorLayer: - palyr = QgsPalLayerSettings() - palyr.readFromLayer(layer) - dataDefined = palyr.dataDefinedProperty(QgsPalLayerSettings.Size) - return get_activeDataDefinedPropertyFieldNames(layer, dataDefined) - - return [] - - -#=============================================================================== -# get_labelFontFamilyFieldNames -#=============================================================================== -def get_labelFontFamilyFieldNames(layer): - """ - ritorna la lista dei campi che concorrono a formare il nome del font dell'etichetta - """ - if layer.type() == QgsMapLayer.VectorLayer: - palyr = QgsPalLayerSettings() - palyr.readFromLayer(layer) - dataDefined = palyr.dataDefinedProperty(QgsPalLayerSettings.Family) - return get_activeDataDefinedPropertyFieldNames(layer, dataDefined) - - return [] - - -#=============================================================================== -# get_labelText -#=============================================================================== -def get_labelText(palLayerSettings, feature): - """ - restituisce il testo dell'etichetta - """ - lblFieldName = palLayerSettings.fieldName - if lblFieldName == "": - return "" - if palLayerSettings.isExpression: # Is this label made from a expression string eg FieldName || 'mm'. - expr = QgsExpression(lblFieldName) - val = expr.evaluate(feature) - else: - val = feature.attribute(lblFieldName) - - return val - - -#=============================================================================== -# get_labelFontSize -#=============================================================================== -def get_labelFontSize(palLayerSettings, feature): - """ - restituisce la dimensione del font dell'etichetta - """ - val = None - dataDefined = palLayerSettings.dataDefinedProperty(QgsPalLayerSettings.Size) - if (dataDefined is not None) and dataDefined.isActive(): - if dataDefined.useExpression(): - expr = QgsExpression(dataDefined.expressionString()) - val = expr.evaluate(feature) - else: - val = feature.attribute(dataDefined.field()) - else: - val = palLayerSettings.textFont.pointSize() # Returns the point size of the font - - return val - - -#=============================================================================== -# get_labelFontFamily -#=============================================================================== -def get_labelFontFamily(palLayerSettings, feature): - """ - restituisce il nome del font dell'etichetta - """ - val = None - dataDefined = palLayerSettings.dataDefinedProperty(QgsPalLayerSettings.Family) - if (dataDefined is not None) and dataDefined.isActive(): - if dataDefined.useExpression(): - expr = QgsExpression(dataDefined.expressionString()) - val = expr.evaluate(feature) - else: - val = feature.attribute(dataDefined.field()) - else: - val = palLayerSettings.textFont.family() - - return val - - -#=============================================================================== -# get_labelFontTextNamedStyle -#=============================================================================== -def get_labelFontTextNamedStyle(palLayerSettings, feature): - """ - restituisce il nome dello stile del font dell'etichetta - """ - val = None - dataDefined = palLayerSettings.dataDefinedProperty(QgsPalLayerSettings.FontStyle) - if (dataDefined is not None) and dataDefined.isActive(): - if dataDefined.useExpression(): - expr = QgsExpression(dataDefined.expressionString()) - val = expr.evaluate(feature) - else: - val = feature.attribute(dataDefined.field()) - else: - val = palLayerSettings.textNamedStyle - - return val - - -#=============================================================================== -# get_labelIsBold -#=============================================================================== -def get_labelIsBold(palLayerSettings, feature): - """ - restituisce se il font dell'etichetta é grassetto - """ - val = None - dataDefined = palLayerSettings.dataDefinedProperty(QgsPalLayerSettings.Bold) - if (dataDefined is not None) and dataDefined.isActive(): - if dataDefined.useExpression(): - expr = QgsExpression(dataDefined.expressionString()) - val = expr.evaluate(feature) - else: - val = feature.attribute(dataDefined.field()) - else: - val = palLayerSettings.textFont.bold() - - return val - - -#=============================================================================== -# get_labelIsItalic -#=============================================================================== -def get_labelIsItalic(palLayerSettings, feature): - """ - restituisce se il font dell'etichetta é italico - """ - val = None - dataDefined = palLayerSettings.dataDefinedProperty(QgsPalLayerSettings.Italic) - if (dataDefined is not None) and dataDefined.isActive(): - if dataDefined.useExpression(): - expr = QgsExpression(dataDefined.expressionString()) - val = expr.evaluate(feature) - else: - val = feature.attribute(dataDefined.field()) - else: - val = palLayerSettings.textFont.italic() - - return val - - -#=============================================================================== -# get_labelIsUnderline -#=============================================================================== -def get_labelIsUnderline(palLayerSettings, feature): - """ - restituisce se il font dell'etichetta é sottolineato - """ - val = None - dataDefined = palLayerSettings.dataDefinedProperty(QgsPalLayerSettings.Underline) - if (dataDefined is not None) and dataDefined.isActive(): - if dataDefined.useExpression(): - expr = QgsExpression(dataDefined.expressionString()) - val = expr.evaluate(feature) - else: - val = feature.attribute(dataDefined.field()) - else: - val = palLayerSettings.textFont.underline() - - return val - - -#=============================================================================== -# get_labelIsStrikeOut -#=============================================================================== -def get_labelIsStrikeOut(palLayerSettings, feature): - """ - restituisce se il font dell'etichetta é barrato - """ - val = None - dataDefined = palLayerSettings.dataDefinedProperty(QgsPalLayerSettings.Strikeout) - if (dataDefined is not None) and dataDefined.isActive(): - if dataDefined.useExpression(): - expr = QgsExpression(dataDefined.expressionString()) - val = expr.evaluate(feature) - else: - val = feature.attribute(dataDefined.field()) - else: - val = palLayerSettings.textFont.strikeOut() - - return val - - -#=============================================================================== -# get_labelFontCase -#=============================================================================== -def get_labelFontCase(palLayerSettings, feature): - """ - restituisce se il font dell'etichetta é maiuscolo/minuscolo e varie opzioni: - QFont::MixedCase 0 This is the normal text rendering option where no capitalization change is applied. - QFont::AllUppercase 1 This alters the text to be rendered in all uppercase type. - QFont::AllLowercase 2 This alters the text to be rendered in all lowercase type. - QFont::SmallCaps 3 This alters the text to be rendered in small-caps type. - QFont::Capitalize 4 This alters the text to be rendered with the first character of each word as an uppercase character. - """ - val = None - dataDefined = palLayerSettings.dataDefinedProperty(QgsPalLayerSettings.FontCase) - if (dataDefined is not None) and dataDefined.isActive(): - if dataDefined.useExpression(): - expr = QgsExpression(dataDefined.expressionString()) - val = expr.evaluate(feature) - else: - val = feature.attribute(dataDefined.field()) - else: - val = palLayerSettings.textFont.capitalization() - - return val - - -#=============================================================================== -# get_labelFontSizeInMapUnits -#=============================================================================== -def get_labelFontSizeInMapUnits(palLayerSettings, feature): - """ - restituisce se l'unità del font dell'etichetta é in unità mappa - """ - val = None - dataDefined = palLayerSettings.dataDefinedProperty(QgsPalLayerSettings.FontSizeUnit) - if (dataDefined is not None) and dataDefined.isActive(): - if dataDefined.useExpression(): - expr = QgsExpression(dataDefined.expressionString()) - val = True if expr.evaluate(feature) == QgsPalLayerSettings.MapUnits else False - else: - val = True if feature.attribute(dataDefined.field()) == QgsPalLayerSettings.MapUnits else False - else: - val = palLayerSettings.fontSizeInMapUnits - - return val - - -#=============================================================================== -# get_labelRot -#=============================================================================== -def get_labelRot(palLayerSettings, feature): - """ - restituisce la rotazione dell'etichetta - """ - val = 0 - dataDefined = palLayerSettings.dataDefinedProperty(QgsPalLayerSettings.Rotation) - if (dataDefined is not None) and dataDefined.isActive(): - if dataDefined.useExpression(): - expr = QgsExpression(dataDefined.expressionString()) - val = expr.evaluate(feature) - else: - val = feature.attribute(dataDefined.field()) - - return val - - -#=============================================================================== -# calculateLabelSize -#=============================================================================== -def calculateLabelSize(layer, feature, canvas): - """ - return size for label in map units - """ - if layer.type() != QgsMapLayer.VectorLayer: - return None, None - palyr = QgsPalLayerSettings() - palyr.readFromLayer(layer) - - text = get_labelText(palyr, feature) - - font = QFont(palyr.textFont) - - fontName = get_labelFontFamily(palyr, feature) - if fontName is not None: - font.setFamily(fontName) - - fontBold = get_labelIsBold(palyr, feature) - if fontBold is not None: - font.setBold(fontBold) - - fontItalic = get_labelIsItalic(palyr, feature) - if fontItalic is not None: - font.setItalic(fontItalic) - - fontUnderline = get_labelIsUnderline(palyr, feature) - if fontUnderline is not None: - font.setUnderline(fontUnderline) - - fontStrikeOut = get_labelIsStrikeOut(palyr, feature) - if fontStrikeOut is not None: - font.setStrikeOut(fontStrikeOut) - - fontCase = get_labelFontCase(palyr, feature) - if fontCase is not None: - font.setCapitalization(fontCase) - - fontSize = get_labelFontSize(palyr, feature) - if fontSize is not None: - - isFontSizeInMapUnits = get_labelFontSizeInMapUnits(palyr, feature) - - if isFontSizeInMapUnits: - if fontSize < 100: - font.setPixelSize(fontSize * 100) - else: - font.setPixelSize(fontSize) - - fontMetricsF = QFontMetricsF(font) - rect = fontMetricsF.boundingRect(text) - dimX = rect.width() - dimY = rect.height() - if isFontSizeInMapUnits: - if fontSize < 100: # se dimX e dimY sono già in map units * 10 - return dimX / 100, dimY / 100 - else: - return dimX, dimY - else: - mapToPixel = canvas.getCoordinateTransform() # trasformo pixel in map units - dimX = dimX * mapToPixel.mapUnitsPerPixel() - dimY = dimY * mapToPixel.mapUnitsPerPixel() - return dimX, dimY - - return None, None +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + funzioni per le etichette + + ------------------- + begin : 2014-04-24 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.PyQt.QtCore import * +from qgis.PyQt.QtGui import * +from qgis.core import * +from qgis.gui import * + + +# =============================================================================== +# get_tokenListFromLblFieldName ausilio di getTokenListFromLblFieldName +# =============================================================================== +def getToken(expr, start, endChar = None): + """ + ritorna una parola dentro la stringa expr che inizia nella posizione start e che termina con il carattere + endChar. Se endChar <> None allora due endChar consecutivi valgono uno es. 'a''a' = a'a + """ + token = "" + tot = len(expr) + i = start + if endChar is None: + separators = "()+-*/%^=><|, \"'" + while i < tot: + ch = expr[i] + if separators.find(ch) >= 0: + return token, i + token = token + ch + i = i + 1 + else: + while i < tot: + ch = expr[i] + if ch != endChar: + token = token + ch + elif i + 1 < tot: # se c'é un carattere successivo + if expr[i + 1] == endChar: # se il carattere successivo = endChar + token = token + ch + i = i + 1 + else: + return token, i + i = i + 1 + + return token, i + + +# =============================================================================== +# getTokenListFromLblFieldName ausilio di get_labelFieldNames +# =============================================================================== +def getTokenListFromLblFieldName(expr): + """ + ritorna una lista di token escluse le stringhe, dall'espressione passata come parametro + """ + result = [] + i = 0 + tot = len(expr) + while i < tot: + ch = expr[i] + if ch == "\"": # se inizia un nome di campo + token, i = getToken(expr, i + 1, "\"") + if len(token) > 0: + result.append(token) + elif ch == "'": # se inizia una stringa + token, i = getToken(expr, i + 1, "'") + else: + token, i = getToken(expr, i) + if len(token) > 0: + result.append(token) + + i = i + 1 + + return result + + +# =============================================================================== +# get_activeDataDefinedPropertyFieldNames +# =============================================================================== +def get_activeDataDefinedPropertyFieldNames(layer, dataDefinedProperty): + """ + ritorna la lista dei nomi dei campi che determinano il valore della proprietà attiva + """ + result = [] + + if (dataDefinedProperty is not None) and dataDefinedProperty.isActive(): + if len(dataDefinedProperty.expressionString()) > 0: + # estraggo i token + tokenList = getTokenListFromLblFieldName(dataDefinedProperty.expressionString()) + + labeling = layer.labeling() + if labeling is not None: + if type(labeling) == QgsVectorLayerSimpleLabeling: + palLayerSettings = labeling.settings() + fields = layer.fields() + + for field in fields: + if field.name() in tokenList: + if field.name() not in result: # evito duplicati + result.append(field.name()) + else: + result.append(dataDefinedProperty.field()) + + return result + + +# =============================================================================== +# get_scaleFieldName +# =============================================================================== +def get_labelFieldNames(layer): + """ + ritorna la lista dei campi che concorrono a formare il testo dell'etichetta + """ + result = [] + + if layer.type() == QgsMapLayer.VectorLayer and layer.labeling() is not None: + palyr = layer.labeling().settings() + lblFieldName = palyr.fieldName + if palyr.isExpression: # Is this label made from a expression string eg FieldName || 'mm'. + # estraggo i token + tokenList = getTokenListFromLblFieldName(lblFieldName) + + labeling = layer.labeling() + if labeling is not None: + if type(labeling) == QgsVectorLayerSimpleLabeling: + palLayerSettings = labeling.settings() + fields = palLayerSettings.mCurFields + for field in fields: + if field.name() in tokenList: + if field.name() not in result: # evito duplicati + result.append(field.name()) + else: + result.append(lblFieldName) + + return result + + +# =============================================================================== +# get_labelRotationFieldNames +# =============================================================================== +def get_labelRotationFieldNames(layer): + """ + ritorna la lista dei campi che concorrono a formare la rotazione dell'etichetta + """ + if layer.type() == QgsMapLayer.VectorLayer and layer.labeling() is not None: + palyr = layer.labeling().settings() + dataDefined = palyr.dataDefinedProperties().property(QgsPalLayerSettings.LabelRotation) + return get_activeDataDefinedPropertyFieldNames(layer, dataDefined) + + return [] + + +# =============================================================================== +# get_labelSizeFieldNames +# =============================================================================== +def get_labelSizeFieldNames(layer): + """ + ritorna la lista dei campi che concorrono a formare la dimensione del testo dell'etichetta + """ + if layer.type() == QgsMapLayer.VectorLayer and layer.labeling() is not None: + palyr = layer.labeling().settings() + dataDefined = palyr.dataDefinedProperties().property(QgsPalLayerSettings.Size) + return get_activeDataDefinedPropertyFieldNames(layer, dataDefined) + + return [] + + +# =============================================================================== +# get_labelFontFamilyFieldNames +# =============================================================================== +def get_labelFontFamilyFieldNames(layer): + """ + ritorna la lista dei campi che concorrono a formare il nome del font dell'etichetta + """ + if layer.type() == QgsMapLayer.VectorLayer and layer.labeling() is not None: + palyr = layer.labeling().settings() + dataDefined = palyr.dataDefinedProperties().property(QgsPalLayerSettings.Family) + return get_activeDataDefinedPropertyFieldNames(layer, dataDefined) + + return [] + + +# =============================================================================== +# get_labelText +# =============================================================================== +def get_labelText(palLayerSettings, feature): + """ + restituisce il testo dell'etichetta + """ + lblFieldName = palLayerSettings.fieldName + if lblFieldName == "": + return "" + if palLayerSettings.isExpression: # Is this label made from a expression string eg FieldName || 'mm'. + expr = QgsExpression(lblFieldName) + val = expr.evaluate(feature) + else: + val = feature.attribute(lblFieldName) + + return val + + +# =============================================================================== +# get_labelFontSize +# =============================================================================== +def get_labelFontSize(palLayerSettings, feature): + """ + restituisce la dimensione del font dell'etichetta come numero intero + """ + val = None + dataDefined = palLayerSettings.dataDefinedProperties().property(QgsPalLayerSettings.Size) + if (dataDefined is not None) and dataDefined.isActive(): + if len(dataDefined.expressionString()) > 0: + expr = QgsExpression(dataDefined.expressionString()) + val = expr.evaluate(feature) + else: + val = feature.attribute(dataDefined.field()) + else: + # Returns the label text formatting settings, e.g., font settings, buffer settings, etc. + fmt = palLayerSettings.format() + val = fmt.font().pointSize() # Returns the point size of the font + + return None if val is None else int(val) + + +# =============================================================================== +# get_labelFontFamily +# =============================================================================== +def get_labelFontFamily(palLayerSettings, feature): + """ + restituisce il nome del font dell'etichetta + """ + val = None + dataDefined = palLayerSettings.dataDefinedProperties().property(QgsPalLayerSettings.Family) + if (dataDefined is not None) and dataDefined.isActive(): + if len(dataDefined.expressionString()) > 0: + expr = QgsExpression(dataDefined.expressionString()) + val = expr.evaluate(feature) + else: + val = feature.attribute(dataDefined.field()) + else: + # Returns the label text formatting settings, e.g., font settings, buffer settings, etc. + fmt = palLayerSettings.format() + val = fmt.font().family() + + return val + + +# =============================================================================== +# get_labelFontTextNamedStyle +# =============================================================================== +def get_labelFontTextNamedStyle(palLayerSettings, feature): + """ + restituisce il nome dello stile del font dell'etichetta + """ + val = None + dataDefined = palLayerSettings.dataDefinedProperties().property(QgsPalLayerSettings.FontStyle) + if (dataDefined is not None) and dataDefined.isActive(): + if len(dataDefined.expressionString()) > 0: + expr = QgsExpression(dataDefined.expressionString()) + val = expr.evaluate(feature) + else: + val = feature.attribute(dataDefined.field()) + else: + val = palLayerSettings.textNamedStyle + + return val + + +# =============================================================================== +# get_labelIsBold +# =============================================================================== +def get_labelIsBold(palLayerSettings, feature): + """ + restituisce se il font dell'etichetta é grassetto + """ + val = None + dataDefined = palLayerSettings.dataDefinedProperties().property(QgsPalLayerSettings.Bold) + if (dataDefined is not None) and dataDefined.isActive(): + if len(dataDefined.expressionString()) > 0: + expr = QgsExpression(dataDefined.expressionString()) + val = expr.evaluate(feature) + else: + val = feature.attribute(dataDefined.field()) + else: + # Returns the label text formatting settings, e.g., font settings, buffer settings, etc. + fmt = palLayerSettings.format() + val = fmt.font().bold() + + return val + + +# =============================================================================== +# get_labelIsItalic +# =============================================================================== +def get_labelIsItalic(palLayerSettings, feature): + """ + restituisce se il font dell'etichetta é italico + """ + val = None + dataDefined = palLayerSettings.dataDefinedProperties().property(QgsPalLayerSettings.Italic) + if (dataDefined is not None) and dataDefined.isActive(): + if len(dataDefined.expressionString()) > 0: + expr = QgsExpression(dataDefined.expressionString()) + val = expr.evaluate(feature) + else: + val = feature.attribute(dataDefined.field()) + else: + # Returns the label text formatting settings, e.g., font settings, buffer settings, etc. + fmt = palLayerSettings.format() + val = fmt.font().italic() + + return val + + +# =============================================================================== +# get_labelIsUnderline +# =============================================================================== +def get_labelIsUnderline(palLayerSettings, feature): + """ + restituisce se il font dell'etichetta é sottolineato + """ + val = None + dataDefined = palLayerSettings.dataDefinedProperties().property(QgsPalLayerSettings.Underline) + if (dataDefined is not None) and dataDefined.isActive(): + if len(dataDefined.expressionString()) > 0: + expr = QgsExpression(dataDefined.expressionString()) + val = expr.evaluate(feature) + else: + val = feature.attribute(dataDefined.field()) + else: + # Returns the label text formatting settings, e.g., font settings, buffer settings, etc. + fmt = palLayerSettings.format() + val = fmt.font().underline() + + return val + + +# =============================================================================== +# get_labelIsStrikeOut +# =============================================================================== +def get_labelIsStrikeOut(palLayerSettings, feature): + """ + restituisce se il font dell'etichetta é barrato + """ + val = None + dataDefined = palLayerSettings.dataDefinedProperties().property(QgsPalLayerSettings.Strikeout) + if (dataDefined is not None) and dataDefined.isActive(): + if len(dataDefined.expressionString()) > 0: + expr = QgsExpression(dataDefined.expressionString()) + val = expr.evaluate(feature) + else: + val = feature.attribute(dataDefined.field()) + else: + # Returns the label text formatting settings, e.g., font settings, buffer settings, etc. + fmt = palLayerSettings.format() + val = fmt.font().strikeOut() + + return val + + +# =============================================================================== +# get_labelFontCase +# =============================================================================== +def get_labelFontCase(palLayerSettings, feature): + """ + restituisce se il font dell'etichetta é maiuscolo/minuscolo e varie opzioni: + QFont::MixedCase 0 This is the normal text rendering option where no capitalization change is applied. + QFont::AllUppercase 1 This alters the text to be rendered in all uppercase type. + QFont::AllLowercase 2 This alters the text to be rendered in all lowercase type. + QFont::SmallCaps 3 This alters the text to be rendered in small-caps type. + QFont::Capitalize 4 This alters the text to be rendered with the first character of each word as an uppercase character. + """ + val = None + dataDefined = palLayerSettings.dataDefinedProperties().property(QgsPalLayerSettings.FontCase) + if (dataDefined is not None) and dataDefined.isActive(): + if len(dataDefined.expressionString()) > 0: + expr = QgsExpression(dataDefined.expressionString()) + val = expr.evaluate(feature) + else: + val = feature.attribute(dataDefined.field()) + else: + # Returns the label text formatting settings, e.g., font settings, buffer settings, etc. + fmt = palLayerSettings.format() + val = fmt.font().capitalization() + + return val + + +# =============================================================================== +# get_labelFontSizeInMapUnits +# =============================================================================== +def get_labelFontSizeInMapUnits(palLayerSettings, feature): + """ + restituisce se l'unità del font dell'etichetta é in unità mappa + """ + val = None + dataDefined = palLayerSettings.dataDefinedProperties().property(QgsPalLayerSettings.FontSizeUnit) + if (dataDefined is not None) and dataDefined.isActive(): + if len(dataDefined.expressionString()) > 0: + expr = QgsExpression(dataDefined.expressionString()) + val = True if expr.evaluate(feature) == QgsUnitTypes.RenderMapUnits else False + else: + val = True if feature.attribute(dataDefined.field()) == QgsUnitTypes.RenderMapUnits else False + else: + # Returns the label text formatting settings, e.g., font settings, buffer settings, etc. + fmt = palLayerSettings.format() + val = True if fmt.sizeUnit() == QgsUnitTypes.RenderMapUnits else False + + return val + + +# =============================================================================== +# get_labelRot +# =============================================================================== +def get_labelRot(palLayerSettings, feature): + """ + restituisce la rotazione dell'etichetta + """ + val = 0 + dataDefined = palLayerSettings.dataDefinedProperties().property(QgsPalLayerSettings.LabelRotation) + if (dataDefined is not None) and dataDefined.isActive(): + if len(dataDefined.expressionString()) > 0: + expr = QgsExpression(dataDefined.expressionString()) + val = expr.evaluate(feature) + else: + val = feature.attribute(dataDefined.field()) + + return val + + +# =============================================================================== +# calculateLabelSize +# =============================================================================== +def calculateLabelSize(layer, feature, canvas): + """ + return size for label in map units + """ + if layer.type() != QgsMapLayer.VectorLayer and layer.labeling() is not None: + return None, None + palyr = layer.labeling().settings() + # Returns the label text formatting settings, e.g., font settings, buffer settings, etc. + fmt = palyr.format() + font = fmt.font() + + text = get_labelText(palyr, feature) + + fontName = get_labelFontFamily(palyr, feature) + if fontName is not None: + font.setFamily(fontName) + + fontBold = get_labelIsBold(palyr, feature) + if fontBold is not None and fontBold: + font.setBold(fontBold) + + fontItalic = get_labelIsItalic(palyr, feature) + if fontItalic is not None and fontItalic: + font.setItalic(fontItalic) + + fontUnderline = get_labelIsUnderline(palyr, feature) + if fontUnderline is not None and fontUnderline: + font.setUnderline(fontUnderline) + + fontStrikeOut = get_labelIsStrikeOut(palyr, feature) + if fontStrikeOut is not None and fontStrikeOut: + font.setStrikeOut(fontStrikeOut) + + fontCase = get_labelFontCase(palyr, feature) + if fontCase is not None: + font.setCapitalization(fontCase) + + fontSize = get_labelFontSize(palyr, feature) + if fontSize is not None: + + isFontSizeInMapUnits = get_labelFontSizeInMapUnits(palyr, feature) + + if isFontSizeInMapUnits: + if fontSize < 100: + font.setPixelSize(fontSize * 100) + else: + font.setPixelSize(fontSize) + + fontMetricsF = QFontMetricsF(font) + rect = fontMetricsF.boundingRect(text) + dimX = rect.width() + dimY = rect.height() + if isFontSizeInMapUnits: + if fontSize < 100: # se dimX e dimY sono già in map units * 10 + return dimX / 100, dimY / 100 + else: + return dimX, dimY + else: + mapToPixel = canvas.getCoordinateTransform() # trasformo pixel in map units + dimX = dimX * mapToPixel.mapUnitsPerPixel() + dimY = dimY * mapToPixel.mapUnitsPerPixel() + return dimX, dimY + + return None, None diff --git a/qad_layer.py b/qad_layer.py index 0a8f5392..0dec4629 100644 --- a/qad_layer.py +++ b/qad_layer.py @@ -1,702 +1,902 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - funzioni per i layer - - ------------------- - begin : 2013-11-15 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -from qgis.gui import * -import re # regular expression - - -from qad_msg import QadMsg -import qad_utils - - -#=============================================================================== -# layerGeometryTypeToStr -#=============================================================================== -def layerGeometryTypeToStr(geomType): - """ - restituisce la stringa corrispondente al tipo di geometria del layer - """ - msg = "" - if (type(geomType) == list or type(geomType) == tuple): # se lista di tipi - for gType in geomType: - if len(msg) > 0: - msg = msg + ", " - msg = msg + layerGeometryTypeToStr(gType) - else: - if geomType == QGis.Point: - msg = QadMsg.translate("QAD", "point") - elif geomType == QGis.Line: - msg = QadMsg.translate("QAD", "line") - elif geomType == QGis.Polygon: - msg = QadMsg.translate("QAD", "polygon") - - return msg - - -#=============================================================================== -# getCurrLayerEditable -#=============================================================================== -def getCurrLayerEditable(canvas, geomType = None): - """ - Ritorna il layer corrente se é aggiornabile e compatibile con il tipo geomType + - eventuale messaggio di errore. - Se é una lista allora verifica che sia compatibile con almeno un tipo nella lista - altrimenti se <> None verifica che sia compatibile con il tipo - """ - vLayer = canvas.currentLayer() - if vLayer is None: - return None, QadMsg.translate("QAD", "\nNo current layer.\n") - - if (vLayer.type() != QgsMapLayer.VectorLayer): - return None, QadMsg.translate("QAD", "\nThe current layer is not a vector layer.\n") - - if geomType is not None: - if (type(geomType) == list or type(geomType) == tuple): # se lista di tipi - if vLayer.geometryType() not in geomType: - errMsg = QadMsg.translate("QAD", "\nThe geometry type of the current layer is {0} and it is not valid.\n") - errMsg = errMsg + QadMsg.translate("QAD", "Admitted {1} layer type only.\n") - errMsg.format(layerGeometryTypeToStr(vLayer.geometryType()), layerGeometryTypeToStr(geomType)) - return None, errMsg.format(layerGeometryTypeToStr(vLayer.geometryType()), layerGeometryTypeToStr(geomType)) - else: - if vLayer.geometryType() != geomType: - errMsg = QadMsg.translate("QAD", "\nThe geometry type of the current layer is {0} and it is not valid.\n") - errMsg = errMsg + QadMsg.translate("QAD", "Admitted {1} layer type only.\n") - errMsg.format(layerGeometryTypeToStr(vLayer.geometryType()), layerGeometryTypeToStr(geomType)) - return None, errMsg.format(layerGeometryTypeToStr(vLayer.geometryType()), layerGeometryTypeToStr(geomType)) - - provider = vLayer.dataProvider() - if not (provider.capabilities() & QgsVectorDataProvider.EditingCapabilities): - return None, QadMsg.translate("QAD", "\nThe current layer is not editable.\n") - - if not vLayer.isEditable(): - return None, QadMsg.translate("QAD", "\nThe current layer is not editable.\n") - - return vLayer, None - - -#=============================================================================== -# addPointToLayer -#=============================================================================== -def addPointToLayer(plugIn, layer, point, transform = True, refresh = True, check_validity = False): - """ - Aggiunge un punto ad un layer. Se il punto é già - nel sistema di coordinate del layer allora non va trasformato se invece é nel - sistema map-coordinate allora transform deve essere = True - """ - if len(points) < 2: - return False - - f = QgsFeature() - - if transform: - transformedPoint = plugIn.canvas.mapRenderer().mapToLayerCoordinates(layer, point) - g = QgsGeometry.fromPoint(transformedPoint) - else: - g = QgsGeometry.fromPoint(point) - - if check_validity: - if not g.isGeosValid(): - return False - - f.setGeometry(g) - - # Add attributefields to feature. - fields = layer.pendingFields() - f.setFields(fields) - - # assegno i valori di default - provider = layer.dataProvider() - for field in fields.toList(): - i = fields.indexFromName(field.name()) - f[field.name()] = provider.defaultValue(i) - - if refresh == True: - plugIn.beginEditCommand("Feature added", layer) - - if layer.addFeature(f, False): - if refresh == True: - plugIn.endEditCommand() - plugIn.setLastEntity(layer, f.id()) - result = True - else: - if refresh == True: - plugIn.destroyEditCommand() - result = False - - return result - - -#=============================================================================== -# addLineToLayer -#=============================================================================== -def addLineToLayer(plugIn, layer, points, transform = True, refresh = True, check_validity = False): - """ - Aggiunge una linea (lista di punti) ad un layer. Se la lista di punti é già - nel sistema di coordinate del layer allora non va trasformata se invece é nel - sistema map-coordinate allora transform deve essere = True - """ - if len(points) < 2: # almeno 2 punti - return False - - f = QgsFeature() - - if transform: - layerPoints = [] - for point in points: - transformedPoint = plugIn.canvas.mapRenderer().mapToLayerCoordinates(layer, point) - layerPoints.append(transformedPoint) - g = QgsGeometry.fromPolyline(layerPoints) - else: - g = QgsGeometry.fromPolyline(points) - - if check_validity: - if not g.isGeosValid(): - return False - - f.setGeometry(g) - - # Add attributefields to feature. - fields = layer.pendingFields() - f.setFields(fields) - - # assegno i valori di default - provider = layer.dataProvider() - for field in fields.toList(): - i = fields.indexFromName(field.name()) - f[field.name()] = provider.defaultValue(i) - - if refresh == True: - plugIn.beginEditCommand("Feature added", layer) - - if layer.addFeature(f, False): - if refresh == True: - plugIn.endEditCommand() - plugIn.setLastEntity(layer, f.id()) - result = True - else: - if refresh == True: - plugIn.destroyEditCommand() - result = False - - return result - - -#=============================================================================== -# addPolygonToLayer -#=============================================================================== -def addPolygonToLayer(plugIn, layer, points, transform = True, refresh = True, check_validity = False): - """ - Aggiunge un poligono (lista di punti) ad un layer. Se la lista di punti é già - nel sistema di coordinate del layer allora non va trasformata se invece é nel - sistema map-coordinate allora transform deve essere = True - """ - if len(points) < 3: # almeno 4 punti (il primo e l'ultimo sono uguali) - return False - - f = QgsFeature() - - if transform: - layerPoints = [] - for point in points: - transformedPoint = plugIn.canvas.mapRenderer().mapToLayerCoordinates(layer, point) - layerPoints.append(transformedPoint) - g = QgsGeometry.fromPolygon([layerPoints]) - else: - g = QgsGeometry.fromPolygon([points]) - - if check_validity: - if not g.isGeosValid(): - return False - - f.setGeometry(g) - - # Add attributefields to feature. - fields = layer.pendingFields() - f.setFields(fields) - - # assegno i valori di default - provider = layer.dataProvider() - for field in fields.toList(): - i = fields.indexFromName(field.name()) - f[field.name()] = provider.defaultValue(i) - - if refresh == True: - plugIn.beginEditCommand("Feature added", layer) - - if layer.addFeature(f, False): - if refresh == True: - plugIn.endEditCommand() - plugIn.setLastEntity(layer, f.id()) - result = True - else: - if refresh == True: - plugIn.destroyEditCommand() - result = False - - return result - - -#=============================================================================== -# addGeomToLayer -#=============================================================================== -def addGeomToLayer(plugIn, layer, geom, coordTransform = None, refresh = True, check_validity = False): - """ - Aggiunge una geometria ad un layer. Se la geometria é da convertire allora - deve essere passato il parametro di tipo QgsCoordinateTransform. - refresh controlla la transazione del comando e il refresh del canvas - """ - f = QgsFeature() - - g = QgsGeometry(geom) - if coordTransform is not None: - g.transform(coordTransform) - - if check_validity: - if not g.isGeosValid(): - return False - - f.setGeometry(g) - - # Add attributefields to feature. - fields = layer.pendingFields() - f.setFields(fields) - - # assegno i valori di default - provider = layer.dataProvider() - for field in fields.toList(): - i = fields.indexFromName(field.name()) - f[field.name()] = provider.defaultValue(i) - - if refresh == True: - plugIn.beginEditCommand("Feature added", layer) - - if layer.addFeature(f, False): - if refresh == True: - plugIn.endEditCommand() - plugIn.setLastEntity(layer, f.id()) - result = True - else: - if refresh == True: - plugIn.destroyEditCommand() - result = False - - return result - - -#=============================================================================== -# addGeomsToLayer -#=============================================================================== -def addGeomsToLayer(plugIn, layer, geoms, coordTransform = None, refresh = True, check_validity = False): - """ - Aggiunge le geometrie ad un layer. Se la geometria é da convertire allora - deve essere passato il parametro di tipo QgsCoordinateTransform. - refresh controlla la transazione del comando e il refresh del canvas - """ - if refresh == True: - plugIn.beginEditCommand("Feature added", layer) - - for geom in geoms: - if addGeomToLayer(plugIn, layer, geom, coordTransform, False, check_validity) == False: - if refresh == True: - plugIn.destroyEditCommand() - return False - - if refresh == True: - plugIn.endEditCommand() - - return True - - -#=============================================================================== -# addFeatureToLayer -#=============================================================================== -def addFeatureToLayer(plugIn, layer, f, coordTransform = None, refresh = True, check_validity = False): - """ - Aggiunge una feature ad un layer. Se la geometria é da convertire allora - deve essere passato il parametro di tipo QgsCoordinateTransform. - controlla la transazione del comando e il refresh del canvas - """ - - if coordTransform is not None: - g = QgsGeometry(f.geometry()) - g.transform(coordTransform) - f.setGeometry(g) - - if check_validity: - if not f.geometry().isGeosValid(): - return False - - if refresh == True: - plugIn.beginEditCommand("Feature added", layer) - - if layer.addFeature(f, False): - if refresh == True: - plugIn.endEditCommand() - - plugIn.setLastEntity(layer, f.id()) - result = True - else: - if refresh == True: - plugIn.destroyEditCommand() - result = False - - return result - - -#=============================================================================== -# addFeaturesToLayer -#=============================================================================== -def addFeaturesToLayer(plugIn, layer, features, coordTransform = None, refresh = True, check_validity = False): - """ - Aggiunge le feature ad un layer. Se la geometria é da convertire allora - deve essere passato il parametro di tipo QgsCoordinateTransform. - controlla la transazione del comando e il refresh del canvas - """ - if refresh == True: - plugIn.beginEditCommand("Feature added", layer) - - for f in features: - if addFeatureToLayer(plugIn, layer, f, coordTransform, False, check_validity) == False: - if refresh == True: - plugIn.destroyEditCommand() - return False - - if refresh == True: - plugIn.endEditCommand() - - return True - - -#=============================================================================== -# updateFeatureToLayer -#=============================================================================== -def updateFeatureToLayer(plugIn, layer, f, refresh = True, check_validity = False): - """ - Aggiorna la feature ad un layer. - refresh controlla la transazione del comando e il refresh del canvas - """ - if check_validity: - if not f.geometry().isGeosValid(): - return False - - if refresh == True: - plugIn.beginEditCommand("Feature modified", layer) - - if layer.updateFeature(f): - if refresh == True: - plugIn.endEditCommand() - - result = True - else: - if refresh == True: - plugIn.destroyEditCommand() - result = False - - return result - - -#=============================================================================== -# updateFeaturesToLayer -#=============================================================================== -def updateFeaturesToLayer(plugIn, layer, features, refresh = True, check_validity = False): - """ - Aggiorna le features ad un layer. - refresh controlla la transazione del comando e il refresh del canvas - """ - if refresh == True: - plugIn.beginEditCommand("Feature modified", layer) - - for f in features: - if updateFeatureToLayer(plugIn, layer, f, False, check_validity) == False: - if refresh == True: - plugIn.destroyEditCommand() - return False - - if refresh == True: - plugIn.endEditCommand() - - return True - - -#=============================================================================== -# deleteFeatureToLayer -#=============================================================================== -def deleteFeatureToLayer(plugIn, layer, featureId, refresh = True): - """ - Cancella la feature da un layer. - refresh controlla la transazione del comando e il refresh del canvas - """ - if refresh == True: - plugIn.beginEditCommand("Feature deleted", layer) - - if layer.deleteFeature(featureId): - if refresh == True: - plugIn.endEditCommand() - - result = True - else: - if refresh == True: - plugIn.destroyEditCommand() - result = False - - return result - - -#=============================================================================== -# deleteFeaturesToLayer -#=============================================================================== -def deleteFeaturesToLayer(plugIn, layer, featureIds, refresh = True): - """ - Aggiorna le features ad un layer. - refresh controlla la transazione del comando e il refresh del canvas - """ - if refresh == True: - plugIn.beginEditCommand("Feature deleted", layer) - - for featureId in featureIds: - if deleteFeatureToLayer(plugIn, layer, featureId, False) == False: - if refresh == True: - plugIn.destroyEditCommand() - return False - - if refresh == True: - plugIn.endEditCommand() - - return True - - -#=============================================================================== -# getLayersByName -#=============================================================================== -def getLayersByName(regularExprName): - """ - Ritorna la lista dei layer il cui nome soddisfa la regular expression di ricerca - (per conversione da wildcards vedi la funzione wildCard2regularExpr) - """ - result = [] - regExprCompiled = re.compile(regularExprName) - for layer in QgsMapLayerRegistry.instance().mapLayers().values(): - if re.match(regExprCompiled, layer.name()): - if layer.isValid(): - result.append(layer) - - return result - - -#=============================================================================== -# getLayerById -#=============================================================================== -def getLayerById(id): - """ - Ritorna il layer con id noto - """ - for layer in QgsMapLayerRegistry.instance().mapLayers().values(): - if layer.id() == id: - return layer - return None - - -#=============================================================================== -# get_symbolRotationFieldName -#=============================================================================== -def get_symbolRotationFieldName(layer): - """ - return rotation field name (or empty string if not set or not supported by renderer) - """ - if (layer.type() != QgsMapLayer.VectorLayer) or (layer.geometryType() != QGis.Point): - return "" - - try: - expr = QgsSymbolLayerV2Utils.fieldOrExpressionToExpression(layer.rendererV2().rotationField()) - columns = expr.referencedColumns() - return columns[0] if len(columns) == 1 else "" - except: - return "" - - -#=============================================================================== -# get_symbolScaleFieldName -#=============================================================================== -def get_symbolScaleFieldName(layer): - """ - return symbol scale field name (or empty string if not set or not supported by renderer) - """ - if (layer.type() != QgsMapLayer.VectorLayer) or (layer.geometryType() != QGis.Point): - return "" - - try: - expr = QgsSymbolLayerV2Utils.fieldOrExpressionToExpression(layer.rendererV2().sizeScaleField()) - columns = expr.referencedColumns() - return columns[0] if len(columns) == 1 else "" - except: - return "" - - - -#=============================================================================== -# isTextLayer -#=============================================================================== -def isTextLayer(layer): - """ - return True se il layer é di tipo testo - """ - # deve essere un VectorLayer di tipo puntuale - if (layer.type() != QgsMapLayer.VectorLayer) or (layer.geometryType() != QGis.Point): - return False - # deve avere il-i simbolo-i trasparenti almeno entro il 10% - for symbol in layer.rendererV2().symbols(): - if symbol.alpha() > 0.1: # Get alpha transparency 1 for opaque, 0 for invisible - return False - # deve avere etichette - palyr = QgsPalLayerSettings() - palyr.readFromLayer(layer) - if palyr.enabled == False: - return False - - return True - - -#=============================================================================== -# isSymbolLayer -#=============================================================================== -def isSymbolLayer(layer): - """ - return True se il layer é di tipo simbolo - """ - # deve essere un VectorLayer di tipo puntuale - if (layer.type() != QgsMapLayer.VectorLayer) or (layer.geometryType() != QGis.Point): - return False - # se la rotazione é letta da un campo ricordarsi che per i simboli la rotazione é in senso orario - # quindi usare l'espressione 360 - - # se non é un layer di tipo testo é di tipo simbolo - return False if isTextLayer(layer) else True - - -#============================================================================ -# INIZIO - Gestione layer temporanei di QAD -#============================================================================ - - -#=============================================================================== -# createQADTempLayer -#=============================================================================== -def createQADTempLayer(plugIn, GeomType): - """ - Aggiunge tre liste di geometrie rispettivamente a tre layer temporanei di QAD (uno per tipologia di - geometria). Se le geometrie sono da convertire allora - deve essere passato il parametro di tipo QgsCoordinateTransform. - = the authority identifier for this srs - """ - layer = None - epsg = plugIn.iface.mapCanvas().mapRenderer().destinationCrs().authid() - - if GeomType == QGis.Point: - layerName = QadMsg.translate("QAD", "QAD - Temporary points") - layerList = getLayersByName(qad_utils.wildCard2regularExpr(layerName)) - if len(layerList) == 0: - layer = QgsVectorLayer("Point?crs=%s&index=yes" % epsg, layerName, "memory") - QgsMapLayerRegistry.instance().addMapLayers([layer], True) - else: - layer = layerList[0] - elif GeomType == QGis.Line: - layerName = QadMsg.translate("QAD", "QAD - Temporary lines") - layerList = getLayersByName(qad_utils.wildCard2regularExpr(layerName)) - if len(layerList) == 0: - layer = QgsVectorLayer("LineString?crs=%s&index=yes" % epsg, layerName, "memory") - QgsMapLayerRegistry.instance().addMapLayers([layer], True) - else: - layer = layerList[0] - elif GeomType == QGis.Polygon: - layerName = QadMsg.translate("QAD", "QAD - Temporary polygons") - layerList = getLayersByName(qad_utils.wildCard2regularExpr(layerName)) - if len(layerList) == 0: - layer = QgsVectorLayer("Polygon?crs=%s&index=yes" % epsg, layerName, "memory") - QgsMapLayerRegistry.instance().addMapLayers([layer], True) - else: - layer = layerList[0] - - layer.startEditing() - return layer - - -#=============================================================================== -# addGeometriesToQADTempLayers -#=============================================================================== -def addGeometriesToQADTempLayers(plugIn, pointGeoms = None, lineGeoms = None, polygonGeoms = None, \ - crs = None, refresh = True): - """ - Aggiunge tre liste di geometrie rispettivamente a tre layer temporanei di QAD (uno per tipologia di - geometria). Se le geometrie sono da convertire allora - deve essere passato il parametro che definisce il sistema di coordinate delle geometrie. - """ - if pointGeoms is not None and len(pointGeoms) > 0: - layer = createQADTempLayer(plugIn, QGis.Point) - if layer is None: - return False - if crs is None: - # plugIn, layer, geoms, coordTransform , refresh, check_validity - if addGeomsToLayer(plugIn, layer, pointGeoms, None, refresh, False) == False: - return False - else: - # plugIn, layer, geoms, coordTransform , refresh, check_validity - if addGeomsToLayer(plugIn, layer, pointGeoms, QgsCoordinateTransform(crs, layer.crs()), \ - refresh, False) == False: - return False - - if lineGeoms is not None and len(lineGeoms) > 0: - layer = createQADTempLayer(plugIn, QGis.Line) - if layer is None: - return False - if crs is None: - # plugIn, layer, geoms, coordTransform , refresh, check_validity - if addGeomsToLayer(plugIn, layer, lineGeoms, None, refresh, False) == False: - return False - else: - # plugIn, layer, geoms, coordTransform , refresh, check_validity - if addGeomsToLayer(plugIn, layer, lineGeoms, QgsCoordinateTransform(crs, layer.crs()), \ - refresh, False) == False: - return False - - if polygonGeoms is not None and len(polygonGeoms) > 0: - layer = createQADTempLayer(plugIn, QGis.Polygon) - if layer is None: - return False - if crs is None: - # plugIn, layer, geoms, coordTransform , refresh, check_validity - if addGeomsToLayer(plugIn, layer, polygonGeoms, None, refresh, False) == False: - return False - else: - # plugIn, layer, geoms, coordTransform , refresh, check_validity - if addGeomsToLayer(plugIn, layer, polygonGeoms, QgsCoordinateTransform(crs, layer.crs()), \ - refresh, False) == False: - return False - - return True - - \ No newline at end of file +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + funzioni per i layer + + ------------------- + begin : 2013-11-15 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.PyQt.QtCore import * +from qgis.PyQt.QtGui import * +from qgis.core import * +from qgis.gui import * +import re # regular expression + + +from .qad_msg import QadMsg +from .qad_label import get_labelFieldNames + + +# =============================================================================== +# layerGeometryTypeToStr +# =============================================================================== +def layerGeometryTypeToStr(geomType): + """ + restituisce la stringa corrispondente al tipo di geometria del layer + """ + msg = "" + if (type(geomType) == list or type(geomType) == tuple): # se lista di tipi + for gType in geomType: + if len(msg) > 0: + msg = msg + ", " + msg = msg + layerGeometryTypeToStr(gType) + else: + if geomType == QgsWkbTypes.PointGeometry: + msg = QadMsg.translate("QAD", "point") + elif geomType == QgsWkbTypes.LineGeometry: + msg = QadMsg.translate("QAD", "line") + elif geomType == QgsWkbTypes.PolygonGeometry: + msg = QadMsg.translate("QAD", "polygon") + + return msg + + +# =============================================================================== +# getCurrLayerEditable +# =============================================================================== +def getCurrLayerEditable(canvas, geomType = None): + """ + Ritorna il layer corrente se é aggiornabile e compatibile con il tipo geomType + + eventuale messaggio di errore. + Se é una lista allora verifica che sia compatibile con almeno un tipo nella lista + altrimenti se <> None verifica che sia compatibile con il tipo + """ + vLayer = canvas.currentLayer() + if vLayer is None: + return None, QadMsg.translate("QAD", "\nNo current layer.\n") + + if (vLayer.type() != QgsMapLayer.VectorLayer): + return None, QadMsg.translate("QAD", "\nThe current layer is not a vector layer.\n") + + if geomType is not None: + if (type(geomType) == list or type(geomType) == tuple): # se lista di tipi + if vLayer.geometryType() not in geomType: + errMsg = QadMsg.translate("QAD", "\nThe geometry type of the current layer is {0} and it is not valid.\n") + errMsg = errMsg + QadMsg.translate("QAD", "Admitted {1} layer type only.\n") + errMsg.format(layerGeometryTypeToStr(vLayer.geometryType()), layerGeometryTypeToStr(geomType)) + return None, errMsg.format(layerGeometryTypeToStr(vLayer.geometryType()), layerGeometryTypeToStr(geomType)) + else: + if vLayer.geometryType() != geomType: + errMsg = QadMsg.translate("QAD", "\nThe geometry type of the current layer is {0} and it is not valid.\n") + errMsg = errMsg + QadMsg.translate("QAD", "Admitted {1} layer type only.\n") + errMsg.format(layerGeometryTypeToStr(vLayer.geometryType()), layerGeometryTypeToStr(geomType)) + return None, errMsg.format(layerGeometryTypeToStr(vLayer.geometryType()), layerGeometryTypeToStr(geomType)) + + provider = vLayer.dataProvider() + if not (provider.capabilities() & QgsVectorDataProvider.EditingCapabilities): + return None, QadMsg.translate("QAD", "\nThe current layer is not editable.\n") + + if not vLayer.isEditable(): + return None, QadMsg.translate("QAD", "\nThe current layer is not editable.\n") + + return vLayer, None + + +# =============================================================================== +# addPointToLayer +# =============================================================================== +def addPointToLayer(plugIn, layer, point, transform = True, refresh = True, check_validity = False, openForm = True): + """ + Aggiunge un punto ad un layer. Se il punto é già + nel sistema di coordinate del layer allora non va trasformato se invece é nel + sistema map-coordinate allora transform deve essere = True + """ + if transform: + transformedPoint = plugIn.canvas.mapSettings().mapToLayerCoordinates(layer, point) + g = QgsGeometry.fromPointXY(transformedPoint) + else: + g = QgsGeometry.fromPointXY(point) + + return addGeomToLayer(plugIn, layer, g, None, refresh, check_validity, openForm) + +# f = QgsFeature() +# +# if transform: +# transformedPoint = plugIn.canvas.mapSettings().mapToLayerCoordinates(layer, point) +# g = QgsGeometry.fromPointXY(transformedPoint) +# else: +# g = QgsGeometry.fromPointXY(point) +# +# if check_validity: +# if not g.isGeosValid(): +# return False +# +# f.setGeometry(g) +# +# # Add attributefields to feature. +# fields = layer.fields() +# f.setFields(fields) +# +# # assegno i valori di default +# provider = layer.dataProvider() +# for field in fields.toList(): +# i = fields.indexFromName(field.name()) +# f[field.name()] = provider.defaultValue(i) +# +# if openForm == True: +# if get_Disable_enter_attribute_values_dialog() == False: +# if plugIn.iface.openFeatureForm(layer, f, True) == False: +# return False +# +# if refresh == True: +# plugIn.beginEditCommand("Feature added", layer) +# +# if layer.addFeature(f): +# if refresh == True: +# plugIn.endEditCommand() +# plugIn.setLastEntity(layer, f.id()) +# result = True +# else: +# if refresh == True: +# plugIn.destroyEditCommand() +# result = False +# +# return result + + +# =============================================================================== +# addLineToLayer +# =============================================================================== +def addLineToLayer(plugIn, layer, points, transform = True, refresh = True, check_validity = False, openForm = True): + """ + Aggiunge una linea (lista di punti) ad un layer. Se la lista di punti é già + nel sistema di coordinate del layer allora non va trasformata se invece é nel + sistema map-coordinate allora transform deve essere = True + """ + if len(points) < 2: # almeno 2 punti + return False + + if transform: + layerPoints = [] + for point in points: + transformedPoint = plugIn.canvas.mapSettings().mapToLayerCoordinates(layer, point) + layerPoints.append(transformedPoint) + g = QgsGeometry.fromPolylineXY(layerPoints) + else: + g = QgsGeometry.fromPolylineXY(points) + + return addGeomToLayer(plugIn, layer, g, None, refresh, check_validity, openForm) + +# if len(points) < 2: # almeno 2 punti +# return False +# +# f = QgsFeature() +# +# if transform: +# layerPoints = [] +# for point in points: +# transformedPoint = plugIn.canvas.mapSettings().mapToLayerCoordinates(layer, point) +# layerPoints.append(transformedPoint) +# g = QgsGeometry.fromPolylineXY(layerPoints) +# else: +# g = QgsGeometry.fromPolylineXY(points) +# +# if check_validity: +# if not g.isGeosValid(): +# return False +# +# f.setGeometry(g) +# +# # Add attributefields to feature. +# fields = layer.fields() +# f.setFields(fields) +# +# # assegno i valori di default +# provider = layer.dataProvider() +# for field in fields.toList(): +# i = fields.indexFromName(field.name()) +# f[field.name()] = provider.defaultValue(i) +# +# if openForm == True: +# if get_Disable_enter_attribute_values_dialog() == False: +# if plugIn.iface.openFeatureForm(layer, f, True) == False: +# return False +# +# if refresh == True: +# plugIn.beginEditCommand("Feature added", layer) +# +# if layer.addFeature(f): +# if refresh == True: +# plugIn.endEditCommand() +# plugIn.setLastEntity(layer, f.id()) +# result = True +# else: +# if refresh == True: +# plugIn.destroyEditCommand() +# result = False +# +# return result + + +# =============================================================================== +# addPolygonToLayer +# =============================================================================== +def addPolygonToLayer(plugIn, layer, points, transform = True, refresh = True, check_validity = False, openForm = True): + """ + Aggiunge un poligono (lista di punti) ad un layer. Se la lista di punti é già + nel sistema di coordinate del layer allora non va trasformata se invece é nel + sistema map-coordinate allora transform deve essere = True + """ + if len(points) < 3: # almeno 4 punti (il primo e l'ultimo sono uguali) + return False + + if transform: + layerPoints = [] + for point in points: + transformedPoint = plugIn.canvas.mapSettings().mapToLayerCoordinates(layer, point) + layerPoints.append(transformedPoint) + g = QgsGeometry.fromPolygonXY([layerPoints]) + else: + g = QgsGeometry.fromPolygonXY([points]) + + return addGeomToLayer(plugIn, layer, g, None, refresh, check_validity, openForm) + +# if len(points) < 3: # almeno 4 punti (il primo e l'ultimo sono uguali) +# return False +# +# f = QgsFeature() +# +# if transform: +# layerPoints = [] +# for point in points: +# transformedPoint = plugIn.canvas.mapSettings().mapToLayerCoordinates(layer, point) +# layerPoints.append(transformedPoint) +# g = QgsGeometry.fromPolygonXY([layerPoints]) +# else: +# g = QgsGeometry.fromPolygonXY([points]) +# +# if check_validity: +# if not g.isGeosValid(): +# return False +# +# f.setGeometry(g) +# +# # Add attributefields to feature. +# fields = layer.fields() +# f.setFields(fields) +# +# # assegno i valori di default +# provider = layer.dataProvider() +# for field in fields.toList(): +# i = fields.indexFromName(field.name()) +# f[field.name()] = provider.defaultValue(i) +# +# if openForm == True: +# if get_Disable_enter_attribute_values_dialog() == False: +# if plugIn.iface.openFeatureForm(layer, f, True) == False: +# return False +# +# if refresh == True: +# plugIn.beginEditCommand("Feature added", layer) +# +# if layer.addFeature(f): +# if refresh == True: +# plugIn.endEditCommand() +# plugIn.setLastEntity(layer, f.id()) +# result = True +# else: +# if refresh == True: +# plugIn.destroyEditCommand() +# result = False +# +# return result + + +# =============================================================================== +# addGeomToLayer +# =============================================================================== +def addGeomToLayer(plugIn, layer, geom, coordTransform = None, refresh = True, check_validity = False, openForm = True): + """ + Aggiunge una geometria ad un layer. Se la geometria é da convertire allora + deve essere passato il parametro di tipo QgsCoordinateTransform. + refresh controlla la transazione del comando e il refresh del canvas + """ + g = QgsGeometry(geom) + if coordTransform is not None: + g.transform(coordTransform) + + if check_validity: + if not g.isGeosValid(): + return False + + f = QgsVectorLayerUtils.createFeature(layer, g, {}, layer.createExpressionContext()) + if refresh == True: plugIn.beginEditCommand("Feature added", layer) + if layer.addFeature(f) == False: + if refresh == True: plugIn.destroyEditCommand() + return False + + if openForm == True: + if get_Disable_enter_attribute_values_dialog() == False: + if plugIn.iface.openFeatureForm(layer, f) == False: + if refresh == True: plugIn.destroyEditCommand() + re = layer.deleteFeature(f.id()) + return False + + if refresh == True: plugIn.endEditCommand() + plugIn.setLastEntity(layer, f.id()) + + return True + +# f = QgsFeature() +# +# g = QgsGeometry(geom) +# if coordTransform is not None: +# g.transform(coordTransform) +# +# if check_validity: +# if not g.isGeosValid(): +# return False +# +# f.setGeometry(g) +# +# # Add attribute fields to feature. +# fields = layer.fields() +# f.setFields(fields) +# +# # assegno i valori di default +# provider = layer.dataProvider() +# for field in fields.toList(): +# i = fields.indexFromName(field.name()) +# f[field.name()] = provider.defaultValue(i) +# +# f = QgsVectorLayerUtils.createFeature(layer, g, {}, layer.createExpressionContext()) +# +# if openForm == True: +# if get_Disable_enter_attribute_values_dialog() == False: +# if plugIn.iface.openFeatureForm(layer, f) == False: +# return False +# +# if refresh == True: +# plugIn.beginEditCommand("Feature added", layer) +# +# if layer.addFeature(f): +# if refresh == True: +# plugIn.endEditCommand() +# plugIn.setLastEntity(layer, f.id()) +# result = True +# else: +# if refresh == True: +# plugIn.destroyEditCommand() +# result = False +# +# return result + +# =============================================================================== +# addGeomsToLayer +# =============================================================================== +def addGeomsToLayer(plugIn, layer, geoms, coordTransform = None, refresh = True, check_validity = False): + """ + Aggiunge le geometrie ad un layer. Se la geometria é da convertire allora + deve essere passato il parametro di tipo QgsCoordinateTransform. + refresh controlla la transazione del comando e il refresh del canvas + """ + if refresh == True: + plugIn.beginEditCommand("Feature added", layer) + + for geom in geoms: + if addGeomToLayer(plugIn, layer, geom, coordTransform, False, check_validity) == False: + if refresh == True: + plugIn.destroyEditCommand() + return False + + if refresh == True: + plugIn.endEditCommand() + + return True + + +# =============================================================================== +# addFeatureToLayer +# =============================================================================== +def addFeatureToLayer(plugIn, layer, f, coordTransform = None, refresh = True, check_validity = False, openForm = True): + """ + Aggiunge una feature ad un layer. Se la geometria é da convertire allora + deve essere passato il parametro di tipo QgsCoordinateTransform. + controlla la transazione del comando e il refresh del canvas + """ + + if coordTransform is not None: + g = QgsGeometry(f.geometry()) + g.transform(coordTransform) + f.setGeometry(g) + + if check_validity: + if not f.geometry().isGeosValid(): + return False + + if openForm == True: + if get_Disable_enter_attribute_values_dialog() == False: + if plugIn.iface.openFeatureForm(layer, f, True) == False: + return False + + if refresh == True: + plugIn.beginEditCommand("Feature added", layer) + + # use default value for primary key fields if it's NOT NULL + provider = layer.dataProvider() + pkAttrList = layer.primaryKeyAttributes() + count = layer.fields().count() + i = 0 + while i < count: + if i in pkAttrList: + defVal = provider.defaultValue(i) + f[i] = defVal + # if defVal is not None or layer.providerType() == "spatialite": + # f[i] = provider.defaultValue(i) + i = i + 1 + + if layer.addFeature(f): + if refresh == True: + plugIn.endEditCommand() + layer.triggerRepaint() + + plugIn.setLastEntity(layer, f.id()) + result = True + else: + if refresh == True: + plugIn.destroyEditCommand() + result = False + + return result + + +# =============================================================================== +# addFeaturesToLayer +# =============================================================================== +def addFeaturesToLayer(plugIn, layer, features, coordTransform = None, refresh = True, check_validity = False): + """ + Aggiunge le feature ad un layer. Se la geometria é da convertire allora + deve essere passato il parametro di tipo QgsCoordinateTransform. + controlla la transazione del comando e il refresh del canvas + """ + if refresh == True: + plugIn.beginEditCommand("Feature added", layer) + + for f in features: + if addFeatureToLayer(plugIn, layer, f, coordTransform, False, check_validity, False) == False: + if refresh == True: + plugIn.destroyEditCommand() + return False + + if refresh == True: + plugIn.endEditCommand() + + return True + + +# =============================================================================== +# updateFeatureToLayer +# =============================================================================== +def updateFeatureToLayer(plugIn, layer, f, refresh = True, check_validity = False): + """ + Aggiorna la feature ad un layer. + refresh controlla la transazione del comando e il refresh del canvas + """ + if check_validity: + if not f.geometry().isGeosValid(): + return False + + if refresh == True: + plugIn.beginEditCommand("Feature modified", layer) + + if layer.updateFeature(f): + if refresh == True: + plugIn.endEditCommand() + + result = True + else: + if refresh == True: + plugIn.destroyEditCommand() + result = False + + return result + + +# =============================================================================== +# updateFeaturesToLayer +# =============================================================================== +def updateFeaturesToLayer(plugIn, layer, features, refresh = True, check_validity = False): + """ + Aggiorna le features ad un layer. + refresh controlla la transazione del comando e il refresh del canvas + """ + if refresh == True: + plugIn.beginEditCommand("Feature modified", layer) + + for f in features: + if updateFeatureToLayer(plugIn, layer, f, False, check_validity) == False: + if refresh == True: + plugIn.destroyEditCommand() + return False + + if refresh == True: + plugIn.endEditCommand() + + return True + + +# =============================================================================== +# deleteFeatureToLayer +# =============================================================================== +def deleteFeatureToLayer(plugIn, layer, featureId, refresh = True): + """ + Cancella la feature da un layer. + refresh controlla la transazione del comando e il refresh del canvas + """ + if refresh == True: + plugIn.beginEditCommand("Feature deleted", layer) + + if layer.deleteFeature(featureId): + if refresh == True: + plugIn.endEditCommand() + + result = True + else: + if refresh == True: + plugIn.destroyEditCommand() + result = False + + return result + + +# =============================================================================== +# deleteFeaturesToLayer +# =============================================================================== +def deleteFeaturesToLayer(plugIn, layer, featureIds, refresh = True): + """ + Aggiorna le features ad un layer. + refresh controlla la transazione del comando e il refresh del canvas + """ + if refresh == True: + plugIn.beginEditCommand("Feature deleted", layer) + + for featureId in featureIds: + if deleteFeatureToLayer(plugIn, layer, featureId, False) == False: + if refresh == True: + plugIn.destroyEditCommand() + return False + + if refresh == True: + plugIn.endEditCommand() + + return True + + +# =============================================================================== +# getLayersByName +# =============================================================================== +def getLayersByName(regularExprName): + """ + Ritorna la lista dei layer il cui nome soddisfa la regular expression di ricerca + (per conversione da wildcards vedi la funzione wildCard2regularExpr) + the regular expression to only match if the text is an exact match is + (for example, to match for abc, then 1abc1, 1abc, and abc1 would not match): + use the start and end delimiters: ^abc$ + """ + result = [] + regExprCompiled = re.compile(regularExprName) + for layer in QgsProject.instance().mapLayers().values(): + if re.match(regExprCompiled, layer.name()): + if layer.isValid(): + result.append(layer) + + return result + + +# =============================================================================== +# getLayerById +# =============================================================================== +def getLayerById(id): + """ + Ritorna il layer con id noto + """ + for layer in QgsProject.instance().mapLayers().values(): + if layer.id() == id: + return layer + return None + + +# =============================================================================== +# get_symbolRotationFieldName +# =============================================================================== +def get_symbolRotationFieldName(layer): + """ + return rotation field name (or empty string if not set or not supported by renderer) + """ + if (layer.type() != QgsMapLayer.VectorLayer) or (layer.geometryType() != QgsWkbTypes.PointGeometry): + return "" + + try: + renderer = layer.renderer() + expr = renderer.symbol().dataDefinedAngle().asExpression() + expr = QgsSymbolLayerUtils.fieldOrExpressionToExpression(expr) + columns = expr.referencedColumns() + if len(columns) == 1: + for column in columns: + return column + else: + return "" + except: + return "" + + +# =============================================================================== +# get_symbolScaleFieldName +# =============================================================================== +def get_symbolScaleFieldName(layer): + """ + return symbol scale field name (or empty string if not set or not supported by renderer) + """ + if (layer.type() != QgsMapLayer.VectorLayer) or (layer.geometryType() != QgsWkbTypes.PointGeometry): + return "" + + try: + renderer = layer.renderer() + expr = renderer.symbol().dataDefinedSize().asExpression() + expr = QgsSymbolLayerUtils.fieldOrExpressionToExpression(expr) + columns = expr.referencedColumns() + if len(columns) == 1: + for column in columns: + return column + else: + return "" + + except: + return "" + + + +# =============================================================================== +# isTextLayer +# =============================================================================== +def isTextLayer(layer): + """ + return True se il layer é di tipo testo + """ + # deve essere un VectorLayer di tipo puntuale + if (layer.type() != QgsMapLayer.VectorLayer) or (layer.geometryType() != QgsWkbTypes.PointGeometry): + return False + renderer = layer.renderer() + if renderer is None: return False + + context = QgsRenderContext() + # deve avere il-i simbolo-i trasparenti almeno entro il 10% + for symbol in renderer.symbols(context): + if symbol.opacity() > 0.1: # 1 for opaque, 0 for invisible + return False + # deve avere etichette + if layer.labeling() is None: + return False + if layer.labelsEnabled() == False: + return False + + # verifico che ci sia almeno un campo come etichetta + labelFieldNames = get_labelFieldNames(layer) + if len(labelFieldNames) == 0: + return False + + return True + + +# =============================================================================== +# isSymbolLayer +# =============================================================================== +def isSymbolLayer(layer): + """ + return True se il layer é di tipo simbolo + """ + # deve essere un VectorLayer di tipo puntuale + if (layer.type() != QgsMapLayer.VectorLayer) or (layer.geometryType() != QgsWkbTypes.PointGeometry): + return False + # se la rotazione é letta da un campo ricordarsi che per i simboli la rotazione é in senso orario + # quindi usare l'espressione 360 - + # se non é un layer di tipo testo é di tipo simbolo + return False if isTextLayer(layer) else True + + + +# =============================================================================== +# get_Disable_enter_attribute_values_dialog +# =============================================================================== +def get_Disable_enter_attribute_values_dialog(): + value = QSettings().value('/qgis/digitizing/disable_enter_attribute_values_dialog', True) + if type(value) == str: + return False if value.lower() == 'false' else True + elif type(value) == bool: + return value + + return True + +# ============================================================================ +# INIZIO - Gestione layer temporanei di QAD +# ============================================================================ + + +# =============================================================================== +# createQADTempLayer +# =============================================================================== +def createQADTempLayer(plugIn, GeomType): + """ + Aggiunge tre liste di geometrie rispettivamente a tre layer temporanei di QAD (uno per tipologia di + geometria). Se le geometrie sono da convertire allora + deve essere passato il parametro di tipo QgsCoordinateTransform. + = the authority identifier for this srs + """ + layer = None + crs = plugIn.iface.mapCanvas().mapSettings().destinationCrs() + + if GeomType == QgsWkbTypes.PointGeometry: + layerName = QadMsg.translate("QAD", "QAD - Temporary points") + layerList = QgsProject.instance().mapLayersByName(layerName) + if len(layerList) == 0: + layer = createMemoryLayer(layerName, "Point", crs) + QgsProject.instance().addMapLayers([layer], True) + else: + layer = layerList[0] + elif GeomType == QgsWkbTypes.LineGeometry: + layerName = QadMsg.translate("QAD", "QAD - Temporary lines") + layerList = QgsProject.instance().mapLayersByName(layerName) + if len(layerList) == 0: + layer = createMemoryLayer(layerName, "LineString", crs) + QgsProject.instance().addMapLayers([layer], True) + else: + layer = layerList[0] + elif GeomType == QgsWkbTypes.PolygonGeometry: + layerName = QadMsg.translate("QAD", "QAD - Temporary polygons") + layerList = QgsProject.instance().mapLayersByName(layerName) + if len(layerList) == 0: + layer = createMemoryLayer(layerName, "Polygon", crs) + QgsProject.instance().addMapLayers([layer], True) + else: + layer = layerList[0] + + layer.startEditing() + return layer + + +# =============================================================================== +# createMemoryLayer +# =============================================================================== +def createMemoryLayer(layerName, geomType, crs): + """ + Crea un layer in memoria con indice spaziale. + = nome del layer + = stringa rappresentante il tiipo di geometria: + "LineString", "Polygon", "MultiPoint", "MultiLineString", or "MultiPolygon" + = oggetto QgsCoordinateReferenceSystem che rappresenta il sistema di coordinate + """ + # prima creo un layer con un crs sicuramente valido + # poi setto il sistema di coordinate corretto + # faccio così perchè se il nome del sistema di coordinate contiene caratteri strani + # allora il costruttore di QgsVectorLayer fa casino (a volta fa casino ad es. con "USER:100004") + layer = QgsVectorLayer(geomType + "?crs=epsg:3003&index=yes", layerName, "memory") + layer.setCrs(crs, False) + return layer + + +# =============================================================================== +# addGeometriesToQADTempLayers +# =============================================================================== +def addGeometriesToQADTempLayers(plugIn, pointGeoms = None, lineGeoms = None, polygonGeoms = None, \ + crs = None, refresh = True): + """ + Aggiunge tre liste di geometrie rispettivamente a tre layer temporanei di QAD (uno per tipologia di + geometria). Se le geometrie sono da convertire allora + deve essere passato il parametro che definisce il sistema di coordinate delle geometrie. + """ + if pointGeoms is not None and len(pointGeoms) > 0: + layer = createQADTempLayer(plugIn, QgsWkbTypes.PointGeometry) + if layer is None: + return False + if crs is None: + # plugIn, layer, geoms, coordTransform , refresh, check_validity + if addGeomsToLayer(plugIn, layer, pointGeoms, None, refresh, False) == False: + return False + else: + # plugIn, layer, geoms, coordTransform , refresh, check_validity + if addGeomsToLayer(plugIn, layer, pointGeoms, QgsCoordinateTransform(crs, layer.crs(), QgsProject.instance()), \ + refresh, False) == False: + return False + + if lineGeoms is not None and len(lineGeoms) > 0: + layer = createQADTempLayer(plugIn, QgsWkbTypes.LineGeometry) + if layer is None: + return False + if crs is None: + # plugIn, layer, geoms, coordTransform , refresh, check_validity + if addGeomsToLayer(plugIn, layer, lineGeoms, None, refresh, False) == False: + return False + else: + # plugIn, layer, geoms, coordTransform , refresh, check_validity + if addGeomsToLayer(plugIn, layer, lineGeoms, QgsCoordinateTransform(crs, layer.crs(), QgsProject.instance()), \ + refresh, False) == False: + return False + + if polygonGeoms is not None and len(polygonGeoms) > 0: + layer = createQADTempLayer(plugIn, QgsWkbTypes.PolygonGeometry) + if layer is None: + return False + if crs is None: + # plugIn, layer, geoms, coordTransform , refresh, check_validity + if addGeomsToLayer(plugIn, layer, polygonGeoms, None, refresh, False) == False: + return False + else: + # plugIn, layer, geoms, coordTransform , refresh, check_validity + if addGeomsToLayer(plugIn, layer, polygonGeoms, QgsCoordinateTransform(crs, layer.crs(), QgsProject.instance()), \ + refresh, False) == False: + return False + + return True + + +# =============================================================================== +# QadLayerStatusEnum class. +# =============================================================================== +class QadLayerStatusEnum(): + UNKNOWN = 0 + COMMIT_BY_EXTERNAL = 1 # salvataggio quando questo è richiamato da eventi esterni a QAD + COMMIT_BY_INTERNAL = 2 # salvataggio quando questo è richiamato da eventi interni a QAD + +# =============================================================================== +# QadLayerStatusListClass class. +# =============================================================================== +class QadLayerStatusListClass(): + def __init__(self): + self.layerStatusList = [] # lista di coppie (-) + + def __del__(self): + del self.layerStatusList + + def getStatus(self, layerId): + for layerStatus in self.layerStatusList: + if layerStatus[0] == layerId: + return layerStatus[1] + return QadLayerStatusEnum.UNKNOWN + + def setStatus(self, layerId, status): + # verifico se c'era già in lista + for layerStatus in self.layerStatusList: + if layerStatus[0] == layerId: + layerStatus[1] = status + return + # se non c'era lo aggiungo + self.layerStatusList.append([layerId, status]) + return + + def remove(self, layerId): + i = 0 + for layerStatus in self.layerStatusList: + if layerStatus[0] == layerId: + del self.layerStatusList[i] + return + else: + i = i + 1 + return \ No newline at end of file diff --git a/qad_lengthen_cmd.py b/qad_lengthen_cmd.py deleted file mode 100644 index 8512773a..00000000 --- a/qad_lengthen_cmd.py +++ /dev/null @@ -1,1033 +0,0 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - comando ALLUNGA per allungare un oggetto - - ------------------- - begin : 2015-10-05 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -from qgis.gui import * - - -import qad_utils -from qad_generic_cmd import QadCommandClass -from qad_getdist_cmd import QadGetDistClass -from qad_getangle_cmd import QadGetAngleClass -from qad_snapper import * -from qad_getpoint import * -from qad_lengthen_maptool import * -from qad_textwindow import * -from qad_msg import QadMsg -import qad_layer -from qad_arc import * -from qad_circle import * -from qad_dim import QadDimStyles -import qad_grip - - -# Classe che gestisce il comando LENGTHEN -class QadLENGTHENCommandClass(QadCommandClass): - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadLENGTHENCommandClass(self.plugIn) - - def getName(self): - return QadMsg.translate("Command_list", "LENGTHEN") - - def getEnglishName(self): - return "LENGTHEN" - - def connectQAction(self, action): - QObject.connect(action, SIGNAL("triggered()"), self.plugIn.runLENGTHENCommand) - - def getIcon(self): - return QIcon(":/plugins/qad/icons/lengthen.png") - - def getNote(self): - # impostare le note esplicative del comando - return QadMsg.translate("Command_LENGTHEN", "Lengthen an object.") - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - self.OpMode = plugIn.lastOpMode_lengthen # "DElta" o "Percent" o "Total" o "DYnamic" - self.OpType = None # "length" o "Angle" - self.value = None - - self.startPt = None - self.GetDistClass = None - self.GetAngleClass = None - self.entity = QadEntity() - self.linearObjectList = None - self.atSubGeom = None - self.move_startPt = None - - self.nOperationsToUndo = 0 - - - def __del__(self): - QadCommandClass.__del__(self) - if self.GetDistClass is not None: - del self.GetDistClass - if self.GetAngleClass is not None: - del self.GetAngleClass - - def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): - if self.step == 3: # quando si é in fase di richiesta distanza - return self.GetDistClass.getPointMapTool() - if self.step == 4: # quando si é in fase di richiesta angolo - return self.GetAngleClass.getPointMapTool() - elif (self.plugIn is not None): - if self.PointMapTool is None: - self.PointMapTool = Qad_lengthen_maptool(self.plugIn) - return self.PointMapTool - else: - return None - - def setInfo(self, entity, point): - # setta: self.entity, self.linearObjectList, self.atSubGeom e self.move_startPt - if self.linearObjectList is not None: - del self.linearObjectList - self.linearObjectList = None - - self.entity.set(entity.layer, entity.featureId) - transformedPt = self.mapToLayerCoordinates(self.entity.layer, point) - f = self.entity.getFeature() - geom = self.entity.getGeometry() - - # ritorna una tupla (, - # - # - # ) - res = False - dummy = qad_utils.closestSegmentWithContext(transformedPt, geom) - if dummy[2] is None: - return False - # ritorna la sotto-geometria al vertice e la sua posizione nella geometria (0-based) - subGeom, self.atSubGeom = qad_utils.getSubGeomAtVertex(geom, dummy[2]) - self.linearObjectList = qad_utils.QadLinearObjectList() - self.linearObjectList.fromPolyline(subGeom.asPolyline()) - - if qad_utils.getDistance(self.linearObjectList.getStartPt(), transformedPt) <= \ - qad_utils.getDistance(self.linearObjectList.getEndPt(), transformedPt): - # si allunga dal punto iniziale - self.move_startPt = True - else: - # si allunga dal punto finale - self.move_startPt = False - - return True - - - #============================================================================ - # lengthen - #============================================================================ - def lengthen(self, point): - layer = self.entity.layer - f = self.entity.getFeature() - if f is None: # non c'è più la feature - return False - geom = self.entity.getGeometry() - - # ritorna una tupla (, - # - # - # ) - res = False - if self.OpMode == "DElta": - newLinearObjectList = qad_utils.QadLinearObjectList(self.linearObjectList) - if self.OpType == "length": - res = newLinearObjectList.lengthen_delta(self.move_startPt, self.value) - elif self.OpType == "Angle": - res = newLinearObjectList.lengthen_deltaAngle(self.move_startPt, self.value) - elif self.OpMode == "Percent": - newLinearObjectList = qad_utils.QadLinearObjectList(self.linearObjectList) - value = newLinearObjectList.length() * self.value / 100 - value = value - newLinearObjectList.length() - res = newLinearObjectList.lengthen_delta(self.move_startPt, value) - elif self.OpMode == "Total": - newLinearObjectList = qad_utils.QadLinearObjectList(self.linearObjectList) - if self.OpType == "length": - value = self.value - newLinearObjectList.length() - res = newLinearObjectList.lengthen_delta(self.move_startPt, value) - elif self.OpType == "Angle": - if newLinearObjectList.qty() == 1: - linearObject = newLinearObjectList.getLinearObjectAt(0) - if linearObject.isArc() == True: # se è un arco - value = self.value - linearObject.getArc().totalAngle() - res = newLinearObjectList.lengthen_deltaAngle(self.move_startPt, value) - elif self.OpMode == "DYnamic": - newLinearObjectList = qad_utils.QadLinearObjectList(self.linearObjectList) - transformedPt = self.mapToLayerCoordinates(layer, point) - - if self.move_startPt: - linearObject = newLinearObjectList.getLinearObjectAt(0) - else: - linearObject = newLinearObjectList.getLinearObjectAt(-1) - - if linearObject.isSegment(): - newPt = qad_utils.getPerpendicularPointOnInfinityLine(linearObject.getStartPt(), linearObject.getEndPt(), transformedPt) - else: # arco - newPt = qad_utils.getPolarPointByPtAngle(linearObject.getArc().center, \ - qad_utils.getAngleBy2Pts(linearObject.getArc().center, transformedPt), \ - linearObject.getArc().radius) - - if newLinearObjectList.qty() > 1 and linearObject.isSegment(): - ang = linearObject.getTanDirectionOnStartPt() - - if self.move_startPt: - linearObject.setStartPt(newPt) - else: - linearObject.setEndPt(newPt) - - if newLinearObjectList.qty() > 1 and linearObject.isSegment() and \ - qad_utils.TanDirectionNear(ang, linearObject.getTanDirectionOnStartPt()) == False: - res = False - else: - res = True - - if res == False: # allungamento impossibile - return False - - pts = newLinearObjectList.asPolyline() - updSubGeom = QgsGeometry.fromPolyline(pts) - - updGeom = qad_utils.setSubGeom(geom, updSubGeom, self.atSubGeom) - if updGeom is None: - return False - f.setGeometry(updGeom) - - self.plugIn.beginEditCommand("Feature edited", layer) - - # plugIn, layer, feature, refresh, check_validity - if qad_layer.updateFeatureToLayer(self.plugIn, layer, f, False, False) == False: - self.plugIn.destroyEditCommand() - return False - - self.plugIn.endEditCommand() - self.nOperationsToUndo = self.nOperationsToUndo + 1 - - return True - - - - def showLength(self, entity, pt): - # visualizza la lunghezza dell'entità in unità di mappa - geom = entity.getGeometry() - if geom is None: - self.showErr(QadMsg.translate("QAD", "\nInvalid object.")) - return None - - # Trasformo il punto nel sistema di coordinate del layer - convPt = self.mapToLayerCoordinates(entity.layer, pt) - - # ritorna una tupla (, - # - # - # ) - dummy = qad_utils.closestSegmentWithContext(pt, geom) - if dummy[2] is None: - self.showErr(QadMsg.translate("QAD", "\nInvalid object.")) - return None - # ritorna la sotto-geometria al vertice e la sua posizione nella geometria (0-based) - subGeom, atSubGeom = qad_utils.getSubGeomAtVertex(geom, dummy[2]) - - LinearObjectListToMisure = qad_utils.QadLinearObjectList() - pointList = subGeom.asPolyline() - LinearObjectListToMisure.fromPolyline(pointList) - # la trasformo in unità di mappa - LinearObjectListToMisure.transformFromCRSToCRS(entity.layer.crs(), self.plugIn.canvas.mapRenderer().destinationCrs()) - - msg = QadMsg.translate("Command_LENGTHEN", "\nCurrent length: {0}") - msg = msg.format(str(LinearObjectListToMisure.length())) - - arc = QadArc() - startEndVertices = arc.fromPolyline(pointList, 0) - # se la polilinea è composta solo da un arco - if startEndVertices and startEndVertices[0] == 0 and startEndVertices[1] == len(pointList)-1: - msg = msg + QadMsg.translate("Command_LENGTHEN", ", included angle: {0}") - msg = msg.format(str(qad_utils.toDegrees(arc.totalAngle()))) - - self.showMsg(msg) - - - def waitForObjectSelToMisure(self): - self.step = 1 - # imposto il map tool - self.getPointMapTool().setMode(Qad_lengthen_maptool_ModeEnum.ASK_FOR_OBJ_TO_MISURE) - - if self.plugIn.lastOpMode_lengthen == "DElta": - self.defaultValue = QadMsg.translate("Command_LENGTHEN", "DElta") - elif self.plugIn.lastOpMode_lengthen == "Percent": - self.defaultValue = QadMsg.translate("Command_LENGTHEN", "Percent") - elif self.plugIn.lastOpMode_lengthen == "Total": - self.defaultValue = QadMsg.translate("Command_LENGTHEN", "Total") - elif self.plugIn.lastOpMode_lengthen == "DYnamic": - self.defaultValue = QadMsg.translate("Command_LENGTHEN", "DYnamic") - else: - self.defaultValue = None - - keyWords = QadMsg.translate("Command_LENGTHEN", "DElta") + "/" + \ - QadMsg.translate("Command_LENGTHEN", "Percent") + "/" + \ - QadMsg.translate("Command_LENGTHEN", "Total") + "/" + \ - QadMsg.translate("Command_LENGTHEN", "DYnamic") - if self.defaultValue is None: - prompt = QadMsg.translate("Command_LENGTHEN", "Select an object or [{0}] <{1}>: ").format(keyWords, self.defaultValue) - else: - prompt = QadMsg.translate("Command_LENGTHEN", "Select an object or [{0}] <{1}>: ").format(keyWords, self.defaultValue) - - englishKeyWords = "DElta" + "/" + "Percent" + "/" + "Total" + "/" + "DYnamic" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o enter o una parola chiave - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - self.defaultValue, \ - keyWords, QadInputModeEnum.NONE) - - - def waitForDelta(self): - self.step = 2 - self.OpMode = "DElta" - self.plugIn.setLastOpMode_lengthen(self.OpMode) - # imposto il map tool - self.getPointMapTool().setMode(Qad_lengthen_maptool_ModeEnum.ASK_FOR_DELTA) - - keyWords = QadMsg.translate("Command_LENGTHEN", "Angle") - prompt = QadMsg.translate("Command_LENGTHEN", "Enter delta length or [{0}] <{1}>: ").format(keyWords, str(self.plugIn.lastDelta_lengthen)) - - englishKeyWords = "Angle" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o enter o una parola chiave - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS | QadInputTypeEnum.FLOAT, \ - self.plugIn.lastDelta_lengthen, \ - keyWords, QadInputModeEnum.NONE) - - - def waitForDeltaLength(self, msgMapTool, msg): - self.step = 3 - self.OpType = "length" - - # si appresta ad attendere una distanza - if self.GetDistClass is not None: - del self.GetDistClass - self.GetDistClass = QadGetDistClass(self.plugIn) - prompt = QadMsg.translate("Command_LENGTHEN", "Enter delta length <{0}>: ") - self.GetDistClass.msg = prompt.format(str(self.plugIn.lastDelta_lengthen)) - self.GetDistClass.startPt = self.startPt - self.GetDistClass.dist = self.plugIn.lastDelta_lengthen - self.GetDistClass.inputMode = QadInputModeEnum.NONE - self.GetDistClass.run(msgMapTool, msg) - - - def waitForDeltaAngle(self, msgMapTool, msg): - self.step = 4 - self.OpType = "Angle" - - # si appresta ad attendere l'angolo di rotazione - if self.GetAngleClass is not None: - del self.GetAngleClass - self.GetAngleClass = QadGetAngleClass(self.plugIn) - prompt = QadMsg.translate("Command_LENGTHEN", "Enter delta angle <{0}>: ") - self.GetAngleClass.msg = prompt.format(str(qad_utils.toDegrees(self.plugIn.lastDeltaAngle_lengthen))) - self.GetAngleClass.angle = self.plugIn.lastDeltaAngle_lengthen - self.GetAngleClass.run(msgMapTool, msg) - - - def waitForObjectSel(self): - self.step = 5 - # imposto il map tool - self.getPointMapTool().setMode(Qad_lengthen_maptool_ModeEnum.ASK_FOR_OBJ_TO_LENGTHEN) - self.getPointMapTool().refreshSnapType() # aggiorno lo snapType che può essere variato dal maptool di distanza o angolo - self.getPointMapTool().OpType = self.OpType - self.getPointMapTool().value = self.value - - keyWords = QadMsg.translate("Command_LENGTHEN", "Undo") - prompt = QadMsg.translate("Command_LENGTHEN", "Select an object to change or [{0}]: ").format(QadMsg.translate("Command_LENGTHEN", "Undo")) - - englishKeyWords = "Undo" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o enter o una parola chiave - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - None, \ - keyWords, QadInputModeEnum.NONE) - - - def waitForPercent(self): - self.step = 6 - self.OpMode = "Percent" - self.plugIn.setLastOpMode_lengthen(self.OpMode) - - # imposto il map tool - self.getPointMapTool().setMode(Qad_lengthen_maptool_ModeEnum.ASK_FOR_PERCENT) - - prompt = QadMsg.translate("Command_LENGTHEN", "Enter percentage length <{0}>: ") - prompt = prompt.format(str(self.plugIn.lastPerc_lengthen)) - # si appresta ad attendere un numero reale - # msg, inputType, default, keyWords, valori positivi - self.waitFor(prompt, QadInputTypeEnum.FLOAT, \ - self.plugIn.lastPerc_lengthen, "", \ - QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) - - - def waitForTotal(self): - self.step = 7 - self.OpMode = "Total" - self.plugIn.setLastOpMode_lengthen(self.OpMode) - # imposto il map tool - self.getPointMapTool().setMode(Qad_lengthen_maptool_ModeEnum.ASK_FOR_TOTAL) - - keyWords = QadMsg.translate("Command_LENGTHEN", "Angle") - prompt = QadMsg.translate("Command_LENGTHEN", "Specify total length or [{0}] <{1}>: ").format(keyWords, str(self.plugIn.lastTotal_lengthen)) - - englishKeyWords = "Angle" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o enter o una parola chiave - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS | QadInputTypeEnum.FLOAT, \ - self.plugIn.lastTotal_lengthen, \ - keyWords, QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) - - - def waitForTotalLength(self, msgMapTool, msg): - self.step = 8 - self.OpType = "length" - - # si appresta ad attendere una distanza - if self.GetDistClass is not None: - del self.GetDistClass - self.GetDistClass = QadGetDistClass(self.plugIn) - prompt = QadMsg.translate("Command_LENGTHEN", "Enter total length <{0}>: ") - self.GetDistClass.msg = prompt.format(str(self.plugIn.lastTotal_lengthen)) - self.GetDistClass.startPt = self.startPt - self.GetDistClass.dist = self.plugIn.lastTotal_lengthen - self.GetDistClass.inputMode = QadInputModeEnum.NONE - self.GetDistClass.run(msgMapTool, msg) - - - def waitForTotalAngle(self, msgMapTool, msg): - self.step = 9 - self.OpType = "Angle" - - # si appresta ad attendere l'angolo di rotazione - if self.GetAngleClass is not None: - del self.GetAngleClass - self.GetAngleClass = QadGetAngleClass(self.plugIn) - prompt = QadMsg.translate("Command_LENGTHEN", "Enter total angle <{0}>: ") - self.GetAngleClass.msg = prompt.format(str(qad_utils.toDegrees(self.plugIn.lastTotalAngle_lengthen))) - self.GetAngleClass.angle = self.plugIn.lastTotalAngle_lengthen - self.GetAngleClass.run(msgMapTool, msg) - - - def waitForDynamicPt(self): - self.step = 10 - # imposto il map tool - self.getPointMapTool().setMode(Qad_lengthen_maptool_ModeEnum.ASK_FOR_DYNAMIC_POINT) - - prompt = QadMsg.translate("Command_LENGTHEN", "Specify new endpoint: ") - - # si appresta ad attendere un punto o enter o una parola chiave - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D, \ - None, \ - "", QadInputModeEnum.NONE) - - - #============================================================================ - # run - #============================================================================ - def run(self, msgMapTool = False, msg = None): - if self.plugIn.canvas.mapRenderer().destinationCrs().geographicFlag(): - self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) - return True # fine comando - - #========================================================================= - # RICHIESTA SELEZIONE OGGETTO - if self.step == 0: # inizio del comando - # si appresta ad attendere la selezione degli oggetti da estendere/tagliare - self.waitForObjectSelToMisure() - return False - - #========================================================================= - # RISPOSTA ALLA SELEZIONE OGGETTI DA MISURARE - elif self.step == 1: - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - value = self.defaultValue - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - else: - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("Command_LENGTHEN", "DElta") or value == "DElta": - self.waitForDelta() - return False - elif value == QadMsg.translate("Command_LENGTHEN", "Percent") or value == "Percent": - self.waitForPercent() - return False - elif value == QadMsg.translate("Command_LENGTHEN", "Total") or value == "Total": - self.waitForTotal() - return False - elif value == QadMsg.translate("Command_LENGTHEN", "DYnamic") or value == "DYnamic": - self.OpMode = "DYnamic" - self.plugIn.setLastOpMode_lengthen(self.OpMode) - # si appresta ad attendere la selezione degli oggetti da allungare - self.waitForObjectSel() - return False - - elif type(value) == QgsPoint: # se é stato selezionato un punto - if self.getPointMapTool().entity.isInitialized(): - self.showLength(self.getPointMapTool().entity, value) - else: - # cerco se ci sono entità nel punto indicato considerando - # solo layer di tipo lineari che non appartengano a quote o di tipo poligono - layerList = [] - for layer in self.plugIn.canvas.layers(): - if layer.type() == QgsMapLayer.VectorLayer and \ - layer.geometryType() == QGis.Line or layer.geometryType() == QGis.Polygon: - if len(QadDimStyles.getDimListByLayer(layer)) == 0: - layerList.append(layer) - - result = qad_utils.getEntSel(self.getPointMapTool().toCanvasCoordinates(value), - self.getPointMapTool(), \ - layerList) - if result is not None: - feature = result[0] - layer = result[1] - self.showLength(QadEntity().set(layer, feature.id()), value) - else: - return True # fine comando - - # si appresta ad attendere la selezione degli oggetti da misurare - self.waitForObjectSelToMisure() - - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DEL DELTA (da step = 1) - elif self.step == 2: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - value = self.plugIn.lastDelta_lengthen # opzione di default "spostamento" - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - else: - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("Command_LENGTHEN", "Angle") or value == "Angle": - self.waitForDeltaAngle(msgMapTool, msg) - elif type(value) == QgsPoint: # se é stato inserito un punto - self.startPt = value - self.waitForDeltaLength(msgMapTool, msg) - elif type(value) == float: # se é stato inserito il delta - self.plugIn.setLastDelta_lengthen(value) - self.OpType = "length" - self.value = value - # si appresta ad attendere la selezione degli oggetti da allungare - self.waitForObjectSel() - - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DELLA LUNGHEZZA DEL DELTA (da step = 2) - elif self.step == 3: # dopo aver atteso un punto o un numero reale si riavvia il comando - if self.GetDistClass.run(msgMapTool, msg) == True: - if self.GetDistClass.dist is not None: - self.plugIn.setLastDelta_lengthen(self.GetDistClass.dist) - self.value = self.GetDistClass.dist - # si appresta ad attendere la selezione degli oggetti da allungare - self.waitForObjectSel() - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DELL'ANGOLO DEL DELTA (da step = 2) - elif self.step == 4: # dopo aver atteso un punto o un numero reale si riavvia il comando - if self.GetAngleClass.run(msgMapTool, msg) == True: - if self.GetAngleClass.angle is not None: - self.plugIn.setLastDeltaAngle_lengthen(self.GetAngleClass.angle) - self.value = self.GetAngleClass.angle - # si appresta ad attendere la selezione degli oggetti da allungare - self.waitForObjectSel() - - - #========================================================================= - # RISPOSTA ALLA SELEZIONE OGGETTI DA ALLUNGARE - elif self.step == 5: - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - else: - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("Command_LENGTHEN", "Undo") or value == "Undo": - if self.nOperationsToUndo > 0: - self.nOperationsToUndo = self.nOperationsToUndo - 1 - self.plugIn.undoEditCommand() - else: - self.showMsg(QadMsg.translate("QAD", "\nThe command has been canceled.")) - elif type(value) == QgsPoint: # se é stato selezionato un punto - if self.getPointMapTool().entity.isInitialized(): - self.setInfo(self.getPointMapTool().entity, value) - if self.OpMode != "DYnamic": - self.lengthen(value) - else: - self.waitForDynamicPt() - return False - else: - # cerco se ci sono entità nel punto indicato considerando - # solo layer lineari editabili che non appartengano a quote - layerList = [] - for layer in self.plugIn.canvas.layers(): - if layer.type() == QgsMapLayer.VectorLayer and layer.geometryType() == QGis.Line and \ - layer.isEditable(): - if len(QadDimStyles.getDimListByLayer(layer)) == 0: - layerList.append(layer) - - result = qad_utils.getEntSel(self.getPointMapTool().toCanvasCoordinates(value), - self.getPointMapTool(), \ - layerList) - if result is not None: - feature = result[0] - layer = result[1] - self.setInfo(QadEntity().set(layer, feature.id()), value) - - if self.OpMode != "DYnamic": - self.lengthen(value) - else: - self.waitForDynamicPt() - return False - else: - return True # fine comando - - # si appresta ad attendere la selezione degli oggetti da allungare - self.waitForObjectSel() - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA PERCENTUALE (da step = 1) - elif self.step == 6: # dopo aver atteso un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - value = self.plugIn.lastPerc_lengthen - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - else: - return False - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == float: # é stata inserita la percentuale - self.plugIn.setLastPerc_lengthen(value) - self.value = value - # si appresta ad attendere la selezione degli oggetti da allungare - self.waitForObjectSel() - - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DEL TOTALE (da step = 1) - elif self.step == 7: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - value = self.plugIn.lastTotal_lengthen - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - else: - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("Command_LENGTHEN", "Angle") or value == "Angle": - self.waitForTotalAngle(msgMapTool, msg) - elif type(value) == QgsPoint: # se é stato inserito un punto - self.startPt = value - self.waitForTotalLength(msgMapTool, msg) - elif type(value) == float: # se é stato inserito il delta - self.plugIn.setLastTotal_lengthen(value) - self.OpType = "length" - self.value = value - # si appresta ad attendere la selezione degli oggetti da allungare - self.waitForObjectSel() - - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DELLA LUNGHEZZA DEL TOTALE (da step = 7) - elif self.step == 8: # dopo aver atteso un punto o un numero reale si riavvia il comando - if self.GetDistClass.run(msgMapTool, msg) == True: - if self.GetDistClass.dist is not None: - self.plugIn.setLastTotal_lengthen(self.GetDistClass.dist) - self.value = self.GetDistClass.dist - # si appresta ad attendere la selezione degli oggetti da allungare - self.waitForObjectSel() - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DELL'ANGOLO DEL DELTA (da step = 7) - elif self.step == 9: # dopo aver atteso un punto o un numero reale si riavvia il comando - if self.GetAngleClass.run(msgMapTool, msg) == True: - if self.GetAngleClass.angle is not None: - self.plugIn.setLastTotalAngle_lengthen(self.GetAngleClass.angle) - self.value = self.GetAngleClass.angle - # si appresta ad attendere la selezione degli oggetti da allungare - self.waitForObjectSel() - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DELLA NUOVA ESTREMITA' IN MODO DINAMICO (da step = 5) - elif self.step == 10: # dopo aver atteso un punto - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == QgsPoint: # se é stato inserito un punto - self.lengthen(value) - - # si appresta ad attendere la selezione degli oggetti da allungare - self.waitForObjectSel() - - return False - - - - -#============================================================================ -# Classe che gestisce il comando LENGTHEN per i grip -#============================================================================ -class QadGRIPLENGTHENCommandClass(QadCommandClass): - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadGRIPLENGTHENCommandClass(self.plugIn) - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - self.entity = None - self.skipToNextGripCommand = False - self.copyEntities = False - self.basePt = QgsPoint() - self.nOperationsToUndo = 0 - - self.linearObjectList = None - self.atSubGeom = None - self.move_startPt = None - - - def __del__(self): - QadCommandClass.__del__(self) - - def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): - if (self.plugIn is not None): - if self.PointMapTool is None: - self.PointMapTool = Qad_lengthen_maptool(self.plugIn) - return self.PointMapTool - else: - return None - - - #============================================================================ - # setSelectedEntityGripPoints - #============================================================================ - def setSelectedEntityGripPoints(self, entitySetGripPoints): - # lista delle entityGripPoint con dei grip point selezionati - # setta la prima entità con un grip selezionato - self.entity = None - for entityGripPoints in entitySetGripPoints.entityGripPoints: - for gripPoint in entityGripPoints.gripPoints: - # grip point selezionato - if gripPoint.getStatus() == qad_grip.QadGripStatusEnum.SELECTED: - # verifico se l'entità appartiene ad uno stile di quotatura - if entityGripPoints.entity.isDimensionComponent(): - return False - if entityGripPoints.entity.getEntityType() != QadEntityGeomTypeEnum.ARC and \ - entityGripPoints.entity.getEntityType() != QadEntityGeomTypeEnum.LINESTRING: - return False - - # setta: self.entity, self.linearObjectList, self.atSubGeom e self.move_startPt - self.entity = entityGripPoints.entity - - if self.linearObjectList is not None: - del self.linearObjectList - self.linearObjectList = None - - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - geom = self.layerToMapCoordinates(self.entity.layer, self.entity.getGeometry()) - - # ritorna una tupla (, - # - # - # ) - res = False - dummy = qad_utils.closestSegmentWithContext(gripPoint.getPoint(), geom) - if dummy[2] is None: - return False - # ritorna la sotto-geometria al vertice e la sua posizione nella geometria (0-based) - subGeom, self.atSubGeom = qad_utils.getSubGeomAtVertex(geom, dummy[2]) - self.linearObjectList = qad_utils.QadLinearObjectList() - - self.linearObjectList.fromPolyline(subGeom.asPolyline()) - - if qad_utils.getDistance(self.linearObjectList.getStartPt(), gripPoint.getPoint()) <= \ - qad_utils.getDistance(self.linearObjectList.getEndPt(), gripPoint.getPoint()): - # si allunga dal punto iniziale - self.move_startPt = True - else: - # si allunga dal punto finale - self.move_startPt = False - - # imposto il map tool - if self.getPointMapTool().setInfo(self.entity, gripPoint.getPoint()) == False: - return False - - return True - return False - - - #============================================================================ - # lengthen - #============================================================================ - def lengthen(self, point): - layer = self.entity.layer - f = self.entity.getFeature() - if f is None: # non c'è più la feature - return False - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - geom = self.layerToMapCoordinates(layer, self.entity.getGeometry()) - - # ritorna una tupla (, - # - # - # ) - res = False - newLinearObjectList = qad_utils.QadLinearObjectList(self.linearObjectList) - - if self.move_startPt: - linearObject = newLinearObjectList.getLinearObjectAt(0) - else: - linearObject = newLinearObjectList.getLinearObjectAt(-1) - - if linearObject.isSegment(): - newPt = qad_utils.getPerpendicularPointOnInfinityLine(linearObject.getStartPt(), linearObject.getEndPt(), point) - else: # arco - newPt = qad_utils.getPolarPointByPtAngle(linearObject.getArc().center, \ - qad_utils.getAngleBy2Pts(linearObject.getArc().center, point), \ - linearObject.getArc().radius) - - if newLinearObjectList.qty() > 1 and linearObject.isSegment(): - ang = linearObject.getTanDirectionOnStartPt() - - if self.move_startPt: - linearObject.setStartPt(newPt) - else: - linearObject.setEndPt(newPt) - - if newLinearObjectList.qty() > 1 and linearObject.isSegment() and \ - qad_utils.TanDirectionNear(ang, linearObject.getTanDirectionOnStartPt()) == False: - res = False - else: - res = True - - if res == False: # allungamento impossibile - return False - - pts = newLinearObjectList.asPolyline() - updSubGeom = QgsGeometry.fromPolyline(pts) - - updGeom = qad_utils.setSubGeom(geom, updSubGeom, self.atSubGeom) - if updGeom is None: - return False - # trasformo la geometria nel crs del layer - f.setGeometry(self.mapToLayerCoordinates(layer, updGeom)) - - self.plugIn.beginEditCommand("Feature edited", layer) - - if self.copyEntities == False: - # plugIn, layer, feature, refresh, check_validity - if qad_layer.updateFeatureToLayer(self.plugIn, layer, f, False, False) == False: - self.plugIn.destroyEditCommand() - return False - else: - # plugIn, layer, features, coordTransform, refresh, check_validity - if qad_layer.addFeatureToLayer(self.plugIn, layer, f, None, False, False) == False: - self.plugIn.destroyEditCommand() - return False - - self.plugIn.endEditCommand() - self.nOperationsToUndo = self.nOperationsToUndo + 1 - - return True - - - def waitForDynamicPt(self): - keyWords = QadMsg.translate("Command_GRIP", "Copy") + "/" + \ - QadMsg.translate("Command_GRIP", "Undo") + "/" + \ - QadMsg.translate("Command_GRIP", "eXit") - prompt = QadMsg.translate("Command_GRIPLENGTHEN", "Specify new endpoint or [{0}]: ").format(keyWords) - - englishKeyWords = "Copy" + "/" + "Undo" + "/" "eXit" - keyWords += "_" + englishKeyWords - - # si appresta ad attendere un punto o enter o una parola chiave - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - None, \ - keyWords, QadInputModeEnum.NONE) - self.step = 1 - # imposto il map tool - self.getPointMapTool().setMode(Qad_lengthen_maptool_ModeEnum.ASK_FOR_DYNAMIC_POINT) - - - #============================================================================ - # run - #============================================================================ - def run(self, msgMapTool = False, msg = None): - if self.plugIn.canvas.mapRenderer().destinationCrs().geographicFlag(): - self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) - return True # fine comando - - #========================================================================= - # RICHIESTA SELEZIONE OGGETTO - if self.step == 0: # inizio del comando - self.waitForDynamicPt() - return False - - #========================================================================= - # RISPOSTA ALLA SELEZIONE OGGETTI DA MISURARE - elif self.step == 1: - ctrlKey = False - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - if self.copyEntities == False: - self.skipToNextGripCommand = True - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - ctrlKey = self.getPointMapTool().ctrlKey - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("Command_GRIP", "Copy") or value == "Copy": - # Copia entità lasciando inalterate le originali - self.copyEntities = True - - self.waitForDynamicPt() - elif value == QadMsg.translate("Command_GRIP", "Undo") or value == "Undo": - if self.nOperationsToUndo > 0: - self.nOperationsToUndo = self.nOperationsToUndo - 1 - self.plugIn.undoEditCommand() - else: - self.showMsg(QadMsg.translate("QAD", "\nThe command has been canceled.")) - - self.waitForDynamicPt() - elif value == QadMsg.translate("Command_GRIP", "eXit") or value == "eXit": - return True # fine comando - elif type(value) == QgsPoint: # se é stato selezionato un punto - if ctrlKey: - self.copyEntities = True - - self.lengthen(value) - - if self.copyEntities == False: - return True - - self.waitForDynamicPt() - else: - if self.copyEntities == False: - self.skipToNextGripCommand = True - return True # fine comando - - return False \ No newline at end of file diff --git a/qad_lengthen_maptool.py b/qad_lengthen_maptool.py deleted file mode 100644 index 759613a7..00000000 --- a/qad_lengthen_maptool.py +++ /dev/null @@ -1,260 +0,0 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - classe per gestire il map tool in ambito del comando lengthen - - ------------------- - begin : 2015-10-06 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -from qgis.gui import * -import math - - -import qad_utils -from qad_snapper import * -from qad_variables import * -from qad_getpoint import * -from qad_rubberband import QadRubberBand -from qad_dim import QadDimStyles - - -#=============================================================================== -# Qad_lengthen_maptool_ModeEnum class. -#=============================================================================== -class Qad_lengthen_maptool_ModeEnum(): - # si richiede la selezione dell'oggetto da misurare - ASK_FOR_OBJ_TO_MISURE = 1 - # si richiede il delta - ASK_FOR_DELTA = 2 - # non si richiede niente - NONE = 3 - # si richiede la selezione dell'oggetto da allungare - ASK_FOR_OBJ_TO_LENGTHEN = 4 - # si richiede la percentuale - ASK_FOR_PERCENT = 5 - # si richiede il totale - ASK_FOR_TOTAL = 6 - # si richiede il nuovo punto dell'estremità in modalità dinamica - ASK_FOR_DYNAMIC_POINT = 7 - -#=============================================================================== -# Qad_lengthen_maptool class -#=============================================================================== -class Qad_lengthen_maptool(QadGetPoint): - - def __init__(self, plugIn): - QadGetPoint.__init__(self, plugIn) - - self.OpMode = None # "DElta" o "Percent" o "Total" o "DYnamic" - self.OpType = None # "length" o "Angle" - self.value = None - self.tmpLinearObjectList = None - - self.__rubberBand = QadRubberBand(self.canvas) - - - def hidePointMapToolMarkers(self): - QadGetPoint.hidePointMapToolMarkers(self) - self.__rubberBand.hide() - - def showPointMapToolMarkers(self): - QadGetPoint.showPointMapToolMarkers(self) - self.__rubberBand.show() - - def clear(self): - QadGetPoint.clear(self) - self.__rubberBand.reset() - self.mode = None - - def setInfo(self, entity, point): - # setta: self.layer, self.tmpLinearObjectList e self.move_startPt - - if self.tmpLinearObjectList is not None: - del self.tmpLinearObjectList - self.tmpLinearObjectList = None - - if entity.isInitialized() == False: - return False - - self.layer = entity.layer - transformedPt = self.canvas.mapRenderer().mapToLayerCoordinates(self.layer, point) - geom = entity.getGeometry() - - # ritorna una tupla (, - # - # - # ) - dummy = qad_utils.closestSegmentWithContext(transformedPt, geom) - if dummy[2] is None: - return False - # ritorna la sotto-geometria al vertice e la sua posizione nella geometria (0-based) - subGeom, atSubGeom = qad_utils.getSubGeomAtVertex(geom, dummy[2]) - self.tmpLinearObjectList = qad_utils.QadLinearObjectList() - self.tmpLinearObjectList.fromPolyline(subGeom.asPolyline()) - - if qad_utils.getDistance(self.tmpLinearObjectList.getStartPt(), transformedPt) <= \ - qad_utils.getDistance(self.tmpLinearObjectList.getEndPt(), transformedPt): - # si allunga/accorcia dal punto iniziale - self.move_startPt = True - else: - # si allunga dal punto finale - self.move_startPt = False - return True - - - def canvasMoveEvent(self, event): - QadGetPoint.canvasMoveEvent(self, event) - - self.__rubberBand.reset() - res = False - - # si richiede la selezione dell'oggetto da allungare - if self.mode == Qad_lengthen_maptool_ModeEnum.ASK_FOR_OBJ_TO_LENGTHEN: - if self.tmpEntity.isInitialized(): - if self.setInfo(self.tmpEntity, self.tmpPoint) == False: - return - - if self.OpMode == "DElta": - newTmpLinearObjectList = qad_utils.QadLinearObjectList(self.tmpLinearObjectList) - if self.OpType == "length": - res = newTmpLinearObjectList.lengthen_delta(self.move_startPt, self.value) - elif self.OpType == "Angle": - res = newTmpLinearObjectList.lengthen_deltaAngle(self.move_startPt, self.value) - elif self.OpMode == "Percent": - newTmpLinearObjectList = qad_utils.QadLinearObjectList(self.tmpLinearObjectList) - value = newTmpLinearObjectList.length() * self.value / 100 - value = value - newTmpLinearObjectList.length() - res = newTmpLinearObjectList.lengthen_delta(self.move_startPt, value) - elif self.OpMode == "Total": - newTmpLinearObjectList = qad_utils.QadLinearObjectList(self.tmpLinearObjectList) - if self.OpType == "length": - value = self.value - self.tmpLinearObjectList.length() - res = newTmpLinearObjectList.lengthen_delta(self.move_startPt, value) - elif self.OpType == "Angle": - if newTmpLinearObjectList.qty() == 1: - linearObject = newTmpLinearObjectList.getLinearObjectAt(0) - if linearObject.isArc() == True: # se è un arco - value = self.value - linearObject.getArc().totalAngle() - res = newTmpLinearObjectList.lengthen_deltaAngle(self.move_startPt, value) - - # si richiede un punto per la nuova estremità - elif self.mode == Qad_lengthen_maptool_ModeEnum.ASK_FOR_DYNAMIC_POINT: - newTmpLinearObjectList = qad_utils.QadLinearObjectList(self.tmpLinearObjectList) - transformedPt = self.canvas.mapRenderer().mapToLayerCoordinates(self.layer, self.tmpPoint) - - if self.move_startPt: - linearObject = newTmpLinearObjectList.getLinearObjectAt(0) - else: - linearObject = newTmpLinearObjectList.getLinearObjectAt(-1) - - if linearObject.isSegment(): - newPt = qad_utils.getPerpendicularPointOnInfinityLine(linearObject.getStartPt(), linearObject.getEndPt(), transformedPt) - else: # arco - newPt = qad_utils.getPolarPointByPtAngle(linearObject.getArc().center, \ - qad_utils.getAngleBy2Pts(linearObject.getArc().center, transformedPt), \ - linearObject.getArc().radius) - - if newTmpLinearObjectList.qty() > 1 and linearObject.isSegment(): - ang = linearObject.getTanDirectionOnStartPt() - - if self.move_startPt: - linearObject.setStartPt(newPt) - else: - linearObject.setEndPt(newPt) - - if newTmpLinearObjectList.qty() > 1 and linearObject.isSegment() and \ - qad_utils.TanDirectionNear(ang, linearObject.getTanDirectionOnStartPt()) == False: - res = False - else: - res = True - - if res == False: # allungamento impossibile - return - pts = newTmpLinearObjectList.asPolyline() - geom = QgsGeometry.fromPolyline(pts) - self.__rubberBand.addGeometry(geom, self.layer) - - - def activate(self): - QadGetPoint.activate(self) - self.__rubberBand.show() - - def deactivate(self): - try: # necessario perché se si chiude QGIS parte questo evento nonostante non ci sia più l'oggetto maptool ! - QadGetPoint.deactivate(self) - self.__rubberBand.hide() - except: - pass - - def setMode(self, mode): - self.mode = mode - - # si richiede la selezione dell'oggetto da misurare - if self.mode == Qad_lengthen_maptool_ModeEnum.ASK_FOR_OBJ_TO_MISURE: - self.setSelectionMode(QadGetPointSelectionModeEnum.ENTITY_SELECTION) - - # solo layer di tipo lineari che non appartengano a quote o di tipo poligono - layerList = [] - for layer in self.plugIn.canvas.layers(): - if layer.type() == QgsMapLayer.VectorLayer and \ - layer.geometryType() == QGis.Line or layer.geometryType() == QGis.Polygon: - if len(QadDimStyles.getDimListByLayer(layer)) == 0: - layerList.append(layer) - - self.layersToCheck = layerList - self.onlyEditableLayers = False - self.setSnapType(QadSnapTypeEnum.DISABLE) - # si richiede il delta - elif self.mode == Qad_lengthen_maptool_ModeEnum.ASK_FOR_DELTA: - self.OpMode = "DElta" - self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) - # non si richiede niente - elif self.mode == Qad_lengthen_maptool_ModeEnum.NONE: - self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) - # si richiede la selezione dell'oggetto da allungare - elif self.mode == Qad_lengthen_maptool_ModeEnum.ASK_FOR_OBJ_TO_LENGTHEN: - self.setSelectionMode(QadGetPointSelectionModeEnum.ENTITY_SELECTION_DYNAMIC) - - # solo layer lineari editabili che non appartengano a quote - layerList = [] - for layer in self.plugIn.canvas.layers(): - if layer.type() == QgsMapLayer.VectorLayer and layer.geometryType() == QGis.Line and \ - layer.isEditable(): - if len(QadDimStyles.getDimListByLayer(layer)) == 0: - layerList.append(layer) - - self.layersToCheck = layerList - self.onlyEditableLayers = True - self.setSnapType(QadSnapTypeEnum.DISABLE) - # si richiede la percentuale - elif self.mode == Qad_lengthen_maptool_ModeEnum.ASK_FOR_PERCENT: - self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) - self.OpMode = "Percent" - # si richiede il totale - elif self.mode == Qad_lengthen_maptool_ModeEnum.ASK_FOR_TOTAL: - self.OpMode = "Total" - self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) - # si richiede il nuovo punto dell'estremità in modalità dinamica - elif self.mode == Qad_lengthen_maptool_ModeEnum.ASK_FOR_DYNAMIC_POINT: - self.OpMode = "DYnamic" - self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) diff --git a/qad_line.py b/qad_line.py new file mode 100644 index 00000000..54ea6d41 --- /dev/null +++ b/qad_line.py @@ -0,0 +1,802 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + classe per la gestione delle linee + + ------------------- + begin : 2018-12-27 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from qgis.PyQt.QtCore import * +from qgis.PyQt.QtGui import * +from qgis.core import * +from qgis.gui import * + +import math + + +from . import qad_utils + + +# =============================================================================== +# QadLine line class +# =============================================================================== +class QadLine(): + + def __init__(self, line = None): + if line is not None: + self.set(line.pt1, line.pt2) + else: + self.pt1 = None + self.pt2 = None + + + def whatIs(self): + # obbligatoria + return "LINE" + + + def set(self, pt1, pt2 = None): + if isinstance(pt1, QadLine): + line = pt1 + return self.set(line.pt1, line.pt2) + + self.pt1 = QgsPointXY(pt1) + self.pt2 = QgsPointXY(pt2) + return self + + def transform(self, coordTransform): + # obbligatoria + """Transform this geometry as described by CoordinateTransform ct.""" + self.pt1 = coordTransform.transform(self.pt1) + self.pt2 = coordTransform.transform(self.pt2) + + + def transformFromCRSToCRS(self, sourceCRS, destCRS): + # obbligatoria + """Transform this geometry as described by CRS.""" + if (sourceCRS is not None) and (destCRS is not None) and sourceCRS != destCRS: + coordTransform = QgsCoordinateTransform(sourceCRS, destCRS, QgsProject.instance()) # trasformo le coord + self.transform(coordTransform) + + + def __eq__(self, line): + # obbligatoria + """self == other""" + if line.whatIs() != "LINE": return False + # strettamente uguali (conta il verso) + if self.pt1 != line.pt1 or self.pt2 != line.pt2: + return False + else: + return True + + + def __ne__(self, line): + """self != other""" + return not self.__eq__(line) + + + def equals(self, line): + # uguali geometricamente (NON conta il verso) + if line.whatIs() != "LINE": return False + if self.__eq__(line): return True + dummy = line.copy() + dummy.reverse() + return self.__eq__(dummy) + + + def copy(self): + # obbligatoria + return QadLine(self) + + + # ============================================================================ + # reverse + # ============================================================================ + def reverse(self): + # obbligatoria + # inverto direzione della linea + dummy = self.pt1 + self.pt1 = self.pt2 + self.pt2 = dummy + return self + + # ============================================================================ + # getStartPt, setStartPt + # ============================================================================ + def getStartPt(self): + # obbligatoria + return self.pt1 + + def setStartPt(self, pt): + # obbligatoria + self.pt1 = QgsPointXY(pt) + + + # ============================================================================ + # getEndPt, setEndPt + # ============================================================================ + def getEndPt(self): + # obbligatoria + return self.pt2 + + def setEndPt(self, pt): + # obbligatoria + self.pt2 = QgsPointXY(pt) + + + # =============================================================================== + # getMiddlePt + # =============================================================================== + def getMiddlePt(self): + """ + la funzione ritorna il punto medio della linea (QgsPointXY) + """ + x = (self.pt1.x() + self.pt2.x()) / 2 + y = (self.pt1.y() + self.pt2.y()) / 2 + + return QgsPointXY(x, y) + + + # =============================================================================== + # getBoundingBox + # =============================================================================== + def getBoundingBox(self): + """ + la funzione ritorna il rettangolo che racchiude il segmento. + """ + if self.pt1.x() > self.pt2.x(): + xMaxLine = self.pt1.x() + xMinLine = self.pt2.x() + else: + xMaxLine = self.pt2.x() + xMinLine = self.pt1.x() + + if self.pt1.y() > self.pt2.y(): + yMaxLine = self.pt1.y() + yMinLine = self.pt2.y() + else: + yMaxLine = self.pt2.y() + yMinLine = self.pt1.y() + + return QgsRectangle(xMinLine, yMinLine, xMaxLine, yMaxLine) + + + # ============================================================================ + # getTanDirectionOnPt + # ============================================================================ + def getTanDirectionOnPt(self, pt = None): + # obbligatoria + """ + la funzione ritorna la direzione della tangente al punto dell'oggetto. + pt è usato solo per compatibilità con le altre classi lineari (es. arco) + """ + return qad_utils.getAngleBy2Pts(self.getStartPt(), self.getEndPt()) + + + # ============================================================================ + # getTanDirectionOnStartPt, getTanDirectionOnEndPt, getTanDirectionOnMiddlePt + # ============================================================================ + def getTanDirectionOnStartPt(self): + # obbligatoria + """ + la funzione ritorna la direzione della tangente al punto iniziale dell'oggetto. + """ + return self.getTanDirectionOnPt() + + def getTanDirectionOnEndPt(self): + # obbligatoria + """ + la funzione ritorna la direzione della tangente al punto finale dell'oggetto. + """ + return self.getTanDirectionOnPt() + + def getTanDirectionOnMiddlePt(self): + # obbligatoria + """ + la funzione ritorna la direzione della tangente al punto medio dell'oggetto. + """ + return self.getTanDirectionOnPt() + + + # ============================================================================ + # fromPt1PolarPt2 + # ============================================================================ + def fromPt1PolarPt2(self, pt1, angle, dist): + """ + setta le caratteristiche della linea attraverso: + punto iniziale + angolo + distanza dal punto iniziale + """ + self.pt1 = QgsPointXY(pt1) + self.pt2 = qad_utils.getPolarPointByPtAngle(pt1, angle, dist) + return True + + + # =============================================================================== + # getXOnInfinityLine + # =============================================================================== + def getXOnInfinityLine(self, y): + """ + data la coordinata Y di un punto la funzione ritorna la coordinata X dello stesso + sulla linea + """ + + diffX = self.pt2.x() - self.pt1.x() + diffY = self.pt2.y() - self.pt1.y() + + if qad_utils.doubleNear(diffX, 0): # se la retta passante per p1 e p2 é verticale + return self.pt1.x() + elif qad_utils.doubleNear(diffY, 0): # se la retta passante per p1 e p2 é orizzontale + return None # infiniti punti + else: + coeff = diffY / diffX + return self.pt1.x() + (y - self.pt1.y()) / coeff + + + # =============================================================================== + # getYOnInfinityLine + # =============================================================================== + def getYOnInfinityLine(self, x): + """ + data la coordinata X di un punto la funzione ritorna la coordinata Y dello stesso + sulla linea + """ + + diffX = self.pt2.x() - self.pt1.x() + diffY = self.pt2.y() - self.pt1.y() + + if qad_utils.doubleNear(diffX, 0): # se la retta passante per p1 e p2 é verticale + return None # infiniti punti + elif qad_utils.doubleNear(diffY, 0): # se la retta passante per p1 e p2 é orizzontale + return self.pt1.y() + else: + coeff = diffY / diffX + return self.pt1.y() + (x - self.pt1.x()) * coeff + + + # =============================================================================== + # getSqrLength + # =============================================================================== + def getSqrLength(self): + """ + la funzione ritorna la lunghezza al quadrato della linea + """ + dx = self.pt2.x() - self.pt1.x() + dy = self.pt2.y() - self.pt1.y() + + return dx * dx + dy * dy + + + # =============================================================================== + # length + # =============================================================================== + def length(self): + # obbligatoria + return math.sqrt(self.getSqrLength()) + + + # =============================================================================== + # getMinDistancePtBetweenSegmentAndPt + # =============================================================================== + def getMinDistancePtBetweenSegmentAndPt(self, pt): + """ + la funzione ritorna il punto di distanza minima e la distanza minima tra un segmento ed un punto + () + """ + if self.containsPt(pt) == True: + return [pt, 0] + perpPt = self.getPerpendicularPointOnInfinityLine(pt) + if perpPt is not None: + if self.containsPt(perpPt) == True: + return [perpPt, perpPt.distance(pt)] + + distFromP1 = self.pt1.distance(pt) + distFromP2 = self.pt2.distance(pt) + if distFromP1 < distFromP2: + return [self.pt1, distFromP1] + else: + return [self.pt2, distFromP2] + + + # =============================================================================== + # getPerpendicularPointOnInfinityLine + # =============================================================================== + def getPerpendicularPointOnInfinityLine(self, pt): + """ + la funzione ritorna il punto di proiezione perpendicolare di pt alla linea. + """ + + diffX = self.pt2.x() - self.pt1.x() + diffY = self.pt2.y() - self.pt1.y() + + if qad_utils.doubleNear(diffX, 0): # se la retta passante per p1 e p2 é verticale + return QgsPointXY(self.pt1.x(), pt.y()) + elif qad_utils.doubleNear(diffY, 0): # se la retta passante per p1 e p2 é orizzontale + return QgsPointXY(self.pt.x(), pt1.y()) + else: + coeff = diffY / diffX + x = (coeff * self.pt1.x() - self.pt1.y() + pt.x() / coeff + pt.y()) / (coeff + 1 / coeff) + y = coeff * (x - self.pt1.x()) + self.pt1.y() + + return QgsPointXY(x, y) + + + # =============================================================================== + # getInfinityLinePerpOnMiddle + # =============================================================================== + def getInfinityLinePerpOnMiddle(self): + """ + la funzione trova una linea perpendicolare e passante per il punto medio della linea. + """ + ptMiddle = self.getMiddlePt() + dist = self.pt1.distance(ptMiddle) + if dist == 0: + return None + angle = qad_utils.getAngleBy2Pts(self.pt1, self.pt2) + math.pi / 2 + pt2Middle = qad_utils.getPolarPointByPtAngle(ptMiddle, angle, dist) + line = QadLine() + line.set(ptMiddle, pt2Middle) + return line + + + # =============================================================================== + # isPtOnInfinityLine + # =============================================================================== + def isPtOnInfinityLine(self, point): + """ + la funzione ritorna true se il punto é sul segmento (estremi compresi). + point è di tipo QgsPointXY. + """ + y = self.getYOnInfinityLine(point.x()) + if y is None: # la linea infinita lineP1-lineP2 é verticale + if qad_utils.doubleNear(point.x(), self.pt1.x()): + return True + else: + # se il punto é sulla linea infinita che passa da p1-p2 + if qad_utils.doubleNear(point.y(), y): + return True + + return False + + + # =============================================================================== + # containsPt + # =============================================================================== + def containsPt(self, point): + # obbligatoria + """ + la funzione ritorna true se il punto é sul segmento (estremi compresi). + point è di tipo QgsPointXY. + """ + if self.pt1.x() < self.pt2.x(): + xMin = self.pt1.x() + xMax = self.pt2.x() + else: + xMax = self.pt1.x() + xMin = self.pt2.x() + + # verifico se il punto può essere sul segmento + if qad_utils.doubleSmaller(point.x(), xMin) or qad_utils.doubleGreater(point.x(), xMax): return False + + if self.pt1.y() < self.pt2.y(): + yMin = self.pt1.y() + yMax = self.pt2.y() + else: + yMax = self.pt1.y() + yMin = self.pt2.y() + + # verifico se il punto può essere sul segmento + if qad_utils.doubleSmaller(point.y(), yMin) or qad_utils.doubleGreater(point.y(), yMax): return False + + return self.isPtOnInfinityLine(point) + + + # =============================================================================== + # leftOf + # =============================================================================== + def leftOf(self, pt): + # obbligatoria + """ + la funzione ritorna una numero < 0 se il punto pt é alla sinistra della linea pt1 -> pt2 + """ + f1 = pt.x() - self.pt1.x() + f2 = self.pt2.y() - self.pt1.y() + f3 = pt.y() - self.pt1.y() + f4 = self.pt2.x() - self.pt1.x() + return f1*f2 - f3*f4 + + + # =============================================================================== + # get a and b for line equation (y = ax + b) + # =============================================================================== + def get_A_B_LineEquation(self): + # dati 2 punti vengono calcolati a e b dell'equazione della retta passante per i due punti (y = ax + b) + a = (self.pt2.y() - self.pt1.y()) / (self.pt2.x() - self.pt1.x()) + # y = ax + b -> b = y - ax + b = self.pt1.y() - (a * self.pt1.x()) + + return a, b + + + # =============================================================================== + # sqrDist + # =============================================================================== + def sqrDist(self, point): + # obbligatoria + """ + la funzione ritorna una lista con + ( + ) + """ + minDistPoint = QgsPointXY() + + if self.pt1.x() == self.pt2.x() and self.pt1.y() == self.pt2.y(): + minDistPoint.setX(self.pt1.x()) + minDistPoint.setY(self.pt1.y()) + else: + nx = self.pt2.y() - self.pt1.y() + ny = -( self.pt2.x() - self.pt1.x() ) + + t = (point.x() * ny - point.y() * nx - self.pt1.x() * ny + self.pt1.y() * nx ) / \ + (( self.pt2.x() - self.pt1.x() ) * ny - ( self.pt2.y() - self.pt1.y() ) * nx ) + + if t < 0.0: + minDistPoint.setX(self.pt1.x()) + minDistPoint.setY(self.pt1.y()) + elif t > 1.0: + minDistPoint.setX(self.pt2.x()) + minDistPoint.setY(self.pt2.y()) + else: + minDistPoint.setX( self.pt1.x() + t *( self.pt2.x() - self.pt1.x() ) ) + minDistPoint.setY( self.pt1.y() + t *( self.pt2.y() - self.pt1.y() ) ) + + dist = point.sqrDist(minDistPoint) + # prevent rounding errors if the point is directly on the segment + if qad_utils.doubleNear(dist, 0.0): + minDistPoint.setX( point.x() ) + minDistPoint.setY( point.y() ) + return (0.0, minDistPoint) + + return (dist, minDistPoint) + + + # ============================================================================ + # getDistanceFromStart + # ============================================================================ + def getDistanceFromStart(self, pt): + # obbligatoria + """ + la funzione restituisce la distanza di (che deve essere sull'oggetto o sua estensione) + dal punto iniziale. + """ + dummy = QadLine(self) + dummy.setEndPt(pt) + + # se il punto é sull'estensione dalla parte del punto iniziale + if self.containsPt(pt) == False and \ + self.getStartPt().distance(pt) < self.getEndPt().distance(pt): + return -dummy.length() + else: + return dummy.length() + + + # ============================================================================ + # getPointFromStart + # ============================================================================ + def getPointFromStart(self, distance): + # obbligatoria + """ + la funzione restituisce un punto (e la direzione della tangente) alla distanza + (che deve essere sull'oggetto) dal punto iniziale. + """ + if distance < 0: + return None, None + l = self.length() + if distance > l: + return None, None + + angle = self.getTanDirectionOnStartPt() + return qad_utils.getPolarPointByPtAngle(self.getStartPt(), angle, distance), angle + + + # ============================================================================ + # getDistanceFromEnd + # ============================================================================ + def getDistanceFromEnd(self, pt): + # obbligatoria + """ + la funzione restituisce la distanza di (che deve essere sull'oggetto o sua estensione) + dal punto finale. + """ + return self.length() - self.getDistanceFromStart() + + + # =============================================================================== + # getPointFromEnd + # =============================================================================== + def getPointFromEnd(self, distance): + """ + la funzione restituisce un punto (e la direzione della tangente) alla distanza + (che deve essere sull'oggetto) dal punto finale. + """ + d = self.length() - distance + return self.getPointFromStart(d) + + + # ============================================================================ + # asPolyline + # ============================================================================ + def asPolyline(self, tolerance2ApproxCurve = None, atLeastNSegment = None): + # obbligatoria + """ + ritorna una lista di punti che definisce la linea + """ + return [self.getStartPt(), self.getEndPt()] + + + # =============================================================================== + # asLineString + # =============================================================================== + def asLineString(self, tolerance2ApproxCurve = None, atLeastNSegment = None, forcedStartPt = None): + """ + la funzione ritorna la linea in forma di lineString. + tolerance2ApproxCurve e atLeastNSegment sono usati solo per compatibilità + Quando la linea fa parte di una polilinea è necessario che il suo punto iniziale coincida con quello finale della parte precedente + percui il punto iniziale viene forzato. + """ + if forcedStartPt is None: + return QgsLineString(QgsPoint(self.getStartPt()), QgsPoint(self.getEndPt())) + else: + return QgsLineString(QgsPoint(forcedStartPt), QgsPoint(self.getEndPt())) + + + # =============================================================================== + # asAbstractGeom + # =============================================================================== + def asAbstractGeom(self, wkbType = QgsWkbTypes.LineString, tolerance2ApproxCurve = None, atLeastNSegment = None): + """ + la funzione ritorna la linea in forma di QgsAbstractGeometry. + """ + flatType = QgsWkbTypes.flatType(wkbType) + + if flatType == QgsWkbTypes.CompoundCurve: + lineString = self.asLineString() + compoundCurve = QgsCompoundCurve() + compoundCurve.addCurve(lineString) + return compoundCurve + + elif flatType == QgsWkbTypes.MultiCurve: + lineString = self.asLineString() + multiCurve = QgsMultiCurve() + multiCurve.addGeometry(lineString) + return multiCurve + + elif flatType == QgsWkbTypes.MultiLineString: + lineString = self.asLineString() + multiLineString = QgsMultiLineString() + multiLineString.addGeometry(lineString) + return multiLineString + + return self.asLineString() + + + # =============================================================================== + # asGeom + # =============================================================================== + def asGeom(self, wkbType = QgsWkbTypes.LineString, tolerance2ApproxCurve = None, atLeastNSegment = None): + """ + la funzione ritorna la linea in forma di QgsGeometry. + tolerance2ApproxCurve e atLeastNSegment sono dichiarati solo per compatibilità + """ + return QgsGeometry(self.asAbstractGeom(wkbType, tolerance2ApproxCurve, atLeastNSegment)) + + + # ============================================================================ + # lengthen_delta + # ============================================================================ + def lengthen_delta(self, move_startPt, delta): + # obbligatoria + """ + la funzione sposta il punto iniziale (se move_startPt = True) o finale (se move_startPt = False) + di una distanza delta + """ + length = self.length() + # lunghezza della parte + delta non può essere <= 0 + if length + delta <= 0: + return False + + angle = self.getTanDirectionOnPt() + if move_startPt == True: + self.setStartPt(qad_utils.getPolarPointByPtAngle(self.getStartPt(), angle + math.pi, delta)) + else: + self.setEndPt(qad_utils.getPolarPointByPtAngle(self.getEndPt(), angle, delta)) + return True + + + # ============================================================================ + # lengthen_deltaAngle + # ============================================================================ + def lengthen_deltaAngle(self, move_startPt, delta): + # obbligatoria + """ + la funzione sposta il punto iniziale (se move_startPt = True) o finale (se move_startPt = False) + della linea di un certo numero di gradi delta rispetto il coefficiente angolare precedente + """ + angle = self.getTanDirectionOnPt() + if move_startPt == True: + self.setStartPt(qad_utils.getPolarPointByPtAngle(self.getEndPt(), angle + math.pi + delta, self.length())) + else: + self.setEndPt(qad_utils.getPolarPointByPtAngle(self.getStartPt(), angle + delta, self.length())) + return True + + + # ============================================================================ + # move + # ============================================================================ + def move(self, offsetX, offsetY): + # obbligatoria + self.pt1 = qad_utils.movePoint(self.pt1, offsetX, offsetY) + self.pt2 = qad_utils.movePoint(self.pt2, offsetX, offsetY) + + + # ============================================================================ + # rotate + # ============================================================================ + def rotate(self, basePt, angle): + self.pt1 = qad_utils.rotatePoint(self.pt1, basePt, angle) + self.pt2 = qad_utils.rotatePoint(self.pt2, basePt, angle) + + + # ============================================================================ + # scale + # ============================================================================ + def scale(self, basePt, scale): + self.pt1 = qad_utils.scalePoint(self.pt1, basePt, scale) + self.pt2 = qad_utils.scalePoint(self.pt2, basePt, scale) + + + # ============================================================================ + # mirror + # ============================================================================ + def mirror(self, mirrorPt, mirrorAngle): + self.pt1 = qad_utils.mirrorPoint(self.pt1, mirrorPt, mirrorAngle) + self.pt2 = qad_utils.mirrorPoint(self.pt2, mirrorPt, mirrorAngle) + + + # =============================================================================== + # offset + # =============================================================================== + def offset(self, offsetDist, offsetSide): + """ + la funzione ritorna l'offset di una linea + secondo una distanza e un lato di offset ("right" o "left") + """ + if offsetSide == "right": + AngleProjected = qad_utils.getAngleBy2Pts(self.pt1, self.pt2) - (math.pi / 2) + else: + AngleProjected = qad_utils.getAngleBy2Pts(self.pt1, self.pt2) + (math.pi / 2) + # calcolo il punto proiettato + self.pt1 = qad_utils.getPolarPointByPtAngle(self.pt1, AngleProjected, offsetDist) + self.pt2 = qad_utils.getPolarPointByPtAngle(self.pt2, AngleProjected, offsetDist) + return True + + + # ============================================================================ + # extend + # ============================================================================ + def extend(self, limitPt): + """ + la funzione estende la linea (punto iniziale o finale della linea) fino ad incontrare il punto . + """ + if self.pt1.distance(limitPt) < self.pt2.distance(limitPt): + self.pt1.setX(limitPt.x()) + self.pt1.setY(limitPt.y()) + else: + self.pt2.setX(limitPt.x()) + self.pt2.setY(limitPt.y()) + + + # =============================================================================== + # breakOnPts + # =============================================================================== + def breakOnPts(self, firstPt, secondPt): + # obbligatoria + """ + la funzione spezza la geometria in un punto (se = None) o in due punti + come fa il trim. Ritorna una o due geometrie risultanti dall'operazione. + = primo punto di divisione + = secondo punto di divisione + """ + # la funzione ritorna una lista con ( ) + dummy = self.sqrDist(firstPt) + myFirstPt = dummy[1] + + mySecondPt = None + if secondPt is not None: + dummy = self.sqrDist(secondPt) + mySecondPt = dummy[1] + + # verifico se è il caso di invertire i punti + if self.getDistanceFromStart(myFirstPt) > self.getDistanceFromStart(mySecondPt): + dummy = myFirstPt + myFirstPt = mySecondPt + mySecondPt = dummy + + part1 = self.getGeomBetween2Pts(self.getStartPt(), myFirstPt) + if mySecondPt is None: + part2 = self.getGeomBetween2Pts(myFirstPt, self.getEndPt()) + else: + part2 = self.getGeomBetween2Pts(mySecondPt, self.getEndPt()) + + return [part1, part2] + + + # =============================================================================== + # getGeomBetween2Pts + # =============================================================================== + def getGeomBetween2Pts(self, startPt, endPt): + """ + Ritorna una sotto geometria che parte dal punto startPt e finisce al punto endPt seguendo il tracciato della geometria. + """ + if qad_utils.ptNear(startPt, endPt): return None + if self.containsPt(startPt) == False: return None + if self.containsPt(endPt) == False: return None + + return QadLine().set(startPt, endPt) + + +# =============================================================================== +# getBoundingPtsOnOnInfinityLine +# =============================================================================== +def getBoundingPtsOnOnInfinityLine(pts): + """ + Data una lista di punti non ordinati su una linea infinita, + la funzione ritorna i due punti estremi al fascio di punti (i due punti più lontani tra di loro). + """ + tot = len(pts) + if tot < 3: + return pts[:] # copio la lista + + result = [] + # elaboro i tratti intermedi + # calcolo la direzione dal primo punto al secondo punto + angle = qad_utils.getAngleBy2Pts(pts[0], pts[1]) + # ciclo su tutti i punti considerando solo quelli che hanno la stessa direzione con il punto precedente (boundingPt1) + i = 2 + boundingPt1 = pts[1] + while i < tot: + pt2 = pts[i] + if qad_utils.TanDirectionNear(angle, qad_utils.getAngleBy2Pts(boundingPt1, pt2)): + boundingPt1 = pt2 + i = i + 1 + + # calcolo la direzione dal secondo punto al primo punto + angle = qad_utils.getAngleBy2Pts(pts[1], pts[0]) + # ciclo su tutti i punti considerando solo quelli che hanno la stessa direzione con il punto precedente (boundingPt2) + i = 2 + boundingPt2 = pts[0] + while i < tot: + pt2 = pts[i] + if qad_utils.TanDirectionNear(angle, qad_utils.getAngleBy2Pts(boundingPt2, pt2)): + boundingPt2 = pt2 + i = i + 1 + + return [QgsPointXY(boundingPt1), QgsPointXY(boundingPt2)] diff --git a/qad_line_cmd.py b/qad_line_cmd.py deleted file mode 100644 index 4abc4d07..00000000 --- a/qad_line_cmd.py +++ /dev/null @@ -1,400 +0,0 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - comando PLINE per disegnare una linea - - ------------------- - begin : 2013-07-15 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -from qad_getpoint import * -from qad_line_maptool import * -from qad_generic_cmd import QadCommandClass -from qad_msg import QadMsg -from qad_textwindow import * -from qad_snapper import * -import qad_utils -import qad_layer -from qad_rubberband import createRubberBand - - -# Classe che gestisce il comando LINE -class QadLINECommandClass(QadCommandClass): - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadLINECommandClass(self.plugIn) - - def getName(self): - return QadMsg.translate("Command_list", "LINE") - - def getEnglishName(self): - return "LINE" - - def connectQAction(self, action): - QObject.connect(action, SIGNAL("triggered()"), self.plugIn.runLINECommand) - - def getIcon(self): - return QIcon(":/plugins/qad/icons/line.png") - - def getNote(self): - # impostare le note esplicative del comando - return QadMsg.translate("Command_LINE", "Creates straight line segments.") - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - self.vertices = [] - self.rubberBand = createRubberBand(self.plugIn.canvas, QGis.Line) - self.firstPtTan = None - self.firstPtPer = None - # se questo flag = True il comando serve all'interno di un altro comando per disegnare una linea - # che non verrà salvata su un layer - self.virtualCmd = False - - def __del__(self): - QadCommandClass.__del__(self) - self.rubberBand.hide() - self.plugIn.canvas.scene().removeItem(self.rubberBand) - - def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): - if (self.plugIn is not None): - if self.PointMapTool is None: - self.PointMapTool = Qad_line_maptool(self.plugIn) - return self.PointMapTool - else: - return None - - def addVertex(self, point): - self.vertices.append(point) - self.addPointToRubberBand(point) - self.plugIn.setLastPointAndSegmentAng(self.vertices[-1]) - self.setTmpGeometriesToMapTool() - - def delLastVertex(self): - if len(self.vertices) > 0: - del self.vertices[-1] # cancello ultimo vertice - self.removeLastPointToRubberBand() - if len(self.vertices) > 0: - self.plugIn.setLastPointAndSegmentAng(self.vertices[-1]) - self.setTmpGeometriesToMapTool() - - - #============================================================================ - # addPointToRubberBand - #============================================================================ - def addPointToRubberBand(self, point, doUpdate = True): - numberOfVertices = self.rubberBand.numberOfVertices() - - if numberOfVertices == 2: - # per un baco non ancora capito: se la linea ha solo 2 vertici e - # hanno la stessa x o y (linea orizzontale o verticale) - # la linea non viene disegnata perciò sposto un pochino la x o la y - adjustedPoint = qad_utils.getAdjustedRubberBandVertex(self.rubberBand.getPoint(0, 0), point) - self.rubberBand.addPoint(adjustedPoint, doUpdate) - else: - self.rubberBand.addPoint(point, doUpdate) - - - #============================================================================ - # removeLastPointToRubberBand - #============================================================================ - def removeLastPointToRubberBand(self): - self.rubberBand.removeLastPoint() - - def addLinesToLayer(self, layer): - i = 1 - while i < len(self.vertices): - qad_layer.addLineToLayer(self.plugIn, layer, - [self.vertices[i - 1], self.vertices[i]]) - i = i + 1 - - - #============================================================================ - # setTmpGeometriesToMapTool - #============================================================================ - def setTmpGeometriesToMapTool(self): - self.getPointMapTool().clearTmpGeometries() - i = 1 - while i < len(self.vertices): - # per lo snap aggiungo questa geometria temporanea - self.getPointMapTool().appendTmpGeometry(QgsGeometry.fromPolyline([self.vertices[i - 1], self.vertices[i]])) - i = i + 1 - - - def run(self, msgMapTool = False, msg = None): - if self.plugIn.canvas.mapRenderer().destinationCrs().geographicFlag(): - self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) - return True # fine comando - - if self.virtualCmd == False: # se si vuole veramente salvare la polylinea in un layer - currLayer, errMsg = qad_layer.getCurrLayerEditable(self.plugIn.canvas, QGis.Line) - if currLayer is None: - self.showErr(errMsg) - return True # fine comando - - # RICHIESTA PRIMO PUNTO - if self.step == 0: # inizio del comando - # imposto il map tool - self.getPointMapTool().setMode(Qad_line_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_FIRST_PT) - # si appresta ad attendere un punto o enter - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(QadMsg.translate("Command_LINE", "Specify first point: "), \ - QadInputTypeEnum.POINT2D, None, "", QadInputModeEnum.NONE) - self.step = 1 - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA PUNTO OPPURE MENU PRINCIPALE - elif self.step == 1: # dopo aver atteso un punto si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - if self.virtualCmd == False: # se si vuole veramente salvare in un layer - self.addLinesToLayer(currLayer) - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - snapTypeOnSel = self.getPointMapTool().snapTypeOnSelection - value = self.getPointMapTool().point - entity = self.getPointMapTool().entity - else: # il punto arriva come parametro della funzione - value = msg - snapTypeOnSel = QadSnapTypeEnum.NONE - - if type(value) == unicode: - if value == QadMsg.translate("Command_LINE", "Undo") or value == "Undo": - self.delLastVertex() # cancello ultimo vertice - # imposto il map tool - if len(self.vertices) == 0: - self.getPointMapTool().setMode(Qad_line_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_FIRST_PT) - # si appresta ad attendere un punto o enter - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(QadMsg.translate("Command_LINE", "Specify first point: "), \ - QadInputTypeEnum.POINT2D, None, "", QadInputModeEnum.NONE) - return False - else: - self.getPointMapTool().firstPt = self.vertices[-1] - self.getPointMapTool().setMode(Qad_line_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT) - elif value == QadMsg.translate("Command_LINE", "Close") or VALUE == "Close": - newPt = self.vertices[0] - self.addVertex(newPt) # aggiungo un nuovo vertice - if self.virtualCmd == False: # se si vuole veramente salvare in un layer - self.addLinesToLayer(currLayer) - return True # fine comando - else: - if len(self.vertices) == 0: # primo punto - if value is None: - if self.plugIn.lastPoint is not None: - value = self.plugIn.lastPoint - else: - return True # fine comando - - # se é stato selezionato un punto con la modalità TAN_DEF é un punto differito - if snapTypeOnSel == QadSnapTypeEnum.TAN_DEF and entity.isInitialized(): - # se era stato selezionato un punto esplicito - if (self.firstPtTan is None) and (self.firstPtPer is None): - self.firstPtPer = None - self.firstPtTan = value - self.firstGeom = QgsGeometry(entity.getGeometry()) # duplico la geometria - coordTransform = QgsCoordinateTransform(entity.layer.crs(), self.plugIn.canvas.mapRenderer().destinationCrs()) # trasformo la geometria - self.firstGeom.transform(coordTransform) - # imposto il map tool - self.getPointMapTool().tan1 = self.firstPtTan - self.getPointMapTool().geom1 = self.firstGeom - self.getPointMapTool().setMode(Qad_line_maptool_ModeEnum.FIRST_TAN_KNOWN_ASK_FOR_SECOND_PT) - # se era stato selezionato un punto con la modalità TAN_DEF - elif self.firstPtTan is not None: - secondGeom = QgsGeometry(entity.getGeometry()) # duplico la geometria - coordTransform = QgsCoordinateTransform(entity.layer.crs(), self.plugIn.canvas.mapRenderer().destinationCrs()) # trasformo la geometria - secondGeom.transform(coordTransform) - tangent = qad_utils.lineFrom2TanPts(self.firstGeom, self.firstPtTan, secondGeom, value) - if tangent is not None: - # prendo il punto più vicino a value - if qad_utils.getDistance(tangent[0], value) < qad_utils.getDistance(tangent[1], value): - self.addVertex(tangent[1]) # aggiungo un nuovo vertice - self.addVertex(tangent[0]) # aggiungo un nuovo vertice - self.getPointMapTool().firstPt = tangent[0] - else: - self.addVertex(tangent[0]) # aggiungo un nuovo vertice - self.addVertex(tangent[1]) # aggiungo un nuovo vertice - self.getPointMapTool().firstPt = tangent[1] - # imposto il map tool - self.getPointMapTool().setMode(Qad_line_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT) - else: - self.showMsg(QadMsg.translate("Command_LINE", "\nNo tangent possible")) - # se era stato selezionato un punto con la modalità PER_DEF - elif self.firstPtPer is not None: - secondGeom = QgsGeometry(entity.getGeometry()) # duplico la geometria - coordTransform = QgsCoordinateTransform(entity.layer.crs(), self.plugIn.canvas.mapRenderer().destinationCrs()) # trasformo la geometria - secondGeom.transform(coordTransform) - tangent = qad_utils.lineFromTanPerPts(secondGeom, value, self.firstGeom, self.firstPtPer) - if tangent is not None: - # prendo il punto più vicino a value - if qad_utils.getDistance(tangent[0], value) < qad_utils.getDistance(tangent[1], value): - self.addVertex(tangent[1]) # aggiungo un nuovo vertice - self.addVertex(tangent[0]) # aggiungo un nuovo vertice - self.getPointMapTool().firstPt = tangent[0] - else: - self.addVertex(tangent[0]) # aggiungo un nuovo vertice - self.addVertex(tangent[1]) # aggiungo un nuovo vertice - self.getPointMapTool().firstPt = tangent[1] - # imposto il map tool - self.getPointMapTool().setMode(Qad_line_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT) - else: - self.showMsg(QadMsg.translate("Command_LINE", "\nNo tangent possible")) - - # se é stato selezionato un punto con la modalità PER_DEF é un punto differito - elif snapTypeOnSel == QadSnapTypeEnum.PER_DEF and entity.isInitialized(): - # se era stato selezionato un punto esplicito - if (self.firstPtTan is None) and (self.firstPtPer is None): - self.firstPtTan = None - self.firstPtPer = value - self.firstGeom = QgsGeometry(entity.getGeometry()) # duplico la geometria - coordTransform = QgsCoordinateTransform(entity.layer.crs(), self.plugIn.canvas.mapRenderer().destinationCrs()) # trasformo la geometria - self.firstGeom.transform(coordTransform) - # imposto il map tool - self.getPointMapTool().per1 = self.firstPtPer - self.getPointMapTool().geom1 = self.firstGeom - self.getPointMapTool().setMode(Qad_line_maptool_ModeEnum.FIRST_PER_KNOWN_ASK_FOR_SECOND_PT) - # se era stato selezionato un punto con la modalità TAN_DEF - elif self.firstPtTan is not None: - secondGeom = QgsGeometry(entity.getGeometry()) # duplico la geometria - coordTransform = QgsCoordinateTransform(entity.layer.crs(), self.plugIn.canvas.mapRenderer().destinationCrs()) # trasformo la geometria - secondGeom.transform(coordTransform) - tangent = qad_utils.lineFromTanPerPts(self.firstGeom, self.firstPtTan, secondGeom, value) - if tangent is not None: - # prendo il punto più vicino a value - if qad_utils.getDistance(tangent[0], value) < qad_utils.getDistance(tangent[1], value): - self.addVertex(tangent[1]) # aggiungo un nuovo vertice - self.addVertex(tangent[0]) # aggiungo un nuovo vertice - self.getPointMapTool().firstPt = tangent[0] - else: - self.addVertex(tangent[0]) # aggiungo un nuovo vertice - self.addVertex(tangent[1]) # aggiungo un nuovo vertice - self.getPointMapTool().firstPt = tangent[1] - # imposto il map tool - self.getPointMapTool().setMode(Qad_line_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT) - else: - self.showMsg(QadMsg.translate("Command_LINE", "\nNo perpendicular possible")) - # se era stato selezionato un punto con la modalità PER_DEF - elif self.firstPtPer is not None: - secondGeom = QgsGeometry(entity.getGeometry()) # duplico la geometria - coordTransform = QgsCoordinateTransform(entity.layer.crs(), self.plugIn.canvas.mapRenderer().destinationCrs()) # trasformo la geometria - secondGeom.transform(coordTransform) - line = qad_utils.lineFrom2PerPts(self.firstGeom, self.firstPtPer, secondGeom, value) - if line is not None: - # prendo il punto più vicino a value - if qad_utils.getDistance(line[0], value) < qad_utils.getDistance(line[1], value): - self.addVertex(line[1]) # aggiungo un nuovo vertice - self.addVertex(line[0]) # aggiungo un nuovo vertice - self.getPointMapTool().firstPt = line[0] - else: - self.addVertex(line[0]) # aggiungo un nuovo vertice - self.addVertex(line[1]) # aggiungo un nuovo vertice - self.getPointMapTool().firstPt = line[1] - # imposto il map tool - self.getPointMapTool().setMode(Qad_line_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT) - else: - self.showMsg(QadMsg.translate("Command_LINE", "\nNo perpendicular possible")) - else: # altrimenti é un punto esplicito - # se era stato selezionato un punto con la modalità TAN_DEF - if self.firstPtTan is not None: - snapper = QadSnapper() - snapper.setSnapPointCRS(self.plugIn.canvas.mapRenderer().destinationCrs()) - snapper.setSnapType(QadSnapTypeEnum.TAN) - snapper.setStartPoint(value) - oSnapPoints = snapper.getSnapPoint(self.firstGeom, self.firstPtTan, - self.plugIn.canvas.mapRenderer().destinationCrs()) - # memorizzo il punto di snap in point (prendo il primo valido) - for item in oSnapPoints.items(): - points = item[1] - if points is not None: - self.addVertex(points[0]) # aggiungo un nuovo vertice - self.addVertex(value) # aggiungo un nuovo vertice - break - - if len(self.vertices) == 0: - self.showMsg(QadMsg.translate("Command_LINE", "\nNo tangent possible")) - # se era stato selezionato un punto con la modalità PER_DEF - elif self.firstPtPer is not None: - snapper = QadSnapper() - snapper.setSnapPointCRS(self.plugIn.canvas.mapRenderer().destinationCrs()) - snapper.setSnapType(QadSnapTypeEnum.PER) - snapper.setStartPoint(value) - oSnapPoints = snapper.getSnapPoint(self.firstGeom, self.firstPtPer, - self.plugIn.canvas.mapRenderer().destinationCrs()) - # memorizzo il punto di snap in point (prendo il primo valido) - for item in oSnapPoints.items(): - points = item[1] - if points is not None: - self.addVertex(points[0]) # aggiungo un nuovo vertice - self.addVertex(value) # aggiungo un nuovo vertice - break - - if len(self.vertices) == 0: - self.showMsg(QadMsg.translate("Command_LINE", "\nNo perpendicular possible")) - else: - self.addVertex(value) # aggiungo un nuovo vertice - - if len(self.vertices) > 0: - # imposto il map tool - self.getPointMapTool().firstPt = value - self.getPointMapTool().setMode(Qad_line_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT) - else: # secondo punto - if value is None: - if self.virtualCmd == False: # se si vuole veramente salvare in un layer - self.addLinesToLayer(currLayer) - return True # fine comando - # se il primo punto é esplicito - if len(self.vertices) > 0 is not None: - self.addVertex(value) # aggiungo un nuovo vertice - # imposto il map tool - self.getPointMapTool().firstPt = value - self.getPointMapTool().setMode(Qad_line_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT) - - if len(self.vertices) > 2: - keyWords = QadMsg.translate("Command_LINE", "Close") + "/" + \ - QadMsg.translate("Command_LINE", "Undo") - else: - keyWords = QadMsg.translate("Command_LINE", "Undo") - prompt = QadMsg.translate("Command_LINE", "Specify next point or [{0}]: ").format(keyWords) - - englishKeyWords = "Close" + "/" + "Undo" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o enter o una parola chiave - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - None, \ - keyWords, QadInputModeEnum.NONE) - - return False - \ No newline at end of file diff --git a/qad_maptool.py b/qad_maptool.py index f07702be..83824f30 100644 --- a/qad_maptool.py +++ b/qad_maptool.py @@ -1,698 +1,946 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - map tool per lo stato di quiete - - ------------------- - begin : 2013-05-22 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -from qgis.gui import * - - -import qad_utils -from qad_variables import * -from qad_rubberband import * -from qad_getpoint import * -from qad_generic_cmd import QadCommandClass -from qad_entity import * -from qad_ssget_cmd import QadSSGetClass -from qad_grip import * -from qad_stretch_cmd import QadGRIPSTRETCHCommandClass -from qad_move_cmd import QadGRIPMOVECommandClass -from qad_rotate_cmd import QadGRIPROTATECommandClass -from qad_scale_cmd import QadGRIPSCALECommandClass -from qad_mirror_cmd import QadGRIPMIRRORCommandClass -from qad_arc_cmd import QadGRIPCHANGEARCRADIUSCommandClass -from qad_lengthen_cmd import QadGRIPLENGTHENCommandClass -from qad_pedit_cmd import QadGRIPINSERTREMOVEVERTEXCommandClass, QadGRIPARCLINECONVERTCommandClass - -from qad_msg import QadMsg - - -# Main Map Tool class. -class QadMapTool(QgsMapTool): - - def __init__(self, plugIn): - QgsMapTool.__init__(self, plugIn.iface.mapCanvas()) - self.plugIn = plugIn - self.iface = self.plugIn.iface - self.canvas = self.plugIn.iface.mapCanvas() - self.cursor = QCursor(Qt.BlankCursor) - self.__csrRubberBand = QadCursorRubberBand(self.canvas, QadCursorTypeEnum.BOX | QadCursorTypeEnum.CROSS) - self.entitySet = QadEntitySet() - self.entitySetGripPoints = QadEntitySetGripPoints(plugIn) - - self.gripPopupMenu = None - self.timerForGripMenu = QTimer() - self.timerForGripMenu.setSingleShot(True) - - def __del__(self): - self.removeItems() - - - def removeItems(self): - if self.__csrRubberBand is not None: - self.__csrRubberBand.removeItems() # prima lo stacco dal canvas altrimenti non si rimuove perchè usato da canvas - del self.__csrRubberBand - __csrRubberBand = None - self.entitySet.clear() - self.entitySetGripPoints.removeItems() - - - def UpdatedVariablesEvent(self): - # aggiorna in base alle nuove impostazioni delle variabili - self.removeItems() - self.__csrRubberBand = QadCursorRubberBand(self.canvas, QadCursorTypeEnum.BOX | QadCursorTypeEnum.CROSS) - - - def clearEntitySet(self): - self.entitySet.deselectOnLayer() - self.entitySet.clear() - - - def clearEntityGripPoints(self): - self.entitySetGripPoints.removeItems() # svuoto la lista - - - def refreshEntityGripPoints(self, entitySet = None): - if entitySet is None: - entitySet = self.entitySet - - # cancello i grip delle entità che non sono in entitySet o che non sono in layer vettoriali modificabili - i = self.entitySetGripPoints.count() - 1 - while i >= 0: - entityGripPoint = self.entitySetGripPoints.entityGripPoints[i] - if entitySet.containsEntity(entityGripPoint.entity) == False or \ - entityGripPoint.entity.layer.type() != QgsMapLayer.VectorLayer or entityGripPoint.entity.layer.isEditable() == False: - del self.entitySetGripPoints.entityGripPoints[i] - i = i - 1 - - entity = QadEntity() - for layerEntitySet in entitySet.layerEntitySetList: - # considero solo i layer vettoriali che sono modificabili - layer = layerEntitySet.layer - if layer.type() == QgsMapLayer.VectorLayer and layer.isEditable(): - for featureId in layerEntitySet.featureIds: - entity.set(layer, featureId) - self.entitySetGripPoints.addEntity(entity, QadVariables.get(QadMsg.translate("Environment variables", "GRIPS"))) - - - #============================================================================ - # INIZIO - eventi per il mouse - #============================================================================ - - - def canvasPressEvent(self, event): - # volevo mettere questo evento nel canvasReleaseEvent - # ma il tasto destro non genera quel tipo di evento - if event.button() == Qt.RightButton: - self.displayPopupMenuOnQuiescentState(event.pos()) - elif event.button() == Qt.LeftButton: - # verifico se tasto shift premuto - shiftKey = True if event.modifiers() & Qt.ShiftModifier else False - # posizione corrente del mouse - point = self.toMapCoordinates(event.pos()) - # leggo il punto grip che si interseca alla posizione del mouse - entityGripPoint = self.entitySetGripPoints.isIntersecting(point) - if entityGripPoint is not None: - if shiftKey == False: # lancio il comando - selectedEntityGripPoints = self.entitySetGripPoints.getSelectedEntityGripPoints() - # se non ci sono già grip selezionati - if len(selectedEntityGripPoints) == 0: - # seleziono il corrente - if self.entitySetGripPoints.selectIntersectingGripPoints(point) > 0: - selectedEntityGripPoints = self.entitySetGripPoints.getSelectedEntityGripPoints() - - # lancio il comando - self.plugIn.runCommand("QadVirtualGripCommandsClass", [QadVirtualGripCommandsEnum.STRECTH, self.entitySetGripPoints, point]) - else: # shift premuto - # inverto lo stato ai grip che intersecano il punto - self.entitySetGripPoints.toggleSelectIntersectingGripPoints(point) - else: - result = qad_utils.getEntSel(event.pos(), self) - if result is not None: - feature = result[0] - layer = result[1] - tmpEntity = QadEntity() - tmpEntity.set(layer, feature.id()) - SSGetClass = QadSSGetClass(self.plugIn) - SSGetClass.entitySet.set(self.entitySet) - SSGetClass.elaborateEntity(tmpEntity, shiftKey) - self.entitySet.set(SSGetClass.entitySet) - del SSGetClass # che deseleziona gli oggetti - self.entitySet.selectOnLayer(False) - self.refreshEntityGripPoints(self.entitySet) - else: - self.plugIn.runCommand("QadVirtualSelCommandClass", point) - - - def canvasDoubleClickEvent(self,event): - pass - - - def canvasMoveEvent(self, event): - self.timerForGripMenu.stop() - point = self.toMapCoordinates(event.pos()) - self.__csrRubberBand.moveEvent(point) - if self.entitySetGripPoints.hoverIntersectingGripPoints(point) == 1: - for entityGripPoint in self.entitySetGripPoints.entityGripPoints: - for gripPoint in entityGripPoint.gripPoints: - if gripPoint.isIntersecting(point) and gripPoint.getStatus() == QadGripStatusEnum.HOVER: - pos = QPoint(event.pos().x(), event.pos().y()) - shot = lambda: self.displayPopupMenuOnGrip(pos, entityGripPoint.entity, gripPoint) - - del self.timerForGripMenu - self.timerForGripMenu = QTimer() - self.timerForGripMenu.setSingleShot(True) - self.timerForGripMenu.timeout.connect(shot) - self.timerForGripMenu.start(1000) - return - - # se non ci sono grip point selezionati - if len(self.entitySetGripPoints.getSelectedEntityGripPoints()) == 0: - # leggo il punto grip che si interseca alla posizione del mouse - entityGripPoint = self.entitySetGripPoints.isIntersecting(point) - if entityGripPoint is not None: - # leggo il primo punto di grip che interseca point (in map coordinate) - gripPoint = entityGripPoint.isIntersecting(point) - - - def canvasReleaseEvent(self, event): - pass - - - #============================================================================ - # FINE - eventi per il mouse - # INIZIO - eventi per la tastiera - #============================================================================ - - - def keyPressEvent(self, event): - self.plugIn.keyPressEvent(event) - - - def keyReleaseEvent(self, event): - pass - - - #============================================================================ - # FINE - eventi per la tastiera - # INIZIO - eventi per la rotella - #============================================================================ - - - def wheelEvent(self, event): - self.__csrRubberBand.moveEvent(self.toMapCoordinates(event.pos())) - - - #============================================================================ - # FINE - eventi per la rotella - #============================================================================ - - - def activate(self): - self.canvas.setCursor(self.cursor) - # posizione corrente del mouse - self.__csrRubberBand.moveEvent(self.toMapCoordinates(self.canvas.mouseLastXY())) - self.__csrRubberBand.show() - self.entitySet.initByCurrentQgsSelectedFeatures(self.canvas.layers()) - self.refreshEntityGripPoints(self.entitySet) - - self.plugIn.QadCommands.continueCommandFromMapTool() - - def deactivate(self): - self.__csrRubberBand.hide() - self.timerForGripMenu.stop() - - def isTransient(self): - return False # questo tool non fa zoom o pan - - def isEditTool(self): - return False # questo tool non fa editing - - - #============================================================================ - # displayPopupMenuOnQuiescentState - #============================================================================ - def displayPopupMenuOnQuiescentState(self, pos): - popupMenu = QMenu(self.canvas) - history = self.plugIn.getHistoryfromTxtWindow() - isLastCmdToInsert = True - isRecentMenuToInsert = True - - historyLen = len(history) - i = historyLen - 1 - cmdInputHistoryMax = QadVariables.get(QadMsg.translate("Environment variables", "CMDINPUTHISTORYMAX")) - while i >= 0 and (historyLen - i) <= cmdInputHistoryMax: - cmdName = history[i] - i = i - 1 - cmd = self.plugIn.QadCommands.getCommandObj(cmdName) - if cmd is not None: - if isLastCmdToInsert: - isLastCmdToInsert = False - msg = QadMsg.translate("Popup_menu_graph_window", "Repeat ") + cmd.getName() - icon = cmd.getIcon() - if icon is None: - lastCmdAction = QAction(msg, popupMenu) - else: - lastCmdAction = QAction(icon, msg, popupMenu) - cmd.connectQAction(lastCmdAction) - popupMenu.addAction(lastCmdAction) - else: - if isRecentMenuToInsert: - isRecentMenuToInsert = False - recentCmdsMenu = popupMenu.addMenu(QadMsg.translate("Popup_menu_graph_window", "Recent commands")) - - icon = cmd.getIcon() - if icon is None: - recentCmdAction = QAction(cmd.getName(), recentCmdsMenu) - else: - recentCmdAction = QAction(icon, cmd.getName(), recentCmdsMenu) - cmd.connectQAction(recentCmdAction) - recentCmdsMenu.addAction(recentCmdAction) - - if isLastCmdToInsert == False: # menu non vuoto - popupMenu.popup(self.canvas.mapToGlobal(pos)) - - - #============================================================================ - # runCmdFromPopupMenuOnGrip - #============================================================================ - def runCmdFromPopupMenuOnGrip(self, virtualGripCommand, gripPoint): - # seleziona il grip - gripPoint.select() - # lancio il comando - self.plugIn.runCommand("QadVirtualGripCommandsClass", [virtualGripCommand, self.entitySetGripPoints, gripPoint.getPoint()]) - - - #============================================================================ - # displayPopupMenuOnGrip - #============================================================================ - def displayPopupMenuOnGrip(self, pos, entity, gripPoint): - if self.gripPopupMenu is not None: - self.gripPopupMenu.hide() - del self.gripPopupMenu - self.gripPopupMenu = None - - popupMenu = QadGripPopupMenu(self.canvas) - - found = False - - # verifico se l'entità appartiene ad uno stile di quotatura - if entity.isDimensionComponent(): - pass - else: - entityType = entity.getEntityType(gripPoint.atGeom, gripPoint.atSubGeom) - if entityType == QadEntityGeomTypeEnum.ARC: - arc = entity.getQadGeom(gripPoint.atGeom, gripPoint.atSubGeom) - - # se punti finali - if gripPoint.isIntersecting(arc.getStartPt()) or gripPoint.isIntersecting(arc.getEndPt()): - found = True - msg = QadMsg.translate("Popup_menu_grip_window", "Stretch") - action = QAction(msg, popupMenu) - f = lambda : self.runCmdFromPopupMenuOnGrip(QadVirtualGripCommandsEnum.STRECTH, gripPoint) - QObject.connect(action, SIGNAL("triggered()"), f) - popupMenu.addAction(action) - - msg = QadMsg.translate("Popup_menu_grip_window", "Lengthen") - action = QAction(msg, popupMenu) - f = lambda : self.runCmdFromPopupMenuOnGrip(QadVirtualGripCommandsEnum.LENGTHEN, gripPoint) - QObject.connect(action, SIGNAL("triggered()"), f) - popupMenu.addAction(action) - # se punto medio - elif gripPoint.isIntersecting(entity.qadGeom.getMiddlePt()): - found = True - msg = QadMsg.translate("Popup_menu_grip_window", "Stretch") - action = QAction(msg, popupMenu) - f = lambda : self.runCmdFromPopupMenuOnGrip(QadVirtualGripCommandsEnum.STRECTH, gripPoint) - QObject.connect(action, SIGNAL("triggered()"), f) - popupMenu.addAction(action) - - msg = QadMsg.translate("Popup_menu_grip_window", "Radius") - action = QAction(msg, popupMenu) - f = lambda : self.runCmdFromPopupMenuOnGrip(QadVirtualGripCommandsEnum.CHANGE_RADIUS, gripPoint) - QObject.connect(action, SIGNAL("triggered()"), f) - popupMenu.addAction(action) - - msg = QadMsg.translate("Popup_menu_grip_window", "Convert to line") - action = QAction(msg, popupMenu) - f = lambda : self.runCmdFromPopupMenuOnGrip(QadVirtualGripCommandsEnum.ARC_TO_LINE, gripPoint) - QObject.connect(action, SIGNAL("triggered()"), f) - popupMenu.addAction(action) - - elif entityType == QadEntityGeomTypeEnum.LINESTRING: - linearObjectList = entity.getQadGeom(gripPoint.atGeom, gripPoint.atSubGeom) - isClosed = linearObjectList.isClosed() - nVertex = 0 - found = False - while nVertex < linearObjectList.qty(): - linearObject = linearObjectList.getLinearObjectAt(nVertex) - - if gripPoint.isIntersecting(linearObject.getStartPt()): - found = True - msg = QadMsg.translate("Popup_menu_grip_window", "Stretch vertex") - action = QAction(msg, popupMenu) - f = lambda : self.runCmdFromPopupMenuOnGrip(QadVirtualGripCommandsEnum.STRECTH, gripPoint) - QObject.connect(action, SIGNAL("triggered()"), f) - popupMenu.addAction(action) - - # punto iniziale - if isClosed == False and nVertex == 0: - msg = QadMsg.translate("Popup_menu_grip_window", "Lengthen") - action = QAction(msg, popupMenu) - f = lambda : self.runCmdFromPopupMenuOnGrip(QadVirtualGripCommandsEnum.LENGTHEN, gripPoint) - QObject.connect(action, SIGNAL("triggered()"), f) - popupMenu.addAction(action) - - msg = QadMsg.translate("Popup_menu_grip_window", "Add vertex") - action = QAction(msg, popupMenu) - f = lambda : self.runCmdFromPopupMenuOnGrip(QadVirtualGripCommandsEnum.ADD_VERTEX, gripPoint) - QObject.connect(action, SIGNAL("triggered()"), f) - popupMenu.addAction(action) - - msg = QadMsg.translate("Popup_menu_grip_window", "Add vertex before") - action = QAction(msg, popupMenu) - f = lambda : self.runCmdFromPopupMenuOnGrip(QadVirtualGripCommandsEnum.ADD_VERTEX_BEFORE, gripPoint) - QObject.connect(action, SIGNAL("triggered()"), f) - popupMenu.addAction(action) - break - - # punto medio - if gripPoint.isIntersecting(linearObject.getMiddlePt()): - found = True - msg = QadMsg.translate("Popup_menu_grip_window", "Stretch") - action = QAction(msg, popupMenu) - f = lambda : self.runCmdFromPopupMenuOnGrip(QadVirtualGripCommandsEnum.STRECTH, gripPoint) - QObject.connect(action, SIGNAL("triggered()"), f) - popupMenu.addAction(action) - - msg = QadMsg.translate("Popup_menu_grip_window", "Add vertex") - action = QAction(msg, popupMenu) - f = lambda : self.runCmdFromPopupMenuOnGrip(QadVirtualGripCommandsEnum.ADD_VERTEX, gripPoint) - QObject.connect(action, SIGNAL("triggered()"), f) - popupMenu.addAction(action) - - msg = QadMsg.translate("Popup_menu_grip_window", "Add vertex before") - action = QAction(msg, popupMenu) - f = lambda : self.runCmdFromPopupMenuOnGrip(QadVirtualGripCommandsEnum.ADD_VERTEX_BEFORE, gripPoint) - QObject.connect(action, SIGNAL("triggered()"), f) - popupMenu.addAction(action) - - if linearObject.isSegment(): # linea - msg = QadMsg.translate("Popup_menu_grip_window", "Convert to arc") - action = QAction(msg, popupMenu) - f = lambda : self.runCmdFromPopupMenuOnGrip(QadVirtualGripCommandsEnum.LINE_TO_ARC, gripPoint) - else: # arco - msg = QadMsg.translate("Popup_menu_grip_window", "Convert to line") - action = QAction(msg, popupMenu) - f = lambda : self.runCmdFromPopupMenuOnGrip(QadVirtualGripCommandsEnum.ARC_TO_LINE, gripPoint) - QObject.connect(action, SIGNAL("triggered()"), f) - popupMenu.addAction(action) - break - - nVertex = nVertex + 1 - - linearObject = linearObjectList.getLinearObjectAt(-1) # ultima parte - if not found and isClosed == False: - # punto finale - if gripPoint.isIntersecting(linearObject.getEndPt()): - found = True - msg = QadMsg.translate("Popup_menu_grip_window", "Stretch vertex") - action = QAction(msg, popupMenu) - f = lambda : self.runCmdFromPopupMenuOnGrip(QadVirtualGripCommandsEnum.STRECTH, gripPoint) - QObject.connect(action, SIGNAL("triggered()"), f) - popupMenu.addAction(action) - - msg = QadMsg.translate("Popup_menu_grip_window", "Lengthen") - action = QAction(msg, popupMenu) - f = lambda : self.runCmdFromPopupMenuOnGrip(QadVirtualGripCommandsEnum.LENGTHEN, gripPoint) - QObject.connect(action, SIGNAL("triggered()"), f) - popupMenu.addAction(action) - - msg = QadMsg.translate("Popup_menu_grip_window", "Add vertex") - action = QAction(msg, popupMenu) - f = lambda : self.runCmdFromPopupMenuOnGrip(QadVirtualGripCommandsEnum.ADD_VERTEX, gripPoint) - QObject.connect(action, SIGNAL("triggered()"), f) - popupMenu.addAction(action) - - msg = QadMsg.translate("Popup_menu_grip_window", "Add vertex before") - action = QAction(msg, popupMenu) - f = lambda : self.runCmdFromPopupMenuOnGrip(QadVirtualGripCommandsEnum.ADD_VERTEX_BEFORE, gripPoint) - QObject.connect(action, SIGNAL("triggered()"), f) - popupMenu.addAction(action) - - if isClosed == False: # polyline - # ci devono essere almeno 2 parti - if linearObjectList.qty() >= 2: - msg = QadMsg.translate("Popup_menu_grip_window", "Remove vertex") - action = QAction(msg, popupMenu) - f = lambda : self.runCmdFromPopupMenuOnGrip(QadVirtualGripCommandsEnum.REMOVE_VERTEX, gripPoint) - QObject.connect(action, SIGNAL("triggered()"), f) - popupMenu.addAction(action) - else: # polygon - # ci devono essere almeno 4 parti - if linearObjectList.qty() >= 4: - msg = QadMsg.translate("Popup_menu_grip_window", "Remove vertex") - action = QAction(msg, popupMenu) - f = lambda : self.runCmdFromPopupMenuOnGrip(QadVirtualGripCommandsEnum.REMOVE_VERTEX, gripPoint) - QObject.connect(action, SIGNAL("triggered()"), f) - popupMenu.addAction(action) - - if found: # menu non vuoto - popupMenu.popup(self.canvas.mapToGlobal(pos)) - self.gripPopupMenu = popupMenu - - return None - - -class QadGripPopupMenu(QMenu): - def __init__(self, parent): - QMenu.__init__(self, parent) - self.offset = 0 - - def popup(self, pos, action = None): - newPos = QPoint(pos.x() + self.offset, pos.y() + self.offset) - QMenu.popup(self, newPos, action) - -# def leaveEvent(self, event): -# if event.pos().x() < -1 * self.offset or event.pos().y() < -1 * self.offset: -# self.hide() - #self.hide() - - def mouseMoveEvent(self, event): - x = event.pos().x() - y = event.pos().y() - if x < -1 * self.offset or y < -1 * self.offset or \ - x > self.width() or y > self.height(): - self.hide() - else: - QMenu.mouseMoveEvent(self, event) - -# newPos = self.parentWidget().mapFromGlobal(event.globalPos()) -# newMouseEvent = QMouseEvent(QEvent.MouseMove, newPos, Qt.NoButton, event.buttons(), event.modifiers()) -# self.parentWidget().mouseMoveEvent(newMouseEvent) - - -# Classe che gestisce il comando di selezione quando QAD è in stato di quiete -class QadVirtualSelCommandClass(QadCommandClass): - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadVirtualSelCommandClass(self.plugIn) - - def getName(self): - return "QadVirtualSelCommandClass" - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - self.SSGetClass = QadSSGetClass(plugIn) - self.SSGetClass.entitySet.set(plugIn.tool.entitySet) # da usare solo con QadMapTool - self.SSGetClass.exitAfterSelection = True - self.SSGetClass.step = 1 - - def __del__(self): - QadCommandClass.__del__(self) - del self.SSGetClass - - def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): - return self.SSGetClass.getPointMapTool(drawMode) - - def run(self, msgMapTool = False, msg = None): - res = self.SSGetClass.run(msgMapTool, msg) - if res == True: - self.plugIn.tool.entitySet.set(self.SSGetClass.entitySet) # da usare solo con QadMapTool - self.plugIn.tool.entitySet.selectOnLayer() - return res - - -#=============================================================================== -# QadVirtualGripCommandsEnum class. -#=============================================================================== -class QadVirtualGripCommandsEnum(): - NONE = 0 - STRECTH = 1 - MOVE = 2 - ROTATE = 3 - SCALE = 4 - MIRROR = 5 - LENGTHEN = 6 - ADD_VERTEX = 7 - REMOVE_VERTEX = 8 - LINE_TO_ARC = 9 - ARC_TO_LINE = 10 - CHANGE_RADIUS = 11 - ADD_VERTEX_BEFORE = 12 - - -# Classe che gestisce i comando disponibili sui grip quando QAD è in stato di quiete -class QadVirtualGripCommandsClass(QadCommandClass): - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadVirtualGripCommandsClass(self.plugIn) - - def getName(self): - return "QadVirtualGripCommandsClass" - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - self.commandNum = QadVirtualGripCommandsEnum.NONE - self.currentCommand = None - self.entitySetGripPoints = None - self.basePt = QgsPoint() - - def __del__(self): - QadCommandClass.__del__(self) - del self.currentCommand - - - def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): - if self.currentCommand is not None: - return self.currentCommand.getPointMapTool(drawMode) - else: - return None - - def getCommand(self): - if self.commandNum == QadVirtualGripCommandsEnum.STRECTH: - return QadGRIPSTRETCHCommandClass(self.plugIn) - elif self.commandNum == QadVirtualGripCommandsEnum.MOVE: - return QadGRIPMOVECommandClass(self.plugIn) - elif self.commandNum == QadVirtualGripCommandsEnum.ROTATE: - return QadGRIPROTATECommandClass(self.plugIn) - elif self.commandNum == QadVirtualGripCommandsEnum.SCALE: - return QadGRIPSCALECommandClass(self.plugIn) - elif self.commandNum == QadVirtualGripCommandsEnum.MIRROR: - return QadGRIPMIRRORCommandClass(self.plugIn) - elif self.commandNum == QadVirtualGripCommandsEnum.CHANGE_RADIUS: - return QadGRIPCHANGEARCRADIUSCommandClass(self.plugIn) - elif self.commandNum == QadVirtualGripCommandsEnum.LENGTHEN: - return QadGRIPLENGTHENCommandClass(self.plugIn) - elif self.commandNum == QadVirtualGripCommandsEnum.ADD_VERTEX: - cmd = QadGRIPINSERTREMOVEVERTEXCommandClass(self.plugIn) - cmd.setInsertVertexAfter_Mode() - return cmd - elif self.commandNum == QadVirtualGripCommandsEnum.ADD_VERTEX_BEFORE: - cmd = QadGRIPINSERTREMOVEVERTEXCommandClass(self.plugIn) - cmd.setInsertVertexBefore_Mode() - return cmd - elif self.commandNum == QadVirtualGripCommandsEnum.REMOVE_VERTEX: - cmd = QadGRIPINSERTREMOVEVERTEXCommandClass(self.plugIn) - cmd.setRemoveVertex_mode() - return cmd - elif self.commandNum == QadVirtualGripCommandsEnum.LINE_TO_ARC: - cmd = QadGRIPARCLINECONVERTCommandClass(self.plugIn) - cmd.setLineToArcConvert_Mode() - return cmd - elif self.commandNum == QadVirtualGripCommandsEnum.ARC_TO_LINE: - cmd = QadGRIPARCLINECONVERTCommandClass(self.plugIn) - cmd.setArcToLineConvert_Mode() - return cmd - - return None - - - def initStartCommand(self, commandNum): - if self.currentCommand is not None: - del self.currentCommand - self.currentCommand = None - - self.commandNum = commandNum - self.currentCommand = self.getCommand() - - if self.currentCommand is not None: - self.currentCommand.basePt.set(self.basePt.x(), self.basePt.y()) - self.currentCommand.setSelectedEntityGripPoints(self.entitySetGripPoints) - return True - else: - return False - - - def initNextCommand(self): - if self.currentCommand is not None: - del self.currentCommand - self.currentCommand = None - - if self.commandNum == QadVirtualGripCommandsEnum.STRECTH or \ - self.commandNum == QadVirtualGripCommandsEnum.LENGTHEN or \ - self.commandNum == QadVirtualGripCommandsEnum.ADD_VERTEX or \ - self.commandNum == QadVirtualGripCommandsEnum.ADD_VERTEX_BEFORE or \ - self.commandNum == QadVirtualGripCommandsEnum.REMOVE_VERTEX or \ - self.commandNum == QadVirtualGripCommandsEnum.LINE_TO_ARC or \ - self.commandNum == QadVirtualGripCommandsEnum.ARC_TO_LINE or \ - self.commandNum == QadVirtualGripCommandsEnum.CHANGE_RADIUS: - self.commandNum = QadVirtualGripCommandsEnum.MOVE - elif self.commandNum == QadVirtualGripCommandsEnum.MOVE: - self.commandNum = QadVirtualGripCommandsEnum.ROTATE - elif self.commandNum == QadVirtualGripCommandsEnum.ROTATE: - self.commandNum = QadVirtualGripCommandsEnum.SCALE - elif self.commandNum == QadVirtualGripCommandsEnum.SCALE: - self.commandNum = QadVirtualGripCommandsEnum.MIRROR - elif self.commandNum == QadVirtualGripCommandsEnum.MIRROR: - self.commandNum = QadVirtualGripCommandsEnum.MOVE - - self.currentCommand = self.getCommand() - - if self.currentCommand is not None: - self.currentCommand.basePt.set(self.basePt.x(), self.basePt.y()) - self.currentCommand.setSelectedEntityGripPoints(self.entitySetGripPoints) - return True - else: - return False - - - def run(self, msgMapTool = False, msg = None): - if self.currentCommand is None: - return True - res = self.currentCommand.run(msgMapTool, msg) - if res == True: - if self.currentCommand.skipToNextGripCommand == True: - if self.initNextCommand(): # attivo comando successivo - return self.currentCommand.run(msgMapTool, msg) - else: - # ridisegno i grip point nelle nuove posizioni resettando quelli selezionati - self.plugIn.tool.clearEntityGripPoints() - self.plugIn.tool.refreshEntityGripPoints() - - return res - +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + map tool per lo stato di quiete + + ------------------- + begin : 2013-05-22 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from qgis.core import QgsMapLayer, QgsPointXY +from qgis.gui import QgsMapTool +from qgis.PyQt.QtCore import Qt, QTimer, QPoint, QEvent +from qgis.PyQt.QtGui import QCursor, QKeyEvent +from qgis.PyQt.QtWidgets import QAction, QMenu + +import datetime + + +from . import qad_utils +from .qad_variables import QadVariables +from .qad_rubberband import QadCursorRubberBand, QadCursorTypeEnum +from .qad_getpoint import QadGetPointDrawModeEnum +from .qad_entity import QadEntitySet, QadEntity +from .qad_grip import QadEntitySetGripPoints, QadGripStatusEnum +from .qad_dynamicinput import QadDynamicCmdInput, QadDynamicInputContextEnum + +from .cmd.qad_generic_cmd import QadCommandClass +from .cmd.qad_ssget_cmd import QadSSGetClass +from .cmd.qad_arc_cmd import QadGRIPCHANGEARCRADIUSCommandClass +from .cmd.qad_generic_cmd import QadCommandClass +from .cmd.qad_lengthen_cmd import QadGRIPLENGTHENCommandClass +from .cmd.qad_mirror_cmd import QadGRIPMIRRORCommandClass +from .cmd.qad_move_cmd import QadGRIPMOVECommandClass +from .cmd.qad_pedit_cmd import QadGRIPINSERTREMOVEVERTEXCommandClass, QadGRIPARCLINECONVERTCommandClass +from .cmd.qad_rotate_cmd import QadGRIPROTATECommandClass +from .cmd.qad_scale_cmd import QadGRIPSCALECommandClass +from .cmd.qad_ssget_cmd import QadSSGetClass +from .cmd.qad_stretch_cmd import QadGRIPSTRETCHCommandClass +from .qad_dim import QadDimStyles +from .qad_msg import QadMsg + + +# Main Map Tool class. +class QadMapTool(QgsMapTool): + + def __init__(self, plugIn): + QgsMapTool.__init__(self, plugIn.iface.mapCanvas()) + self.plugIn = plugIn + self.iface = self.plugIn.iface + self.canvas = self.plugIn.iface.mapCanvas() + self.cursor = QCursor(Qt.BlankCursor) + self.__csrRubberBand = QadCursorRubberBand(self.canvas, QadCursorTypeEnum.BOX | QadCursorTypeEnum.CROSS) + self.entitySet = QadEntitySet() + self.entitySetGripPoints = QadEntitySetGripPoints(plugIn) + + self.gripPopupMenu = None + self.timerForGripMenu = QTimer() + self.timerForGripMenu.setSingleShot(True) + + self.startDateTimeForRightClick = 0 + + # input dinamico + self.dynamicCmdInput = QadDynamicCmdInput(plugIn) + + + def __del__(self): + self.removeItems() + + + def removeItems(self): + if self.__csrRubberBand is not None: + self.__csrRubberBand.removeItems() # prima lo stacco dal canvas altrimenti non si rimuove perchè usato da canvas + del self.__csrRubberBand + __csrRubberBand = None + self.entitySet.clear() + self.entitySetGripPoints.removeItems() + + if self.dynamicCmdInput is not None: + self.dynamicCmdInput.removeItems() + del self.dynamicCmdInput + self.dynamicCmdInput = None + + + # ============================================================================ + # getDynamicInput + # ============================================================================ + def getDynamicInput(self): + return self.dynamicCmdInput + + + # ============================================================================ + # UpdatedVariablesEvent + # ============================================================================ + def UpdatedVariablesEvent(self): + # aggiorna in base alle nuove impostazioni delle variabili + self.removeItems() + self.__csrRubberBand = QadCursorRubberBand(self.canvas, QadCursorTypeEnum.BOX | QadCursorTypeEnum.CROSS) + if self.dynamicCmdInput is not None: + del self.dynamicCmdInput + self.dynamicCmdInput = QadDynamicCmdInput(self.plugIn) + + + # ============================================================================ + # clearEntitySet + # ============================================================================ + def clearEntitySet(self): + self.entitySet.deselectOnLayer() + self.entitySet.clear() + + + # ============================================================================ + # clearEntityGripPoints + # ============================================================================ + def clearEntityGripPoints(self): + self.entitySetGripPoints.removeItems() # svuoto la lista + + + # ============================================================================ + # refreshEntityGripPoints + # ============================================================================ + def refreshEntityGripPoints(self, entitySet = None): + if entitySet is None: + entitySet = self.entitySet + + gripObjLimit = QadVariables.get(QadMsg.translate("Environment variables", "GRIPOBJLIMIT")) + if gripObjLimit != 0: # When set to 0, grips are always displayed. + if entitySet.count() > gripObjLimit: + # Suppresses the display of grips when the selection set includes more than the specified number of objects + self.clearEntityGripPoints() + return + + # cancello i grip delle entità che non sono in entitySet o che non sono in layer vettoriali modificabili + i = self.entitySetGripPoints.count() - 1 + while i >= 0: + entityGripPoint = self.entitySetGripPoints.entityGripPoints[i] + if entitySet.containsEntity(entityGripPoint.entity) == False or \ + entityGripPoint.entity.layer.type() != QgsMapLayer.VectorLayer or entityGripPoint.entity.layer.isEditable() == False: + self.entitySetGripPoints.entityGripPoints[i].removeItems() # lo stacco dal canvas + del self.entitySetGripPoints.entityGripPoints[i] + i = i - 1 + + entity = QadEntity() + for layerEntitySet in entitySet.layerEntitySetList: + # considero solo i layer vettoriali che sono modificabili + layer = layerEntitySet.layer + if layer.type() == QgsMapLayer.VectorLayer and layer.isEditable(): + for featureId in layerEntitySet.featureIds: + entity.set(layer, featureId) + self.entitySetGripPoints.addEntity(entity, QadVariables.get(QadMsg.translate("Environment variables", "GRIPS"))) + + + # ============================================================================ + # INIZIO - eventi per il mouse + # ============================================================================ + + + # ============================================================================ + # canvasPressEvent + # ============================================================================ + def canvasPressEvent(self, event): + if event.button() == Qt.RightButton: + self.startDateTimeForRightClick = datetime.datetime.now() + elif event.button() == Qt.LeftButton: + # verifico se tasto shift premuto + shiftKey = True if event.modifiers() & Qt.ShiftModifier else False + # posizione corrente del mouse + point = self.toMapCoordinates(event.pos()) + # leggo il punto grip che si interseca alla posizione del mouse + entityGripPoints, entityGripPoint = self.entitySetGripPoints.isIntersecting(point) + if entityGripPoint is not None: + if shiftKey == False: # lancio il comando + selectedEntityGripPoints = self.entitySetGripPoints.getSelectedEntityGripPoints() + # se non ci sono già grip selezionati + if len(selectedEntityGripPoints) == 0: + # seleziono il corrente + if self.entitySetGripPoints.selectIntersectingGripPoints(point) > 0: + selectedEntityGripPoints = self.entitySetGripPoints.getSelectedEntityGripPoints() + + # lancio il comando + self.plugIn.runCommand("QadVirtualGripCommandsClass", [QadVirtualGripCommandsEnum.STRECTH, \ + self.entitySetGripPoints, entityGripPoint.getPoint()]) + else: # shift premuto + # inverto lo stato ai grip che intersecano il punto + self.entitySetGripPoints.toggleSelectIntersectingGripPoints(point) + else: + result = qad_utils.getEntSel(event.pos(), self, \ + QadVariables.get(QadMsg.translate("Environment variables", "PICKBOX"))) + if result is not None: + feature = result[0] + layer = result[1] + tmpEntity = QadEntity() + tmpEntity.set(layer, feature.id()) + SSGetClass = QadSSGetClass(self.plugIn) + SSGetClass.entitySet.set(self.entitySet) + SSGetClass.elaborateEntity(tmpEntity, shiftKey) + self.plugIn.showMsg("\n", True) # ripete il prompt + self.entitySet.set(SSGetClass.entitySet) + del SSGetClass # che deseleziona gli oggetti + self.entitySet.selectOnLayer(False) + self.refreshEntityGripPoints(self.entitySet) + else: + self.plugIn.runCommand("QadVirtualSelCommandClass", point) + + + # ============================================================================ + # canvasDoubleClickEvent + # ============================================================================ + def canvasDoubleClickEvent(self,event): + pass + + + # ============================================================================ + # canvasMoveEvent + # ============================================================================ + def canvasMoveEvent(self, event): + self.timerForGripMenu.stop() + point = self.toMapCoordinates(event.pos()) + self.__csrRubberBand.moveEvent(point) + + if self.dynamicCmdInput.prevPart is not None or self.dynamicCmdInput.nextPart is not None: + changedPart = True + else: + changedPart = False + self.dynamicCmdInput.setPrevPart(None) + self.dynamicCmdInput.setNextPart(None) + + # hover grip points + if self.entitySetGripPoints.hoverIntersectingGripPoints(point) == 1: + for entityGripPoint in self.entitySetGripPoints.entityGripPoints: + for gripPoint in entityGripPoint.gripPoints: + if gripPoint.isIntersecting(point) and gripPoint.getStatus() == QadGripStatusEnum.HOVER: + self.dynamicCmdInput.setPrevNextPart(entityGripPoint.entity, gripPoint) + self.dynamicCmdInput.show(True, self.canvas.mouseLastXY()) + + # Specifica i metodi di accesso per le opzioni dei grip multifunzionali. + # se > 1 devono essere mostrati i menu dinamici + if QadVariables.get(QadMsg.translate("Environment variables", "GRIPMULTIFUNCTIONAL")) > 1: + pos = QPoint(event.pos().x(), event.pos().y()) + shot = lambda: self.displayPopupMenuOnGrip(pos, entityGripPoint.entity, gripPoint) + + del self.timerForGripMenu + self.timerForGripMenu = QTimer() + self.timerForGripMenu.setSingleShot(True) + self.timerForGripMenu.timeout.connect(shot) + self.timerForGripMenu.start(1000) # 1 sec tempo per il grip + return + else: + if changedPart == True: + self.dynamicCmdInput.show(True, self.canvas.mouseLastXY()) + else: + self.dynamicCmdInput.mouseMoveEvent(event.pos()) + + + # ============================================================================ + # canvasReleaseEvent + # ============================================================================ + def canvasReleaseEvent(self, event): + if event.button() == Qt.RightButton: + shortCutMenu = QadVariables.get(QadMsg.translate("Environment variables", "SHORTCUTMENU")) + if shortCutMenu == 0: + # equivale a premere INVIO + return self.plugIn.showEvaluateMsg(None) + else: + if self.entitySet.count() == 0: # nessun oggetto selezionato (modalità Default) + # 16 = Enables the display of a shortcut menu when the right button on the pointing device is held down long enough + if shortCutMenu & 16: + now = datetime.datetime.now() + value = QadVariables.get(QadMsg.translate("Environment variables", "SHORTCUTMENUDURATION")) + shortCutMenuDuration = datetime.timedelta(0, 0, 0, value) + # se supera il numero di millisecondi impostato da SHORTCUTMENUDURATION + if now - self.startDateTimeForRightClick > shortCutMenuDuration: + return self.displayPopupMenuOnQuiescentState(event.pos()) + else: + # se click veloce equivale a premere INVIO + return self.plugIn.showEvaluateMsg(None) + else: + # 1 = Enables Default mode shortcut menus + if shortCutMenu & 1: + return self.displayPopupMenuOnQuiescentState(event.pos()) + else: + # equivale a premere INVIO + return self.plugIn.showEvaluateMsg(None) + else: # ci sono degli oggetti selezionati + # 2 = Enables Edit mode shortcut menus + if shortCutMenu & 2: + return self.displayPopupMenuOnQuiescentState(event.pos()) + else: + # equivale a premere INVIO + return self.plugIn.showEvaluateMsg(None) + + + # ============================================================================ + # FINE - eventi per il mouse + # INIZIO - eventi per la tastiera + # ============================================================================ + + + # ============================================================================ + # keyPressEvent + # ============================================================================ + def keyPressEvent(self, e): + myEvent = e + + if self.plugIn.shortCutManagement(myEvent): # se è stata gestita una sequenza di tasti scorciatoia + return + + if myEvent.text() != "" and self.dynamicCmdInput.show(True, self.canvas.mouseLastXY(), self.dynamicCmdInput.getPrompt()) == True: + self.dynamicCmdInput.keyPressEvent(myEvent) + else: + self.plugIn.keyPressEvent(myEvent) + + + # ============================================================================ + # FINE - eventi per la tastiera + # INIZIO - eventi per la rotella + # ============================================================================ + + + # ============================================================================ + # wheelEvent + # ============================================================================ + def wheelEvent(self, event): + QgsMapTool.wheelEvent(self, event) + self.__csrRubberBand.moveEvent(self.toMapCoordinates(event.pos())) + + + # ============================================================================ + # FINE - eventi per la rotella + # ============================================================================ + + + # ============================================================================ + # activate + # ============================================================================ + def activate(self): + self.canvas.setToolTip("") + self.canvas.setCursor(self.cursor) + # posizione corrente del mouse + self.__csrRubberBand.moveEvent(self.toMapCoordinates(self.canvas.mouseLastXY())) + self.__csrRubberBand.show() + self.entitySet.initByCurrentQgsSelectedFeatures(qad_utils.getVisibleVectorLayers(self.canvas)) # Tutti i layer vettoriali visibili + self.refreshEntityGripPoints(self.entitySet) + + self.plugIn.QadCommands.continueCommandFromMapTool() + self.plugIn.disableShortcut() + + self.dynamicCmdInput.setPrevPart(None) + self.dynamicCmdInput.setNextPart(None) + self.dynamicCmdInput.show(True, self.canvas.mouseLastXY()) + + + # ============================================================================ + # deactivate + # ============================================================================ + def deactivate(self): + self.__csrRubberBand.hide() + self.timerForGripMenu.stop() + self.plugIn.enableShortcut() + + self.dynamicCmdInput.show(False) + + + # ============================================================================ + # isTransient + # ============================================================================ + def isTransient(self): + return False # questo tool non fa zoom o pan + + + # ============================================================================ + # isEditTool + # ============================================================================ + def isEditTool(self): + return False # questo tool non fa editing + + + # ============================================================================ + # displayPopupMenuOnQuiescentState + # ============================================================================ + def displayPopupMenuOnQuiescentState(self, pos): + popupMenu = QMenu(self.canvas) + history = self.plugIn.cmdsHistory + isLastCmdToInsert = True + isRecentMenuToInsert = True + + historyLen = len(history) + i = historyLen - 1 + cmdInputHistoryMax = QadVariables.get(QadMsg.translate("Environment variables", "CMDINPUTHISTORYMAX")) + while i >= 0 and (historyLen - i) <= cmdInputHistoryMax: + cmdName = history[i] + i = i - 1 + cmd = self.plugIn.QadCommands.getCommandObj(cmdName) + if cmd is not None: + if isLastCmdToInsert: + isLastCmdToInsert = False + msg = QadMsg.translate("Popup_menu_graph_window", "Repeat ") + cmd.getName() + icon = cmd.getIcon() + if icon is None: + lastCmdAction = QAction(msg, popupMenu) + else: + lastCmdAction = QAction(icon, msg, popupMenu) + cmd.connectQAction(lastCmdAction) + popupMenu.addAction(lastCmdAction) + else: + if isRecentMenuToInsert: + isRecentMenuToInsert = False + recentCmdsMenu = popupMenu.addMenu(QadMsg.translate("Popup_menu_graph_window", "Recent commands")) + + icon = cmd.getIcon() + if icon is None: + recentCmdAction = QAction(cmd.getName(), recentCmdsMenu) + else: + recentCmdAction = QAction(icon, cmd.getName(), recentCmdsMenu) + cmd.connectQAction(recentCmdAction) + recentCmdsMenu.addAction(recentCmdAction) + + + if isLastCmdToInsert == False: # menu non vuoto + popupMenu.addSeparator() + + # aggiungo comando "OPTIONS" + cmd = self.plugIn.QadCommands.getCommandObj(QadMsg.translate("Command_list", "OPTIONS")) + icon = cmd.getIcon() + if icon is None: + optionsCmdAction = QAction(cmd.getName(), popupMenu) + else: + optionsCmdAction = QAction(icon, cmd.getName(), popupMenu) + cmd.connectQAction(optionsCmdAction) + popupMenu.addAction(optionsCmdAction) + + popupMenu.popup(self.canvas.mapToGlobal(pos)) + + + # ============================================================================ + # runCmdFromPopupMenuOnGrip + # ============================================================================ + def runCmdFromPopupMenuOnGrip(self, virtualGripCommand, gripPoint): + # seleziona il grip + gripPoint.select() + # lancio il comando + self.plugIn.runCommand("QadVirtualGripCommandsClass", [virtualGripCommand, self.entitySetGripPoints, gripPoint.getPoint()]) + + + # ============================================================================ + # displayPopupMenuOnGrip + # ============================================================================ + def displayPopupMenuOnGrip(self, pos, entity, gripPoint): + if self.gripPopupMenu is not None: + self.gripPopupMenu.hide() + del self.gripPopupMenu + self.gripPopupMenu = None + + popupMenu = QadGripPopupMenu(self.canvas) + + found = False + + # verifico se l'entità appartiene ad uno stile di quotatura + if QadDimStyles.isDimEntity(entity): + pass + else: + qadGeom = entity.getQadGeom(gripPoint.atGeom, gripPoint.atSubGeom) + qadGeomType = qadGeom.whatIs() + if qadGeomType == "ARC": + qadGeom = entity.getQadGeom(gripPoint.atGeom, gripPoint.atSubGeom) + + # se punti finali + if gripPoint.isIntersecting(qadGeom.getStartPt()) or gripPoint.isIntersecting(qadGeom.getEndPt()): + found = True + msg = QadMsg.translate("Popup_menu_grip_window", "Stretch") + action = QAction(msg, popupMenu) + f = lambda : self.runCmdFromPopupMenuOnGrip(QadVirtualGripCommandsEnum.STRECTH, gripPoint) + action.triggered.connect(f) + popupMenu.addAction(action) + + msg = QadMsg.translate("Popup_menu_grip_window", "Lengthen") + action = QAction(msg, popupMenu) + f = lambda : self.runCmdFromPopupMenuOnGrip(QadVirtualGripCommandsEnum.LENGTHEN, gripPoint) + action.triggered.connect(f) + popupMenu.addAction(action) + # se punto medio + elif gripPoint.isIntersecting(qadGeom.getMiddlePt()): + found = True + msg = QadMsg.translate("Popup_menu_grip_window", "Stretch") + action = QAction(msg, popupMenu) + f = lambda : self.runCmdFromPopupMenuOnGrip(QadVirtualGripCommandsEnum.STRECTH, gripPoint) + action.triggered.connect(f) + popupMenu.addAction(action) + + msg = QadMsg.translate("Popup_menu_grip_window", "Radius") + action = QAction(msg, popupMenu) + f = lambda : self.runCmdFromPopupMenuOnGrip(QadVirtualGripCommandsEnum.CHANGE_RADIUS, gripPoint) + action.triggered.connect(f) + popupMenu.addAction(action) + + msg = QadMsg.translate("Popup_menu_grip_window", "Convert to line") + action = QAction(msg, popupMenu) + f = lambda : self.runCmdFromPopupMenuOnGrip(QadVirtualGripCommandsEnum.ARC_TO_LINE, gripPoint) + action.triggered.connect(f) + popupMenu.addAction(action) + + if qadGeomType == "LINE": + qadGeom = entity.getQadGeom(gripPoint.atGeom, gripPoint.atSubGeom) + + # se punti finali + if gripPoint.isIntersecting(qadGeom.getStartPt()) or gripPoint.isIntersecting(qadGeom.getEndPt()): + found = True + msg = QadMsg.translate("Popup_menu_grip_window", "Stretch") + action = QAction(msg, popupMenu) + f = lambda : self.runCmdFromPopupMenuOnGrip(QadVirtualGripCommandsEnum.STRECTH, gripPoint) + action.triggered.connect(f) + popupMenu.addAction(action) + + msg = QadMsg.translate("Popup_menu_grip_window", "Lengthen") + action = QAction(msg, popupMenu) + f = lambda : self.runCmdFromPopupMenuOnGrip(QadVirtualGripCommandsEnum.LENGTHEN, gripPoint) + action.triggered.connect(f) + popupMenu.addAction(action) + + elif qadGeomType == "POLYLINE": + isClosed = qadGeom.isClosed() + nVertex = 0 + found = False + while nVertex < qadGeom.qty(): + linearObject = qadGeom.getLinearObjectAt(nVertex) + + if gripPoint.isIntersecting(linearObject.getStartPt()): + found = True + msg = QadMsg.translate("Popup_menu_grip_window", "Stretch vertex") + action = QAction(msg, popupMenu) + f = lambda : self.runCmdFromPopupMenuOnGrip(QadVirtualGripCommandsEnum.STRECTH, gripPoint) + action.triggered.connect(f) + popupMenu.addAction(action) + + # punto iniziale + if isClosed == False and nVertex == 0: + msg = QadMsg.translate("Popup_menu_grip_window", "Lengthen") + action = QAction(msg, popupMenu) + f = lambda : self.runCmdFromPopupMenuOnGrip(QadVirtualGripCommandsEnum.LENGTHEN, gripPoint) + action.triggered.connect(f) + popupMenu.addAction(action) + + msg = QadMsg.translate("Popup_menu_grip_window", "Add vertex") + action = QAction(msg, popupMenu) + f = lambda : self.runCmdFromPopupMenuOnGrip(QadVirtualGripCommandsEnum.ADD_VERTEX, gripPoint) + action.triggered.connect(f) + popupMenu.addAction(action) + + msg = QadMsg.translate("Popup_menu_grip_window", "Add vertex before") + action = QAction(msg, popupMenu) + f = lambda : self.runCmdFromPopupMenuOnGrip(QadVirtualGripCommandsEnum.ADD_VERTEX_BEFORE, gripPoint) + action.triggered.connect(f) + popupMenu.addAction(action) + break + + # punto medio + if gripPoint.isIntersecting(linearObject.getMiddlePt()): + found = True + msg = QadMsg.translate("Popup_menu_grip_window", "Stretch") + action = QAction(msg, popupMenu) + f = lambda : self.runCmdFromPopupMenuOnGrip(QadVirtualGripCommandsEnum.STRECTH, gripPoint) + action.triggered.connect(f) + popupMenu.addAction(action) + + msg = QadMsg.translate("Popup_menu_grip_window", "Add vertex") + action = QAction(msg, popupMenu) + f = lambda : self.runCmdFromPopupMenuOnGrip(QadVirtualGripCommandsEnum.ADD_VERTEX, gripPoint) + action.triggered.connect(f) + popupMenu.addAction(action) + + msg = QadMsg.translate("Popup_menu_grip_window", "Add vertex before") + action = QAction(msg, popupMenu) + f = lambda : self.runCmdFromPopupMenuOnGrip(QadVirtualGripCommandsEnum.ADD_VERTEX_BEFORE, gripPoint) + action.triggered.connect(f) + popupMenu.addAction(action) + + gType = linearObject.whatIs() + if gType == "LINE": + msg = QadMsg.translate("Popup_menu_grip_window", "Convert to arc") + action = QAction(msg, popupMenu) + f = lambda : self.runCmdFromPopupMenuOnGrip(QadVirtualGripCommandsEnum.LINE_TO_ARC, gripPoint) + elif gType == "ARC": + msg = QadMsg.translate("Popup_menu_grip_window", "Convert to line") + action = QAction(msg, popupMenu) + f = lambda : self.runCmdFromPopupMenuOnGrip(QadVirtualGripCommandsEnum.ARC_TO_LINE, gripPoint) + action.triggered.connect(f) + popupMenu.addAction(action) + break + + nVertex = nVertex + 1 + + linearObject = qadGeom.getLinearObjectAt(-1) # ultima parte + if not found and isClosed == False: + # punto finale + if gripPoint.isIntersecting(linearObject.getEndPt()): + found = True + msg = QadMsg.translate("Popup_menu_grip_window", "Stretch vertex") + action = QAction(msg, popupMenu) + f = lambda : self.runCmdFromPopupMenuOnGrip(QadVirtualGripCommandsEnum.STRECTH, gripPoint) + action.triggered.connect(f) + popupMenu.addAction(action) + + msg = QadMsg.translate("Popup_menu_grip_window", "Lengthen") + action = QAction(msg, popupMenu) + f = lambda : self.runCmdFromPopupMenuOnGrip(QadVirtualGripCommandsEnum.LENGTHEN, gripPoint) + action.triggered.connect(f) + popupMenu.addAction(action) + + msg = QadMsg.translate("Popup_menu_grip_window", "Add vertex") + action = QAction(msg, popupMenu) + f = lambda : self.runCmdFromPopupMenuOnGrip(QadVirtualGripCommandsEnum.ADD_VERTEX, gripPoint) + action.triggered.connect(f) + popupMenu.addAction(action) + + msg = QadMsg.translate("Popup_menu_grip_window", "Add vertex before") + action = QAction(msg, popupMenu) + f = lambda : self.runCmdFromPopupMenuOnGrip(QadVirtualGripCommandsEnum.ADD_VERTEX_BEFORE, gripPoint) + action.triggered.connect(f) + popupMenu.addAction(action) + + if isClosed == False: # polyline + # ci devono essere almeno 2 parti + if qadGeom.qty() >= 2: + msg = QadMsg.translate("Popup_menu_grip_window", "Remove vertex") + action = QAction(msg, popupMenu) + f = lambda : self.runCmdFromPopupMenuOnGrip(QadVirtualGripCommandsEnum.REMOVE_VERTEX, gripPoint) + action.triggered.connect(f) + popupMenu.addAction(action) + else: # polygon + # ci devono essere almeno 4 parti + if qadGeom.qty() >= 4: + msg = QadMsg.translate("Popup_menu_grip_window", "Remove vertex") + action = QAction(msg, popupMenu) + f = lambda : self.runCmdFromPopupMenuOnGrip(QadVirtualGripCommandsEnum.REMOVE_VERTEX, gripPoint) + action.triggered.connect(f) + popupMenu.addAction(action) + + if found: # menu non vuoto + popupMenu.popup(self.canvas.mapToGlobal(pos)) + self.gripPopupMenu = popupMenu + + return None + + +# ============================================================================ +# QadGripPopupMenu +# ============================================================================ +class QadGripPopupMenu(QMenu): + def __init__(self, parent): + QMenu.__init__(self, parent) + self.offset = 0 + + + # ============================================================================ + # popup + # ============================================================================ + def popup(self, pos, action = None): + newPos = QPoint(pos.x() + self.offset, pos.y() + self.offset) + QMenu.popup(self, newPos, action) + +# def leaveEvent(self, event): +# if event.pos().x() < -1 * self.offset or event.pos().y() < -1 * self.offset: +# self.hide() + #self.hide() + + + # ============================================================================ + # mouseMoveEvent + # ============================================================================ + def mouseMoveEvent(self, event): + x = event.pos().x() + y = event.pos().y() + if x < -1 * self.offset or y < -1 * self.offset or \ + x > self.width() or y > self.height(): + self.hide() + else: + QMenu.mouseMoveEvent(self, event) + + +# ============================================================================ +# QadVirtualSelCommandClass +# Classe che gestisce il comando di selezione quando QAD è in stato di quiete +# ============================================================================ +class QadVirtualSelCommandClass(QadCommandClass): + + # ============================================================================ + # instantiateNewCmd + # ============================================================================ + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadVirtualSelCommandClass(self.plugIn) + + + # ============================================================================ + # getName + # ============================================================================ + def getName(self): + return "QadVirtualSelCommandClass" + + + # ============================================================================ + # __init__ + # ============================================================================ + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + self.SSGetClass = QadSSGetClass(plugIn) + self.SSGetClass.entitySet.set(plugIn.tool.entitySet) # da usare solo con QadMapTool + self.SSGetClass.exitAfterSelection = True + self.SSGetClass.step = 1 + + + # ============================================================================ + # __del__ + # ============================================================================ + def __del__(self): + QadCommandClass.__del__(self) + del self.SSGetClass + + + # ============================================================================ + # getPointMapTool + # ============================================================================ + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + return self.SSGetClass.getPointMapTool(drawMode) + + + # ============================================================================ + # run + # ============================================================================ + def run(self, msgMapTool = False, msg = None): + res = self.SSGetClass.run(msgMapTool, msg) + if res == True: + self.plugIn.tool.entitySet.set(self.SSGetClass.entitySet) # da usare solo con QadMapTool + self.plugIn.tool.entitySet.selectOnLayer() + return res + + +# =============================================================================== +# QadVirtualGripCommandsEnum class. +# =============================================================================== +class QadVirtualGripCommandsEnum(): + NONE = 0 + STRECTH = 1 + MOVE = 2 + ROTATE = 3 + SCALE = 4 + MIRROR = 5 + LENGTHEN = 6 + ADD_VERTEX = 7 + REMOVE_VERTEX = 8 + LINE_TO_ARC = 9 + ARC_TO_LINE = 10 + CHANGE_RADIUS = 11 + ADD_VERTEX_BEFORE = 12 + + +# ============================================================================ +# QadVirtualGripCommandsClass +# ============================================================================ +# Classe che gestisce i comando disponibili sui grip quando QAD è in stato di quiete +class QadVirtualGripCommandsClass(QadCommandClass): + + # ============================================================================ + # instantiateNewCmd + # ============================================================================ + def instantiateNewCmd(self): + """ istanzia un nuovo comando dello stesso tipo """ + return QadVirtualGripCommandsClass(self.plugIn) + + + # ============================================================================ + # getName + # ============================================================================ + def getName(self): + return "QadVirtualGripCommandsClass" + + + # ============================================================================ + # __init__ + # ============================================================================ + def __init__(self, plugIn): + QadCommandClass.__init__(self, plugIn) + self.commandNum = QadVirtualGripCommandsEnum.NONE + self.currentCommand = None + self.entitySetGripPoints = None + self.basePt = QgsPointXY() + + + # ============================================================================ + # __del__ + # ============================================================================ + def __del__(self): + QadCommandClass.__del__(self) + del self.currentCommand + + + # ============================================================================ + # getPointMapTool + # ============================================================================ + def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): + if self.currentCommand is not None: + return self.currentCommand.getPointMapTool(drawMode) + else: + return None + + + # ============================================================================ + # getCurrentContextualMenu + # ============================================================================ + def getCurrentContextualMenu(self): + if self.currentCommand is not None: + return self.currentCommand.getCurrentContextualMenu() + else: + return None + + + # ============================================================================ + # getCommand + # ============================================================================ + def getCommand(self): + if self.commandNum == QadVirtualGripCommandsEnum.STRECTH: + return QadGRIPSTRETCHCommandClass(self.plugIn) + elif self.commandNum == QadVirtualGripCommandsEnum.MOVE: + return QadGRIPMOVECommandClass(self.plugIn) + elif self.commandNum == QadVirtualGripCommandsEnum.ROTATE: + return QadGRIPROTATECommandClass(self.plugIn) + elif self.commandNum == QadVirtualGripCommandsEnum.SCALE: + return QadGRIPSCALECommandClass(self.plugIn) + elif self.commandNum == QadVirtualGripCommandsEnum.MIRROR: + return QadGRIPMIRRORCommandClass(self.plugIn) + elif self.commandNum == QadVirtualGripCommandsEnum.CHANGE_RADIUS: + return QadGRIPCHANGEARCRADIUSCommandClass(self.plugIn) + elif self.commandNum == QadVirtualGripCommandsEnum.LENGTHEN: + return QadGRIPLENGTHENCommandClass(self.plugIn) + elif self.commandNum == QadVirtualGripCommandsEnum.ADD_VERTEX: + cmd = QadGRIPINSERTREMOVEVERTEXCommandClass(self.plugIn) + cmd.setInsertVertexAfter_Mode() + return cmd + elif self.commandNum == QadVirtualGripCommandsEnum.ADD_VERTEX_BEFORE: + cmd = QadGRIPINSERTREMOVEVERTEXCommandClass(self.plugIn) + cmd.setInsertVertexBefore_Mode() + return cmd + elif self.commandNum == QadVirtualGripCommandsEnum.REMOVE_VERTEX: + cmd = QadGRIPINSERTREMOVEVERTEXCommandClass(self.plugIn) + cmd.setRemoveVertex_mode() + return cmd + elif self.commandNum == QadVirtualGripCommandsEnum.LINE_TO_ARC: + cmd = QadGRIPARCLINECONVERTCommandClass(self.plugIn) + cmd.setLineToArcConvert_Mode() + return cmd + elif self.commandNum == QadVirtualGripCommandsEnum.ARC_TO_LINE: + cmd = QadGRIPARCLINECONVERTCommandClass(self.plugIn) + cmd.setArcToLineConvert_Mode() + return cmd + + return None + + + # ============================================================================ + # initStartCommand + # ============================================================================ + def initStartCommand(self, commandNum): + if self.currentCommand is not None: + del self.currentCommand + self.currentCommand = None + + self.commandNum = commandNum + self.currentCommand = self.getCommand() + + if self.currentCommand is not None: + self.currentCommand.basePt.set(self.basePt.x(), self.basePt.y()) + self.currentCommand.setSelectedEntityGripPoints(self.entitySetGripPoints) + return True + else: + return False + + + # ============================================================================ + # initNextCommand + # ============================================================================ + def initNextCommand(self): + if self.currentCommand is not None: + del self.currentCommand + self.currentCommand = None + + if self.commandNum == QadVirtualGripCommandsEnum.STRECTH or \ + self.commandNum == QadVirtualGripCommandsEnum.LENGTHEN or \ + self.commandNum == QadVirtualGripCommandsEnum.ADD_VERTEX or \ + self.commandNum == QadVirtualGripCommandsEnum.ADD_VERTEX_BEFORE or \ + self.commandNum == QadVirtualGripCommandsEnum.REMOVE_VERTEX or \ + self.commandNum == QadVirtualGripCommandsEnum.LINE_TO_ARC or \ + self.commandNum == QadVirtualGripCommandsEnum.ARC_TO_LINE or \ + self.commandNum == QadVirtualGripCommandsEnum.CHANGE_RADIUS: + self.commandNum = QadVirtualGripCommandsEnum.MOVE + elif self.commandNum == QadVirtualGripCommandsEnum.MOVE: + self.commandNum = QadVirtualGripCommandsEnum.ROTATE + elif self.commandNum == QadVirtualGripCommandsEnum.ROTATE: + self.commandNum = QadVirtualGripCommandsEnum.SCALE + elif self.commandNum == QadVirtualGripCommandsEnum.SCALE: + self.commandNum = QadVirtualGripCommandsEnum.MIRROR + elif self.commandNum == QadVirtualGripCommandsEnum.MIRROR: + self.commandNum = QadVirtualGripCommandsEnum.MOVE + + self.currentCommand = self.getCommand() + + if self.currentCommand is not None: + self.currentCommand.basePt.set(self.basePt.x(), self.basePt.y()) + self.currentCommand.setSelectedEntityGripPoints(self.entitySetGripPoints) + return True + else: + return False + + + # ============================================================================ + # run + # ============================================================================ + def run(self, msgMapTool = False, msg = None): + if self.currentCommand is None: + return True + res = self.currentCommand.run(msgMapTool, msg) + if res == True: + if self.currentCommand.skipToNextGripCommand == True: + if self.initNextCommand(): # attivo comando successivo + return self.currentCommand.run(msgMapTool, msg) + else: + # ridisegno i grip point nelle nuove posizioni resettando quelli selezionati + self.plugIn.tool.clearEntityGripPoints() + self.plugIn.tool.refreshEntityGripPoints() + + return res + diff --git a/qad_mbuffer_fun.py b/qad_mbuffer_fun.py new file mode 100644 index 00000000..ab4dbc37 --- /dev/null +++ b/qad_mbuffer_fun.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + funzioni per stirare oggetti grafici + + ------------------- + begin : 2013-11-11 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.PyQt.QtCore import * +from qgis.PyQt.QtGui import * +from qgis.core import * + + +from . import qad_utils +from .qad_msg import QadMsg +from .qad_variables import QadVariables +from .qad_multi_geom import * + + +# =============================================================================== +# buffer +# =============================================================================== +def buffer(qadGeom, distance): + """ + Returns a buffer region around this geometry having the given distance. + """ + g = qadGeom.asGeom() + nSegments = QadVariables.get(QadMsg.translate("Environment variables", "ARCMINSEGMENTQTY"), 12) + bufferedGeom = g.buffer(distance, nSegments) + if bufferedGeom.isEmpty(): return None + return fromQgsGeomToQadGeom(bufferedGeom) + \ No newline at end of file diff --git a/qad_mbuffer_maptool.py b/qad_mbuffer_maptool.py deleted file mode 100644 index d7dbffbd..00000000 --- a/qad_mbuffer_maptool.py +++ /dev/null @@ -1,130 +0,0 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - classe per gestire il map tool in ambito del comando mbuffer - - ------------------- - begin : 2013-09-19 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -from qgis.gui import * -import math - - -import qad_utils -from qad_snapper import * -from qad_snappointsdisplaymanager import * -from qad_variables import * -from qad_getpoint import * -from qad_rubberband import QadRubberBand - - -#=============================================================================== -# Qad_mbuffer_maptool_ModeEnum class. -#=============================================================================== -class Qad_mbuffer_maptool_ModeEnum(): - # noto niente si richiede il primo punto - NONE_KNOWN_ASK_FOR_FIRST_PT = 1 - # noto il primo punto si richiede la larghezza del buffer - FIRST_PT_ASK_FOR_BUFFER_WIDTH = 2 - -#=============================================================================== -# Qad_mbuffer_maptool class -#=============================================================================== -class Qad_mbuffer_maptool(QadGetPoint): - - def __init__(self, plugIn): - QadGetPoint.__init__(self, plugIn) - - self.startPtForBufferWidth = None - # vedi il numero minimo di punti affinché venga riconosciuto un arco o un cerchio - # nei files qad_arc.py e qad_circle.py - self.segments = 12 - self.entitySet = QadEntitySet() - self.geomType = QGis.Polygon - self.__rubberBand = QadRubberBand(self.canvas, True) - - def setRubberBandColor(self, rubberBandBorderColor, rubberBandFillColor): - if rubberBandBorderColor is not None: - self.__rubberBand.setBorderColor(rubberBandBorderColor) - if rubberBandFillColor is not None: - self.__rubberBand.setFillColor(rubberBandFillColor) - - def hidePointMapToolMarkers(self): - QadGetPoint.hidePointMapToolMarkers(self) - self.__rubberBand.hide() - - def showPointMapToolMarkers(self): - QadGetPoint.showPointMapToolMarkers(self) - self.__rubberBand.show() - - def clear(self): - QadGetPoint.clear(self) - self.__rubberBand.reset() - self.mode = None - - - def canvasMoveEvent(self, event): - QadGetPoint.canvasMoveEvent(self, event) - - self.__rubberBand.reset() - - # noto il primo punto si richiede la larghezza del buffer - if self.mode == Qad_mbuffer_maptool_ModeEnum.FIRST_PT_ASK_FOR_BUFFER_WIDTH: - width = qad_utils.getDistance(self.startPtForBufferWidth, self.tmpPoint) - tolerance = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) - - for layerEntitySet in self.entitySet.layerEntitySetList: - layer = layerEntitySet.layer - geoms = layerEntitySet.getGeometryCollection() - - for geom in geoms: - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - newGeom = self.layerToMapCoordinates(layer, geom) - bufferGeom = qad_utils.ApproxCurvesOnGeom(newGeom.buffer(width, self.segments), \ - self.segments, self.segments, \ - tolerance) - if bufferGeom: - # trasformo la geometria nel crs del layer - self.__rubberBand.addGeometry(self.mapToLayerCoordinates(layer, bufferGeom), layer) - - - def activate(self): - QadGetPoint.activate(self) - self.__rubberBand.show() - - def deactivate(self): - try: # necessario perché se si chiude QGIS parte questo evento nonostante non ci sia più l'oggetto maptool ! - QadGetPoint.deactivate(self) - self.__rubberBand.hide() - except: - pass - - def setMode(self, mode): - self.mode = mode - # noto niente si richiede il primo punto - if self.mode == Qad_mbuffer_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_FIRST_PT: - self.setDrawMode(QadGetPointDrawModeEnum.NONE) - # noto il primo punto si richiede la larghezza del buffer - elif self.mode == Qad_mbuffer_maptool_ModeEnum.FIRST_PT_ASK_FOR_BUFFER_WIDTH: - self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.setStartPoint(self.startPtForBufferWidth) diff --git a/qad_mirror_cmd.py b/qad_mirror_cmd.py deleted file mode 100644 index 5611e52e..00000000 --- a/qad_mirror_cmd.py +++ /dev/null @@ -1,601 +0,0 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - comando MIRROR per spostare oggetti - - ------------------- - begin : 2013-12-11 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -from qad_mirror_maptool import * -from qad_generic_cmd import QadCommandClass -from qad_msg import QadMsg -from qad_getpoint import * -from qad_textwindow import * -from qad_ssget_cmd import QadSSGetClass -from qad_entity import * -import qad_utils -import qad_layer -import qad_label - - -# Classe che gestisce il comando MIRROR -class QadMIRRORCommandClass(QadCommandClass): - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadMIRRORCommandClass(self.plugIn) - - def getName(self): - return QadMsg.translate("Command_list", "MIRROR") - - def getEnglishName(self): - return "MIRROR" - - def connectQAction(self, action): - QObject.connect(action, SIGNAL("triggered()"), self.plugIn.runMIRRORCommand) - - def getIcon(self): - return QIcon(":/plugins/qad/icons/mirror.png") - - def getNote(self): - # impostare le note esplicative del comando - return QadMsg.translate("Command_MIRROR", "Creates a mirrored copy of selected objects.") - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - self.SSGetClass = QadSSGetClass(plugIn) - self.SSGetClass.onlyEditableLayers = True - self.entitySet = QadEntitySet() - self.firstMirrorPt = QgsPoint() - self.secondMirrorPt = QgsPoint() - self.copyFeatures = True - - def __del__(self): - QadCommandClass.__del__(self) - del self.SSGetClass - - def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): - if self.step == 0: # quando si é in fase di selezione entità - return self.SSGetClass.getPointMapTool() - else: - if (self.plugIn is not None): - if self.PointMapTool is None: - self.PointMapTool = Qad_mirror_maptool(self.plugIn) - return self.PointMapTool - else: - return None - - - #============================================================================ - # scale - #============================================================================ - def mirror(self, f, pt1, pt2, rotFldName, layerEntitySet, entitySet, dimEntity): - - if dimEntity is None: - # scalo la feature e la rimuovo da entitySet (é la prima) - f.setGeometry(qad_utils.mirrorQgsGeometry(f.geometry(), pt1, pt2)) - if len(rotFldName) > 0: - rotValue = f.attribute(rotFldName) - rotValue = 0 if rotValue is None else qad_utils.toRadians(rotValue) # la rotazione é in gradi nel campo della feature - ptDummy = qad_utils.getPolarPointByPtAngle(pt1, rotValue, 1) - mirrorAngle = qad_utils.getAngleBy2Pts(pt1, pt2) - ptDummy = qad_utils.mirrorPoint(ptDummy, pt1, mirrorAngle) - rotValue = qad_utils.getAngleBy2Pts(pt1, ptDummy) - f.setAttribute(rotFldName, qad_utils.toDegrees(qad_utils.normalizeAngle(rotValue))) - - if self.copyFeatures == False: - # plugIn, layer, feature, refresh, check_validity - if qad_layer.updateFeatureToLayer(self.plugIn, layerEntitySet.layer, f, False, False) == False: - self.plugIn.destroyEditCommand() - return False - else: - # plugIn, layer, features, coordTransform, refresh, check_validity - if qad_layer.addFeatureToLayer(self.plugIn, layerEntitySet.layer, f, None, False, False) == False: - self.plugIn.destroyEditCommand() - return False - - del layerEntitySet.featureIds[0] - else: - # scalo la quota e la rimuovo da entitySet - mirrorAngle = qad_utils.getAngleBy2Pts(pt1, pt2) - dimEntitySet = dimEntity.getEntitySet() - if self.copyFeatures == False: - if dimEntity.deleteToLayers(self.plugIn) == False: - return False - dimEntity.mirror(self.plugIn, pt1, mirrorAngle) - if dimEntity.addToLayers(self.plugIn) == False: - return False - entitySet.subtract(dimEntitySet) - - - #============================================================================ - # mirrorGeoms - #============================================================================ - def mirrorGeoms(self): - # copio entitySet - entitySet = QadEntitySet(self.entitySet) - - self.plugIn.beginEditCommand("Feature mirrored", self.entitySet.getLayerList()) - - for layerEntitySet in entitySet.layerEntitySetList: - layer = layerEntitySet.layer - - transformedBasePt = self.mapToLayerCoordinates(layer, self.firstMirrorPt) - transformedNewPt = self.mapToLayerCoordinates(layer, self.secondMirrorPt) - - rotFldName = "" - if qad_layer.isTextLayer(layer): - # se la rotazione dipende da un solo campo - rotFldNames = qad_label.get_labelRotationFieldNames(layer) - if len(rotFldNames) == 1 and len(rotFldNames[0]) > 0: - rotFldName = rotFldNames[0] - elif qad_layer.isSymbolLayer(layer): - rotFldName = qad_layer.get_symbolRotationFieldName(layer) - - while len(layerEntitySet.featureIds) > 0: - featureId = layerEntitySet.featureIds[0] - f = layerEntitySet.getFeature(featureId) - - entity = QadEntity() - entity.set(layer, featureId) - # verifico se l'entità appartiene ad uno stile di quotatura - dimStyle, dimId = QadDimStyles.getDimIdByEntity(entity) - - if dimStyle is not None: - dimEntity = QadDimEntity() - if dimEntity.initByDimId(dimStyle, dimId) == False: - dimEntity = None - else: - dimEntity = None - - if self.mirror(f, transformedBasePt, transformedNewPt, rotFldName, layerEntitySet, entitySet, dimEntity) == False: - self.plugIn.destroyEditCommand() - return - - self.plugIn.endEditCommand() - - - def run(self, msgMapTool = False, msg = None): - if self.plugIn.canvas.mapRenderer().destinationCrs().geographicFlag(): - self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) - return True # fine comando - - #========================================================================= - # RICHIESTA SELEZIONE OGGETTI - if self.step == 0: # inizio del comando - if self.SSGetClass.run(msgMapTool, msg) == True: - # selezione terminata - self.step = 1 - self.getPointMapTool().refreshSnapType() # aggiorno lo snapType che può essere variato dal maptool di selezione entità - return self.run(msgMapTool, msg) - - #========================================================================= - # SPECCHIA OGGETTI - elif self.step == 1: - self.entitySet.set(self.SSGetClass.entitySet) - - if self.entitySet.count() == 0: - return True # fine comando - - # imposto il map tool - self.getPointMapTool().entitySet.set(self.entitySet) - self.getPointMapTool().setMode(Qad_mirror_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_FIRST_PT) - - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_MIRROR", "Specify first point of mirror line: ")) - self.step = 2 - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA PUNTO BASE (da step = 1) - elif self.step == 2: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_MIRROR", "Specify first point of mirror line: ")) - return False - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - self.firstMirrorPt.set(value.x(), value.y()) - - # imposto il map tool - self.getPointMapTool().firstMirrorPt = self.firstMirrorPt - self.getPointMapTool().setMode(Qad_mirror_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT) - - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_MIRROR", "Specify second point of mirror line: ")) - self.step = 3 - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA SECONDO PUNTO PER SPECCHIO (da step = 2) - elif self.step == 3: # dopo aver atteso un punto si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_MIRROR", "Specify second point of mirror line: ")) - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if qad_utils.ptNear(self.firstMirrorPt, value): - self.showMsg(QadMsg.translate("Command_MIRROR", "\nThe points must be different.")) - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_MIRROR", "Specify second point of mirror line: ")) - return False - - self.secondMirrorPt.set(value.x(), value.y()) - - keyWords = QadMsg.translate("QAD", "Yes") + "/" + \ - QadMsg.translate("QAD", "No") - if self.copyFeatures == False: - default = QadMsg.translate("QAD", "Yes") - else: - default = QadMsg.translate("QAD", "No") - prompt = QadMsg.translate("Command_MIRROR", "Erase source objects ? [{0}] <{1}>: ").format(keyWords, default) - - englishKeyWords = "Yes" + "/" + "No" - keyWords += "_" + englishKeyWords - # si appresta ad attendere enter o una parola chiave - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(prompt, \ - QadInputTypeEnum.KEYWORDS, \ - default, \ - keyWords, QadInputModeEnum.NONE) - self.getPointMapTool().setMode(Qad_mirror_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_FIRST_PT) - self.step = 4 - - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DI CANCELLAZIONE OGGETTO SORGENTE (da step = 3) - elif self.step == 4: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - value = QadMsg.translate("QAD", "No") - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - else: # il valore arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("QAD", "Yes") or value == "Yes": - self.copyFeatures = False - elif value == QadMsg.translate("QAD", "No") or value == "No": - self.copyFeatures = True - - self.mirrorGeoms() - return True # fine comando - - return False - - - - -# Classe che gestisce il comando MIRROR per i grip -class QadGRIPMIRRORCommandClass(QadCommandClass): - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadMIRRORCommandClass(self.plugIn) - - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - self.entitySet = QadEntitySet() - self.basePt = QgsPoint() - self.secondMirrorPt = QgsPoint() - self.skipToNextGripCommand = False - self.copyEntities = False - self.nOperationsToUndo = 0 - - def __del__(self): - QadCommandClass.__del__(self) - - def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): - if (self.plugIn is not None): - if self.PointMapTool is None: - self.PointMapTool = Qad_mirror_maptool(self.plugIn) - return self.PointMapTool - else: - return None - - - #============================================================================ - # setSelectedEntityGripPoints - #============================================================================ - def setSelectedEntityGripPoints(self, entitySetGripPoints): - # lista delle entityGripPoint con dei grip point selezionati - self.entitySet.clear() - - for entityGripPoints in entitySetGripPoints.entityGripPoints: - self.entitySet.addEntity(entityGripPoints.entity) - self.getPointMapTool().entitySet.set(self.entitySet) - - - #============================================================================ - # mirror - #============================================================================ - def mirror(self, entity, pt1, pt2, rotFldName): - # entity = entità da specchiare - # pt1 e pt2 = linea di simmetria - # rotFldName = campo della tabella che memorizza la rotazione - # verifico se l'entità appartiene ad uno stile di quotatura - if entity.whatIs() == "ENTITY": - f = entity.getFeature() - # specchio l'entità - f.setGeometry(qad_utils.mirrorQgsGeometry(entity.getGeometry(), pt1, pt2)) - if len(rotFldName) > 0: - rotValue = f.attribute(rotFldName) - rotValue = 0 if rotValue is None else qad_utils.toRadians(rotValue) # la rotazione é in gradi nel campo della feature - ptDummy = qad_utils.getPolarPointByPtAngle(pt1, rotValue, 1) - mirrorAngle = qad_utils.getAngleBy2Pts(pt1, pt2) - ptDummy = qad_utils.mirrorPoint(ptDummy, pt1, mirrorAngle) - rotValue = qad_utils.getAngleBy2Pts(pt1, ptDummy) - f.setAttribute(rotFldName, qad_utils.toDegrees(qad_utils.normalizeAngle(rotValue))) - - if self.copyEntities == False: - # plugIn, layer, feature, refresh, check_validity - if qad_layer.updateFeatureToLayer(self.plugIn, entity.layer, f, False, False) == False: - return False - else: - # plugIn, layer, features, coordTransform, refresh, check_validity - if qad_layer.addFeatureToLayer(self.plugIn, entity.layer, f, None, False, False) == False: - return False - elif entity.whatIs() == "DIMENTITY": - mirrorAngle = qad_utils.getAngleBy2Pts(pt1, pt2) - # specchio la quota - if self.copyEntities == False: - if entity.deleteToLayers(self.plugIn) == False: - return False - entity.mirror(self.plugIn, pt1, mirrorAngle) - if entity.addToLayers(self.plugIn) == False: - return False - - return True - - - #============================================================================ - # mirrorGeoms - #============================================================================ - def mirrorGeoms(self): - entity = QadEntity() - self.plugIn.beginEditCommand("Feature mirrored", self.entitySet.getLayerList()) - - dimElaboratedList = [] # lista delle quotature già elaborate - - for layerEntitySet in self.entitySet.layerEntitySetList: - layer = layerEntitySet.layer - - transformedBasePt = self.mapToLayerCoordinates(layer, self.basePt) - transformedNewPt = self.mapToLayerCoordinates(layer, self.secondMirrorPt) - - rotFldName = "" - if qad_layer.isTextLayer(layer): - # se la rotazione dipende da un solo campo - rotFldNames = qad_label.get_labelRotationFieldNames(layer) - if len(rotFldNames) == 1 and len(rotFldNames[0]) > 0: - rotFldName = rotFldNames[0] - elif qad_layer.isSymbolLayer(layer): - rotFldName = qad_layer.get_symbolRotationFieldName(layer) - - for featureId in layerEntitySet.featureIds: - entity.set(layer, featureId) - - # verifico se l'entità appartiene ad uno stile di quotatura - dimEntity = QadDimStyles.getDimEntity(entity) - if dimEntity is None: - if self.mirror(entity, transformedBasePt, transformedNewPt, rotFldName) == False: - self.plugIn.destroyEditCommand() - return - else: - found = False - for dimElaborated in dimElaboratedList: - if dimElaborated == dimEntity: - found = True - - if found == False: # quota non ancora elaborata - dimElaboratedList.append(dimEntity) - if self.mirror(dimEntity, transformedBasePt, transformedNewPt, rotFldName) == False: - self.plugIn.destroyEditCommand() - return - - self.plugIn.endEditCommand() - self.nOperationsToUndo = self.nOperationsToUndo + 1 - - - #============================================================================ - # waitForMirrorPoint - #============================================================================ - def waitForMirrorPoint(self): - self.step = 1 - self.plugIn.setLastPoint(self.basePt) - # imposto il map tool - self.getPointMapTool().firstMirrorPt = self.basePt - self.getPointMapTool().setMode(Qad_mirror_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT) - - keyWords = QadMsg.translate("Command_GRIPMIRROR", "Base point") + "/" + \ - QadMsg.translate("Command_GRIPMIRROR", "Copy") + "/" + \ - QadMsg.translate("Command_GRIPMIRROR", "Undo") + "/" + \ - QadMsg.translate("Command_GRIPMIRROR", "eXit") - - prompt = QadMsg.translate("Command_GRIPMIRROR", "Specify second point or [{0}]: ").format(keyWords) - - englishKeyWords = "Base point" + "/" + "Copy" + "/" + "Undo" + "/" + "eXit" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto, un numero reale o enter o una parola chiave - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(prompt, QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - None, \ - keyWords, QadInputModeEnum.NONE) - - - #============================================================================ - # waitForBasePt - #============================================================================ - def waitForBasePt(self): - self.step = 2 - # imposto il map tool - self.getPointMapTool().setMode(Qad_mirror_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_FIRST_PT) - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_GRIPROTATE", "Specify base point: ")) - - - def run(self, msgMapTool = False, msg = None): - if self.plugIn.canvas.mapRenderer().destinationCrs().geographicFlag(): - self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) - return True # fine comando - - #========================================================================= - # RICHIESTA SELEZIONE OGGETTI - if self.step == 0: # inizio del comando - if self.entitySet.isEmpty(): # non ci sono oggetti da ruotare - return True - self.showMsg(QadMsg.translate("Command_GRIPMIRROR", "\n** MIRROR **\n")) - # si appresta ad attendere il secondo punto di specchio - self.waitForMirrorPoint() - - #========================================================================= - # RISPOSTA ALLA RICHIESTA SECONDO PUNTO PER SPECCHIO - elif self.step == 1: # dopo aver atteso un punto si riavvia il comando - ctrlKey = False - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - value = None - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - ctrlKey = self.getPointMapTool().ctrlKey - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("Command_GRIPMIRROR", "Base point") or value == "Base point": - # si appresta ad attendere il punto base - self.waitForBasePt() - elif value == QadMsg.translate("Command_GRIPMIRROR", "Copy") or value == "Copy": - # Copia entità lasciando inalterate le originali - self.copyEntities = True - # si appresta ad attendere il secondo punto di specchio - self.waitForMirrorPoint() - elif value == QadMsg.translate("Command_GRIPMIRROR", "Undo") or value == "Undo": - if self.nOperationsToUndo > 0: - self.nOperationsToUndo = self.nOperationsToUndo - 1 - self.plugIn.undoEditCommand() - else: - self.showMsg(QadMsg.translate("QAD", "\nThe command has been canceled.")) - # si appresta ad attendere il secondo punto di specchio - self.waitForMirrorPoint() - elif value == QadMsg.translate("Command_GRIPMIRROR", "eXit") or value == "eXit": - return True # fine comando - elif type(value) == QgsPoint: # se é stato inserito il secondo punto - if qad_utils.ptNear(self.basePt, value): - self.showMsg(QadMsg.translate("Command_GRIPMIRROR", "\nThe points must be different.")) - # si appresta ad attendere il secondo punto di specchio - self.waitForMirrorPoint() - return False - - self.secondMirrorPt.set(value.x(), value.y()) - - if ctrlKey: - self.copyEntities = True - - self.mirrorGeoms() - - if self.copyEntities == False: - return True - - # si appresta ad attendere il secondo punto di specchio - self.waitForMirrorPoint() - - else: - if self.copyEntities == False: - self.skipToNextGripCommand = True - return True # fine comando - - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA PUNTO BASE (da step = 1) - elif self.step == 2: # dopo aver atteso un punto - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - pass # opzione di default "spostamento" - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == QgsPoint: # se é stato inserito il punto base - self.basePt.set(value.x(), value.y()) - - # si appresta ad attendere il secondo punto di specchio - self.waitForMirrorPoint() - - return False diff --git a/qad_mirror_maptool.py b/qad_mirror_maptool.py deleted file mode 100644 index a44c16e7..00000000 --- a/qad_mirror_maptool.py +++ /dev/null @@ -1,145 +0,0 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - classe per gestire il map tool in ambito del comando mirror - - ------------------- - begin : 2013-12-11 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -from qgis.gui import * -import math - - -import qad_utils -from qad_snapper import * -from qad_snappointsdisplaymanager import * -from qad_variables import * -from qad_getpoint import * -from qad_dim import * -from qad_highlight import QadHighlight - - -#=============================================================================== -# Qad_copy_maptool_ModeEnum class. -#=============================================================================== -class Qad_mirror_maptool_ModeEnum(): - # noto niente si richiede il primo punto della linea speculare - NONE_KNOWN_ASK_FOR_FIRST_PT = 1 - # noto il primo punto si richiede il secondo punto della linea speculare - FIRST_PT_KNOWN_ASK_FOR_SECOND_PT = 2 - -#=============================================================================== -# Qad_mirror_maptool class -#=============================================================================== -class Qad_mirror_maptool(QadGetPoint): - - def __init__(self, plugIn): - QadGetPoint.__init__(self, plugIn) - - self.firstMirrorPt = None - self.entitySet = QadEntitySet() - self.__highlight = QadHighlight(self.canvas) - - def hidePointMapToolMarkers(self): - QadGetPoint.hidePointMapToolMarkers(self) - self.__highlight.hide() - - def showPointMapToolMarkers(self): - QadGetPoint.showPointMapToolMarkers(self) - self.__highlight.show() - - def clear(self): - QadGetPoint.clear(self) - self.__highlight.reset() - self.mode = None - - - #============================================================================ - # mirror - #============================================================================ - def mirror(self, f, pt1, pt2, layerEntitySet, entitySet): - # verifico se la feature appartiene ad una quotatura - dimEntity = QadDimStyles.getDimEntity(layerEntitySet.layer, f.id()) - - if dimEntity is None: - # specchio la feature e la rimuovo da entitySet (é la prima) - f.setGeometry(qad_utils.mirrorQgsGeometry(f.geometry(), pt1, pt2)) - self.__highlight.addGeometry(f.geometry(), layerEntitySet.layer) - del layerEntitySet.featureIds[0] - else: - # specchio la quota e la rimuovo da entitySet - dimEntitySet = dimEntity.getEntitySet() - dimEntity.mirror(self.plugIn, pt1, qad_utils.getAngleBy2Pts(pt1, pt2)) - self.__highlight.addGeometry(dimEntity.textualFeature.geometry(), dimEntity.getTextualLayer()) - self.__highlight.addGeometries(dimEntity.getLinearGeometryCollection(), dimEntity.getLinearLayer()) - self.__highlight.addGeometries(dimEntity.getSymbolGeometryCollection(), dimEntity.getSymbolLayer()) - entitySet.subtract(dimEntitySet) - - - def setMirroredGeometries(self, newPt): - self.__highlight.reset() - - # copio entitySet - entitySet = QadEntitySet(self.entitySet) - - for layerEntitySet in entitySet.layerEntitySetList: - layer = layerEntitySet.layer - - transformedFirstMirrorPt = self.canvas.mapRenderer().mapToLayerCoordinates(layer, self.firstMirrorPt) - transformedNewPtMirrorPt = self.canvas.mapRenderer().mapToLayerCoordinates(layer, newPt) - - while len(layerEntitySet.featureIds) > 0: - featureId = layerEntitySet.featureIds[0] - f = layerEntitySet.getFeature(featureId) - self.mirror(f, transformedFirstMirrorPt, transformedNewPtMirrorPt, layerEntitySet, entitySet) - - - def canvasMoveEvent(self, event): - QadGetPoint.canvasMoveEvent(self, event) - - # noto il primo punto si richiede il secondo punto della linea speculare - if self.mode == Qad_mirror_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT: - self.setMirroredGeometries(self.tmpPoint) - - - def activate(self): - QadGetPoint.activate(self) - self.__highlight.show() - - def deactivate(self): - try: # necessario perché se si chiude QGIS parte questo evento nonostante non ci sia più l'oggetto maptool ! - QadGetPoint.deactivate(self) - self.__highlight.hide() - except: - pass - - def setMode(self, mode): - self.mode = mode - # noto niente si richiede il primo punto della linea speculare - if self.mode == Qad_mirror_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_FIRST_PT: - self.setDrawMode(QadGetPointDrawModeEnum.NONE) - self.__highlight.reset() - # noto il primo punto si richiede il secondo punto della linea speculare - elif self.mode == Qad_mirror_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT: - self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.setStartPoint(self.firstMirrorPt) \ No newline at end of file diff --git a/qad_move_cmd.py b/qad_move_cmd.py deleted file mode 100644 index 53483fde..00000000 --- a/qad_move_cmd.py +++ /dev/null @@ -1,546 +0,0 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - comando MOVE per spostare oggetti - - ------------------- - begin : 2013-09-27 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -from qad_move_maptool import * -from qad_generic_cmd import QadCommandClass -from qad_msg import QadMsg -from qad_getpoint import * -from qad_textwindow import * -from qad_ssget_cmd import QadSSGetClass -from qad_entity import * -import qad_utils -import qad_layer -from qad_dim import * - - -# Classe che gestisce il comando MOVE -class QadMOVECommandClass(QadCommandClass): - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadMOVECommandClass(self.plugIn) - - def getName(self): - return QadMsg.translate("Command_list", "MOVE") - - def getEnglishName(self): - return "MOVE" - - def connectQAction(self, action): - QObject.connect(action, SIGNAL("triggered()"), self.plugIn.runMOVECommand) - - def getIcon(self): - return QIcon(":/plugins/qad/icons/move.png") - - def getNote(self): - # impostare le note esplicative del comando - return QadMsg.translate("Command_MOVE", "Moves the selected objects.") - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - self.SSGetClass = QadSSGetClass(plugIn) - self.SSGetClass.onlyEditableLayers = True - self.entitySet = QadEntitySet() - self.basePt = QgsPoint() - - def __del__(self): - QadCommandClass.__del__(self) - del self.SSGetClass - - def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): - if self.step == 0: # quando si é in fase di selezione entità - return self.SSGetClass.getPointMapTool() - else: - if (self.plugIn is not None): - if self.PointMapTool is None: - self.PointMapTool = Qad_move_maptool(self.plugIn) - return self.PointMapTool - else: - return None - - - #============================================================================ - # move - #============================================================================ - def move(self, f, offSetX, offSetY, layerEntitySet, entitySet): - # verifico se l'entità appartiene ad uno stile di quotatura - dimEntity = QadDimStyles.getDimEntity(layerEntitySet.layer, f.id()) - - if dimEntity is None: - # sposto la feature e la rimuovo da entitySet (é la prima) - f.setGeometry(qad_utils.moveQgsGeometry(f.geometry(), offSetX, offSetY)) - # plugIn, layer, feature, refresh, check_validity - if qad_layer.updateFeatureToLayer(self.plugIn, layerEntitySet.layer, f, False, False) == False: - return False - del layerEntitySet.featureIds[0] - else: - # sposto la quota e la rimuovo da entitySet - dimEntitySet = dimEntity.getEntitySet() - if dimEntity.deleteToLayers(self.plugIn) == False: - return False - if dimEntity.move(offSetX, offSetY) == False: - return False - if dimEntity.addToLayers(self.plugIn) == False: - return False - entitySet.subtract(dimEntitySet) - - return True - - - #============================================================================ - # moveGeoms - #============================================================================ - def moveGeoms(self, newPt): - # copio entitySet - entitySet = QadEntitySet(self.entitySet) - - self.plugIn.beginEditCommand("Feature moved", entitySet.getLayerList()) - - for layerEntitySet in entitySet.layerEntitySetList: - transformedBasePt = self.mapToLayerCoordinates(layerEntitySet.layer, self.basePt) - transformedNewPt = self.mapToLayerCoordinates(layerEntitySet.layer, newPt) - offSetX = transformedNewPt.x() - transformedBasePt.x() - offSetY = transformedNewPt.y() - transformedBasePt.y() - - while len(layerEntitySet.featureIds) > 0: - featureId = layerEntitySet.featureIds[0] - f = layerEntitySet.getFeature(featureId) - - if self.move(f, offSetX, offSetY, layerEntitySet, entitySet) == False: - self.plugIn.destroyEditCommand() - return - - self.plugIn.endEditCommand() - - def run(self, msgMapTool = False, msg = None): - if self.plugIn.canvas.mapRenderer().destinationCrs().geographicFlag(): - self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) - return True # fine comando - - #========================================================================= - # RICHIESTA SELEZIONE OGGETTI - if self.step == 0: # inizio del comando - if self.SSGetClass.run(msgMapTool, msg) == True: - # selezione terminata - self.step = 1 - self.getPointMapTool().refreshSnapType() # aggiorno lo snapType che può essere variato dal maptool di selezione entità - return self.run(msgMapTool, msg) - - #========================================================================= - # SPOSTA OGGETTI - elif self.step == 1: - self.entitySet.set(self.SSGetClass.entitySet) - - if self.entitySet.count() == 0: - return True # fine comando - - # imposto il map tool - self.getPointMapTool().entitySet.set(self.entitySet) - self.getPointMapTool().setMode(Qad_move_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_BASE_PT) - - keyWords = QadMsg.translate("Command_MOVE", "Displacement") - prompt = QadMsg.translate("Command_MOVE", "Specify base point or [{0}] <{0}>: ").format(keyWords) - - englishKeyWords = "Displacement" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o enter o una parola chiave - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - None, \ - keyWords, QadInputModeEnum.NONE) - self.step = 2 - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA PUNTO BASE (da step = 1) - elif self.step == 2: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - pass # opzione di default "spostamento" - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if value is None or type(value) == unicode: - self.basePt.set(0, 0) - self.getPointMapTool().basePt = self.basePt - self.getPointMapTool().setMode(Qad_move_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_MOVE_PT) - # si appresta ad attendere un punto - msg = QadMsg.translate("Command_MOVE", "Specify the displacement fom the origin point 0,0 <{0}, {1}>: ") - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(msg.format(str(self.plugIn.lastOffsetPt.x()), str(self.plugIn.lastOffsetPt.y())), \ - QadInputTypeEnum.POINT2D, \ - self.plugIn.lastOffsetPt, \ - "", QadInputModeEnum.NONE) - self.step = 4 - elif type(value) == QgsPoint: # se é stato inserito il punto base - self.basePt.set(value.x(), value.y()) - - # imposto il map tool - self.getPointMapTool().basePt = self.basePt - self.getPointMapTool().setMode(Qad_move_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_MOVE_PT) - - # si appresta ad attendere un punto o enter o una parola chiave - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(QadMsg.translate("Command_MOVE", "Specify the second point or : "), \ - QadInputTypeEnum.POINT2D, \ - None, \ - "", QadInputModeEnum.NONE) - self.step = 3 - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA SECONDO PUNTO PER SPOSTAMENTO (da step = 2) - elif self.step == 3: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if value is None: - newPt = QgsPoint(self.basePt.x() * 2, self.basePt.y() * 2) - self.moveGeoms(newPt) - elif type(value) == QgsPoint: # se é stato inserito lo spostamento con un punto - self.moveGeoms(value) - - return True # fine comando - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DEL PUNTO DI SPOSTAMENTO (da step = 2) - elif self.step == 4: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - self.plugIn.setLastOffsetPt(value) - self.moveGeoms(value) - return True - - -# Classe che gestisce il comando MOVE per i grip -class QadGRIPMOVECommandClass(QadCommandClass): - - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadGRIPMOVECommandClass(self.plugIn) - - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - self.entitySet = QadEntitySet() - self.basePt = QgsPoint() - self.skipToNextGripCommand = False - self.copyEntities = False - self.nOperationsToUndo = 0 - - - def __del__(self): - QadCommandClass.__del__(self) - - - def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): - if (self.plugIn is not None): - if self.PointMapTool is None: - self.PointMapTool = Qad_move_maptool(self.plugIn) - return self.PointMapTool - else: - return None - - - #============================================================================ - # setSelectedEntityGripPoints - #============================================================================ - def setSelectedEntityGripPoints(self, entitySetGripPoints): - # lista delle entityGripPoint con dei grip point selezionati - self.entitySet.clear() - - for entityGripPoints in entitySetGripPoints.entityGripPoints: - self.entitySet.addEntity(entityGripPoints.entity) - self.getPointMapTool().entitySet.set(self.entitySet) - - - #============================================================================ - # move - #============================================================================ - def move(self, entity, offSetX, offSetY): - # entity = entità da spostare - # offSetX, offSetY = spostamento da fare - # tolerance2ApproxCurve = tolleranza per ricreare le curve - # verifico se l'entità appartiene ad uno stile di quotatura - if entity.whatIs() == "ENTITY": - # sposto l'entità - movedGeom = qad_utils.moveQgsGeometry(entity.getGeometry(), offSetX, offSetY) - - if movedGeom is not None: - f = entity.getFeature() - f.setGeometry(movedGeom) - if self.copyEntities == False: - # plugIn, layer, feature, refresh, check_validity - if qad_layer.updateFeatureToLayer(self.plugIn, entity.layer, f, False, False) == False: - return False - else: - # plugIn, layer, features, coordTransform, refresh, check_validity - if qad_layer.addFeatureToLayer(self.plugIn, entity.layer, f, None, False, False) == False: - return False - - elif entity.whatIs() == "DIMENTITY": - # stiro la quota - if self.copyEntities == False: - if entity.deleteToLayers(self.plugIn) == False: - return False - entity.move(offSetX, offSetY) - if entity.addToLayers(self.plugIn) == False: - return False - - return True - - - #============================================================================ - # moveFeatures - #============================================================================ - def moveFeatures(self, newPt): - entity = QadEntity() - self.plugIn.beginEditCommand("Feature moved", self.entitySet.getLayerList()) - - dimElaboratedList = [] # lista delle quotature già elaborate - - for layerEntitySet in self.entitySet.layerEntitySetList: - layer = layerEntitySet.layer - - transformedBasePt = self.mapToLayerCoordinates(layerEntitySet.layer, self.basePt) - transformedNewPt = self.mapToLayerCoordinates(layerEntitySet.layer, newPt) - offSetX = transformedNewPt.x() - transformedBasePt.x() - offSetY = transformedNewPt.y() - transformedBasePt.y() - - for featureId in layerEntitySet.featureIds: - entity.set(layerEntitySet.layer, featureId) - - # verifico se l'entità appartiene ad uno stile di quotatura - dimEntity = QadDimStyles.getDimEntity(entity) - if dimEntity is None: - if self.move(entity, offSetX, offSetY) == False: - self.plugIn.destroyEditCommand() - return - else: - found = False - for dimElaborated in dimElaboratedList: - if dimElaborated == dimEntity: - found = True - - if found == False: # quota non ancora elaborata - dimElaboratedList.append(dimEntity) - if self.move(dimEntity, offSetX, offSetY) == False: - self.plugIn.destroyEditCommand() - return - - self.plugIn.endEditCommand() - self.nOperationsToUndo = self.nOperationsToUndo + 1 - - - #============================================================================ - # waitForMovePoint - #============================================================================ - def waitForMovePoint(self): - self.step = 1 - self.plugIn.setLastPoint(self.basePt) - # imposto il map tool - self.getPointMapTool().basePt = self.basePt - self.getPointMapTool().setMode(Qad_move_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_MOVE_PT) - - keyWords = QadMsg.translate("Command_GRIPMOVE", "Base point") + "/" + \ - QadMsg.translate("Command_GRIPMOVE", "Copy") + "/" + \ - QadMsg.translate("Command_GRIPMOVE", "Undo") + "/" + \ - QadMsg.translate("Command_GRIPMOVE", "eXit") - - prompt = QadMsg.translate("Command_GRIPMOVE", "Specify move point or [{0}]: ").format(keyWords) - - englishKeyWords = "Base point" + "/" + "Copy" + "/" + "Undo" + "/" + "eXit" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o enter o una parola chiave - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(prompt, QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - None, \ - keyWords, QadInputModeEnum.NONE) - - - #============================================================================ - # waitForBasePt - #============================================================================ - def waitForBasePt(self): - self.step = 2 - # imposto il map tool - self.getPointMapTool().setMode(Qad_move_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_BASE_PT) - - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_GRIPMOVE", "Specify base point: ")) - - - #============================================================================ - # run - #============================================================================ - def run(self, msgMapTool = False, msg = None): - if self.plugIn.canvas.mapRenderer().destinationCrs().geographicFlag(): - self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) - return True # fine comando - - #========================================================================= - # RICHIESTA SELEZIONE OGGETTI - if self.step == 0: # inizio del comando - if self.entitySet.isEmpty(): # non ci sono oggetti da spostare - return True - self.showMsg(QadMsg.translate("Command_GRIPMOVE", "\n** MOVE **\n")) - # si appresta ad attendere un punto di spostamento - self.waitForMovePoint() - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DI UN PUNTO DI SPOSTAMENTO - elif self.step == 1: - ctrlKey = False - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - value = None - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - else: - value = self.getPointMapTool().point - - ctrlKey = self.getPointMapTool().ctrlKey - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("Command_GRIPMOVE", "Base point") or value == "Base point": - # si appresta ad attendere il punto base - self.waitForBasePt() - elif value == QadMsg.translate("Command_GRIPMOVE", "Copy") or value == "Copy": - # Copia entità lasciando inalterate le originali - self.copyEntities = True - # si appresta ad attendere un punto di spostamento - self.waitForMovePoint() - elif value == QadMsg.translate("Command_GRIPMOVE", "Undo") or value == "Undo": - if self.nOperationsToUndo > 0: - self.nOperationsToUndo = self.nOperationsToUndo - 1 - self.plugIn.undoEditCommand() - else: - self.showMsg(QadMsg.translate("QAD", "\nThe command has been canceled.")) - # si appresta ad attendere un punto di spostamento - self.waitForMovePoint() - elif value == QadMsg.translate("Command_GRIPMOVE", "eXit") or value == "eXit": - return True # fine comando - elif type(value) == QgsPoint: # se é stato selezionato un punto - if ctrlKey: - self.copyEntities = True - - self.moveFeatures(value) - - if self.copyEntities == False: - return True - # si appresta ad attendere un punto di stiramento - self.waitForMovePoint() - - else: - if self.copyEntities == False: - self.skipToNextGripCommand = True - return True # fine comando - - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA PUNTO BASE (da step = 1) - elif self.step == 2: # dopo aver atteso un punto - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - pass # opzione di default "spostamento" - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == QgsPoint: # se é stato inserito il punto base - self.basePt.set(value.x(), value.y()) - # imposto il map tool - self.getPointMapTool().basePt = self.basePt - - # si appresta ad attendere un punto di spostamento - self.waitForMovePoint() - - return False diff --git a/qad_move_maptool.py b/qad_move_maptool.py deleted file mode 100644 index 3b7b1d0f..00000000 --- a/qad_move_maptool.py +++ /dev/null @@ -1,156 +0,0 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - classe per gestire il map tool in ambito del comando move - - ------------------- - begin : 2013-09-27 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -from qgis.gui import * -import math - - -import qad_utils -from qad_snapper import * -from qad_snappointsdisplaymanager import * -from qad_variables import * -from qad_getpoint import * -from qad_highlight import QadHighlight -from qad_dim import * - - -#=============================================================================== -# Qad_move_maptool_ModeEnum class. -#=============================================================================== -class Qad_move_maptool_ModeEnum(): - # noto niente si richiede il punto base - NONE_KNOWN_ASK_FOR_BASE_PT = 1 - # noto il punto base si richiede il secondo punto per lo spostamento - BASE_PT_KNOWN_ASK_FOR_MOVE_PT = 2 - - -#=============================================================================== -# Qad_move_maptool class -#=============================================================================== -class Qad_move_maptool(QadGetPoint): - - def __init__(self, plugIn): - QadGetPoint.__init__(self, plugIn) - - self.basePt = None - self.entitySet = QadEntitySet() - self.__highlight = QadHighlight(self.canvas) - - def hidePointMapToolMarkers(self): - QadGetPoint.hidePointMapToolMarkers(self) - self.__highlight.hide() - - def showPointMapToolMarkers(self): - QadGetPoint.showPointMapToolMarkers(self) - self.__highlight.show() - - def clear(self): - QadGetPoint.clear(self) - self.__highlight.reset() - self.mode = None - - - #============================================================================ - # move - #============================================================================ - def move(self, entity, offSetX, offSetY): - # verifico se l'entità appartiene ad uno stile di quotatura - if entity.whatIs() == "ENTITY": - # sposto l'entità - movedGeom = qad_utils.moveQgsGeometry(entity.getGeometry(), offSetX, offSetY) - if movedGeom is not None: - self.__highlight.addGeometry(movedGeom, entity.layer) - else: - # sposto la quota - entity.move(offSetX, offSetY) - self.__highlight.addGeometry(entity.textualFeature.geometry(), entity.getTextualLayer()) - self.__highlight.addGeometries(entity.getLinearGeometryCollection(), entity.getLinearLayer()) - self.__highlight.addGeometries(entity.getSymbolGeometryCollection(), entity.getSymbolLayer()) - - - def addMovedGeometries(self, newPt): - self.__highlight.reset() - - dimElaboratedList = [] # lista delle quotature già elaborate - entity = QadEntity() - - for layerEntitySet in self.entitySet.layerEntitySetList: - layer = layerEntitySet.layer - - transformedBasePt = self.canvas.mapRenderer().mapToLayerCoordinates(layer, self.basePt) - transformedNewPt = self.canvas.mapRenderer().mapToLayerCoordinates(layer, newPt) - offSetX = transformedNewPt.x() - transformedBasePt.x() - offSetY = transformedNewPt.y() - transformedBasePt.y() - - for featureId in layerEntitySet.featureIds: - # verifico se l'entità appartiene ad uno stile di quotatura - dimEntity = QadDimStyles.getDimEntity(layer, featureId) - if dimEntity is None: - entity.set(layer, featureId) - f = layerEntitySet.getFeature(featureId) - self.move(entity, offSetX, offSetY) - else: - found = False - for dimElaborated in dimElaboratedList: - if dimElaborated == dimEntity: - found = True - if found == False: # quota non ancora elaborata - dimElaboratedList.append(dimEntity) - self.stretch(dimEntity, offSetX, offSetY) - - - def canvasMoveEvent(self, event): - QadGetPoint.canvasMoveEvent(self, event) - - # noto il punto base si richiede il secondo punto - if self.mode == Qad_move_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_MOVE_PT: - self.addMovedGeometries(self.tmpPoint) - - - def activate(self): - QadGetPoint.activate(self) - self.__highlight.show() - - def deactivate(self): - try: # necessario perché se si chiude QGIS parte questo evento nonostante non ci sia più l'oggetto maptool ! - QadGetPoint.deactivate(self) - self.__highlight.hide() - except: - pass - - def setMode(self, mode): - self.mode = mode - # noto niente si richiede il punto base - if self.mode == Qad_move_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_BASE_PT: - self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) - self.setDrawMode(QadGetPointDrawModeEnum.NONE) - self.__highlight.reset() - # noto il punto base si richiede il secondo punto - elif self.mode == Qad_move_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_MOVE_PT: - self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.setStartPoint(self.basePt) \ No newline at end of file diff --git a/qad_mpolygon_cmd.py b/qad_mpolygon_cmd.py deleted file mode 100644 index 4212f00a..00000000 --- a/qad_mpolygon_cmd.py +++ /dev/null @@ -1,141 +0,0 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - comando MPOLYGON per disegnare un poligono - - ------------------- - begin : 2013-09-18 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -from qad_generic_cmd import QadCommandClass -from qad_pline_cmd import QadPLINECommandClass -from qad_msg import QadMsg -from qad_textwindow import * -from qad_getpoint import * -import qad_utils -import qad_layer - - -# Classe che gestisce il comando MPOLYGON -class QadMPOLYGONCommandClass(QadCommandClass): - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadMPOLYGONCommandClass(self.plugIn) - - def getName(self): - return QadMsg.translate("Command_list", "MPOLYGON") - - def getEnglishName(self): - return "MPOLYGON" - - def connectQAction(self, action): - QObject.connect(action, SIGNAL("triggered()"), self.plugIn.runMPOLYGONCommand) - - def getIcon(self): - return QIcon(":/plugins/qad/icons/mpolygon.png") - - def getNote(self): - # impostare le note esplicative del comando - return QadMsg.translate("Command_MPOLYGON", "Draws a polygon by many methods.\nA Polygon is a closed sequence of straight line segments,\narcs or a combination of two.") - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - self.vertices = [] - # se questo flag = True il comando serve all'interno di un altro comando per disegnare un poligono - # che non verrà salvato su un layer - self.virtualCmd = False - self.rubberBandBorderColor = None - self.rubberBandFillColor = None - self.PLINECommand = None - - def __del__(self): - QadCommandClass.__del__(self) - del self.SSGetClass - - def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): - if self.PLINECommand is not None: - self.PointMapTool = self.PLINECommand.getPointMapTool(drawMode) - return self.PointMapTool - else: - return QadCommandClass.getPointMapTool(self, drawMode) - - - def setRubberBandColor(self, rubberBandBorderColor, rubberBandFillColor): - self.rubberBandBorderColor = rubberBandBorderColor - self.rubberBandFillColor = rubberBandFillColor - if self.PLINECommand is not None: - self.PLINECommand.setRubberBandColor(rubberBandBorderColor, rubberBandFillColor) - - - def run(self, msgMapTool = False, msg = None): - if self.plugIn.canvas.mapRenderer().destinationCrs().geographicFlag(): - self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) - return True # fine comando - - if self.virtualCmd == False: # se si vuole veramente salvare la polylinea in un layer - currLayer, errMsg = qad_layer.getCurrLayerEditable(self.plugIn.canvas, QGis.Polygon) - if currLayer is None: - self.showErr(errMsg) - return True # fine comando - - #========================================================================= - # RICHIESTA PRIMO PUNTO PER SELEZIONE OGGETTI - if self.step == 0: - self.PLINECommand = QadPLINECommandClass(self.plugIn, True) - self.PLINECommand.setRubberBandColor(self.rubberBandBorderColor, self.rubberBandFillColor) - # se questo flag = True il comando serve all'interno di un altro comando per disegnare una linea - # che non verrà salvata su un layer - self.PLINECommand.virtualCmd = True - self.PLINECommand.asToolForMPolygon = True # per rubberband tipo poligono - self.PLINECommand.run(msgMapTool, msg) - self.step = 1 - return False # continua - - #========================================================================= - # RISPOSTA ALLA RICHIESTA PUNTO (da step = 0 o 1) - elif self.step == 1: # dopo aver atteso un punto si riavvia il comando - if self.PLINECommand.run(msgMapTool, msg) == True: - verticesLen = len(self.PLINECommand.vertices) - if verticesLen >= 3: - self.vertices = self.PLINECommand.vertices[:] # copio la lista - firstVertex = self.vertices[0] - # se l'ultimo vertice non é uguale al primo - if self.vertices[verticesLen - 1] != firstVertex: - # aggiungo un vertice con le stesse coordinate del primo - self.vertices.append(firstVertex) - if self.virtualCmd == False: # se si vuole veramente salvare la polylinea in un layer - if qad_layer.addPolygonToLayer(self.plugIn, currLayer, self.vertices) == False: - self.showMsg(QadMsg.translate("Command_MPOLYGON", "\nPolygon not valid.\n")) - del self.vertices[:] # svuoto la lista - else: - self.showMsg(QadMsg.translate("Command_MPOLYGON", "\nPolygon not valid.\n")) - - del self.PLINECommand - self.PLINECommand = None - - return True # fine - - return False diff --git a/qad_msg.py b/qad_msg.py index 92008484..19c1299f 100644 --- a/qad_msg.py +++ b/qad_msg.py @@ -1,134 +1,199 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - classe per le traduzioni dei messaggi - - ------------------- - begin : 2013-05-22 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -from PyQt4.QtCore import * -from PyQt4.QtGui import * # for QDesktopServices -import os.path - -import urllib -import platform - - -# traduction class. -class QadMsgClass(): - - - def __init__(self): - pass - - - #============================================================================ - # translate - #============================================================================ - def translate(self, context, sourceText, disambiguation = None, encoding = QCoreApplication.UnicodeUTF8, n = -1): - # da usare in una riga senza accoppiarla ad altre chiamate ad esempio (per lupdate.exe che altrimenti non le trova): - # NON VA BENE - # proplist["blockScale"] = [QadMsg.translate("Dimension", "Scala frecce"), \ - # self.blockScale] - # VA BENE - # msg = QadMsg.translate("Dimension", "Scala frecce") - # proplist["blockScale"] = [msg, self.blockScale] - - # contesti: - # "QAD" per traduzioni generali - # "Popup_menu_graph_window" per il menu popup nella finestra grafica - # "Text_window" per la finestra testuale - # "Command_list" per nomi di comandi - # "Command_" per traduzioni di un comando specifico (es. "Command_PLINE") - # "Snap" per i tipi di snap - # finestre varie (es. "DSettings_Dialog", DimStyle_Dialog, ...) - # "Dimension" per le quotature - # "Environment variables" per i nomi delle variabili di ambiente - # "Help" per i titoli dei capitoli del manuale che servono da section nel file html di help - return QCoreApplication.translate(context, sourceText, disambiguation, encoding, n) - - -#=============================================================================== -# qadShowPluginHelp -#=============================================================================== -def qadShowPluginHelp(section = "", filename = "index", packageName = None): - """ - show a help in the user's html browser. - per conoscere la sezione/pagina del file html usare internet explorer, - selezionare nella finestra di destra la voce di interesse e leggerne l'indirizzo dalla casella in alto. - Questo perché internet explorer inserisce tutti i caratteri di spaziatura e tab che gli altri browser non fanno. - """ - try: - source = "" - if packageName is None: - import inspect - source = inspect.currentframe().f_back.f_code.co_filename - else: - source = sys.modules[packageName].__file__ - except: - return - - # initialize locale - userLocaleList = QSettings().value("locale/userLocale").split("_") - language = userLocaleList[0] - region = userLocaleList[1] if len(userLocaleList) > 1 else "" - - path = QDir.cleanPath(os.path.dirname(source) + "/help/help") - helpPath = path + "_" + language + "_" + region # provo a caricare la lingua e la regione selezionate - - if not os.path.exists(helpPath): - helpPath = path + "_" + language # provo a caricare la lingua - if not os.path.exists(helpPath): - helpPath = path + "_en" # provo a caricare la lingua inglese - if not os.path.exists(helpPath): - return - - helpfile = os.path.join(helpPath, filename + ".html") - if os.path.exists(helpfile): - url = "file:///"+helpfile - - if section != "": - url = url + "#" + urllib.quote(section.encode('utf-8')) - - # la funzione QDesktopServices.openUrl in windows non apre la sezione - if platform.system() == "Windows": - import subprocess - from _winreg import HKEY_CURRENT_USER, OpenKey, QueryValue - # In Py3, this module is called winreg without the underscore - - with OpenKey(HKEY_CURRENT_USER, r"Software\Classes\http\shell\open\command") as key: - cmd = QueryValue(key, None) - - if cmd.find("\"%1\"") >= 0: - subprocess.Popen(cmd.replace("%1", url)) - else: - if cmd.find("%1") >= 0: - subprocess.Popen(cmd.replace("%1", "\"" + url + "\"")) - else: - subprocess.Popen(cmd + " \"" + url + "\"") - else: - QDesktopServices.openUrl(QUrl(url)) - - -#=============================================================================== -# QadMsg = variabile globale -#=============================================================================== - -QadMsg = QadMsgClass() +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + classe per le traduzioni dei messaggi + + ------------------- + begin : 2013-05-22 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from qgis.PyQt.QtCore import * +from qgis.PyQt.QtGui import * # for QDesktopServices +import os.path +import sys + +import urllib.parse +import platform +#from plotly.graph_objs.bar.marker.colorbar import title +import webbrowser + + +# traduction class. +class QadMsgClass(): + + + def __init__(self): + pass + + + # ============================================================================ + # translate + # ============================================================================ + def translate(self, context, sourceText, disambiguation = None, n = -1): + # da usare in una riga senza accoppiarla ad altre chiamate ad esempio (per lupdate.exe che altrimenti non le trova): + # NON VA BENE + # proplist["blockScale"] = [QadMsg.translate("Dimension", "Scala frecce"), \ + # self.blockScale] + # VA BENE + # msg = QadMsg.translate("Dimension", "Scala frecce") + # proplist["blockScale"] = [msg, self.blockScale] + + # contesti: + # "QAD" per traduzioni generali + # "Popup_menu_graph_window" per il menu popup nella finestra grafica + # "Text_window" per la finestra testuale + # "Command_list" per nomi di comandi + # "Command_" per traduzioni di un comando specifico (es. "Command_PLINE") + # "Snap" per i tipi di snap + # finestre varie (es. "DSettings_Dialog", DimStyle_Dialog, ...) + # "Dimension" per le quotature + # "Environment variables" per i nomi delle variabili di ambiente + # "Help" per i titoli dei capitoli del manuale che servono da section nel file html di help + return QCoreApplication.translate(context, sourceText, disambiguation, n) + + + + # =============================================================================== + # getQADTitle + # =============================================================================== + def getQADTitle(self, sponsorWith = False): + title = QadMsg.translate("QAD", "QAD") + if sponsorWith == True: + title += " (supported by " + title += QadMsg.translate("SUPPORTER", "SUPPORTER WANTED") + title += ")" + + return title + + +# =============================================================================== +# qadShowPluginHelp +# =============================================================================== +def qadShowPluginPDFHelp(section = "", filename = "QAD"): + """ + Apre il file di help in formato PDF alla sezione nota. + per conoscere la sezione/pagina del file html usare internet explorer, + selezionare nella finestra di destra la voce di interesse e leggerne l'indirizzo dalla casella in alto. + Questo perché internet explorer inserisce tutti i caratteri di spaziatura e tab che gli altri browser non fanno. + """ + basepath = os.path.dirname(os.path.realpath(__file__)) + + # initialize locale + userLocaleList = QSettings().value("locale/userLocale").split("_") + language = userLocaleList[0] + region = userLocaleList[1] if len(userLocaleList) > 1 else "" + + path = QDir.cleanPath(basepath + "/help") + helpfile = os.path.join(path, filename + "_" + language + "_" + region + ".pdf") # provo a caricare la lingua e la regione selezionate + + if not os.path.exists(helpfile): + helpfile = os.path.join(path, filename + "_" + language + ".pdf") # provo a caricare la lingua + if not os.path.exists(helpfile): + helpfile = os.path.join(path, filename + "_en" + ".pdf") # provo a caricare la lingua inglese + if not os.path.exists(helpfile): + return + + if section != "": + helpfile = helpfile + "#" + urllib.parse.quote(section.encode('utf-8').decode('utf-8')) + + webbrowser.open_new(helpfile) + + +# =============================================================================== +# qadShowPluginHelp +# =============================================================================== +def qadShowPluginHelp(section = "", filename = "index", packageName = None): + """ + show a help in the user's html browser. + per conoscere la sezione/pagina del file html usare internet explorer, + selezionare nella finestra di destra la voce di interesse e leggerne l'indirizzo dalla casella in alto. + Questo perché internet explorer inserisce tutti i caratteri di spaziatura e tab che gli altri browser non fanno. + """ + try: + basepath = "" + if packageName is None: + basepath = os.path.dirname(os.path.realpath(__file__)) + else: + basepath = os.path.dirname(os.path.realpath(sys.modules[packageName].__file__)) + except: + return + + # initialize locale + userLocaleList = QSettings().value("locale/userLocale").split("_") + language = userLocaleList[0] + region = userLocaleList[1] if len(userLocaleList) > 1 else "" + + path = QDir.cleanPath(basepath + "/help/help") + helpPath = path + "_" + language + "_" + region # provo a caricare la lingua e la regione selezionate + + if not os.path.exists(helpPath): + helpPath = path + "_" + language # provo a caricare la lingua + if not os.path.exists(helpPath): + helpPath = path + "_en" # provo a caricare la lingua inglese + if not os.path.exists(helpPath): + return + + helpfile = os.path.join(helpPath, filename + ".html") + if os.path.exists(helpfile): + url = "file:///"+helpfile + + if section != "": + url = url + "#" + urllib.parse.quote(section.encode('utf-8').decode('utf-8')) + + # la funzione QDesktopServices.openUrl in windows non apre la sezione + if platform.system() == "Windows": + import subprocess + from winreg import HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, OpenKey, QueryValue + + try: # provo a livello di utente + with OpenKey(HKEY_CURRENT_USER, r"Software\Classes\http\shell\open\command") as key: + cmd = QueryValue(key, None) + except: # se non c'era a livello di utente provo a livello di macchina + with OpenKey(HKEY_LOCAL_MACHINE, r"Software\Classes\http\shell\open\command") as key: + cmd = QueryValue(key, None) + + if cmd.find("\"%1\"") >= 0: + subprocess.Popen(cmd.replace("%1", url)) + else: + if cmd.find("%1") >= 0: + subprocess.Popen(cmd.replace("%1", "\"" + url + "\"")) + else: + subprocess.Popen(cmd + " \"" + url + "\"") + else: + QDesktopServices.openUrl(QUrl(url)) + + +# =============================================================================== +# qadShowSupportersPage +# =============================================================================== +def qadShowSupportersPage(): + """ + show the supporter members page in the user's html browser. + """ + try: + webbrowser.open_new("https://qadplugin.wordpress.com/donations") + except: + return + + +# =============================================================================== +# QadMsg = variabile globale +# =============================================================================== + +QadMsg = QadMsgClass() diff --git a/qad_multi_geom.py b/qad_multi_geom.py new file mode 100644 index 00000000..ca5615f8 --- /dev/null +++ b/qad_multi_geom.py @@ -0,0 +1,1353 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + classe per la gestione delle multi geometrie (multipoligoni) + + ------------------- + begin : 2019-03-15 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from qgis.PyQt.QtCore import * +from qgis.core import * +from qgis.gui import * +import qgis.utils + + +from .qad_point import * +from .qad_line import QadLine +from .qad_circle import QadCircle +from .qad_ellipse import QadEllipse +from .qad_polyline import QadPolyline +from .qad_polygon import QadPolygon +from .qad_layer import createMemoryLayer + + +# =============================================================================== +# QadLinearObject class +# restituiesce un oggetto lineare linea, arco, arco di ellisse, polilinea ma anche +# cerchio ed ellisse (impropriamente) perchè possono esistere in layer LINESTRING +# =============================================================================== +class QadLinearObject(): + + def __init__(self): + pass + + + # ============================================================================ + # fromPolyline + # ============================================================================ + @staticmethod + def fromPolyline(points): + """ + la funzione restituisce un oggetto lineare che può essere: + linea, arco, arco di ellisse, polilinea ma anche + cerchio ed ellisse (impropriamente) perchè possono esistere in layer LINESTRING + """ + tot_points = len(points) + if tot_points == 0: + return None + + if tot_points == 2: + line = QadLine() + line.set(points[0], points[1]) + return line + + # verifico se è un cerchio + circle = QadCircle() + if circle.fromPolyline(points): return circle + del circle + # verifico se è una ellisse + ellipse = QadEllipse() + if ellipse.fromPolyline(points): return ellipse + del ellipse + # verifico se è una polilinea + polyline = QadPolyline() + if polyline.fromPolyline(points): + if polyline.qty() == 1: # se è composto da solo 1 oggetto + return polyline.getLinearObjectAt(0) + else: + return polyline + del polyline + + return None + + + # ============================================================================ + # fromGeom + # ============================================================================ + @staticmethod + def fromGeom(geom): + """ + la funzione restituisce un oggetto lineare che può essere: + linea, arco, arco di ellisse, polilinea ma anche + cerchio ed ellisse (impropriamente) perchè possono esistere in layer LINESTRING + """ + return QadLinearObject.fromPolyline(geom.asPolyline()) + + +# =============================================================================== +# QadMultiPoint class +# rappresenta una lista di oggetti puntuali +# =============================================================================== +class QadMultiPoint(): + + def __init__(self, multiPoint=None): + self.defList = [] + # deflist = ( ...) + if multiPoint is not None: + self.set(multiPoint) + + + # ============================================================================ + # whatIs + # ============================================================================ + def whatIs(self): + return "MULTI_POINT" + + + # ============================================================================ + # isClosed + # ============================================================================ + def isClosed(self): + return False + + + # ============================================================================ + # set + # ============================================================================ + def set(self, multiPoint): + self.removeAll() + for point in multiPoint.defList: + self.append(point) + return self + + + def __eq__(self, multiPoint): + # obbligatoria + """self == other""" + if multiPoint.whatIs() != "MULTI_POINT": return False + if self.qty() != multiPoint.qty(): return False + for i in range(0, self.qty()): + if self.getPointAt(i) != multiPoint.getPointAt(i): return False + return True + + + def __ne__(self, multiPoint): + """self != other""" + return not self.__eq__(multiPoint) + + + # ============================================================================ + # append + # ============================================================================ + def append(self, point): + """ + la funzione aggiunge un punto lineare in fondo alla lista. + """ + if point is None: return + if type(point) == QgsPointXY: + self.defList.append(QadPoint(point)) + else: + objectType = point.whatIs() + if objectType != "POINT": return False + self.defList.append(point.copy()) + + return True + + + # ============================================================================ + # insert + # ============================================================================ + def insert(self, i, point): + """ + la funzione aggiunge un punto nella posizione i-esima della lista dei punti. + """ + if i >= self.qty(): + return self.append(point) + else: + if type(point) == QgsPointXY: + self.defList.append(QadPoint(point)) + else: + objectType = point.whatIs() + if objectType != "POINT": return False + return self.defList.insert(i, point.copy()) + + + + # ============================================================================ + # remove + # ============================================================================ + def remove(self, i): + """ + la funzione cancella un punto nella posizione i-esima della lista. + """ + del self.defList[i] + + + # ============================================================================ + # removeAll + # ============================================================================ + def removeAll(self): + """ + la funzione cancella i punti della lista. + """ + del self.defList[:] + + + # ============================================================================ + # getPointAt + # ============================================================================ + def getPointAt(self, i): + """ + la funzione restituisce il punto alla posizione i-esima + con numeri negativi parte dal fondo (es. -1 = ultima posizione) + """ + if self.qty() == 0 or i > self.qty() - 1: + return None + return self.defList[i] + + + # ============================================================================ + # setPointAt + # ============================================================================ + def setPointAt(self, pt, i): + """ + la funzione setta il punto i-esimo + """ + return self.getPointAt(i).set(pt) + + + # ============================================================================ + # fromMultiPoint + # ============================================================================ + def fromMultiPoint(self, pointList): + """ + la funzione inizializza una lista di punti che compone il multiPoint passato in forma di lista di punti. + """ + self.removeAll() + for point in pointList: + self.append(point) + + if self.qty() == 0: return False + + return True + + + # ============================================================================ + # fromGeom + # ============================================================================ + def fromGeom(self, geom): + """ + la funzione inizializza una lista di punti QgsPointXY che compone il multiPoint da un oggetto QgsGeometry. + """ + return self.fromMultiPoint(geom.asMultiPoint()) + + + # =============================================================================== + # asMultiPoint + # =============================================================================== + def asMultiPoint(self): + """ + la funzione ritorna una lista punti QgsPointXY che compongono un multiPoint. + """ + result = [] + for point in self.defList: + result.append(QgsPointXY(point)) + + return result + + + # =============================================================================== + # asGeom + # =============================================================================== + def asGeom(self, wkbType = QgsWkbTypes.MultiPoint , tolerance2ApproxCurve = None, atLeastNSegment = None): + """ + la funzione ritorna il multiPoint in forma di QgsGeometry. + wkbType, tolerance2ApproxCurve e atLeastNSegment sono dichiarati solo per compatibilità + """ + return QgsGeometry.fromMultiPointXY(self.asMultiPoint()) + + + # =============================================================================== + # copy + # =============================================================================== + def copy(self): + # obbligatoria + return QadMultiPoint(self) + + + # ============================================================================ + # move + # ============================================================================ + def move(self, offsetX, offsetY): + """ + la funzione sposta i punti secondo un offset X e uno Y + """ + for point in self.defList: + point.move(offsetX, offsetY) + + + # ============================================================================ + # rotate + # ============================================================================ + def rotate(self, basePt, angle): + for point in self.defList: + point.rotate(basePt, angle) + + + # ============================================================================ + # scale + # ============================================================================ + def scale(self, basePt, scale): + for point in self.defList: + point.scale(basePt, scale) + + + # ============================================================================ + # mirror + # ============================================================================ + def mirror(self, mirrorPt, mirrorAngle): + for point in self.defList: + point.mirror(mirrorPt, mirrorAngle) + + + # ============================================================================ + # qty + # ============================================================================ + def qty(self): + """ + la funzione restituisce la quantità di punti che compongono il multipoint. + """ + return len(self.defList) + + + # =============================================================================== + # transform + # =============================================================================== + def transform(self, coordTransform): + """ + la funzione restituisce un nuovo multipoint con le coordinate trasformate. + """ + result = QadMultiPoint() + for point in self.defList: + result.append(point.transform(coordTransform)) + return result + + + # =============================================================================== + # transformFromCRSToCRS + # =============================================================================== + def transformFromCRSToCRS(self, sourceCRS, destCRS): + """ + la funzione trasforma le coordinate dei punti che compongono il multipoint. + """ + return self.transform(QgsCoordinateTransform(sourceCRS, destCRS, QgsProject.instance())) + + + # =============================================================================== + # closestPoint + # =============================================================================== + def closestPoint(pt): + """ + la funzione ritorna una lista con + ( + + + """ + dist = sys.float_info.max + index = 0 + for point in self.defList: + d = point.distance(pt) + if d < dist: + dist = d + minDistPoint = point + pointIndex = index + + index = index + 1 + + return (dist, minDistPoint, pointIndex) + + + # =============================================================================== + # getBoundingBox + # =============================================================================== + def getBoundingBox(self): + """ + la funzione ritorna il rettangolo che racchiude i punti. + """ + boundingBox = self.getPointAt(0).getBoundingBox() + i = 1 + while i < self.qty(): + boundingBox.combineExtentWith(self.getPointAt(i).getBoundingBox()) + i = i + 1 + + return boundingBox + + +# =============================================================================== +# QadMultiLinearObject class +# rappresenta una lista di oggetti lineari compreso cerchio ed ellisse (impropriamente) perchè possono esistere in layer LINESTRING +# =============================================================================== +class QadMultiLinearObject(): + + def __init__(self, multiLinearObject=None): + self.defList = [] + # deflist = ( ...) + if multiLinearObject is not None: + self.set(multiLinearObject) + + + # ============================================================================ + # whatIs + # ============================================================================ + def whatIs(self): + return "MULTI_LINEAR_OBJ" + + + # ============================================================================ + # set + # ============================================================================ + def set(self, multiLinearObject): + self.removeAll() + for linearObject in multiLinearObject.defList: + self.append(linearObject) + return self + + + def __eq__(self, multiLinearObject): + # obbligatoria + """self == other""" + if multiLinearObject.whatIs() != "MULTI_LINEAR_OBJ": return False + if self.qty() != multiLinearObject.qty(): return False + for i in range(0, self.qty()): + if self.getLinearObjectAt(i) != multiLinearObject.getLinearObjectAt(i): return False + return True + + + def __ne__(self, multiLinearObject): + """self != other""" + return not self.__eq__(multiLinearObject) + + + # ============================================================================ + # append + # ============================================================================ + def append(self, linearObject): + """ + la funzione aggiunge un oggetto lineare in fondo alla lista. + """ + if linearObject is None: return + objectType = linearObject.whatIs() + if objectType != "LINE" and objectType != "ARC" and objectType != "ELLIPSE_ARC" and \ + objectType != "POLYLINE" and objectType != "CIRCLE" and objectType != "ELLIPSE": + return False + self.defList.append(linearObject.copy()) + return True + + + # ============================================================================ + # insert + # ============================================================================ + def insert(self, i, linearObject): + """ + la funzione aggiunge un pggetto lineare nella posizione i-esima della lista degli oggetti lineari. + """ + if i >= self.qty(): + return self.append(linearObject) + else: + return self.defList.insert(i, linearObject.copy()) + + + # ============================================================================ + # remove + # ============================================================================ + def remove(self, i): + """ + la funzione cancella un oggetto lineare nella posizione i-esima della lista. + """ + del self.defList[i] + + + # ============================================================================ + # removeAll + # ============================================================================ + def removeAll(self): + """ + la funzione cancella gli oggetti lineari della lista. + """ + del self.defList[:] + + + # ============================================================================ + # getLinearObjectAt + # ============================================================================ + def getLinearObjectAt(self, i): + """ + la funzione restituisce l'oggetto lineare alla posizione i-esima + con numeri negativi parte dal fondo (es. -1 = ultima posizione) + """ + if self.qty() == 0 or i > self.qty() - 1: + return None + return self.defList[i] + + + # ============================================================================ + # setLinearObjectAt + # ============================================================================ + def setLinearObjectAt(self, linearObject, i): + """ + la funzione setta l'oggetto lineare i-esimo + """ + return self.getLinearObjectAt(i).set(linearObject) + + + # ============================================================================ + # fromMultiLinearObject + # ============================================================================ + def fromMultiLinearObject(self, linearObjectList): + """ + la funzione inizializza una lista di oggetti lineari che compone il multiLinearObject passato in forma di lista di punti. + """ + self.removeAll() + + for points in linearObjectList: + linearObject = QadLinearObject.fromPolyline(points) + if linearObject is not None: + self.append(linearObject) + + if self.qty() == 0: return False + return True + + + # ============================================================================ + # fromGeom + # ============================================================================ + def fromGeom(self, geom): + """ + la funzione inizializza una lista di oggetti lineari che compone il multiLinearObject da un oggetto QgsGeometry. + """ + return self.fromMultiLinearObject(geom.asMultiPolyline()) + + + # =============================================================================== + # asMultiPolyline + # =============================================================================== + def asMultiPolyline(self, tolerance2ApproxCurve = None, atLeastNSegment = None): + """ + la funzione ritorna una lista di liste di liste di punti che compongono un multiLinearObject. + """ + result = [] + for linearObject in self.defList: + result.append(linearObject.asPolyline(tolerance2ApproxCurve, atLeastNSegment)) + + return result + + + # =============================================================================== + # asGeom + # =============================================================================== + def asGeom(self, wkbType = QgsWkbTypes.MultiLineString, tolerance2ApproxCurve = None, atLeastNSegment = None): + """ + la funzione ritorna il multiLinearObject in forma di QgsGeometry. + """ + flatType = QgsWkbTypes.flatType(wkbType) + + if flatType == QgsWkbTypes.MultiCurve: + multiCurve = QgsMultiCurve() + for linearObject in self.defList: + multiCurve.addGeometry(linearObject.asAbstractGeom(QgsWkbTypes.CompoundCurve, tolerance2ApproxCurve, atLeastNSegment)) + return QgsGeometry(multiCurve) + + return QgsGeometry.fromMultiPolylineXY(self.asMultiPolyline(tolerance2ApproxCurve, atLeastNSegment)) + + + # =============================================================================== + # copy + # =============================================================================== + def copy(self): + # obbligatoria + return QadMultiLinearObject(self) + + + # ============================================================================ + # move + # ============================================================================ + def move(self, offsetX, offsetY): + """ + la funzione sposta il gli oggetti lineari secondo un offset X e uno Y + """ + for linearObject in self.defList: + linearObject.move(offsetX, offsetY) + + + # ============================================================================ + # rotate + # ============================================================================ + def rotate(self, basePt, angle): + for linearObject in self.defList: + linearObject.rotate(basePt, angle) + + + # ============================================================================ + # scale + # ============================================================================ + def scale(self, basePt, scale): + for linearObject in self.defList: + linearObject.scale(basePt, scale) + + + # ============================================================================ + # mirror + # ============================================================================ + def mirror(self, mirrorPt, mirrorAngle): + for linearObject in self.defList: + linearObject.mirror(mirrorPt, mirrorAngle) + + + # ============================================================================ + # qty + # ============================================================================ + def qty(self): + """ + la funzione restituisce la quantità di oggetti lineari che compongono il multilinea. + """ + return len(self.defList) + + + # ============================================================================ + # getCentroid + # ============================================================================ + def getCentroid(self, tolerance2ApproxCurve = None): + """ + la funzione restituisce il punto centroide. + """ + g = self.asGeom(QgsWkbTypes.LineString, tolerance2ApproxCurve) + if g is not None: + centroid = g.centroid() + if centroid is not None: + return g.centroid().asPoint() + + return None + + + # =============================================================================== + # transform + # =============================================================================== + def transform(self, coordTransform): + """ + la funzione restituisce un nuovo multilinea con le coordinate trasformate. + """ + result = QadMultiLinearObject() + for linearObject in self.defList: + result.append(linearObject.transform(coordTransform)) + return result + + + # =============================================================================== + # transformFromCRSToCRS + # =============================================================================== + def transformFromCRSToCRS(self, sourceCRS, destCRS): + """ + la funzione trasforma le coordinate dei punti che compongono il multilinea. + """ + return self.transform(QgsCoordinateTransform(sourceCRS, destCRS, QgsProject.instance())) + + + # =============================================================================== + # getBoundingBox + # =============================================================================== + def getBoundingBox(self): + """ + la funzione ritorna il rettangolo che racchiude gli elementi lineari. + """ + boundingBox = self.getLinearObjectAt(0).getBoundingBox() + i = 1 + while i < self.qty(): + boundingBox.combineExtentWith(self.getLinearObjectAt(i).getBoundingBox()) + i = i + 1 + + return boundingBox + + + # =============================================================================== + # containsPt + # =============================================================================== + def containsPt(self, pt): + """ + la funzione ritorna True se il punto è sulla multilinea altrimenti False. + """ + for linearObject in self.defList: + if linearObject.containsPt(pt): return True + + return False + + +# =============================================================================== +# QadMultiPolygon class +# rappresenta una lista di poligoni +# =============================================================================== +class QadMultiPolygon(): + + def __init__(self, multiPolygon=None): + self.defList = [] + # deflist = ( ...) + if multiPolygon is not None: + self.set(multiPolygon) + + + # ============================================================================ + # whatIs + # ============================================================================ + def whatIs(self): + return "MULTI_POLYGON" + + + # ============================================================================ + # isClosed + # ============================================================================ + def isClosed(self): + return True + + + # ============================================================================ + # set + # ============================================================================ + def set(self, multiPolygon): + self.removeAll() + for polygon in multiPolygon.defList: + self.append(polygon) + return self + + + def __eq__(self, multiPolygon): + # obbligatoria + """self == other""" + if multiPolygon.whatIs() != "MULTI_POLYGON": return False + if self.qty() != multiPolygon.qty(): return False + for i in range(0, self.qty()): + if self.getPointAt(i) != multiPolygon.getPolygonAt(i): return False + return True + + + def __ne__(self, multiPolygon): + """self != other""" + return not self.__eq__(multiPolygon) + + + # ============================================================================ + # append + # ============================================================================ + def append(self, polygon): + """ + la funzione aggiunge un poligono in fondo alla lista. + """ + if polygon is None: return + if polygon.whatIs() != "POLYGON": return False + self.defList.append(polygon.copy()) + return True + + + # ============================================================================ + # insert + # ============================================================================ + def insert(self, i, polygon): + """ + la funzione aggiunge un poligono nella posizione i-esima della lista dei poligoni. + """ + if i >= self.qty(): + return self.append(polygon) + else: + return self.defList.insert(i, polygon.copy()) + + + # ============================================================================ + # remove + # ============================================================================ + def remove(self, i): + """ + la funzione cancella un poligono nella posizione i-esima della lista. + """ + del self.defList[i] + + + # ============================================================================ + # removeAll + # ============================================================================ + def removeAll(self): + """ + la funzione cancella i poligoni della lista. + """ + del self.defList[:] + + + # ============================================================================ + # getPolygonAt + # ============================================================================ + def getPolygonAt(self, i): + """ + la funzione restituisce il poligono alla posizione i-esima + con numeri negativi parte dal fondo (es. -1 = ultima posizione) + """ + if self.qty() == 0 or i > self.qty() - 1: + return None + return self.defList[i] + + + # ============================================================================ + # setPolygonAt + # ============================================================================ + def setPolygonAt(self, polygon, i): + """ + la funzione setta il poligono i-esimo + """ + return self.getPolygonAt(i).set(polygon) + + + # ============================================================================ + # fromMultiPolygon + # ============================================================================ + def fromMultiPolygon(self, polygonList): + """ + la funzione inizializza una lista di poligoni che compone il multipoligono passato in forma di lista di punti. + """ + self.removeAll() + polygon = QadPolygon() + + for points in polygonList: + # verifico se è un poligono + if polygon.fromPolygon(points): + self.append(polygon) + + if self.qty() == 0: return False + return True + + + # ============================================================================ + # fromGeom + # ============================================================================ + def fromGeom(self, geom): + """ + la funzione inizializza il multipoligono da un oggetto QgsGeometry. + """ + return self.fromMultiPolygon(geom.asMultiPolygon()) + + + # =============================================================================== + # asMultiPolygon + # =============================================================================== + def asMultiPolygon(self, tolerance2ApproxCurve = None, atLeastNSegment = None): + """ + la funzione ritorna una lista di liste di liste di punti che compongono un multipoligono. + """ + result = [] + for polygon in self.defList: + result.append(polygon.asPolygon(tolerance2ApproxCurve, atLeastNSegment)) + + return result + + + # =============================================================================== + # asGeom + # =============================================================================== + def asGeom(self, wkbType = QgsWkbTypes.LineString, tolerance2ApproxCurve = None, atLeastNSegment = None): + """ + la funzione ritorna il multipoligono in forma di QgsGeometry. + """ + flatType = QgsWkbTypes.flatType(wkbType) + + if flatType == QgsWkbTypes.MultiSurface: # Geometry that is combined from several Curvepolygons is called MultiSurface + multiSurface = QgsMultiSurface() + for polygon in self.defList: + multiSurface.addGeometry(polygon.asAbstractGeom(QgsWkbTypes.CurvePolygon, tolerance2ApproxCurve, atLeastNSegment)) + return QgsGeometry(multiSurface) + + return QgsGeometry.fromMultiPolygonXY(self.asMultiPolygon(tolerance2ApproxCurve, atLeastNSegment)) + + + # =============================================================================== + # copy + # =============================================================================== + def copy(self): + # obbligatoria + return QadMultiPolygon(self) + + + # ============================================================================ + # move + # ============================================================================ + def move(self, offsetX, offsetY): + """ + la funzione sposta i poligoni secondo un offset X e uno Y + """ + for polygon in self.defList: + polygon.move(offsetX, offsetY) + + + # ============================================================================ + # rotate + # ============================================================================ + def rotate(self, basePt, angle): + for polygon in self.defList: + polygon.rotate(basePt, angle) + + + # ============================================================================ + # scale + # ============================================================================ + def scale(self, basePt, scale): + for polygon in self.defList: + polygon.scale(basePt, scale) + + + # ============================================================================ + # mirror + # ============================================================================ + def mirror(self, mirrorPt, mirrorAngle): + for polygon in self.defList: + polygon.mirror(mirrorPt, mirrorAngle) + + + # ============================================================================ + # qty + # ============================================================================ + def qty(self): + """ + la funzione restituisce la quantità di poligoni che compongono il multipoligono. + """ + return len(self.defList) + + + # ============================================================================ + # getCentroid + # ============================================================================ + def getCentroid(self, tolerance2ApproxCurve = None): + """ + la funzione restituisce il punto centroide. + """ + g = self.asGeom(QgsWkbTypes.LineString, tolerance2ApproxCurve) + if g is not None: + centroid = g.centroid() + if centroid is not None: + return g.centroid().asPoint() + + return None + + + # =============================================================================== + # transform + # =============================================================================== + def transform(self, coordTransform): + """ + la funzione restituisce un nuovo multipoligono con le coordinate trasformate. + """ + result = QadMultiPolygon() + for polygon in self.defList: + result.append(polygon.transform(coordTransform)) + return result + + + # =============================================================================== + # transformFromCRSToCRS + # =============================================================================== + def transformFromCRSToCRS(self, sourceCRS, destCRS): + """ + la funzione trasforma le coordinate dei punti che compongono il multipoligono. + """ + return self.transform(QgsCoordinateTransform(sourceCRS, destCRS, QgsProject.instance())) + + + # =============================================================================== + # getBoundingBox + # =============================================================================== + def getBoundingBox(self): + """ + la funzione ritorna il rettangolo che racchiude il multi poligono. + """ + boundingBox = self.getPolygonAt(0).getBoundingBox() + i = 1 + while i < self.qty(): + boundingBox.combineExtentWith(self.getPolygonAt(i).getBoundingBox()) + i = i + 1 + + return boundingBox + + + # =============================================================================== + # containsPt + # =============================================================================== + def containsPt(self, pt): + """ + la funzione ritorna True se il punto è sul multi poligono altrimenti False. + """ + for polygon in self.defList: + if polygon.containsPt(pt): return True + + return False + + +# =============================================================================== +# fromQgsGeomtoQadGeom +# =============================================================================== +def fromQgsGeomToQadGeom(QgsGeom, crs = None): + """ + la funzione ritorna una geometria di QAD da una geometria di QGIS e il suo sistema di coordinate. + Le coordinate della geometria di QAD sono quelle del canvas per lavorare con coordinate piane xy + """ + g = QgsGeometry(QgsGeom) + + if crs is None: + g = QgsGeom + else: + # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy + canvasCrs = qgis.utils.iface.mapCanvas().mapSettings().destinationCrs() + if crs != canvasCrs: + coordTransform = QgsCoordinateTransform(crs, canvasCrs, QgsProject.instance()) + g.transform(coordTransform) + + # commentato perchè se ho un poligono e lo divido in 2 parti diventa non valido ma lo si vuole gestire comunque + # a volte la trasformazione di coordinate genera oggetti non validi + #if g.isGeosValid() == False: return None + gType = g.type() + if g.isMultipart() == False: + if gType == QgsWkbTypes.PointGeometry: + qadGeom = QadPoint() + if qadGeom.fromGeom(g): return qadGeom + elif gType == QgsWkbTypes.LineGeometry: + return QadLinearObject.fromGeom(g) + elif gType == QgsWkbTypes.PolygonGeometry: + qadGeom = QadPolygon() + if qadGeom.fromGeom(g): return qadGeom + else: + if gType == QgsWkbTypes.PointGeometry: + qadGeom = QadMultiPoint() + if qadGeom.fromGeom(g): return qadGeom + elif gType == QgsWkbTypes.LineGeometry: + qadGeom = QadMultiLinearObject() + if qadGeom.fromGeom(g): return qadGeom + elif gType == QgsWkbTypes.PolygonGeometry: + qadGeom = QadMultiPolygon() + if qadGeom.fromGeom(g): return qadGeom + + return None + + +# =============================================================================== +# fromQadGeomToQgsGeom +# =============================================================================== +def fromQadGeomToQgsGeom(qadGeom, layer): + """ + la funzione ritorna una geometria di QGIS da una geometria di QAD. + Le coordinate della geometria di QAD sono quelle del canvas per lavorare con coordinate piane xy + """ + g = qadGeom.asGeom(layer.wkbType()) + if g is None: return None + + # trasformo la geometria nel crs del layer (la geometria di QAD è nel sistema del canvas per lavorare con coordinate piane xy) + canvasCrs = qgis.utils.iface.mapCanvas().mapSettings().destinationCrs() + if layer.crs() != canvasCrs: + coordTransform = QgsCoordinateTransform(canvasCrs, layer.crs(), QgsProject.instance()) + g.transform(coordTransform) + + return g + + +# =============================================================================== +# getQadGeomAt +# =============================================================================== +def getQadGeomAt(qadGeom, atGeom = 0, atSubGeom = 0): + """ + la funzione ritorna la geometria alla posizione specificata + """ + qadGeomType = qadGeom.whatIs() + if qadGeomType == "MULTI_POINT": + return None if atSubGeom != 0 else qadGeom.getPointAt(atGeom) + elif qadGeomType == "MULTI_LINEAR_OBJ": + return None if atSubGeom != 0 else qadGeom.getLinearObjectAt(atGeom) + elif qadGeomType == "MULTI_POLYGON": + g = qadGeom.getPolygonAt(atGeom) + if g is None: return None + return g.getClosedObjectAt(atSubGeom) + elif qadGeomType == "POLYGON": + return None if atGeom != 0 else qadGeom.getClosedObjectAt(atSubGeom) + else: + return None if atGeom != 0 or atSubGeom != 0 else qadGeom + + +# =============================================================================== +# getQadGeomPartAt +# =============================================================================== +def getQadGeomPartAt(qadGeom, atGeom = 0, atSubGeom = 0, atPart = 0): + """ + la funzione ritorna la parte della geometria alla posizione specificata + """ + subQadGeom = getQadGeomAt(qadGeom, atGeom, atSubGeom) + if subQadGeom is None: return None + qadSubGeomType = subQadGeom.whatIs() + if qadSubGeomType == "POLYLINE": + return subQadGeom.getLinearObjectAt(atPart) + else: + return subQadGeom + + +# =============================================================================== +# setQadGeomAt +# =============================================================================== +def setQadGeomAt(qadGeom, newGeom, atGeom = 0, atSubGeom = 0): + """ + la funzione retituisce la nuova geometria modificata alla posizione specificata + """ + qadGeomType = qadGeom.whatIs() + if qadGeomType == "MULTI_POINT" or qadGeomType == "MULTI_LINEAR_OBJ": + if atSubGeom != 0: return None + newQadGeom = qadGeom.copy() + newQadGeom.remove(atGeom) + newQadGeom.insert(atGeom, newGeom) + elif qadGeomType == "MULTI_POLYGON": + newQadGeom = qadGeom.copy() + if atSubGeom == 0: + newQadGeom.remove(atGeom) + newQadGeom.insert(atGeom, newGeom) + else: + g = newQadGeom.getPolygonAt(atGeom) + g.remove(atSubGeom) + g.insert(atSubGeom, newGeom) + elif qadGeomType == "POLYGON": + if atGeom != 0: return None + newQadGeom = qadGeom.copy() + newQadGeom.remove(atSubGeom) + newQadGeom.insert(atSubGeom, newGeom) + else: + if atGeom != 0 or atSubGeom != 0: return None + newQadGeom = newGeom + + return newQadGeom + + +# =============================================================================== +# delQadGeomAt +# =============================================================================== +def delQadGeomAt(qadGeom, atGeom = 0, atSubGeom = 0): + """ + la funzione cancella la sotto-geometria alla posizione specificata + """ + qadGeomType = qadGeom.whatIs() + if qadGeomType == "MULTI_POINT": + if atSubGeom != 0: + return False + else: + del qadGeom.defList[atGeom] + return True + elif qadGeomType == "MULTI_LINEAR_OBJ": + if atSubGeom != 0: + return False + else: + del qadGeom.defList[atGeom] + return True + elif qadGeomType == "MULTI_POLYGON": + g = qadGeom.getPolygonAt(atGeom) + if g is None: + return False + del g.defList[atSubGeom] + return True + elif qadGeomType == "POLYGON": + if atGeom != 0: + return False + else: + del qadGeom.defList[atSubGeom] + return True + else: + return False + + +# =============================================================================== +# isLinearQadGeom +# =============================================================================== +def isLinearQadGeom(qadGeom): + """ + la funzione retituisce True se si tratta di una geometria lineare + """ + gType = qadGeom.whatIs() + if gType == "POLYLINE" or gType == "LINE" or gType == "ARC" or gType == "ELLIPSE_ARC": + return True + else: + return False + + +# =============================================================================== +# convertToPolyline +# =============================================================================== +def convertToPolyline(qadGeom): + """ + la funzione trasforma una geometria in QadPolyline, se possibile + """ + gType = qadGeom.whatIs() + if gType != "POLYLINE" and gType != "LINE" and gType != "ARC" and gType != "ELLIPSE_ARC": + return None + if gType == "POLYLINE": + polyline = qadGeom.copy() + else: + polyline = QadPolyline() + polyline.append(qadGeom) + + return polyline + + +# =============================================================================== +# QadGeomBoundingBoxCache class +# classe per cercare velocemente quali parti di una polilinea o poligono o multi oggetto si intersecano con +# un boundingBox. +# =============================================================================== +class QadGeomBoundingBoxCache(): + + def __init__(self, geom): + # creo un layer temporaneo in memoria + self.cacheLayer = createMemoryLayer("QadLayerCacheArea", "Polygon", qgis.utils.iface.mapCanvas().mapSettings().destinationCrs()) + + provider = self.cacheLayer.dataProvider() + provider.addAttributes([QgsField("geom_at", QMetaType.Int, "Int")]) # codice della geometria + provider.addAttributes([QgsField("sub_geom_at", QMetaType.Int, "Int")]) # codice della sotto geometria + provider.addAttributes([QgsField("part_at", QMetaType.Int, "Int")]) # codice della parte + self.cacheLayer.updateFields() + + if provider.capabilities() & QgsVectorDataProvider.CreateSpatialIndex: + provider.createSpatialIndex() + + if self.cacheLayer.startEditing() == False: return + + geomAt = 0 + subGeomAt = 0 + partAt = 0 + error = False + geomType = geom.whatIs() + + if geomType == "MULTI_POINT": + for geomAt in range(0, geom.qty()): + if self.insertBoundingBox(self.getPointAt(geomAt).getBoundingBox(), geomAt, subGeomAt, partAt) == False: + error = True + break + + elif geomType == "MULTI_LINEAR_OBJ": + for geomAt in range(0, geom.qty()): + linearObj = geom.getLinearObjectAt(geomAt) + if linearObj.whatIs() == "POLYLINE": + for partAt in range(0, linearObj.qty()): + part = linearObj.getLinearObjectAt(partAt) + if self.insertBoundingBox(part.getBoundingBox(), geomAt, subGeomAt, partAt) == False: + error = True + break + else: + if self.insertBoundingBox(linearObj.getBoundingBox(), geomAt, subGeomAt, partAt) == False: + error = True + if error: break + + elif geomType == "POLYLINE": + for partAt in range(0, geom.qty()): + part = geom.getLinearObjectAt(partAt) + if self.insertBoundingBox(part.getBoundingBox(), geomAt, subGeomAt, partAt) == False: + error = True + break + + elif geomType == "POLYGON": + for subGeomAt in range(0, geom.qty()): + closedObj = geom.getClosedObjectAt(geomAt) + for partAt in range(0, closedObj.qty()): + part = closedObj.getLinearObjectAt(partAt) + if self.insertBoundingBox(part.getBoundingBox(), geomAt, subGeomAt, partAt) == False: + error = True + break + if error: break + + elif geomType == "MULTI_POLYGON": + for geomAt in range(0, geom.qty()): + polygon = geom.getPolygonAt(geomAt) + for subGeomAt in range(0, polygon.qty()): + closedObj = subGeomAt.getClosedObjectAt(subGeomAt) + for partAt in range(0, closedObj.qty()): + part = closedObj.getLinearObjectAt(partAt) + if self.insertBoundingBox(part.getBoundingBox(), geomAt, subGeomAt, partAt) == False: + error = True + break + if error: break + if error: break + + else: + error = True if self.insertBoundingBox(geom.getBoundingBox(), geomAt, subGeomAt, partAt) == False else False + + if error: + self.cacheLayer.rollBack() + del self.cacheLayer + self.cacheLayer = None + else: + self.cacheLayer.commitChanges() + + + # ============================================================================ + # __del__ + # ============================================================================ + def __del__(self): + del self.cacheLayer + self.cacheLayer = None + + + # ============================================================================ + # insertBoundingBox + # ============================================================================ + def insertBoundingBox(self, boundingBox, geomAt, subGeomAt, partAt): + newFeature = QgsFeature() + newFeature.initAttributes(3) + newFeature.setAttribute(0, geomAt) + newFeature.setAttribute(1, subGeomAt) + newFeature.setAttribute(2, partAt) + newFeature.setGeometry(QgsGeometry().fromRect(boundingBox)) + return self.cacheLayer.addFeature(newFeature) + + + # ============================================================================ + # getIntersectionWithBoundingBox + # ============================================================================ + def getIntersectionWithBoundingBox(self, boundingBox): + request = QgsFeatureRequest() + request.setFilterRect(boundingBox) + request.setSubsetOfAttributes([]) + + feature = QgsFeature() + result = [] + featureIterator = self.cacheLayer.getFeatures(request) + for feature in featureIterator: + geom_at = feature.attribute("geom_at") + sub_geom_at = feature.attribute("sub_geom_at") + part_at = feature.attribute("part_at") + result.append((geom_at, sub_geom_at, part_at)) + + return result + + + # ============================================================================ + # getTotalBoundingBox + # ============================================================================ + def getTotalBoundingBox(self): + feature = QgsFeature() + featureIterator = self.cacheLayer.getFeatures(qad_utils.getFeatureRequest()) + result = None + for feature in featureIterator: + if result is None: + result = feature.geometry().boundingBox() + else: + result.combineExtentWith(feature.geometry().boundingBox()) + return result \ No newline at end of file diff --git a/qad_offset_cmd.py b/qad_offset_cmd.py deleted file mode 100644 index 7beaa425..00000000 --- a/qad_offset_cmd.py +++ /dev/null @@ -1,741 +0,0 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - comando OFFSET per fare l'offset di un oggetto - - ------------------- - begin : 2013-10-04 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -from qad_offset_maptool import * -from qad_generic_cmd import QadCommandClass -from qad_msg import QadMsg -from qad_getpoint import * -from qad_textwindow import * -from qad_entity import * -from qad_variables import * -import qad_utils -import qad_layer -from qad_rubberband import createRubberBand - - -# Classe che gestisce il comando OFFSET -class QadOFFSETCommandClass(QadCommandClass): - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadOFFSETCommandClass(self.plugIn) - - def getName(self): - return QadMsg.translate("Command_list", "OFFSET") - - def getEnglishName(self): - return "OFFSET" - - def connectQAction(self, action): - QObject.connect(action, SIGNAL("triggered()"), self.plugIn.runOFFSETCommand) - - def getIcon(self): - return QIcon(":/plugins/qad/icons/offset.png") - - def getNote(self): - # impostare le note esplicative del comando - return QadMsg.translate("Command_OFFSET", "Creates concentric circles, parallel lines, and parallel curves.") - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - self.entity = QadEntity() - self.subGeom = None - self.subGeomSelectedPt = None - self.offSet = QadVariables.get(QadMsg.translate("Environment variables", "OFFSETDIST")) - self.lastOffSetOnLeftSide = 0 - self.lastOffSetOnRightSide = 0 - self.firstPt = QgsPoint() - self.eraseEntity = False - self.multi = False - self.OnlySegment = False - self.gapType = QadVariables.get(QadMsg.translate("Environment variables", "OFFSETGAPTYPE")) - - self.featureCache = [] # lista di (layer, feature) - self.undoFeatureCacheIndexes = [] # posizioni in featureCache dei punti di undo - self.rubberBand = createRubberBand(self.plugIn.canvas, QGis.Line) - self.rubberBandPolygon = createRubberBand(self.plugIn.canvas, QGis.Polygon) - - def __del__(self): - QadCommandClass.__del__(self) - self.rubberBand.hide() - self.plugIn.canvas.scene().removeItem(self.rubberBand) - self.rubberBandPolygon.hide() - self.plugIn.canvas.scene().removeItem(self.rubberBandPolygon) - - def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): - if (self.plugIn is not None): - if self.PointMapTool is None: - self.PointMapTool = Qad_offset_maptool(self.plugIn) - return self.PointMapTool - else: - return None - - #============================================================================ - # addFeatureCache - #============================================================================ - def addFeatureCache(self, newPt): - featureCacheLen = len(self.featureCache) - layer = self.entity.layer - f = self.entity.getFeature() - - # ritorna una tupla (, - # - # - # ) - dummy = qad_utils.closestSegmentWithContext(newPt, self.subGeom) - if self.offSet < 0: - afterVertex = dummy[2] - pt = qad_utils.getPerpendicularPointOnInfinityLine(self.subGeom.vertexAt(afterVertex - 1), \ - self.subGeom.vertexAt(afterVertex), \ - newPt) - offSetDistance = qad_utils.getDistance(newPt, pt) - else: - offSetDistance = self.offSet - if self.multi == True: - if dummy[3] < 0: # alla sinistra - offSetDistance = offSetDistance + self.lastOffSetOnLeftSide - self.lastOffSetOnLeftSide = offSetDistance - self.getPointMapTool().lastOffSetOnLeftSide = self.lastOffSetOnLeftSide - else: # alla destra - offSetDistance = offSetDistance + self.lastOffSetOnRightSide - self.lastOffSetOnRightSide = offSetDistance - self.getPointMapTool().lastOffSetOnRightSide = self.lastOffSetOnRightSide - - tolerance2ApproxCurve = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) - # uso il crs del canvas per lavorare con coordinate piane xy - epsg = self.plugIn.canvas.mapRenderer().destinationCrs().authid() - lines = qad_utils.offSetPolyline(self.subGeom.asPolyline(), epsg, \ - offSetDistance, \ - "left" if dummy[3] < 0 else "right", \ - self.gapType, \ - tolerance2ApproxCurve) - added = False - for line in lines: - if layer.geometryType() == QGis.Polygon: - if line[0] == line[-1]: # se é una linea chiusa - offsetGeom = QgsGeometry.fromPolygon([line]) - else: - offsetGeom = QgsGeometry.fromPolyline(line) - else: - offsetGeom = QgsGeometry.fromPolyline(line) - - if offsetGeom.type() == QGis.Line or offsetGeom.type() == QGis.Polygon: - offsetFeature = QgsFeature(f) - # trasformo la geometria nel crs del layer - offsetFeature.setGeometry(self.mapToLayerCoordinates(layer, offsetGeom)) - self.featureCache.append([layer, offsetFeature]) - self.addFeatureToRubberBand(layer, offsetFeature) - added = True - - if added: - self.undoFeatureCacheIndexes.append(featureCacheLen) - - - #============================================================================ - # undoGeomsInCache - #============================================================================ - def undoGeomsInCache(self): - tot = len(self.featureCache) - if tot > 0: - iEnd = self.undoFeatureCacheIndexes[-1] - i = tot - 1 - - del self.undoFeatureCacheIndexes[-1] # cancello ultimo undo - while i >= iEnd: - del self.featureCache[-1] # cancello feature - i = i - 1 - self.refreshRubberBand() - - - #============================================================================ - # addFeatureToRubberBand - #============================================================================ - def addFeatureToRubberBand(self, layer, feature): - if layer.geometryType() == QGis.Polygon: - if feature.geometry().type() == QGis.Polygon: - self.rubberBandPolygon.addGeometry(feature.geometry(), layer) - else: - self.rubberBand.addGeometry(feature.geometry(), layer) - else: - self.rubberBand.addGeometry(feature.geometry(), layer) - - - #============================================================================ - # refreshRubberBand - #============================================================================ - def refreshRubberBand(self): - self.rubberBand.reset(QGis.Line) - self.rubberBandPolygon.reset(QGis.Polygon) - for f in self.featureCache: - layer = f[0] - feature = f[1] - if layer.geometryType() == QGis.Polygon: - if feature.geometry().type() == QGis.Polygon: - self.rubberBandPolygon.addGeometry(feature.geometry(), layer) - else: - self.rubberBand.addGeometry(feature.geometry(), layer) - else: - self.rubberBand.addGeometry(feature.geometry(), layer) - - - #============================================================================ - # offsetGeoms - #============================================================================ - def offsetGeoms(self, currLayer): - featuresLayers = [] # lista di (layer, features) - - for f in self.featureCache: - layer = f[0] - feature = f[1] - found = False - for featuresLayer in featuresLayers: - if featuresLayer[0].id() == layer.id(): - found = True - featuresLayer[1].append(feature) - break - # se non c'era ancora il layer - if not found: - featuresLayers.append([layer, [feature]]) - - layerList = [] - for featuresLayer in featuresLayers: - layerList.append(featuresLayer[0]) - - PointTempLayer = None - LineTempLayer = None - PolygonTempLayer = None - self.plugIn.beginEditCommand("Feature offseted", layerList) - - for featuresLayer in featuresLayers: - # filtro le features per tipo - pointGeoms, lineGeoms, polygonGeoms = qad_utils.filterFeaturesByType(featuresLayer[1], \ - currLayer.geometryType()) - # aggiungo le features con geometria del tipo corretto - if currLayer.geometryType() == QGis.Line: - polygonToLines = [] - # Riduco le geometrie in linee - for g in polygonGeoms: - lines = qad_utils.asPointOrPolyline(g) - for l in lines: - if l.type() == QGis.Line: - polygonToLines.append(l) - # plugIn, layer, geoms, coordTransform , refresh, check_validity - if qad_layer.addGeomsToLayer(self.plugIn, currLayer, polygonToLines, None, False, False) == False: - self.plugIn.destroyEditCommand() - return - - del polygonGeoms[:] # svuoto la lista - - # plugIn, layer, features, coordTransform, refresh, check_validity - if qad_layer.addFeaturesToLayer(self.plugIn, currLayer, featuresLayer[1], None, False, False) == False: - self.plugIn.destroyEditCommand() - return - - if pointGeoms is not None and len(pointGeoms) > 0 and PointTempLayer is None: - PointTempLayer = qad_layer.createQADTempLayer(self.plugIn, QGis.Point) - self.plugIn.addLayerToLastEditCommand("Feature offseted", PointTempLayer) - - if lineGeoms is not None and len(lineGeoms) > 0 and LineTempLayer is None: - LineTempLayer = qad_layer.createQADTempLayer(self.plugIn, QGis.Line) - self.plugIn.addLayerToLastEditCommand("Feature offseted", LineTempLayer) - - if polygonGeoms is not None and len(polygonGeoms) > 0 and PolygonTempLayer is None: - PolygonTempLayer = qad_layer.createQADTempLayer(self.plugIn, QGis.Polygon) - self.plugIn.addLayerToLastEditCommand("Feature offseted", PolygonTempLayer) - - # aggiungo gli scarti nei layer temporanei di QAD - # trasformo la geometria in quella dei layer temporanei - # plugIn, pointGeoms, lineGeoms, polygonGeoms, coord, refresh - if qad_layer.addGeometriesToQADTempLayers(self.plugIn, pointGeoms, lineGeoms, polygonGeoms, \ - featuresLayer[0].crs(), False) == False: - self.plugIn.destroyEditCommand() - return - - self.plugIn.endEditCommand() - - - #============================================================================ - # waitForDistance - #============================================================================ - def waitForDistance(self): - # imposto il map tool - self.getPointMapTool().setMode(Qad_offset_maptool_ModeEnum.ASK_FOR_FIRST_OFFSET_PT) - self.getPointMapTool().gapType = self.gapType - - keyWords = QadMsg.translate("Command_OFFSET", "Through") + "/" + \ - QadMsg.translate("Command_OFFSET", "Erase") - if self.offSet < 0: - default = QadMsg.translate("Command_OFFSET", "Through") - else: - default = self.offSet - prompt = QadMsg.translate("Command_OFFSET", "Specify the offset distance or [{0}] <{1}>: ").format(keyWords, str(default)) - - englishKeyWords = "Through" + "/" + "Erase" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o enter o una parola chiave o un numero reale - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT | QadInputTypeEnum.KEYWORDS, \ - default, \ - keyWords, \ - QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) - self.step = 1 - - #============================================================================ - # waitForObjectSel - #============================================================================ - def waitForObjectSel(self): - # imposto il map tool - self.getPointMapTool().setMode(Qad_offset_maptool_ModeEnum.ASK_FOR_ENTITY_SELECTION) - self.lastOffSetOnLeftSide = 0 - self.getPointMapTool().lastOffSetOnLeftSide = self.lastOffSetOnLeftSide - self.lastOffSetOnRightSide = 0 - self.getPointMapTool().lastOffSetOnRightSide = self.lastOffSetOnRightSide - - # "Esci" "ANnulla" - keyWords = QadMsg.translate("Command_OFFSET", "Exit") + "/" + \ - QadMsg.translate("Command_OFFSET", "Undo") - default = QadMsg.translate("Command_OFFSET", "Exit") - prompt = QadMsg.translate("Command_OFFSET", "Select object to offset or [{0}] <{1}>: ").format(keyWords, default) - - englishKeyWords = "Exit" + "/" + "Undo" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o enter o una parola chiave - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - default, \ - keyWords, QadInputModeEnum.NONE) - self.step = 2 - - #============================================================================ - # waitForSidePt - #============================================================================ - def waitForSidePt(self): - # imposto il map tool - self.getPointMapTool().setMode(Qad_offset_maptool_ModeEnum.OFFSET_KNOWN_ASK_FOR_SIDE_PT) - - if self.multi == False: - keyWords = QadMsg.translate("Command_OFFSET", "Exit") + "/" + \ - QadMsg.translate("Command_OFFSET", "Multiple") + "/" + \ - QadMsg.translate("Command_OFFSET", "Undo") - defaultMsg = QadMsg.translate("Command_OFFSET", "Exit") - default = QadMsg.translate("Command_OFFSET", "Exit") - englishKeyWords = "Exit" + "/" + "Multiple" + "/" + "Undo" - else: - keyWords = QadMsg.translate("Command_OFFSET", "Exit") + "/" + \ - QadMsg.translate("Command_OFFSET", "Undo") - defaultMsg = QadMsg.translate("Command_OFFSET", "next object") - default = None - englishKeyWords = "Exit" + "/" + "Undo" - - if self.OnlySegment == False: - keyWords = keyWords + "/" + \ - QadMsg.translate("Command_OFFSET", "Segment") - englishKeyWords = englishKeyWords + "/" + "Segment" - - prompt = QadMsg.translate("Command_OFFSET", "Specify point on side to offset or [{0}] <{1}>: ") - - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o enter o una parola chiave - # msg, inputType, default, keyWords, valore nullo non permesso - self.waitFor(prompt.format(keyWords, defaultMsg), \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - default, \ - keyWords, QadInputModeEnum.NONE) - self.step = 3 - - #============================================================================ - # waitForPassagePt - #============================================================================ - def waitForPassagePt(self): - # imposto il map tool - self.getPointMapTool().setMode(Qad_offset_maptool_ModeEnum.ASK_FOR_PASSAGE_PT) - - if self.multi == False: - keyWords = QadMsg.translate("Command_OFFSET", "Exit") + "/" + \ - QadMsg.translate("Command_OFFSET", "Multiple") + "/" + \ - QadMsg.translate("Command_OFFSET", "Undo") - defaultMsg = QadMsg.translate("Command_OFFSET", "Exit") - default = QadMsg.translate("Command_OFFSET", "Exit") - englishKeyWords = "Exit" + "/" + "Multiple" + "/" + "Undo" - else: - keyWords = QadMsg.translate("Command_OFFSET", "Exit") + "/" + \ - QadMsg.translate("Command_OFFSET", "Undo") - defaultMsg = QadMsg.translate("Command_OFFSET", "next object") - default = None - englishKeyWords = "Exit" + "/" + "Undo" - - if self.OnlySegment == False: - keyWords = keyWords + "/" + \ - QadMsg.translate("Command_OFFSET", "Segment") - englishKeyWords = englishKeyWords + "/" + "Segment" - - prompt = QadMsg.translate("Command_OFFSET", "Specify through point or [{0}] <{1}>: ") - - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o enter o una parola chiave - # msg, inputType, default, keyWords, valore nullo non permesso - self.waitFor(prompt.format(keyWords, defaultMsg), \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - default, \ - keyWords, QadInputModeEnum.NONE) - self.step = 4 - - #============================================================================ - # run - #============================================================================ - def run(self, msgMapTool = False, msg = None): - if self.plugIn.canvas.mapRenderer().destinationCrs().geographicFlag(): - self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) - return True # fine comando - - # il layer corrente deve essere editabile e di tipo linea o poligono - currLayer, errMsg = qad_layer.getCurrLayerEditable(self.plugIn.canvas, [QGis.Line, QGis.Polygon]) - if currLayer is None: - self.showErr(errMsg) - return True # fine comando - - #========================================================================= - # RICHIESTA DISTANZA DI OFFSET - if self.step == 0: # inizio del comando - CurrSettingsMsg = QadMsg.translate("QAD", "\nCurrent settings: ") - CurrSettingsMsg = CurrSettingsMsg + QadMsg.translate("Command_OFFSET", "OFFSETGAPTYPE = ") + str(self.gapType) - if self.gapType == 0: - CurrSettingsMsg = CurrSettingsMsg + QadMsg.translate("Command_OFFSET", " (extends the segments)") - elif self.gapType == 1: - CurrSettingsMsg = CurrSettingsMsg + QadMsg.translate("Command_OFFSET", " (fillets the segments)") - elif self.gapType == 2: - CurrSettingsMsg = CurrSettingsMsg + QadMsg.translate("Command_OFFSET", " (chamfers the segments)") - - self.showMsg(CurrSettingsMsg) - - self.waitForDistance() - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DELLA DISTANZA DI OFFSET (da step = 0) - elif self.step == 1: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - if self.offSet < 0: - value = QadMsg.translate("Command_OFFSET", "Through") - else: - value = self.offSet - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - else: - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("Command_OFFSET", "Through") or value == "Through": - self.offSet = -1 - self.getPointMapTool().offSet = self.offSet - QadVariables.set(QadMsg.translate("Environment variables", "OFFSETDIST"), self.offSet) - QadVariables.save() - # si appresta ad attendere la selezione di un oggetto - self.waitForObjectSel() - elif value == QadMsg.translate("Command_OFFSET", "Erase") or value == "Erase": - keyWords = QadMsg.translate("QAD", "Yes") + "/" + \ - QadMsg.translate("QAD", "No") - - if self.eraseEntity == True: - default = QadMsg.translate("QAD", "Yes") - else: - default = QadMsg.translate("QAD", "No") - prompt = QadMsg.translate("Command_OFFSET", "Erase source object after offsetting ? [{0}] <{1}>: ").format(keyWords, default) - - englishKeyWords = "Yes" + "/" + "No" - keyWords += "_" + englishKeyWords - # si appresta ad attendere enter o una parola chiave - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(prompt, \ - QadInputTypeEnum.KEYWORDS, \ - default, \ - keyWords, QadInputModeEnum.NONE) - self.step = 5 - elif value == QadMsg.translate("Command_OFFSET", "Multiple") or value == "Multiple": - self.multi = True - self.waitForBasePt() - elif type(value) == QgsPoint: # se é stato inserito il primo punto per il calcolo della distanza - self.firstPt.set(value.x(), value.y()) - # imposto il map tool - self.getPointMapTool().firstPt = self.firstPt - self.getPointMapTool().setMode(Qad_offset_maptool_ModeEnum.FIRST_OFFSET_PT_KNOWN_ASK_FOR_SECOND_PT) - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_OFFSET", "Specify second point: ")) - self.step = 6 - elif type(value) == float: - self.offSet = value - self.getPointMapTool().offSet = self.offSet - QadVariables.set(QadMsg.translate("Environment variables", "OFFSETDIST"), self.offSet) - QadVariables.save() - # si appresta ad attendere la selezione di un oggetto - self.waitForObjectSel() - - return False - - #========================================================================= - # RISPOSTA ALLA SELEZIONE DI UN OGGETTO - elif self.step == 2: - entity = None - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - value = QadMsg.translate("Command_OFFSET", "Exit") - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - else: - entity = self.getPointMapTool().entity - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("Command_OFFSET", "Exit") or value == "Exit": - self.offsetGeoms(currLayer) - return True - elif value == QadMsg.translate("Command_OFFSET", "Undo") or value == "Undo": - self.undoGeomsInCache() - # si appresta ad attendere la selezione di un oggetto - self.waitForObjectSel() - elif type(value) == QgsPoint: # se é stato selezionato un punto - if entity is not None and entity.isInitialized(): # se é stata selezionata una entità - self.entity.set(entity.layer, entity.featureId) - self.getPointMapTool().layer = self.entity.layer - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - geom = self.layerToMapCoordinates(entity.layer, entity.getGeometry()) - - # ritorna una tupla (, - # - # - # ) - dummy = qad_utils.closestSegmentWithContext(value, geom) - if dummy[2] is not None: - # ritorna la sotto-geometria al vertice e la sua posizione nella geometria (0-based) - # la posizione é espressa con una lista ( []) - self.subGeom = qad_utils.getSubGeomAtVertex(geom, dummy[2])[0] - self.subGeomSelectedPt = QgsPoint(value) - - self.getPointMapTool().subGeom = self.subGeom - if self.offSet < 0: # richiesta di punto di passaggio - self.waitForPassagePt() - else: # richiesta la parte dell'oggetto - self.waitForSidePt() - else: - # si appresta ad attendere la selezione di un oggetto - self.waitForObjectSel() - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DI UN PUNTO PER STABILIRE LA PARTE DI OFFSET (da step = 2) - elif self.step == 3: - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - if self.multi == False: # default = esci - self.offsetGeoms(currLayer) - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if value is None: # oggetto successivo - # si appresta ad attendere la selezione di un oggetto - self.waitForObjectSel() - else: - if type(value) == unicode: - if value == QadMsg.translate("Command_OFFSET", "Exit") or value == "Exit": - self.offsetGeoms(currLayer) - return True # fine comando - elif value == QadMsg.translate("Command_OFFSET", "Multiple") or value == "Multiple": - self.multi = True - self.waitForSidePt() - elif value == QadMsg.translate("Command_OFFSET", "Undo") or value == "Undo": - self.undoGeomsInCache() - # si appresta ad attendere la selezione di un oggetto - self.waitForObjectSel() - elif value == QadMsg.translate("Command_OFFSET", "Segment") or value == "Segment": - self.OnlySegment = True - linearObject = qad_utils.QadLinearObject() - if linearObject.setByClosestSegmentOfGeom(self.subGeomSelectedPt, self.subGeom) == True: - self.subGeom = QgsGeometry.fromPolyline(linearObject.asPolyline()) - self.getPointMapTool().subGeom = self.subGeom - - self.waitForSidePt() - elif type(value) == QgsPoint: # se é stato selezionato un punto - self.addFeatureCache(value) - if self.multi == False: - # si appresta ad attendere la selezione di un oggetto - self.waitForObjectSel() - else: - # richiesta la parte dell'oggetto - self.waitForSidePt() - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DI UN PUNTO DI PASSAGGIO DI OFFSET (da step = 2) - elif self.step == 4: - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - if self.multi == False: # default = esci - self.offsetGeoms(currLayer) - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if value is None: # oggetto successivo - # si appresta ad attendere la selezione di un oggetto - self.waitForObjectSel() - else: - if type(value) == unicode: - if value == QadMsg.translate("Command_OFFSET", "Exit") or value == "Exit": - self.offsetGeoms(currLayer) - return True # fine comando - elif value == QadMsg.translate("Command_OFFSET", "Multiple") or value == "Multiple": - self.multi = True - self.waitForPassagePt() - elif value == QadMsg.translate("Command_OFFSET", "Undo") or value == "Undo": - self.undoGeomsInCache() - # si appresta ad attendere la selezione di un oggetto - self.waitForObjectSel() - elif value == QadMsg.translate("Command_OFFSET", "Segment") or value == "Segment": - self.OnlySegment = True - linearObject = qad_utils.QadLinearObject() - if linearObject.setByClosestSegmentOfGeom(self.subGeomSelectedPt, self.subGeom) == True: - self.subGeom = QgsGeometry.fromPolyline(linearObject.asPolyline()) - self.getPointMapTool().subGeom = self.subGeom - - self.waitForPassagePt() - elif type(value) == QgsPoint: # se é stato selezionato un punto - self.addFeatureCache(value) - if self.multi == False: - # si appresta ad attendere la selezione di un oggetto - self.waitForObjectSel() - else: - # richiesta di punto di passaggio - self.waitForPassagePt() - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DI CANCELLAZIONE OGGETTO SORGENTE (da step = 1) - elif self.step == 5: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - value = QadMsg.translate("QAD", "No") - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - else: # il valore arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("QAD", "Yes") or value == "Yes": - self.eraseEntity = True - self.waitForDistance() - elif value == QadMsg.translate("QAD", "No") or value == "No": - self.eraseEntity = False - self.waitForDistance() - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA SECONDO PUNTO PER LUNGHEZZA OFFSET (da step = 1) - elif self.step == 6: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if value == self.firstPt: - self.showMsg(QadMsg.translate("QAD", "\nThe value must be positive and not zero.")) - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_OFFSET", "Specify second point: ")) - return False - - self.offSet = qad_utils.getDistance(self.firstPt, value) - self.getPointMapTool().offSet = self.offSet - QadVariables.set(QadMsg.translate("Environment variables", "OFFSETDIST"), self.offSet) - QadVariables.save() - # si appresta ad attendere la selezione di un oggetto - self.waitForObjectSel() - - return False - - \ No newline at end of file diff --git a/qad_offset_fun.py b/qad_offset_fun.py new file mode 100644 index 00000000..9d6143c7 --- /dev/null +++ b/qad_offset_fun.py @@ -0,0 +1,1516 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + funzioni per offset + + ------------------- + begin : 2019-05-20 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.PyQt.QtCore import * +from qgis.PyQt.QtGui import * +from qgis.core import * +from qgis.gui import * +import qgis.utils + +import math + +from . import qad_utils +from .qad_geom_relations import * +from .qad_join_fun import selfJoinPolyline +from .qad_arc import QadArc +from .qad_polyline import * +from .qad_variables import QadVariables + + +# =============================================================================== +# offsetQGSGeom +# =============================================================================== +def offsetQGSGeom(qgsGeom, offsetDistOrPoint, gapType, forcedOffsetDist = None): + """ + la funzione fa l'offset di una geometria di QGIS + + secondo una distanza o un punto (da cui calcolare la distanza) + - per poligoni un valore positivo è verso l'esterno, negativo è verso l'interno + - per linee un valore positivo è verso la sinistra, negativo è verso destra rispetto il verso della linea + + un modo : + 0 = Estende i segmenti di linea alle relative intersezioni proiettate + 1 = Raccorda i segmenti di linea in corrispondenza delle relative intersezioni proiettate. + Il raggio di ciascun segmento di arco é uguale alla distanza di offset + 2 = Cima i segmenti di linea in corrispondenza delle intersezioni proiettate. + La distanza perpendicolare da ciascuna cima al rispettivo vertice + sull'oggetto originale é uguale alla distanza di offset. + + se è un punto e forcedDist <> None + la distanza viene forzata con questo parametro (che deve essere sempre positivo) mentre il lato di offset viene preso dal punto + + La funzione ritorna una lista di geometrie qgis risultato dell'offset + """ + if type(offsetDistOrPoint) == QgsPointXY: + # ritorna una tupla (, + # + # + # ) + dummy = qgsGeom.closestSegmentWithContext(offsetDistOrPoint) + if dummy is None or dummy[0] <= 0: + return [] + if forcedOffsetDist is None: + offsetDist = math.sqrt(dummy[0]) # radice quadrata + else: + offsetDist = forcedOffsetDist + + if offsetDist == 0: + return [] + + leftOf = dummy[3] + if qgsGeom.type() == QgsWkbTypes.LineGeometry: + # se leftOf > 0 il punto è a destra della linea quindi offsetDist deve essere negativo + if leftOf > 0: + offsetDist = -offsetDist + else: + # se il punto è interno alla geometria offsetDist deve essere negativo + if QgsGeometry.fromPointXY(offsetDistOrPoint).within(qgsGeom) == True: + offsetDist = -offsetDist + else: + offsetDist = offsetDistOrPoint + + atLeastNSegment = QadVariables.get(QadMsg.translate("Environment variables", "ARCMINSEGMENTQTY"), 12) + if gapType == 0: + joinStyle = Qgis.JoinStyle.Miter + elif gapType == 1: + joinStyle = Qgis.JoinStyle.Round + elif gapType == 1: + joinStyle = Qgis.JoinStyle.Bevel + + miterLimit= 20 + if qgsGeom.type() == QgsWkbTypes.LineGeometry: + resultGeom = qgsGeom.offsetCurve(offsetDist, atLeastNSegment, joinStyle, miterLimit) + else: + capStyle = Qgis.EndCapStyle.Round + resultGeom = qgsGeom.buffer(offsetDist, atLeastNSegment, capStyle, joinStyle, miterLimit) + + result = [] + if resultGeom is not None: + for part in resultGeom.parts(): + result.append(QgsGeometry.fromWkt(part.asWkt())) # se aggiungo direttamente part poi si pianta qgis + + return result + + +# =============================================================================== +# offsetPolyline +# =============================================================================== +def offsetPolyline(qadGeom, offsetDist, offsetSide, gapType): + """ + la funzione fa l'offset di una geometria QAD + secondo una distanza e un lato di offset ("right" o "left") + ed un modo : + 0 = Estende i segmenti di linea alle relative intersezioni proiettate + 1 = Raccorda i segmenti di linea in corrispondenza delle relative intersezioni proiettate. + Il raggio di ciascun segmento di arco é uguale alla distanza di offset + 2 = Cima i segmenti di linea in corrispondenza delle intersezioni proiettate. + La distanza perpendicolare da ciascuna cima al rispettivo vertice + sull'oggetto originale é uguale alla distanza di offset. + + La funzione ritorna una lista di geometrie qad risultato dell'offset + """ + result = [] + + linearObj = qadGeom.copy() # ne faccio una copia + gType = linearObj.whatIs() + if gType == "CIRCLE": + # se offsetSide = "right" significa verso l'esterno del cerchio + # se offsetSide = "left" significa verso l'interno del cerchio + if offsetSide == "left": + # offset verso l'interno del cerchio + if linearObj.offset(offsetDist, "internal") == True: result.append(linearObj) + else: + # offset verso l'esterno del cerchio + if linearObj.offset(offsetDist, "external") == True: result.append(linearObj) + elif gType == "ELLIPSE": + # l'offset di una ellisse non è una ellisse + # se offsetSide = "right" significa verso l'esterno dell'ellisse + # se offsetSide = "left" significa verso l'interno dell'ellisse + if offsetSide == "left": + # offset verso l'interno dell'ellisse + pts = linearObj.offset(offsetDist, "internal") + else: + # offset verso l'esterno dell'ellisse + pts = linearObj.offset(offsetDist, "external") + + if pts is not None: + polyline = QadPolyline() + polyline.fromPolyline(pts) + result.append(polyline) + elif gType == "LINE" or gType == "ARC": + if linearObj.offset(offsetDist, offsetSide) == True: result.append(linearObj) + elif gType == "ELLIPSE_ARC": + # l'offset di una ellisse non è una ellisse + pts = linearObj.offset(offsetDist, offsetSide) + if pts is not None: + polyline = QadPolyline() + polyline.fromPolyline(pts) + result.append(polyline) + elif gType == "POLYLINE": + # attualmente gli archi di ellisse in una polilinea non sono supportati quindi li trasformo in segmenti + if linearObj.segmentizeEllipseArcs() == False: + return [] + # ottengo la polilinea di offset non tagliata + untrimmedOffsetPolyline = getUntrimmedOffSetPolyline(linearObj, offsetDist, offsetSide, gapType) + # test + #return [untrimmedOffsetPolyline] + + # inverto il senso dei punti x ottenere la polilinea di offset non tagliata invertita + reversedPolyline = linearObj.copy() # duplico la polilinea + reversedPolyline.reverse() + untrimmedReversedOffsetPolyline = getUntrimmedOffSetPolyline(reversedPolyline, offsetDist, offsetSide, gapType) + + # test + #return [untrimmedReversedOffsetPolyline] + + # taglio la polilinea dove necessario + result = getTrimmedOffSetPolyline(linearObj, \ + untrimmedOffsetPolyline, \ + untrimmedReversedOffsetPolyline, \ + offsetDist) + + return result + + + +# =============================================================================== +# dualClipping +# =============================================================================== +def dualClipping(polyline, untrimmedOffsetPolyline, untrimmedReversedOffsetPolyline, offsetDist): + """ + la funzione effettua il dual clipping su untrimmedOffsetPolyline. + : lista delle parti originali della polilinea + : lista delle parti non tagliate derivate dall'offset + : lista delle parti non tagliate derivate dall'offset in senso inverso + + La funzione ritorna una lista di parti risultato del dual clipping + """ + + # inizio Dual Clipping + + # Calcolo i punti di intersezione tra e + intPtList = getIntersectionPointsWithPolyline(untrimmedOffsetPolyline, untrimmedReversedOffsetPolyline) + # Calcolo i punti di self intersection di + intSelfPtList = getSelfIntersectionPoints(untrimmedOffsetPolyline) + """ + Le 2 funzioni precedenti restituiscono una lista in cui ogni elemento è una sottolista composta da: + 1) punto di intersezione della polilinea con se stessa + 2) distanza del punti di intersezione dall'inizio della polilinea + 3) numero della parte contenente il punto di intersezione + """ + # Aggiungo alla lista di punti di intersezione quelli di tipo self intersection senza duplicare i punti + # usando come chiave univoca le coordinate del punto e il numero di parte su cui si trova il punto + lenOriginalIntPtList = len(intPtList) + for intSelfPt in intSelfPtList: + found = False + for i in range(lenOriginalIntPtList): + intPt = intPtList[i] + # se i punti sono così vicini da essere considerati uguali e si tratta della stessa parte + if qad_utils.ptNear(intPt[0], intSelfPt[0]) == True and \ + intPt[2] == intSelfPt[2]: + found = True + + if found == False: + intPtList.append(intSelfPt) + + # se non ci sono punti di intersezione nè quelli di tipo self intersection + if len(intPtList) == 0: + return untrimmedOffsetPolyline.defList + + # ordino i punti di intersezione per distanza dall'inizio di untrimmedOffsetPolyline + intPtListOrderedByDistFromStart = [] + for intPt in intPtList: + insertAt = 0 + for intPtOrderedByDistFromStart in intPtListOrderedByDistFromStart: + if intPtOrderedByDistFromStart[1] < intPt[1]: + insertAt = insertAt + 1 + else: + break + intPtListOrderedByDistFromStart.insert(insertAt, intPt) + + # genero una nuova lista con le parti con intersezioni divise in tante sottoparti + splittedPolyline = [] + for iPart in range(len(untrimmedOffsetPolyline.defList)): + part = untrimmedOffsetPolyline.getLinearObjectAt(iPart) + # verifico se la parte ha delle intersezioni + subPartList = [] + for intPt in intPtListOrderedByDistFromStart: + if (intPt[2] == iPart): # se l'intersezione si riferisce alla parte interessata + if len(subPartList) == 0: # prima intersezione per questa parte + subPartList.append(part.getGeomBetween2Pts(part.getStartPt(), intPt[0])) + else: # dalla seconda intersezione in poi + subPartList.append(part.getGeomBetween2Pts(lastIntPt[0], intPt[0])) + lastIntPt = intPt + else: + if intPt[2] > iPart: # non continuo con le intersezioni delle parti successive + break + if len(subPartList) > 0: # se la parte è stata divisa in sotto-parti + subPartList.append(part.getGeomBetween2Pts(lastIntPt[0], part.getEndPt())) + splittedPolyline.extend(subPartList) + else: + splittedPolyline.append(part) + + # test + #return splittedPolyline + + dualClippedPartList = [] + partListForClipByCircle = [] # lista di parti che devono essere sottoposte al taglio con un cerchio + #nPenultimaParte = polyline.qty() - 2 # 0-indexed + circle = QadCircle() + + # per tutte le parti di splittedPolyline + for part in splittedPolyline: + # calcolo le intersezioni della parte con tutte le parti di (polilinea orginale) + dummyPolyline = QadPolyline() + dummyPolyline.append(part) + intPtList = getIntersectionPointsWithPolyline(polyline, dummyPolyline) + # se non ci sono punti di intersezione aggiungo questa parte a dualClippedPartList + if len(intPtList) == 0: + dualClippedPartList.append(part) + else: # se esistono punti di intersezione + # verifico se tutti i punti di intersezione non sono sul primo segmento o sul penultimo segmento di + reject = True + for intPt in intPtList: + if intPt[2] == 0 or intPt[2] == polyline.qty() - 1: # se almeno un punto di intersezione è sul primo o sull'ultimo segmento + reject = False + break + + if reject == False: + # attengo le parti di che sono esterne a tutti i cerchi + # con centro = i punti di intersezione e raggio = la distanza di offset + partListForClipByCircle = [part] + for intPt in intPtList: + # Costruisco un cerchio il cui centro è il punto di intersezione e il raggio è la distanza di offset + circle.set(intPt[0], offsetDist) + + i = 0 + while i < len(partListForClipByCircle): + externalPartsOfIntPt = getPartsExternalToCircle(partListForClipByCircle[i], circle) + del partListForClipByCircle[i] + for externalPartOfIntPt in externalPartsOfIntPt.defList: + partListForClipByCircle.insert(i, externalPartOfIntPt) + i = i + 1 + + for partForClipByCircle in partListForClipByCircle: + dualClippedPartList.append(partForClipByCircle) + + return dualClippedPartList + + +# =============================================================================== +# generalClosedPointPairClipping +# =============================================================================== +def generalClosedPointPairClipping(polyline, dualClippedPolyline, offsetDist): + """ + la funzione effettua il general closed point pair clipping su dualClippedPolyline. + : lista delle parti originali della polilinea + : lista delle parti risultato del dual clipping + distanza di offset + + Per ogni parte della polilinea originale cerco qual'è il punto più vicino per ogni + parte di dualClippedPolyline. Se questo punto è più vicino di offsetDist allora faccio + un cerchio con centro il punto della polilinea originale e cancello il + pezzo di segmento di dualClippedPolyline iterno al cerchio. Questo per eliminare i pezzi di + dualClippedPolyline più vicino di offsetDist a polyline. + + La funzione ritorna una lista di parti risultato del general closed point pair clipping + """ + # inizio di General Closed Point Pair clipping + GCPPCList = QadPolyline(dualClippedPolyline) # duplico la lista di parti + circle = QadCircle() + +# # per ogni parte di polyline +# for part in polyline.defList: +# # per ogni parte di GCPPCList +# i = 0 +# while i < GCPPCList.qty(): +# GCPPCPart = GCPPCList.getLinearObjectAt(i) +# # verifico quale é il punto di part più vicino a GCPPCPart +# # la funzione ritorna +# MinDistancePts = QadMinDistance.fromTwoBasicGeomObjects(part, GCPPCPart) +# # se la distanza é inferiore a offsetDist (e non così vicina da essere considerata uguale) +# if qad_utils.doubleSmaller(MinDistancePts[0], offsetDist): +# # creo un cerchio nel punto di part più vicino a GCPPCPart +# circle.set(MinDistancePts[1], offsetDist) +# # ottengo le parti di GCPPCPart esterne al cerchio +# splittedParts = getPartsExternalToCircle(GCPPCPart, circle) +# # se la splittedParts è composta da una sola parte che è uguale a GCPPCPart +# # ad es. se GCPPCPart è tangente al cerchio allora non faccio niente +# if splittedParts.qty() == 1 and splittedParts.getLinearObjectAt(0) == GCPPCPart: +# i = i + 1 +# else: +# # le sostituisco a GCPPCPart +# GCPPCList.remove(i) +# for splittedPart in splittedParts.defList: +# GCPPCList.insert(i, splittedPart) +# i = i + 1 +# else: +# i = i + 1 + +# GCPPCList = QadPolyline() +# circle = QadCircle() +# +# # per ogni parte di GCPPCList cerco la coppia dei punti più vicini con +# for part in dualClippedPolyline: +# """ +# la funzione ritorna +# +# +# +# +# +# +# +# +# +# dei 2 oggetti geometrici. +# """ +# for origPart in polyline.defList: +# MinDistancePts = QadMinDistance.fromTwoGeomObjects(part, origPart) +# # se la distanza é inferiore a offsetDist (per precisione dei calcoli potrebbe essere molto vicina quindi accetto una tolleranza) +# if qad_utils.doubleSmaller(MinDistancePts[0], offsetDist) == True: +# # creo un cerchio nel punto di polyline più vicino a part +# circle.set(MinDistancePts[5], offsetDist) +# # ottengo le parti di part esterne al cerchio +# splittedParts = getPartsExternalToCircle(part, circle) +# for splittedPart in splittedParts.defList: +# GCPPCList.append(splittedPart) +# else: +# GCPPCList.append(part) + + GCPPCList = QadPolyline() + circle = QadCircle() + + # per ogni parte di dualClippedPolyline cerco la coppia dei punti più vicini con + for part in dualClippedPolyline: + """ + la funzione ritorna + + + + """ + splittedParts = [part] + for origPart in polyline.defList: + while True: + i = 0 + splitted = False + while i < len(splittedParts): + splittedPart = splittedParts[i] + MinDistancePts = QadMinDistance.fromTwoBasicGeomObjects(splittedPart, origPart) + # se la distanza é inferiore a offsetDist (per precisione dei calcoli potrebbe essere molto vicina quindi accetto una tolleranza) + if qad_utils.doubleSmaller(MinDistancePts[0], offsetDist) == True: + splitted = True + # creo un cerchio nel punto di polyline più vicino a part + circle.set(MinDistancePts[2], offsetDist) + # ottengo le parti di parti esterne al cerchio + outsideParts = getPartsExternalToCircle(splittedPart, circle) + # le sostituisco a splittedPart + del splittedParts[i] + for outsidePart in outsideParts.defList: + splittedParts.insert(i, outsidePart) + i = i + 1 + else: + i = i + 1 + + if splitted == False: + break + + for splittedPart in splittedParts: + GCPPCList.append(splittedPart) + + return GCPPCList + + +# =============================================================================== +# getTrimmedOffSetPolyline +# =============================================================================== +def getTrimmedOffSetPolyline(polyline, untrimmedOffsetPolyline, untrimmedReversedOffsetPolyline, \ + offsetDist): + """ + la funzione taglia la polilinea dove necessario usando e . + : lista delle parti originali della polilinea + : lista delle parti non tagliate derivate dall'offset + : lista delle partinon tagliate derivate dall'offset in senso inverso + distanza di offset + + La funzione ritorna una lista di parti della polilinee (lista di segmenti o archi o archi di ellisse) + """ + + # faccio il dual clipping + dualClippedPolyline = dualClipping(polyline, untrimmedOffsetPolyline, untrimmedReversedOffsetPolyline, offsetDist) + # test + #return dualClippedPolyline + #GCPPCList = untrimmedOffsetPolyline + #GCPPCList = dualClipping(polyline, untrimmedOffsetPolyline, untrimmedReversedOffsetPolyline, offsetDist) + + # faccio il general closed point pair clipping + GCPPCList = generalClosedPointPairClipping(polyline, dualClippedPolyline, offsetDist) + # test + #return GCPPCList.defList + + # faccio il join tra le parti + return selfJoinPolyline(GCPPCList) + + +# =============================================================================== +# getUntrimmedOffSetPolyline +# =============================================================================== +def getUntrimmedOffSetPolyline(polyline, offsetDist, offsetSide, gapType): + """ + la funzione fa l'offset non pulito da eventuali tagli da apportare (vedi + getTrimmedOffSetPolyline") di una polilinea + secondo una distanza e un lato di offset ("right" o "left") + ed un modo : + 0 = Estende i segmenti di linea alle relative intersezioni proiettate + 1 = Raccorda i segmenti di linea in corrispondenza delle relative intersezioni proiettate. + Il raggio di ciascun segmento di arco é uguale alla distanza di offset + 2 = Cima i segmenti di linea in corrispondenza delle intersezioni proiettate. + La distanza perpendicolare da ciascuna cima al rispettivo vertice + sull'oggetto originale é uguale alla distanza di offset. + + La funzione ritorna una polilinea le cui parti non sono collegate + """ + # verifico se polilinea chiusa + isClosedPolyline = polyline.isClosed() + + # creo una lista dei segmenti e archi che formano la polilinea + polyline = preTreatmentOffset(polyline) + + # faccio l'offset di ogni parte della polilinea + newPolyline = QadPolyline() + i = 0 + while i < polyline.qty(): + part = polyline.getLinearObjectAt(i) + gType = part.whatIs() + if gType == "LINE": # segmento + newPart = part.copy() + newPart.offset(offsetDist, offsetSide) + newPolyline.append(newPart) + elif gType == "ARC": # arco + newPart = part.copy() + if newPart.offset(offsetDist, offsetSide) == True: + newPolyline.append(newPart) + elif gType == "ELLIPSE_ARC": # arco di ellisse + pts = part.offset(offsetDist, offsetSide) + if pts is not None: + offsetEllipseArc = QadPolyline() + if offsetEllipseArc.fromPolyline(pts) == True: + newPolyline.appendPolyline(offsetEllipseArc) + del pts + + i = i + 1 + + # test fino qui OK + # return newPolyline + + # calcolo i punti di intersezione tra parti adiacenti + # per ottenere una linea di offset non tagliata + if isClosedPolyline == True: + i = -1 + else: + i = 0 + + untrimmedOffsetPolyline = QadPolyline() + virtualPartPositionList = [] + while i < newPolyline.qty() - 1: + if i == -1: # polylinea chiusa quindi prendo in esame l'ultimo segmento e il primo + part = newPolyline.getLinearObjectAt(-1) # ultima parte + nextPart = newPolyline.getLinearObjectAt(0) # prima parte + else: + part = newPolyline.getLinearObjectAt(i) + nextPart = newPolyline.getLinearObjectAt(i + 1) + + if untrimmedOffsetPolyline.qty() == 0: + lastUntrimmedOffsetPt = part.getStartPt() + else: + lastUntrimmedOffsetPt = untrimmedOffsetPolyline.getLinearObjectAt(-1).getEndPt() # ultima parte + + IntPointInfo = getIntersectionPointInfoOffset(part, nextPart) + if IntPointInfo is not None: # se c'é un'intersezione + IntPoint = IntPointInfo[0] + IntPointTypeForPart = IntPointInfo[1] + IntPointTypeForNextPart = IntPointInfo[2] + + if part.whatIs() == "LINE": # segmento + if nextPart.whatIs() == "LINE": # segmento-segmento + if IntPointInfo is not None: # se esiste un punto di intersezione + if IntPointTypeForPart == 1: # TIP + if IntPointTypeForNextPart == 1: # TIP + untrimmedOffsetPolyline.append(QadLine().set(lastUntrimmedOffsetPt, IntPoint)) + else: # FIP + untrimmedOffsetPolyline.append(QadLine().set(lastUntrimmedOffsetPt, part.getEndPt())) + untrimmedOffsetPolyline.append(QadLine().set(part.getEndPt(), nextPart.getStartPt())) + # aggiungo la posizione di questa parte virtuale + virtualPartPositionList.append(untrimmedOffsetPolyline.qty() - 1) + else: # FIP + if IntPointTypeForPart == 3: # PFIP + if gapType != 0: + newLines = offsetBridgeTheGapBetweenLines(part, nextPart, offsetDist, gapType) + untrimmedOffsetPolyline.append(newLines[0]) + untrimmedOffsetPolyline.append(newLines[1]) # arco o linea di raccordo + else: + untrimmedOffsetPolyline.append(QadLine().set(lastUntrimmedOffsetPt, IntPoint)) + else: # NFIP + untrimmedOffsetPolyline.append(QadLine().set(lastUntrimmedOffsetPt, part.getEndPt())) + untrimmedOffsetPolyline.append(QadLine().set(part.getEndPt(), nextPart.getStartPt())) + # aggiungo la posizione di questa parte virtuale + virtualPartPositionList.append(untrimmedOffsetPolyline.qty() - 1) + + elif nextPart.whatIs() == "ARC": # segmento-arco + if IntPointInfo is not None: # se esiste un punto di intersezione + if IntPointTypeForPart == 1: # TIP + if IntPointTypeForNextPart == 1: # TIP + untrimmedOffsetPolyline.append(QadLine().set(lastUntrimmedOffsetPt, IntPoint)) + else: # FIP + untrimmedOffsetPolyline.append(QadLine().set(lastUntrimmedOffsetPt, part.getEndPt())) + untrimmedOffsetPolyline.append(QadLine().set(part.getEndPt(), nextPart.getStartPt())) + # aggiungo la posizione di questa parte virtuale + virtualPartPositionList.append(untrimmedOffsetPolyline.qty() - 1) + else: # FIP + if IntPointTypeForPart == 3: # PFIP + if IntPointTypeForNextPart == 2: # FIP + untrimmedOffsetPolyline.append(QadLine().set(lastUntrimmedOffsetPt, part.getEndPt())) + newPart = fillet2PartsOffset(part, nextPart, offsetSide, offsetDist) + if newPart is not None: + untrimmedOffsetPolyline.append(newPart) + elif IntPointTypeForNextPart == 1: # TIP + untrimmedOffsetPolyline.append(QadLine().set(lastUntrimmedOffsetPt, IntPoint)) + else: # NFIP + if IntPointTypeForNextPart == 1: # TIP + untrimmedOffsetPolyline.append(QadLine().set(lastUntrimmedOffsetPt, part.getEndPt())) + untrimmedOffsetPolyline.append(QadLine().set(part.getEndPt(), nextPart.getStartPt())) + # aggiungo la posizione di questa parte virtuale + virtualPartPositionList.append(untrimmedOffsetPolyline.qty() - 1) + else: # non esiste un punto di intersezione + untrimmedOffsetPolyline.append(QadLine().set(lastUntrimmedOffsetPt, part.getEndPt())) + newPart = fillet2PartsOffset(part, nextPart, offsetSide, offsetDist) + if newPart is not None: + untrimmedOffsetPolyline.append(newPart) + elif part.whatIs() == "ARC": # arco + if nextPart.whatIs() == "LINE": # arco-segmento + if IntPointInfo is not None: # se esiste un punto di intersezione + if IntPointTypeForPart == 1: # TIP + newPart = part.copy() + newPart.setStartPt(lastUntrimmedOffsetPt) # modifico l'arco + newPart.setEndPt(IntPoint) # modifico l'arco + untrimmedOffsetPolyline.append(newPart) + + if IntPointTypeForNextPart != 1: # TIP + untrimmedOffsetPolyline.append(QadLine().set(IntPoint, nextPart.getStartPt())) + # aggiungo la posizione di questa parte virtuale + virtualPartPositionList.append(untrimmedOffsetPolyline.qty() - 1) + + else: # FIP + newPart = part.copy() + newPart.setStartPt(lastUntrimmedOffsetPt) # modifico l'arco + untrimmedOffsetPolyline.append(newPart) + + if IntPointTypeForNextPart == 4: # NFIP + newPart = fillet2PartsOffset(part, nextPart, offsetSide, offsetDist) + if newPart is not None: + untrimmedOffsetPolyline.append(newPart) + elif IntPointTypeForNextPart == 1: # TIP + untrimmedOffsetPolyline.append(QadLine().set(part.getEndPt(), nextPart.getStartPt())) + # aggiungo la posizione di questa parte virtuale + virtualPartPositionList.append(untrimmedOffsetPolyline.qty() - 1) + else: # non esiste un punto di intersezione + newPart = part.copy() + newPart.setStartPt(lastUntrimmedOffsetPt) # modifico l'arco + untrimmedOffsetPolyline.append(newPart) + newPart = fillet2PartsOffset(part, nextPart, offsetSide, offsetDist) + if newPart is not None: + untrimmedOffsetPolyline.append(newPart) + + elif nextPart.whatIs() == "ARC": # arco-arco + if IntPointInfo is not None: # se esiste un punto di intersezione + if IntPointTypeForPart == 1: # TIP + if IntPointTypeForNextPart == 1: # TIP + newPart = part.copy() + newPart.setStartPt(lastUntrimmedOffsetPt) # modifico l'arco + newPart.setEndPt(IntPoint) # modifico l'arco + untrimmedOffsetPolyline.append(newPart) + else : # FIP + newPart = part.copy() + newPart.setStartPt(lastUntrimmedOffsetPt) # modifico l'arco + untrimmedOffsetPolyline.append(newPart) + + if part.reversed == False: + center = qad_utils.getPolarPointByPtAngle(part.center, part.endAngle, part.radius - offsetDist) + else: + center = qad_utils.getPolarPointByPtAngle(part.center, part.startAngle, part.radius - offsetDist) + + secondPtNewArc = qad_utils.getPolarPointByPtAngle(center, \ + qad_utils.getAngleBy2Pts(center, IntPoint), \ + offsetDist) + newArc = QadArc() + newArc.fromStartSecondEndPts(part.getEndPt(), \ + secondPtNewArc, \ + nextPart.getStartPt()) + + untrimmedOffsetPolyline.append(newArc) + # aggiungo la posizione di questa parte virtuale + virtualPartPositionList.append(untrimmedOffsetPolyline.qty() - 1) + else: # FIP + if IntPointTypeForNextPart == 1: # TIP + newPart = part.copy() + newPart.setStartPt(lastUntrimmedOffsetPt) # modifico l'arco + untrimmedOffsetPolyline.append(newPart) + + if reversed == False: + center = qad_utils.getPolarPointByPtAngle(part.center, part.endAngle, part.radius - offsetDist) + else: + center = qad_utils.getPolarPointByPtAngle(part.center, part.startAngle, part.radius - offsetDist) + + secondPtNewArc = qad_utils.getPolarPointByPtAngle(center, \ + qad_utils.getAngleBy2Pts(center, IntPoint), \ + offsetDist) + newArc = QadArc() + newArc.fromStartSecondEndPts(part.getEndPt(), \ + secondPtNewArc, \ + nextPart.getStartPt()) + untrimmedOffsetPolyline.append(newArc) + # aggiungo la posizione di questa parte virtuale + virtualPartPositionList.append(untrimmedOffsetPolyline.qty() - 1) + else: # FIP + newPart = part.copy() + newPart.setStartPt(lastUntrimmedOffsetPt) # modifico l'arco + newPart.setEndPt(IntPoint) # modifico l'arco + untrimmedOffsetPolyline.append(newPart) + else: # non esiste un punto di intersezione + newPart = part.copy() + newPart.setStartPt(lastUntrimmedOffsetPt) # modifico l'arco + untrimmedOffsetPolyline.append(newPart) + + # prima di raccordare verifico se l'arco si trova interamente dentro la zona di offset + # dell'arco e viceversa. + # Per replicare questa eccezione fare una polilinea composta da 2 archi: + # il primo con centro in ..., raggio..., angolo iniziale ... angolo finale ... + # il secondo con centro in ..., raggio..., angolo iniziale ... angolo finale ... + # offset a destra = 8 + dist = qad_utils.getDistance(part.center, nextPart.center) + minDistArc, maxDistArc = getOffsetDistancesFromCenterOnOffsetedArc(part, offsetDist, offsetSide) + minDistNextArc, maxDistNextArc = getOffsetDistancesFromCenterOnOffsetedArc(nextPart, offsetDist, offsetSide) + + if (dist + nextPart.radius <= maxDistArc and dist - nextPart.radius >= minDistArc) or \ + (dist + part.radius <= maxDistNextArc and dist - part.radius >= minDistNextArc): + untrimmedOffsetPolyline.append(QadLine().set(newPart.getEndPt(), nextPart.getStartPt())) + else: + newPart = fillet2PartsOffset(part, nextPart, offsetSide, offsetDist) + if newPart is not None: + untrimmedOffsetPolyline.append(newPart) + + i = i + 1 + + if newPolyline.qty() > 0: + if isClosedPolyline == False: + if untrimmedOffsetPolyline.qty() == 0: + # primo punto della prima parte di newPolyline + lastUntrimmedOffsetPt = newPolyline.getLinearObjectAt(0).getStartPt() + else: + # ultimo punto dell'ultima parte di untrimmedOffsetPolyline + lastUntrimmedOffsetPt = untrimmedOffsetPolyline.getLinearObjectAt(-1).getEndPt() + + newPart = newPolyline.getLinearObjectAt(-1).copy() + newPart.setStartPt(lastUntrimmedOffsetPt) # modifico l'inizio + untrimmedOffsetPolyline.append(newPart) + else: + # primo punto = ultimo punto + untrimmedOffsetPolyline.getLinearObjectAt(0).setStartPt(untrimmedOffsetPolyline.getLinearObjectAt(-1).getEndPt()) # modifico l'inizio + + # faccio un pre-clipping sulle parti virtuali + #return virtualPartClipping(untrimmedOffsetPolyline, virtualPartPositionList) + # test + return untrimmedOffsetPolyline + + +# =============================================================================== +# preTreatmentOffset +# =============================================================================== +def preTreatmentOffset(polyline): + """ + la funzione controlla le "local self intersection"> : + se il segmento (o arco o arco di ellisse) i-esimo e il successivo hanno 2 intersezioni allora si inserisce un vertice + nel segmento (o arco o arco di ellisse) i-esimo tra i 2 punti di intersezione. + La funzione riceve una lista di segmenti, archi ed archi di ellisse e ritorna una nuova lista di parti + """ + # verifico se polilinea chiusa + i = -1 if polyline.isClosed() else 0 + + result = QadPolyline() + while i < polyline.qty() - 1: + if i == -1: # polilinea chiusa quindi prendo in esame l'ultimo segmento e il primo + part = polyline.getLinearObjectAt(-1) + nextPart = polyline.getLinearObjectAt(0) + else: + part = polyline.getLinearObjectAt(i) + nextPart = polyline.getLinearObjectAt(i + 1) + + ptIntList = QadIntersections.twoBasicGeomObjects(part, nextPart) + if len(ptIntList) == 2: # 2 punti di intersezione + # calcolo il punto medio tra i 2 punti di intersezione in part + gType = part.whatIs() + if gType == "LINE": # segmento + ptMiddle = qad_utils.getMiddlePoint(ptIntList[0], ptIntList[1]) + result.append(QadLine().set(part.getStartPt(), ptMiddle)) + result.append(QadLine().set(ptMiddle, part.getEndPt())) + elif gType == "ARC": # arco + arc1 = part.copy() + arc2 = part.copy() + # se i punti sono così vicini da essere considerati uguali + if qad_utils.ptNear(part.getEndPt(), ptIntList[0]): + ptInt = part.getEndPt() + else: + ptInt = part.getStartPt() + + arc1.setEndPt(ptInt) + arc2.setStartPt(ptInt) + result.append(arc1) + result.append(arc2) + else: # un solo punto di intersezione + result.append(part) + + i = i + 1 + + if polyline.isClosed() == False: # se non é chiusa aggiungo l'ultima parte + if polyline.qty() > 1: + result.append(nextPart) + else: + result.append(polyline.getLinearObjectAt(0)) + + return result + + +# =============================================================================== +# getIntersectionPointInfoOffset +# =============================================================================== +def getIntersectionPointInfoOffset(part, nextPart): + """ + la funzione restituisce il punto di intersezione tra le 2 parti e + e il tipo di intersezione per e per . + Alle parti deve essere già stato fatto l'offset singolarmente: + + 1 = TIP (True Intersection Point) se il punto di intersezione ottenuto estendendo + le 2 parti si trova su + + 2 = FIP (False Intersection Point) se il punto di intersezione ottenuto estendendo + le 2 parti non si trova su + + 3 = PFIP (Positive FIP) se il punto di intersezione é nella stessa direzione di part + + 4 = NFIP (Negative FIP) se il punto di intersezione é nella direzione opposta di part + """ + + ptIntList = QadIntersections.twoBasicGeomObjectExtensions(part, nextPart) + + if len(ptIntList) == 0: + if qad_utils.ptNear(part.getEndPt(), nextPart.getStartPt()) == True: + #if part.getEndPt() == nextPart.getStartPt(): # inizia dove finisce + return [part.getEndPt(), 1, 1] # TIP-TIP + else: + return None + elif len(ptIntList) == 1: + gType = part.whatIs() + if gType == "LINE": # segmento + if part.containsPt(ptIntList[0]): + intTypePart = 1 # TIP + else: # l'intersezione non é sul segmento (FIP) + # se la direzione é la stessa del segmento + if qad_utils.doubleNear(qad_utils.getAngleBy2Pts(part.getStartPt(), part.getEndPt()), \ + qad_utils.getAngleBy2Pts(part.getStartPt(), ptIntList[0])): + intTypePart = 3 # PFIP + else: + intTypePart = 4 # NFIP + else: # arco o arco di ellisse + if part.containsPt(ptIntList[0]): + intTypePart = 1 # TIP + else: + intTypePart = 2 # FIP + + gType = nextPart.whatIs() + if gType == "LINE": # segmento + if nextPart.containsPt(ptIntList[0]): + intTypeNextPart = 1 # TIP + else: # l'intersezione non é sul segmento (FIP) + # se la direzione é la stessa del segmento + if qad_utils.doubleNear(qad_utils.getAngleBy2Pts(nextPart.getStartPt(), nextPart.getEndPt()), \ + qad_utils.getAngleBy2Pts(nextPart.getStartPt(), ptIntList[0])): + intTypeNextPart = 3 # PFIP + else: + intTypeNextPart = 4 # NFIP + else: # arco o arco di ellisse + if nextPart.containsPt(ptIntList[0]): + intTypeNextPart = 1 # TIP + else: + intTypeNextPart = 2 # FIP + + return [ptIntList[0], intTypePart, intTypeNextPart] + + else: # 2 punti di intersezione + # scelgo il punto più vicino al punto finale di part + gType = part.whatIs() + if gType == "LINE": # segmento + if qad_utils.getDistance(ptIntList[0], part.getEndPt()) < qad_utils.getDistance(ptIntList[1], part.getEndPt()): + ptInt = ptIntList[0] + else: + ptInt = ptIntList[1] + + if part.containsPt(ptInt): + intTypePart = 1 # TIP + else: # l'intersezione non é sul segmento (FIP) + # se la direzione é la stessa del segmento + if qad_utils.doubleNear(qad_utils.getAngleBy2Pts(part.getStartPt(), part.getEndPt()), \ + qad_utils.getAngleBy2Pts(part.getStartPt(), ptInt)): + intTypePart = 3 # PFIP + else: + intTypePart = 4 # NFIP + + # la seconda parte é sicuramente un'arco + if nextPart.containsPt(ptInt): + intTypeNextPart = 1 # TIP + else: # l'intersezione non é sull'arco (FIP) + intTypeNextPart = 2 # FIP + + return [ptInt, intTypePart, intTypeNextPart] + else: # arco o arco di ellisse + finalPt = part.getEndPt() + + if qad_utils.getDistance(ptIntList[0], finalPt) < qad_utils.getDistance(ptIntList[1], finalPt): + ptInt = ptIntList[0] + else: + ptInt = ptIntList[1] + + if part.containsPt(ptInt): + intTypePart = 1 # TIP + else: # l'intersezione non é sull'arco (FIP) + intTypePart = 2 # FIP + + gType = nextPart.whatIs() + if gType == "LINE": # segmento + if nextPart.containsPt(ptInt): + intTypeNextPart = 1 # TIP + else: # l'intersezione non é sul segmento (FIP) + # se la direzione é la stessa del segmento + if qad_utils.doubleNear(qad_utils.getAngleBy2Pts(nextPart.getStartPt(), nextPart.getEndPt()), \ + qad_utils.getAngleBy2Pts(nextPart.getStartPt(), ptInt)): + intTypeNextPart = 3 # PFIP + else: + intTypeNextPart = 4 # NFIP + else: # arco o arco di ellisse + if nextPart.containsPt(ptInt): + intTypeNextPart = 1 # TIP + else: # l'intersezione non é sull'arco (FIP) + intTypeNextPart = 2 # FIP + + return [ptInt, intTypePart, intTypeNextPart] + + +# ============================================================================ +# getSelfIntersectionPoints +# ============================================================================ +def getSelfIntersectionPoints(polyline): + """ + la funzione restituisce una lista in cui ogni elemento è una sottolista composta da: + 1) punto di intersezione della polilinea con se stessa + 2) distanza del punti di intersezione dall'inizio della polilinea + 3) numero della parte contenente il punto di intersezione + """ + result = [] + distFromStartPrevPart = 0 + for iPart in range(len(polyline.defList)): + part = polyline.defList[iPart] + startPtOfPart = part.getStartPt() + endPtOfPart = part.getEndPt() + + # calcolo le intersezioni con tutte le parti ad eccezione di se stessa + for jPart in range(len(polyline.defList)): + if (iPart == jPart): + continue + partialIntPtList = QadIntersections.twoBasicGeomObjects(part, polyline.defList[jPart]) + for partialIntPt in partialIntPtList: + # escludo i punti che sono all'inizio-fine di part + # se i punti sono così vicini da essere considerati uguali + if qad_utils.ptNear(startPtOfPart, partialIntPt) == False and \ + qad_utils.ptNear(endPtOfPart, partialIntPt) == False: + # inserisco il punto con lar distanza dall'inizio della polilinea e il numero di parte + distFromStartPart = part.getDistanceFromStart(partialIntPt) + result.append([partialIntPt, distFromStartPart + distFromStartPrevPart, iPart]) + + distFromStartPrevPart += part.length() + + return result + + +# ============================================================================ +# getIntersectionPointsWithPolyline +# ============================================================================ +def getIntersectionPointsWithPolyline(polyline1, polyline2): + """ + la funzione restituisce una lista in cui ogni elemento è una sottolista composta da: + 1) punto di intersezione della polilinea con + 2) distanza del punti di intersezione dall'inizio della polilinea + 3) numero della parte contenente il punto di intersezione + """ + result = [] + distFromStartPrevPart = 0 + for iPart in range(len(polyline1.defList)): + part = polyline1.defList[iPart] + startPtOfPart = part.getStartPt() + endPtOfPart = part.getEndPt() + + # calcolo le intersezioni con tutte le parti di + for jPart in range(len(polyline2.defList)): + partialIntPtList = QadIntersections.twoBasicGeomObjects(part, polyline2.defList[jPart]) + for partialIntPt in partialIntPtList: + # escludo i punti che sono all'inizio-fine di part + # se i punti sono così vicini da essere considerati uguali + if qad_utils.ptNear(startPtOfPart, partialIntPt) == False and \ + qad_utils.ptNear(endPtOfPart, partialIntPt) == False: + # inserisco il punto con lar distanza dall'inizio della polilinea e il numero di parte + distFromStartPart = part.getDistanceFromStart(partialIntPt) + result.append([partialIntPt, distFromStartPart + distFromStartPrevPart, iPart]) + + distFromStartPrevPart += part.length() + + return result + + + +# =============================================================================== +# offsetBridgeTheGapBetweenLines +# =============================================================================== + + +def offsetBridgeTheGapBetweenLines(line1, line2, offset, gapType): + """ + la funzione colma il vuoto tra 2 segmenti retti (QadLine) nel comando offset + secondo una distanza (che corrisponde alla distanza di offset s + chiamata da tale comando) ed un modo : + 0 = Estende i segmenti alle relative intersezioni proiettate + 1 = Raccorda i segmenti attraverso un arco di raccordo di raggio + 2 = Cima i segmenti di linea in corrispondenza delle intersezioni proiettate. + La distanza perpendicolare da ciascuna cima al rispettivo vertice + sull'oggetto originale é uguale alla distanza . + + Se + Ritorna una lista di 3 elementi (None in caso di errore): + una linea che sostituisce , se = None va rimossa + un arco, se = None non c'é arco di raccordo tra le due linee + una linea che sostituisce , se = None va rimossa + """ + # cerco il punto di intersezione tra le due linee + ptInt = QadIntersections.twoInfinityLines(line1, line2) + if ptInt is None: # linee parallele + return None + distBetweenLine1Pt1AndPtInt = qad_utils.getDistance(line1.getStartPt(), ptInt) + distBetweenLine1Pt2AndPtInt = qad_utils.getDistance(line1.getEndPt(), ptInt) + distBetweenLine2Pt1AndPtInt = qad_utils.getDistance(line2.getStartPt(), ptInt) + distBetweenLine2Pt2AndPtInt = qad_utils.getDistance(line2.getEndPt(), ptInt) + + if gapType == 0: # Estende i segmenti + if distBetweenLine1Pt1AndPtInt > distBetweenLine1Pt2AndPtInt: + # secondo punto di line1 più vicino al punto di intersezione + newLine1 = QadLine().set(line1.getStartPt(), ptInt) + else: + # primo punto di line1 più vicino al punto di intersezione + newLine1 = QadLine().set(ptInt, line1.getEndPt()) + + if distBetweenLine2Pt1AndPtInt > distBetweenLine2Pt2AndPtInt: + # secondo punto di line2 più vicino al punto di intersezione + newLine2 = QadLine().set(line2.getStartPt(), ptInt) + else: + # primo punto di line2 più vicino al punto di intersezione + newLine2 = QadLine().set(ptInt, line2.getEndPt()) + + return [newLine1, None, newLine2] + elif gapType == 1: # Raccorda i segmenti + pt1Distant = line1.getStartPt() if distBetweenLine1Pt1AndPtInt > distBetweenLine1Pt2AndPtInt else line1.getEndPt() + angleLine1 = qad_utils.getAngleBy2Pts(ptInt, pt1Distant) + + pt2Distant = line2.getStartPt() if distBetweenLine2Pt1AndPtInt > distBetweenLine2Pt2AndPtInt else line2.getEndPt() + angleLine2 = qad_utils.getAngleBy2Pts(ptInt, pt2Distant) + + bisectorInfinityLinePts = qad_utils.getBisectorInfinityLine(pt1Distant, ptInt, pt2Distant, True) + bisectorLine = QadLine().set(bisectorInfinityLinePts[0], bisectorInfinityLinePts[1]) + + # cerco il punto di intersezione tra la bisettrice e + # la retta che congiunge i punti più distanti delle due linee + pt = QadIntersections.twoInfinityLines(bisectorLine, \ + QadLine().set(pt1Distant, pt2Distant)) + angleBisectorLine = qad_utils.getAngleBy2Pts(ptInt, pt) + + # calcolo l'angolo (valore assoluto) tra un lato e la bisettrice + alfa = angleLine1 - angleBisectorLine + if alfa < 0: + alfa = angleBisectorLine - angleLine1 + if alfa > math.pi: + alfa = (2 * math.pi) - alfa + + # calcolo l'angolo del triangolo rettangolo sapendo che la somma degli angoli interni = 180 + # - alfa - 90 gradi (angolo retto) + distFromPtInt = math.tan(math.pi - alfa - (math.pi / 2)) * offset + pt1Proj = qad_utils.getPolarPointByPtAngle(ptInt, angleLine1, distFromPtInt) + pt2Proj = qad_utils.getPolarPointByPtAngle(ptInt, angleLine2, distFromPtInt) + # Pitagora + distFromPtInt = math.sqrt((distFromPtInt * distFromPtInt) + (offset * offset)) + secondPt = qad_utils.getPolarPointByPtAngle(ptInt, angleBisectorLine, distFromPtInt - offset) + arc = QadArc() + arc.fromStartSecondEndPts(pt1Proj, secondPt, pt2Proj) + + if distBetweenLine1Pt1AndPtInt > distBetweenLine1Pt2AndPtInt: + # secondo punto di line1 più vicino al punto di intersezione + newLine1 = QadLine().set(pt1Distant, pt1Proj) + else: + # primo punto di line1 più vicino al punto di intersezione + newLine1 = QadLine().set(pt1Proj, pt1Distant) + + if distBetweenLine2Pt1AndPtInt > distBetweenLine2Pt2AndPtInt: + # secondo punto di line2 più vicino al punto di intersezione + newLine2 = QadLine().set(pt2Distant, pt2Proj) + else: + # primo punto di line2 più vicino al punto di intersezione + newLine2 = QadLine().set(pt2Proj, pt2Distant) + + # se i punti sono così vicini da essere considerati uguali + if qad_utils.ptNear(newLine1.getEndPt(), arc.getStartPt()) == False: + arc.reverse() + return [newLine1, arc, newLine2] + elif gapType == 2: # Cima i segmenti + bisectorInfinityLinePts = qad_utils.getBisectorInfinityLine(line1.getEndPt(), ptInt, line2.getEndPt(), True) + bisectorLine = QadLine().set(bisectorInfinityLinePts[0], bisectorInfinityLinePts[1]) + + angleBisectorLine = qad_utils.getAngleBy2Pts(bisectorLine[0], bisectorLine[1]) + ptProj = qad_utils.getPolarPointByPtAngle(ptInt, angleBisectorLine, offset) + + pt1Proj = QadPerpendicularity.fromPointToInfinityLine(ptProj, line1) + if distBetweenLine1Pt1AndPtInt > distBetweenLine1Pt2AndPtInt: + # secondo punto di line1 più vicino al punto di intersezione + newLine1 = QadLine().set(line1.getStartPt(), pt1Proj) + else: + # primo punto di line1 più vicino al punto di intersezione + newLine1 = QadLine().set(pt1Proj, line1.getEndPt()) + + pt2Proj = QadPerpendicularity.fromPointToInfinityLine(ptProj, line2) + if distBetweenLine2Pt1AndPtInt > distBetweenLine2Pt2AndPtInt: + # secondo punto di line2 più vicino al punto di intersezione + newLine2 = QadLine().set(line2.getStartPt(), pt2Proj) + else: + # primo punto di line2 più vicino al punto di intersezione + newLine2 = QadLine().set(pt2Proj, line2.getEndPt()) + + return [newLine1, QadLine().set(pt1Proj, pt2Proj), newLine2] + + return None + + +# =============================================================================== +# fillet2PartsOffset +# =============================================================================== +def fillet2PartsOffset(part, nextPart, offsetSide, offsetDist): + """ + la funzione raccorda 2 parti nei seguenti casi: + 1) segmento-arco (PFIP-FIP, nessuna intersezione) + 2) arco-segmento (FIP-NFIP, nessuna intersezione) + 3) arco-arco (nessuna intersezione) + """ + gType = part.whatIs() + # se la prima parte é un segmento + if gType == "LINE": + newNextPart = part.copy() + newNextPart.reverse() # rovescio la direzione + newPart = nextPart.copy() + newPart.reverse() # rovescio la direzione + newOffSetSide = "left" if offsetSide == "right" else "right" + result = fillet2PartsOffset(newPart, newNextPart, newOffSetSide, offsetDist) + if result is not None: + result.reverse() # rovescio la direzione + return result + + elif gType == "ARC": # se la prima parte é un arco + AngleProjected = qad_utils.getAngleBy2Pts(part.center, part.getEndPt()) + if part.reversed == False: # l'arco gira verso sin + if offsetSide == "right": # l'offset era verso l'esterno + # calcolo il punto proiettato per ri-ottenere quello originale + center = qad_utils.getPolarPointByPtAngle(part.center, AngleProjected, part.radius - offsetDist) + else: # l'offset era verso l'interno + center = qad_utils.getPolarPointByPtAngle(part.center, AngleProjected, part.radius + offsetDist) + else: # l'arco gira verso destra + if offsetSide == "right": # l'offset era verso l'interno + center = qad_utils.getPolarPointByPtAngle(part.center, AngleProjected, part.radius + offsetDist) + else: # l'offset era verso l'esterno + center = qad_utils.getPolarPointByPtAngle(part.center, AngleProjected, part.radius - offsetDist) + + newArc = QadArc() + # se il centro dell'arco di raccordo é interno all'arco di offset + if qad_utils.getDistance(part.center, center) < part.radius: + if part.reversed == False: + if newArc.fromStartCenterEndPts(part.getEndPt(), center, nextPart.getStartPt()) == False: + return None + newArc.reversed = False + else: + if newArc.fromStartCenterEndPts(nextPart.getStartPt(), center, part.getEndPt()) == False: + return None + newArc.reversed = True + else: # se il centro dell'arco di raccordo é esterno all'arco di offset + if part.reversed == False: + if newArc.fromStartCenterEndPts(nextPart.getStartPt(), center, part.getEndPt()) == False: + return None + newArc.reversed = True + else: + if newArc.fromStartCenterEndPts(part.getEndPt(), center, nextPart.getStartPt()) == False: + return None + newArc.reversed = False + + return newArc + + +# =============================================================================== +# getOffsetDistancesFromCenterOnOffsetedArc +# =============================================================================== +def getOffsetDistancesFromCenterOnOffsetedArc(arc, offsetDist, offsetSide): + """ + la funzione restituisce la distanza minima e massima dal centro dell'arco su cui è già stato fatto un offset. + Queste distanze generano un'area di offset intorno all'arco originale. + arco a cui è già stato fatto un offset + distanza di offset + parte in cui si vuole l'offset "right" o "left" + """ + if arc.reversed: # l'arco gira verso destra + if offsetSide == "right": # offset sulla parte interna dell'arco + minDist = arc.radius + maxDist = arc.radius + 2 * offsetDist + else: # offset sulla parte esterna dell'arco + maxDist = arc.radius + minDist = arc.radius - 2 * offsetDist + else: # l'arco gira verso sin + if offsetSide == "right": # offset sulla parte esterna dell'arco + maxDist = arc.radius + minDist = arc.radius - 2 * offsetDist + else: # offset sulla parte interna dell'arco + minDist = arc.radius + maxDist = arc.radius + 2 * offsetDist + + if minDist < 0: minDist = 0 + + return minDist, maxDist + + +# =============================================================================== +# virtualPartClipping +# =============================================================================== +def virtualPartClipping(untrimmedOffsetPolyline, virtualPartPositionList): + """ + la funzione restituisce una lista di parti in cui vengono tagliate le isole generate + da parti virtuali (che invertono il senso della linea). + Per ogni parte virtuale, si verifica se le parti che precedono e che seguono formano un'isola. + In caso affermativo, se possibile (vedi casi specifici), l'sola viene rimossa. + lista delle parti + lista delle posizioni delle parti virtuali (viene modificata) + """ + result = untrimmedOffsetPolyline.copy() + + # per prima cosa elimino tutte le isole con parti virtuali che hanno le parti + # direttamente adiacenti intersecanti + i = len(virtualPartPositionList) - 1 + while i >= 0: + virtualPartPosition = virtualPartPositionList[i] + # parte successiva a quella virtuale + nextPos = result.getNextPos(virtualPartPosition) + # parte precedente + prevPos = result.getPrevPos(virtualPartPosition) + + if (prevPos is not None) and (nextPos is not None): + nextPart = result.getLinearObjectAt(nextPos) + prevPart = result.getLinearObjectAt(prevPos) + # verifico se hanno un solo punto di intersezione + ptIntList = QadIntersections.twoBasicGeomObjects(prevPart, nextPart) + if len(ptIntList) == 1: + nextPart.setStartPt(ptIntList[0]) # modifico l'inizio + prevPart.setEndPt(ptIntList[0]) # modifico la fine + result.remove(virtualPartPosition) + del virtualPartPositionList[i] + for j in range(i, len(virtualPartPositionList)): + virtualPartPositionList[j] = virtualPartPositionList[j] - 1 # scalo tutto di uno + i = i - 1 + + # elimino tutte le isole con parti virtuali che hanno le parti adiacenti intersecanti + # ma che non formino con il resto della linea altre isole. + # quando considero un lato adiacente alla parte virtuale da un lato devo considerare le intersezioni + # partendo dal lato successivo quello adicente nella parte opposta di quello virtuale + for i in range(len(virtualPartPositionList) - 1, -1, -1): + virtualPartPosition = virtualPartPositionList[i] + # finché non trovo l'intersezione + nPrevPartsToRemove = -1 + prevPos = virtualPartPosition + ptIntList = [] + while len(ptIntList) == 0: + virtualPart = result.getLinearObjectAt(virtualPartPosition) + # parte successiva a quella virtuale + nextPos = result.getNextPos(virtualPartPosition) + nNextPartsToRemove = 0 + # parte precedente + prevPos = result.getPrevPos(prevPos) + # se trovo una parte virtuale mi fermo + if virtualPartPositionList.count(prevPos) > 0: + break + + # l'ultima condizione é nel caso la polilinea sia chiusa + if (prevPos is None) or (nextPos is None) or prevPos == nextPos: + break + + nPrevPartsToRemove = nPrevPartsToRemove + 1 + prevPart = result.getLinearObjectAt(prevPos) + + # ciclo finche non ci sono più parti successive + while (nextPos is not None) and (prevPos != nextPos): + # se trovo una parte virtuale mi fermo + if virtualPartPositionList.count(nextPos) > 0: + break + nextPart = result.getLinearObjectAt(nextPos) + ptIntList = QadIntersections.twoBasicGeomObjects(prevPart, nextPart) + if len(ptIntList) > 0: + break + nextPos = result.getNextPos(nextPos) # parte successiva + nNextPartsToRemove = nNextPartsToRemove + 1 + + if len(ptIntList) == 1 and \ + not qad_utils.ptNear(ptIntList[0], virtualPart.getStartPt()) and \ + not qad_utils.ptNear(ptIntList[0], virtualPart.getEndPt()): + prevPart_1 = prevPart.copy() + # se il punto iniziale della parte non coincide con quella del punto di intersezione + if not qad_utils.ptNear(ptIntList[0], prevPart.getStartPt()): + prevPart_1.setEndPt(ptIntList[0]) # modifico la fine + prevPart_2 = prevPart.copy() + prevPart_2.setStartPt(ptIntList[0]) # modifico l'inizio + else: + prevPart_2 = None + + nextPart_1 = nextPart.copy() + # se il punto finale della parte non coincide con quella del punto di intersezione + if not qad_utils.ptNear(ptIntList[0], nextPart.getEndPt()): + nextPart_1.setEndPt(ptIntList[0]) # modifico la fine + nextPart_2 = nextPart.copy() + nextPart_2.setStartPt(ptIntList[0]) # modifico l'inizio + else: + nextPart_2 = None + + ######################################################## + # Creo una lista di parti che definisce l'isola - inizio + islandPolyline = QadPolyline() + + if prevPart_2 is None: + islandPolyline.append(prevPart_1) + else: + islandPolyline.append(prevPart_2) + + pos = virtualPartPosition + for j in range(nPrevPartsToRemove, 0, - 1): + pos = result.getPrevPos(pos) # parte precedente + islandPolyline.append(result.getLinearObjectAt(pos)) + + islandPolyline.append(virtualPart) + + pos = virtualPartPosition + for j in range(1, nNextPartsToRemove + 1, 1): + pos = result.getNextPos(pos) # parte successiva + islandPolyline.append(result.getLinearObjectAt(pos)) + + islandPolyline.append(nextPart_1) + + # Creo una lista di parti che definisce l'isola - fine + ######################################################## + + # verifico se le parti seguenti formano con islandPolyline delle aree (più di 2 intersezioni) + if nextPart_2 is not None: + nIntersections = 1 + else: + nIntersections = 0 + + for j in range(nextPos + 1, result.qty(), 1): + dummy = QadIntersections.getOrderedPolylineIntersectionPtsWithBasicGeom(islandPolyline, result.getLinearObjectAt(j)) + intPtList = dummy[0] + nIntersections = nIntersections + len(intPtList) + + # se é positivo e minore o uguale a 2 verifico anche dall'altra parte + if nIntersections > 0 and nIntersections <= 2: + # verifico se le parti precedenti formano con islandPolyline delle aree (almeno 2 intersezioni) + if prevPart_2 is not None: + nIntersections = 1 + else: + nIntersections = 0 + + for j in range(prevPos - 1, -1, -1): + dummy = QadIntersections.getOrderedPolylineIntersectionPtsWithBasicGeom(islandPolyline, result.getLinearObjectAt(j)) + intPtList = dummy[0] + nIntersections = nIntersections + len(intPtList) + + # se é positivo e minore o uguale a 2 verifico anche dall'altra parte + if nIntersections > 0 and nIntersections <= 2: + # rimuovo island da result + if nextPart_2 is not None: + nextPart.setStartPt(nextPart_2.getStartPt()) # modifico l'inizio + else: + result.remove(nextPos) + + # cancello le parti inutili + for j in range(0, nNextPartsToRemove, 1): + result.remove(virtualPartPosition + 1) + + # cancello la parte virtuale + result.remove(virtualPartPosition) + + # cancello le parti inutili + for j in range(0, nPrevPartsToRemove, 1): + result.remove(virtualPartPosition - nPrevPartsToRemove) + + if prevPart_2 is not None: + prevPart.setEndPt(nextPart_2.getStartPt()) # modifico la fine + else: + result.remove(prevPos) + + del virtualPartPositionList[i] + + return result + + +# =============================================================================== +# getIntPtListBetweenPartAndPartListOffset +# =============================================================================== +def getIntPtListBetweenPartAndPartListOffset(part, polyline): + """ + la funzione restituisce due liste: + la prima é una lista di punti di intersezione tra la parte + e la polilinea ordinata per distanza dal punto iniziale + di part (scarta i doppioni e i punti iniziale-finale di part) + la seconda é una lista che contiene, rispettivamente per ogni punto di intersezione, + il numero della parte (0-based) di in cui si trova quel punto. + : un segmento o arco + : lista delle parti di una polilinea + """ + startPtOfPart = part.getStartPt() + endPtOfPart = part.getEndPt() + intPtSortedList = [] # lista di ((punto, distanza dall'inizio della parte) ...) + partNumber = -1 + # per ogni parte di polyline + for part2 in polyline.defList: + partNumber = partNumber + 1 + partialIntPtList = QadIntersections.twoBasicGeomObjects(part, part2) + for partialIntPt in partialIntPtList: + # escludo i punti che sono all'inizio-fine di part + + # se i punti sono così vicini da essere considerati uguali + if qad_utils.ptNear(startPtOfPart, partialIntPt) == False and \ + qad_utils.ptNear(endPtOfPart, partialIntPt) == False: + # escludo i punti che sono già in intPtSortedList + found = False + for intPt in intPtSortedList: + if qad_utils.ptNear(intPt[0], partialIntPt): + found = True + break + + if found == False: + # inserisco il punto ordinato per distanza dal inizio di part + distFromStart = part.getDistanceFromStart(partialIntPt) + insertAt = 0 + for intPt in intPtSortedList: + if intPt[1] < distFromStart: + insertAt = insertAt + 1 + else: + break + intPtSortedList.insert(insertAt, [partialIntPt, distFromStart, partNumber]) + resultIntPt = [] + resultPartNumber = [] + for intPt in intPtSortedList: + resultIntPt.append(intPt[0]) + resultPartNumber.append(intPt[2]) + + return resultIntPt, resultPartNumber + + +# ============================================================================ +# getPartsExternalToCircle +# ============================================================================ +def getPartsExternalToCircle(linearObj, circle): + """ + la funzione usa un cerchio per dividere l'oggetto lineare. + Le parti esterne al cerchio vengono restituite + nell'ordine dal punto iniziale a quello finale dell'oggetto lineare. + """ + result = QadPolyline() + + startPt = linearObj.getStartPt() + endPt = linearObj.getEndPt() + intPtList = QadIntersections.twoBasicGeomObjects(circle, linearObj) + + intPtSortedList = [] + for pt in intPtList: + # inserisco il punto ordinato per distanza dall'inizio di part + distFromStart = linearObj.getDistanceFromStart(pt) + insertAt = 0 + for intPt in intPtSortedList: + if intPt[1] < distFromStart: + insertAt = insertAt + 1 + else: + break + intPtSortedList.insert(insertAt, [pt, distFromStart]) + + del intPtList[:] # svuoto la lista + for intPt in intPtSortedList: + intPtList.append(intPt[0]) + + startPtFromCenter = qad_utils.getDistance(circle.center, startPt) + endPtFromCenter = qad_utils.getDistance(circle.center, endPt) + intPtListLen = len(intPtList) + if intPtListLen == 0: # se non ci sono punti di intersezione + # se entrambi i punti terminali della parte sono esterni al cerchio + if startPtFromCenter >= circle.radius and endPtFromCenter >= circle.radius: + result.append(linearObj) + elif intPtListLen == 1: # se c'é un solo punto di intersezione + # se entrambi i punti terminali della parte sono esterni al cerchio + if startPtFromCenter >= circle.radius and endPtFromCenter >= circle.radius: + result.append(linearObj) + # se il primo punto della parte é interno e il secondo esterno al cerchio + elif startPtFromCenter < circle.radius and endPtFromCenter > circle.radius: + newLinearobj = linearObj.copy() + newLinearobj.setStartPt(intPtList[0]) + result.append(newLinearobj) + # se il primo punto della parte é esterno e il secondo interno al cerchio + elif startPtFromCenter > circle.radius and endPtFromCenter < circle.radius: + newLinearobj = linearObj.copy() + newLinearobj.setEndPt(intPtList[0]) + result.append(newLinearobj) + else : # se ci sono due punti di intersezione + # se il primo punto della parte é esterno al cerchio + if startPtFromCenter > circle.radius: + newLinearobj = linearObj.copy() + newLinearobj.setEndPt(intPtList[0]) + result.append(newLinearobj) + # se il secondo punto della parte é esterno al cerchio + if endPtFromCenter > circle.radius: + newLinearobj = linearObj.copy() + newLinearobj.setStartPt(intPtList[1]) + result.append(newLinearobj) + + return result diff --git a/qad_offset_maptool.py b/qad_offset_maptool.py deleted file mode 100644 index cdc33dd2..00000000 --- a/qad_offset_maptool.py +++ /dev/null @@ -1,190 +0,0 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - classe per gestire il map tool in ambito del comando offset - - ------------------- - begin : 2013-10-04 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -from qgis.gui import * -import math - - -import qad_utils -from qad_snapper import * -from qad_snappointsdisplaymanager import * -from qad_variables import * -from qad_getpoint import * -from qad_highlight import QadHighlight -from qad_dim import QadDimStyles - - -#=============================================================================== -# Qad_offset_maptool_ModeEnum class. -#=============================================================================== -class Qad_offset_maptool_ModeEnum(): - # si richiede il primo punto per calcolo offset - ASK_FOR_FIRST_OFFSET_PT = 1 - # noto il primo punto per calcolo offset si richiede il secondo punto - FIRST_OFFSET_PT_KNOWN_ASK_FOR_SECOND_PT = 2 - # nota la distanza di offset si richiede il punto per stabilire da che parte - OFFSET_KNOWN_ASK_FOR_SIDE_PT = 3 - # si richiede il punto di passaggio per stabilire da che parte e a quale offset - ASK_FOR_PASSAGE_PT = 4 - # si richiede la selezione di un oggetto - ASK_FOR_ENTITY_SELECTION = 5 - -#=============================================================================== -# Qad_offset_maptool class -#=============================================================================== -class Qad_offset_maptool(QadGetPoint): - - def __init__(self, plugIn): - QadGetPoint.__init__(self, plugIn) - - self.firstPt = None - self.layer = None - self.subGeom = None - self.offSet = 0 - self.lastOffSetOnLeftSide = 0 - self.lastOffSetOnRightSide = 0 - self.gapType = 0 - self.__highlight = QadHighlight(self.canvas) - - def hidePointMapToolMarkers(self): - QadGetPoint.hidePointMapToolMarkers(self) - self.__highlight.hide() - - def showPointMapToolMarkers(self): - QadGetPoint.showPointMapToolMarkers(self) - self.__highlight.show() - - def clear(self): - QadGetPoint.clear(self) - self.__highlight.reset() - self.mode = None - - def addOffSetGeometries(self, newPt): - self.__highlight.reset() - - # ritorna una tupla (, - # - # - # ) - dummy = qad_utils.closestSegmentWithContext(newPt, self.subGeom) - if self.offSet < 0: - afterVertex = dummy[2] - pt = qad_utils.getPerpendicularPointOnInfinityLine(self.subGeom.vertexAt(afterVertex - 1), \ - self.subGeom.vertexAt(afterVertex), \ - newPt) - offSetDistance = qad_utils.getDistance(newPt, pt) - else: - offSetDistance = self.offSet - - if dummy[3] < 0: # alla sinistra - offSetDistance = offSetDistance + self.lastOffSetOnLeftSide - else: # alla destra - offSetDistance = offSetDistance + self.lastOffSetOnRightSide - - tolerance2ApproxCurve = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) - # uso il crs del canvas per lavorare con coordinate piane xy - epsg = self.canvas.mapRenderer().destinationCrs().authid() - lines = qad_utils.offSetPolyline(self.subGeom.asPolyline(), epsg, \ - offSetDistance, \ - "left" if dummy[3] < 0 else "right", \ - self.gapType, \ - tolerance2ApproxCurve) - - for line in lines: - if self.layer.geometryType() == QGis.Polygon: - if line[0] == line[-1]: # se é una linea chiusa - offsetGeom = QgsGeometry.fromPolygon([line]) - else: - offsetGeom = QgsGeometry.fromPolyline(line) - else: - offsetGeom = QgsGeometry.fromPolyline(line) - - self.__highlight.addGeometry(self.mapToLayerCoordinates(self.layer, offsetGeom), self.layer) - - - def canvasMoveEvent(self, event): - QadGetPoint.canvasMoveEvent(self, event) - - # nota la distanza di offset si richiede il punto per stabilire da che parte - if self.mode == Qad_offset_maptool_ModeEnum.OFFSET_KNOWN_ASK_FOR_SIDE_PT: - self.addOffSetGeometries(self.tmpPoint) - # si richiede il punto di passaggio per stabilire da che parte e a quale offset - elif self.mode == Qad_offset_maptool_ModeEnum.ASK_FOR_PASSAGE_PT: - self.addOffSetGeometries(self.tmpPoint) - - - def activate(self): - QadGetPoint.activate(self) - self.__highlight.show() - - def deactivate(self): - try: # necessario perché se si chiude QGIS parte questo evento nonostante non ci sia più l'oggetto maptool ! - QadGetPoint.deactivate(self) - self.__highlight.hide() - except: - pass - - def setMode(self, mode): - self.clear() - self.mode = mode - # si richiede il primo punto per calcolo offset - if self.mode == Qad_offset_maptool_ModeEnum.ASK_FOR_FIRST_OFFSET_PT: - self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) - self.setDrawMode(QadGetPointDrawModeEnum.NONE) - self.onlyEditableLayers = False - # noto il primo punto per calcolo offset si richiede il secondo punto - if self.mode == Qad_offset_maptool_ModeEnum.FIRST_OFFSET_PT_KNOWN_ASK_FOR_SECOND_PT: - self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) - self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.setStartPoint(self.firstPt) - self.onlyEditableLayers = False - # nota la distanza di offset si richiede il punto per stabilire da che parte - elif self.mode == Qad_offset_maptool_ModeEnum.OFFSET_KNOWN_ASK_FOR_SIDE_PT: - self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) - self.setDrawMode(QadGetPointDrawModeEnum.NONE) - self.onlyEditableLayers = False - # si richiede il punto di passaggio per stabilire da che parte e a quale offset - elif self.mode == Qad_offset_maptool_ModeEnum.ASK_FOR_PASSAGE_PT: - self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) - self.setDrawMode(QadGetPointDrawModeEnum.NONE) - self.onlyEditableLayers = False - # si richiede la selezione di un oggetto - elif self.mode == Qad_offset_maptool_ModeEnum.ASK_FOR_ENTITY_SELECTION: - self.setSelectionMode(QadGetPointSelectionModeEnum.ENTITY_SELECTION) - # solo layer lineari o poligono editabili che non appartengano a quote - layerList = [] - for layer in self.plugIn.canvas.layers(): - if layer.type() == QgsMapLayer.VectorLayer and \ - (layer.geometryType() == QGis.Line or layer.geometryType() == QGis.Polygon) and \ - layer.isEditable(): - if len(QadDimStyles.getDimListByLayer(layer)) == 0: - layerList.append(layer) - - self.layersToCheck = layerList - self.setDrawMode(QadGetPointDrawModeEnum.NONE) - self.onlyEditableLayers = True diff --git a/qad_options.ui b/qad_options.ui new file mode 100644 index 00000000..72b5802f --- /dev/null +++ b/qad_options.ui @@ -0,0 +1,1318 @@ + + + Options_Dialog + + + Qt::NonModal + + + + 0 + 0 + 591 + 386 + + + + + 591 + 386 + + + + + 591 + 386 + + + + Options + + + false + + + false + + + + + 240 + 350 + 341 + 32 + + + + Qt::Horizontal + + + QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Help|QDialogButtonBox::Ok + + + + + + 10 + 10 + 571 + 331 + + + + + 16777215 + 16777215 + + + + 2 + + + + Display + + + + + 10 + 10 + 251 + 291 + + + + Window Elements + + + + + 20 + 260 + 91 + 23 + + + + Displays the Drawing Window Colors dialog box. + + + Colors + + + + + + 20 + 100 + 211 + 17 + + + + Automatically appends suggestions as each keystroke is entered after the second keystroke (system variable INPUTSEARCHOPTIONS). + + + AutoComplete + + + + + + 10 + 20 + 231 + 20 + + + + Show the text window at startup (system variable SHOWTEXTWINDOW). + + + Show the text window at startup + + + + + + 50 + 50 + 191 + 21 + + + + Maximun command history length + + + true + + + + + + 10 + 50 + 31 + 20 + + + + Sets the maximum number of previous input values that are stored for a prompt in a command (system variable CMDINPUTHISTORYMAX). + + + + + + 20 + 180 + 41 + 20 + + + + <html><head/><body><p>Controls the amount of time that elapses before automated keyboard features display at the Command prompt.</p><p>Valid values are real numbers from 100 to 10,000, which represent milliseconds.</p></body></html> + + + + + + 70 + 180 + 171 + 21 + + + + Delay time (msec) + + + true + + + + + + 10 + 80 + 231 + 17 + + + + Turns on/off all automated keyboard features when typing at the Command prompt (system variable INPUTSEARCHOPTIONS). + + + Automated keyboard features + + + + + + 20 + 120 + 211 + 17 + + + + Displays a list of suggestions as keystrokes are entered (system variable INPUTSEARCHOPTIONS). + + + Displays a list of suggestions + + + + + + 20 + 140 + 211 + 17 + + + + Displays the icon of the command or system variable, if available (system variable INPUTSEARCHOPTIONS). + + + Displays the icon of the command + + + + + + 20 + 160 + 221 + 17 + + + + Excludes the display of system variables (system variable INPUTSEARCHOPTIONS). + + + Excludes system variables + + + + + + + 270 + 10 + 281 + 221 + + + + Resolution + + + + + 10 + 50 + 41 + 20 + + + + Minimum number of segments to approximate an arc (system variable ARCMINSEGMENTQTY). + + + + + + 60 + 50 + 211 + 21 + + + + Minimum number of segments in an arc + + + true + + + + + + 60 + 80 + 211 + 21 + + + + Minimum number of segments to approximate a circle (system variable CIRCLEMINSEGMENTQTY). + + + Minimum number of segments in a circle + + + true + + + + + + 10 + 80 + 41 + 20 + + + + Minimum number of segments to approximate a circle (system variable CIRCLEMINSEGMENTQTY). + + + + + + 10 + 170 + 41 + 20 + + + + Maximum error approximating a curve to segments (system variable TOLERANCE2APPROXCURVE). + + + + + + 60 + 160 + 211 + 51 + + + + Maximun admitted error between a real curve and the aproximated, segmented curve + + + true + + + + + + 10 + 140 + 41 + 20 + + + + Minimum number of segments to approximate an ellipse (system variable ELLIPSEMINSEGMENTQTY). + + + + + + 60 + 140 + 211 + 21 + + + + Minimum number of segments to approximate an ellipse (system variable ELLIPSEMINSEGMENTQTY). + + + Minimum number of segments in an ellipse + + + true + + + + + + 10 + 20 + 41 + 20 + + + + Proximity (tolerance) within 2 point are to be considered coincident (system variable TOLERANCE2COINCIDENT). + + + + + + 60 + 20 + 211 + 21 + + + + Tolerance for coincident points + + + true + + + + + + 10 + 110 + 41 + 20 + + + + Minimum number of segments to approximate an arc (system variable ARCMINSEGMENTQTY). + + + + + + 60 + 100 + 211 + 41 + + + + Minimum number of segments in an arc of ellipse + + + true + + + + + + + 270 + 250 + 281 + 51 + + + + Crosshair size + + + + + 10 + 20 + 31 + 20 + + + + Determines the size of the crosshair as a percentage of the screen size (system variable CURSORSIZE). + + + + + + 50 + 20 + 221 + 22 + + + + Determines the size of the crosshair as a percentage of the screen size (system variable CURSORSIZE). + + + Qt::Horizontal + + + + + + + User Preferences + + + + + 10 + 10 + 281 + 91 + + + + Windows Standard Behavior + + + + + 10 + 40 + 261 + 17 + + + + Controls whether Default, Edit, and Command mode shortcut menus are available in the drawing area. ( SHORTCUTMENU system variable) + + + Shortcut menus in drawing area + + + + + + 10 + 60 + 261 + 23 + + + + Displays the Right-Click Customization dialog box. This dialog box provides further definition for the Shortcut Menus in Drawing Area option. ( SHORTCUTMENU system variable) + + + Right-click customization... + + + + + + + Drafting + + + + + 10 + 10 + 261 + 141 + + + + AutoSnap Settings + + + + + 10 + 20 + 241 + 17 + + + + Controls the display of the AutoSnap marker. The marker is a geometric symbol that is displayed when the crosshairs move over a snap point ( AUTOSNAP system variable). + + + Marker + + + + + + 10 + 110 + 81 + 23 + + + + Displays the Drawing Window Colors dialog box. + + + Colors + + + + + + 10 + 80 + 241 + 17 + + + + Turns the display of the AutoSnap aperture box on or off. +The aperture box is a box that appears inside the crosshairs when you snap to an object (APBOX system variable). + + + Display AutoSnap aperture box + + + + + + 10 + 60 + 241 + 17 + + + + Controls the display of the AutoSnap tooltip. The tooltip is a label that describes which part of the object you are snapping to (AUTOSNAP system variable). + + + Display AutoSnap tooltip + + + + + + 10 + 40 + 241 + 17 + + + + Turns the AutoSnap magnet on or off. The magnet is an automatic movement of the crosshairs that locks the crosshairs onto the nearest snap point (AUTOSNAP system variable). + + + Magnet + + + + + + + 280 + 10 + 271 + 71 + + + + Alignment Point Acquisition + + + + + 10 + 20 + 241 + 17 + + + + Displays tracking vectors automatically when the aperture moves over an object snap. + + + Automatic + + + + + + 10 + 40 + 241 + 17 + + + + Displays tracking vectors when you press Shift and move the aperture over an object snap. + + + Shift to acquire + + + + + + + 10 + 160 + 261 + 81 + + + + AutoSnap Marker Size + + + + + 70 + 30 + 181 + 22 + + + + Sets the display size for the AutoSnap marker. + + + Qt::Horizontal + + + + + + 10 + 20 + 51 + 51 + + + + + + + + 280 + 160 + 271 + 81 + + + + Aperture Size + + + + + 70 + 30 + 181 + 22 + + + + Sets the display size for the object snap target box, in pixels (APERTURE system variable). + + + Qt::Horizontal + + + + + + 10 + 20 + 51 + 51 + + + + + + + + 280 + 250 + 271 + 23 + + + + Displays the Drafting Tooltip dialog box. + + + Drafting Tooltip settings... + + + + + + Selection + + + + + 10 + 10 + 261 + 81 + + + + Pickbox size + + + + + 70 + 30 + 181 + 22 + + + + Sets the object selection target height, in pixels (PICKBOX system variable). + + + Qt::Horizontal + + + + + + 10 + 20 + 51 + 51 + + + + + + + + 280 + 10 + 271 + 81 + + + + Grip size + + + + + 70 + 30 + 191 + 22 + + + + Sets the size of the grip box in pixels ( GRIPSIZE system variable). + + + Qt::Horizontal + + + + + + 10 + 20 + 51 + 51 + + + + + + + + 10 + 100 + 261 + 131 + + + + Selection modes + + + + + 10 + 20 + 241 + 17 + + + + Controls whether you select objects before (noun-verb selection) or after you issue a command (PICKFIRST system variable). + + + Noun/verb selection + + + + + + 10 + 40 + 241 + 17 + + + + Controls whether subsequent selections replace the current selection set or add to it (PICKADD system variable). + + + Use Shift to add to selection + + + + + + + 280 + 100 + 271 + 131 + + + + Grips + + + + + 70 + 20 + 141 + 23 + + + + Displays the Grip Colors dialog box where you can specify the colors for different grip status and elements. + + + Grip Colors... + + + + + + 10 + 50 + 251 + 17 + + + + Controls the display of grips on selected objects. Displaying grips in a drawing significantly affects performance. Clear this option to optimize performance ( GRIPS system variable). + + + Shows grips + + + + + + 20 + 70 + 241 + 17 + + + + Controls the display of dynamic menu when pausing over a multi-functional grip (GRIPMULTIFUNCTIONAL system variable). + + + Shows dynamic grip menu + + + + + + 20 + 100 + 31 + 20 + + + + Suppresses the display of grips when the selection set includes more than the specified number of objects (GRIPOBJLIMIT system variable). + + + + + + 60 + 100 + 201 + 31 + + + + + + + Object selection limit for display of grips + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + + + + + buttonBox + accepted() + Options_Dialog + ButtonBOX_Accepted() + + + 330 + 361 + + + 199 + 357 + + + + + buttonBox + clicked(QAbstractButton*) + Options_Dialog + ButtonBOX_Apply() + + + 463 + 366 + + + 221 + 365 + + + + + buttonBox + helpRequested() + Options_Dialog + ButtonHELP_Pressed() + + + 480 + 364 + + + 235 + 372 + + + + + checkBox_INPUTSEARCHOPTIONS_ON + clicked() + Options_Dialog + checkBox_INPUTSEARCHOPTIONS_ON_clicked() + + + 74 + 129 + + + 14 + 354 + + + + + horizontalSlider_CURSORSIZE + sliderMoved(int) + Options_Dialog + horizontalSlider_CURSORSIZE_moved() + + + 449 + 315 + + + 586 + 314 + + + + + lineEdit_CURSORSIZE + textEdited(QString) + Options_Dialog + lineEdit_CURSORSIZE_textChanged() + + + 307 + 313 + + + 587 + 282 + + + + + horizontalSlider_CURSORSIZE + valueChanged(int) + Options_Dialog + horizontalSlider_CURSORSIZE_moved() + + + 491 + 315 + + + 587 + 230 + + + + + horizontalSlider_AUTOSNAPSIZE + valueChanged(int) + Options_Dialog + horizontalSlider_AUTOSNAPSIZE_changed() + + + 120 + 230 + + + 50 + 358 + + + + + horizontalSlider_APERTURE + valueChanged(int) + Options_Dialog + horizontalSlider_APERTURE_changed() + + + 499 + 230 + + + 584 + 264 + + + + + horizontalSlider_PICKBOX + valueChanged(int) + Options_Dialog + horizontalSlider_PICKBOX_changed() + + + 136 + 82 + + + 113 + 354 + + + + + horizontalSlider_GRIPSIZE + valueChanged(int) + Options_Dialog + horizontalSlider_GRIPSIZE_changed() + + + 401 + 80 + + + 586 + 89 + + + + + checkBox_GRIPS + clicked() + Options_Dialog + checkBox_GRIPS_ON_clicked() + + + 347 + 190 + + + 583 + 206 + + + + + button_GripColor + clicked() + Options_Dialog + button_GripColor_clicked() + + + 413 + 162 + + + 586 + 172 + + + + + Button_TextWindowColor + clicked() + Options_Dialog + Button_TextWindowColor_clicked() + + + 94 + 316 + + + 127 + 361 + + + + + pushButton_AutoSnapColor + clicked() + Options_Dialog + Button_AutoSnapWindowColor_clicked() + + + 67 + 159 + + + 78 + 368 + + + + + button_DraftingTootipSettings + clicked() + Options_Dialog + button_DraftingTooltipSettings_clicked() + + + 547 + 291 + + + 582 + 341 + + + + + button_rightclick + clicked() + Options_Dialog + button_rightclick_clicked() + + + 174 + 110 + + + 6 + 115 + + + + + checkBox_shortcutmenu + clicked() + Options_Dialog + checkBox_shortcutmenu_clicked() + + + 73 + 89 + + + 4 + 92 + + + + + + ButtonBOX_Accepted() + ButtonBOX_Apply() + ButtonHELP_Pressed() + checkBox_INPUTSEARCHOPTIONS_ON_clicked() + horizontalSlider_CURSORSIZE_moved() + lineEdit_CURSORSIZE_textChanged() + horizontalSlider_AUTOSNAPSIZE_changed() + horizontalSlider_APERTURE_changed() + horizontalSlider_PICKBOX_changed() + horizontalSlider_GRIPSIZE_changed() + checkBox_GRIPS_ON_clicked() + button_GripColor_clicked() + Button_TextWindowColor_clicked() + Button_AutoSnapWindowColor_clicked() + button_DraftingTooltipSettings_clicked() + button_rightclick_clicked() + checkBox_shortcutmenu_clicked() + + diff --git a/qad_options_dlg.py b/qad_options_dlg.py new file mode 100644 index 00000000..c4914b9a --- /dev/null +++ b/qad_options_dlg.py @@ -0,0 +1,895 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + comando OPTIONS per opzioni di QAD + + ------------------- + begin : 2016-10-02 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.PyQt.QtWidgets import QDialog, QWidget, QDialogButtonBox +from qgis.PyQt.QtCore import * +from qgis.PyQt.QtGui import * +from qgis.core import * +from qgis.utils import * + + +from .qad_options_ui import Ui_Options_Dialog + + +from .qad_variables import * +from .qad_msg import QadMsg, qadShowPluginPDFHelp +from . import qad_utils +from .qad_gripcolor_dlg import QadGripColorDialog +from .qad_windowcolor_dlg import QadColorContextEnum, QadColorElementEnum, QadWindowColorDialog +from .qad_dsettings_dlg import QadTOOLTIPAPPEARANCEDialog +from .qad_rightclick_dlg import QadRightClickDialog + + +# =============================================================================== +# QadOPTIONSTabIndexEnum class. +# =============================================================================== +class QadOPTIONSTabIndexEnum(): + DISPLAY = 0 + USER_PREFERENCES = 1 + DRAFTING = 2 + SELECTION = 3 + + +####################################################################################### +# Classe che gestisce l'interfaccia grafica del comando OPTIONS +class QadOPTIONSDialog(QDialog, QObject, Ui_Options_Dialog): + def __init__(self, plugIn, optionsTabIndex = None): + self.plugIn = plugIn + self.iface = self.plugIn.iface.mainWindow() + + QDialog.__init__(self) + # non passo il parent perchè altrimenti il font e la sua dimensione verrebbero ereditati dalla dialog scombinando tutto + #QDialog.__init__(self, self.iface) + + self.previewAutoSnapMarker = None + + self.setupUi(self) + self.setWindowTitle(QadMsg.getQADTitle() + " - " + self.windowTitle()) + + self.tempQadVariables = QadVariablesClass() + QadVariables.copyTo(self.tempQadVariables) + + # Inizializzazione del TAB "display" + self.init_display_tab() + + # Inizializzazione del TAB "user preferences" + self.init_user_preferences_tab() + + # Inizializzazione del TAB "drafting" + self.init_drafting_tab() + + # Inizializzazione del TAB "selection" + self.init_selection_tab() + + if optionsTabIndex is not None: + self.tabWidget.setCurrentIndex(optionsTabIndex) + else: + if self.plugIn.optionsLastUsedTabIndex == -1: # non inizializzato + self.plugIn.optionsLastUsedTabIndex = QadOPTIONSTabIndexEnum.DISPLAY + self.tabWidget.setCurrentIndex(self.plugIn.optionsLastUsedTabIndex) + + + ###################################### + # TAB "display" + def init_display_tab(self): + # Inizializzazione del TAB "display" + + # SHOWTEXTWINDOW + self.checkBox_SHOWTEXTWINDOW.setChecked(self.tempQadVariables.get(QadMsg.translate("Environment variables", "SHOWTEXTWINDOW"))) + + # CMDINPUTHISTORYMAX + historyMax = self.tempQadVariables.get(QadMsg.translate("Environment variables", "CMDINPUTHISTORYMAX")) + self.lineEdit_CMDINPUTHISTORYMAX.setText(str(historyMax)) + self.lineEdit_CMDINPUTHISTORYMAX.setValidator(QIntValidator(self.lineEdit_CMDINPUTHISTORYMAX)) + self.lineEdit_CMDINPUTHISTORYMAX.installEventFilter(self) + + # Memorizzo il valore dell'INPUTSEARCHOPTIONS + inputSearchOptions = self.tempQadVariables.get(QadMsg.translate("Environment variables", "INPUTSEARCHOPTIONS")) + self.checkBox_INPUTSEARCHOPTIONS_ON.setChecked(inputSearchOptions & QadINPUTSEARCHOPTIONSEnum.ON) + self.checkBox_INPUTSEARCHOPTIONS_AUTOCOMPLETE.setChecked(inputSearchOptions & QadINPUTSEARCHOPTIONSEnum.AUTOCOMPLETE) + self.checkBox_INPUTSEARCHOPTIONS_DISPLAY_LIST.setChecked(inputSearchOptions & QadINPUTSEARCHOPTIONSEnum.DISPLAY_LIST) + self.checkBox_INPUTSEARCHOPTIONS_DISPLAY_ICON.setChecked(inputSearchOptions & QadINPUTSEARCHOPTIONSEnum.DISPLAY_ICON) + self.checkBox_INPUTSEARCHOPTIONS_EXCLUDE_SYS_VAR.setChecked(inputSearchOptions & QadINPUTSEARCHOPTIONSEnum.EXCLUDE_SYS_VAR) + + # Memorizzo il valore di INPUTSEARCHDELAY + inputSearchDelay = self.tempQadVariables.get(QadMsg.translate("Environment variables", "INPUTSEARCHDELAY")) + self.lineEdit_INPUTSEARCHDELAY.setText(str(inputSearchDelay)) + self.lineEdit_INPUTSEARCHDELAY.setValidator(QIntValidator(self.lineEdit_INPUTSEARCHDELAY)) + self.lineEdit_INPUTSEARCHDELAY.installEventFilter(self) + + # Memorizzo il valore di TOLERANCE2COINCIDENT + tolerance2Coindident = self.tempQadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2COINCIDENT")) + self.lineEdit_TOLERANCE2COINCIDENT.setText(str(tolerance2Coindident)) + self.lineEdit_TOLERANCE2COINCIDENT.setValidator(QDoubleValidator(self.lineEdit_TOLERANCE2COINCIDENT)) + self.lineEdit_TOLERANCE2COINCIDENT.installEventFilter(self) + + # Memorizzo il valore di ARCMINSEGMENTQTY + arcMinSegmentQty = self.tempQadVariables.get(QadMsg.translate("Environment variables", "ARCMINSEGMENTQTY")) + self.lineEdit_ARCMINSEGMENTQTY.setText(str(arcMinSegmentQty)) + self.lineEdit_ARCMINSEGMENTQTY.setValidator(QIntValidator(self.lineEdit_ARCMINSEGMENTQTY)) + self.lineEdit_ARCMINSEGMENTQTY.installEventFilter(self) + + # Memorizzo il valore di CIRCLEMINSEGMENTQTY + circleMinSegmentQty = self.tempQadVariables.get(QadMsg.translate("Environment variables", "CIRCLEMINSEGMENTQTY")) + self.lineEdit_CIRCLEMINSEGMENTQTY.setText(str(circleMinSegmentQty)) + self.lineEdit_CIRCLEMINSEGMENTQTY.setValidator(QIntValidator(self.lineEdit_CIRCLEMINSEGMENTQTY)) + self.lineEdit_CIRCLEMINSEGMENTQTY.installEventFilter(self) + + # Memorizzo il valore di ELLIPSEARCMINSEGMENTQTY + ellipseArcMinSegmentQty = self.tempQadVariables.get(QadMsg.translate("Environment variables", "ELLIPSEARCMINSEGMENTQTY")) + self.lineEdit_ELLIPSEARCMINSEGMENTQTY.setText(str(ellipseArcMinSegmentQty)) + self.lineEdit_ELLIPSEARCMINSEGMENTQTY.setValidator(QIntValidator(self.lineEdit_ELLIPSEARCMINSEGMENTQTY)) + self.lineEdit_ELLIPSEARCMINSEGMENTQTY.installEventFilter(self) + + # Memorizzo il valore di ELLIPSEMINSEGMENTQTY + circleMinSegmentQty = self.tempQadVariables.get(QadMsg.translate("Environment variables", "ELLIPSEMINSEGMENTQTY")) + self.lineEdit_ELLIPSEMINSEGMENTQTY.setText(str(circleMinSegmentQty)) + self.lineEdit_ELLIPSEMINSEGMENTQTY.setValidator(QIntValidator(self.lineEdit_ELLIPSEMINSEGMENTQTY)) + self.lineEdit_ELLIPSEMINSEGMENTQTY.installEventFilter(self) + + # Memorizzo il valore di TOLERANCE2APPROXCURVE + tolerance2ApproxCurve = self.tempQadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) + self.lineEdit_TOLERANCE2APPROXCURVE.setText(str(tolerance2ApproxCurve)) + self.lineEdit_TOLERANCE2APPROXCURVE.setValidator(QDoubleValidator(self.lineEdit_TOLERANCE2APPROXCURVE)) + self.lineEdit_TOLERANCE2APPROXCURVE.installEventFilter(self) + + # Memorizzo il valore di CURSORSIZE + var = self.tempQadVariables.getVariable(QadMsg.translate("Environment variables", "CURSORSIZE")) + self.lineEdit_CURSORSIZE.setText(str(var.value)) + self.lineEdit_CURSORSIZE.setValidator(QIntValidator(self.lineEdit_CURSORSIZE)) + self.lineEdit_CURSORSIZE.installEventFilter(self) + self.horizontalSlider_CURSORSIZE.setMinimum(var.minNum) + self.horizontalSlider_CURSORSIZE.setMaximum(var.maxNum) + self.horizontalSlider_CURSORSIZE.setValue(var.value) + + self.checkBox_INPUTSEARCHOPTIONS_ON_clicked() + + + def accept_display_tab(self): + # Memorizzo il valore di SHOWTEXTWINDOW + newSHOWTEXTWINDOW = True if self.checkBox_SHOWTEXTWINDOW.checkState() == Qt.Checked else False + self.tempQadVariables.set(QadMsg.translate("Environment variables", "SHOWTEXTWINDOW"), newSHOWTEXTWINDOW) + + # Memorizzo il valore di CMDINPUTHISTORYMAX + SHistoryMax = self.lineEdit_CMDINPUTHISTORYMAX.text() + historyMax = qad_utils.str2int(SHistoryMax) + self.tempQadVariables.set(QadMsg.translate("Environment variables", "CMDINPUTHISTORYMAX"), historyMax) + + # Memorizzo il valore di INPUTSEARCHOPTIONS + newInputSearchOptions = 0 + if self.checkBox_INPUTSEARCHOPTIONS_ON.checkState() == Qt.Checked: + newInputSearchOptions = newInputSearchOptions | QadINPUTSEARCHOPTIONSEnum.ON + if self.checkBox_INPUTSEARCHOPTIONS_AUTOCOMPLETE.checkState() == Qt.Checked: + newInputSearchOptions = newInputSearchOptions | QadINPUTSEARCHOPTIONSEnum.AUTOCOMPLETE + if self.checkBox_INPUTSEARCHOPTIONS_DISPLAY_LIST.checkState() == Qt.Checked: + newInputSearchOptions = newInputSearchOptions | QadINPUTSEARCHOPTIONSEnum.DISPLAY_LIST + if self.checkBox_INPUTSEARCHOPTIONS_DISPLAY_ICON.checkState() == Qt.Checked: + newInputSearchOptions = newInputSearchOptions | QadINPUTSEARCHOPTIONSEnum.DISPLAY_ICON + if self.checkBox_INPUTSEARCHOPTIONS_EXCLUDE_SYS_VAR.checkState() == Qt.Checked: + newInputSearchOptions = newInputSearchOptions | QadINPUTSEARCHOPTIONSEnum.EXCLUDE_SYS_VAR + self.tempQadVariables.set(QadMsg.translate("Environment variables", "INPUTSEARCHOPTIONS"), newInputSearchOptions) + + # Memorizzo il valore di INPUTSEARCHDELAY + SInputSearchDelay = self.lineEdit_INPUTSEARCHDELAY.text() + InputSearchDelay = qad_utils.str2int(SInputSearchDelay) + self.tempQadVariables.set(QadMsg.translate("Environment variables", "INPUTSEARCHDELAY"), InputSearchDelay) + + # Memorizzo il valore di TOLERANCE2COINCIDENT + STolerance2Coincident = self.lineEdit_TOLERANCE2COINCIDENT.text() + Tolerance2Coincident = qad_utils.str2float(STolerance2Coincident) + self.tempQadVariables.set(QadMsg.translate("Environment variables", "TOLERANCE2COINCIDENT"), Tolerance2Coincident) + + # Memorizzo il valore di ARCMINSEGMENTQTY + SArcMinSegmentQty = self.lineEdit_ARCMINSEGMENTQTY.text() + ArcMinSegmentQty = qad_utils.str2int(SArcMinSegmentQty) + self.tempQadVariables.set(QadMsg.translate("Environment variables", "ARCMINSEGMENTQTY"), ArcMinSegmentQty) + + # Memorizzo il valore di CIRCLEMINSEGMENTQTY + SCircleMinSegmentQty = self.lineEdit_CIRCLEMINSEGMENTQTY.text() + CircleMinSegmentQty = qad_utils.str2int(SCircleMinSegmentQty) + self.tempQadVariables.set(QadMsg.translate("Environment variables", "CIRCLEMINSEGMENTQTY"), CircleMinSegmentQty) + + # Memorizzo il valore di ELLIPSEARCMINSEGMENTQTY + SEllipseArcMinSegmentQty = self.lineEdit_ELLIPSEARCMINSEGMENTQTY.text() + EllipseArcMinSegmentQty = qad_utils.str2int(SEllipseArcMinSegmentQty) + self.tempQadVariables.set(QadMsg.translate("Environment variables", "ELLIPSEARCMINSEGMENTQTY"), EllipseArcMinSegmentQty) + + # Memorizzo il valore di ELLIPSEMINSEGMENTQTY + SEllipseMinSegmentQty = self.lineEdit_ELLIPSEMINSEGMENTQTY.text() + EllipseMinSegmentQty = qad_utils.str2int(SEllipseMinSegmentQty) + self.tempQadVariables.set(QadMsg.translate("Environment variables", "ELLIPSEMINSEGMENTQTY"), EllipseMinSegmentQty) + + # Memorizzo il valore di TOLERANCE2APPROXCURVE + STolerance2ApproxCurve = self.lineEdit_TOLERANCE2APPROXCURVE.text() + Tolerance2ApproxCurve = qad_utils.str2float(STolerance2ApproxCurve) + self.tempQadVariables.set(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE"), Tolerance2ApproxCurve) + + # Memorizzo il valore di CURSORSIZE + SCursorSize = self.lineEdit_CURSORSIZE.text() + CursorSize = qad_utils.str2int(SCursorSize) + self.tempQadVariables.set(QadMsg.translate("Environment variables", "CURSORSIZE"), CursorSize) + + + def checkBox_INPUTSEARCHOPTIONS_ON_clicked(self): + value = True if self.checkBox_INPUTSEARCHOPTIONS_ON.checkState() == Qt.Checked else False + self.checkBox_INPUTSEARCHOPTIONS_AUTOCOMPLETE.setEnabled(value) + self.checkBox_INPUTSEARCHOPTIONS_DISPLAY_LIST.setEnabled(value) + self.checkBox_INPUTSEARCHOPTIONS_DISPLAY_ICON.setEnabled(value) + self.checkBox_INPUTSEARCHOPTIONS_EXCLUDE_SYS_VAR.setEnabled(value) + self.lineEdit_INPUTSEARCHDELAY.setEnabled(value) + self.label_INPUTSEARCHDELAY.setEnabled(value) + + + def horizontalSlider_CURSORSIZE_moved(self): + self.lineEdit_CURSORSIZE.setText(str(self.horizontalSlider_CURSORSIZE.value())) + + + def lineEdit_CURSORSIZE_textChanged(self): + value = qad_utils.str2int(self.lineEdit_CURSORSIZE.text()) + self.horizontalSlider_CURSORSIZE.setValue(value) + + + def lineEdit_CMDINPUTHISTORYMAX_Validation(self): + varName = QadMsg.translate("Environment variables", "CMDINPUTHISTORYMAX") + var = self.tempQadVariables.getVariable(QadMsg.translate("Environment variables", varName)) + return qad_utils.intLineEditWidgetValidation(self.lineEdit_CMDINPUTHISTORYMAX, \ + var, \ + QadMsg.translate("Options_Dialog", "Invalid maximum command history length")) + + + def lineEdit_INPUTSEARCHDELAY_Validation(self): + varName = QadMsg.translate("Environment variables", "INPUTSEARCHDELAY") + var = self.tempQadVariables.getVariable(QadMsg.translate("Environment variables", varName)) + return qad_utils.intLineEditWidgetValidation(self.lineEdit_INPUTSEARCHDELAY, \ + var, \ + QadMsg.translate("Options_Dialog", "Invalid delay time")) + + + def lineEdit_TOLERANCE2COINCIDENT_Validation(self): + varName = QadMsg.translate("Environment variables", "TOLERANCE2COINCIDENT") + var = self.tempQadVariables.getVariable(QadMsg.translate("Environment variables", varName)) + return qad_utils.floatLineEditWidgetValidation(self.lineEdit_TOLERANCE2COINCIDENT, \ + var, \ + QadMsg.translate("Options_Dialog", "Invalid tolerance value")) + + + def lineEdit_ARCMINSEGMENTQTY_Validation(self): + varName = QadMsg.translate("Environment variables", "ARCMINSEGMENTQTY") + var = self.tempQadVariables.getVariable(QadMsg.translate("Environment variables", varName)) + return qad_utils.intLineEditWidgetValidation(self.lineEdit_ARCMINSEGMENTQTY, \ + var, \ + QadMsg.translate("Options_Dialog", "Invalid minimum number of segments in an arc")) + + + def lineEdit_CIRCLEMINSEGMENTQTY_Validation(self): + varName = QadMsg.translate("Environment variables", "CIRCLEMINSEGMENTQTY") + var = self.tempQadVariables.getVariable(QadMsg.translate("Environment variables", varName)) + return qad_utils.intLineEditWidgetValidation(self.lineEdit_CIRCLEMINSEGMENTQTY, \ + var, \ + QadMsg.translate("Options_Dialog", "Invalid minimum number of segments in a circle")) + + + def lineEdit_ELLIPSEARCMINSEGMENTQTY_Validation(self): + varName = QadMsg.translate("Environment variables", "ELLIPSEARCMINSEGMENTQTY") + var = self.tempQadVariables.getVariable(QadMsg.translate("Environment variables", varName)) + return qad_utils.intLineEditWidgetValidation(self.lineEdit_ELLIPSEARCMINSEGMENTQTY, \ + var, \ + QadMsg.translate("Options_Dialog", "Invalid minimum number of segments in an arc of ellipse")) + + + def lineEdit_ELLIPSEMINSEGMENTQTY_Validation(self): + varName = QadMsg.translate("Environment variables", "ELLIPSEMINSEGMENTQTY") + var = self.tempQadVariables.getVariable(QadMsg.translate("Environment variables", varName)) + return qad_utils.intLineEditWidgetValidation(self.lineEdit_ELLIPSEMINSEGMENTQTY, \ + var, \ + QadMsg.translate("Options_Dialog", "Invalid minimum number of segments in an ellipse")) + + + def lineEdit_TOLERANCE2APPROXCURVE_Validation(self): + varName = QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE") + var = self.tempQadVariables.getVariable(QadMsg.translate("Environment variables", varName)) + return qad_utils.floatLineEditWidgetValidation(self.lineEdit_TOLERANCE2APPROXCURVE, \ + var, \ + QadMsg.translate("Options_Dialog", "Invalid tolerance between real and segmented curve")) + + + def lineEdit_CURSORSIZE_Validation(self): + varName = QadMsg.translate("Environment variables", "CURSORSIZE") + var = self.tempQadVariables.getVariable(QadMsg.translate("Environment variables", varName)) + return qad_utils.intLineEditWidgetValidation(self.lineEdit_CURSORSIZE, \ + var, \ + QadMsg.translate("Options_Dialog", "Invalid crosshair size")) + + + def Button_TextWindowColor_clicked(self): + Form = QadWindowColorDialog(self.plugIn, self, QadColorContextEnum.COMMAND_LINE, QadColorElementEnum.COMMAND_HISTORY_BACKGROUND) + + if Form.exec_() == QDialog.Accepted: + # copio i valori dei colori in self.tempQadVariables + variables = Form.getSysVariableList() + for variable in variables: + self.tempQadVariables.set(variable.name, variable.value) + + self.refreshPreviewColor() + + + ###################################### + # TAB "user preferences" + def init_user_preferences_tab(self): + # Inizializzazione del TAB "user preferences" + + # SHORTCUTMENU + shortcutmenu = self.tempQadVariables.get(QadMsg.translate("Environment variables", "SHORTCUTMENU")) + if shortcutmenu == 0: + self.checkBox_shortcutmenu.setChecked(False) + else: + self.checkBox_shortcutmenu.setChecked(True) + self.checkBox_shortcutmenu_clicked() + + def checkBox_shortcutmenu_clicked(self): + if self.checkBox_shortcutmenu.checkState() == Qt.Checked: + self.button_rightclick.setEnabled(True) + else: + self.button_rightclick.setEnabled(False) + + + def button_rightclick_clicked(self): + Form = QadRightClickDialog(self.plugIn, self) + if Form.exec_() == QDialog.Accepted: + # copio i valori dei colori in self.tempQadVariables + variables = Form.getSysVariableList() + for variable in variables: + self.tempQadVariables.set(variable.name, variable.value) + self.init_user_preferences_tab() + + + def accept_user_preferences_tab(self): + if self.checkBox_shortcutmenu.checkState() == Qt.Unchecked: + self.tempQadVariables.set(QadMsg.translate("Environment variables", "SHORTCUTMENU"), 0) + elif self.tempQadVariables.get(QadMsg.translate("Environment variables", "SHORTCUTMENU")) == 0: + self.tempQadVariables.set(QadMsg.translate("Environment variables", "SHORTCUTMENU"), 11) # inizializzo questo default + + + ###################################### + # TAB "drafting" + def init_drafting_tab(self): + # Inizializzazione del TAB "drafting" + + # AUTOSNAP + autoSnap = self.tempQadVariables.get(QadMsg.translate("Environment variables", "AUTOSNAP")) + self.checkBox_AUTOSNAP_DISPLAY_MARK.setChecked(autoSnap & QadAUTOSNAPEnum.DISPLAY_MARK) + self.checkBox_AUTOSNAP_MAGNET.setChecked(autoSnap & QadAUTOSNAPEnum.MAGNET) + self.checkBox_AUTOSNAP_DISPLAY_TOOLTIPS.setChecked(autoSnap & QadAUTOSNAPEnum.DISPLAY_TOOLTIPS) + + # APBOX + apBox = False if self.tempQadVariables.get(QadMsg.translate("Environment variables", "APBOX")) == 0 else True + self.checkBox_APBOX.setChecked(apBox) + + # AUTOSNAPSIZE + # aggiungo il QWidget chiamato QadPreviewAutoSnapMarker + # che eredita la posizione di widget_AUTOSNAPSIZE (che viene nascosto) + self.widget_AUTOSNAPSIZE.setHidden(True) + autoSnapColor = QColor(self.tempQadVariables.get(QadMsg.translate("Environment variables", "AUTOSNAPCOLOR"))) + self.previewAutoSnapMarker = QadPreviewAutoSnapMarker(self.plugIn, autoSnapColor, self.widget_AUTOSNAPSIZE.parent()) + self.previewAutoSnapMarker.setGeometry(self.widget_AUTOSNAPSIZE.geometry()) + self.previewAutoSnapMarker.setObjectName("previewAutoSnapMarker") + + var = self.tempQadVariables.getVariable(QadMsg.translate("Environment variables", "AUTOSNAPSIZE")) + self.horizontalSlider_AUTOSNAPSIZE.setMinimum(var.minNum) + #self.horizontalSlider_AUTOSNAPSIZE.setMaximum(var.maxNum) + self.horizontalSlider_AUTOSNAPSIZE.setMaximum(20) # oltre i 20 non ci sta nel riquadro + self.horizontalSlider_AUTOSNAPSIZE.setValue(var.value) + + # POLARMODE + polarMode = self.tempQadVariables.get(QadMsg.translate("Environment variables", "POLARMODE")) + if polarMode & QadPOLARMODEnum.SHIFT_TO_ACQUIRE: + self.radioButton_POLARMODE_SHIFT_TO_ACQUIRE.setChecked(True) + else: + self.radioButton_POLARMODE_AUTO_ACQUIRE.setChecked(True) + + # APERTURE + # aggiungo il QWidget chiamato QadPreviewAperture + # che eredita la posizione di widget_APERTURESIZE (che viene nascosto) + self.widget_APERTURESIZE.setHidden(True) + apertureColor = QColor(self.tempQadVariables.get(QadMsg.translate("Environment variables", "PICKBOXCOLOR"))) + cursorColor = QColor(self.tempQadVariables.get(QadMsg.translate("Environment variables", "CURSORCOLOR"))) + self.previewAperture = QadPreviewAperture(self.plugIn, apertureColor, cursorColor, self.widget_APERTURESIZE.parent()) + self.previewAperture.setGeometry(self.widget_APERTURESIZE.geometry()) + self.previewAperture.setObjectName("previewAperture") + + var = self.tempQadVariables.getVariable(QadMsg.translate("Environment variables", "APERTURE")) + self.horizontalSlider_APERTURE.setMinimum(var.minNum) + #self.horizontalSlider_APERTURE.setMaximum(var.maxNum) + self.horizontalSlider_APERTURE.setMaximum(20) # oltre i 20 non ci sta nel riquadro + self.horizontalSlider_APERTURE.setValue(var.value) + + + def button_DraftingTooltipSettings_clicked(self): + Form = QadTOOLTIPAPPEARANCEDialog(self.plugIn, self) + if Form.exec_() == QDialog.Accepted: + # copio i valori dei colori in self.tempQadVariables + variables = Form.getSysVariableList() + for variable in variables: + self.tempQadVariables.set(variable.name, variable.value) + + + def accept_drafting_tab(self): + # AUTOSNAP + autoSnap = self.tempQadVariables.get(QadMsg.translate("Environment variables", "AUTOSNAP")) + + if self.checkBox_AUTOSNAP_DISPLAY_MARK.checkState() == Qt.Checked: + autoSnap = autoSnap | QadAUTOSNAPEnum.DISPLAY_MARK # aggiungo i bit + else: + autoSnap = autoSnap &~ QadAUTOSNAPEnum.DISPLAY_MARK # tolgo il bit + + if self.checkBox_AUTOSNAP_MAGNET.checkState() == Qt.Checked: + autoSnap = autoSnap | QadAUTOSNAPEnum.MAGNET # aggiungo i bit + else: + autoSnap = autoSnap &~ QadAUTOSNAPEnum.MAGNET # tolgo il bit + + if self.checkBox_AUTOSNAP_DISPLAY_TOOLTIPS.checkState() == Qt.Checked: + autoSnap = autoSnap | QadAUTOSNAPEnum.DISPLAY_TOOLTIPS # aggiungo i bit + else: + autoSnap = autoSnap &~ QadAUTOSNAPEnum.DISPLAY_TOOLTIPS # tolgo il bit + + self.tempQadVariables.set(QadMsg.translate("Environment variables", "AUTOSNAP"), autoSnap) + + # APBOX + newAPBOX = 1 if self.checkBox_APBOX.checkState() == Qt.Checked else 0 + self.tempQadVariables.set(QadMsg.translate("Environment variables", "APBOX"), newAPBOX) + + # AUTOSNAPSIZE + self.tempQadVariables.set(QadMsg.translate("Environment variables", "AUTOSNAPSIZE"), self.horizontalSlider_AUTOSNAPSIZE.value()) + + # POLARMODE + polarMode = self.tempQadVariables.get(QadMsg.translate("Environment variables", "POLARMODE")) + + if self.radioButton_POLARMODE_SHIFT_TO_ACQUIRE.isChecked(): + polarMode = polarMode | QadPOLARMODEnum.SHIFT_TO_ACQUIRE # aggiungo i bit + else: + polarMode = polarMode &~ QadPOLARMODEnum.SHIFT_TO_ACQUIRE # tolgo il bit + + self.tempQadVariables.set(QadMsg.translate("Environment variables", "POLARMODE"), polarMode) + + # APERTURE + self.tempQadVariables.set(QadMsg.translate("Environment variables", "APERTURE"), self.horizontalSlider_APERTURE.value()) + + + def horizontalSlider_AUTOSNAPSIZE_changed(self): + if self.previewAutoSnapMarker is not None: + self.previewAutoSnapMarker.size = self.horizontalSlider_AUTOSNAPSIZE.value() + self.previewAutoSnapMarker.update() # forzo il disegno del preview + + + def horizontalSlider_APERTURE_changed(self): + if self.previewAperture is not None: + self.previewAperture.size = self.horizontalSlider_APERTURE.value() + self.previewAperture.update() # forzo il disegno del preview + + + def Button_AutoSnapWindowColor_clicked(self): + Form = QadWindowColorDialog(self.plugIn, self, QadColorContextEnum.MODEL_SPACE_2D, QadColorElementEnum.AUTOSNAP_MARKER) + + if Form.exec_() == QDialog.Accepted: + # copio i valori dei colori in self.tempQadVariables + variables = Form.getSysVariableList() + for variable in variables: + self.tempQadVariables.set(variable.name, variable.value) + + self.refreshPreviewColor() + + + ###################################### + # TAB "selection" + def init_selection_tab(self): + # Inizializzazione del TAB "selection" + + # PICKBOX + # aggiungo il QWidget chiamato QadPreviewPickBox + # che eredita la posizione di widget_PICKBOX (che viene nascosto) + self.widget_PICKBOX.setHidden(True) + pickBoxColor = QColor(self.tempQadVariables.get(QadMsg.translate("Environment variables", "PICKBOXCOLOR"))) + self.previewPickBox = QadPreviewPickBox(self.plugIn, pickBoxColor, self.widget_PICKBOX.parent()) + self.previewPickBox.setGeometry(self.widget_PICKBOX.geometry()) + self.previewPickBox.setObjectName("previewPickBox") + + var = self.tempQadVariables.getVariable(QadMsg.translate("Environment variables", "PICKBOX")) + self.horizontalSlider_PICKBOX.setMinimum(var.minNum) + #self.horizontalSlider_PICKBOX.setMaximum(var.maxNum) + self.horizontalSlider_PICKBOX.setMaximum(20) # oltre i 20 non ci sta nel riquadro + self.horizontalSlider_PICKBOX.setValue(var.value) + + # PICKFIRST + pickFirst = False if self.tempQadVariables.get(QadMsg.translate("Environment variables", "PICKFIRST")) == 0 else True + self.checkBox_PICKFIRST.setChecked(pickFirst) + + # PICKADD + pickAdd = self.tempQadVariables.get(QadMsg.translate("Environment variables", "PICKADD")) + if pickAdd == 0: + self.checkBox_PICKADD.setChecked(True) + else: + self.checkBox_PICKADD.setChecked(False) + + # GRIPSIZE + # aggiungo il QWidget chiamato QadPreviewGripSize + # che eredita la posizione di widget_GRIPSIZE (che viene nascosto) + self.widget_GRIPSIZE.setHidden(True) + fillColor = QColor(self.tempQadVariables.get(QadMsg.translate("Environment variables", "GRIPCOLOR"))) + borderColor = QColor(self.tempQadVariables.get(QadMsg.translate("Environment variables", "GRIPCONTOUR"))) + self.previewGripSize = QadPreviewGripSize(self.plugIn, fillColor, borderColor, self.widget_GRIPSIZE.parent()) + self.previewGripSize.setGeometry(self.widget_GRIPSIZE.geometry()) + self.previewGripSize.setObjectName("previewGripSize") + + var = self.tempQadVariables.getVariable(QadMsg.translate("Environment variables", "GRIPSIZE")) + self.horizontalSlider_GRIPSIZE.setMinimum(var.minNum) + #self.horizontalSlider_PGRIPSIZE.setMaximum(var.maxNum) + self.horizontalSlider_GRIPSIZE.setMaximum(20) # oltre i 20 non ci sta nel riquadro + self.horizontalSlider_GRIPSIZE.setValue(var.value) + + # GRIPS + grips = False if self.tempQadVariables.get(QadMsg.translate("Environment variables", "GRIPS")) == 0 else True + self.checkBox_GRIPS.setChecked(grips) + + # GRIPMULTIFUNCTIONAL + gripMultiFunctional = self.tempQadVariables.get(QadMsg.translate("Environment variables", "GRIPMULTIFUNCTIONAL")) + self.checkBox_GRIPMULTIFUNCTIONAL_ON_DYNAMIC_MENU_AND_HOT_GRIPT.setChecked(gripMultiFunctional & QadGRIPMULTIFUNCTIONALEnum.ON_DYNAMIC_MENU_AND_HOT_GRIPT) + + # GRIPOBJLIMIT + gripObjLimit = self.tempQadVariables.get(QadMsg.translate("Environment variables", "GRIPOBJLIMIT")) + self.lineEdit_GRIPOBJLIMIT.setText(str(gripObjLimit)) + self.lineEdit_GRIPOBJLIMIT.setValidator(QIntValidator(self.lineEdit_GRIPOBJLIMIT)) + self.lineEdit_GRIPOBJLIMIT.installEventFilter(self) + + self.checkBox_GRIPS_ON_clicked() + + + def accept_selection_tab(self): + # PICKBOX + self.tempQadVariables.set(QadMsg.translate("Environment variables", "PICKBOX"), self.horizontalSlider_PICKBOX.value()) + + # PICKFIRST + pickFirst = 1 if self.checkBox_PICKFIRST.checkState() == Qt.Checked else 0 + self.tempQadVariables.set(QadMsg.translate("Environment variables", "PICKFIRST"), pickFirst) + + # PICKADD + pickAdd = 0 if self.checkBox_PICKADD.checkState() == Qt.Checked else 1 + self.tempQadVariables.set(QadMsg.translate("Environment variables", "PICKADD"), pickAdd) + + # GRIPSIZE + self.tempQadVariables.set(QadMsg.translate("Environment variables", "GRIPSIZE"), self.horizontalSlider_GRIPSIZE.value()) + + # GRIPS + grips = 1 if self.checkBox_GRIPS.checkState() == Qt.Checked else 0 + self.tempQadVariables.set(QadMsg.translate("Environment variables", "GRIPS"), grips) + + # GRIPMULTIFUNCTIONAL + gripMultiFunctional = self.tempQadVariables.get(QadMsg.translate("Environment variables", "GRIPMULTIFUNCTIONAL")) + if self.checkBox_GRIPMULTIFUNCTIONAL_ON_DYNAMIC_MENU_AND_HOT_GRIPT.checkState() == Qt.Checked: + gripMultiFunctional = gripMultiFunctional | QadGRIPMULTIFUNCTIONALEnum.ON_DYNAMIC_MENU_AND_HOT_GRIPT # aggiungo i bit + else: + gripMultiFunctional = gripMultiFunctional &~ QadGRIPMULTIFUNCTIONALEnum.ON_DYNAMIC_MENU_AND_HOT_GRIPT # tolgo i bit + self.tempQadVariables.set(QadMsg.translate("Environment variables", "GRIPMULTIFUNCTIONAL"), gripMultiFunctional) + + # GRIPOBJLIMIT + gripObjLimit = qad_utils.str2int(self.lineEdit_GRIPOBJLIMIT.text()) + self.tempQadVariables.set(QadMsg.translate("Environment variables", "GRIPOBJLIMIT"), gripObjLimit) + + + def checkBox_GRIPS_ON_clicked(self): + value = True if self.checkBox_GRIPS.checkState() == Qt.Checked else False + self.checkBox_GRIPMULTIFUNCTIONAL_ON_DYNAMIC_MENU_AND_HOT_GRIPT.setEnabled(value) + self.lineEdit_GRIPOBJLIMIT.setEnabled(value) + self.label_GRIPOBJLIMIT.setEnabled(value) + + + def horizontalSlider_PICKBOX_changed(self): + if self.previewPickBox is not None: + self.previewPickBox.size = self.horizontalSlider_PICKBOX.value() + self.previewPickBox.update() # forzo il disegno del preview + + + def horizontalSlider_GRIPSIZE_changed(self): + if self.previewGripSize is not None: + self.previewGripSize.size = self.horizontalSlider_GRIPSIZE.value() + self.previewGripSize.update() # forzo il disegno del preview + + + def lineEdit_GRIPOBJLIMIT_Validation(self): + varName = QadMsg.translate("Environment variables", "GRIPOBJLIMIT") + var = self.tempQadVariables.getVariable(QadMsg.translate("Environment variables", varName)) + return qad_utils.intLineEditWidgetValidation(self.lineEdit_GRIPOBJLIMIT, \ + var, \ + QadMsg.translate("Options_Dialog", "Invalid object selection limit for display of grips")) + + + def button_GripColor_clicked(self): + Form = QadGripColorDialog(self.plugIn, self, \ + self.tempQadVariables.get(QadMsg.translate("Environment variables", "GRIPCOLOR")), \ + self.tempQadVariables.get(QadMsg.translate("Environment variables", "GRIPHOT")), \ + self.tempQadVariables.get(QadMsg.translate("Environment variables", "GRIPHOVER")), \ + self.tempQadVariables.get(QadMsg.translate("Environment variables", "GRIPCONTOUR"))) + + if Form.exec_() == QDialog.Accepted: + self.tempQadVariables.set(QadMsg.translate("Environment variables", "GRIPCOLOR"), Form.gripColor) + self.tempQadVariables.set(QadMsg.translate("Environment variables", "GRIPHOT"), Form.gripHot) + self.tempQadVariables.set(QadMsg.translate("Environment variables", "GRIPHOVER"), Form.gripHover) + self.tempQadVariables.set(QadMsg.translate("Environment variables", "GRIPCONTOUR"), Form.gripContour) + + self.previewGripSize.fillColor = QColor(Form.gripColor) + self.previewGripSize.borderColor = QColor(Form.gripContour) + + + ###################################### + # Funzioni generiche + def intLineEditWidgetValidation(self, widget, varName, msg): + var = self.tempQadVariables.getVariable(QadMsg.translate("Environment variables", varName)) + + err = False + string = widget.text() + if qad_utils.str2int(string) is None: + err = True + else: + if var.minNum is not None: + if qad_utils.str2int(string) < var.minNum: + err = True + if var.maxNum is not None: + if qad_utils.str2int(string) > var.maxNum: + err = True + + if err: + msg = msg + QadMsg.translate("QAD", ": enter a number") + if var.minNum is not None: + msg = msg + QadMsg.translate("QAD", " >= {0}").format(str(var.minNum)) + if var.maxNum is not None: + if var.minNum is not None: + msg = msg + QadMsg.translate("QAD", " and") + msg = msg + QadMsg.translate("QAD", " <= {0}").format(str(var.maxNum)) + msg = msg + "." + QMessageBox.critical(self, QadMsg.getQADTitle(), msg) + widget.setFocus() + widget.selectAll() + return False + return True + + + def floatLineEditWidgetValidation(self, widget, varName, msg): + var = self.tempQadVariables.getVariable(QadMsg.translate("Environment variables", varName)) + + err = False + string = widget.text() + if qad_utils.str2float(string) is None: + err = True + else: + if var.minNum is not None: + if qad_utils.str2float(string) < var.minNum: + err = True + if var.maxNum is not None: + if qad_utils.str2float(string) > var.maxNum: + err = True + + if err: + msg = msg + QadMsg.translate("QAD", ": enter a number") + if var.minNum is not None: + minValMsg = msg + QadMsg.translate("QAD", " > {0}").format(str(var.minNum)) + else: + minValMsg = "" + if var.maxNum is not None: + if len(minValMsg) > 0: + msg = msg + QadMsg.translate("QAD", " and") + msg = msg + QadMsg.translate("QAD", " < {0}").format(str(var.maxNum)) + msg = msg + "." + QMessageBox.critical(self, QadMsg.getQADTitle(), msg) + widget.setFocus() + widget.selectAll() + return False + return True + + def eventFilter(self, obj, event): + if event is not None: + if event.type() == QEvent.FocusOut: + if obj == self.lineEdit_CMDINPUTHISTORYMAX: + return not self.lineEdit_CMDINPUTHISTORYMAX_Validation() + elif obj == self.lineEdit_INPUTSEARCHDELAY: + return not self.lineEdit_INPUTSEARCHDELAY_Validation() + elif obj == self.lineEdit_TOLERANCE2COINCIDENT: + return not self.lineEdit_TOLERANCE2COINCIDENT_Validation() + elif obj == self.lineEdit_ARCMINSEGMENTQTY: + return not self.lineEdit_ARCMINSEGMENTQTY_Validation() + elif obj == self.lineEdit_CIRCLEMINSEGMENTQTY: + return not self.lineEdit_CIRCLEMINSEGMENTQTY_Validation() + elif obj == self.lineEdit_ELLIPSEARCMINSEGMENTQTY: + return not self.lineEdit_ELLIPSEARCMINSEGMENTQTY_Validation() + elif obj == self.lineEdit_ELLIPSEMINSEGMENTQTY: + return not self.lineEdit_ELLIPSEMINSEGMENTQTY_Validation() + elif obj == self.lineEdit_TOLERANCE2APPROXCURVE: + return not self.lineEdit_TOLERANCE2APPROXCURVE_Validation() + elif obj == self.lineEdit_CURSORSIZE: + return not self.lineEdit_CURSORSIZE_Validation() + elif obj == self.lineEdit_GRIPOBJLIMIT: + return not self.lineEdit_GRIPOBJLIMIT_Validation() + + # standard event processing + return QObject.eventFilter(self, obj, event); + + + # ============================================================================ + # refreshPreviewColor + # ============================================================================ + def refreshPreviewColor(self): + pickBoxColor = QColor(self.tempQadVariables.get(QadMsg.translate("Environment variables", "PICKBOXCOLOR"))) + self.previewPickBox.color = pickBoxColor + autoSnapColor = QColor(self.tempQadVariables.get(QadMsg.translate("Environment variables", "AUTOSNAPCOLOR"))) + self.previewAutoSnapMarker.color = autoSnapColor + apertureColor = QColor(self.tempQadVariables.get(QadMsg.translate("Environment variables", "PICKBOXCOLOR"))) + cursorColor = QColor(self.tempQadVariables.get(QadMsg.translate("Environment variables", "CURSORCOLOR"))) + self.previewAperture.color = apertureColor + self.previewAperture.cursorColor = cursorColor + + + def ButtonBOX_Accepted(self): + self.apply() + QDialog.accept(self) + + + def ButtonBOX_Apply(self, button): + if self.buttonBox.standardButton(button) == QDialogButtonBox.Apply: + self.apply() + elif self.buttonBox.standardButton(button) == QDialogButtonBox.Cancel: + self.close() + + return True + + + def apply(self): + self.accept_display_tab() + self.accept_user_preferences_tab() + self.accept_drafting_tab() + self.accept_selection_tab() + + self.tempQadVariables.copyTo(QadVariables) + QadVariables.save() + self.plugIn.UpdatedVariablesEvent() + + self.plugIn.optionsLastUsedTabIndex = self.tabWidget.currentIndex() + + + def ButtonHELP_Pressed(self): + qadShowPluginPDFHelp(QadMsg.translate("Help", "OPTIONS")) + + +# =============================================================================== +# QadPreviewAutoSnapMarker class. +# =============================================================================== +class QadPreviewAutoSnapMarker(QWidget): + def __init__(self, plugIn, color, parent = None, windowFlags = Qt.Widget): + self.plugIn = plugIn + self.color = color + self.size = 0 + QWidget.__init__(self, parent, windowFlags) + + def paintEvent(self, event): + rect = self.rect() + painter = QPainter(self) + painter.fillRect(rect, self.plugIn.canvas.canvasColor()) + if self.size == 0: + return + size = rect.width()/2 if self.size > rect.width()/2 else self.size # oltre non ci sta nel riquadro + center = rect.center() + x1 = center.x() - size + y1 = center.y() - size + dblSize = size * 2 + 1 + painter.setRenderHint(QPainter.Antialiasing) + painter.setPen(QPen(self.color, 2)) + #painter.setPen(QPen(self.color, 12, Qt.DashDotLine, Qt.RoundCap)) + #painter.drawLine(x1, y1, x2, y2) + painter.drawRect(x1, y1, dblSize, dblSize) + + +# =============================================================================== +# QadPreviewAperture class. +# =============================================================================== +class QadPreviewAperture(QWidget): + def __init__(self, plugIn, color, cursorColor, parent = None, windowFlags = Qt.Widget): + self.plugIn = plugIn + self.color = color + self.cursorColor = cursorColor + self.size = 0 + QWidget.__init__(self, parent, windowFlags) + + def paintEvent(self, event): + rect = self.rect() + painter = QPainter(self) + painter.fillRect(rect, self.plugIn.canvas.canvasColor()) + if self.size == 0: + return + size = rect.width()/2 if self.size > rect.width()/2 else self.size # oltre non ci sta nel riquadro + center = rect.center() + x1 = center.x() - size + y1 = center.y() - size + dblSize = size * 2 + 1 + painter.setRenderHint(QPainter.Antialiasing) + painter.setPen(QPen(self.cursorColor, 1)) + painter.drawLine(center.x(), 0, center.x(), rect.height()) + painter.drawLine(0, center.y(), rect.width(), center.y()) + painter.setPen(QPen(self.color, 1, Qt.DotLine)) + painter.drawRect(x1, y1, dblSize, dblSize) + + +# =============================================================================== +# QadPreviewPickBox class. +# =============================================================================== +class QadPreviewPickBox(QWidget): + def __init__(self, plugIn, color, parent = None, windowFlags = Qt.Widget): + self.plugIn = plugIn + self.color = color + self.size = 0 + QWidget.__init__(self, parent, windowFlags) + + def paintEvent(self, event): + rect = self.rect() + painter = QPainter(self) + painter.fillRect(rect, self.plugIn.canvas.canvasColor()) + if self.size == 0: + return + size = rect.width()/2 if self.size > rect.width()/2 else self.size # oltre non ci sta nel riquadro + center = rect.center() + x1 = center.x() - size + y1 = center.y() - size + dblSize = size * 2 + 1 + + painter.setRenderHint(QPainter.Antialiasing) + painter.setPen(QPen(self.color, 1)) + painter.drawRect(x1, y1, dblSize, dblSize) + + +# =============================================================================== +# QadPreviewGripSize class. +# =============================================================================== +class QadPreviewGripSize(QWidget): + def __init__(self, plugIn, fillColor, borderColor, parent = None, windowFlags = Qt.Widget): + self.plugIn = plugIn + self.fillColor = fillColor + self.borderColor = borderColor + self.size = 0 + QWidget.__init__(self, parent, windowFlags) + + def paintEvent(self, event): + rect = self.rect() + painter = QPainter(self) + painter.fillRect(rect, self.plugIn.canvas.canvasColor()) + if self.size == 0: + return + size = rect.width()/2 if self.size > rect.width()/2 else self.size # oltre non ci sta nel riquadro + center = rect.center() + x1 = center.x() - size + y1 = center.y() - size + dblSize = size * 2 + 1 + + painter.setRenderHint(QPainter.Antialiasing) + painter.fillRect(x1, y1, dblSize, dblSize, self.fillColor) + painter.setPen(QPen(self.borderColor, 1)) + painter.drawRect(x1, y1, dblSize, dblSize) + \ No newline at end of file diff --git a/qad_options_ui.py b/qad_options_ui.py new file mode 100644 index 00000000..ca73a5d3 --- /dev/null +++ b/qad_options_ui.py @@ -0,0 +1,358 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file '.\qad_options.ui' +# +# Created by: PyQt5 UI code generator 5.15.4 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_Options_Dialog(object): + def setupUi(self, Options_Dialog): + Options_Dialog.setObjectName("Options_Dialog") + Options_Dialog.setWindowModality(QtCore.Qt.NonModal) + Options_Dialog.resize(591, 386) + Options_Dialog.setMinimumSize(QtCore.QSize(591, 386)) + Options_Dialog.setMaximumSize(QtCore.QSize(591, 386)) + Options_Dialog.setSizeGripEnabled(False) + Options_Dialog.setModal(False) + self.buttonBox = QtWidgets.QDialogButtonBox(Options_Dialog) + self.buttonBox.setGeometry(QtCore.QRect(240, 350, 341, 32)) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Apply|QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Help|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.tabWidget = QtWidgets.QTabWidget(Options_Dialog) + self.tabWidget.setGeometry(QtCore.QRect(10, 10, 571, 331)) + self.tabWidget.setMaximumSize(QtCore.QSize(16777215, 16777215)) + self.tabWidget.setObjectName("tabWidget") + self.DisplayTab = QtWidgets.QWidget() + self.DisplayTab.setObjectName("DisplayTab") + self.groupBox = QtWidgets.QGroupBox(self.DisplayTab) + self.groupBox.setGeometry(QtCore.QRect(10, 10, 251, 291)) + self.groupBox.setObjectName("groupBox") + self.Button_TextWindowColor = QtWidgets.QPushButton(self.groupBox) + self.Button_TextWindowColor.setGeometry(QtCore.QRect(20, 260, 91, 23)) + self.Button_TextWindowColor.setObjectName("Button_TextWindowColor") + self.checkBox_INPUTSEARCHOPTIONS_AUTOCOMPLETE = QtWidgets.QCheckBox(self.groupBox) + self.checkBox_INPUTSEARCHOPTIONS_AUTOCOMPLETE.setGeometry(QtCore.QRect(20, 100, 211, 17)) + self.checkBox_INPUTSEARCHOPTIONS_AUTOCOMPLETE.setObjectName("checkBox_INPUTSEARCHOPTIONS_AUTOCOMPLETE") + self.checkBox_SHOWTEXTWINDOW = QtWidgets.QCheckBox(self.groupBox) + self.checkBox_SHOWTEXTWINDOW.setGeometry(QtCore.QRect(10, 20, 231, 20)) + self.checkBox_SHOWTEXTWINDOW.setObjectName("checkBox_SHOWTEXTWINDOW") + self.label_4 = QtWidgets.QLabel(self.groupBox) + self.label_4.setGeometry(QtCore.QRect(50, 50, 191, 21)) + self.label_4.setWordWrap(True) + self.label_4.setObjectName("label_4") + self.lineEdit_CMDINPUTHISTORYMAX = QtWidgets.QLineEdit(self.groupBox) + self.lineEdit_CMDINPUTHISTORYMAX.setGeometry(QtCore.QRect(10, 50, 31, 20)) + self.lineEdit_CMDINPUTHISTORYMAX.setObjectName("lineEdit_CMDINPUTHISTORYMAX") + self.lineEdit_INPUTSEARCHDELAY = QtWidgets.QLineEdit(self.groupBox) + self.lineEdit_INPUTSEARCHDELAY.setGeometry(QtCore.QRect(20, 180, 41, 20)) + self.lineEdit_INPUTSEARCHDELAY.setObjectName("lineEdit_INPUTSEARCHDELAY") + self.label_INPUTSEARCHDELAY = QtWidgets.QLabel(self.groupBox) + self.label_INPUTSEARCHDELAY.setGeometry(QtCore.QRect(70, 180, 171, 21)) + self.label_INPUTSEARCHDELAY.setWordWrap(True) + self.label_INPUTSEARCHDELAY.setObjectName("label_INPUTSEARCHDELAY") + self.checkBox_INPUTSEARCHOPTIONS_ON = QtWidgets.QCheckBox(self.groupBox) + self.checkBox_INPUTSEARCHOPTIONS_ON.setGeometry(QtCore.QRect(10, 80, 231, 17)) + self.checkBox_INPUTSEARCHOPTIONS_ON.setObjectName("checkBox_INPUTSEARCHOPTIONS_ON") + self.checkBox_INPUTSEARCHOPTIONS_DISPLAY_LIST = QtWidgets.QCheckBox(self.groupBox) + self.checkBox_INPUTSEARCHOPTIONS_DISPLAY_LIST.setGeometry(QtCore.QRect(20, 120, 211, 17)) + self.checkBox_INPUTSEARCHOPTIONS_DISPLAY_LIST.setObjectName("checkBox_INPUTSEARCHOPTIONS_DISPLAY_LIST") + self.checkBox_INPUTSEARCHOPTIONS_DISPLAY_ICON = QtWidgets.QCheckBox(self.groupBox) + self.checkBox_INPUTSEARCHOPTIONS_DISPLAY_ICON.setGeometry(QtCore.QRect(20, 140, 211, 17)) + self.checkBox_INPUTSEARCHOPTIONS_DISPLAY_ICON.setObjectName("checkBox_INPUTSEARCHOPTIONS_DISPLAY_ICON") + self.checkBox_INPUTSEARCHOPTIONS_EXCLUDE_SYS_VAR = QtWidgets.QCheckBox(self.groupBox) + self.checkBox_INPUTSEARCHOPTIONS_EXCLUDE_SYS_VAR.setGeometry(QtCore.QRect(20, 160, 221, 17)) + self.checkBox_INPUTSEARCHOPTIONS_EXCLUDE_SYS_VAR.setObjectName("checkBox_INPUTSEARCHOPTIONS_EXCLUDE_SYS_VAR") + self.groupBox_2 = QtWidgets.QGroupBox(self.DisplayTab) + self.groupBox_2.setGeometry(QtCore.QRect(270, 10, 281, 221)) + self.groupBox_2.setObjectName("groupBox_2") + self.lineEdit_ARCMINSEGMENTQTY = QtWidgets.QLineEdit(self.groupBox_2) + self.lineEdit_ARCMINSEGMENTQTY.setGeometry(QtCore.QRect(10, 50, 41, 20)) + self.lineEdit_ARCMINSEGMENTQTY.setObjectName("lineEdit_ARCMINSEGMENTQTY") + self.label = QtWidgets.QLabel(self.groupBox_2) + self.label.setGeometry(QtCore.QRect(60, 50, 211, 21)) + self.label.setWordWrap(True) + self.label.setObjectName("label") + self.label_2 = QtWidgets.QLabel(self.groupBox_2) + self.label_2.setGeometry(QtCore.QRect(60, 80, 211, 21)) + self.label_2.setWordWrap(True) + self.label_2.setObjectName("label_2") + self.lineEdit_CIRCLEMINSEGMENTQTY = QtWidgets.QLineEdit(self.groupBox_2) + self.lineEdit_CIRCLEMINSEGMENTQTY.setGeometry(QtCore.QRect(10, 80, 41, 20)) + self.lineEdit_CIRCLEMINSEGMENTQTY.setObjectName("lineEdit_CIRCLEMINSEGMENTQTY") + self.lineEdit_TOLERANCE2APPROXCURVE = QtWidgets.QLineEdit(self.groupBox_2) + self.lineEdit_TOLERANCE2APPROXCURVE.setGeometry(QtCore.QRect(10, 170, 41, 20)) + self.lineEdit_TOLERANCE2APPROXCURVE.setObjectName("lineEdit_TOLERANCE2APPROXCURVE") + self.label_3 = QtWidgets.QLabel(self.groupBox_2) + self.label_3.setGeometry(QtCore.QRect(60, 160, 211, 51)) + self.label_3.setWordWrap(True) + self.label_3.setObjectName("label_3") + self.lineEdit_ELLIPSEMINSEGMENTQTY = QtWidgets.QLineEdit(self.groupBox_2) + self.lineEdit_ELLIPSEMINSEGMENTQTY.setGeometry(QtCore.QRect(10, 140, 41, 20)) + self.lineEdit_ELLIPSEMINSEGMENTQTY.setObjectName("lineEdit_ELLIPSEMINSEGMENTQTY") + self.label_5 = QtWidgets.QLabel(self.groupBox_2) + self.label_5.setGeometry(QtCore.QRect(60, 140, 211, 21)) + self.label_5.setWordWrap(True) + self.label_5.setObjectName("label_5") + self.lineEdit_TOLERANCE2COINCIDENT = QtWidgets.QLineEdit(self.groupBox_2) + self.lineEdit_TOLERANCE2COINCIDENT.setGeometry(QtCore.QRect(10, 20, 41, 20)) + self.lineEdit_TOLERANCE2COINCIDENT.setObjectName("lineEdit_TOLERANCE2COINCIDENT") + self.label_6 = QtWidgets.QLabel(self.groupBox_2) + self.label_6.setGeometry(QtCore.QRect(60, 20, 211, 21)) + self.label_6.setWordWrap(True) + self.label_6.setObjectName("label_6") + self.lineEdit_ELLIPSEARCMINSEGMENTQTY = QtWidgets.QLineEdit(self.groupBox_2) + self.lineEdit_ELLIPSEARCMINSEGMENTQTY.setGeometry(QtCore.QRect(10, 110, 41, 20)) + self.lineEdit_ELLIPSEARCMINSEGMENTQTY.setObjectName("lineEdit_ELLIPSEARCMINSEGMENTQTY") + self.label_7 = QtWidgets.QLabel(self.groupBox_2) + self.label_7.setGeometry(QtCore.QRect(60, 100, 211, 41)) + self.label_7.setWordWrap(True) + self.label_7.setObjectName("label_7") + self.groupBox_3 = QtWidgets.QGroupBox(self.DisplayTab) + self.groupBox_3.setGeometry(QtCore.QRect(270, 250, 281, 51)) + self.groupBox_3.setObjectName("groupBox_3") + self.lineEdit_CURSORSIZE = QtWidgets.QLineEdit(self.groupBox_3) + self.lineEdit_CURSORSIZE.setGeometry(QtCore.QRect(10, 20, 31, 20)) + self.lineEdit_CURSORSIZE.setObjectName("lineEdit_CURSORSIZE") + self.horizontalSlider_CURSORSIZE = QtWidgets.QSlider(self.groupBox_3) + self.horizontalSlider_CURSORSIZE.setGeometry(QtCore.QRect(50, 20, 221, 22)) + self.horizontalSlider_CURSORSIZE.setOrientation(QtCore.Qt.Horizontal) + self.horizontalSlider_CURSORSIZE.setObjectName("horizontalSlider_CURSORSIZE") + self.tabWidget.addTab(self.DisplayTab, "") + self.UserPreferencesTab = QtWidgets.QWidget() + self.UserPreferencesTab.setObjectName("UserPreferencesTab") + self.groupBox_12 = QtWidgets.QGroupBox(self.UserPreferencesTab) + self.groupBox_12.setGeometry(QtCore.QRect(10, 10, 281, 91)) + self.groupBox_12.setObjectName("groupBox_12") + self.checkBox_shortcutmenu = QtWidgets.QCheckBox(self.groupBox_12) + self.checkBox_shortcutmenu.setGeometry(QtCore.QRect(10, 40, 261, 17)) + self.checkBox_shortcutmenu.setObjectName("checkBox_shortcutmenu") + self.button_rightclick = QtWidgets.QPushButton(self.groupBox_12) + self.button_rightclick.setGeometry(QtCore.QRect(10, 60, 261, 23)) + self.button_rightclick.setObjectName("button_rightclick") + self.tabWidget.addTab(self.UserPreferencesTab, "") + self.DraftingTab = QtWidgets.QWidget() + self.DraftingTab.setObjectName("DraftingTab") + self.groupBox_4 = QtWidgets.QGroupBox(self.DraftingTab) + self.groupBox_4.setGeometry(QtCore.QRect(10, 10, 261, 141)) + self.groupBox_4.setObjectName("groupBox_4") + self.checkBox_AUTOSNAP_DISPLAY_MARK = QtWidgets.QCheckBox(self.groupBox_4) + self.checkBox_AUTOSNAP_DISPLAY_MARK.setGeometry(QtCore.QRect(10, 20, 241, 17)) + self.checkBox_AUTOSNAP_DISPLAY_MARK.setObjectName("checkBox_AUTOSNAP_DISPLAY_MARK") + self.pushButton_AutoSnapColor = QtWidgets.QPushButton(self.groupBox_4) + self.pushButton_AutoSnapColor.setGeometry(QtCore.QRect(10, 110, 81, 23)) + self.pushButton_AutoSnapColor.setObjectName("pushButton_AutoSnapColor") + self.checkBox_APBOX = QtWidgets.QCheckBox(self.groupBox_4) + self.checkBox_APBOX.setGeometry(QtCore.QRect(10, 80, 241, 17)) + self.checkBox_APBOX.setObjectName("checkBox_APBOX") + self.checkBox_AUTOSNAP_DISPLAY_TOOLTIPS = QtWidgets.QCheckBox(self.groupBox_4) + self.checkBox_AUTOSNAP_DISPLAY_TOOLTIPS.setGeometry(QtCore.QRect(10, 60, 241, 17)) + self.checkBox_AUTOSNAP_DISPLAY_TOOLTIPS.setObjectName("checkBox_AUTOSNAP_DISPLAY_TOOLTIPS") + self.checkBox_AUTOSNAP_MAGNET = QtWidgets.QCheckBox(self.groupBox_4) + self.checkBox_AUTOSNAP_MAGNET.setGeometry(QtCore.QRect(10, 40, 241, 17)) + self.checkBox_AUTOSNAP_MAGNET.setObjectName("checkBox_AUTOSNAP_MAGNET") + self.groupBox_5 = QtWidgets.QGroupBox(self.DraftingTab) + self.groupBox_5.setGeometry(QtCore.QRect(280, 10, 271, 71)) + self.groupBox_5.setObjectName("groupBox_5") + self.radioButton_POLARMODE_AUTO_ACQUIRE = QtWidgets.QRadioButton(self.groupBox_5) + self.radioButton_POLARMODE_AUTO_ACQUIRE.setGeometry(QtCore.QRect(10, 20, 241, 17)) + self.radioButton_POLARMODE_AUTO_ACQUIRE.setObjectName("radioButton_POLARMODE_AUTO_ACQUIRE") + self.radioButton_POLARMODE_SHIFT_TO_ACQUIRE = QtWidgets.QRadioButton(self.groupBox_5) + self.radioButton_POLARMODE_SHIFT_TO_ACQUIRE.setGeometry(QtCore.QRect(10, 40, 241, 17)) + self.radioButton_POLARMODE_SHIFT_TO_ACQUIRE.setObjectName("radioButton_POLARMODE_SHIFT_TO_ACQUIRE") + self.groupBox_6 = QtWidgets.QGroupBox(self.DraftingTab) + self.groupBox_6.setGeometry(QtCore.QRect(10, 160, 261, 81)) + self.groupBox_6.setObjectName("groupBox_6") + self.horizontalSlider_AUTOSNAPSIZE = QtWidgets.QSlider(self.groupBox_6) + self.horizontalSlider_AUTOSNAPSIZE.setGeometry(QtCore.QRect(70, 30, 181, 22)) + self.horizontalSlider_AUTOSNAPSIZE.setOrientation(QtCore.Qt.Horizontal) + self.horizontalSlider_AUTOSNAPSIZE.setObjectName("horizontalSlider_AUTOSNAPSIZE") + self.widget_AUTOSNAPSIZE = QtWidgets.QWidget(self.groupBox_6) + self.widget_AUTOSNAPSIZE.setGeometry(QtCore.QRect(10, 20, 51, 51)) + self.widget_AUTOSNAPSIZE.setObjectName("widget_AUTOSNAPSIZE") + self.groupBox_7 = QtWidgets.QGroupBox(self.DraftingTab) + self.groupBox_7.setGeometry(QtCore.QRect(280, 160, 271, 81)) + self.groupBox_7.setObjectName("groupBox_7") + self.horizontalSlider_APERTURE = QtWidgets.QSlider(self.groupBox_7) + self.horizontalSlider_APERTURE.setGeometry(QtCore.QRect(70, 30, 181, 22)) + self.horizontalSlider_APERTURE.setOrientation(QtCore.Qt.Horizontal) + self.horizontalSlider_APERTURE.setObjectName("horizontalSlider_APERTURE") + self.widget_APERTURESIZE = QtWidgets.QWidget(self.groupBox_7) + self.widget_APERTURESIZE.setGeometry(QtCore.QRect(10, 20, 51, 51)) + self.widget_APERTURESIZE.setObjectName("widget_APERTURESIZE") + self.button_DraftingTootipSettings = QtWidgets.QPushButton(self.DraftingTab) + self.button_DraftingTootipSettings.setGeometry(QtCore.QRect(280, 250, 271, 23)) + self.button_DraftingTootipSettings.setObjectName("button_DraftingTootipSettings") + self.tabWidget.addTab(self.DraftingTab, "") + self.SelectionTab = QtWidgets.QWidget() + self.SelectionTab.setObjectName("SelectionTab") + self.groupBox_8 = QtWidgets.QGroupBox(self.SelectionTab) + self.groupBox_8.setGeometry(QtCore.QRect(10, 10, 261, 81)) + self.groupBox_8.setObjectName("groupBox_8") + self.horizontalSlider_PICKBOX = QtWidgets.QSlider(self.groupBox_8) + self.horizontalSlider_PICKBOX.setGeometry(QtCore.QRect(70, 30, 181, 22)) + self.horizontalSlider_PICKBOX.setOrientation(QtCore.Qt.Horizontal) + self.horizontalSlider_PICKBOX.setObjectName("horizontalSlider_PICKBOX") + self.widget_PICKBOX = QtWidgets.QWidget(self.groupBox_8) + self.widget_PICKBOX.setGeometry(QtCore.QRect(10, 20, 51, 51)) + self.widget_PICKBOX.setObjectName("widget_PICKBOX") + self.groupBox_9 = QtWidgets.QGroupBox(self.SelectionTab) + self.groupBox_9.setGeometry(QtCore.QRect(280, 10, 271, 81)) + self.groupBox_9.setObjectName("groupBox_9") + self.horizontalSlider_GRIPSIZE = QtWidgets.QSlider(self.groupBox_9) + self.horizontalSlider_GRIPSIZE.setGeometry(QtCore.QRect(70, 30, 191, 22)) + self.horizontalSlider_GRIPSIZE.setOrientation(QtCore.Qt.Horizontal) + self.horizontalSlider_GRIPSIZE.setObjectName("horizontalSlider_GRIPSIZE") + self.widget_GRIPSIZE = QtWidgets.QWidget(self.groupBox_9) + self.widget_GRIPSIZE.setGeometry(QtCore.QRect(10, 20, 51, 51)) + self.widget_GRIPSIZE.setObjectName("widget_GRIPSIZE") + self.groupBox_10 = QtWidgets.QGroupBox(self.SelectionTab) + self.groupBox_10.setGeometry(QtCore.QRect(10, 100, 261, 131)) + self.groupBox_10.setObjectName("groupBox_10") + self.checkBox_PICKFIRST = QtWidgets.QCheckBox(self.groupBox_10) + self.checkBox_PICKFIRST.setGeometry(QtCore.QRect(10, 20, 241, 17)) + self.checkBox_PICKFIRST.setObjectName("checkBox_PICKFIRST") + self.checkBox_PICKADD = QtWidgets.QCheckBox(self.groupBox_10) + self.checkBox_PICKADD.setGeometry(QtCore.QRect(10, 40, 241, 17)) + self.checkBox_PICKADD.setObjectName("checkBox_PICKADD") + self.groupBox_11 = QtWidgets.QGroupBox(self.SelectionTab) + self.groupBox_11.setGeometry(QtCore.QRect(280, 100, 271, 131)) + self.groupBox_11.setObjectName("groupBox_11") + self.button_GripColor = QtWidgets.QPushButton(self.groupBox_11) + self.button_GripColor.setGeometry(QtCore.QRect(70, 20, 141, 23)) + self.button_GripColor.setObjectName("button_GripColor") + self.checkBox_GRIPS = QtWidgets.QCheckBox(self.groupBox_11) + self.checkBox_GRIPS.setGeometry(QtCore.QRect(10, 50, 251, 17)) + self.checkBox_GRIPS.setObjectName("checkBox_GRIPS") + self.checkBox_GRIPMULTIFUNCTIONAL_ON_DYNAMIC_MENU_AND_HOT_GRIPT = QtWidgets.QCheckBox(self.groupBox_11) + self.checkBox_GRIPMULTIFUNCTIONAL_ON_DYNAMIC_MENU_AND_HOT_GRIPT.setGeometry(QtCore.QRect(20, 70, 241, 17)) + self.checkBox_GRIPMULTIFUNCTIONAL_ON_DYNAMIC_MENU_AND_HOT_GRIPT.setObjectName("checkBox_GRIPMULTIFUNCTIONAL_ON_DYNAMIC_MENU_AND_HOT_GRIPT") + self.lineEdit_GRIPOBJLIMIT = QtWidgets.QLineEdit(self.groupBox_11) + self.lineEdit_GRIPOBJLIMIT.setGeometry(QtCore.QRect(20, 100, 31, 20)) + self.lineEdit_GRIPOBJLIMIT.setObjectName("lineEdit_GRIPOBJLIMIT") + self.label_GRIPOBJLIMIT = QtWidgets.QLabel(self.groupBox_11) + self.label_GRIPOBJLIMIT.setGeometry(QtCore.QRect(60, 100, 201, 31)) + self.label_GRIPOBJLIMIT.setToolTip("") + self.label_GRIPOBJLIMIT.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) + self.label_GRIPOBJLIMIT.setWordWrap(True) + self.label_GRIPOBJLIMIT.setObjectName("label_GRIPOBJLIMIT") + self.tabWidget.addTab(self.SelectionTab, "") + + self.retranslateUi(Options_Dialog) + self.tabWidget.setCurrentIndex(2) + self.buttonBox.accepted.connect(Options_Dialog.ButtonBOX_Accepted) + self.buttonBox.clicked['QAbstractButton*'].connect(Options_Dialog.ButtonBOX_Apply) + self.buttonBox.helpRequested.connect(Options_Dialog.ButtonHELP_Pressed) + self.checkBox_INPUTSEARCHOPTIONS_ON.clicked.connect(Options_Dialog.checkBox_INPUTSEARCHOPTIONS_ON_clicked) + self.horizontalSlider_CURSORSIZE.sliderMoved['int'].connect(Options_Dialog.horizontalSlider_CURSORSIZE_moved) + self.lineEdit_CURSORSIZE.textEdited['QString'].connect(Options_Dialog.lineEdit_CURSORSIZE_textChanged) + self.horizontalSlider_CURSORSIZE.valueChanged['int'].connect(Options_Dialog.horizontalSlider_CURSORSIZE_moved) + self.horizontalSlider_AUTOSNAPSIZE.valueChanged['int'].connect(Options_Dialog.horizontalSlider_AUTOSNAPSIZE_changed) + self.horizontalSlider_APERTURE.valueChanged['int'].connect(Options_Dialog.horizontalSlider_APERTURE_changed) + self.horizontalSlider_PICKBOX.valueChanged['int'].connect(Options_Dialog.horizontalSlider_PICKBOX_changed) + self.horizontalSlider_GRIPSIZE.valueChanged['int'].connect(Options_Dialog.horizontalSlider_GRIPSIZE_changed) + self.checkBox_GRIPS.clicked.connect(Options_Dialog.checkBox_GRIPS_ON_clicked) + self.button_GripColor.clicked.connect(Options_Dialog.button_GripColor_clicked) + self.Button_TextWindowColor.clicked.connect(Options_Dialog.Button_TextWindowColor_clicked) + self.pushButton_AutoSnapColor.clicked.connect(Options_Dialog.Button_AutoSnapWindowColor_clicked) + self.button_DraftingTootipSettings.clicked.connect(Options_Dialog.button_DraftingTooltipSettings_clicked) + self.button_rightclick.clicked.connect(Options_Dialog.button_rightclick_clicked) + self.checkBox_shortcutmenu.clicked.connect(Options_Dialog.checkBox_shortcutmenu_clicked) + QtCore.QMetaObject.connectSlotsByName(Options_Dialog) + + def retranslateUi(self, Options_Dialog): + _translate = QtCore.QCoreApplication.translate + Options_Dialog.setWindowTitle(_translate("Options_Dialog", "Options")) + self.groupBox.setTitle(_translate("Options_Dialog", "Window Elements")) + self.Button_TextWindowColor.setToolTip(_translate("Options_Dialog", "Displays the Drawing Window Colors dialog box. ")) + self.Button_TextWindowColor.setText(_translate("Options_Dialog", "Colors")) + self.checkBox_INPUTSEARCHOPTIONS_AUTOCOMPLETE.setToolTip(_translate("Options_Dialog", "Automatically appends suggestions as each keystroke is entered after the second keystroke (system variable INPUTSEARCHOPTIONS).")) + self.checkBox_INPUTSEARCHOPTIONS_AUTOCOMPLETE.setText(_translate("Options_Dialog", "AutoComplete")) + self.checkBox_SHOWTEXTWINDOW.setToolTip(_translate("Options_Dialog", "Show the text window at startup (system variable SHOWTEXTWINDOW).")) + self.checkBox_SHOWTEXTWINDOW.setText(_translate("Options_Dialog", "Show the text window at startup")) + self.label_4.setText(_translate("Options_Dialog", "Maximun command history length")) + self.lineEdit_CMDINPUTHISTORYMAX.setToolTip(_translate("Options_Dialog", "Sets the maximum number of previous input values that are stored for a prompt in a command (system variable CMDINPUTHISTORYMAX).")) + self.lineEdit_INPUTSEARCHDELAY.setToolTip(_translate("Options_Dialog", "

      Controls the amount of time that elapses before automated keyboard features display at the Command prompt.

      Valid values are real numbers from 100 to 10,000, which represent milliseconds.

      ")) + self.label_INPUTSEARCHDELAY.setText(_translate("Options_Dialog", "Delay time (msec)")) + self.checkBox_INPUTSEARCHOPTIONS_ON.setToolTip(_translate("Options_Dialog", "Turns on/off all automated keyboard features when typing at the Command prompt (system variable INPUTSEARCHOPTIONS).")) + self.checkBox_INPUTSEARCHOPTIONS_ON.setText(_translate("Options_Dialog", "Automated keyboard features")) + self.checkBox_INPUTSEARCHOPTIONS_DISPLAY_LIST.setToolTip(_translate("Options_Dialog", "Displays a list of suggestions as keystrokes are entered (system variable INPUTSEARCHOPTIONS).")) + self.checkBox_INPUTSEARCHOPTIONS_DISPLAY_LIST.setText(_translate("Options_Dialog", "Displays a list of suggestions")) + self.checkBox_INPUTSEARCHOPTIONS_DISPLAY_ICON.setToolTip(_translate("Options_Dialog", "Displays the icon of the command or system variable, if available (system variable INPUTSEARCHOPTIONS).")) + self.checkBox_INPUTSEARCHOPTIONS_DISPLAY_ICON.setText(_translate("Options_Dialog", "Displays the icon of the command")) + self.checkBox_INPUTSEARCHOPTIONS_EXCLUDE_SYS_VAR.setToolTip(_translate("Options_Dialog", "Excludes the display of system variables (system variable INPUTSEARCHOPTIONS).")) + self.checkBox_INPUTSEARCHOPTIONS_EXCLUDE_SYS_VAR.setText(_translate("Options_Dialog", "Excludes system variables")) + self.groupBox_2.setTitle(_translate("Options_Dialog", "Resolution")) + self.lineEdit_ARCMINSEGMENTQTY.setToolTip(_translate("Options_Dialog", "Minimum number of segments to approximate an arc (system variable ARCMINSEGMENTQTY).")) + self.label.setText(_translate("Options_Dialog", "Minimum number of segments in an arc")) + self.label_2.setToolTip(_translate("Options_Dialog", "Minimum number of segments to approximate a circle (system variable CIRCLEMINSEGMENTQTY).")) + self.label_2.setText(_translate("Options_Dialog", "Minimum number of segments in a circle")) + self.lineEdit_CIRCLEMINSEGMENTQTY.setToolTip(_translate("Options_Dialog", "Minimum number of segments to approximate a circle (system variable CIRCLEMINSEGMENTQTY).")) + self.lineEdit_TOLERANCE2APPROXCURVE.setToolTip(_translate("Options_Dialog", "Maximum error approximating a curve to segments (system variable TOLERANCE2APPROXCURVE).")) + self.label_3.setText(_translate("Options_Dialog", "Maximun admitted error between a real curve and the aproximated, segmented curve")) + self.lineEdit_ELLIPSEMINSEGMENTQTY.setToolTip(_translate("Options_Dialog", "Minimum number of segments to approximate an ellipse (system variable ELLIPSEMINSEGMENTQTY).")) + self.label_5.setToolTip(_translate("Options_Dialog", "Minimum number of segments to approximate an ellipse (system variable ELLIPSEMINSEGMENTQTY).")) + self.label_5.setText(_translate("Options_Dialog", "Minimum number of segments in an ellipse")) + self.lineEdit_TOLERANCE2COINCIDENT.setToolTip(_translate("Options_Dialog", "Proximity (tolerance) within 2 point are to be considered coincident (system variable TOLERANCE2COINCIDENT).")) + self.label_6.setText(_translate("Options_Dialog", "Tolerance for coincident points")) + self.lineEdit_ELLIPSEARCMINSEGMENTQTY.setToolTip(_translate("Options_Dialog", "Minimum number of segments to approximate an arc (system variable ARCMINSEGMENTQTY).")) + self.label_7.setText(_translate("Options_Dialog", "Minimum number of segments in an arc of ellipse")) + self.groupBox_3.setTitle(_translate("Options_Dialog", "Crosshair size")) + self.lineEdit_CURSORSIZE.setToolTip(_translate("Options_Dialog", "Determines the size of the crosshair as a percentage of the screen size (system variable CURSORSIZE).")) + self.horizontalSlider_CURSORSIZE.setToolTip(_translate("Options_Dialog", "Determines the size of the crosshair as a percentage of the screen size (system variable CURSORSIZE).")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.DisplayTab), _translate("Options_Dialog", "Display")) + self.groupBox_12.setTitle(_translate("Options_Dialog", "Windows Standard Behavior")) + self.checkBox_shortcutmenu.setToolTip(_translate("Options_Dialog", "Controls whether Default, Edit, and Command mode shortcut menus are available in the drawing area. ( SHORTCUTMENU system variable)")) + self.checkBox_shortcutmenu.setText(_translate("Options_Dialog", "Shortcut menus in drawing area")) + self.button_rightclick.setToolTip(_translate("Options_Dialog", "Displays the Right-Click Customization dialog box. This dialog box provides further definition for the Shortcut Menus in Drawing Area option. ( SHORTCUTMENU system variable)")) + self.button_rightclick.setText(_translate("Options_Dialog", "Right-click customization...")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.UserPreferencesTab), _translate("Options_Dialog", "User Preferences")) + self.groupBox_4.setTitle(_translate("Options_Dialog", "AutoSnap Settings")) + self.checkBox_AUTOSNAP_DISPLAY_MARK.setToolTip(_translate("Options_Dialog", "Controls the display of the AutoSnap marker. The marker is a geometric symbol that is displayed when the crosshairs move over a snap point ( AUTOSNAP system variable).")) + self.checkBox_AUTOSNAP_DISPLAY_MARK.setText(_translate("Options_Dialog", "Marker")) + self.pushButton_AutoSnapColor.setToolTip(_translate("Options_Dialog", "Displays the Drawing Window Colors dialog box. ")) + self.pushButton_AutoSnapColor.setText(_translate("Options_Dialog", "Colors")) + self.checkBox_APBOX.setToolTip(_translate("Options_Dialog", "Turns the display of the AutoSnap aperture box on or off.\n" +"The aperture box is a box that appears inside the crosshairs when you snap to an object (APBOX system variable).")) + self.checkBox_APBOX.setText(_translate("Options_Dialog", "Display AutoSnap aperture box")) + self.checkBox_AUTOSNAP_DISPLAY_TOOLTIPS.setToolTip(_translate("Options_Dialog", "Controls the display of the AutoSnap tooltip. The tooltip is a label that describes which part of the object you are snapping to (AUTOSNAP system variable).")) + self.checkBox_AUTOSNAP_DISPLAY_TOOLTIPS.setText(_translate("Options_Dialog", "Display AutoSnap tooltip")) + self.checkBox_AUTOSNAP_MAGNET.setToolTip(_translate("Options_Dialog", "Turns the AutoSnap magnet on or off. The magnet is an automatic movement of the crosshairs that locks the crosshairs onto the nearest snap point (AUTOSNAP system variable).")) + self.checkBox_AUTOSNAP_MAGNET.setText(_translate("Options_Dialog", "Magnet")) + self.groupBox_5.setTitle(_translate("Options_Dialog", "Alignment Point Acquisition")) + self.radioButton_POLARMODE_AUTO_ACQUIRE.setToolTip(_translate("Options_Dialog", "Displays tracking vectors automatically when the aperture moves over an object snap.")) + self.radioButton_POLARMODE_AUTO_ACQUIRE.setText(_translate("Options_Dialog", "Automatic")) + self.radioButton_POLARMODE_SHIFT_TO_ACQUIRE.setToolTip(_translate("Options_Dialog", "Displays tracking vectors when you press Shift and move the aperture over an object snap.")) + self.radioButton_POLARMODE_SHIFT_TO_ACQUIRE.setText(_translate("Options_Dialog", "Shift to acquire")) + self.groupBox_6.setTitle(_translate("Options_Dialog", "AutoSnap Marker Size")) + self.horizontalSlider_AUTOSNAPSIZE.setToolTip(_translate("Options_Dialog", "Sets the display size for the AutoSnap marker.")) + self.groupBox_7.setTitle(_translate("Options_Dialog", "Aperture Size")) + self.horizontalSlider_APERTURE.setToolTip(_translate("Options_Dialog", "Sets the display size for the object snap target box, in pixels (APERTURE system variable).")) + self.button_DraftingTootipSettings.setToolTip(_translate("Options_Dialog", "Displays the Drafting Tooltip dialog box.")) + self.button_DraftingTootipSettings.setText(_translate("Options_Dialog", "Drafting Tooltip settings...")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.DraftingTab), _translate("Options_Dialog", "Drafting")) + self.groupBox_8.setTitle(_translate("Options_Dialog", "Pickbox size")) + self.horizontalSlider_PICKBOX.setToolTip(_translate("Options_Dialog", "Sets the object selection target height, in pixels (PICKBOX system variable).")) + self.groupBox_9.setTitle(_translate("Options_Dialog", "Grip size")) + self.horizontalSlider_GRIPSIZE.setToolTip(_translate("Options_Dialog", "Sets the size of the grip box in pixels ( GRIPSIZE system variable).")) + self.groupBox_10.setTitle(_translate("Options_Dialog", "Selection modes")) + self.checkBox_PICKFIRST.setToolTip(_translate("Options_Dialog", "Controls whether you select objects before (noun-verb selection) or after you issue a command (PICKFIRST system variable).")) + self.checkBox_PICKFIRST.setText(_translate("Options_Dialog", "Noun/verb selection")) + self.checkBox_PICKADD.setToolTip(_translate("Options_Dialog", "Controls whether subsequent selections replace the current selection set or add to it (PICKADD system variable).")) + self.checkBox_PICKADD.setText(_translate("Options_Dialog", "Use Shift to add to selection")) + self.groupBox_11.setTitle(_translate("Options_Dialog", "Grips")) + self.button_GripColor.setToolTip(_translate("Options_Dialog", "Displays the Grip Colors dialog box where you can specify the colors for different grip status and elements.")) + self.button_GripColor.setText(_translate("Options_Dialog", "Grip Colors...")) + self.checkBox_GRIPS.setToolTip(_translate("Options_Dialog", "Controls the display of grips on selected objects. Displaying grips in a drawing significantly affects performance. Clear this option to optimize performance ( GRIPS system variable).")) + self.checkBox_GRIPS.setText(_translate("Options_Dialog", "Shows grips")) + self.checkBox_GRIPMULTIFUNCTIONAL_ON_DYNAMIC_MENU_AND_HOT_GRIPT.setToolTip(_translate("Options_Dialog", "Controls the display of dynamic menu when pausing over a multi-functional grip (GRIPMULTIFUNCTIONAL system variable).")) + self.checkBox_GRIPMULTIFUNCTIONAL_ON_DYNAMIC_MENU_AND_HOT_GRIPT.setText(_translate("Options_Dialog", "Shows dynamic grip menu")) + self.lineEdit_GRIPOBJLIMIT.setToolTip(_translate("Options_Dialog", "Suppresses the display of grips when the selection set includes more than the specified number of objects (GRIPOBJLIMIT system variable).")) + self.label_GRIPOBJLIMIT.setText(_translate("Options_Dialog", "Object selection limit for display of grips")) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.SelectionTab), _translate("Options_Dialog", "Selection")) diff --git a/qad_pedit_cmd.py b/qad_pedit_cmd.py deleted file mode 100644 index 628e791f..00000000 --- a/qad_pedit_cmd.py +++ /dev/null @@ -1,2008 +0,0 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - comando PEDIT per editare una polilinea o un poligono esistente - - ------------------- - begin : 2014-01-13 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -from qgis.gui import * - - -from qad_generic_cmd import QadCommandClass -from qad_snapper import * -from qad_pedit_maptool import * -from qad_ssget_cmd import QadSSGetClass -from qad_msg import QadMsg -from qad_textwindow import * -import qad_utils -import qad_layer -from qad_variables import * -from qad_snappointsdisplaymanager import * -from qad_dim import QadDimStyles -import qad_grip - - -# Classe che gestisce il comando PEDIT -class QadPEDITCommandClass(QadCommandClass): - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadPEDITCommandClass(self.plugIn) - - def getName(self): - return QadMsg.translate("Command_list", "PEDIT") - - def getEnglishName(self): - return "PEDIT" - - def connectQAction(self, action): - QObject.connect(action, SIGNAL("triggered()"), self.plugIn.runPEDITCommand) - - def getIcon(self): - return QIcon(":/plugins/qad/icons/pedit.png") - - def getNote(self): - # impostare le note esplicative del comando - return QadMsg.translate("Command_PEDIT", "Modifies existing polylines or polygon.") - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - self.SSGetClass = QadSSGetClass(plugIn) - self.SSGetClass.onlyEditableLayers = True - self.SSGetClass.checkPointLayer = False # scarto i punto - self.SSGetClass.checkLineLayer = True - self.SSGetClass.checkDimLayers = False # scarto le quote - - self.entitySet = QadEntitySet() - self.entity = QadEntity() - self.atSubGeom = None - self.linearObjectList = qad_utils.QadLinearObjectList() - self.joinToleranceDist = plugIn.joinToleranceDist - self.joinMode = plugIn.joinMode - - self.editVertexMode = None - self.nOperationsToUndo = 0 - - self.firstPt = QgsPoint() - self.vertexAt = 0 - self.secondVertexAt = 0 - self.after = True - self.snapPointsDisplayManager = QadSnapPointsDisplayManager(self.plugIn.canvas) - self.snapPointsDisplayManager.setIconSize(QadVariables.get(QadMsg.translate("Environment variables", "OSSIZE"))) - self.snapPointsDisplayManager.setColor(QColor(QadVariables.get(QadMsg.translate("Environment variables", "OSCOLOR")))) - - def __del__(self): - QadCommandClass.__del__(self) - del self.SSGetClass - self.entity.deselectOnLayer() - self.entitySet.deselectOnLayer() - del self.snapPointsDisplayManager - - def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): - if self.step == 2: # quando si é in fase di selezione entità - return self.SSGetClass.getPointMapTool() - else: - if (self.plugIn is not None): - if self.PointMapTool is None: - self.PointMapTool = Qad_pedit_maptool(self.plugIn) - return self.PointMapTool - else: - return None - - - #============================================================================ - # setEntityInfo - #============================================================================ - def setEntityInfo(self, layer, featureId, point): - """ - Setta self.entity, self.atSubGeom, self.linearObjectList - """ - self.entity.set(layer, featureId) - geom = self.layerToMapCoordinates(layer, self.entity.getGeometry()) - # ritorna una tupla (, - # - # - # ) - dummy = qad_utils.closestSegmentWithContext(point, geom) - if dummy[2] is not None: - # ritorna la sotto-geometria al vertice e la sua posizione nella geometria (0-based) - subGeom, self.atSubGeom = qad_utils.getSubGeomAtVertex(geom, dummy[2]) - self.linearObjectList.fromPolyline(subGeom.asPolyline()) - if self.linearObjectList.getCircle() is not None: - self.entity.deselectOnLayer() - return False - else: - self.entity.selectOnLayer(False) # non incrementale - return True - else: - self.entity.deselectOnLayer() - return False - - - #============================================================================ - # getNextVertex - #============================================================================ - def getNextVertex(self, vertexAt): - """ - Ritorna la posizione del vertice successivo rispetto vertexAt - """ - tot = self.linearObjectList.qty() - if vertexAt == tot - 1: # se penultimo punto - return 0 if self.linearObjectList.isClosed() else vertexAt + 1 - elif vertexAt < tot: # se non é ultimo punto - return vertexAt + 1 - else: - return vertexAt - - - #============================================================================ - # getPrevVertex - #============================================================================ - def getPrevVertex(self, vertexAt): - """ - Ritorna la posizione del vertice precedente rispetto vertexAt - """ - if vertexAt == 0: # se primo punto - if self.linearObjectList.isClosed(): - return self.linearObjectList.qty() - 1 - else: - return vertexAt - else: - return vertexAt - 1 - - - #============================================================================ - # displayVertexMarker - #============================================================================ - def displayVertexMarker(self, vertexAt): - if vertexAt == self.linearObjectList.qty(): - pt = self.linearObjectList.getLinearObjectAt(-1).getEndPt() - else: - pt = self.linearObjectList.getLinearObjectAt(vertexAt).getStartPt() - - # visualizzo il punto di snap - snapPoint = dict() - snapPoint[QadSnapTypeEnum.END] = [pt] - self.snapPointsDisplayManager.show(snapPoint) - - - #============================================================================ - # setClose - #============================================================================ - def setClose(self, toClose): - if self.entity.isInitialized(): # selezionato solo un oggetto - layer = self.entity.layer - self.plugIn.beginEditCommand("Feature edited", layer) - - tolerance2ApproxCurve = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) - f = self.entity.getFeature() - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - geom = self.layerToMapCoordinates(layer, self.entity.getGeometry()) - geom = f.geometry() - - self.linearObjectList.setClose(toClose) - pts = self.linearObjectList.asPolyline(tolerance2ApproxCurve) - - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - geom = self.layerToMapCoordinates(layer, self.entity.getGeometry()) - - if layer.geometryType() == QGis.Line: - updGeom = qad_utils.setSubGeom(geom, QgsGeometry.fromPolyline(pts), self.atSubGeom) - # trasformo la geometria nel crs del layer - f.setGeometry(self.mapToLayerCoordinates(layer, updGeom)) - - # plugIn, layer, feature, refresh, check_validity - if qad_layer.updateFeatureToLayer(self.plugIn, layer, f, False, False) == False: - self.plugIn.destroyEditCommand() - return - else: # layer di tipo poligono - if toClose == False: # apri - # aggiungo le linee nei layer temporanei di QAD - LineTempLayer = qad_layer.createQADTempLayer(self.plugIn, QGis.Line) - self.plugIn.addLayerToLastEditCommand("Feature edited", LineTempLayer) - - lineGeoms = [QgsGeometry.fromPolyline(pts)] - - # trasformo la geometria in quella dei layer temporanei - # plugIn, pointGeoms, lineGeoms, polygonGeoms, coord, refresh - if qad_layer.addGeometriesToQADTempLayers(self.plugIn, None, lineGeoms, None, \ - None, False) == False: - self.plugIn.destroyEditCommand() - return - - updGeom = qad_utils.delSubGeom(geom, atSubGeom) - - if updGeom is None or updGeom.isGeosEmpty(): # da cancellare - # plugIn, layer, feature id, refresh - if qad_layer.deleteFeatureToLayer(self.plugIn, layer, f.id(), False) == False: - self.plugIn.destroyEditCommand() - return - else: - editedFeature = QgsFeature(f) - # trasformo la geometria nel crs del layer - editedFeature.setGeometry(self.mapToLayerCoordinates(layer, updGeom)) - - # plugIn, layer, feature, refresh, check_validity - if qad_layer.updateFeatureToLayer(self.plugIn, layer, editedFeature, False, False) == False: - self.plugIn.destroyEditCommand() - return - else: - self.plugIn.beginEditCommand("Feature edited", self.entitySet.getLayerList()) - for layerEntitySet in self.entitySet.layerEntitySetList: - layer = layerEntitySet.layer - tolerance2ApproxCurve = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) - - updObjects = [] - for featureId in layerEntitySet.featureIds: - f = layerEntitySet.getFeature(featureId) - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - updGeom = qad_utils.closeQgsGeometry(self.mapToLayerCoordinates(layer, f.geometry()), toClose, tolerance2ApproxCurve) - if updGeom is not None: - updFeature = QgsFeature(f) - # trasformo la geometria nel crs del layer - updFeature.setGeometry(self.mapToLayerCoordinates(layer, updGeom)) - updObjects.append(updFeature) - - # plugIn, layer, features, refresh, check_validity - if qad_layer.updateFeaturesToLayer(self.plugIn, layer, updObjects, False, False) == False: - self.plugIn.destroyEditCommand() - return - - self.plugIn.endEditCommand() - self.nOperationsToUndo = self.nOperationsToUndo + 1 - - - #============================================================================ - # reverse - #============================================================================ - def reverse(self): - if self.entity.isInitialized(): # selezionato solo un oggetto - layer = self.entity.layer - self.plugIn.beginEditCommand("Feature edited", layer) - - tolerance2ApproxCurve = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) - f = self.entity.getFeature() - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - geom = self.layerToMapCoordinates(layer, self.entity.getGeometry()) - - self.linearObjectList.reverse() - updSubGeom = QgsGeometry.fromPolyline(self.linearObjectList.asPolyline(tolerance2ApproxCurve)) - updGeom = qad_utils.setSubGeom(geom, updSubGeom, self.atSubGeom) - if updGeom is not None: - # trasformo la geometria nel crs del layer - f.setGeometry(self.mapToLayerCoordinates(layer, updGeom)) - # plugIn, layer, feature, refresh, check_validity - if qad_layer.updateFeatureToLayer(self.plugIn, self.entity.layer, f, False, False) == False: - self.plugIn.destroyEditCommand() - return - else: - self.plugIn.beginEditCommand("Feature edited", self.entitySet.getLayerList()) - - for layerEntitySet in self.entitySet.layerEntitySetList: - tolerance2ApproxCurve = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) - - updObjects = [] - for featureId in layerEntitySet.featureIds: - f = layerEntitySet.getFeature(featureId) - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - geom = self.layerToMapCoordinates(layerEntitySet.layer, f.geometry()) - updGeom = qad_utils.reverseQgsGeometry(geom, tolerance2ApproxCurve) - if updGeom is not None: - updFeature = QgsFeature(f) - # trasformo la geometria nel crs del layer - updFeature.setGeometry(self.mapToLayerCoordinates(layerEntitySet.layer, updGeom)) - updObjects.append(updFeature) - - # plugIn, layer, features, refresh, check_validity - if qad_layer.updateFeaturesToLayer(self.plugIn, layerEntitySet.layer, updObjects, False, False) == False: - self.plugIn.destroyEditCommand() - return - - self.plugIn.endEditCommand() - self.nOperationsToUndo = self.nOperationsToUndo + 1 - - - #============================================================================ - # join - #============================================================================ - def join(self): - tolerance2ApproxCurve = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) - epsg = self.plugIn.canvas.currentLayer().crs().authid() - # creo un layer temporaneo in memoria con campo numerico per - # contenere la posizione dell'entità originale nella lista newIdFeatureList - vectorLayer = QgsVectorLayer("LineString?crs=%s&index=yes" % epsg, "QAD_SelfJoinLines", "memory") - - provider = vectorLayer.dataProvider() - provider.addAttributes([QgsField('index', QVariant.Int, 'Int')]) - vectorLayer.updateFields() - - if vectorLayer.startEditing() == False: - return - - # inserisco nel layer i vari oggetti lineari (WKBLineString) - layerList = [] - newIdFeatureList = [] # lista ((newId - layer - feature) ...) - i = 0 - - if self.entity.isInitialized(): # selezionato solo un oggetto - self.entitySet.removeEntity(self.entity) # elimino dal gruppo l'entità da unire - - # aggiungo l'entità a cui unirsi - layer = self.entity.layer - - if layer.geometryType() != QGis.Line: - return - - tolerance2ApproxCurve = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) - - f = self.entity.getFeature() - if f.geometry().wkbType() != QGis.WKBLineString: - return - newFeature = QgsFeature() - newFeature.initAttributes(1) - newFeature.setAttribute(0, 0) - - geom = QgsGeometry.fromPolyline(self.linearObjectList.asPolyline(tolerance2ApproxCurve)) - newFeature.setGeometry(geom) - i = i + 1 - - if vectorLayer.addFeature(newFeature) == False: - vectorLayer.destroyEditCommand() - return - newIdFeatureList.append([newFeature.id(), layer, f]) - - - for layerEntitySet in self.entitySet.layerEntitySetList: - layer = layerEntitySet.layer - - if layer.geometryType() != QGis.Line: - continue - - for f in layerEntitySet.getFeatureCollection(): - if f.geometry().wkbType() != QGis.WKBLineString: - continue - newFeature = QgsFeature() - newFeature.initAttributes(1) - newFeature.setAttribute(0, i) - - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - geom = self.layerToMapCoordinates(layerEntitySet.layer, f.geometry()) - newFeature.setGeometry(geom) - i = i + 1 - - if vectorLayer.addFeature(newFeature) == False: - vectorLayer.destroyEditCommand() - return - newIdFeatureList.append([newFeature.id(), layer, f]) - - vectorLayer.endEditCommand(); - vectorLayer.updateExtents() - - if provider.capabilities() & QgsVectorDataProvider.CreateSpatialIndex: - provider.createSpatialIndex() - - deleteFeatures = [] - if self.entity.isInitialized(): # selezionato solo un oggetto - featureIdToJoin = newIdFeatureList[0][0] - # featureIdToJoin, vectorLayer, tolerance2ApproxCurve, toleranceDist, mode - deleteFeatures.extend(qad_utils.joinFeatureInVectorLayer(featureIdToJoin, vectorLayer, \ - tolerance2ApproxCurve, \ - self.joinToleranceDist, self.joinMode)) - else: - i = 0 - tot = len(newIdFeatureList) - while i < tot: - featureIdToJoin = newIdFeatureList[i][0] - # featureIdToJoin, vectorLayer, tolerance2ApproxCurve, toleranceDist, mode - deleteFeatures.extend(qad_utils.joinFeatureInVectorLayer(featureIdToJoin, vectorLayer, \ - tolerance2ApproxCurve, \ - self.joinToleranceDist, self.joinMode)) - i = i + 1 - - self.plugIn.beginEditCommand("Feature edited", self.entitySet.getLayerList()) - - if self.entity.isInitialized(): # selezionato solo un oggetto - newFeature = qad_utils.getFeatureById(vectorLayer, newIdFeatureList[0][0]) - if newFeature is None: - self.plugIn.destroyEditCommand() - return - - layer = newIdFeatureList[0][1] - f = newIdFeatureList[0][2] - - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - geom = self.layerToMapCoordinates(self.entity.layer, f.geometry()) - - updGeom = qad_utils.setSubGeom(geom, newFeature.geometry(), self.atSubGeom) - - # trasformo la geometria nel crs del layer - f.setGeometry(self.mapToLayerCoordinates(self.entity.layer, updGeom)) - # plugIn, layer, feature, refresh, check_validity - if qad_layer.updateFeatureToLayer(self.plugIn, layer, f, False, False) == False: - self.plugIn.destroyEditCommand() - return - else: - # aggiorno la geometria delle features rimaste nel layer temporaneo - # fetchAttributes, fetchGeometry, rectangle, useIntersect - for newFeature in vectorLayer.getFeatures(qad_utils.getFeatureRequest([], True, None, False)): - layer = newIdFeatureList[newFeature['index']][1] - f = newIdFeatureList[newFeature['index']][2] - f.setGeometry(newFeature.geometry()) - - # plugIn, layer, feature, refresh, check_validity - if qad_layer.updateFeatureToLayer(self.plugIn, layer, f, False, False) == False: - self.plugIn.destroyEditCommand() - return - - # cancello le features rimosse dal layer temporaneo - for newFeature in deleteFeatures: - layer = newIdFeatureList[newFeature['index']][1] - f = newIdFeatureList[newFeature['index']][2] - # plugIn, layer, feature id, refresh - if qad_layer.deleteFeatureToLayer(self.plugIn, layer, f.id(), False) == False: - self.plugIn.destroyEditCommand() - return - - self.plugIn.endEditCommand() - self.nOperationsToUndo = self.nOperationsToUndo + 1 - - - #============================================================================ - # curve - #============================================================================ - def curve(self, toCurve): - if self.entity.isInitialized(): # selezionato solo un oggetto - layer = self.entity.layer - self.plugIn.beginEditCommand("Feature edited", layer) - - tolerance2ApproxCurve = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) - f = self.entity.getFeature() - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - geom = self.layerToMapCoordinates(layer, f.geometry()) - - self.linearObjectList.curve(toCurve) - updSubGeom = QgsGeometry.fromPolyline(self.linearObjectList.asPolyline(tolerance2ApproxCurve)) - updGeom = qad_utils.setSubGeom(geom, updSubGeom, self.atSubGeom) - if updGeom is not None: - # trasformo la geometria nel crs del layer - f.setGeometry(self.mapToLayerCoordinates(layer, updGeom)) - # plugIn, layer, feature, refresh, check_validity - if qad_layer.updateFeatureToLayer(self.plugIn, self.entity.layer, f, False, False) == False: - self.plugIn.destroyEditCommand() - return - else: - self.plugIn.beginEditCommand("Feature edited", self.entitySet.getLayerList()) - - for layerEntitySet in self.entitySet.layerEntitySetList: - tolerance2ApproxCurve = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) - - updObjects = [] - for featureId in layerEntitySet.featureIds: - f = layerEntitySet.getFeature(featureId) - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - geom = self.layerToMapCoordinates(layerEntitySet.layer, f.geometry()) - - updGeom = qad_utils.curveQgsGeometry(geom, toCurve, tolerance2ApproxCurve) - if updGeom is not None: - updFeature = QgsFeature(f) - # trasformo la geometria nel crs del layer - updFeature.setGeometry(self.mapToLayerCoordinates(layerEntitySet.layer, updGeom)) - updObjects.append(updFeature) - - # plugIn, layer, features, refresh, check_validity - if qad_layer.updateFeaturesToLayer(self.plugIn, layerEntitySet.layer, updObjects, False, False) == False: - self.plugIn.destroyEditCommand() - return - - self.plugIn.endEditCommand() - self.nOperationsToUndo = self.nOperationsToUndo + 1 - - - #============================================================================ - # insertVertexAt - #============================================================================ - def insertVertexAt(self, pt): - layer = self.entity.layer - - tolerance2ApproxCurve = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) - f = self.entity.getFeature() - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - geom = self.layerToMapCoordinates(layer, f.geometry()) - - if self.after: # dopo - if self.vertexAt == self.linearObjectList.qty() and self.linearObjectList.isClosed(): - self.linearObjectList.insertPoint(0, pt) - else: - self.linearObjectList.insertPoint(self.vertexAt, pt) - else: # prima - if self.vertexAt == 0 and self.linearObjectList.isClosed(): - self.linearObjectList.insertPoint(self.linearObjectList.qty() - 1, pt) - else: - self.linearObjectList.insertPoint(self.vertexAt - 1, pt) - - updSubGeom = QgsGeometry.fromPolyline(self.linearObjectList.asPolyline(tolerance2ApproxCurve)) - updGeom = qad_utils.setSubGeom(geom, updSubGeom, self.atSubGeom) - if updGeom is None: - return - # trasformo la geometria nel crs del layer - f.setGeometry(self.mapToLayerCoordinates(layer, updGeom)) - - self.plugIn.beginEditCommand("Feature edited", layer) - - # plugIn, layer, feature, refresh, check_validity - if qad_layer.updateFeatureToLayer(self.plugIn, layer, f, False, False) == False: - self.plugIn.destroyEditCommand() - return - - self.plugIn.endEditCommand() - self.nOperationsToUndo = self.nOperationsToUndo + 1 - - - #============================================================================ - # moveVertexAt - #============================================================================ - def moveVertexAt(self, pt): - layer = self.entity.layer - - tolerance2ApproxCurve = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) - f = self.entity.getFeature() - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - geom = self.layerToMapCoordinates(layer, f.geometry()) - - self.linearObjectList.movePoint(self.vertexAt, pt) - - updSubGeom = QgsGeometry.fromPolyline(self.linearObjectList.asPolyline(tolerance2ApproxCurve)) - updGeom = qad_utils.setSubGeom(geom, updSubGeom, self.atSubGeom) - if updGeom is None: - return - # trasformo la geometria nel crs del layer - f.setGeometry(self.mapToLayerCoordinates(layer, updGeom)) - - self.plugIn.beginEditCommand("Feature edited", layer) - - # plugIn, layer, feature, refresh, check_validity - if qad_layer.updateFeatureToLayer(self.plugIn, layer, f, False, False) == False: - self.plugIn.destroyEditCommand() - return - - self.plugIn.endEditCommand() - self.nOperationsToUndo = self.nOperationsToUndo + 1 - - - #============================================================================ - # straightenFromVertexAtToSecondVertexAt - #============================================================================ - def straightenFromVertexAtToSecondVertexAt(self): - if self.vertexAt == self.secondVertexAt: - return - - layer = self.entity.layer - tolerance2ApproxCurve = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) - f = self.entity.getFeature() - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - geom = self.layerToMapCoordinates(layer, f.geometry()) - - if self.vertexAt < self.secondVertexAt: - firstPt = self.linearObjectList.getPointAtVertex(self.vertexAt) - secondPt = self.linearObjectList.getPointAtVertex(self.secondVertexAt) - for i in xrange(self.vertexAt, self.secondVertexAt, 1): - self.linearObjectList.remove(self.vertexAt) - self.linearObjectList.insert(self.vertexAt, [firstPt, secondPt]) - elif self.vertexAt > self.secondVertexAt: - if self.linearObjectList.isClosed(): - firstPt = self.linearObjectList.getPointAtVertex(self.vertexAt) - secondPt = self.linearObjectList.getPointAtVertex(self.secondVertexAt) - for i in xrange(self.vertexAt, self.linearObjectList.qty(), 1): - self.linearObjectList.remove(self.vertexAt) - for i in xrange(0, self.secondVertexAt, 1): - self.linearObjectList.remove(0) - - self.linearObjectList.insert(self.vertexAt, [firstPt, secondPt]) - else: - firstPt = self.linearObjectList.getPointAtVertex(self.secondVertexAt) - secondPt = self.linearObjectList.getPointAtVertex(self.vertexAt) - for i in xrange(self.secondVertexAt, self.vertexAt, 1): - self.linearObjectList.remove(self.secondVertexAt) - self.linearObjectList.insert(self.secondVertexAt, [firstPt, secondPt]) - - updSubGeom = QgsGeometry.fromPolyline(self.linearObjectList.asPolyline(tolerance2ApproxCurve)) - updGeom = qad_utils.setSubGeom(geom, updSubGeom, self.atSubGeom) - if updGeom is None: - return - # trasformo la geometria nel crs del layer - f.setGeometry(self.mapToLayerCoordinates(layer, updGeom)) - - self.plugIn.beginEditCommand("Feature edited", layer) - - # plugIn, layer, feature, refresh, check_validity - if qad_layer.updateFeatureToLayer(self.plugIn, layer, f, False, False) == False: - self.plugIn.destroyEditCommand() - return - - self.plugIn.endEditCommand() - self.nOperationsToUndo = self.nOperationsToUndo + 1 - - - #============================================================================ - # breakFromVertexAtToSecondVertexAt - #============================================================================ - def breakFromVertexAtToSecondVertexAt(self): - layer = self.entity.layer - - tolerance2ApproxCurve = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) - f = self.entity.getFeature() - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - geom = self.layerToMapCoordinates(layer, f.geometry()) - - firstPt = self.linearObjectList.getPointAtVertex(self.vertexAt) - secondPt = self.linearObjectList.getPointAtVertex(self.secondVertexAt) - - result = qad_utils.breakQgsGeometry(QgsGeometry.fromPolyline(self.linearObjectList.asPolyline(tolerance2ApproxCurve)), \ - firstPt, secondPt, \ - tolerance2ApproxCurve) - if result is None: - return - - line1 = result[0] - line2 = result[1] - atSubGeom = result[2] - - updGeom = qad_utils.setSubGeom(geom, line1, self.atSubGeom) - if updGeom is None: - return - # trasformo la geometria nel crs del layer - f.setGeometry(self.mapToLayerCoordinates(layer, updGeom)) - - self.plugIn.beginEditCommand("Feature edited", layer) - - # plugIn, layer, feature, refresh, check_validity - if qad_layer.updateFeatureToLayer(self.plugIn, self.entity.layer, f, False, False) == False: - self.plugIn.destroyEditCommand() - return - - if line2 is not None: - brokenFeature2 = QgsFeature(f) - brokenFeature2.setGeometry(line2) - # plugIn, layer, feature, coordTransform, refresh, check_validity - if qad_layer.addFeatureToLayer(self.plugIn, layer, brokenFeature2, None, False, False) == False: - self.plugIn.destroyEditCommand() - return - - self.linearObjectList.fromPolyline(line1.asPolyline()) - self.plugIn.endEditCommand() - self.nOperationsToUndo = self.nOperationsToUndo + 1 - - - #============================================================================ - # waitForEntsel - #============================================================================ - def waitForEntsel(self): - # imposto il map tool - self.step = 1 - self.getPointMapTool().setMode(Qad_pedit_maptool_ModeEnum.ASK_FOR_ENTITY_SEL) - - keyWords = QadMsg.translate("Command_PEDIT", "Last") + "/" + \ - QadMsg.translate("Command_PEDIT", "Multiple") - prompt = QadMsg.translate("Command_PEDIT", "Select polyline or [{0}]: ").format(QadMsg.translate("Command_PEDIT", "Multiple")) - - englishKeyWords = "Last" + "/" + "Multiple" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o enter o una parola chiave - # msg, inputType, default, keyWords, valore nullo non permesso - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - None, \ - keyWords, QadInputModeEnum.NOT_NULL) - - - #============================================================================ - # WaitForMainMenu - #============================================================================ - def WaitForMainMenu(self): - # verifico se ci sono layer di tipo linea - line = False - if self.entity.isInitialized(): # selezionato solo un oggetto - if self.entity.layer.geometryType() == QGis.Line: - line = True - else: - layerList = self.entitySet.getLayerList() - for layer in layerList: - if layer.geometryType() == QGis.Line: - line = True - break - - if line == True: # se ci sono dei layer linea - if self.entity.isInitialized(): # selezionato solo un oggetto - if self.linearObjectList.isClosed(): # se é chiusa - keyWords = QadMsg.translate("Command_PEDIT", "Open") + "/" - englishKeyWords = "Open" - else: - keyWords = QadMsg.translate("Command_PEDIT", "Close") + "/" - englishKeyWords = "Close" - else: # selezionati più oggetti - keyWords = QadMsg.translate("Command_PEDIT", "Close") + "/" + \ - QadMsg.translate("Command_PEDIT", "Open") + "/" - englishKeyWords = "Close" + "/" + "Open" - - keyWords = keyWords + QadMsg.translate("Command_PEDIT", "Join") + "/" - englishKeyWords = englishKeyWords + "Join" - else: # se non ci sono dei layer linea - keyWords = "" - msg = "" - englishKeyWords = "" - - if self.entity.isInitialized(): # selezionato solo un oggetto - keyWords = keyWords + QadMsg.translate("Command_PEDIT", "Edit vertex") + "/" - englishKeyWords = englishKeyWords + "Edit vertex" - - keyWords = keyWords + QadMsg.translate("Command_PEDIT", "Fit") + "/" + \ - QadMsg.translate("Command_PEDIT", "Decurve") + "/" + \ - QadMsg.translate("Command_PEDIT", "Reverse") + "/" + \ - QadMsg.translate("Command_PEDIT", "Undo") - englishKeyWords = englishKeyWords + "Fit" + "/" + "Decurve" + "/" + "Reverse" + "/" + "Undo" - prompt = QadMsg.translate("Command_PEDIT", "Enter an option [{0}]: ").format(keyWords) - - self.step = 3 - self.getPointMapTool().setMode(Qad_pedit_maptool_ModeEnum.NONE) - - keyWords += "_" + englishKeyWords - # si appresta ad attendere enter o una parola chiave - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(prompt, \ - QadInputTypeEnum.KEYWORDS, \ - None, \ - keyWords, QadInputModeEnum.NONE) - return False - - - #============================================================================ - # WaitForJoin - #============================================================================ - def WaitForJoin(self): - CurrSettingsMsg = QadMsg.translate("QAD", "\nCurrent settings: ") - CurrSettingsMsg = CurrSettingsMsg + QadMsg.translate("Command_PEDIT", "Join type = ") - if self.joinMode == 1: - CurrSettingsMsg = CurrSettingsMsg + QadMsg.translate("Command_PEDIT", "extends the segments") - elif self.joinMode == 2: - CurrSettingsMsg = CurrSettingsMsg + QadMsg.translate("Command_PEDIT", "adds segments") - elif self.joinMode == 3: - CurrSettingsMsg = CurrSettingsMsg + QadMsg.translate("Command_PEDIT", "extends and adds segments") - - self.showMsg(CurrSettingsMsg) - self.waitForDistance() - - - #============================================================================ - # waitForDistance - #============================================================================ - def waitForDistance(self): - # imposto il map tool - self.getPointMapTool().setMode(Qad_pedit_maptool_ModeEnum.ASK_FOR_FIRST_TOLERANCE_PT) - - keyWords = QadMsg.translate("Command_PEDIT", "Join type") - prompt = QadMsg.translate("Command_PEDIT", "Specify gap tolerance or [{0}] <{1}>: ").format(keyWords, str(self.joinToleranceDist)) - - englishKeyWords = "Join type" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o enter o una parola chiave o un numero reale - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT | QadInputTypeEnum.KEYWORDS, \ - self.joinToleranceDist, \ - keyWords, \ - QadInputModeEnum.NOT_NEGATIVE) - self.step = 4 - - - #============================================================================ - # waitForJoinType - #============================================================================ - def waitForJoinType(self): - # imposto il map tool - self.getPointMapTool().setMode(Qad_pedit_maptool_ModeEnum.NONE) - - keyWords = QadMsg.translate("Command_PEDIT", "Extend") + "/" + \ - QadMsg.translate("Command_PEDIT", "Add") + "/" + \ - QadMsg.translate("Command_PEDIT", "Both") - englishKeyWords = "Extend" + "/" + "Add" + "/" + "Both" - if self.joinMode == 1: - default = QadMsg.translate("Command_PEDIT", "Extend") - elif self.joinMode == 2: - default = QadMsg.translate("Command_PEDIT", "Add") - elif self.joinMode == 3: - default = QadMsg.translate("Command_PEDIT", "Both") - prompt = QadMsg.translate("Command_PEDIT", "Specify join type [{0}] <{1}>: ").format(keyWords, default) - - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o enter o una parola chiave o un numero reale - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(prompt, QadInputTypeEnum.KEYWORDS, default, \ - keyWords) - self.step = 6 - - - #============================================================================ - # WaitForVertexEditingMenu - #============================================================================ - def WaitForVertexEditingMenu(self): - self.getPointMapTool().setLinearObjectList(self.linearObjectList, self.entity.layer) - - self.displayVertexMarker(self.vertexAt) - - keyWords = QadMsg.translate("Command_PEDIT", "Next") + "/" + \ - QadMsg.translate("Command_PEDIT", "Previous") - englishKeyWords = "Next" + "/" + "Previous" - - if self.entity.layer.geometryType() == QGis.Line: - keyWords = keyWords + "/" + QadMsg.translate("Command_PEDIT", "Break") - englishKeyWords = englishKeyWords + "/" + "Break" - - keyWords = keyWords + "/" + QadMsg.translate("Command_PEDIT", "Insert") + "/" + \ - QadMsg.translate("Command_PEDIT", "INsert before") + "/" + \ - QadMsg.translate("Command_PEDIT", "Move") + "/" + \ - QadMsg.translate("Command_PEDIT", "Straighten") + "/" + \ - QadMsg.translate("Command_PEDIT", "eXit") - englishKeyWords = englishKeyWords + "/" + "Insert" + "/" + "INsert before" + "/" + \ - "Move" + "/" + "Straighten" + "/" + "eXit" - - prompt = QadMsg.translate("Command_PEDIT", "Enter a vertex editing option [{0}] <{1}>: ").format(keyWords, self.default) - - self.step = 8 - self.getPointMapTool().setMode(Qad_pedit_maptool_ModeEnum.ASK_FOR_VERTEX) - - keyWords += "_" + englishKeyWords - # si appresta ad attendere enter o una parola chiave - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - self.default, \ - keyWords, QadInputModeEnum.NONE) - return False - - - #============================================================================ - # waitForNewVertex - #============================================================================ - def waitForNewVertex(self): - # imposto il map tool - self.getPointMapTool().setVertexAt(self.vertexAt, self.after) - self.getPointMapTool().setMode(Qad_pedit_maptool_ModeEnum.ASK_FOR_NEW_VERTEX) - - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_PEDIT", "Specify the position of the new vertex: ")) - self.step = 9 - - - #============================================================================ - # waitForMoveVertex - #============================================================================ - def waitForMoveVertex(self): - # imposto il map tool - self.getPointMapTool().setVertexAt(self.vertexAt) - self.getPointMapTool().setMode(Qad_pedit_maptool_ModeEnum.ASK_FOR_MOVE_VERTEX) - - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_PEDIT", "Specify the new vertex position: ")) - self.step = 10 - - - #============================================================================ - # WaitForVertexEditingMenu - #============================================================================ - def WaitForSecondVertex(self): - self.displayVertexMarker(self.secondVertexAt) - - keyWords = QadMsg.translate("Command_PEDIT", "Next") + "/" + \ - QadMsg.translate("Command_PEDIT", "Previous") + "/" + \ - QadMsg.translate("Command_PEDIT", "Go") + "/" + \ - QadMsg.translate("Command_PEDIT", "eXit") - englishKeyWords = "Next" + "/" + "Previous" + "/" + "Go" + "/" + "eXit" - prompt = QadMsg.translate("Command_PEDIT", "Enter a selection option for the second vertex [{0}] <{1}>: ").format(keyWords, self.default1) - - self.getPointMapTool().setMode(Qad_pedit_maptool_ModeEnum.ASK_FOR_VERTEX) - - keyWords += "_" + englishKeyWords - # si appresta ad attendere enter o una parola chiave - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - self.default1, \ - keyWords, QadInputModeEnum.NONE) - self.step = 11 - - return False - - - def run(self, msgMapTool = False, msg = None): - if self.plugIn.canvas.mapRenderer().destinationCrs().geographicFlag(): - self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) - return True # fine comando - - if self.step == 0: - self.waitForEntsel() - return False # continua - - #========================================================================= - # RISPOSTA ALLA SELEZIONE OGGETTI - elif self.step == 1: - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - else: - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("Command_PEDIT", "Multiple") or value == "Multiple": - self.SSGetClass.checkPolygonLayer = True - self.SSGetClass.run(msgMapTool, msg) - self.step = 2 - return False - elif type(value) == QgsPoint: # se é stato selezionato un punto - self.entity.clear() - self.linearObjectList.removeAll() - - if self.getPointMapTool().entity.isInitialized(): - if self.setEntityInfo(self.getPointMapTool().entity.layer, \ - self.getPointMapTool().entity.featureId, value) == True: - self.WaitForMainMenu() - return False - else: - # cerco se ci sono entità nel punto indicato considerando - # solo layer lineari o poligono editabili che non appartengano a quote - layerList = [] - for layer in self.plugIn.canvas.layers(): - if layer.type() == QgsMapLayer.VectorLayer and \ - (layer.geometryType() == QGis.Line or layer.geometryType() == QGis.Polygon) and \ - layer.isEditable(): - if len(QadDimStyles.getDimListByLayer(layer)) == 0: - layerList.append(layer) - - result = qad_utils.getEntSel(self.getPointMapTool().toCanvasCoordinates(value), - self.getPointMapTool(), \ - layerList) - if result is not None: - # result[0] = feature, result[1] = layer, result[0] = point - if self.setEntityInfo(result[1], result[0].id(), result[2]) == True: - self.WaitForMainMenu() - return False - else: - return True # fine comando - - # si appresta ad attendere la selezione degli oggetti - self.waitForEntsel() - return False - - #========================================================================= - # RISPOSTA ALLA SELEZIONE DI UN GRUPPO OGGETTI - elif self.step == 2: - if self.SSGetClass.run(msgMapTool, msg) == True: - self.entitySet.set(self.SSGetClass.entitySet) - - if self.entitySet.count() == 0: - self.waitForEntsel() - else: - self.WaitForMainMenu() - self.getPointMapTool().refreshSnapType() # aggiorno lo snapType che può essere variato dal maptool di selezione entità - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DEL MENU PRINCIPALE (da step = 1 e 2) - elif self.step == 3: # dopo aver atteso una opzione si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - self.WaitForMainMenu() - return False - else: # il punto arriva come parametro della funzione - value = msg - - if value == QadMsg.translate("Command_PEDIT", "Close") or value == "Close": - self.setClose(True) - elif value == QadMsg.translate("Command_PEDIT", "Open") or value == "Open": - self.setClose(False) - elif value == QadMsg.translate("Command_PEDIT", "Edit vertex") or value == "Edit vertex": - self.vertexAt = 0 - self.default = QadMsg.translate("Command_PEDIT", "Next") - self.WaitForVertexEditingMenu() - return False - elif value == QadMsg.translate("Command_PEDIT", "Join") or value == "Join": - if self.entity.isInitialized(): # selezionato solo un oggetto - self.SSGetClass.checkPolygonLayer = False # scarto i poligoni - self.SSGetClass.run(msgMapTool, msg) - self.step = 7 - return False - else: - self.WaitForJoin() - return False - elif value == QadMsg.translate("Command_PEDIT", "Fit") or value == "Fit": - self.curve(True) - elif value == QadMsg.translate("Command_PEDIT", "Decurve") or value == "Decurve": - self.curve(False) - elif value == QadMsg.translate("Command_PEDIT", "Reverse") or value == "Reverse": - self.reverse() - elif value == QadMsg.translate("Command_PEDIT", "Undo") or value == "Undo": - if self.nOperationsToUndo > 0: - self.nOperationsToUndo = self.nOperationsToUndo - 1 - self.plugIn.undoEditCommand() - else: - self.showMsg(QadMsg.translate("QAD", "\nThe command has been canceled.")) - - if self.entity.isInitialized(): # selezionato solo un oggetto - if self.atSubGeom is not None: - # ricarico la geometria ripristinata dall'annulla - geom = self.entity.getGeometry() - # ritorna la sotto-geometria al vertice e la sua posizione nella geometria (0-based) - subGeom = qad_utils.getSubGeomAt(geom, self.atSubGeom) - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - subGeom = self.layerToMapCoordinates(self.entity.layer, subGeom) - self.linearObjectList.fromPolyline(subGeom.asPolyline()) - else: - return True # fine comando - - self.entity.deselectOnLayer() - self.entitySet.deselectOnLayer() - self.WaitForMainMenu() - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DELLA DISTANZA DI APPROSSIMAZIONE (da step = 3) - elif self.step == 4: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - value = self.joinToleranceDist - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - else: - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("Command_PEDIT", "Join type") or value == "Join type": - # si appresta ad attendere il tipo di unione - self.waitForJoinType() - elif type(value) == QgsPoint: # se é stato inserito il primo punto per il calcolo della distanza - # imposto il map tool - self.firstPt.set(value.x(), value.y()) - self.getPointMapTool().firstPt = self.firstPt - self.getPointMapTool().setMode(Qad_pedit_maptool_ModeEnum.FIRST_TOLERANCE_PT_KNOWN_ASK_FOR_SECOND_PT) - - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_PEDIT", "Specify second point: ")) - self.step = 5 - elif type(value) == float: - self.joinToleranceDist = value - self.plugIn.setJoinToleranceDist(self.joinToleranceDist) - self.join() - self.entity.deselectOnLayer() - self.entitySet.deselectOnLayer() - self.WaitForMainMenu() - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA SECONDO PUNTO PER DISTANZA DI APPROSSIMAZIONE (da step = 4) - elif self.step == 5: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if value == self.firstPt: - self.showMsg(QadMsg.translate("QAD", "\nThe value must be positive and not zero.")) - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_PEDIT", "Specify second point: ")) - return False - - self.joinToleranceDist = qad_utils.getDistance(self.firstPt, value) - self.plugIn.setJoinToleranceDist(self.joinToleranceDist) - self.join() - self.entity.deselectOnLayer() - self.entitySet.deselectOnLayer() - self.WaitForMainMenu() - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DELLA MODALITA' DI UNIONE (da step = 4) - elif self.step == 6: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("Command_PEDIT", "Extend") or value == "Extend": - self.joinMode = 1 - self.plugIn.setJoinMode(self.joinMode) - elif value == QadMsg.translate("Command_PEDIT", "Add") or value == "Add": - self.joinMode = 2 - self.plugIn.setJoinMode(self.joinMode) - elif value == QadMsg.translate("Command_PEDIT", "Both") or value == "Both": - self.joinMode = 3 - self.plugIn.setJoinMode(self.joinMode) - - self.WaitForJoin() - return False - - #========================================================================= - # RISPOSTA ALLA SELEZIONE DI UN GRUPPO OGGETTI DA UNIRE (da step = 3) - elif self.step == 7: - if self.SSGetClass.run(msgMapTool, msg) == True: - self.entitySet.set(self.SSGetClass.entitySet) - - if self.entitySet.count() > 0: - self.joinToleranceDist = 0.0 - self.join() - - self.entity.deselectOnLayer() - self.entitySet.deselectOnLayer() - self.WaitForMainMenu() - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DI OPZIONI EDITAZIONE VERTICI (da step = 3) - elif self.step == 8: # dopo aver atteso un punto o una opzione si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - value = self.default - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - else: - value = self.getPointMapTool().point - else: # il punto o l'opzione arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("Command_PEDIT", "Next") or value == "Next": - self.default = value - self.vertexAt = self.getNextVertex(self.vertexAt) - self.WaitForVertexEditingMenu() - elif value == QadMsg.translate("Command_PEDIT", "Previous") or value == "Previous": - self.default = value - self.vertexAt = self.getPrevVertex(self.vertexAt) - self.WaitForVertexEditingMenu() - elif value == QadMsg.translate("Command_PEDIT", "Break") or value == "Break": - self.editVertexMode = QadMsg.translate("Command_PEDIT", "Break") - self.secondVertexAt = self.vertexAt - self.default1 = QadMsg.translate("Command_PEDIT", "Next") - self.WaitForSecondVertex() - return False - elif value == QadMsg.translate("Command_PEDIT", "Insert") or value == "Insert": - self.after = True - self.waitForNewVertex() - elif value == QadMsg.translate("Command_PEDIT", "INsert before") or value == "INsert before": - self.after = False - self.waitForNewVertex() - elif value == QadMsg.translate("Command_PEDIT", "Move") or value == "Move": - self.waitForMoveVertex() - elif value == QadMsg.translate("Command_PEDIT", "Straighten") or value == "Straighten": - self.editVertexMode = QadMsg.translate("Command_PEDIT", "Straighten") - self.secondVertexAt = self.vertexAt - self.default1 = QadMsg.translate("Command_PEDIT", "Next") - self.WaitForSecondVertex() - return False - elif value == QadMsg.translate("Command_PEDIT", "eXit") or value == "eXit": - self.WaitForMainMenu() - elif type(value) == QgsPoint: # se é stato inserito un punto - # cerco il vertice più vicino al punto - self.vertexAt = self.linearObjectList.closestVertexWithContext(value) - self.WaitForVertexEditingMenu() - - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DEL NUOVO VERTICE DA INSERIRE (da step = 8) - elif self.step == 9: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - self.insertVertexAt(value) - self.vertexAt = self.vertexAt + (1 if self.after else -1) - self.WaitForVertexEditingMenu() - - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DELLA POSIZIONE DEL VERTICE DA SPOSTARE (da step = 8) - elif self.step == 10: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - self.moveVertexAt(value) - self.WaitForVertexEditingMenu() - - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DEL SECONDO VERTICE (da step = 8) - elif self.step == 11: # dopo aver atteso un punto o una opzione si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - value = self.default - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - else: - value = self.getPointMapTool().point - else: # il punto o l'opzione arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("Command_PEDIT", "Next") or value == "Next": - self.default1 = value - self.secondVertexAt = self.getNextVertex(self.secondVertexAt) - self.WaitForSecondVertex() - elif value == QadMsg.translate("Command_PEDIT", "Previous") or value == "Previous": - self.default1 = value - self.secondVertexAt = self.getPrevVertex(self.secondVertexAt) - self.WaitForSecondVertex() - elif value == QadMsg.translate("Command_PEDIT", "Go") or value == "Go": - pt = self.linearObjectList.getPointAtVertex(self.vertexAt) - - if self.editVertexMode == QadMsg.translate("Command_PEDIT", "Break"): - self.breakFromVertexAtToSecondVertexAt() - elif self.editVertexMode == QadMsg.translate("Command_PEDIT", "Straighten"): - self.straightenFromVertexAtToSecondVertexAt() - - self.vertexAt = self.linearObjectList.getVertexPosAtPt(pt) - self.WaitForVertexEditingMenu() - elif value == QadMsg.translate("Command_PEDIT", "eXit") or value == "eXit": - self.WaitForVertexEditingMenu() - elif type(value) == QgsPoint: # se é stato inserito il primo punto - # cerco il vertice più vicino al punto - self.secondVertexAt = self.linearObjectList.closestVertexWithContext(value) - self.WaitForSecondVertex() - - return False - - -#============================================================================ -# Classe che gestisce il comando per inserire/cancellare un vertice per i grip -#============================================================================ -class QadGRIPINSERTREMOVEVERTEXCommandClass(QadCommandClass): - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadGRIPINSERTREMOVEVERTEXCommandClass(self.plugIn) - - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - self.entity = None - self.skipToNextGripCommand = False - self.copyEntities = False - self.basePt = QgsPoint() - self.nOperationsToUndo = 0 - - self.after = True - self.insert_mode = True - self.vertexAt = 0 - self.firstPt = QgsPoint() - self.linearObjectList = qad_utils.QadLinearObjectList() - - def __del__(self): - QadCommandClass.__del__(self) - - - def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): - if (self.plugIn is not None): - if self.PointMapTool is None: - self.PointMapTool = Qad_pedit_maptool(self.plugIn) - return self.PointMapTool - else: - return None - - - def setInsertVertexAfter_Mode(self): - self.after = True - self.insert_mode = True - - def setInsertVertexBefore_Mode(self): - self.after = False - self.insert_mode = True - - def setRemoveVertex_mode(self): - self.insert_mode = False - - - #============================================================================ - # setSelectedEntityGripPoints - #============================================================================ - def setSelectedEntityGripPoints(self, entitySetGripPoints): - # lista delle entityGripPoint con dei grip point selezionati - # setta la prima entità con un grip selezionato - self.entity = None - for entityGripPoints in entitySetGripPoints.entityGripPoints: - for gripPoint in entityGripPoints.gripPoints: - # grip point selezionato - if gripPoint.getStatus() == qad_grip.QadGripStatusEnum.SELECTED: - # verifico se l'entità appartiene ad uno stile di quotatura - if entityGripPoints.entity.isDimensionComponent(): - return False - if entityGripPoints.entity.getEntityType() != QadEntityGeomTypeEnum.LINESTRING: - return False - - # setta: self.entity, self.linearObjectList, self.atSubGeom - self.entity = entityGripPoints.entity - - self.firstPt.set(gripPoint.getPoint().x(), gripPoint.getPoint().y()) - - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - geom = self.layerToMapCoordinates(self.entity.layer, self.entity.getGeometry()) - # ritorna una tupla (, - # - # - # ) - dummy = qad_utils.closestSegmentWithContext(self.firstPt, geom) - if dummy[2] is not None: - # ritorna la sotto-geometria al vertice e la sua posizione nella geometria (0-based) - subGeom, self.atSubGeom = qad_utils.getSubGeomAtVertex(geom, dummy[2]) - self.linearObjectList.fromPolyline(subGeom.asPolyline()) - # setto il n. di vertice - self.vertexAt = gripPoint.nVertex - - self.getPointMapTool().setLinearObjectList(self.linearObjectList, self.entity.layer) - return True - - return False - - - #============================================================================ - # insertVertexAt - #============================================================================ - def insertVertexAt(self, pt): - layer = self.entity.layer - f = self.entity.getFeature() - if f is None: # non c'è più la feature - return False - - # faccio una copia locale - linearObjectList = qad_utils.QadLinearObjectList(self.linearObjectList) - - tolerance2ApproxCurve = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) - - if self.after: # dopo - if self.vertexAt == linearObjectList.qty() and linearObjectList.isClosed(): - linearObjectList.insertPoint(0, pt) - else: - linearObjectList.insertPoint(self.vertexAt, pt) - else: # prima - if self.vertexAt == 0 and linearObjectList.isClosed(): - linearObjectList.insertPoint(self.linearObjectList.qty() - 1, pt) - else: - linearObjectList.insertPoint(self.vertexAt - 1, pt) - - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - geom = self.layerToMapCoordinates(layer, self.entity.getGeometry()) - - updSubGeom = QgsGeometry.fromPolyline(linearObjectList.asPolyline(tolerance2ApproxCurve)) - updGeom = qad_utils.setSubGeom(geom, updSubGeom, self.atSubGeom) - if updGeom is None: - return - # trasformo la geometria nel crs del layer - f.setGeometry(self.mapToLayerCoordinates(layer, updGeom)) - - self.plugIn.beginEditCommand("Feature edited", layer) - - if self.copyEntities == False: - # plugIn, layer, feature, refresh, check_validity - if qad_layer.updateFeatureToLayer(self.plugIn, layer, f, False, False) == False: - self.plugIn.destroyEditCommand() - return False - else: - # plugIn, layer, features, coordTransform, refresh, check_validity - if qad_layer.addFeatureToLayer(self.plugIn, layer, f, None, False, False) == False: - self.plugIn.destroyEditCommand() - return False - - self.plugIn.endEditCommand() - self.nOperationsToUndo = self.nOperationsToUndo + 1 - - - #============================================================================ - # removeVertexAt - #============================================================================ - def removeVertexAt(self): - layer = self.entity.layer - f = self.entity.getFeature() - if f is None: # non c'è più la feature - return False - - # faccio una copia locale - linearObjectList = qad_utils.QadLinearObjectList(self.linearObjectList) - - prevLinearObject, nextLinearObject = linearObjectList.getPrevNextLinearObjectsAtVertex(self.vertexAt) - if prevLinearObject: - firstPt = prevLinearObject.getStartPt() - linearObjectList.remove(self.vertexAt - 1) # rimuovo la parte precedente - - if nextLinearObject: - if prevLinearObject: - # modifico la parte successiva - secondPt = nextLinearObject.getEndPt() - nextLinearObject.setSegment(firstPt, secondPt) - else: - linearObjectList.remove(self.vertexAt) # rimuovo la parte - - - tolerance2ApproxCurve = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) - - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - geom = self.layerToMapCoordinates(layer, self.entity.getGeometry()) - - updSubGeom = QgsGeometry.fromPolyline(linearObjectList.asPolyline(tolerance2ApproxCurve)) - updGeom = qad_utils.setSubGeom(geom, updSubGeom, self.atSubGeom) - if updGeom is None: - return - # trasformo la geometria nel crs del layer - f.setGeometry(self.mapToLayerCoordinates(layer, updGeom)) - - self.plugIn.beginEditCommand("Feature edited", layer) - - # plugIn, layer, feature, refresh, check_validity - if qad_layer.updateFeatureToLayer(self.plugIn, layer, f, False, False) == False: - self.plugIn.destroyEditCommand() - return False - - self.plugIn.endEditCommand() - - - #============================================================================ - # waitForBasePt - #============================================================================ - def waitForBasePt(self): - self.step = 2 - # imposto il map tool - self.getPointMapTool().setMode(Qad_pedit_maptool_ModeEnum.ASK_FOR_BASE_PT) - - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_GRIP", "Specify base point: ")) - - - #============================================================================ - # waitForNewVertex - #============================================================================ - def waitForNewVertex(self): - # imposto il map tool - self.getPointMapTool().setVertexAt(self.vertexAt, self.after) - if self.basePt is not None: - self.getPointMapTool().firstPt = self.basePt - self.getPointMapTool().setMode(Qad_pedit_maptool_ModeEnum.ASK_FOR_NEW_VERTEX) - - keyWords = QadMsg.translate("Command_GRIP", "Base point") + "/" + \ - QadMsg.translate("Command_GRIP", "Copy") + "/" + \ - QadMsg.translate("Command_GRIP", "Undo") + "/" + \ - QadMsg.translate("Command_GRIP", "eXit") - prompt = QadMsg.translate("Command_GRIPINSERTREMOVEVERTEX", "Specify the position of the new vertex or [{0}]: ").format(keyWords) - - englishKeyWords = "Base point" + "/" + "Copy" + "/" + "Undo" + "/" "eXit" - keyWords += "_" + englishKeyWords - - # si appresta ad attendere un punto o enter o una parola chiave - # msg, inputType, default, keyWords, valori positivi - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - None, \ - keyWords, QadInputModeEnum.NONE) - self.step = 1 - - - #============================================================================ - # run - #============================================================================ - def run(self, msgMapTool = False, msg = None): - if self.plugIn.canvas.mapRenderer().destinationCrs().geographicFlag(): - self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) - return True # fine comando - - #========================================================================= - # RICHIESTA SELEZIONE OGGETTI - if self.step == 0: # inizio del comando - if self.entity is None: # non ci sono oggetti da stirare - return True - - if self.insert_mode: - self.showMsg(QadMsg.translate("Command_GRIPINSERTREMOVEVERTEX", "\n** ADD VERTEX **\n")) - # si appresta ad attendere un nuovo punto - self.waitForNewVertex() - else: - self.showMsg(QadMsg.translate("Command_GRIPINSERTREMOVEVERTEX", "\n** REMOVE VERTEX **\n")) - self.removeVertexAt() - return True - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DEL NUOVO VERTICE DA INSERIRE (da step = 1) - elif self.step == 1: - ctrlKey = False - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - if self.copyEntities == False: - self.skipToNextGripCommand = True - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - ctrlKey = self.getPointMapTool().ctrlKey - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("Command_GRIP", "Base point") or value == "Base point": - # si appresta ad attendere il punto base - self.waitForBasePt() - elif value == QadMsg.translate("Command_GRIP", "Copy") or value == "Copy": - # Copia entità lasciando inalterate le originali - self.copyEntities = True - # si appresta ad attendere un nuovo punto - self.waitForNewVertex() - elif value == QadMsg.translate("Command_GRIP", "Undo") or value == "Undo": - if self.nOperationsToUndo > 0: - self.nOperationsToUndo = self.nOperationsToUndo - 1 - self.plugIn.undoEditCommand() - else: - self.showMsg(QadMsg.translate("QAD", "\nThe command has been canceled.")) - # si appresta ad attendere un nuovo punto - self.waitForNewVertex() - elif value == QadMsg.translate("Command_GRIP", "eXit") or value == "eXit": - return True # fine comando - elif type(value) == QgsPoint: - if ctrlKey: - self.copyEntities = True - - offsetX = value.x() - self.basePt.x() - offsetY = value.y() - self.basePt.y() - value.set(self.firstPt.x() + offsetX, self.firstPt.y() + offsetY) - self.insertVertexAt(value) - - if self.copyEntities == False: - return True - # si appresta ad attendere un nuovo punto - self.waitForNewVertex() - else: - if self.copyEntities == False: - self.skipToNextGripCommand = True - return True # fine comando - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA PUNTO BASE (da step = 1) - elif self.step == 2: # dopo aver atteso un punto - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - pass # opzione di default - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == QgsPoint: # se é stato inserito il punto base - self.basePt.set(value.x(), value.y()) - # imposto il map tool - self.getPointMapTool().basePt = self.basePt - self.getPointMapTool().firstPt = self.basePt - - # si appresta ad attendere un nuovo punto - self.waitForNewVertex() - - return False - - -#============================================================================ -# Classe che gestisce il comando per convertire in arco o in linea un segmento per i grip -#============================================================================ -class QadGRIPARCLINECONVERTCommandClass(QadCommandClass): - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadGRIPARCLINECONVERTCommandClass(self.plugIn) - - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - self.entity = None - self.skipToNextGripCommand = False - self.copyEntities = False - self.nOperationsToUndo = 0 - self.basePt = QgsPoint() - - self.lineToArc = True - self.partAt = 0 - self.linearObjectList = qad_utils.QadLinearObjectList() # in map coordinate - - def __del__(self): - QadCommandClass.__del__(self) - - - def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): - if (self.plugIn is not None): - if self.PointMapTool is None: - self.PointMapTool = Qad_gripLineToArcConvert_maptool(self.plugIn) - return self.PointMapTool - else: - return None - - - def setLineToArcConvert_Mode(self): - self.lineToArc = True - - def setArcToLineConvert_Mode(self): - self.lineToArc = False - - - #============================================================================ - # setSelectedEntityGripPoints - #============================================================================ - def setSelectedEntityGripPoints(self, entitySetGripPoints): - # lista delle entityGripPoint con dei grip point selezionati - # setta la prima entità con un grip selezionato - self.entity = None - for entityGripPoints in entitySetGripPoints.entityGripPoints: - for gripPoint in entityGripPoints.gripPoints: - # grip point selezionato - if gripPoint.getStatus() == qad_grip.QadGripStatusEnum.SELECTED and \ - (gripPoint.gripType == qad_grip.QadGripPointTypeEnum.LINE_MID_POINT or \ - gripPoint.gripType == qad_grip.QadGripPointTypeEnum.ARC_MID_POINT): - # verifico se l'entità appartiene ad uno stile di quotatura - if entityGripPoints.entity.isDimensionComponent(): - return False - if entityGripPoints.entity.getEntityType() != QadEntityGeomTypeEnum.LINESTRING and \ - entityGripPoints.entity.getEntityType() != QadEntityGeomTypeEnum.ARC: - return False - - # setta: self.entity, self.linearObjectList, self.atSubGeom - self.entity = entityGripPoints.entity - - firstPt = QgsPoint(gripPoint.getPoint()) - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - geom = self.layerToMapCoordinates(self.entity.layer, self.entity.getGeometry()) - # ritorna una tupla (, - # - # - # ) - dummy = qad_utils.closestSegmentWithContext(firstPt, geom) - if dummy[2] is not None: - # ritorna la sotto-geometria al vertice e la sua posizione nella geometria (0-based) - subGeom, self.atSubGeom = qad_utils.getSubGeomAtVertex(geom, dummy[2]) - self.linearObjectList.fromPolyline(subGeom.asPolyline()) - # setto il n. della parte - self.partAt = gripPoint.nVertex - - self.getPointMapTool().setLinearObjectList(self.linearObjectList, self.entity.layer, self.partAt) - - return True - - return False - - - #============================================================================ - # convertLineToArc - #============================================================================ - def convertLineToArc(self, pt): - layer = self.entity.layer - f = self.entity.getFeature() - if f is None: # non c'è più la feature - return False - - # faccio una copia locale - linearObjectList = qad_utils.QadLinearObjectList(self.linearObjectList) - - tolerance2ApproxCurve = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) - linearObject = linearObjectList.getLinearObjectAt(self.partAt) - if linearObject.isArc(): # se è già arco - return False - - startPt = linearObject.getStartPt() - endPt = linearObject.getEndPt() - arc = QadArc() - if arc.fromStartSecondEndPts(startPt, pt, endPt) == False: - return - if qad_utils.ptNear(startPt, arc.getStartPt()): - linearObject.setArc(arc, False) # arco non inverso - else: - linearObject.setArc(arc, True) # arco inverso - - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - geom = self.layerToMapCoordinates(layer, self.entity.getGeometry()) - - updSubGeom = QgsGeometry.fromPolyline(linearObjectList.asPolyline(tolerance2ApproxCurve)) - updGeom = qad_utils.setSubGeom(geom, updSubGeom, self.atSubGeom) - if updGeom is None: - return - # trasformo la geometria nel crs del layer - f.setGeometry(self.mapToLayerCoordinates(layer, updGeom)) - - self.plugIn.beginEditCommand("Feature edited", layer) - - if self.copyEntities == False: - # plugIn, layer, feature, refresh, check_validity - if qad_layer.updateFeatureToLayer(self.plugIn, layer, f, False, False) == False: - self.plugIn.destroyEditCommand() - return False - else: - # plugIn, layer, features, coordTransform, refresh, check_validity - if qad_layer.addFeatureToLayer(self.plugIn, layer, f, None, False, False) == False: - self.plugIn.destroyEditCommand() - return False - - self.plugIn.endEditCommand() - self.nOperationsToUndo = self.nOperationsToUndo + 1 - - - #============================================================================ - # convertArcToLine - #============================================================================ - def convertArcToLine(self): - layer = self.entity.layer - f = self.entity.getFeature() - if f is None: # non c'è più la feature - return False - - # faccio una copia locale - linearObjectList = qad_utils.QadLinearObjectList(self.linearObjectList) - - tolerance2ApproxCurve = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) - - linearObject = linearObjectList.getLinearObjectAt(self.partAt) - if linearObject.isSegment(): # se è già segmento retto - return False - - linearObject.setSegment(linearObject.getStartPt(), linearObject.getEndPt()) - - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - geom = self.layerToMapCoordinates(layer, self.entity.getGeometry()) - - updSubGeom = QgsGeometry.fromPolyline(linearObjectList.asPolyline(tolerance2ApproxCurve)) - updGeom = qad_utils.setSubGeom(geom, updSubGeom, self.atSubGeom) - if updGeom is None: - return - # trasformo la geometria nel crs del layer - f.setGeometry(self.mapToLayerCoordinates(layer, updGeom)) - - self.plugIn.beginEditCommand("Feature edited", layer) - - if self.copyEntities == False: - # plugIn, layer, feature, refresh, check_validity - if qad_layer.updateFeatureToLayer(self.plugIn, layer, f, False, False) == False: - self.plugIn.destroyEditCommand() - return False - else: - # plugIn, layer, features, coordTransform, refresh, check_validity - if qad_layer.addFeatureToLayer(self.plugIn, layer, f, None, False, False) == False: - self.plugIn.destroyEditCommand() - return False - - self.plugIn.endEditCommand() - self.nOperationsToUndo = self.nOperationsToUndo + 1 - - - #============================================================================ - # waitForConvertToArc - #============================================================================ - def waitForConvertToArc(self): - # imposto il map tool - self.getPointMapTool().setMode(Qad_gripLineToArcConvert_maptool_ModeEnum.START_END_PT_KNOWN_ASK_FOR_SECOND_PT) - - keyWords = QadMsg.translate("Command_GRIP", "Copy") + "/" + \ - QadMsg.translate("Command_GRIP", "Undo") + "/" + \ - QadMsg.translate("Command_GRIP", "eXit") - prompt = QadMsg.translate("Command_GRIPARCLINECONVERT", "Specify the arc middle point or [{0}]: ").format(keyWords) - - englishKeyWords = "Copy" + "/" + "Undo" + "/" "eXit" - keyWords += "_" + englishKeyWords - - # si appresta ad attendere un punto o enter o una parola chiave - # msg, inputType, default, keyWords, valori positivi - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - None, \ - keyWords, QadInputModeEnum.NONE) - self.step = 1 - - - #============================================================================ - # run - #============================================================================ - def run(self, msgMapTool = False, msg = None): - if self.plugIn.canvas.mapRenderer().destinationCrs().geographicFlag(): - self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) - return True # fine comando - - #========================================================================= - # RICHIESTA SELEZIONE OGGETTI - if self.step == 0: # inizio del comando - if self.entity is None: # non ci sono oggetti da stirare - return True - - if self.lineToArc: - self.showMsg(QadMsg.translate("Command_GRIPINSERTREMOVEVERTEX", "\n** CONVERT TO ARC **\n")) - # si appresta ad attendere un punto per definire l'arco - self.waitForConvertToArc() - else: - self.showMsg(QadMsg.translate("Command_GRIPINSERTREMOVEVERTEX", "\n** CONVERT TO LINE **\n")) - self.convertArcToLine() - return True - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DEL NUOVO PUNTO PER DEFINIRE UN ARCO (da step = 1) - elif self.step == 1: - ctrlKey = False - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - if self.copyEntities == False: - self.skipToNextGripCommand = True - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - ctrlKey = self.getPointMapTool().ctrlKey - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("Command_GRIP", "Copy") or value == "Copy": - # Copia entità lasciando inalterate le originali - self.copyEntities = True - # si appresta ad attendere un punto per definire l'arco - self.waitForConvertToArc() - elif value == QadMsg.translate("Command_GRIP", "Undo") or value == "Undo": - if self.nOperationsToUndo > 0: - self.nOperationsToUndo = self.nOperationsToUndo - 1 - self.plugIn.undoEditCommand() - else: - self.showMsg(QadMsg.translate("QAD", "\nThe command has been canceled.")) - # si appresta ad attendere un punto per definire l'arco - self.waitForConvertToArc() - elif value == QadMsg.translate("Command_GRIP", "eXit") or value == "eXit": - return True # fine comando - elif type(value) == QgsPoint: - if ctrlKey: - self.copyEntities = True - - self.convertLineToArc(value) - - if self.copyEntities == False: - return True - # si appresta ad attendere un punto per definire l'arco - self.waitForConvertToArc() - else: - if self.copyEntities == False: - self.skipToNextGripCommand = True - return True # fine comando - - return False diff --git a/qad_pedit_maptool.py b/qad_pedit_maptool.py deleted file mode 100644 index 8eca08ee..00000000 --- a/qad_pedit_maptool.py +++ /dev/null @@ -1,333 +0,0 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - classe per gestire il map tool in ambito del comando pedit - - ------------------- - begin : 2013-05-22 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -from qgis.gui import * -import math - - -import qad_utils -from qad_snapper import * -from qad_variables import * -from qad_getpoint import * -from qad_rubberband import QadRubberBand -from qad_highlight import QadHighlight -from qad_dim import QadDimStyles - - -#=============================================================================== -# Qad_pedit_maptool_ModeEnum class. -#=============================================================================== -class Qad_pedit_maptool_ModeEnum(): - # si richiede la selezione di un'entità - ASK_FOR_ENTITY_SEL = 1 - # non si richiede niente - NONE = 2 - # si richiede il primo punto per calcolo distanza di approssimazione - ASK_FOR_FIRST_TOLERANCE_PT = 3 - # noto il primo punto per calcolo distanza di approssimazione si richiede il secondo punto - FIRST_TOLERANCE_PT_KNOWN_ASK_FOR_SECOND_PT = 4 - # si richiede un nuovo vertice da inserire - ASK_FOR_NEW_VERTEX = 5 - # si richiede la nuova posizione di un vertice da spostare - ASK_FOR_MOVE_VERTEX = 6 - # si richiede la posizione più vicina ad un vertice - ASK_FOR_VERTEX = 7 - # si richede il punto base (grip mode) - ASK_FOR_BASE_PT = 8 - - -#=============================================================================== -# Qad_pedit_maptool class -#=============================================================================== -class Qad_pedit_maptool(QadGetPoint): - - def __init__(self, plugIn): - QadGetPoint.__init__(self, plugIn) - - self.firstPt = None - self.mode = None - - self.layer = None - self.linearObjectList = qad_utils.QadLinearObjectList() - self.tolerance2ApproxCurve = None - self.vertexAt = 0 - self.vertexPt = None - self.after = True - self.basePt = None - self.__highlight = QadHighlight(self.canvas) - - - def hidePointMapToolMarkers(self): - QadGetPoint.hidePointMapToolMarkers(self) - self.__highlight.hide() - - def showPointMapToolMarkers(self): - QadGetPoint.showPointMapToolMarkers(self) - self.__highlight.show() - - def clear(self): - QadGetPoint.clear(self) - self.__highlight.reset() - self.mode = None - if self.basePt is not None: - del(self.basePt) - self.basePt = None - - def setLinearObjectList(self, linearObjectList, layer): - self.linearObjectList.set(linearObjectList) - self.layer = layer - self.tolerance2ApproxCurve = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) - - - def setVertexAt(self, vertexAt, after = None): - if vertexAt == self.linearObjectList.qty(): - self.firstPt = self.linearObjectList.getLinearObjectAt(-1).getEndPt() - else: - self.firstPt = self.linearObjectList.getLinearObjectAt(vertexAt).getStartPt() - - self.vertexPt = QgsPoint(self.firstPt) - self.vertexAt = vertexAt - self.after = after - - - def canvasMoveEvent(self, event): - QadGetPoint.canvasMoveEvent(self, event) - - self.__highlight.reset() - tmpLinearObjectList = None - - # si richiede un nuovo vertice da inserire - if self.mode == Qad_pedit_maptool_ModeEnum.ASK_FOR_NEW_VERTEX: - if self.basePt is not None: - offSetX = self.tmpPoint.x() - self.basePt.x() - offSetY = self.tmpPoint.y() - self.basePt.y() - newPt = QgsPoint(self.vertexPt.x() + offSetX, self.vertexPt.y() + offSetY) - else: - newPt = QgsPoint(self.tmpPoint) - - tmpLinearObjectList = qad_utils.QadLinearObjectList() - tmpLinearObjectList.set(self.linearObjectList) - if self.after: # dopo - if self.vertexAt == tmpLinearObjectList.qty() and tmpLinearObjectList.isClosed(): - tmpLinearObjectList.insertPoint(0, newPt) - else: - tmpLinearObjectList.insertPoint(self.vertexAt, newPt) - else: # prima - if self.vertexAt == 0 and tmpLinearObjectList.isClosed(): - tmpLinearObjectList.insertPoint(tmpLinearObjectList.qty() - 1, newPt) - else: - tmpLinearObjectList.insertPoint(self.vertexAt - 1, newPt) - elif self.mode == Qad_pedit_maptool_ModeEnum.ASK_FOR_MOVE_VERTEX: - newPt = QgsPoint(self.tmpPoint) - tmpLinearObjectList = qad_utils.QadLinearObjectList() - tmpLinearObjectList.set(self.linearObjectList) - tmpLinearObjectList.movePoint(self.vertexAt, newPt) - - if tmpLinearObjectList is not None: - pts = tmpLinearObjectList.asPolyline(self.tolerance2ApproxCurve) - if self.layer.geometryType() == QGis.Polygon: - geom = QgsGeometry.fromPolygon([pts]) - else: - geom = QgsGeometry.fromPolyline(pts) - - # trasformo la geometria nel crs del layer - self.__highlight.addGeometry(self.mapToLayerCoordinates(self.layer, geom), self.layer) - - - def activate(self): - QadGetPoint.activate(self) - self.__highlight.show() - - def deactivate(self): - try: # necessario perché se si chiude QGIS parte questo evento nonostante non ci sia più l'oggetto maptool ! - QadGetPoint.deactivate(self) - self.__highlight.hide() - except: - pass - - def setMode(self, mode): - self.mode = mode - - # si richiede la selezione di un'entità - if self.mode == Qad_pedit_maptool_ModeEnum.ASK_FOR_ENTITY_SEL: - self.setSelectionMode(QadGetPointSelectionModeEnum.ENTITY_SELECTION) - - # solo layer lineari o poligono editabili che non appartengano a quote - layerList = [] - for layer in self.plugIn.canvas.layers(): - if layer.type() == QgsMapLayer.VectorLayer and \ - (layer.geometryType() == QGis.Line or layer.geometryType() == QGis.Polygon) and \ - layer.isEditable(): - if len(QadDimStyles.getDimListByLayer(layer)) == 0: - layerList.append(layer) - - self.layersToCheck = layerList - self.setSnapType(QadSnapTypeEnum.DISABLE) - # non si richiede niente - elif self.mode == Qad_pedit_maptool_ModeEnum.NONE: - self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) - self.setDrawMode(QadGetPointDrawModeEnum.NONE) - # si richiede il primo punto per calcolo distanza di approssimazione - # si richiede la posizione più vicina ad un vertice - elif self.mode == Qad_pedit_maptool_ModeEnum.ASK_FOR_FIRST_TOLERANCE_PT: - self.onlyEditableLayers = False - self.checkPointLayer = True - self.checkLineLayer = True - self.checkPolygonLayer = True - self.setSnapType() - self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) - self.setDrawMode(QadGetPointDrawModeEnum.NONE) - # noto il primo punto per calcolo distanza di approssimazione si richiede il secondo punto - elif self.mode == Qad_pedit_maptool_ModeEnum.FIRST_TOLERANCE_PT_KNOWN_ASK_FOR_SECOND_PT or \ - self.mode == Qad_pedit_maptool_ModeEnum.ASK_FOR_NEW_VERTEX or \ - self.mode == Qad_pedit_maptool_ModeEnum.ASK_FOR_MOVE_VERTEX: - self.onlyEditableLayers = False - self.checkPointLayer = True - self.checkLineLayer = True - self.checkPolygonLayer = True - self.setSnapType() - self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) - self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.setStartPoint(self.firstPt) - # si richiede la posizione più vicina ad un vertice - elif self.mode == Qad_pedit_maptool_ModeEnum.ASK_FOR_VERTEX: - self.setSnapType(QadSnapTypeEnum.DISABLE) - self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) - self.setDrawMode(QadGetPointDrawModeEnum.NONE) - # si richede il punto base (grip mode) - elif self.mode == Qad_pedit_maptool_ModeEnum.ASK_FOR_BASE_PT: - self.setSnapType() - self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) - self.setDrawMode(QadGetPointDrawModeEnum.NONE) - - -#=============================================================================== -# Qad_gripLineToArcConvert_maptool_ModeEnum class. -#=============================================================================== -class Qad_gripLineToArcConvert_maptool_ModeEnum(): - # noti il punto iniziale e finale dell'arco si richiede il punto intermedio - START_END_PT_KNOWN_ASK_FOR_SECOND_PT = 1 - # non si richiede niente - NONE = 2 - - -#=============================================================================== -# Qad_gripLineToArcConvert_maptool class -#=============================================================================== -class Qad_gripLineToArcConvert_maptool(QadGetPoint): - - def __init__(self, plugIn): - QadGetPoint.__init__(self, plugIn) - - self.firstPt = None - self.mode = None - - self.layer = None - self.linearObjectList = qad_utils.QadLinearObjectList() - self.linearObject = None - self.startPt = None - self.endPt = None - self.tolerance2ApproxCurve = None - self.__highlight = QadHighlight(self.canvas) - - - def hidePointMapToolMarkers(self): - QadGetPoint.hidePointMapToolMarkers(self) - self.__highlight.hide() - - def showPointMapToolMarkers(self): - QadGetPoint.showPointMapToolMarkers(self) - self.__highlight.show() - - def clear(self): - QadGetPoint.clear(self) - self.__highlight.reset() - self.mode = None - - def setLinearObjectList(self, linearObjectList, layer, partAt): - self.linearObjectList.set(linearObjectList) - self.layer = layer - self.tolerance2ApproxCurve = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) - self.linearObject = self.linearObjectList.getLinearObjectAt(partAt) - self.firstPt = self.linearObject.getMiddlePt() - self.startPt = self.linearObject.getStartPt() - self.endPt = self.linearObject.getEndPt() - - - def canvasMoveEvent(self, event): - QadGetPoint.canvasMoveEvent(self, event) - - self.__highlight.reset() - ok = False - - # noti il punto iniziale e finale dell'arco si richiede il punto intermedio - if self.mode == Qad_gripLineToArcConvert_maptool_ModeEnum.START_END_PT_KNOWN_ASK_FOR_SECOND_PT: - if self.linearObject is None: - return - arc = QadArc() - if arc.fromStartSecondEndPts(self.startPt, self.tmpPoint, self.endPt) == False: - return - if qad_utils.ptNear(self.startPt, arc.getStartPt()): - self.linearObject.setArc(arc, False) # arco non inverso - else: - self.linearObject.setArc(arc, True) # arco inverso - ok = True - - if ok: - pts = self.linearObjectList.asPolyline(self.tolerance2ApproxCurve) - if self.layer.geometryType() == QGis.Polygon: - geom = QgsGeometry.fromPolygon([pts]) - else: - geom = QgsGeometry.fromPolyline(pts) - # trasformo la geometria nel crs del layer - self.__highlight.addGeometry(self.mapToLayerCoordinates(self.layer, geom), self.layer) - - - def activate(self): - QadGetPoint.activate(self) - self.__highlight.show() - - def deactivate(self): - try: # necessario perché se si chiude QGIS parte questo evento nonostante non ci sia più l'oggetto maptool ! - QadGetPoint.deactivate(self) - self.__highlight.hide() - except: - pass - - def setMode(self, mode): - self.mode = mode - - # noti il punto iniziale e finale dell'arco si richiede il punto intermedio - if self.mode == Qad_gripLineToArcConvert_maptool_ModeEnum.START_END_PT_KNOWN_ASK_FOR_SECOND_PT: - self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) - self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.setStartPoint(self.firstPt) - # non si richiede niente - elif self.mode == Qad_pedit_maptool_ModeEnum.NONE: - self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) - self.setDrawMode(QadGetPointDrawModeEnum.NONE) diff --git a/qad_pline_cmd.py b/qad_pline_cmd.py deleted file mode 100644 index 352c5a65..00000000 --- a/qad_pline_cmd.py +++ /dev/null @@ -1,1457 +0,0 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - comando PLINE per disegnare una polilinea - - ------------------- - begin : 2013-05-22 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -from qad_entsel_cmd import QadEntSelClass -from qad_getpoint import * -from qad_arc_maptool import * -from qad_arc import * -from qad_generic_cmd import QadCommandClass -from qad_msg import QadMsg -from qad_textwindow import * -import qad_utils -import qad_layer -from qad_rubberband import createRubberBand - - -# Classe che gestisce il comando PLINE -class QadPLINECommandClass(QadCommandClass): - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadPLINECommandClass(self.plugIn) - - def getName(self): - return QadMsg.translate("Command_list", "PLINE") - - def getEnglishName(self): - return "PLINE" - - def connectQAction(self, action): - QObject.connect(action, SIGNAL("triggered()"), self.plugIn.runPLINECommand) - - def getIcon(self): - return QIcon(":/plugins/qad/icons/pline.png") - - def getNote(self): - # impostare le note esplicative del comando - return QadMsg.translate("Command_PLINE", "Creates a polyline by many methods.\n\nA polyline is a single object that is composed of line,\nand arc segments.") - - def __init__(self, plugIn, asToolForMPolygon = False): - QadCommandClass.__init__(self, plugIn) - self.vertices = [] - self.realVerticesIndexes = [] # posizioni in vertices dei vertici reali - # (l'arco è approssimato a tanti segmenti) - self.firstVertex = True - - self.asToolForMPolygon = asToolForMPolygon - if self.asToolForMPolygon: - self.rubberBand = createRubberBand(self.plugIn.canvas, QGis.Polygon, True) - else: - self.rubberBand = createRubberBand(self.plugIn.canvas, QGis.Line) - - self.ArcPointMapTool = None - self.mode = "LINE" - self.EntSelClass = None - # se questo flag = True il comando serve all'interno di un altro comando per disegnare una linea - # che non verrà salvata su un layer - self.virtualCmd = False - - - def __del__(self): - QadCommandClass.__del__(self) - self.rubberBand.hide() - self.plugIn.canvas.scene().removeItem(self.rubberBand) - - def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): - if self.step == 3: # quando si é in fase di selezione entità - return self.EntSelClass.getPointMapTool(drawMode) - else: - if self.mode == "LINE": - return QadCommandClass.getPointMapTool(self, drawMode) - elif self.mode == "ARC": - return self.getArcPointMapTool() - - - def setRubberBandColor(self, rubberBandBorderColor, rubberBandFillColor): - if rubberBandBorderColor is not None: - self.rubberBand.setBorderColor(rubberBandBorderColor) - if rubberBandFillColor is not None: - self.rubberBand.setFillColor(rubberBandFillColor) - - - def waitForEntsel(self, msgMapTool, msg): - if self.EntSelClass is not None: - del self.EntSelClass - self.EntSelClass = QadEntSelClass(self.plugIn) - self.EntSelClass.msg = QadMsg.translate("Command_PLINE", "Select the object in the trace end point: ") - # scarto la selezione di punti - self.EntSelClass.checkPointLayer = False - self.EntSelClass.checkLineLayer = True - self.EntSelClass.checkPolygonLayer = True - self.EntSelClass.onlyEditableLayers = False - - self.EntSelClass.getPointMapTool().setSnapType(QadSnapTypeEnum.END) - self.EntSelClass.run(msgMapTool, msg) - - def getArcPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): - if (self.plugIn is not None): - if self.ArcPointMapTool is None: - self.ArcPointMapTool = Qad_arc_maptool(self.plugIn) - return self.ArcPointMapTool - else: - return None - - def addRealVertex(self, point): - self.realVerticesIndexes.append(len(self.vertices)) - self.vertices.append(point) - self.addPointToRubberBand(point) - self.plugIn.setLastPoint(self.vertices[-1]) - self.plugIn.setLastSegmentAng(self.getLastSegmentAng()) - - def delLastRealVertex(self): - tot = len(self.realVerticesIndexes) - if tot > 0: - i = self.realVerticesIndexes[tot - 1] - if tot > 1: - iEnd = self.realVerticesIndexes[tot - 2] - else: - iEnd = -1 - - del self.realVerticesIndexes[-1] # cancello ultimo vertice reale - while i > iEnd: - del self.vertices[-1] # cancello ultimo vertice - self.removeLastPointToRubberBand() - i = i - 1 - - self.plugIn.setLastPoint(self.vertices[-1]) - self.plugIn.setLastSegmentAng(self.getLastSegmentAng()) - - def addArcVertices(self, points, inverse): - tot = len(points) - if inverse == False: - i = 1 # salto il primo punto dell'arco che coincide con l'ultimo punto precedente - while i < tot: - self.vertices.append(points[i]) - self.addPointToRubberBand(points[i]) - i = i + 1 - else: - i = tot - 2 # salto l'ultimo punto dell'arco che coincide con l'ultimo punto precedente - while i >= 0: - self.vertices.append(points[i]) - self.addPointToRubberBand(points[i]) - i = i - 1 - - self.realVerticesIndexes.append(len(self.vertices) - 1) - self.plugIn.setLastPoint(self.vertices[-1]) - self.plugIn.setLastSegmentAng(self.getLastSegmentAng()) - - - def getLastSegmentAng(self): - if len(self.vertices) < 2: - result = self.plugIn.lastSegmentAng - else: - result = None - lastVertex = self.vertices[-1] # ultimo vertice - # verifico se ci sono archi - arc = None - arcList = QadArcList() - if arcList.fromPoints(self.vertices) > 0: - info = arcList.arcAt(len(self.vertices) - 1) - if info is not None: - arc = info[0] - # se i punti sono così vicini da essere considerati uguali - if qad_utils.ptNear(lastVertex, arc.getStartPt()): - result = arc.getTanDirectionOnStartPt() + math.pi - else: - result = arc.getTanDirectionOnEndPt() - - if result is None: - secondLastVertex = self.vertices[-2] # penultimo vertice - result = qad_utils.getAngleBy2Pts(secondLastVertex, lastVertex) - - return result - - #============================================================================ - # WaitForArcMenu - #============================================================================ - def WaitForArcMenu(self): - # l'opzione CEnter viene tradotta in italiano in "CEntro" nel contesto "WaitForArcMenu" - # l'opzione Undo viene tradotta in italiano in "ANNulla" nel contesto "WaitForArcMenu" - keyWords = QadMsg.translate("Command_PLINE", "Angle") + "/" + \ - QadMsg.translate("Command_PLINE", "CEnter", "WaitForArcMenu") + "/" + \ - QadMsg.translate("Command_PLINE", "Close") + "/" + \ - QadMsg.translate("Command_PLINE", "Direction") + "/" + \ - QadMsg.translate("Command_PLINE", "Line") + "/" + \ - QadMsg.translate("Command_PLINE", "Radius") + "/" + \ - QadMsg.translate("Command_PLINE", "Second point") + "/" + \ - QadMsg.translate("Command_PLINE", "Undo", "WaitForArcMenu") - englishKeyWords = "Angle" + "/" + "CEnter" + "/" + "Close" + "/" + \ - "Direction" + "/" + "Line" + "/" + "Radius" + "/" + \ - "Second point" + "/" + "Undo" - - prompt = QadMsg.translate("Command_PLINE", "Specify the final point of the arc or [{0}]: ").format(keyWords) - - self.arcStartPt = self.vertices[-1] # ultimo vertice - self.arcTanOnStartPt = self.getLastSegmentAng() - - # Il segmento di arco é tangente al precedente segmento della polilinea - # uso il map tool per l'arco - self.mode = "ARC" - self.getPointMapTool().arcStartPt = self.arcStartPt - self.getPointMapTool().arcTanOnStartPt = self.arcTanOnStartPt - self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_PT_TAN_KNOWN_ASK_FOR_END_PT) - - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o enter o una parola chiave - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - None, \ - keyWords, QadInputModeEnum.NONE) - self.step = 101 - return - - #============================================================================ - # WaitForLineMenu - #============================================================================ - def WaitForLineMenu(self): - # l'opzione Undo viene tradotta in italiano in "ANnulla" nel contesto "WaitForLineMenu" - if self.firstVertex: - keyWords = QadMsg.translate("Command_PLINE", "Arc") + "/" + \ - QadMsg.translate("Command_PLINE", "Length") + "/" + \ - QadMsg.translate("Command_PLINE", "Undo", "WaitForLineMenu") + "/" + \ - QadMsg.translate("Command_PLINE", "Trace") - englishKeyWords = "Arc" + "/" + "Length"+ "/" + "Undo" + "/" + "Trace" - else: - keyWords = QadMsg.translate("Command_PLINE", "Arc") + "/" + \ - QadMsg.translate("Command_PLINE", "Close") + "/" + \ - QadMsg.translate("Command_PLINE", "Length") + "/" + \ - QadMsg.translate("Command_PLINE", "Undo", "WaitForLineMenu") + "/" + \ - QadMsg.translate("Command_PLINE", "Trace") - englishKeyWords = "Arc" + "/" + "Close" + "/" + "Length" + "/" + "Undo"+ "/" + "Trace" - prompt = QadMsg.translate("Command_PLINE", "Specify next point or [{0}]: ").format(keyWords) - - self.step = 1 # MENU PRINCIPLE - - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o enter o una parola chiave - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - None, \ - keyWords, QadInputModeEnum.NONE) - - #============================================================================ - # addPointToRubberBand - #============================================================================ - def addPointToRubberBand(self, point, doUpdate = True): - numberOfVertices = self.rubberBand.numberOfVertices() - - if numberOfVertices == 2: - # per un baco non ancora capito: se la linea ha solo 2 vertici e - # hanno la stessa x o y (linea orizzontale o verticale) - # la linea non viene disegnata perciò sposto un pochino la x o la y - adjustedPoint = qad_utils.getAdjustedRubberBandVertex(self.rubberBand.getPoint(0, 0), point) - self.rubberBand.addPoint(adjustedPoint, doUpdate) - else: - self.rubberBand.addPoint(point, doUpdate) - - - #============================================================================ - # removeLastPointToRubberBand - #============================================================================ - def removeLastPointToRubberBand(self): - self.rubberBand.removeLastPoint() - - - def run(self, msgMapTool = False, msg = None): - if self.plugIn.canvas.mapRenderer().destinationCrs().geographicFlag(): - self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) - return True # fine comando - - if self.virtualCmd == False: # se si vuole veramente salvare la polylinea in un layer - currLayer, errMsg = qad_layer.getCurrLayerEditable(self.plugIn.canvas, QGis.Line) - if currLayer is None: - self.showErr(errMsg) - return True # fine comando - - # RICHIESTA PRIMO PUNTO - if self.step == 0: # inizio del comando - # imposto la linea elastica - self.getPointMapTool(QadGetPointDrawModeEnum.ELASTIC_LINE) - # si appresta ad attendere un punto o enter - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(QadMsg.translate("Command_PLINE", "Specify start point: "), \ - QadInputTypeEnum.POINT2D, None, "", QadInputModeEnum.NONE) - self.step = 1 - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA PUNTO OPPURE MENU PRINCIPALE - elif self.step == 1: # dopo aver atteso un punto si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - if self.virtualCmd == False: # se si vuole veramente salvare la polylinea in un layer - qad_layer.addLineToLayer(self.plugIn, currLayer, self.vertices) - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if value is None: - if self.firstVertex: - if self.plugIn.lastPoint is not None: - value = self.plugIn.lastPoint - else: - return True # fine comando - else: - if self.virtualCmd == False: # se si vuole veramente salvare la polylinea in un layer - qad_layer.addLineToLayer(self.plugIn, currLayer, self.vertices) - return True # fine comando - - - if type(value) == unicode: - if value == QadMsg.translate("Command_PLINE", "Arc") or value == "Arc": - self.WaitForArcMenu() - return False - elif value == QadMsg.translate("Command_PLINE", "Length") or value == "Length": - # si appresta ad attendere un punto o un numero reale - # msg, inputType, default, keyWords, valori positivi - # "Specificare lunghezza della linea: " - self.waitFor(QadMsg.translate("Command_PLINE", "Specify line length: "), \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT, \ - None, "", \ - QadInputModeEnum.NOT_NULL | QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) - self.step = 2 - return False - # l'opzione Undo viene tradotta in italiano in "ANnulla" nel contesto "WaitForLineMenu" - elif value == QadMsg.translate("Command_PLINE", "Undo", "WaitForLineMenu") or value == "Undo": - if len(self.vertices) >= 2: - self.delLastRealVertex() # cancello ultimo vertice reale - self.getPointMapTool().clear() - self.getPointMapTool().setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.getPointMapTool().setTmpGeometry(QgsGeometry.fromPolyline(self.vertices)) # per lo snap aggiungo questa geometria temporanea - self.getPointMapTool().setStartPoint(self.vertices[-1]) - elif value == QadMsg.translate("Command_PLINE", "Close") or value == "Close": - newPt = self.vertices[0] - self.addRealVertex(newPt) # aggiungo un nuovo vertice reale - if self.virtualCmd == False: # se si vuole veramente salvare la polylinea in un layer - qad_layer.addLineToLayer(self.plugIn, currLayer, self.vertices) - return True # fine comando - elif value == QadMsg.translate("Command_PLINE", "Trace") or value == "Trace": - self.step = 3 - self.waitForEntsel(msgMapTool, msg) - return False # continua - - elif type(value) == QgsPoint: - self.addRealVertex(value) # aggiungo un nuovo vertice reale - self.getPointMapTool().setTmpGeometry(QgsGeometry.fromPolyline(self.vertices)) # per lo snap aggiungo questa geometria temporanea - self.getPointMapTool().setStartPoint(value) - - self.WaitForLineMenu() - if self.firstVertex: - self.firstVertex = False - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA "Lunghezza" (da step = 1) - elif self.step == 2: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == QgsPoint: - dist = qad_utils.getDistance(self.vertices[-1], value) - else: - dist = value - - newPt = qad_utils.getPolarPointByPtAngle(self.vertices[-1], self.getLastSegmentAng(), dist) - self.addRealVertex(newPt) # aggiungo un nuovo vertice reale - - self.getPointMapTool().setTmpGeometry(QgsGeometry.fromPolyline(self.vertices)) # per lo snap aggiungo questa geometria temporanea - self.getPointMapTool().setStartPoint(newPt) - - self.WaitForLineMenu() - - self.step = 1 # torno al MENU PRINCIPLE - - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA "Selezionare l'oggetto nel punto finale di ricalco: " (da step = 1) - elif self.step == 3: - entSelected = False - if self.EntSelClass.run(msgMapTool, msg) == True: - if self.EntSelClass.entity.isInitialized() and self.EntSelClass.point is not None: - entSelected = True - layer = self.EntSelClass.entity.layer - geom = self.EntSelClass.entity.getGeometry() - ptEnd = qad_utils.closestVertexPtWithContext(self.mapToLayerCoordinates(layer, self.EntSelClass.point), \ - geom) - transformedPt1 = self.mapToLayerCoordinates(layer, self.vertices[-1]) - # leggo la parte di linea tra transformedPt1 e transformedPt2 - points = qad_utils.getLinePart(geom, transformedPt1, ptEnd) - if points is not None: - mapRenderer = self.EntSelClass.getPointMapTool().canvas.mapRenderer() - # converto i punti della linea in map coordinates - transformedPoints = [] - for point in points: - transformedPoints.append(mapRenderer.layerToMapCoordinates(layer, point)) - - self.addArcVertices(transformedPoints, False) # aggiungo i punti in ordine - - del self.EntSelClass - self.EntSelClass = None - - self.WaitForLineMenu() - if entSelected: - self.getPointMapTool().refreshSnapType() # aggiorno lo snapType che può essere variato dal maptool di selezione entità - self.getPointMapTool().setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.getPointMapTool().setTmpGeometry(QgsGeometry.fromPolyline(self.vertices)) # per lo snap aggiungo questa geometria temporanea - self.getPointMapTool().setStartPoint(self.vertices[-1]) - - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA "Specificare punto finale dell'arco o [Angolo/CEntro/CHiudi/Direzione/LInea/Raggio/Secondo punto/ANNulla]: " (da step = 1) - elif self.step == 101: # dopo aver atteso un punto o una parola chiave si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - if self.virtualCmd == False: # se si vuole veramente salvare la polylinea in un layer - qad_layer.addLineToLayer(self.plugIn, currLayer, self.vertices) - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if value is None: - if self.virtualCmd == False: # se si vuole veramente salvare la polylinea in un layer - qad_layer.addLineToLayer(self.plugIn, currLayer, self.vertices) - return True # fine comando - - if type(value) == unicode: - if value == QadMsg.translate("Command_PLINE", "Angle") or value == "Angle": - self.arcStartPt = self.vertices[-1] - - # imposto il map tool - self.getPointMapTool().arcStartPt = self.arcStartPt - self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_PT_KNOWN_ASK_FOR_ANGLE) - # si appresta ad attendere un punto o un numero reale - # msg, inputType, default, keyWords, isNullable - self.waitFor(QadMsg.translate("Command_PLINE", "Specify the included angle: "), \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.ANGLE, \ - None, "", \ - QadInputModeEnum.NOT_NULL | QadInputModeEnum.NOT_ZERO) - self.step = 102 - # l'opzione CEnter viene tradotta in italiano in "CEntro" nel contesto "WaitForArcMenu" - elif value == QadMsg.translate("Command_PLINE", "CEnter", "WaitForArcMenu") or value == "CEnter": - self.arcStartPt = self.vertices[-1] - - # imposto il map tool - self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_CENTER_PT) - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_PLINE", "Specify the center of the arc: ")) - self.step = 108 - elif value == QadMsg.translate("Command_PLINE", "Close") or value == "Close": - arc = QadArc() - - if arc.fromStartEndPtsTan(self.arcStartPt, self.vertices[0], self.arcTanOnStartPt) == True: - points = arc.asPolyline() - if points is not None: - # se i punti sono così vicini da essere considerati uguali - if qad_utils.ptNear(self.arcStartPt, arc.getStartPt()): - self.addArcVertices(points, False) # aggiungo i punti in ordine - else: - self.addArcVertices(points, True) # aggiungo i punti in ordine inverso - if self.virtualCmd == False: # se si vuole veramente salvare la polylinea in un layer - qad_layer.addLineToLayer(self.plugIn, currLayer, self.vertices) - - return True # fine comando - elif value == QadMsg.translate("Command_PLINE", "Direction") or value == "Direction": - self.arcStartPt = self.vertices[-1] - - # imposto il map tool - self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_PT_KNOWN_ASK_FOR_SECOND_PT) - # si appresta ad attendere un punto o un numero reale - # msg, inputType, default, keyWords, isNullable - self.waitFor(QadMsg.translate("Command_PLINE", "Specify the tangent direction for the start point of the arc: "), \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.ANGLE, \ - None, "", QadInputModeEnum.NOT_NULL) - self.step = 112 - elif value == QadMsg.translate("Command_PLINE", "Line") or value == "Line": - self.mode = "LINE" - self.getPointMapTool().refreshSnapType() # riagggiorno lo snapType che può essere variato dal maptool dell'arco - self.getPointMapTool().setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.getPointMapTool().setStartPoint(self.vertices[-1]) - self.WaitForLineMenu() - elif value == QadMsg.translate("Command_PLINE", "Radius") or value == "Radius": - self.arcStartPt = self.vertices[-1] - - # imposto il map tool - self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_PT_ANGLE_KNOWN_ASK_FOR_RADIUS) - # si appresta ad attendere un punto o un numero reale - # msg, inputType, default, keyWords, valori positivi - self.waitFor(QadMsg.translate("Command_PLINE", "Specify the radius of the arc: "), \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT, \ - None, "", \ - QadInputModeEnum.NOT_NULL | QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) - self.step = 114 - elif value == QadMsg.translate("Command_PLINE", "Second point") or value == "Second point": - self.arcStartPt = self.vertices[-1] - - # imposto il map tool - self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_PT_KNOWN_ASK_FOR_SECOND_PT) - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_PLINE", "Specify second point of the arc: ")) - self.step = 119 - # l'opzione Undo viene tradotta in italiano in "ANNulla" nel contesto "WaitForArcMenu" - elif value == QadMsg.translate("Command_PLINE", "Undo", "WaitForArcMenu") or value == "Undo": - if len(self.vertices) >= 2: - self.delLastRealVertex() # cancello ultimo vertice reale - self.getPointMapTool().clear() - self.getPointMapTool().setTmpGeometry(QgsGeometry.fromPolyline(self.vertices)) # per lo snap aggiungo questa geometria temporanea - self.getPointMapTool().setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.WaitForArcMenu() - elif type(value) == QgsPoint: # é stato inserito il punto finale dell'arco - arc = QadArc() - if arc.fromStartEndPtsTan(self.arcStartPt, value, self.arcTanOnStartPt) == True: - points = arc.asPolyline() - if points is not None: - # se i punti sono così vicini da essere considerati uguali - if qad_utils.ptNear(self.arcStartPt, arc.getStartPt()): - self.addArcVertices(points, False) # aggiungo i punti in ordine - else: - self.addArcVertices(points, True) # aggiungo i punti in ordine inverso - self.getPointMapTool().setTmpGeometry(QgsGeometry.fromPolyline(self.vertices)) # per lo snap aggiungo questa geometria temporanea - - self.WaitForArcMenu() - - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA "Specificare angolo inscritto: " (da step = 101) - elif self.step == 102: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == QgsPoint: - self.arcAngle = qad_utils.getAngleBy2Pts(self.arcStartPt, value) - else: - self.arcAngle = value - - # imposto il map tool - self.getPointMapTool().arcAngle = self.arcAngle - self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_PT_ANGLE_KNOWN_ASK_FOR_END_PT) - - # l'opzione CEnter viene tradotta in italiano in "Centro" nel contesto "START_PT_ANGLE_KNOWN_ASK_FOR_END_PT" - keyWords = QadMsg.translate("Command_PLINE", "CEnter", "START_PT_ANGLE_KNOWN_ASK_FOR_END_PT") + "/" + \ - QadMsg.translate("Command_PLINE", "Radius") - englishKeyWords = "CEnter" + "/" + "Radius" - prompt = QadMsg.translate("Command_PLINE", "Specify the final point of the arc or [{0}]: ").format(keyWords) - - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o una parola chiave - # msg, inputType, default, keyWords, isNullable - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - None, \ - keyWords, QadInputModeEnum.NOT_NULL) - self.step = 103 - - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA "Specificare punto finale dell'arco o [Centro/Raggio]: : " (da step = 102) - elif self.step == 103: # dopo aver atteso un punto o una parola chiave si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == unicode: - # l'opzione CEnter viene tradotta in italiano in "Centro" nel contesto "START_PT_ANGLE_KNOWN_ASK_FOR_END_PT" - if value == QadMsg.translate("Command_PLINE", "CEnter", "START_PT_ANGLE_KNOWN_ASK_FOR_END_PT") or value == "CEnter": - # imposto il map tool - self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_PT_ANGLE_KNOWN_ASK_FOR_CENTER_PT) - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_PLINE", "Specify the center of the arc: ")) - self.step = 104 - elif value == QadMsg.translate("Command_PLINE", "Radius") or value == "Radius": - # imposto il map tool - self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_PT_ANGLE_KNOWN_ASK_FOR_RADIUS) - # si appresta ad attendere un punto o un numero reale - # msg, inputType, default, keyWords, valori positivi - self.waitFor(QadMsg.translate("Command_PLINE", "Specify the radius of the arc: "), \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT, \ - None, "", \ - QadInputModeEnum.NOT_NULL | QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) - self.step = 105 - elif type(value) == QgsPoint: # é stato inserito il punto finale dell'arco - arc = QadArc() - if arc.fromStartEndPtsAngle(self.arcStartPt, value, self.arcAngle) == True: - points = arc.asPolyline() - if points is not None: - # se i punti sono così vicini da essere considerati uguali - if qad_utils.ptNear(self.arcStartPt, arc.getStartPt()): - self.addArcVertices(points, False) # aggiungo i punti in ordine - else: - self.addArcVertices(points, True) # aggiungo i punti in ordine inverso - self.getPointMapTool().setTmpGeometry(QgsGeometry.fromPolyline(self.vertices)) # per lo snap aggiungo questa geometria temporanea - - self.WaitForArcMenu() - return False - - # l'opzione CEnter viene tradotta in italiano in "Centro" nel contesto "START_PT_ANGLE_KNOWN_ASK_FOR_END_PT" - keyWords = QadMsg.translate("Command_PLINE", "CEnter", "START_PT_ANGLE_KNOWN_ASK_FOR_END_PT") + "/" + \ - QadMsg.translate("Command_PLINE", "Radius") - prompt = QadMsg.translate("Command_PLINE", "Specify the final point of the arc or [{0}]: ").format(keyWords) - - englishKeyWords = "CEnter" + "/" + "Radius" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o una parola chiave - # msg, inputType, default, keyWords, isNullable - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - None, \ - keyWords, QadInputModeEnum.NOT_NULL) - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA CENTRO DELL'ARCO (da step = 103) - elif self.step == 104: # dopo aver atteso un punto si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - arc = QadArc() - if arc.fromStartCenterPtsAngle(self.arcStartPt, value, self.arcAngle) == True: - points = arc.asPolyline() - if points is not None: - # se i punti sono così vicini da essere considerati uguali - if qad_utils.ptNear(self.arcStartPt, arc.getStartPt()): - self.addArcVertices(points, False) # aggiungo i punti in ordine - else: - self.addArcVertices(points, True) # aggiungo i punti in ordine inverso - self.getPointMapTool().setTmpGeometry(QgsGeometry.fromPolyline(self.vertices)) # per lo snap aggiungo questa geometria temporanea - - self.WaitForArcMenu() - return False - - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_PLINE", "Specify the center of the arc: ")) - - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA RAGGIO (da step = 103) - elif self.step == 105: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == QgsPoint: - self.arcStartPtForRadius = value - - # imposto il map tool - self.getPointMapTool().arcStartPtForRadius = self.arcStartPtForRadius - self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_PT_ANGLE_KNOWN_ASK_FOR_SECONDPTRADIUS) - - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_PLINE", "Specify second point: ")) - self.step = 106 - else: - self.arcRadius = value - self.plugIn.setLastRadius(self.arcRadius) - - # imposto il map tool - self.getPointMapTool().arcRadius = self.arcRadius - self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_PT_ANGLE_RADIUS_KNOWN_ASK_FOR_CHORDDIRECTION) - # si appresta ad attendere un punto o un numero reale - # msg, inputType, default, keyWords, isNullable - msg = QadMsg.translate("Command_PLINE", "Specify the direction for the chord of the arc <{0}>: ") - self.waitFor(msg.format(str(self.getLastSegmentAng())), \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.ANGLE, \ - None, "", QadInputModeEnum.NOT_NULL) - self.step = 107 - - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA SECONDO PUNTO DEL RAGGIO (da step = 105) - elif self.step == 106: # dopo aver atteso un punto si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - self.arcRadius = qad_utils.getDistance(self.arcStartPtForRadius, value) - self.plugIn.setLastRadius(self.arcRadius) - - # imposto il map tool - self.getPointMapTool().arcRadius = self.arcRadius - self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_PT_ANGLE_RADIUS_KNOWN_ASK_FOR_CHORDDIRECTION) - # si appresta ad attendere un punto o un numero reale - # msg, inputType, default, keyWords, isNullable - msg = QadMsg.translate("Command_PLINE", "Specify the direction for the chord of the arc <{0}>: ") - self.waitFor(msg.format(str(self.getLastSegmentAng())), \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.ANGLE, \ - None, "", QadInputModeEnum.NOT_NULL) - self.step = 107 - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DIREZIONE DELLA CORDA DELL'ARCO (da step = 106 e 107) - elif self.step == 107: # dopo aver atteso un punto si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == QgsPoint: - self.arcChordDirection = qad_utils.getAngleBy2Pts(self.arcStartPt, value) - else: - self.arcChordDirection = value - - arc = QadArc() - if arc.fromStartPtAngleRadiusChordDirection(self.arcStartPt, self.arcAngle, \ - self.arcRadius, self.arcChordDirection) == True: - points = arc.asPolyline() - if points is not None: - # se i punti sono così vicini da essere considerati uguali - if qad_utils.ptNear(self.arcStartPt, arc.getStartPt()): - self.addArcVertices(points, False) # aggiungo i punti in ordine - else: - self.addArcVertices(points, True) # aggiungo i punti in ordine inverso - self.getPointMapTool().setTmpGeometry(QgsGeometry.fromPolyline(self.vertices)) # per lo snap aggiungo questa geometria temporanea - - self.WaitForArcMenu() - return False - - # imposto il map tool - self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_PT_ANGLE_RADIUS_KNOWN_ASK_FOR_CHORDDIRECTION) - # si appresta ad attendere un punto o un numero reale - # msg, inputType, default, keyWords, isNullable - msg = QadMsg.translate("Command_PLINE", "Specify the direction for the chord of the arc <{0}>: ") - self.waitFor(msg.format(str(self.getLastSegmentAng())), \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.ANGLE, \ - None, "", QadInputModeEnum.NOT_NULL) - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA CENTRO DELL'ARCO (da step = 101) - elif self.step == 108: # dopo aver atteso un punto si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - self.arcCenterPt = value - - # imposto il map tool - self.getPointMapTool().arcCenterPt = self.arcCenterPt - self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_CENTER_PT_KNOWN_ASK_FOR_END_PT) - - keyWords = QadMsg.translate("Command_PLINE", "Angle") + "/" + \ - QadMsg.translate("Command_PLINE", "chord Length") - prompt = QadMsg.translate("Command_PLINE", "Specify the final point of the arc or [{0}]: ").format(keyWords) - - englishKeyWords = "Angle" + "/" + "chord Length" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o una parola chiave - # msg, inputType, default, keyWords, isNullable - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - None, \ - keyWords, QadInputModeEnum.NOT_NULL) - self.step = 109 - - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA "Specificare punto finale dell'arco o [Angolo/Lunghezza corda]: " (da step = 108) - elif self.step == 109: # dopo aver atteso un punto o una parola chiave si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("Command_PLINE", "Angle") or value == "Angle": - # imposto il map tool - self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_CENTER_PT_KNOWN_ASK_FOR_ANGLE) - # si appresta ad attendere un punto o un numero reale - # msg, inputType, default, keyWords, valori <> 0 - self.waitFor(QadMsg.translate("Command_PLINE", "Specify the included angle: "), \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.ANGLE, \ - None, "", \ - QadInputModeEnum.NOT_NULL | QadInputModeEnum.NOT_ZERO) - self.step = 110 - return False - elif value == QadMsg.translate("Command_PLINE", "chord Length") or value == "chord Length": - # imposto il map tool - self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_CENTER_PT_KNOWN_ASK_FOR_CHORD) - # si appresta ad attendere un punto o un numero reale - # msg, inputType, default, keyWords, valori positivi - self.waitFor(QadMsg.translate("Command_PLINE", "Specify the chord length: "), \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT, \ - None, "", \ - QadInputModeEnum.NOT_NULL | QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) - self.step = 111 - return False - elif type(value) == QgsPoint: # se é stato inserito il punto finale dell'arco - self.arcEndPt = value - - arc = QadArc() - if arc.fromStartCenterEndPts(self.arcStartPt, self.arcCenterPt, self.arcEndPt) == True: - points = arc.asPolyline() - if points is not None: - # se i punti sono così vicini da essere considerati uguali - if qad_utils.ptNear(self.arcStartPt, arc.getStartPt()): - self.addArcVertices(points, False) # aggiungo i punti in ordine - else: - self.addArcVertices(points, True) # aggiungo i punti in ordine inverso - self.getPointMapTool().setTmpGeometry(QgsGeometry.fromPolyline(self.vertices)) # per lo snap aggiungo questa geometria temporanea - - self.WaitForArcMenu() - return False - - keyWords = QadMsg.translate("Command_PLINE", "Angle") + "/" + \ - QadMsg.translate("Command_PLINE", "chord Length") - prompt = QadMsg.translate("Command_PLINE", "Specify the final point of the arc or [{0}]: ").format(keyWords) - - englishKeyWords = "Angle" + "/" + "chord Length" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o una parola chiave - # msg, inputType, default, keyWords, isNullable - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - None, \ - keyWords, QadInputModeEnum.NOT_NULL) - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA "Specificare angolo inscritto: " (da step = 109) - elif self.step == 110: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == QgsPoint: - self.arcAngle = qad_utils.getAngleBy2Pts(self.arcCenterPt, value) - else: - self.arcAngle = value - - arc = QadArc() - if arc.fromStartCenterPtsAngle(self.arcStartPt, self.arcCenterPt, self.arcAngle) == True: - points = arc.asPolyline() - if points is not None: - # se i punti sono così vicini da essere considerati uguali - if qad_utils.ptNear(self.arcStartPt, arc.getStartPt()): - self.addArcVertices(points, False) # aggiungo i punti in ordine - else: - self.addArcVertices(points, True) # aggiungo i punti in ordine inverso - self.getPointMapTool().setTmpGeometry(QgsGeometry.fromPolyline(self.vertices)) # per lo snap aggiungo questa geometria temporanea - - self.WaitForArcMenu() - return False - - # si appresta ad attendere un punto o un numero reale - # msg, inputType, default, keyWords, isNullable - self.waitFor(QadMsg.translate("Command_PLINE", "Specify the included angle: "), \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.ANGLE, \ - None, "", \ - QadInputModeEnum.NOT_NULL | QadInputModeEnum.NOT_ZERO) - - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA "Specificare lunghezza della corda: " (da step = 109) - elif self.step == 111: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == QgsPoint: - self.arcChord = qad_utils.getDistance(self.arcStartPt, value) - else: - self.arcChord = value - - arc = QadArc() - if arc.fromStartCenterPtsChord(self.arcStartPt, self.arcCenterPt, self.arcChord) == True: - points = arc.asPolyline() - if points is not None: - # se i punti sono così vicini da essere considerati uguali - if qad_utils.ptNear(self.arcStartPt, arc.getStartPt()): - self.addArcVertices(points, False) # aggiungo i punti in ordine - else: - self.addArcVertices(points, True) # aggiungo i punti in ordine inverso - self.getPointMapTool().setTmpGeometry(QgsGeometry.fromPolyline(self.vertices)) # per lo snap aggiungo questa geometria temporanea - - self.WaitForArcMenu() - return False - - # si appresta ad attendere un punto o un numero reale - # msg, inputType, default, keyWords, valori positivi - self.waitFor(QadMsg.translate("Command_PLINE", "Specify the chord length: "), \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.FLOAT, \ - None, "", \ - QadInputModeEnum.NOT_NULL | QadInputModeEnum.NOT_ZERO | QadInputModeEnum.NOT_NEGATIVE) - - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA "Specificare direzione tangente per il punto iniziale dell'arco: " (da step = 101) - elif self.step == 112: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == QgsPoint: - self.arcTanOnStartPt = qad_utils.getAngleBy2Pts(self.arcStartPt, value) - else: - self.arcTanOnStartPt = value - - # imposto il map tool - self.getPointMapTool().arcTanOnStartPt = self.arcTanOnStartPt - self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_PT_TAN_KNOWN_ASK_FOR_END_PT) - - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_PLINE", "Specify the final point of the arc: ")) - self.step = 113 - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA PUNTO FINALE DELL'ARCO (da step = 112) - elif self.step == 113: # dopo aver atteso un punto si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - arc = QadArc() - if arc.fromStartEndPtsTan(self.arcStartPt, value, self.arcTanOnStartPt) == True: - points = arc.asPolyline() - if points is not None: - # se i punti sono così vicini da essere considerati uguali - if qad_utils.ptNear(self.arcStartPt, arc.getStartPt()): - self.addArcVertices(points, False) # aggiungo i punti in ordine - else: - self.addArcVertices(points, True) # aggiungo i punti in ordine inverso - self.getPointMapTool().setTmpGeometry(QgsGeometry.fromPolyline(self.vertices)) # per lo snap aggiungo questa geometria temporanea - - self.WaitForArcMenu() - return False - - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_PLINE", "Specify the final point of the arc: ")) - - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA RAGGIO (da step = 101) - elif self.step == 114: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == QgsPoint: - self.arcStartPtForRadius = value - - # imposto il map tool - self.getPointMapTool().arcStartPtForRadius = self.arcStartPtForRadius - self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_PT_ANGLE_KNOWN_ASK_FOR_SECONDPTRADIUS) - - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_PLINE", "Specify second point: ")) - self.step = 115 - else: - self.arcRadius = value - self.plugIn.setLastRadius(self.arcRadius) - - # imposto il map tool - self.getPointMapTool().arcRadius = self.arcRadius - self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_PT_RADIUS_KNOWN_ASK_FOR_END_PT) - - keyWords = QadMsg.translate("Command_PLINE", "Angle") - prompt = QadMsg.translate("Command_PLINE", "Specify the final point of the arc or [{0}]: ").format(keyWords) - englishKeyWords = "Angle" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o un numero reale - # msg, inputType, default, keyWords, isNullable - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - None, keyWords, QadInputModeEnum.NOT_NULL) - self.step = 116 - - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA SECONDO PUNTO DEL RAGGIO (da step = 114) - elif self.step == 115: # dopo aver atteso un punto si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - self.arcRadius = qad_utils.getDistance(self.arcStartPtForRadius, value) - self.plugIn.setLastRadius(self.arcRadius) - - # imposto il map tool - self.getPointMapTool().arcRadius = self.arcRadius - self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_PT_RADIUS_KNOWN_ASK_FOR_END_PT) - - keyWords = QadMsg.translate("Command_PLINE", "Angle") - prompt = QadMsg.translate("Command_PLINE", "Specify the final point of the arc or [{0}]: ").format(keyWords) - englishKeyWords = "Angle" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o un numero reale - # msg, inputType, default, keyWords, isNullable - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - None, keyWords, QadInputModeEnum.NOT_NULL) - self.step = 116 - - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA "Specificare punto finale dell'arco o [Angolo]: " (da step = 114 o 115) - elif self.step == 116: # dopo aver atteso un punto o una parola chiave si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("Command_PLINE", "Angle") or value == "Angle": - # imposto il map tool - self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_PT_KNOWN_ASK_FOR_ANGLE) - # si appresta ad attendere un punto o un numero reale - # msg, inputType, default, keyWords, isNullable - self.waitFor(QadMsg.translate("Command_PLINE", "Specify the included angle: "), \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.ANGLE, \ - None, "", \ - QadInputModeEnum.NOT_NULL | QadInputModeEnum.NOT_ZERO) - self.step = 117 - elif type(value) == QgsPoint: # é stato inserito il punto finale dell'arco - arc = QadArc() - if arc.fromStartEndPtsRadius(self.arcStartPt, value, self.arcRadius) == True: - points = arc.asPolyline() - if points is not None: - # se i punti sono così vicini da essere considerati uguali - if qad_utils.ptNear(self.arcStartPt, arc.getStartPt()): - self.addArcVertices(points, False) # aggiungo i punti in ordine - else: - self.addArcVertices(points, True) # aggiungo i punti in ordine inverso - self.getPointMapTool().setTmpGeometry(QgsGeometry.fromPolyline(self.vertices)) # per lo snap aggiungo questa geometria temporanea - - self.WaitForArcMenu() - - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA "Specificare angolo inscritto: " (da step = 116) - elif self.step == 117: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == QgsPoint: - self.arcAngle = qad_utils.getAngleBy2Pts(self.arcStartPt, value) - else: - self.arcAngle = value - - # imposto il map tool - self.getPointMapTool().arcAngle = self.arcAngle - self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_PT_ANGLE_RADIUS_KNOWN_ASK_FOR_CHORDDIRECTION) - # si appresta ad attendere un punto o un numero reale - # msg, inputType, default, keyWords, isNullable - msg = QadMsg.translate("Command_PLINE", "Specify the direction for the chord of the arc <{0}>: ") - self.waitFor(msg.format(str(self.getLastSegmentAng())), \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.ANGLE, \ - None, "", QadInputModeEnum.NOT_NULL) - self.step = 118 - - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DIREZIONE DELLA CORDA DELL'ARCO (da step = 117) - elif self.step == 118: # dopo aver atteso un punto si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == QgsPoint: - self.arcChordDirection = qad_utils.getAngleBy2Pts(self.arcStartPt, value) - else: - self.arcChordDirection = value - - arc = QadArc() - if arc.fromStartPtAngleRadiusChordDirection(self.arcStartPt, self.arcAngle, \ - self.arcRadius, self.arcChordDirection) == True: - points = arc.asPolyline() - if points is not None: - # se i punti sono così vicini da essere considerati uguali - if qad_utils.ptNear(self.arcStartPt, arc.getStartPt()): - self.addArcVertices(points, False) # aggiungo i punti in ordine - else: - self.addArcVertices(points, True) # aggiungo i punti in ordine inverso - self.getPointMapTool().setTmpGeometry(QgsGeometry.fromPolyline(self.vertices)) # per lo snap aggiungo questa geometria temporanea - - self.WaitForArcMenu() - return False - - # imposto il map tool - self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_PT_ANGLE_RADIUS_KNOWN_ASK_FOR_CHORDDIRECTION) - # si appresta ad attendere un punto o un numero reale - # msg, inputType, default, keyWords, isNullable - msg = QadMsg.translate("Command_PLINE", "Specify the direction for the chord of the arc <{0}>: ") - self.waitFor(msg.format(str(self.getLastSegmentAng())), \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.ANGLE, \ - None, "", QadInputModeEnum.NOT_NULL) - - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA SECONDO PUNTO (da step = 101) - elif self.step == 119: # dopo aver atteso un punto o una parola chiave si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - self.arcSecondPt = value - # imposto il map tool - self.getPointMapTool().arcSecondPt = self.arcSecondPt - self.getPointMapTool().setMode(Qad_arc_maptool_ModeEnum.START_SECOND_PT_KNOWN_ASK_FOR_END_PT) - - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_PLINE", "Specify the final point of the arc: ")) - self.step = 120 - - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA PUNTO FINALE DELL'ARCO (da step = 119) - elif self.step == 120: # dopo aver atteso un punto si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - self.arcEndPt = value - - arc = QadArc() - if arc.fromStartSecondEndPts(self.arcStartPt, self.arcSecondPt, self.arcEndPt) == True: - points = arc.asPolyline() - if points is not None: - # se i punti sono così vicini da essere considerati uguali - if qad_utils.ptNear(self.arcStartPt, arc.getStartPt()): - self.addArcVertices(points, False) # aggiungo i punti in ordine - else: - self.addArcVertices(points, True) # aggiungo i punti in ordine inverso - self.getPointMapTool().setTmpGeometry(QgsGeometry.fromPolyline(self.vertices)) # per lo snap aggiungo questa geometria temporanea - - self.WaitForArcMenu() - return False - - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_PLINE", "Specify the final point of the arc: ")) - return False \ No newline at end of file diff --git a/qad_point.py b/qad_point.py new file mode 100644 index 00000000..ee1bb3cf --- /dev/null +++ b/qad_point.py @@ -0,0 +1,162 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + classe per la gestione dei punti + + ------------------- + begin : 2018-12-27 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from qgis.PyQt.QtCore import * +from qgis.PyQt.QtGui import * +from qgis.core import * +from qgis.gui import * +import math + + +from . import qad_utils + + +# =============================================================================== +# QadPoint point class derivato da QgsPointXY +# =============================================================================== +class QadPoint(QgsPointXY): + + def __init__(self, point = None): + QgsPointXY.__init__(self) + if point is not None: + self.set(point) + + + def whatIs(self): + # obbligatoria + return "POINT" + + + # ============================================================================ + # isClosed + # ============================================================================ + def isClosed(self): + return False + + + def set(self, point): + QgsPointXY.set(self, point.x(), point.y()) + return self + + + def transform(self, coordTransform): + # obbligatoria + """Transform this geometry as described by CoordinateTransform ct.""" + self.set(coordTransform.transform(self)) + + + def transformFromCRSToCRS(self, sourceCRS, destCRS): + # obbligatoria + """Transform this geometry as described by CRS.""" + if (sourceCRS is not None) and (destCRS is not None) and sourceCRS != destCRS: + coordTransform = QgsCoordinateTransform(sourceCRS, destCRS, QgsProject.instance()) # trasformo le coord + self.transform(coordTransform) + + + def __eq__(self, point): + # obbligatoria + """self == other""" + return qad_utils.ptNear(self, point) + + + def __ne__(self, point): + """self != other""" + return not qad_utils.ptNear(self, point) + + + # =============================================================================== + # getBoundingBox + # =============================================================================== + def getBoundingBox(self): + """ + la funzione ritorna il rettangolo che racchiude il punto. + """ + return QgsRectangle(self.x(), self.y(), self.x(), self.y()) + + + def equals(self, pt): + # uguali geometricamente + return self.__eq__(pt) + + + def copy(self): + # obbligatoria + return QadPoint(self) + + + # =============================================================================== + # asGeom + # =============================================================================== + def asGeom(self, wkbType = QgsWkbTypes.LineString, tolerance2ApproxCurve = None, atLeastNSegment = None): + """ + la funzione ritorna il punto in forma di QgsGeometry. + wkbType, tolerance2ApproxCurve e atLeastNSegment sono dichiarati solo per compatibilità + """ + flatType = QgsWkbTypes.flatType(wkbType) + + if flatType == QgsWkbTypes.MultiPoint: + multiPoint = QgsMultiPoint() + multiPoint.addGeometry(QgsPoint(self)) + return QgsGeometry(multiPoint) + + return QgsGeometry.fromPointXY(self) + + + # =============================================================================== + # fromGeom + # =============================================================================== + def fromGeom(self, geom): + """ + la funzione ritorna il punto in forma di QgsGeometry. + """ + return self.set(geom.asPoint()) + + + # ============================================================================ + # move + # ============================================================================ + def move(self, offsetX, offsetY): + return self.set(qad_utils.movePoint(self, offsetX, offsetY)) + + + # ============================================================================ + # rotate + # ============================================================================ + def rotate(self, basePt, angle): + self.set(qad_utils.rotatePoint(self, basePt, angle)) + + + # ============================================================================ + # scale + # ============================================================================ + def scale(self, basePt, scale): + return self.set(qad_utils.scalePoint(self, basePt, scale)) + + + # ============================================================================ + # mirror + # ============================================================================ + def mirror(self, mirrorPt, mirrorAngle): + return self.set(qad_utils.mirrorPoint(self, mirrorPt, mirrorAngle)) diff --git a/qad_pointerinput_settings.ui b/qad_pointerinput_settings.ui new file mode 100644 index 00000000..b2e7c948 --- /dev/null +++ b/qad_pointerinput_settings.ui @@ -0,0 +1,300 @@ + + + PointerInput_Settings_Dialog + + + + 0 + 0 + 300 + 356 + + + + + 300 + 356 + + + + + 300 + 356 + + + + Pointer Input Settings + + + + + 10 + 10 + 281 + 181 + + + + Format + + + + + 10 + 20 + 231 + 41 + + + + For second or next points, default to: + + + Qt::AutoText + + + true + + + + + + 10 + 60 + 261 + 51 + + + + + + + + + 10 + 10 + 241 + 17 + + + + Displays the tooltip for the second or next point in polar coordinate format. Enter a comma (,) to change to Cartesian format. (DYNPIFORMAT system variable) + + + Polar format + + + + + + 10 + 30 + 241 + 17 + + + + Displays the tooltip for the second or next point in Cartesian coordinate format. Enter an angle symbol (<) to change to polar format. (DYNPIFORMAT system variable) + + + Cartesian format + + + + + + + 10 + 120 + 261 + 51 + + + + + + + + + 10 + 10 + 241 + 17 + + + + Displays the tooltip for the second or next point in relative coordinate format. Enter a pound sign (#) to change to absolute format. (DYNPICOORDS system variable) + + + Relative coordinates + + + + + + 10 + 30 + 241 + 17 + + + + Displays the tooltip for the second or next point in absolute coordinate format. Enter an "at" sign (@) to change to relative coordinates format. (DYNPICOORDS system variable) + + + Absolute coordinates + + + + + + + + 10 + 210 + 281 + 101 + + + + Visibility + + + + + 10 + 80 + 261 + 17 + + + + Always displays tooltips when pointer input is turned on. ( DYNPIVIS system variable) + + + Always - even when not in command + + + + + + 10 + 60 + 261 + 17 + + + + When pointer input is turned on, displays tooltips whenever a command prompts you for a point. (DYNPIVIS system variable) + + + When a command asks for a point + + + + + + 10 + 20 + 261 + 31 + + + + Show coordinate tooltips: + + + true + + + + + + + 10 + 320 + 281 + 30 + + + + + + + OK + + + + + + + Cancel + + + + + + + ? + + + + + + + + + + okButton + clicked() + PointerInput_Settings_Dialog + ButtonBOX_Accepted() + + + 47 + 307 + + + 7 + 290 + + + + + helpButton + clicked() + PointerInput_Settings_Dialog + ButtonHELP_Pressed() + + + 213 + 313 + + + 265 + 288 + + + + + cancelButton + clicked() + PointerInput_Settings_Dialog + reject() + + + 132 + 311 + + + 145 + 287 + + + + + + ButtonBOX_Accepted() + ButtonHELP_Pressed() + + diff --git a/qad_pointerinput_settings_ui.py b/qad_pointerinput_settings_ui.py new file mode 100644 index 00000000..3a184f22 --- /dev/null +++ b/qad_pointerinput_settings_ui.py @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file '.\qad_pointerinput_settings.ui' +# +# Created by: PyQt5 UI code generator 5.15.10 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_PointerInput_Settings_Dialog(object): + def setupUi(self, PointerInput_Settings_Dialog): + PointerInput_Settings_Dialog.setObjectName("PointerInput_Settings_Dialog") + PointerInput_Settings_Dialog.resize(300, 356) + PointerInput_Settings_Dialog.setMinimumSize(QtCore.QSize(300, 356)) + PointerInput_Settings_Dialog.setMaximumSize(QtCore.QSize(300, 356)) + self.groupBox = QtWidgets.QGroupBox(PointerInput_Settings_Dialog) + self.groupBox.setGeometry(QtCore.QRect(10, 10, 281, 181)) + self.groupBox.setObjectName("groupBox") + self.label = QtWidgets.QLabel(self.groupBox) + self.label.setGeometry(QtCore.QRect(10, 20, 231, 41)) + self.label.setTextFormat(QtCore.Qt.AutoText) + self.label.setWordWrap(True) + self.label.setObjectName("label") + self.groupBox_3 = QtWidgets.QGroupBox(self.groupBox) + self.groupBox_3.setGeometry(QtCore.QRect(10, 60, 261, 51)) + self.groupBox_3.setTitle("") + self.groupBox_3.setObjectName("groupBox_3") + self.radioPolarFmt = QtWidgets.QRadioButton(self.groupBox_3) + self.radioPolarFmt.setGeometry(QtCore.QRect(10, 10, 241, 17)) + self.radioPolarFmt.setObjectName("radioPolarFmt") + self.radioCartesianFmt = QtWidgets.QRadioButton(self.groupBox_3) + self.radioCartesianFmt.setGeometry(QtCore.QRect(10, 30, 241, 17)) + self.radioCartesianFmt.setObjectName("radioCartesianFmt") + self.groupBox_4 = QtWidgets.QGroupBox(self.groupBox) + self.groupBox_4.setGeometry(QtCore.QRect(10, 120, 261, 51)) + self.groupBox_4.setTitle("") + self.groupBox_4.setObjectName("groupBox_4") + self.radioRelativeCoord = QtWidgets.QRadioButton(self.groupBox_4) + self.radioRelativeCoord.setGeometry(QtCore.QRect(10, 10, 241, 17)) + self.radioRelativeCoord.setObjectName("radioRelativeCoord") + self.radioAbsoluteCoord = QtWidgets.QRadioButton(self.groupBox_4) + self.radioAbsoluteCoord.setGeometry(QtCore.QRect(10, 30, 241, 17)) + self.radioAbsoluteCoord.setObjectName("radioAbsoluteCoord") + self.groupBox_2 = QtWidgets.QGroupBox(PointerInput_Settings_Dialog) + self.groupBox_2.setGeometry(QtCore.QRect(10, 210, 281, 101)) + self.groupBox_2.setObjectName("groupBox_2") + self.radioVisAlways = QtWidgets.QRadioButton(self.groupBox_2) + self.radioVisAlways.setGeometry(QtCore.QRect(10, 80, 261, 17)) + self.radioVisAlways.setObjectName("radioVisAlways") + self.radioVisWhenAsksPt = QtWidgets.QRadioButton(self.groupBox_2) + self.radioVisWhenAsksPt.setGeometry(QtCore.QRect(10, 60, 261, 17)) + self.radioVisWhenAsksPt.setObjectName("radioVisWhenAsksPt") + self.label_2 = QtWidgets.QLabel(self.groupBox_2) + self.label_2.setGeometry(QtCore.QRect(10, 20, 261, 31)) + self.label_2.setWordWrap(True) + self.label_2.setObjectName("label_2") + self.layoutWidget = QtWidgets.QWidget(PointerInput_Settings_Dialog) + self.layoutWidget.setGeometry(QtCore.QRect(10, 320, 281, 30)) + self.layoutWidget.setObjectName("layoutWidget") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.layoutWidget) + self.horizontalLayout.setContentsMargins(0, 0, 0, 0) + self.horizontalLayout.setObjectName("horizontalLayout") + self.okButton = QtWidgets.QPushButton(self.layoutWidget) + self.okButton.setObjectName("okButton") + self.horizontalLayout.addWidget(self.okButton) + self.cancelButton = QtWidgets.QPushButton(self.layoutWidget) + self.cancelButton.setObjectName("cancelButton") + self.horizontalLayout.addWidget(self.cancelButton) + self.helpButton = QtWidgets.QPushButton(self.layoutWidget) + self.helpButton.setObjectName("helpButton") + self.horizontalLayout.addWidget(self.helpButton) + + self.retranslateUi(PointerInput_Settings_Dialog) + self.okButton.clicked.connect(PointerInput_Settings_Dialog.ButtonBOX_Accepted) # type: ignore + self.helpButton.clicked.connect(PointerInput_Settings_Dialog.ButtonHELP_Pressed) # type: ignore + self.cancelButton.clicked.connect(PointerInput_Settings_Dialog.reject) # type: ignore + QtCore.QMetaObject.connectSlotsByName(PointerInput_Settings_Dialog) + + def retranslateUi(self, PointerInput_Settings_Dialog): + _translate = QtCore.QCoreApplication.translate + PointerInput_Settings_Dialog.setWindowTitle(_translate("PointerInput_Settings_Dialog", "Pointer Input Settings")) + self.groupBox.setTitle(_translate("PointerInput_Settings_Dialog", "Format")) + self.label.setText(_translate("PointerInput_Settings_Dialog", "For second or next points, default to:")) + self.radioPolarFmt.setToolTip(_translate("PointerInput_Settings_Dialog", "Displays the tooltip for the second or next point in polar coordinate format. Enter a comma (,) to change to Cartesian format. (DYNPIFORMAT system variable)")) + self.radioPolarFmt.setText(_translate("PointerInput_Settings_Dialog", "Polar format")) + self.radioCartesianFmt.setToolTip(_translate("PointerInput_Settings_Dialog", "Displays the tooltip for the second or next point in Cartesian coordinate format. Enter an angle symbol (<) to change to polar format. (DYNPIFORMAT system variable)")) + self.radioCartesianFmt.setText(_translate("PointerInput_Settings_Dialog", "Cartesian format")) + self.radioRelativeCoord.setToolTip(_translate("PointerInput_Settings_Dialog", "Displays the tooltip for the second or next point in relative coordinate format. Enter a pound sign (#) to change to absolute format. (DYNPICOORDS system variable)")) + self.radioRelativeCoord.setText(_translate("PointerInput_Settings_Dialog", "Relative coordinates")) + self.radioAbsoluteCoord.setToolTip(_translate("PointerInput_Settings_Dialog", "Displays the tooltip for the second or next point in absolute coordinate format. Enter an \"at\" sign (@) to change to relative coordinates format. (DYNPICOORDS system variable)")) + self.radioAbsoluteCoord.setText(_translate("PointerInput_Settings_Dialog", "Absolute coordinates")) + self.groupBox_2.setTitle(_translate("PointerInput_Settings_Dialog", "Visibility")) + self.radioVisAlways.setToolTip(_translate("PointerInput_Settings_Dialog", "Always displays tooltips when pointer input is turned on. ( DYNPIVIS system variable)")) + self.radioVisAlways.setText(_translate("PointerInput_Settings_Dialog", "Always - even when not in command")) + self.radioVisWhenAsksPt.setToolTip(_translate("PointerInput_Settings_Dialog", "When pointer input is turned on, displays tooltips whenever a command prompts you for a point. (DYNPIVIS system variable)")) + self.radioVisWhenAsksPt.setText(_translate("PointerInput_Settings_Dialog", "When a command asks for a point")) + self.label_2.setText(_translate("PointerInput_Settings_Dialog", "Show coordinate tooltips:")) + self.okButton.setText(_translate("PointerInput_Settings_Dialog", "OK")) + self.cancelButton.setText(_translate("PointerInput_Settings_Dialog", "Cancel")) + self.helpButton.setText(_translate("PointerInput_Settings_Dialog", "?")) diff --git a/qad_polygon.py b/qad_polygon.py new file mode 100644 index 00000000..17b692c0 --- /dev/null +++ b/qad_polygon.py @@ -0,0 +1,378 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + classe per la gestione dei poligoni (lista di polilinee) + + ------------------- + begin : 2019-03-14 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from qgis.PyQt.QtCore import * +from qgis.PyQt.QtGui import * +from qgis.core import * +from qgis.gui import * + + +from . import qad_utils +from .qad_circle import QadCircle +from .qad_ellipse import QadEllipse +from .qad_polyline import QadPolyline + + +# =============================================================================== +# QadPolygon class +# rappresenta una lista di geometrie chiuse: QadPolyline, QadCircle, QadEllipse +# =============================================================================== +class QadPolygon(): + + def __init__(self, polygon=None): + self.defList = [] + # deflist = ( ...) + if polygon is not None: + self.set(polygon) + + + # ============================================================================ + # whatIs + # ============================================================================ + def whatIs(self): + return "POLYGON" + + + # ============================================================================ + # isClosed + # ============================================================================ + def isClosed(self): + return True + + + # ============================================================================ + # set + # ============================================================================ + def set(self, polygon): + self.removeAll() + for closedObject in polygon.defList: + self.append(closedObject) + return self + + + def __eq__(self, polygon): + # obbligatoria + """self == other""" + if polygon.whatIs() != "POLYLINE": return False + if self.qty() != polygon.qty(): return False + for i in range(0, self.qty()): + if self.getClosedObjectAt(i) != polygon.getClosedObjectAt(i): return False + return True + + + def __ne__(self, polygon): + """self != other""" + return not self.__eq__(polygon) + + + # ============================================================================ + # append + # ============================================================================ + def append(self, closedObject): + """ + la funzione aggiunge una geometria chiusa in fondo alla lista. + """ + if closedObject is None: return + objectType = closedObject.whatIs() + if objectType == "POLYLINE": + if closedObject.isClosed() == False: return False + elif objectType != "CIRCLE" and objectType != "ELLIPSE": + return False + self.defList.append(closedObject.copy()) + return True + + + # ============================================================================ + # insert + # ============================================================================ + def insert(self, i, closedObject): + """ + la funzione aggiunge una geometria chiusa nella posizione i-esima della lista delle geometrie chiuse. + """ + if i >= self.qty(): + return self.append(closedObject) + else: + return self.defList.insert(i, closedObject.copy()) + + + # ============================================================================ + # remove + # ============================================================================ + def remove(self, i): + """ + la funzione cancella una geometria chiusa nella posizione i-esima della lista. + """ + del self.defList[i] + + + # ============================================================================ + # removeAll + # ============================================================================ + def removeAll(self): + """ + la funzione cancella le geometrie chiuse della lista. + """ + del self.defList[:] + + + # ============================================================================ + # getClosedObjectAt + # ============================================================================ + def getClosedObjectAt(self, i): + """ + la funzione restituisce la geometria chiusa alla posizione i-esima + con numeri negativi parte dal fondo (es. -1 = ultima posizione) + """ + if self.qty() == 0 or i > self.qty() - 1: + return None + return self.defList[i] + + + # ============================================================================ + # fromPolygon + # ============================================================================ + def fromPolygon(self, lineList): + """ + la funzione inizializza una lista di geometrie chiuse che compone il poligono passato in liste di punti. + """ + self.removeAll() + ellipse = QadEllipse() + circle = QadCircle() + polyline = QadPolyline() + + for points in lineList: + # verifico se è un cerchio + if circle.fromPolyline(points): + self.append(circle) + else: + # verifico se è una ellisse + if ellipse.fromPolyline(points): + self.append(ellipse) + else: + # verifico se è una polilinea + if polyline.fromPolyline(points): + if polyline.isClosed(): + self.append(polyline) + + if self.qty() == 0: return None + return True + + + # ============================================================================ + # fromGeom + # ============================================================================ + def fromGeom(self, geom): + """ + la funzione inizializza il poligono da un oggetto QgsGeometry. + """ + return self.fromPolygon(geom.asPolygon()) + + + # =============================================================================== + # asPolygon + # =============================================================================== + def asPolygon(self, tolerance2ApproxCurve = None, atLeastNSegment = None): + """ + la funzione ritorna una lista di liste di punti che compongono un poligono. + """ + result = [] + for closedObject in self.defList: + result.append(closedObject.asPolyline(tolerance2ApproxCurve, atLeastNSegment)) + + return result + + + # =============================================================================== + # asAbstractGeom + # =============================================================================== + def asAbstractGeom(self, wkbType = QgsWkbTypes.LineString, tolerance2ApproxCurve = None, atLeastNSegment = None): + """ + la funzione ritorna il poligono in forma di QgsAbstractGeometry. + """ + flatType = QgsWkbTypes.flatType(wkbType) + + if flatType == QgsWkbTypes.CurvePolygon: + curvePolygon = QgsCurvePolygon() + exteriorRing = True + for closedObject in self.defList: + if exteriorRing == True: + curvePolygon.setExteriorRing(closedObject.asAbstractGeom(QgsWkbTypes.CompoundCurve , tolerance2ApproxCurve, atLeastNSegment)) + exteriorRing = False + else: + curvePolygon.addInteriorRing(closedObject.asAbstractGeom(QgsWkbTypes.CompoundCurve , tolerance2ApproxCurve, atLeastNSegment)) + return curvePolygon + + elif flatType == QgsWkbTypes.MultiSurface: # Geometry that is combined from several Curvepolygons is called MultiSurface + curvedPolygon = self.asAbstractGeom(QgsWkbTypes.CurvePolygon, tolerance2ApproxCurve, atLeastNSegment) + multiSurface = QgsMultiSurface() + multiSurface.addGeometry(curvedPolygon) + return multiSurface + + elif flatType == QgsWkbTypes.MultiPolygon: + polygon = self.asAbstractGeom(QgsWkbTypes.Polygon, tolerance2ApproxCurve, atLeastNSegment) + multiPolygon = QgsMultiPolygon() + multiPolygon.addGeometry(polygon) + return multiPolygon + + polygon = QgsPolygon() + exteriorRing = True + for closedObject in self.defList: + if exteriorRing == True: + polygon.setExteriorRing(closedObject.asAbstractGeom(QgsWkbTypes.LineString , tolerance2ApproxCurve, atLeastNSegment)) + exteriorRing = False + else: + polygon.addInteriorRing(closedObject.asAbstractGeom(QgsWkbTypes.LineString , tolerance2ApproxCurve, atLeastNSegment)) + return polygon + + + # =============================================================================== + # asGeom + # =============================================================================== + def asGeom(self, wkbType = QgsWkbTypes.LineString, tolerance2ApproxCurve = None, atLeastNSegment = None): + """ + la funzione ritorna il poligono in forma di QgsGeometry. + """ + return QgsGeometry(self.asAbstractGeom(wkbType, tolerance2ApproxCurve, atLeastNSegment)) + + + # =============================================================================== + # copy + # =============================================================================== + def copy(self): + # obbligatoria + return QadPolygon(self) + + + # ============================================================================ + # move + # ============================================================================ + def move(self, offsetX, offsetY): + """ + la funzione sposta le geometrie chiuse secondo un offset X e uno Y + """ + for closedObject in self.defList: + closedObject.move(offsetX, offsetY) + + + # ============================================================================ + # rotate + # ============================================================================ + def rotate(self, basePt, angle): + for closedObject in self.defList: + closedObject.rotate(basePt, angle) + + + # ============================================================================ + # scale + # ============================================================================ + def scale(self, basePt, scale): + for closedObject in self.defList: + closedObject.scale(basePt, scale) + + + # ============================================================================ + # mirror + # ============================================================================ + def mirror(self, mirrorPt, mirrorAngle): + for closedObject in self.defList: + closedObject.mirror(mirrorPt, mirrorAngle) + + + # ============================================================================ + # qty + # ============================================================================ + def qty(self): + """ + la funzione restituisce la quantità di geometrie chiuse che compongono il poligono. + """ + return len(self.defList) + + + # ============================================================================ + # getCentroid + # ============================================================================ + def getCentroid(self, tolerance2ApproxCurve = None): + """ + la funzione restituisce il punto centroide. + """ + g = self.asGeom(QgsWkbTypes.LineString, tolerance2ApproxCurve) + if g is not None: + centroid = g.centroid() + if centroid is not None: + return g.centroid().asPoint() + + return None + + + # =============================================================================== + # transform + # =============================================================================== + def transform(self, coordTransform): + """ + la funzione restituisce un nuovo poligono con le coordinate trasformate. + """ + result = QadPolygon() + for closedObject in self.defList: + result.append(closedObject.transform(coordTransform)) + return result + + + # =============================================================================== + # transformFromCRSToCRS + # =============================================================================== + def transformFromCRSToCRS(self, sourceCRS, destCRS): + """ + la funzione trasforma le coordinate dei punti che compone il poligono. + """ + return self.transform(QgsCoordinateTransform(sourceCRS, destCRS, QgsProject.instance())) + + + # =============================================================================== + # getBoundingBox + # =============================================================================== + def getBoundingBox(self): + """ + la funzione ritorna il rettangolo che racchiude il poligono. + """ + boundingBox = self.getClosedObjectAt(0).getBoundingBox() + i = 1 + while i < self.qty(): + boundingBox.combineExtentWith(self.getClosedObjectAt(i).getBoundingBox()) + i = i + 1 + + return boundingBox + + + # =============================================================================== + # containsPt + # =============================================================================== + def containsPt(self, pt): + """ + la funzione ritorna True se il punto è sul poligono altrimenti False. + """ + for closedObject in self.defList: + if closedObject.containsPt(pt): return True + + return False diff --git a/qad_polygon_maptool.py b/qad_polygon_maptool.py deleted file mode 100644 index 7da84b86..00000000 --- a/qad_polygon_maptool.py +++ /dev/null @@ -1,141 +0,0 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - classe per gestire il map tool in ambito del comando Ppolygon - - ------------------- - begin : 2014-11-17 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -from qgis.gui import * -import math - - -import qad_utils -from qad_snapper import * -from qad_snappointsdisplaymanager import * -from qad_variables import * -from qad_getpoint import * -from qad_rubberband import QadRubberBand - - -#=============================================================================== -# Qad_polygon_maptool_ModeEnum class. -#=============================================================================== -class Qad_polygon_maptool_ModeEnum(): - # si richiede il centro - ASK_FOR_CENTER_PT = 1 - # noto il centro si richiede il raggio - CENTER_PT_KNOWN_ASK_FOR_RADIUS = 2 - # si richiede il primo punto dello spigolo - ASK_FOR_FIRST_EDGE_PT = 3 - # si richiede il secondo punto dello spigolo - FIRST_EDGE_PT_KNOWN_ASK_FOR_SECOND_EDGE_PT = 4 - -#=============================================================================== -# Qad_polygon_maptool class -#=============================================================================== -class Qad_polygon_maptool(QadGetPoint): - - def __init__(self, plugIn): - QadGetPoint.__init__(self, plugIn) - self.mode = None - - self.sideNumber = None - self.centerPt = None - self.constructionModeByCenter = None - self.firstEdgePt = None - self.vertices = [] - - self.__rubberBand = QadRubberBand(self.canvas, True) - self.geomType = QGis.Polygon - - def hidePointMapToolMarkers(self): - QadGetPoint.hidePointMapToolMarkers(self) - self.__rubberBand.hide() - - def showPointMapToolMarkers(self): - QadGetPoint.showPointMapToolMarkers(self) - self.__rubberBand.show() - - def clear(self): - QadGetPoint.clear(self) - self.__rubberBand.reset() - self.mode = None - - def canvasMoveEvent(self, event): - QadGetPoint.canvasMoveEvent(self, event) - - self.__rubberBand.reset() - - result = False - del self.vertices[:] # svuoto la lista - - if self.mode is not None: - # noto il centro si richiede il raggio - if self.mode == Qad_polygon_maptool_ModeEnum.CENTER_PT_KNOWN_ASK_FOR_RADIUS: - radius = qad_utils.getDistance(self.centerPt, self.tmpPoint) - - InscribedOption = True if self.constructionModeByCenter == QadMsg.translate("Command_POLYGON", "Inscribed in circle") else False - self.vertices.extend(qad_utils.getPolygonByNsidesCenterRadius(self.sideNumber, self.centerPt, radius, \ - InscribedOption, self.tmpPoint)) - result = True - # si richiede il secondo punto dello spigolo - elif self.mode == Qad_polygon_maptool_ModeEnum.FIRST_EDGE_PT_KNOWN_ASK_FOR_SECOND_EDGE_PT: - self.vertices.extend(qad_utils.getPolygonByNsidesEdgePts(self.sideNumber, self.firstEdgePt, \ - self.tmpPoint)) - result = True - - if result == True: - if self.vertices is not None: - if self.geomType == QGis.Polygon: - self.__rubberBand.setPolygon(self.vertices) - else: - self.__rubberBand.setLine(self.vertices) - - def activate(self): - QadGetPoint.activate(self) - self.__rubberBand.show() - - def deactivate(self): - try: # necessario perché se si chiude QGIS parte questo evento nonostante non ci sia più l'oggetto maptool ! - QadGetPoint.deactivate(self) - self.__rubberBand.hide() - except: - pass - - def setMode(self, mode): - self.mode = mode - # si richiede il centro - if self.mode == Qad_polygon_maptool_ModeEnum.ASK_FOR_CENTER_PT: - self.setDrawMode(QadGetPointDrawModeEnum.NONE) - # noto il centro si richiede il raggio - if self.mode == Qad_polygon_maptool_ModeEnum.CENTER_PT_KNOWN_ASK_FOR_RADIUS: - self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.setStartPoint(self.centerPt) - # si richiede il primo punto dello spigolo - if self.mode == Qad_polygon_maptool_ModeEnum.ASK_FOR_FIRST_EDGE_PT: - self.setDrawMode(QadGetPointDrawModeEnum.NONE) - # si richiede il secondo punto dello spigolo - if self.mode == Qad_polygon_maptool_ModeEnum.FIRST_EDGE_PT_KNOWN_ASK_FOR_SECOND_EDGE_PT: - self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.setStartPoint(self.firstEdgePt) diff --git a/qad_polyline.py b/qad_polyline.py new file mode 100644 index 00000000..58d644e8 --- /dev/null +++ b/qad_polyline.py @@ -0,0 +1,1626 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + classe per la gestione delle polilinee (lista di linee, archi, archi di ellisse) ok + + ------------------- + begin : 2019-02-26 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from qgis.PyQt.QtCore import * +from qgis.PyQt.QtGui import * +from qgis.core import * +from qgis.gui import * +import qgis.utils + +import math + + +from . import qad_utils +from .qad_line import QadLine +from .qad_arc import QadArc +from .qad_ellipse_arc import QadEllipseArc +from .qad_layer import createMemoryLayer +from .qad_msg import QadMsg +from .qad_variables import QadVariables + + +# =============================================================================== +# QadPolyline class +# rappresenta una lista di oggetti lineari come: linee, archi, archi di ellisse +# =============================================================================== +class QadPolyline(): + + def __init__(self, polyline=None): + self.defList = [] + # deflist = (...) + if polyline is not None: + self.set(polyline) + + + # ============================================================================ + # whatIs + # ============================================================================ + def whatIs(self): + return "POLYLINE" + + + # ============================================================================ + # set + # ============================================================================ + def set(self, polyline): + self.removeAll() + if isinstance(polyline, QadPolyline) == True: # è una polilinea + for linearObject in polyline.defList: + self.append(linearObject) + elif isinstance(polyline, list) == True: # è una lista di parti di polilinea + for linearObject in polyline: + self.append(linearObject) + return self + + + def __eq__(self, polyline): + # obbligatoria + """self == other""" + if polyline.whatIs() != "POLYLINE": return False + if self.qty() != polyline.qty(): return False + for i in range(0, self.qty()): + if self.getLinearObjectAt(i) != polyline.getLinearObjectAt(i): return False + return True + + + def __ne__(self, polyline): + """self != other""" + return not self.__eq__(polyline) + + + def equals(self, polyline): + # uguali geometricamente (NON conta il verso) + if polyline.whatIs() != "POLYLINE": return False + if self.__eq__(polyline): return True + dummy = polyline.copy() + dummy.reverse() + return self.__eq__(dummy) + + # ============================================================================ + # append + # ============================================================================ + def append(self, linearObject): + """ + la funzione aggiunge un oggetto lineare in fondo alla lista. + Non viene fatto controllo di continuità geometrica. + """ + if linearObject is None: return + return self.defList.append(linearObject.copy()) + + + # ============================================================================ + # appendPolyline + # ============================================================================ + def appendPolyline(self, polyline, start = None, qty = None): + """ + la funzione aggiunge una polilinea in fondo alla lista. + Se diverso da None significa numero della parte di da cui iniziare. + Se diverso da None significa numero delle parti di da aggiungere, altrimenti significa fino alla fine di . + Non viene fatto controllo di continuità geometrica. + """ + if start is None: + for linearObject in polyline.defList: + self.append(linearObject) + else: + i = start + if qty is None: + tot = polyline.qty() + else: + tot = polyline.qty() if qty > polyline.qty() else qty + + while i < tot: + self.append(polyline.defList[i]) + i = i + 1 + + + # ============================================================================ + # insert + # ============================================================================ + def insert(self, partAt, linearObject): + """ + la funzione aggiunge un oggetto lineare nella posizione i-esima della lista degli oggetti lineari. + Non viene fatto controllo di continuità geometrica. + """ + if partAt >= self.qty(): + return self.append(linearObject) + else: + return self.defList.insert(partAt, linearObject.copy()) + + + # ============================================================================ + # insertPolyline + # ============================================================================ + def insertPolyline(self, i, polyline): + """ + la funzione aggiunge una polilinea nella posizione i-esima della lista. + """ + ndx = i + for linearObject in polyline.defList: + self.insert(ndx, linearObject) + ndx = ndx + 1 + + + # ============================================================================ + # insertPoint + # ============================================================================ + def insertPoint(self, partAt, pt): + """ + la funzione aggiunge un punto tra il punto iniziale e finale della parte i-esima della lista. + se i < 0 aggiunge il punto all'inizio della polilinea + se i >= qty() aggiunge il punto alla fine della polilinea + """ + if partAt < 0: # inserisco una linea all'inizio + line = QadLine() + line.set(pt, self.getStartPt()) + self.insert(0, line) + elif partAt >= self.qty(): # inserisco una linea in fondo + line = QadLine() + line.set(self.getEndPt(), pt) + self.append(line) + else: + linearObject = self.getLinearObjectAt(partAt) + + if linearObject.whatIs() == "LINE": + line = QadLine() + line.set(linearObject.getStartPt(), pt) + self.insert(partAt, line) + linearObject = self.getLinearObjectAt(partAt + 1) + linearObject.set(pt, linearObject.getEndPt()) + + elif linearObject.whatIs() == "ARC": + arc1 = QadArc() + arc2 = QadArc() + totalAngle = linearObject.totalAngle() + if linearObject.reversed: + if arc1.fromStartEndPtsAngle(pt, linearObject.getStartPt(), totalAngle) == False: + return + arc1.reversed = True + if linearObject.fromStartEndPtsAngle(linearObject.getEndPt(), pt, totalAngle) == False: + return + linearObject.reversed = True + else: + if arc1.fromStartEndPtsAngle(linearObject.getStartPt(), pt, totalAngle) == False: + return + if linearObject.fromStartEndPtsAngle(pt, linearObject.getEndPt(), totalAngle) == False: + return + + self.insert(partAt, arc1) + + elif linearObject.whatIs() == "ELLIPSE_ARC": + # da fare + pass + + + # ============================================================================ + # movePoint + # ============================================================================ + def movePoint(self, vertexAt, pt): + """ + la funzione sposta un punto tra il punto iniziale e finale della parte i-esima della lista. + """ + prevLinearObject, nextLinearObject = self.getPrevNextLinearObjectsAtVertex(vertexAt) + + if prevLinearObject is not None: + if prevLinearObject.whatIs() == "LINE": + prevLinearObject.setEndPt(pt) + + elif prevLinearObject.whatIs() == "ARC": + if prevLinearObject.reversed: + # sposto il punto iniziale dell'arco + if prevLinearObject.fromStartEndPtsAngle(pt, \ + prevLinearObject.getStartPt(), \ + prevLinearObject.totalAngle()) == False: + return False + prevLinearObject.reversed = True + else: + # sposto il punto finale dell'arco + if prevLinearObject.fromStartEndPtsAngle(prevLinearObject.getStartPt(), \ + pt, \ + prevLinearObject.totalAngle()) == False: + return False + + elif prevLinearObject.whatIs() == "ELLIPSE_ARC": + # da fare + pass + + if nextLinearObject is not None: + if nextLinearObject.whatIs() == "LINE": + nextLinearObject.setStartPt(pt) + + elif nextLinearObject.whatIs() == "ARC": + if nextLinearObject.reversed: + # sposto il punto finale dell'arco + if nextLinearObject.fromStartEndPtsAngle(nextLinearObject.getEndPt(), \ + pt, \ + nextLinearObject.totalAngle()) == False: + return False + nextLinearObject.reversed = True + else: + # sposto il punto iniziale dell'arco + if nextLinearObject.fromStartEndPtsAngle(pt, \ + nextLinearObject.getEndPt(), \ + nextLinearObject.totalAngle()) == False: + return False + + elif prevLinearObject.whatIs() == "ELLIPSE_ARC": + # da fare + pass + + return True + + + # ============================================================================ + # remove + # ============================================================================ + def remove(self, i): + """ + la funzione cancella un oggetto lineare nella posizione i-esima della lista. + """ + del self.defList[i] + + + # ============================================================================ + # removeAll + # ============================================================================ + def removeAll(self): + """ + la funzione cancella gli oggetti lineari della lista. + """ + del self.defList[:] + + + # ============================================================================ + # getLinearObjectAt + # ============================================================================ + def getLinearObjectAt(self, i): + """ + la funzione restituisce l'oggetto lineare alla posizione i-esima + con numeri negativi parte dal fondo (es. -1 = ultima posizione) + """ + if self.qty() == 0 or i > self.qty() - 1: + return None + return self.defList[i] + + + # ============================================================================ + # getVertexPosAtPt + # ============================================================================ + def getVertexPosAtPt(self, pt): + """ + la funzione restituisce la posizione del vertice con coordinate (0-based), + None se non trovato. + """ + vertexAt = 0 + for linearObject in self.defList: + if qad_utils.ptNear(linearObject.getStartPt(), pt): + return vertexAt + vertexAt = vertexAt + 1 + if self.isClosed() == False: # se non é chiusa verifico ultimo vertice dell'ultima parte + if qad_utils.ptNear(self.defList[-1].getEndPt(), pt): + return vertexAt + + return None + + + # ============================================================================ + # getPrevNextLinearObjectsAtVertex + # ============================================================================ + def getPrevNextLinearObjectsAtVertex(self, vertexAt): + """ + la funzione restituisce l'oggetto lineare precedente e successivo al vertice vertexAt-esimo + """ + prevLinearObject = None + nextLinearObject = None + + if vertexAt == 0: # primo vertice + nextLinearObject = self.getLinearObjectAt(0) + if self.isClosed(): + prevLinearObject = self.getLinearObjectAt(-1) + elif vertexAt == self.qty(): # ultimo vertice + prevLinearObject = self.getLinearObjectAt(-1) + if self.isClosed(): + nextLinearObject = self.getLinearObjectAt(0) + else: + nextLinearObject = self.getLinearObjectAt(vertexAt) + prevLinearObject = self.getLinearObjectAt(vertexAt - 1) + + return prevLinearObject, nextLinearObject + + + # ============================================================================ + # getPointAtVertex + # ============================================================================ + def getPointAtVertex(self, vertexAt): + """ + la funzione restituisce il punto del vertice vertexAt-esimo che compone la polilinea (0-based). + """ + if vertexAt == self.qty(): # ultimo vertice + return self.getLinearObjectAt(-1).getEndPt() + else: + return self.getLinearObjectAt(vertexAt).getStartPt() + + + # ============================================================================ + # getNextPos + # ============================================================================ + def getNextPos(self, i): + """ + la funzione restituisce la posizione dell'oggetto lineare successiva all'i-esimo (0-based) + tenendo conto che se la polilinea è chiusa dopo l'ultimo oggetto si torna all'inizio + N.B: non sono sicuro che debba essere ciclico... + """ + if i == self.qty() - 1 or i == -1: # sono alla fine + if self.isClosed(): # se é chiusa torno all'inizio + return 0 + else: + return None + else: + return i + 1 + + + # ============================================================================ + # getPrevPos + # ============================================================================ + def getPrevPos(self, i): + """ + la funzione restituisce la posizione della parte precedente all' i-esima (0-based) + tenendo conto che se la polilinea è chiusa prima del primo oggetto si va alla fine + N.B: non sono sicuro che debba essere ciclico... + """ + if i == 0: # sono all'inizio + if self.isClosed(): # se é chiusa torno alla fine + return self.qty() - 1 + else: + return None + else: + return i - 1 + + + # ============================================================================ + # fromPolyline + # ============================================================================ + def fromPolyline(self, points): + """ + la funzione inizializza una lista di linee, archi e archi di ellisse che compone la polilinea. + Se un oggetto lineare ha punto iniziale e finale coincidenti (es. 2 vertici consecutivi + che si sovrappongono o arco con angolo totale = 0 oppure = 360) + l'oggetto viene rimosso dalla lista. + """ + pointsLen = len(points) + + self.removeAll() + arc = QadArc() + ellipseArc = QadEllipseArc() + line = QadLine() + + i = 0 + while i < pointsLen - 1: # fino al penultimo punto + # verifico se è un arco + endVertex = arc.fromPolyline(points, i) + if endVertex is not None and qad_utils.ptNear(arc.getStartPt(), points[i]) == True: + self.append(arc) + i = endVertex + else: + # verifico se è un arco di ellisse + endVertex = ellipseArc.fromPolyline(points, i) + if endVertex is not None and qad_utils.ptNear(ellipseArc.getStartPt(), points[i]) == True: + self.append(ellipseArc) + i = endVertex + else: # allora è una linea + line.set(points[i], points[i + 1]) + self.append(line) + i = i + 1 + + if self.qty() == 0: return False + + return True + + + # ============================================================================ + # fromGeom + # ============================================================================ + def fromGeom(self, geom): + """ + la funzione inizializza la polilinea da un oggetto QgsGeometry. + """ + return self.fromPolyline(geom.asPolyline()) + + + # =============================================================================== + # asPolyline + # =============================================================================== + def asPolyline(self, tolerance2ApproxCurve = None, atLeastNSegment = None): + """ + la funzione ritorna una lista di punti che compone la polilinea formata da una lista di + oggetti lineari consecutivi. + """ + result = [] + firstPt = True + for linearObject in self.defList: + pts = linearObject.asPolyline(tolerance2ApproxCurve, atLeastNSegment) + ptsLen = len(pts) + if firstPt: + i = 0 + firstPt = False + else: + i = 1 + while i < ptsLen: + result.append(pts[i]) + i = i + 1 + + return result + + + # =============================================================================== + # asLineString + # =============================================================================== + def asLineString(self, tolerance2ApproxCurve = None, atLeastNSegment = None): + """ + la funzione ritorna la polilinea in forma di lineString. + """ + pts = self.asPolyline(tolerance2ApproxCurve, atLeastNSegment) + if pts is None or len(pts) == 0: + return None + return QgsLineString(pts) + + + # =============================================================================== + # asCompoundString + # =============================================================================== + def asCompoundString(self, tolerance2ApproxCurve = None, atLeastNSegment = None): + """ + la funzione ritorna la polilinea in forma di compoundString. + """ + compoundCurve = QgsCompoundCurve() + lastPt = None + for linearObject in self.defList: + if linearObject.whatIs() == "ARC": + compoundCurve.addCurve(linearObject.asCircularString(lastPt)) + else: + compoundCurve.addCurve(linearObject.asLineString(tolerance2ApproxCurve, atLeastNSegment, lastPt)) + lastPt = linearObject.getEndPt() + + return compoundCurve + + + # =============================================================================== + # asAbstractGeom + # =============================================================================== + def asAbstractGeom(self, wkbType = QgsWkbTypes.LineString, tolerance2ApproxCurve = None, atLeastNSegment = None): + """ + la funzione ritorna la polilinea in forma di QgsAbstractGeometry. + """ + flatType = QgsWkbTypes.flatType(wkbType) + + if flatType == QgsWkbTypes.CompoundCurve: + compoundCurve = self.asCompoundString(tolerance2ApproxCurve, atLeastNSegment) + return compoundCurve + + elif flatType == QgsWkbTypes.MultiCurve: + compoundCurve = self.asCompoundString(tolerance2ApproxCurve, atLeastNSegment) + multiCurve = QgsMultiCurve() + multiCurve.addGeometry(compoundCurve) + return multiCurve + + elif flatType == QgsWkbTypes.CurvePolygon: + compoundCurve = self.asCompoundString(tolerance2ApproxCurve, atLeastNSegment) + curvePolygon = QgsCurvePolygon() + curvePolygon.setExteriorRing(compoundCurve) + return curvePolygon + + elif flatType == QgsWkbTypes.MultiSurface: # Geometry that is combined from several CurvePolygon is called MultiSurface + curvePolygon = self.asAbstractGeom(QgsWkbTypes.CurvePolygon, tolerance2ApproxCurve, atLeastNSegment) + multiSurface = QgsMultiSurface() + multiSurface.addGeometry(curvePolygon) + return multiSurface + + elif flatType == QgsWkbTypes.Polygon: + linestring = self.asLineString(tolerance2ApproxCurve, atLeastNSegment) + polygon = QgsPolygon() + polygon.setExteriorRing(linestring) + return polygon + + elif flatType == QgsWkbTypes.MultiPolygon: + polygon = self.asAbstractGeom(QgsWkbTypes.Polygon, tolerance2ApproxCurve, atLeastNSegment) + multiPolygon = QgsMultiPolygon() + multiPolygon.addGeometry(polygon) + return multiPolygon + + elif flatType == QgsWkbTypes.MultiLineString: + lineString = self.asLineString(tolerance2ApproxCurve, atLeastNSegment) + multiLineString = QgsMultiLineString() + multiLineString.addGeometry(lineString) + return multiLineString + + return self.asLineString(tolerance2ApproxCurve, atLeastNSegment) + + + # =============================================================================== + # asGeom + # =============================================================================== + def asGeom(self, wkbType = QgsWkbTypes.LineString, tolerance2ApproxCurve = None, atLeastNSegment = None): + """ + la funzione ritorna la polilinea in forma di QgsGeometry. + """ + return QgsGeometry(self.asAbstractGeom(wkbType, tolerance2ApproxCurve, atLeastNSegment)) + + + # =============================================================================== + # copy + # =============================================================================== + def copy(self): + # obbligatoria + return QadPolyline(self) + + + # =============================================================================== + # reverse + # =============================================================================== + def reverse(self): + """ + la funzione rovescia il verso di una lista di oggetti lineari consecutivi. + """ + self.defList.reverse() + for linearObject in self.defList: + linearObject.reverse() + return self + + + # =============================================================================== + # reverseCorrection + # =============================================================================== + def reverseCorrection(self): + """ + la funzione controlla e corregge i versi delle parti della polilinea. + """ + totPart = self.qty() + if totPart <= 1: return + atPart = 0 + while atPart < totPart: + linearObject = self.getLinearObjectAt(atPart) + gType = linearObject.whatIs() + if gType == "ARC" or gType == "ELLIPSE_ARC": + startPt = linearObject.getStartPt(usingReversedFlag = False) + if atPart == 0: # prima parte + linearObject2 = self.getLinearObjectAt(atPart + 1) # parte successiva + if qad_utils.ptNear(startPt, linearObject2.getStartPt()) or \ + qad_utils.ptNear(startPt, linearObject2.getEndPt()): + linearObject.reversed = True + else: + linearObject.reversed = False + else: # parti successive alla prima + linearObject2 = self.getLinearObjectAt(atPart - 1) # parte precedente + if qad_utils.ptNear(startPt, linearObject2.getEndPt()): + linearObject.reversed = False + else: + linearObject.reversed = True + elif gType == "LINE": + startPt = linearObject.getStartPt() + if atPart == 0: # prima parte + linearObject2 = self.getLinearObjectAt(atPart + 1) # parte successiva + if qad_utils.ptNear(linearObject.getStartPt(), linearObject2.getStartPt()) or \ + qad_utils.ptNear(startPt, linearObject2.getEndPt()): + linearObject.reverse() + else: + linearObject2 = self.getLinearObjectAt(atPart - 1) # parte precedente + if qad_utils.ptNear(linearObject.getStartPt(), linearObject2.getEndPt()) == False: + linearObject.reverse() + atPart = atPart + 1 + + + # ============================================================================ + # length + # ============================================================================ + def length(self): + """ + la funzione restituisce la somma delle lunghezze della parti. + """ + tot = 0 + for linearObject in self.defList: + tot = tot + linearObject.length() + return tot + + + # ============================================================================ + # move + # ============================================================================ + def move(self, offsetX, offsetY): + """ + la funzione sposta le parti secondo un offset X e uno Y + """ + for linearObject in self.defList: + linearObject.move(offsetX, offsetY) + + + # ============================================================================ + # qty + # ============================================================================ + def qty(self): + """ + la funzione restituisce la quantità di oggetti lineari che compongono la polilinea. + """ + return len(self.defList) + + + # ============================================================================ + # getStartPt + # ============================================================================ + def getStartPt(self): + """ + la funzione restituisce il punto iniziale della polilinea. + """ + linearObject = self.getLinearObjectAt(0) # primo oggetto lineare + return None if linearObject is None else linearObject.getStartPt() + + + # ============================================================================ + # setStartPt + # ============================================================================ + def setStartPt(self, pt): + """ + la funzione setta il punto iniziale della polilinea. + """ + linearObject = self.getLinearObjectAt(0) # primo oggetto lineare + return None if linearObject is None else linearObject.setStartPt(pt) + + + # ============================================================================ + # getEndPt + # ============================================================================ + def getEndPt(self): + """ + la funzione restituisce il punto finale della polilinea. + """ + linearObject = self.getLinearObjectAt(-1) # ultimo oggetto lineare + return None if linearObject is None else linearObject.getEndPt() + + + # ============================================================================ + # setEndPt + # ============================================================================ + def setEndPt(self, pt): + """ + la funzione setta il punto finale della polilinea. + """ + linearObject = self.getLinearObjectAt(-1) # ultimo oggetto lineare + return None if linearObject is None else linearObject.setEndPt(pt) + + + # ============================================================================ + # getMiddlePt + # ============================================================================ + def getMiddlePt(self): + """ + la funzione restituisce il punto medio della polilinea. + """ + return self.getPointFromStart(self.length() / 2) + + + # ============================================================================ + # getCentroid + # ============================================================================ + def getCentroid(self, tolerance2ApproxCurve = None): + """ + la funzione restituisce il punto centroide di una polilinea chiusa. + """ + if self.isClosed(): # verifico se polilinea chiusa + ptList = self.asPolyline(tolerance2ApproxCurve) + g = QgsGeometry.fromPolygonXY([ptList]) + if g is not None: + centroid = g.centroid() + if centroid is not None: + return g.centroid().asPoint() + + return None + + + # ============================================================================ + # isClosed + # ============================================================================ + def isClosed(self): + """ + la funzione restituisce True se la polilinea (lista di parti segmenti-archi) é chiusa. + """ + if len(self.defList) == 0: + return False + else: + return True if qad_utils.ptNear(self.getStartPt(), self.getEndPt()) else False + + + # ============================================================================ + # setClose + # ============================================================================ + def setClose(self, toClose = True): + """ + la funzione chiude o apre la polilinea. + """ + if toClose: # da chiudere + if self.isClosed() == False: + if self.qty() > 0: + linearObject = self.getLinearObjectAt(-1) + # verifico l'ultimo oggetto + if linearObject.whatIs() == "LINE": + if self.qty() > 1: + self.append(QadLine().set(self.getEndPt(), self.getStartPt())) + elif linearObject.whatIs() == "ARC": + arc = QadArc() + if arc.fromStartEndPtsTan(linearObject.getEndPt(), \ + self.getStartPt(), \ + linearObject.getTanDirectionOnEndPt()) == False: + return + self.append(arc) + elif linearObject.whatIs() == "ELLIPSE_ARC": + # da fare + pass + + else: # da aprire + if self.isClosed() == True: + if self.qty() > 1: + self.remove(-1) + + + # =============================================================================== + # getBoundingBox + # =============================================================================== + def getBoundingBox(self): + """ + la funzione ritorna il rettangolo che racchiude la polilinea. + """ + boundingBox = self.getLinearObjectAt(0).getBoundingBox() + i = 1 + while i < self.qty(): + boundingBox.combineExtentWith(self.getLinearObjectAt(i).getBoundingBox()) + i = i + 1 + + return boundingBox + + + # =============================================================================== + # isContainingEllipseArcs + # =============================================================================== + def segmentizeEllipseArcs(self, tolerance2ApproxCurve = None): + """ + la funzione trasforma gli archi di ellisse in segmenti + """ + if tolerance2ApproxCurve is None: + tolerance = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) + else: + tolerance = tolerance2ApproxCurve + + if self.isClosed() == True: + prevPartEndPt = self.defList[-1].getEndPt() + else: + prevPartEndPt = None + + partAt = 0 + while partAt < len(self.defList): + linearObject = self.defList[partAt] + if linearObject.whatIs() == "ELLIPSE_ARC": + points = linearObject.asPolyline(tolerance) + if prevPartEndPt is not None: + points[0] = prevPartEndPt + prevPartEndPt = points[-1] + + self.remove(partAt) + pointsLen = len(points) + i = 0 + while i < pointsLen - 1: # fino al penultimo punto + line = QadLine() + line.set(points[i], points[i + 1]) + self.insert(partAt, line) + partAt = partAt + 1 + i = i + 1 + else: + prevPartEndPt = self.defList[partAt].getEndPt() + partAt = partAt + 1 + + return True + + + # ============================================================================ + # getTanDirectionOnStartPt + # ============================================================================ + def getTanDirectionOnStartPt(self): + """ + la funzione ritorna la direzione della tangente al punto iniziale dell'oggetto. + """ + linearObject = self.getLinearObjectAt(0) # primo oggetto lineare + return None if linearObject is None else linearObject.getTanDirectionOnStartPt() + + + # ============================================================================ + # getTanDirectionOnEndPt + # ============================================================================ + def getTanDirectionOnEndPt(self): + """ + la funzione ritorna la direzione della tangente al punto iniziale dell'oggetto. + """ + linearObject = self.getLinearObjectAt(-1) # ultimo oggetto lineare + return None if linearObject is None else linearObject.getTanDirectionOnEndPt() + + + # ============================================================================ + # curve + # ============================================================================ + def curve(self, toCurve = True): + """ + se toCurve = True: + la funzione curva ogni segmento per adattarlo alla polilinea + facendo passare la nuova polilinea per i vertici. + se toCurve = False: + la funzione trasforma in segmento retto ogni arco della polilinea (lista di parti segmenti-archi). + """ + if toCurve == False: + i = 0 + while i < self.qty(): + linearObject = self.defList[i] + if linearObject.whatIs() != "LINE": + self.insert(i, QadLine().set(linearObject.getStartPt(), linearObject.getEndPt())) + self.remove(i + 1) + i = i + 1 + return + + tot = self.qty() + if tot < 2: return + isClosed = self.isClosed() + + newPolyline = QadPolyline() + + # primo oggetto lineare + current = self.getLinearObjectAt(0) + prev = None + tanDirectionOnStartPt = None + if isClosed: + prev = self.getLinearObjectAt(-1) + arc = QadArc() + if arc.fromStartSecondEndPts(prev.getStartPt(), current.getStartPt(), current.getEndPt()): + if not arc.reversed: # arco non é inverso + arc.setStartAngleByPt(current.getStartPt()) + else: # arco é inverso + arc.setEndAngleByPt(current.getStartPt()) + tanDirectionOnStartPt = arc.getTanDirectionOnEndPt() + + next = self.getLinearObjectAt(1) + newPolyline.defList.extend(getCurveLinearObjects(tanDirectionOnStartPt, prev, current, next)) + + i = 1 + while i < tot - 1: + tanDirectionOnStartPt = newPolyline.getLinearObjectAt(-1).getTanDirectionOnEndPt() + prev = current + current = next + next = self.getLinearObjectAt(i + 1) + newPolyline.defList.extend(getCurveLinearObjects(tanDirectionOnStartPt, prev, current, next)) + i = i + 1 + + # ultimo oggetto lineare + tanDirectionOnStartPt = newPolyline.getLinearObjectAt(-1).getTanDirectionOnEndPt() + prev = current + current = next + next = self.getLinearObjectAt(0) if isClosed else None + newPolyline.defList.extend(getCurveLinearObjects(tanDirectionOnStartPt, prev, current, next)) + + self.set(newPolyline) + + + # ============================================================================ + # simplify + # ============================================================================ + def simplify(self, tolerance): + g = QgsGeometry.fromPolylineXY(self.asPolyline()).simplify(tolerance) + return self.fromPolyline(g.asPolyline()) + + + # ============================================================================ + # getDistanceFromStart + # ============================================================================ + def getDistanceFromStart(self, pt): + """ + la funzione restituisce la distanza di (che deve essere sull'oggetto) dal punto iniziale. + """ + tot = 0 + for linearObject in self.defList: + if linearObject.containsPt(pt) == True: + return tot + linearObject.getDistanceFromStart(pt) + else: + tot = tot + linearObject.length() + + return -1 + + + # ============================================================================ + # getPointFromStart + # ============================================================================ + def getPointFromStart(self, distance): + """ + la funzione restituisce un punto (e la direzione della tangente) della polilinea alla distanza + (che deve essere sull'oggetto) dal punto iniziale. + """ + if distance < 0: + return None, None + d = distance + for linearObject in self.defList: + l = linearObject.length() + if d > l: + d = d - l + else: + return linearObject.getPointFromStart(d) + + return None, None + + + # ============================================================================ + # lengthen_delta + # ============================================================================ + def lengthen_delta(self, move_startPt, delta): + """ + la funzione sposta il punto iniziale (se move_startPt = True) o finale (se move_startPt = False) + di una distanza delta allungando (se delta > 0) o accorciando (se delta < 0) la polilinea + """ + length = self.length() + # lunghezza polilinea + delta non può essere <= 0 + if length + delta <= 0: + return False + + if move_startPt == False: + # dal punto finale + if delta >= 0: # allungo la polilinea + # ultima parte + return self.getLinearObjectAt(-1).lengthen_delta(False, delta) + else: # accorcio la polilinea + # cerco la parte in cui finirebbe la polilinea accorciata + nPart = 0 + d = length + delta + for linearObject in self.defList: + l = linearObject.length() + if d > l: + d = d - l + nPart = nPart + 1 + else: + if linearObject.lengthen_delta(False, -(l - d)) == False: + return False + # se non è l'ultima parte + if nPart+1 < len(self.defList): + # cancello le parti successive a nPart + del self.defList[nPart+1 :] + return True + else: # dal punto iniziale + # prima parte + dummy = self.copy() + dummy.reverse() + if dummy.lengthen_delta(False, delta) == False: + return False + dummy.reverse() + self.set(dummy) + return True + + + # ============================================================================ + # lengthen_deltaAngle + # ============================================================================ + def lengthen_deltaAngle(self, move_startPt, delta): + """ + la funzione sposta il punto iniziale (se move_startPt = True) o finale (se move_startPt = False) + di un certo numero di gradi delta. + """ + if move_startPt == False: + # dal punto finale + return self.getLinearObjectAt(-1).lengthen_deltaAngle(False, delta) + else: + # dal punto iniziale + return self.getLinearObjectAt(0).lengthen_deltaAngle(True, delta) + + + # =============================================================================== + # transform + # =============================================================================== + def transform(self, coordTransform): + """ + la funzione restituisce una nuova polilinea con le coordinate trasformate. + """ + result = QadPolyline() + for linearObject in self.defList: + result.append(linearObject.transform(coordTransform)) + return result + + + # =============================================================================== + # transformFromCRSToCRS + # =============================================================================== + def transformFromCRSToCRS(self, sourceCRS, destCRS): + """ + la funzione trasforma le coordinate dei punti che compone l'oggetto lineare. + """ + return self.transform(QgsCoordinateTransform(sourceCRS, destCRS, QgsProject.instance())) + + + # ============================================================================ + # containsPt + # ============================================================================ + def containsPt(self, pt, startAt = 0): + """ + la funzione ritorna True se il punto è sulla polilinea altrimenti False. + Il controllo inizia dalla parte (0-based) + """ + tot = len(self.defList) + if startAt < 0 or startAt >= tot: + return False + i = startAt + while i < tot: + linearObject = self.defList[i] + if linearObject.containsPt(pt): + return True + i = i + 1 + return False + + + # ============================================================================ + # move + # ============================================================================ + def move(self, offsetX, offsetY): + for linearObject in self.defList: + linearObject.move(offsetX, offsetY) + + + # ============================================================================ + # rotate + # ============================================================================ + def rotate(self, basePt, angle): + for linearObject in self.defList: + linearObject.rotate(basePt, angle) + + + # ============================================================================ + # scale + # ============================================================================ + def scale(self, basePt, scale): + for linearObject in self.defList: + linearObject.scale(basePt, scale) + + + # ============================================================================ + # mirror + # ============================================================================ + def mirror(self, mirrorPt, mirrorAngle): + for linearObject in self.defList: + linearObject.mirror(mirrorPt, mirrorAngle) + + + # =============================================================================== + # funzioni di creazione rettangoli + # getRectByCorners + # =============================================================================== + def getRectByCorners(self, firstCorner, secondCorner, rot, gapType, \ + gapValue1 = None, gapValue2 = None): + """ + ritorna una polilinea che definisce il rettangolo costruito mediante + i due spigoli opposti firstCorner e secondCorner, la rotazione con punto base firstCorner e gapType + 0 = gli spigoli del rettangolo hanno angoli retti + 1 = raccorda gli spigoli del rettangolo con un raggio di curvatura gapValue1 + 2 = smussa gli spigoli del rettangolo con 2 distanze di cimatura gapValue1, gapValue2 + """ + self.removeAll() + # creo un rettangolo ruotato con con angoli retti + secondCornerProj = qad_utils.getPolarPointByPtAngle(firstCorner, rot, 10) + pt2 = qad_utils.getPerpendicularPointOnInfinityLine(firstCorner, secondCornerProj, secondCorner) + angle = qad_utils.getAngleBy2Pts(firstCorner, pt2) + pt4 = qad_utils.getPolarPointByPtAngle(secondCorner, angle + math.pi, \ + qad_utils.getDistance(firstCorner, pt2)) + + line = QadLine() + if gapType == 0: # gli spigoli del rettangolo hanno angoli retti + self.append(line.set(firstCorner, pt2)) + self.append(line.set(pt2, secondCorner)) + self.append(line.set(secondCorner, pt4)) + self.append(line.set(pt4, firstCorner)) + return True + else: + length = qad_utils.getDistance(firstCorner, pt2) + width = qad_utils.getDistance(pt2, secondCorner) + + if gapType == 1: # raccorda gli spigoli del rettangolo con un raggio di curvatura gapValue1 + if (gapValue1 * 2) > length or (gapValue1 * 2) > width: # il rettangolo é troppo piccolo + self.append(line.set(firstCorner, pt2)) + self.append(line.set(pt2, secondCorner)) + self.append(line.set(secondCorner, pt4)) + self.append(line.set(pt4, firstCorner)) + return True + + arc = QadArc() + + diagonal = math.sqrt((gapValue1 * gapValue1) * 2) + diagonal = gapValue1 - (diagonal / 2) + + # lato + p1 = qad_utils.getPolarPointByPtAngle(firstCorner, angle, gapValue1) + p2 = qad_utils.getPolarPointByPtAngle(pt2, angle + math.pi, gapValue1) + self.append(line.set(p1, p2)) + # arco + angle = qad_utils.getAngleBy2Pts(pt2, secondCorner) + p3 = qad_utils.getPolarPointByPtAngle(pt2, angle, gapValue1) + pMiddle = qad_utils.getMiddlePoint(p2, p3) + pMiddle = qad_utils.getPolarPointByPtAngle(pMiddle, qad_utils.getAngleBy2Pts(pMiddle, pt2), diagonal) + arc.fromStartSecondEndPts(p2, pMiddle, p3) + self.append(arc) + # lato + p4 = qad_utils.getPolarPointByPtAngle(secondCorner, angle + math.pi, gapValue1) + self.append(line.set(p3, p4)) + # arco + angle = qad_utils.getAngleBy2Pts(secondCorner, pt4) + p5 = qad_utils.getPolarPointByPtAngle(secondCorner, angle, gapValue1) + pMiddle = qad_utils.getMiddlePoint(p4, p5) + pMiddle = qad_utils.getPolarPointByPtAngle(pMiddle, qad_utils.getAngleBy2Pts(pMiddle, secondCorner), diagonal) + arc.fromStartSecondEndPts(p4, pMiddle, p5) + self.append(arc) + # lato + p6 = qad_utils.getPolarPointByPtAngle(pt4, angle + math.pi, gapValue1) + self.append(line.set(p5, p6)) + # arco + angle = qad_utils.getAngleBy2Pts(pt4, firstCorner) + p7 = qad_utils.getPolarPointByPtAngle(pt4, angle, gapValue1) + pMiddle = qad_utils.getMiddlePoint(p6, p7) + pMiddle = qad_utils.getPolarPointByPtAngle(pMiddle, qad_utils.getAngleBy2Pts(pMiddle, pt4), diagonal) + arc = QadArc() + arc.fromStartSecondEndPts(p6, pMiddle, p7) + self.append(arc) + # lato + p8 = qad_utils.getPolarPointByPtAngle(firstCorner, angle + math.pi, gapValue1) + self.append(line.set(p7, p8)) + # arco + pMiddle = qad_utils.getMiddlePoint(p8, p1) + pMiddle = qad_utils.getPolarPointByPtAngle(pMiddle, qad_utils.getAngleBy2Pts(pMiddle, firstCorner), diagonal) + arc = QadArc() + arc.fromStartSecondEndPts(p8, pMiddle, p1) + self.append(arc) + return True + elif gapType == 2: # smussa gli spigoli del rettangolo con 2 distanze di cimatura gapValue1, gapValue2 + if (gapValue1 + gapValue2) > length or (gapValue1 + gapValue2) > width: # il rettangolo é troppo piccolo + self.append(line.set(firstCorner, pt2)) + self.append(line.set(pt2, secondCorner)) + self.append(line.set(secondCorner, pt4)) + self.append(line.set(pt4, firstCorner)) + return True + + p1 = qad_utils.getPolarPointByPtAngle(firstCorner, angle, gapValue2) + p2 = qad_utils.getPolarPointByPtAngle(pt2, angle + math.pi, gapValue1) + angle = qad_utils.getAngleBy2Pts(pt2, secondCorner) + p3 = qad_utils.getPolarPointByPtAngle(pt2, angle, gapValue2) + p4 = qad_utils.getPolarPointByPtAngle(secondCorner, angle + math.pi, gapValue1) + angle = qad_utils.getAngleBy2Pts(secondCorner, pt4) + p5 = qad_utils.getPolarPointByPtAngle(secondCorner, angle, gapValue2) + p6 = qad_utils.getPolarPointByPtAngle(pt4, angle+ math.pi, gapValue1) + angle = qad_utils.getAngleBy2Pts(pt4, firstCorner) + p7 = qad_utils.getPolarPointByPtAngle(pt4, angle, gapValue2) + p8 = qad_utils.getPolarPointByPtAngle(firstCorner, angle + math.pi, gapValue1) + + self.append(line.set(p1, p2)) + self.append(line.set(p2, p3)) + self.append(line.set(p3, p4)) + self.append(line.set(p4, p5)) + self.append(line.set(p5, p6)) + self.append(line.set(p6, p7)) + self.append(line.set(p7, p8)) + self.append(line.set(p8, p1)) + return True + + return False + + + # =============================================================================== + # getRectByCornerAndDims + # =============================================================================== + def getRectByCornerAndDims(self, firstCorner, lengthDim, widthDim, rot, gapType, \ + gapValue1 = None, gapValue2 = None): + """ + ritorna una polilinea che definisce il rettangolo costruito mediante + uno spigolo , la lunghezza, la larghezza, la rotazione con punto base firstCorner e gapType + 0 = gli spigoli del rettangolo hanno angoli retti + 1 = raccorda gli spigoli del rettangolo con un raggio di curvatura gapValue1 + 2 = smussa gli spigoli del rettangolo con 2 distanze di cimatura gapValue1, gapValue2 + """ + pt2 = qad_utils.getPolarPointByPtAngle(firstCorner, rot, lengthDim) + secondCorner = qad_utils.getPolarPointByPtAngle(pt2, rot + (math.pi / 2), widthDim) + return self.getRectByCorners(firstCorner, secondCorner, rot, gapType, gapValue1, gapValue2) + + + # =============================================================================== + # getRectByAreaAndLength + # =============================================================================== + def getRectByAreaAndLength(self, firstCorner, area, lengthDim, rot, gapType, \ + gapValue1 = None, gapValue2 = None): + """ + ritorna una polilinea che definisce il rettangolo costruito mediante + uno spigolo , l'area, la larghezza, la rotazione con punto base firstCorner e gapType + 0 = gli spigoli del rettangolo hanno angoli retti + 1 = raccorda gli spigoli del rettangolo con un raggio di curvatura gapValue1 + 2 = smussa gli spigoli del rettangolo con 2 distanze di cimatura gapValue1, gapValue2 + """ + if gapType == 0: # gli spigoli del rettangolo hanno angoli retti + widthDim = area / lengthDim + return self.getRectByCornerAndDims(firstCorner, lengthDim, widthDim, rot, gapType, \ + gapValue1, gapValue2) + else: + if gapType == 1: # raccorda gli spigoli del rettangolo con un raggio di curvatura gapValue1 + angleArea = ((2 * gapValue1) * (2 * gapValue1)) - (math.pi * gapValue1 * gapValue1) + widthDim = (area + angleArea) / lengthDim + if (gapValue1 * 2) > lengthDim or (gapValue1 * 2) > widthDim: # il rettangolo é troppo piccolo + widthDim = area / lengthDim + return self.getRectByCornerAndDims(firstCorner, lengthDim, widthDim, rot, gapType, \ + gapValue1, gapValue2) + elif gapType == 2: # smussa gli spigoli del rettangolo con 2 distanze di cimatura gapValue1, gapValue2 + angleArea = 2 * (gapValue1 * gapValue2) + widthDim = (area + angleArea) / lengthDim + if (gapValue1 + gapValue2) > lengthDim or (gapValue1 + gapValue2) > widthDim: # il rettangolo é troppo piccolo + widthDim = area / lengthDim + return self.getRectByCornerAndDims(firstCorner, lengthDim, widthDim, rot, gapType, \ + gapValue1, gapValue2) + + + # =============================================================================== + # getRectByAreaAndWidth + # =============================================================================== + def getRectByAreaAndWidth(self, firstCorner, area, widthDim, rot, gapType, \ + gapValue1 = None, gapValue2 = None): + """ + ritorna una polilinea che definisce il rettangolo costruito mediante + uno spigolo , l'area, la larghezza, la rotazione con punto base firstCorner e gapType + 0 = gli spigoli del rettangolo hanno angoli retti + 1 = raccorda gli spigoli del rettangolo con un raggio di curvatura gapValue1 + 2 = smussa gli spigoli del rettangolo con 2 distanze di cimatura gapValue1, gapValue2 + """ + if gapType == 0: # gli spigoli del rettangolo hanno angoli retti + lengthDim = area / widthDim + return self.getRectByCornerAndDims(firstCorner, lengthDim, widthDim, rot, gapType, \ + gapValue1, gapValue2) + else: + if gapType == 1: # raccorda gli spigoli del rettangolo con un raggio di curvatura gapValue1 + angleArea = math.pi * gapValue1 * gapValue1 + lengthDim = (area + angleArea) / widthDim + if (gapValue1 * 2) > lengthDim or (gapValue1 * 2) > widthDim: # il rettangolo é troppo piccolo + lengthDim = area / widthDim + return self.getRectByCornerAndDims(firstCorner, lengthDim, widthDim, rot, gapType, \ + gapValue1, gapValue2) + elif gapType == 2: # smussa gli spigoli del rettangolo con 2 distanze di cimatura gapValue1, gapValue2 + angleArea = 2 * (gapValue1 * gapValue2) + lengthDim = (area + angleArea) / widthDim + if (gapValue1 + gapValue2) > lengthDim or (gapValue1 + gapValue2) > widthDim: # il rettangolo é troppo piccolo + lengthDim = area / widthDim + return self.getRectByCornerAndDims(firstCorner, lengthDim, widthDim, rot, gapType, \ + gapValue1, gapValue2) + + + # =============================================================================== + # funzioni di creazione poligoni + # getPolygonByNsidesCenterRadius + # =============================================================================== + def getPolygonByNsidesCenterRadius(self, sideNumber, centerPt, radius, Inscribed, ptStart = None): + """ + ritorna una polilinea che definisce il poligono costruito mediante + sideNumber = numero di lati + centerPt = centro del poligono + radius = raggio del cerchio + Inscribed = se True significa poligono inscritto altrimento circoscritto + ptStart = punto da cui partire + """ + self.removeAll() + line = QadLine() + + angleIncrement = 2 * math.pi / sideNumber + # poligono circoscritto + if Inscribed == False: + # calcolo il nuovo raggio + myRadius = radius / math.cos(angleIncrement / 2) + + if ptStart is None: + myPtStart = qad_utils.getPolarPointByPtAngle(centerPt, math.pi / 2 * 3 + (angleIncrement / 2), myRadius) + angle = qad_utils.getAngleBy2Pts(centerPt, myPtStart) + else: + angle = qad_utils.getAngleBy2Pts(centerPt, ptStart) + myPtStart = qad_utils.getPolarPointByPtAngle(centerPt, angle + (angleIncrement / 2), myRadius) + angle = qad_utils.getAngleBy2Pts(centerPt, myPtStart) + else: # poligono inscritto + myRadius = radius + + if ptStart is None: + myPtStart = qad_utils.getPolarPointByPtAngle(centerPt, math.pi / 2 * 3 + (angleIncrement / 2), myRadius) + angle = qad_utils.getAngleBy2Pts(centerPt, myPtStart) + else: + myPtStart = ptStart + angle = qad_utils.getAngleBy2Pts(centerPt, ptStart) + + previusPt = myPtStart + for i in range(1, sideNumber, 1): + angle = angle + angleIncrement + pt = qad_utils.getPolarPointByPtAngle(centerPt, angle, myRadius) + self.append(line.set(previusPt, pt)) + previusPt = pt + self.append(line.set(previusPt, myPtStart)) + + return True + + + # =============================================================================== + # getPolygonByNsidesEdgePts + # =============================================================================== + def getPolygonByNsidesEdgePts(self, sideNumber, firstEdgePt, secondEdgePt): + """ + ritorna una polilinea che definisce il poligono costruito mediante + sideNumber = numero di lati + firstEdgePt = primo punto di un lato + secondEdgePt = secondo punto di un lato + """ + self.removeAll() + line = QadLine() + + angleIncrement = 2 * math.pi / sideNumber + angle = qad_utils.getAngleBy2Pts(firstEdgePt, secondEdgePt) + sideLength = qad_utils.getDistance(firstEdgePt, secondEdgePt) + + self.append(line.set(firstEdgePt, secondEdgePt)) + previusPt = secondEdgePt + for i in range(1, sideNumber - 1, 1): + angle = angle + angleIncrement + pt = qad_utils.getPolarPointByPtAngle(previusPt, angle, sideLength) + self.append(line.set(previusPt, pt)) + previusPt = pt + self.append(line.set(previusPt, firstEdgePt)) + + return True + + + # =============================================================================== + # getPolygonByNsidesArea + # =============================================================================== + def getPolygonByNsidesArea(self, sideNumber, centerPt, area): + """ + ritorna una polilinea che definisce il poligono costruito mediante + sideNumber = numero di lati + centerPt = centro del poligono + area = area del poligono + """ + angle = 2 * math.pi / sideNumber + triangleArea = area / sideNumber / 2 + # divido il poligono in sideNumber triangoli + # ogni trinagolo viene diviso in 2 generando 2 trinagoli rettangoli in cui + # "(base * altezza) / 2 = Area" che equivale a "base = 2 * Area / altezza" + # "tan(alfa) = base / altezza" che equivale a "tan(alfa) * altezza = base + # per sostituzione si ha + # "tan(alfa) * altezza = 2 * Area / altezza" quindi + # "altezza = sqrt(2 * Area / tan(alfa))" + h = math.sqrt(2 * triangleArea / math.tan(angle / 2)) + + return self.getPolygonByNsidesCenterRadius(sideNumber, centerPt, h, False) + + + # ============================================================================ + # getPartPosAtPt + # ============================================================================ + def getPartPosAtPt(self, pt, startAt = 0): + """ + la funzione restituisce la posizione del parte contentente il punto (0-based), + -1 se non trovato. + Il controllo inizia dalla parte (0-based) + """ + tot = len(self.defList) + if startAt < 0 or startAt >= tot: + return -1 + i = startAt + while i < tot: + linearObject = self.defList[i] + if linearObject.containsPt(pt): + return i + i = i + 1 + return -1 + + + # =============================================================================== + # getGeomBetween2Pts + # considerPolylineAsOpened serve per evitare che in caso di polilinea chiusa ritorni il percorso più breve per andare da startPt a endPt. + # vedi break su rettangolo (su layer linestring) + # =============================================================================== + def getGeomBetween2Pts(self, startPt, endPt, considerPolylineAsOpened = False): + """ + Ritorna una sotto geometria che parte dal punto startPt e finisce al punto endPt seguendo il tracciato della geometria. + Se la polilinea è chiusa ritorna il percorso più breve per andare da startPt a endPt. + """ + tot = self.qty() + + iStart = self.getPartPosAtPt(startPt) # numero della parte contenente startPt + if iStart == -1: return None + + result1 = QadPolyline() + i = iStart + lastPt = startPt + ok1 = False + while i < tot: + linearObj = self.getLinearObjectAt(i) + if linearObj.containsPt(endPt): + result1.append(linearObj.getGeomBetween2Pts(lastPt, endPt)) + ok1 = True + break + elif i == iStart: + result1.append(linearObj.getGeomBetween2Pts(lastPt, linearObj.getEndPt())) + else: + result1.append(linearObj) + + if result1.qty() > 0: lastPt = result1.getEndPt() # getGeomBetween2Pts potrebbe restituire None + i = i + 1 + + if ok1: + if self.isClosed() == False or considerPolylineAsOpened == True: return result1 + else: + # se non trovata la fine ed è una polilinea chiusa, riparto dall'inizio + if self.isClosed(): + i = 0 + while i < iStart: + linearObj = self.getLinearObjectAt(i) + if linearObj.containsPt(endPt): + result1.append(linearObj.getGeomBetween2Pts(lastPt, endPt)) + ok1 = True + break + else: + if linearObj.length() > 0: result1.append(linearObj) + + if result1.qty() > 0: lastPt = result1.getEndPt() # getGeomBetween2Pts potrebbe restituire None + + i = i + 1 + + # cerco nel senso opposto + inversedPolyline = QadPolyline(self).reverse() + + result2 = QadPolyline() + iStart = tot - 1 - iStart + i = iStart + lastPt = startPt + ok2 = False + while i < tot: + linearObj = inversedPolyline.getLinearObjectAt(i) + if linearObj.containsPt(endPt): + result2.append(linearObj.getGeomBetween2Pts(lastPt, endPt)) + ok2 = True + break + elif i == iStart: + result2.append(linearObj.getGeomBetween2Pts(lastPt, linearObj.getEndPt())) + else: + result2.append(linearObj) + + if result2.qty() > 0: lastPt = result2.getEndPt() # getGeomBetween2Pts potrebbe restituire None + i = i + 1 + + if ok2: + if self.isClosed() == False: return result2 + else: + # se non trovata la fine ed è una polilinea chiusa, riparto dall'inizio + if self.isClosed(): + i = 0 + while i < iStart: + linearObj = inversedPolyline.getLinearObjectAt(i) + if linearObj.containsPt(endPt): + result2.append(linearObj.getGeomBetween2Pts(lastPt, endPt)) + ok2 = True + break + else: + if linearObj.length() > 0: result2.append(linearObj) + + if result2.qty() > 0: lastPt = result2.getEndPt() # getGeomBetween2Pts potrebbe restituire None + i = i + 1 + + if ok1: + if ok2: + return result1 if result1.length() < result2.length() else result2 + else: + return result1 + else: + if ok2: + return result2 + else: + return None + + + # =============================================================================== + # breakOnPts + # =============================================================================== + def breakOnPts(self, firstPt, secondPt): + """ + la funzione spezza la geometria in un punto (se = None) o in due punti + come fa il trim. Ritorna una o due geometrie risultanti dall'operazione. + = primo punto di divisione + = secondo punto di divisione + """ + if secondPt is None or firstPt == secondPt: # break su un punto + if self.isClosed(): return None, None # se é chiusa + return [self.getGeomBetween2Pts(self.getStartPt(), firstPt), self.getGeomBetween2Pts(firstPt, self.getEndPt())] + else: # break su 2 punti + dist1 = self.getDistanceFromStart(firstPt) + dist2 = self.getDistanceFromStart(secondPt) + if dist1 < dist2: + g1 = self.getGeomBetween2Pts(self.getStartPt(), firstPt, True) + g2 = self.getGeomBetween2Pts(secondPt, self.getEndPt(), True) + else: + g1 = self.getGeomBetween2Pts(self.getStartPt(), secondPt, True) + g2 = self.getGeomBetween2Pts(firstPt, self.getEndPt(), True) + + if self.isClosed(): # se é chiusa + g2.appendPolyline(g1) + return [g2, None] + else: + return [g1, g2] + + +# =============================================================================== +# getCurveLinearObjects +# =============================================================================== +def getCurveLinearObjects(tanDirectionOnStartPt, prev, current, next): + """ + Data la direzione della tangente nel punto iniziale della parte corrente e + una successione di 3 parti lineari, + la funzione ritorna una lista di parti lineari + da sostituire alla parte per curvare la polilinea + """ + # se non ci sono né la parte precedente né la parte successiva + if prev is None and next is None: + return current + + arc = QadArc() + if prev is None: # non c'é una parte precedente + if arc.fromStartSecondEndPts(current.getStartPt(), current.getEndPt(), next.getEndPt()) == False: + return [current] + if not arc.reversed: # arco non é inverso + arc.setEndAngleByPt(current.getEndPt()) + return [arc] + else: # arco é inverso + arc.setStartAngleByPt(current.getEndPt()) + return [arc] + else: # c'é una parte precedente + t = prev.getTanDirectionOnEndPt() if tanDirectionOnStartPt is None else tanDirectionOnStartPt + + if next is None: # non c'é una parte successiva + if arc.fromStartEndPtsTan(current.getStartPt(), current.getEndPt(), t) == False: + return [current] + return [arc] + else: # c'é una parte precedente e successiva + if arc.fromStartSecondEndPts(prev.getStartPt(), current.getStartPt(), current.getEndPt()) == False: + return [current] + if not arc.reversed: # arco non é inverso + arc.setStartAngleByPt(current.getStartPt()) + else: # arco é inverso + arc.setEndAngleByPt(current.getStartPt()) + arc2 = QadArc() + if arc2.fromStartSecondEndPts(current.getStartPt(), current.getEndPt(), next.getEndPt()) == False: + return [current] + if not arc2.reversed: # arco non é inverso + arc2.setEndAngleByPt(current.getEndPt()) + else: # arco é inverso + arc2.setStartAngleByPt(current.getEndPt()) + + midPt = qad_utils.getMiddlePoint(arc.getMiddlePt(), arc2.getMiddlePt()) + + if arc.fromStartEndPtsTan(current.getStartPt(), midPt, t) == False: + return [current] + + if arc2.fromStartEndPtsTan(arc.getEndPt(), current.getEndPt(), \ + arc.getTanDirectionOnEndPt()) == False: + return [current] + + return [arc, arc2] diff --git a/qad_rc.py b/qad_rc.py index e8815f3d..9da304ad 100644 --- a/qad_rc.py +++ b/qad_rc.py @@ -1,5474 +1,12708 @@ -# -*- coding: utf-8 -*- - -# Resource object code -# -# Created: lun 4. gen 11:54:36 2016 -# by: The Resource Compiler for PyQt (Qt v4.8.5) -# -# WARNING! All changes made in this file will be lost! - -from PyQt4 import QtCore - -qt_resource_data = "\ -\x00\x00\x02\xee\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x09\x12\ -\x0d\x16\x23\xa5\xc7\x7d\x6c\x00\x00\x02\x6e\x49\x44\x41\x54\x38\ -\xcb\x7d\x93\xcb\x4b\x54\x71\x14\xc7\xbf\xbf\x7b\xef\x80\xcd\xc3\ -\x84\x59\x24\xd8\xca\xca\x45\x9a\x22\x63\x0b\xf3\x4e\x25\x88\x63\ -\x9a\x86\x99\x42\x49\x63\x0b\x45\xff\x86\x16\x82\x8b\x04\x83\x5a\ -\xb4\x0c\x83\xb4\x5d\xe2\x93\xa4\x24\xcd\xc8\x47\x6e\x8a\x34\x98\ -\x6b\x17\x33\x66\x1c\x66\x6e\xe2\x6b\xe6\x3e\x1c\xe7\x8a\xa7\x45\ -\x3a\x39\xa3\x79\xe0\xb3\x39\x87\x73\x38\x7c\xcf\xf9\x32\x22\xc2\ -\xe1\x78\xdd\x3f\x42\xd3\x33\xb3\x90\x65\x19\x86\x61\xc0\x6a\xb5\ -\x22\x27\x27\x07\x62\xc9\x15\x34\xd4\xd5\x30\xa4\x06\x11\x81\x88\ -\xe0\x0f\xac\xe0\xae\xb7\x99\x3a\xbb\x9e\x92\xbc\xf4\x0b\xa6\xb9\ -\x0b\x22\x82\x69\x9a\x58\x94\x7f\xe2\xf1\x93\x67\x74\xcf\xdb\x4c\ -\xfe\xc0\x4a\xa2\x87\x88\xb0\xdf\x1c\x44\xc5\xcd\xdb\x24\xfd\x58\ -\x4a\x2a\xa6\xe2\x5b\x94\xb3\x3d\x55\xb5\xe4\x0f\x04\x93\x07\x34\ -\x36\xb5\x90\xbc\xb4\x8c\x58\x6c\x07\xeb\x9b\x11\x84\x94\x35\x04\ -\x82\x0a\xfc\x2b\x61\x04\x82\x0a\x42\xca\x1a\xd6\x37\x23\x10\xaf\ -\x97\x93\x4f\x92\xb3\x1b\x9b\x5a\xe8\x60\x00\x37\x30\x3c\x4a\xb9\ -\xb9\x17\x91\x71\x3a\x03\xe1\xd5\x0d\xac\xae\x6d\x61\x63\x4b\x45\ -\x54\xdb\x86\xaa\xc7\x10\xd5\xb6\xb1\xb1\xa5\xe2\x56\x6d\x3d\xbd\ -\xea\xed\x61\x4e\xa7\x73\xf9\xc2\xf9\x73\x18\x18\x7a\x43\x00\xc0\ -\xcd\xce\xce\xe1\xb2\xcb\x85\x88\xaa\x23\x1e\x37\xc1\x31\x06\x8b\ -\xc0\x27\xd1\xd6\xd6\x4a\x2f\xba\xbb\x59\x3c\x6e\x22\xaa\x19\x28\ -\x2a\x72\x61\xe6\xf3\x1c\x00\x40\xf0\x49\x12\xbc\xde\xfb\xad\x3c\ -\xcf\x83\xe7\xff\x89\xeb\x6d\x7a\x90\x38\x4f\x6f\xcf\xcb\x24\xf5\ -\xb3\xb2\xce\xf6\xf9\x7c\x52\xbd\xbb\xd4\x03\xce\x30\x0c\xa4\xa7\ -\x3b\x9e\x5b\x2c\x02\x0e\x03\x00\x53\x93\x63\x0c\x00\x52\x6b\x0e\ -\x87\xbd\x41\xd7\xf5\xbf\x1b\xd8\x6c\x56\xc4\x77\x76\x70\x2a\x2d\ -\xed\xc8\x89\xdd\xa5\x1e\x02\x00\x8b\x20\x24\xe5\xa3\xd1\x08\x6c\ -\x36\x1b\x34\x4d\x83\x90\x97\x9b\x07\x69\x51\x22\xb7\x58\x92\xb4\ -\xe6\xf0\x60\xdf\xd1\xa7\xd9\x8f\xf9\xf9\x05\xca\xbf\x94\x87\xae\ -\xce\x0e\x70\x57\xc5\x62\x8c\x4f\x4c\x82\xe3\xd8\x91\x55\x8f\x83\ -\xf6\xf6\x30\x3e\x3e\x01\xb1\xa4\x18\x00\xc0\x55\x55\x7a\x98\xae\ -\x69\x98\xfa\x34\x4d\x3c\x63\xb0\x08\xc2\x89\x4c\x7c\xf8\x48\x44\ -\x84\xea\xaa\x0a\x96\x78\xe5\x50\x58\x41\x65\xcd\x1d\xea\xeb\x1f\ -\x22\x73\x77\x17\xc7\xa1\xeb\x06\xfa\x07\x47\xe8\x46\x75\x1d\x85\ -\xc2\x4a\xe2\x13\xd9\x81\x99\x14\xe5\x37\x1e\xb6\x77\x10\xcf\x0b\ -\xa8\xab\xad\x81\xab\xb0\x90\x39\xd2\xed\x50\x55\x0d\x5f\xbe\x7e\ -\xa3\xd1\xd1\xb7\x30\x62\xdb\x78\xd4\xd1\xce\x32\x33\xcf\x24\xf4\ -\x60\xa9\x6e\x7c\x37\xf6\x9e\xa6\x66\xe6\xb0\xb0\xf0\x1d\x51\x55\ -\x85\xc3\x6e\x47\x41\x41\x3e\xdc\x62\x31\x2a\xca\xcb\xfe\xef\xc6\ -\x54\xc4\x6b\x65\x27\x1a\xeb\x80\x3f\x01\x3d\x8e\x8e\x2d\x19\xb0\ -\x3e\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x01\x95\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0\x77\x3d\xf8\ -\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xaf\xc8\x37\x05\x8a\xe9\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x01\x27\x49\x44\x41\x54\x78\xda\x62\ -\xfc\xff\xff\x3f\x03\x2d\x01\x40\x00\x31\x31\xd0\x18\x00\x04\x10\ -\x55\x2d\x60\x64\x64\xc4\x08\x0e\x80\x00\x62\xa2\xb6\xe1\xe8\x96\ -\x00\x04\x10\x13\xb5\x0c\x07\x02\x46\x6c\x3e\x01\x08\x20\x26\x6a\ -\x19\x8e\xcb\x12\x80\x00\x62\xa2\x96\xe1\xe8\x3e\x81\xd1\x00\x01\ -\xc4\x44\x2d\xc3\x61\x86\xa2\xfb\x04\x20\x80\x98\xa8\x61\xb8\x98\ -\x98\x08\x56\xc3\x41\x00\x20\x80\x40\x12\x70\x0c\x71\xc4\x7f\x06\ -\x7c\x18\xa6\x06\x44\x83\x30\xd0\xf0\xff\xf8\xf4\x02\x04\x10\x8a\ -\x46\x90\x62\x7c\x96\xc0\x0c\x25\xd6\x70\x10\x06\x08\x20\x26\xe4\ -\xf0\x43\xa6\xb1\x01\x74\xb9\x97\x2f\x5f\x63\x0f\x16\x24\x00\x10\ -\x40\x18\x36\x82\x5c\x05\x73\x19\xba\x38\x7a\xf0\x10\x13\xa4\x00\ -\x01\xc4\x88\xad\xb0\x13\x17\x17\xfd\x0f\x73\x21\xb6\xcc\x83\x33\ -\x42\xb1\x00\x80\x00\xc2\x9a\x8a\x60\x06\x63\x2b\x5b\x40\x29\x86\ -\x50\x50\x22\x03\x80\x00\x22\x98\x62\xb0\x05\x17\x29\x18\x20\x80\ -\x18\x09\xd5\x07\x30\x5f\x10\xed\x62\x34\x00\x10\x40\x4c\x44\xf8\ -\x10\x67\x70\x11\x03\x00\x02\x88\x89\xc8\x60\x24\xdb\x12\x80\x00\ -\x22\xba\xa8\x20\xd7\x12\x80\x00\x22\xa9\x2c\x22\xc7\x12\x80\x00\ -\x22\xb9\xb0\x43\xb7\x84\x90\x65\x00\x01\xc4\x48\x49\xab\x02\x3d\ -\xf3\x61\x53\x03\x10\x40\x14\x55\x38\xc4\x94\x5f\x00\x01\x06\x00\ -\xa0\xe5\x77\xf6\x3a\x06\xb5\x49\x00\x00\x00\x00\x49\x45\x4e\x44\ -\xae\x42\x60\x82\ -\x00\x00\x01\x66\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x18\x00\x00\x00\x18\x08\x03\x00\x00\x00\xd7\xa9\xcd\xca\ -\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xaf\xc8\x37\x05\x8a\xe9\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x00\x21\x50\x4c\x54\x45\x00\x00\xff\ -\x00\x00\x00\x0b\x0b\x0b\x0c\x0c\x0c\x09\x09\x09\x04\x04\x04\x03\ -\x03\x03\x0a\x0a\x0a\xf7\xf7\xf7\x02\x02\x02\xff\xff\xff\x9f\x74\ -\x49\x9e\x00\x00\x00\x0b\x74\x52\x4e\x53\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\x00\x4a\x4f\x01\xf2\x00\x00\x00\xb4\x49\x44\x41\ -\x54\x78\xda\x62\xe0\xc2\x01\x00\x02\x88\x01\x97\x04\x40\x00\x31\ -\x70\x31\xc2\x00\x2b\x8a\x04\x40\x00\x01\x25\x60\x4c\x26\x14\x09\ -\x80\x00\x42\x92\x60\xe3\x02\xe9\x86\xf1\x00\x02\x08\x49\x82\x1d\ -\x44\xc0\xa5\x00\x02\x08\xd3\x28\xa8\x14\x40\x00\x21\x49\x30\xc3\ -\x18\x8c\x1c\x40\x02\x20\x80\x90\x24\x58\xe0\x3a\x40\x7a\x00\x02\ -\x08\xc9\xb9\x9c\x5c\xc8\x66\x01\x04\x10\x36\x0f\x82\xcd\x00\x08\ -\x20\x9c\x3e\x07\x08\x20\x84\x04\x03\x1c\x80\xb9\x00\x01\xc4\x80\ -\xa9\x04\xc2\x00\x08\x20\x9c\x12\x00\x01\x84\x2a\x81\x64\x1c\x40\ -\x00\xa1\x49\x20\x38\x00\x01\x84\x53\x02\x20\x80\x70\x4a\x00\x04\ -\x10\x9a\x73\x11\x12\x00\x01\xc4\x80\xdd\x57\x0c\x5c\x00\x01\x84\ -\x53\x02\x20\x80\xd0\x24\x10\xce\x05\x08\x20\x9c\x61\x05\x10\x60\ -\x00\x25\xbb\x12\xb6\xba\xdc\xe3\x9d\x00\x00\x00\x00\x49\x45\x4e\ -\x44\xae\x42\x60\x82\ -\x00\x00\x02\x73\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0\x77\x3d\xf8\ -\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\ -\x01\x00\x9a\x9c\x18\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xb1\ -\x8e\x7c\xfb\x51\x93\x00\x00\x00\x20\x63\x48\x52\x4d\x00\x00\x7a\ -\x25\x00\x00\x80\x83\x00\x00\xf9\xff\x00\x00\x80\xe9\x00\x00\x75\ -\x30\x00\x00\xea\x60\x00\x00\x3a\x98\x00\x00\x17\x6f\x92\x5f\xc5\ -\x46\x00\x00\x01\xe9\x49\x44\x41\x54\x78\xda\x62\xfc\xff\xff\x3f\ -\x03\x2d\x01\x40\x00\x31\xd1\xca\x60\x46\x06\x46\xb0\xcb\x01\x02\ -\x88\x89\x56\x86\xff\x67\xf8\xcf\x08\x62\x03\x04\x10\x13\xa9\x1a\ -\x61\x2e\x23\xc6\x70\x10\x00\x08\x20\x92\x2c\x80\x69\xc4\x65\x09\ -\xba\xe1\x20\x00\x10\x40\x4c\x94\x86\x31\x3e\xc3\x41\x00\x20\x80\ -\x48\xb6\x00\xd9\x10\x98\x25\xb8\x0c\x07\x01\x80\x00\x22\xcb\x07\ -\xd8\x2c\xc1\x05\x00\x02\x88\x2a\xa9\x08\x68\x21\x4e\x39\x80\x00\ -\x62\xa2\xc0\x50\x46\x94\x28\xc0\x01\x00\x02\x88\x89\x0a\xae\x67\ -\xc4\x27\x0f\x10\x40\x2c\x60\xeb\x19\x19\x89\x2a\x2f\x80\xc5\x0a\ -\x23\x29\x86\x83\x00\x40\x00\xb1\xc0\x18\x6c\x8a\x46\x0c\x8c\x6c\ -\x1c\x0c\x4c\x2c\x1c\x0c\x8c\xec\x40\xcc\xca\x09\x14\xe4\x64\x60\ -\x02\xd2\x8c\xec\x9c\x0c\x9f\xb6\x4f\x02\x3b\x04\xdd\x12\x42\x00\ -\x20\x80\xe0\x41\xf4\xeb\xfe\x39\xa0\x05\x5c\x0c\x8c\x1c\x40\xcc\ -\xce\x0d\xc6\x4c\x20\xcc\x01\x61\x0b\x86\x35\x31\x90\xe2\x5b\x18\ -\x00\x08\x20\x26\x64\xaf\x33\xb1\x03\x0d\x67\xe3\x46\x58\x00\x34\ -\x9c\x01\x6a\x11\x03\x90\x2d\x9c\x3c\x8d\x64\x4b\x00\x02\x08\x25\ -\x92\xbf\x9d\xd9\x04\xf4\x01\xc2\x70\x38\xcd\xc1\x0d\xb1\x1c\x48\ -\x8b\x15\xac\x24\xc9\x12\x80\x00\x62\x42\x8f\x40\x0c\xc3\xe1\xc1\ -\xc5\x03\xb1\x04\x88\x25\x6a\x76\x13\x6d\x09\x40\x00\x61\x24\xd3\ -\xcf\x3b\x26\x33\x30\xb1\x41\x0d\x67\x83\x19\x0e\x31\x98\x01\x14\ -\x47\x50\x2c\xd9\x7e\x86\x28\x4b\x00\x02\x88\x09\x5b\x32\x64\x44\ -\x71\x39\x04\x83\x0c\x65\x82\x5b\x00\x4c\x6d\xac\x1c\x0c\x32\x13\ -\x6f\x11\xb4\x04\x20\x80\xb0\x66\x34\x46\x0e\x1e\xa4\x20\x82\x1a\ -\xca\x0e\x49\xae\x20\xc3\xc1\x98\x95\x9d\x81\x81\x85\x9d\x41\x6e\ -\xc6\x33\xbc\x96\x00\x04\x10\x23\x7a\x9d\x0c\x53\x28\x5a\xb8\x0a\ -\x62\x10\x0b\xcc\x40\x50\x1e\x61\x07\xf2\xd9\x80\x06\xb3\x41\x68\ -\x66\x16\x06\x46\x26\x48\x56\x7a\x94\x2a\x8a\x35\x33\x02\x04\x10\ -\x13\xae\xdc\x0a\x0a\x77\x50\x90\x80\x69\x56\x4e\x70\x90\x30\x00\ -\x5d\xcd\xc8\x06\xb1\x84\x91\x85\x95\x81\x91\x99\x15\xa8\x90\x09\ -\x8c\xe5\xe6\xbe\xc5\x1a\x44\x00\x01\xc4\x88\xad\x55\x41\x6a\x66\ -\xc2\x57\x9c\x00\x04\x10\x23\xad\x9b\x2d\x00\x01\x06\x00\x8a\xf7\ -\x70\x43\xea\x7f\x47\x43\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\ -\x60\x82\ -\x00\x00\x03\xae\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x18\x00\x00\x00\x18\x08\x03\x00\x00\x00\xd7\xa9\xcd\xca\ -\x00\x00\x03\x00\x50\x4c\x54\x45\x47\x47\x47\x2a\x2a\x2a\xd4\xd4\ -\xd4\x78\x78\x78\xa6\xa6\xa6\xe7\xe7\xe7\xc1\xc1\xc1\xcb\xcb\xcb\ -\x39\x39\x39\xf8\xf8\xf8\x30\x30\x30\x7b\x7b\x7b\x4b\x4b\x4b\x1e\ -\x1e\x1e\x1c\x1c\x1c\x21\x21\x21\xc0\xc0\xc0\xc5\xc5\xff\xda\xda\ -\xda\xf3\xf3\xf3\x78\x78\xff\x90\x90\xff\xf5\xf5\xf5\x99\x99\x99\ -\x12\x12\x12\x3e\x3e\x3e\x85\x85\x85\xfa\xfa\xfa\x38\x38\x38\xd9\ -\xd9\xd9\x32\x32\x32\x91\x91\xff\xcc\xcc\xcc\x4e\x4e\x4e\xea\xea\ -\xea\x68\x68\x68\x94\x94\x94\x61\x61\x61\x42\x42\x42\xa8\xa7\xff\ -\xe1\xe1\xe1\xfc\xfc\xfc\x63\x63\x63\xd8\xd8\xd8\x2f\x2f\x2f\x84\ -\x85\xff\x57\x57\x57\xe8\xe8\xe8\x3a\x3a\x3a\x67\x67\x67\x8d\x8d\ -\x8d\xdd\xde\xff\x24\x24\x24\x72\x72\x72\xbd\xbd\xbd\x44\x44\x44\ -\x43\x43\x43\xf2\xf2\xff\x5e\x5e\x5e\xf1\xf1\xf1\x5b\x5b\x5b\xd6\ -\xd6\xff\x73\x73\x73\xce\xce\xce\xcd\xcd\xcd\x77\x77\x77\xdb\xdb\ -\xff\x55\x55\x55\xe5\xe5\xe5\x3c\x3c\x3c\x90\x90\x90\x73\x73\xff\ -\x14\x14\x14\xf9\xf9\xff\x35\x35\x35\x49\x49\x49\xe2\xe2\xe2\x98\ -\x98\x98\xc4\xc4\xc4\xbe\xbe\xbe\x52\x52\x52\x50\x50\x50\xdf\xdf\ -\xdf\xfd\xfd\xfd\x8f\x8f\x8f\x8e\x8e\x8e\x94\x94\xff\x9c\x9c\xff\ -\x8a\x8a\x8a\xb8\xb8\xb8\x36\x36\x36\x9e\x9e\x9e\xf4\xf4\xf4\xb4\ -\xb4\xb4\xa9\xa9\xa9\x29\x29\x29\xed\xed\xff\xa0\xa0\xa0\xab\xab\ -\xab\xb6\xb6\xb6\x89\x89\x89\x0d\x0d\x0d\x4a\x4a\xff\x5a\x5a\x5a\ -\x73\x74\xff\x17\x17\x17\x05\x05\x05\xcf\xcf\xff\x31\x31\x31\x63\ -\x62\xff\xbf\xbf\xbf\x5d\x5d\xff\x9b\x9b\xff\x48\x48\x48\xe0\xe0\ -\xe0\x54\x54\x54\xc3\xc3\xff\x34\x34\x34\xdd\xdd\xff\x3d\x3d\x3d\ -\x16\x16\x16\xc6\xc6\xc6\x6d\x6d\x6d\xef\xef\xff\x4c\x4c\x4c\x81\ -\x81\x81\x65\x65\x65\xde\xde\xde\x01\x01\x01\x0a\x0a\x0a\xc5\xc5\ -\xc5\x95\x95\xff\xf7\xf7\xf7\xc9\xc9\xff\x8b\x8b\x8b\x49\x49\xff\ -\x86\x86\x86\xc9\xca\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\xb7\x9b\xbf\xd3\x00\x00\x00\x01\ -\x74\x52\x4e\x53\x00\x40\xe6\xd8\x66\x00\x00\x00\x01\x62\x4b\x47\ -\x44\x00\x88\x05\x1d\x48\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\ -\x0a\xf0\x00\x00\x0a\xf0\x01\x42\xac\x34\x98\x00\x00\x00\x07\x74\ -\x49\x4d\x45\x07\xdf\x0a\x05\x0d\x2f\x13\xcc\xa8\x06\x29\x00\x00\ -\x00\x27\x49\x44\x41\x54\x28\xcf\x63\x60\x18\x08\xd0\xdd\xdd\x8d\ -\x43\x82\xa1\x9b\x34\x1d\xed\x38\x6c\x68\x27\xd1\x45\xd4\x32\xa7\ -\x9b\xa6\xe2\xd4\x0b\x37\x5a\x03\x00\x18\x09\x0c\xf9\x25\xe3\xb8\ -\x9c\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x03\x14\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x09\x12\ -\x0d\x15\x36\xe3\x37\xca\x44\x00\x00\x02\x94\x49\x44\x41\x54\x38\ -\xcb\x7d\x93\xdb\x4b\x54\x51\x14\xc6\xbf\x7d\x2e\x60\x73\xb1\x40\ -\x48\xc1\x9e\xac\x7c\x48\x53\x64\xec\xc1\x1c\xbb\x80\xa8\x79\x65\ -\x52\x07\x2f\xa1\x3d\x28\xfa\x37\xf4\x20\xf8\x50\x50\x50\x0f\x3d\ -\x86\x41\x5a\x54\x68\x5e\x32\x2d\x45\xcd\xc8\x4b\xbe\x14\x69\x30\ -\x63\x07\x33\x66\x1c\x66\x26\xf1\x36\x33\x67\x8e\xe3\x1c\x71\xf5\ -\x90\xe7\xa0\x53\xb6\xe0\xc7\xda\xec\xcd\xfa\x60\xaf\xb5\x3e\x46\ -\x44\x38\x18\xdd\xbd\x83\x34\x3d\x33\x0b\x49\x92\xa0\x28\x0a\x0c\ -\x06\x03\x52\x53\x53\x61\xcd\xbd\x08\x7b\x65\x39\x43\x6c\x10\x11\ -\x88\x08\x2e\xf7\x0a\x6a\x1b\x9a\xe8\xce\xdd\x07\x24\x2d\xfd\x84\ -\xaa\xee\x82\x88\xa0\xaa\x2a\x16\xa5\x1f\xb8\x77\xff\x21\xd5\x35\ -\x34\x91\xcb\xbd\xa2\xd7\x10\x11\xf6\x8b\x3d\x28\x2a\xbd\x4e\xce\ -\xef\x4b\x87\x1e\x63\x71\x2c\x4a\x29\x85\x25\x36\x72\xb9\x3d\x87\ -\x05\xea\x1b\x9b\x49\x5a\x5a\x46\x24\xb2\x83\xf5\xcd\x00\xbc\xfe\ -\x35\xb8\x3d\x7e\xb8\x56\x7c\x70\x7b\xfc\xf0\xfa\xd7\xb0\xbe\x19\ -\x80\xf5\x4a\x01\x39\x9c\x52\x4a\x7d\x63\x33\x69\x02\x5c\xdf\xeb\ -\x61\x4a\x4b\x3b\x87\x13\xc7\x4f\xc0\xb7\xba\x81\xd5\xb5\x2d\x6c\ -\x6c\x85\x10\x94\xb7\x11\x0a\x47\x10\x94\xb7\xb1\xb1\x15\x42\x85\ -\xad\x9a\x9e\x76\x75\xb2\x84\x84\x84\xe5\xb3\x67\x4e\xa3\x6f\x60\ -\x88\x00\x80\x9b\x9d\x9d\xc3\x05\x8b\x05\x81\x50\x18\xd1\xa8\x0a\ -\x8e\x31\x88\x02\x0f\x51\xe0\xd1\xda\xda\x42\x5a\x7e\xdc\xd1\xc1\ -\xa2\x51\x15\x41\x59\x41\x76\xb6\x05\x33\x9f\xe6\x00\x00\x9c\xc3\ -\xe9\xc4\xc9\xc4\xc4\x16\x9e\xe7\x21\x8a\xc2\x21\x00\x40\x14\x05\ -\x74\x75\x3e\x61\xda\x1d\xcf\xf3\x48\x4e\x3e\xd5\xe3\x70\x38\x91\ -\x77\xb5\x10\x82\xa2\x28\x88\x8f\x37\x3f\x12\x04\x41\x9f\x4c\x6d\ -\xdd\x0d\x7d\xb6\x0d\x8d\x37\x09\x00\x5e\x3c\x7f\xa6\x8f\xd0\x6c\ -\x36\xd9\xc3\xe1\x30\x01\x80\x60\x34\x1a\x10\xdd\xd9\xc1\xb1\xb8\ -\x38\x5d\xe0\x55\xf7\x4b\x06\x00\x55\xf6\x1a\xd2\xce\x07\x23\x18\ -\x0c\xc0\x68\x34\x42\x96\x65\x70\xe9\x69\xe9\x70\x2e\x3a\x49\x14\ -\x79\x68\x54\xd9\x6b\x48\x14\xf9\xfd\x2f\xf0\x7f\x31\x3f\xbf\x40\ -\x19\xe7\xd3\x31\x35\x39\x0a\xee\x92\x35\x07\xe3\x13\x93\xe0\x38\ -\x06\x51\x14\x50\x61\xab\xa6\xb7\x43\xfd\x4c\x14\x05\x68\xf9\x20\ -\xb4\xb7\x87\xf1\xf1\x09\x58\x73\x73\xfe\x34\xb1\xa4\xb8\x90\x85\ -\x65\x19\x53\x1f\xa7\xa9\xb8\xd4\x46\x63\x23\x6f\x98\x28\x08\x38\ -\x8a\x89\xf7\x1f\x88\x88\x50\x56\x52\xc4\xf4\x55\xf6\xfa\xfc\x28\ -\x2e\xaf\xa2\x9e\xde\x01\x52\x77\x77\xf1\x2f\xc2\x61\x05\xbd\xfd\ -\x83\x74\xad\xac\x92\xbc\x3e\xbf\xbe\x89\x4c\x33\x93\xdf\xff\x0b\ -\xb7\xda\xda\x89\xe7\x05\x54\xda\xca\x61\xc9\xca\x62\xe6\x78\x13\ -\x42\x21\x19\x9f\xbf\x7c\xa5\xe1\xe1\x77\x50\x22\xdb\xb8\xdd\xde\ -\xc6\x92\x92\x12\xf5\x86\xb2\x58\x37\x8e\x8c\x8e\xd1\xd4\xcc\x1c\ -\x16\x16\xbe\x21\x18\x0a\xc1\x6c\x32\x21\x33\x33\x03\x79\xd6\x1c\ -\x14\x15\xe4\x1f\xed\xc6\x58\xac\x97\xf3\xff\x6b\x2c\x8d\xdf\x2b\ -\x52\xa0\xd7\xc2\xb7\xc3\x95\x00\x00\x00\x00\x49\x45\x4e\x44\xae\ -\x42\x60\x82\ -\x00\x00\x03\x7b\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x20\x00\x00\x00\x20\x08\x03\x00\x00\x00\x44\xa4\x8a\xc6\ -\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xaf\xc8\x37\x05\x8a\xe9\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x01\x2f\x50\x4c\x54\x45\xf6\xf6\xf6\ -\xfb\xfb\xfb\xf5\xf5\xf5\xfd\xfd\xfd\xa4\xa4\xa4\x4b\x4b\x4b\x8f\ -\x8f\x8f\x54\x54\x54\x55\x55\x55\xfc\xfc\xfc\x92\x92\xff\xf9\xf9\ -\xf9\x6f\x6f\x6f\x82\x82\x82\x9d\x9d\x9d\x66\x66\x66\xf1\xf1\xf1\ -\x6f\x70\xff\xe5\xe5\xe5\xee\xee\xee\xcc\xcc\xff\x5b\x5b\x5b\x77\ -\x77\x77\xac\xac\xac\xa0\xa0\xa0\x76\x76\x76\xc9\xc9\xc9\xc4\xc4\ -\xff\xfa\xfa\xfa\x2a\x2a\x2a\x9b\x9b\xff\xa6\xa6\xa6\x70\x70\xff\ -\x3d\x3d\x3d\x69\x69\x69\xab\xab\xab\xa8\xa8\xa8\xc2\xc2\xc2\xd5\ -\xd5\xd5\xd3\xd3\xd3\xc3\xc3\xff\xcf\xcf\xcf\x46\x46\xf4\x69\x69\ -\xff\xa5\xa5\xff\xdc\xdc\xe5\xd8\xd8\xff\x61\x61\x61\xb5\xb5\xb5\ -\xa2\xa2\xa5\x59\x59\x59\x81\x81\x81\x9a\x9b\xff\x7a\x7a\x7a\x6c\ -\x6c\x6c\x24\x24\x46\x97\x97\x97\x96\x96\x96\xc1\xc1\xcd\x81\x81\ -\xff\x22\x22\x22\x99\x99\x99\x30\x30\x85\x75\x75\x75\xcb\xcb\xcb\ -\x6a\x6a\x6a\xf7\xf7\xff\x47\x47\xff\x68\x68\x68\xcd\xcd\xcd\xae\ -\xae\xae\x72\x72\x72\x72\x72\xff\xed\xed\xed\x2a\x2a\xb9\xce\xce\ -\xce\xeb\xeb\xeb\x91\x91\x91\xc4\xc4\xfd\x95\x95\x95\xef\xef\xef\ -\xd4\xd4\xd4\xca\xca\xff\xd4\xd4\xd9\x8e\x8e\x8e\x6b\x6b\x6b\x26\ -\x26\x26\xf7\xf7\xf7\xa4\xa4\xf8\x27\x27\x27\x80\x80\xff\xe0\xe1\ -\xff\x98\x98\x98\xf8\xf8\xf8\xdd\xdd\xdd\x6f\x6f\xff\xe0\xe0\xff\ -\xa0\xa0\xef\x35\x35\x35\xc3\xc4\xff\xff\xff\xff\x1a\x9b\xc3\x1b\ -\x00\x00\x00\x65\x74\x52\x4e\x53\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x19\x68\xd2\ -\x83\x00\x00\x01\x61\x49\x44\x41\x54\x78\xda\x62\x48\x21\x00\x00\ -\x02\x88\x01\xc1\xe4\x36\xd6\xc4\xa2\x00\x20\x80\x90\x14\x30\xf0\ -\x47\x8a\xaa\x62\x28\x00\x08\x20\x06\x64\x4e\x9c\x64\x98\x22\x0b\ -\x42\x84\x19\x44\x00\x04\x10\x03\xaa\xfa\x00\xcb\x24\x59\x5e\x21\ -\x08\x9b\x91\x11\x44\x02\x04\x10\x03\x86\x99\x06\xac\x36\xfa\xc1\ -\x7e\xd6\x51\xd2\x10\x2e\x40\x00\x31\x60\x71\x97\xba\x92\xb9\x96\ -\x9c\x89\xb3\x1e\x98\x03\x10\x40\x0c\x58\xfd\x96\xc8\x95\x10\xcd\ -\x15\x01\x66\x02\x04\x10\x76\x05\x3a\x0a\x1a\xc9\x1e\x76\x3c\x6a\ -\x40\x26\x40\x00\x61\x57\xa0\xeb\x25\x18\xaf\x6d\xc5\xe1\x08\x64\ -\x02\x04\x10\x56\x05\x0c\x1c\x86\x22\x41\x4e\x29\x29\x9c\x40\x36\ -\x40\x00\x61\x53\xe0\x19\xe8\x8a\xe0\x00\x04\x10\x16\x05\xc2\xac\ -\x6e\x48\x3c\x80\x00\xc2\x54\xc0\xc4\xce\x87\xcc\x05\x08\x20\x4c\ -\x05\x02\xde\x28\x5c\x80\x00\x42\x57\xc0\x64\x24\x85\x2a\x00\x10\ -\x40\x68\x0a\x7c\xd8\xd9\xd0\x74\x00\x04\x10\x9a\x02\x07\x0b\x74\ -\x1b\x01\x02\x08\x45\x81\x80\x3c\xa6\x9f\x00\x02\x08\x59\x01\x13\ -\xbf\x32\xa6\x02\x80\x00\x42\x52\x10\xee\x22\x8e\x25\xd4\x00\x02\ -\x08\xa1\x20\x36\x54\x05\x5b\xb0\x03\x04\x10\x5c\x81\x8c\x19\x0b\ -\xd6\x78\x03\x08\x20\x98\x02\x46\x1e\x09\xec\xc9\x1e\x20\x80\xa0\ -\x0a\x38\xdd\xf9\x70\xe4\x0b\x80\x00\x82\x28\x60\xb6\x8f\xc1\x95\ -\x71\x00\x02\x08\xa2\x40\xcc\x1f\x67\xce\x02\x08\x20\xb0\x02\x53\ -\x5f\xdc\x59\x0f\x20\x80\x80\x0a\xb8\x79\x43\xf0\xe4\x4d\x80\x00\ -\x02\x2a\xb0\x65\xc3\x97\x79\x01\x02\x88\x81\x50\xee\x06\x08\x20\ -\x82\x0a\x00\x02\x0c\x00\x6b\x91\x75\x11\x67\x7f\xfe\xc5\x00\x00\ -\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x01\x40\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0\x77\x3d\xf8\ -\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xaf\xc8\x37\x05\x8a\xe9\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x00\xd2\x49\x44\x41\x54\x78\xda\x62\ -\x64\x60\xf8\xff\x9f\x81\x86\x00\x20\x80\x58\x40\x04\xd0\x0a\x46\ -\x5a\x18\xce\xc8\xc8\xf0\x1f\x20\x80\x98\x18\x68\x0c\x00\x02\x88\ -\xe6\x16\x00\x04\x10\x0b\xe5\xc1\xc0\x88\x12\x87\xff\xff\xff\x47\ -\x09\x6e\x80\x00\xa2\xd8\x07\xe8\x06\xa2\x03\x80\x00\x62\xa2\x86\ -\x0f\xf0\x59\x02\x10\x40\x4c\xd4\x08\x1e\x7c\x96\x00\x04\x10\x13\ -\x35\xc2\x1e\x66\x38\x36\x4b\x00\x02\x88\x89\x5a\x86\xe3\x02\x00\ -\x01\xc4\x44\x4b\xc3\x41\x00\x20\x80\x98\x68\x95\x7a\x60\x00\x20\ -\x80\x58\x68\x65\x30\x0c\x00\x04\x10\x48\x31\xce\x8c\x42\x28\x13\ -\x11\x53\x16\x01\x04\x10\x0b\x3e\x8d\xf8\x2c\x23\x16\x00\x04\x10\ -\xcd\xcb\x22\x80\x00\xa2\xb9\x05\x00\x01\xc4\x44\xab\xc8\x85\x01\ -\x80\x00\xa2\xb9\x0f\x00\x02\x88\xe6\x16\x00\x04\x10\x13\x39\x39\ -\x98\x14\x00\x10\x40\x34\xf7\x01\x40\x00\xd1\xdc\x02\x80\x00\xc2\ -\xc8\xc9\x04\xd2\x12\xc9\x16\x00\x04\x18\x00\xde\x77\x40\x28\x09\ -\x03\x1c\x0a\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x03\x5b\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x20\x00\x00\x00\x20\x08\x03\x00\x00\x00\x44\xa4\x8a\xc6\ -\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xaf\xc8\x37\x05\x8a\xe9\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x01\x2f\x50\x4c\x54\x45\x8a\x8a\x8a\ -\xb2\xb2\xb2\xdf\xdf\xff\x1d\x1d\x1d\x0f\x0f\x0f\x27\x27\x27\x13\ -\x13\x14\x61\x61\x61\x11\x11\x11\x25\x25\x25\xa6\xa6\xff\x9d\x9d\ -\x9d\x80\x80\xff\xea\xea\xea\x1b\x1b\x1b\x9c\x9c\x9c\x23\x23\x23\ -\xc4\xc4\xff\xb3\xb3\xb3\xfc\xfc\xfc\x99\x99\x99\x9b\x9b\x9b\x75\ -\x75\x75\xbc\xbc\xbc\x72\x72\x72\x69\x69\x69\xfa\xfa\xff\xbe\xbe\ -\xff\x84\x84\x84\x49\x49\xff\x45\x45\x45\xd4\xd4\xd4\xf4\xf4\xf4\ -\x87\x87\x87\x70\x70\xff\x59\x59\x59\xc4\xc4\xc4\xec\xec\xec\xf0\ -\xf0\xf0\xb7\xb7\xb7\xbe\xbe\xf3\xef\xef\xef\x8c\x8c\xa4\x4f\x4f\ -\x4f\x40\x40\xa5\x49\x49\xb8\x57\x57\x57\xa5\xa5\xa5\x7f\x7f\xff\ -\xb4\xb4\xff\x0f\x0f\x23\x15\x15\x15\x12\x12\x0d\x14\x14\x14\x2c\ -\x2c\x2c\x6c\x6c\x6c\xc1\xc1\xc1\x3b\x3b\x3b\x5d\x5d\xff\x67\x67\ -\xff\x47\x47\x47\xfd\xfd\xfd\x2a\x2a\x2a\x44\x44\xff\xed\xed\xff\ -\xef\xef\xff\x5f\x5f\x5f\xde\xde\xde\xf6\xf6\xf6\xf8\xf8\xf8\x34\ -\x34\x34\xaa\xaa\xff\x52\x52\xff\x88\x89\xff\x84\x84\xa8\x02\x02\ -\x1a\xd6\xd6\xff\xd2\xd2\xd2\xa5\xa5\xff\xe0\xe0\xe0\x4d\x4d\x4d\ -\xad\xad\xd5\x74\x74\xff\x3d\x3d\x3d\x56\x56\xff\xae\xae\xba\xae\ -\xae\xae\x1f\x1f\x1f\x5b\x5b\xcd\xd8\xd8\xd8\x49\x49\x49\xc5\xc5\ -\xff\xd2\xd2\xff\xf9\xf9\xf9\x36\x36\x36\xe4\xe4\xe4\xe5\xe5\xe5\ -\xdd\xdd\xff\x57\x57\x9e\x2a\x2a\x98\xff\xff\xff\xe8\xad\xe0\xd0\ -\x00\x00\x00\x65\x74\x52\x4e\x53\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x19\x68\xd2\ -\x83\x00\x00\x01\x41\x49\x44\x41\x54\x78\xda\x62\x48\x21\x00\x00\ -\x02\x88\x81\x90\x02\x80\x00\x22\xa8\x00\x20\x80\x08\x2a\x00\x08\ -\x20\x82\x0a\x00\x02\x88\xa0\x02\x80\x00\x62\x48\xd1\x57\xc7\xab\ -\x00\x20\x80\x18\x52\x44\x98\x19\xf1\x29\x00\x08\x20\xa0\x15\x42\ -\x2c\xdc\x78\x14\x00\x04\x10\xc8\x0d\x16\xa6\x78\x5c\x02\x10\x40\ -\x60\x29\x5f\x3e\x31\x9c\x0a\x00\x02\x08\xa2\xd7\x99\x93\x1d\x97\ -\x02\x80\x00\x82\x1a\xce\xeb\xa6\x1d\x8b\x5d\x01\x40\x00\xc1\x6c\ -\x57\x90\x0b\x56\xc3\xaa\x00\x20\x80\xe0\xce\x13\xd6\x33\x4b\x48\ -\x71\x74\xe7\x62\x42\x53\x00\x10\x40\x48\xee\x97\x64\x0d\x0c\x09\ -\xe2\x91\x95\x46\x55\x00\x10\x40\xc8\x1e\x94\xf1\x8e\x10\x8c\x36\ -\xb0\x72\x40\x51\x00\x10\x40\x28\x21\x90\xc4\x16\x2a\x65\xa8\x94\ -\x88\xa2\x00\x20\x80\x50\x14\x58\xeb\x98\x78\x79\xda\xa3\xba\x02\ -\x20\x80\x50\x14\xc4\x78\xe8\x1a\x25\x6b\xa0\xba\x01\x20\x80\x50\ -\x03\xd9\xc7\x4f\x4b\x00\x2d\xd8\x01\x02\x08\x23\x16\x22\xc3\xcd\ -\x51\xf8\x00\x01\x84\x19\x4d\xf1\x76\xca\xb6\x48\x5c\x80\x00\xc2\ -\x12\x8f\x9a\x96\x36\x2e\x08\x1e\x40\x00\x61\x8b\x68\xd7\x80\x38\ -\x55\x38\x07\x20\x80\xb0\xa7\x04\x27\x56\x7f\x18\x13\x20\x80\x70\ -\x24\x15\x09\x66\x79\x28\x0b\x20\x80\x70\xa5\x25\x45\x63\x15\x08\ -\x03\x20\x80\x70\x26\x36\x51\x16\x48\x5a\x06\x08\x20\xdc\xa9\x31\ -\x8c\x83\x1f\x44\x01\x04\x10\x9e\xe4\x2a\x1e\x05\x22\x01\x02\x88\ -\x60\xce\x02\x08\x30\x00\x89\x8e\x7a\x0d\x9d\xde\x9c\x85\x00\x00\ -\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x01\x91\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0\x77\x3d\xf8\ -\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xaf\xc8\x37\x05\x8a\xe9\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x01\x23\x49\x44\x41\x54\x78\xda\x62\ -\xfc\xff\xff\x3f\x03\x35\x01\x23\x23\x23\xd0\xc8\xff\x8c\x30\x3e\ -\x40\x00\x31\x51\xdb\x70\x64\x1a\x04\x00\x02\x88\x89\x56\x2e\x87\ -\x59\x02\x10\x40\x4c\xd4\x34\x1c\x9b\x25\x00\x01\xc4\x44\x4d\xc3\ -\xd1\x7d\x02\xa2\x01\x02\x88\x05\x2a\x08\x53\x8f\x1e\xe3\x8c\xe8\ -\x06\x22\x27\x0a\x74\xc3\x61\x86\x22\x5b\x02\x10\x40\xf8\x7c\xc0\ -\x88\xc5\x42\x9c\x86\x8b\x89\x89\x30\x60\x0b\x26\x80\x00\x62\xc0\ -\x91\x4c\xff\xe3\xf0\x11\x4c\xfd\x7f\x18\x0d\xc2\x40\xc3\xff\x23\ -\x8b\x23\x63\x80\x00\x22\xd9\x02\x98\xa1\xc4\x18\x0e\xc2\x00\x01\ -\xc4\x08\x26\x18\x19\xb1\x19\xc2\x88\x44\xa3\xf8\x00\x5f\x98\xa3\ -\x03\x80\x00\x22\x29\x15\x81\x5c\x8b\x2d\x29\xe2\x32\x1c\x04\x00\ -\x02\x08\x9b\x0f\xd0\x5d\x8d\xcc\x27\xda\xe5\x30\x00\x10\x40\x64\ -\xe5\x03\x50\x8a\x81\x59\x42\x48\x2d\x40\x00\xb1\x90\x9a\xaf\x48\ -\x2d\x1c\x01\x02\x88\xaa\x85\x1d\x36\x00\x10\x40\x4c\x38\x92\x27\ -\xd1\x19\x8e\x10\x00\x08\x20\x5c\x41\x44\xb5\x4a\x02\x20\x80\x68\ -\x1e\x44\x00\x01\xc4\x82\x23\x48\xa8\xe6\x23\x80\x00\x22\xc5\x07\ -\x64\x59\x02\x10\x40\x4c\x78\x32\x18\x21\x5f\x11\x05\x00\x02\x88\ -\x09\x4b\xe1\xf6\x1f\x47\xe1\x46\x96\x2f\x00\x02\x88\xe6\x91\x0c\ -\x10\x40\x8c\xd4\x6e\xb6\xa0\x03\x80\x00\x03\x00\x36\x39\xb8\x24\ -\xe4\xe5\xe8\xbd\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\ -\x00\x00\x01\xf2\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ -\x00\x00\x00\x06\x62\x4b\x47\x44\x00\x00\x00\x00\x00\x00\xf9\x43\ -\xbb\x7f\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\ -\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\ -\xd5\x0a\x1a\x14\x08\x2f\x83\x14\x34\x9b\x00\x00\x01\x7f\x49\x44\ -\x41\x54\x38\xcb\x95\x93\x4b\x4b\x02\x51\x18\x86\xdf\x23\x33\x63\ -\xda\x80\xfd\x0a\xcf\x08\xdd\x16\xfd\x8f\x08\x5a\xb6\x88\xa0\x75\ -\x17\xf0\x42\xb6\xb0\x45\x91\x76\xdb\x44\x44\x44\x1b\xb1\x36\x05\ -\xfe\x04\x31\xb3\x36\x81\x65\x30\xa3\xbf\xa0\x9a\x30\x05\x37\xcd\ -\xed\xb4\x69\x74\x46\x47\xb1\x6f\xf5\x9d\xc3\x79\x1f\xde\xf7\xe3\ -\x3b\xe4\xe8\x24\x33\xaf\xeb\xfa\x2c\x46\x28\xc1\xcf\xbd\x6d\xae\ -\xc5\xef\x9c\x77\x9c\x61\x18\xd3\xf1\xe8\xd6\x28\x7a\xa4\x0f\xf6\ -\x26\x01\xb8\x01\x76\xd3\x6a\x35\xc1\x18\xeb\x13\x11\x42\x00\x00\ -\xa1\xd0\x84\x27\x94\x73\x1e\x6a\xb5\x1a\x5e\xab\x95\x85\xc6\x77\ -\x63\x66\x90\x8b\xfd\xcc\x6e\xca\xee\x79\x9e\xbf\x75\x01\x24\x49\ -\x42\xb1\x54\x98\xfa\x47\xa4\x45\x17\x40\x51\x94\x4e\x8c\x56\xab\ -\x39\x54\x6c\x47\x72\x01\x28\xa5\x28\x96\x0a\x9d\xb3\x2c\xcb\x9e\ -\xe2\x48\x24\xd2\x3f\x03\xc6\x18\x64\x59\x76\x0d\xd2\xf9\x70\x50\ -\xb9\x00\x94\x52\x94\xca\xc5\xa1\x0e\x7a\xa1\x9c\x2d\xb6\x2c\x06\ -\x45\x19\xcd\x41\x30\x38\x8e\x76\xbb\xdd\x05\xe8\xba\x0e\xc6\x18\ -\xc2\x61\x8a\xf2\x53\x69\xa8\x03\x4a\x25\x98\xa6\x81\xca\x4b\xa5\ -\x0b\xd0\xb4\x1f\x04\x02\x63\xb0\x2c\x36\xd4\xee\xe5\xd5\x05\x1e\ -\x1e\xef\xdd\x11\x04\x9e\xaf\x9e\x9d\x9f\xba\x16\x87\x31\xcb\xd3\ -\xb6\x28\x8a\x58\x59\x5e\x85\x65\x99\x50\xbf\x54\xe4\xae\xb3\xe0\ -\x36\xd6\xa3\x79\x00\x79\xc7\x72\x6c\x1b\x86\xe1\xeb\x15\x3b\x4b\ -\x55\x55\xe4\x6e\xb2\x00\xf0\xec\xf3\x1a\x92\xa6\xe9\xf0\xfb\xfd\ -\x10\x04\x01\xa2\x28\x82\x10\x82\x7a\xbd\x0e\x80\xe0\xe3\xf3\xdd\ -\x16\xa7\x12\xb1\xe4\x1c\xe9\x15\x1f\x1e\xa7\xb3\xa6\x69\x2e\x79\ -\x7d\xac\xbf\x2a\x00\x28\x24\x62\xc9\x1d\x00\xf8\x05\x42\x59\xa6\ -\x03\x15\x88\xf7\xd8\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\ -\x82\ -\x00\x00\x04\x70\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x20\x00\x00\x00\x20\x08\x03\x00\x00\x00\x44\xa4\x8a\xc6\ -\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xaf\xc8\x37\x05\x8a\xe9\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x01\xa1\x50\x4c\x54\x45\x47\x47\x47\ -\x2a\x2a\x2a\xd4\xd4\xd4\x78\x78\x78\xa6\xa6\xa6\xe7\xe7\xe7\xc1\ -\xc1\xc1\xcb\xcb\xcb\x39\x39\x39\xf8\xf8\xf8\x30\x30\x30\x7b\x7b\ -\x7b\x4b\x4b\x4b\x1e\x1e\x1e\x1c\x1c\x1c\x21\x21\x21\xc0\xc0\xc0\ -\xc5\xc5\xff\xda\xda\xda\xf3\xf3\xf3\x78\x78\xff\x90\x90\xff\xf5\ -\xf5\xf5\x99\x99\x99\x12\x12\x12\x3e\x3e\x3e\x85\x85\x85\xfa\xfa\ -\xfa\x38\x38\x38\xd9\xd9\xd9\x32\x32\x32\x91\x91\xff\xcc\xcc\xcc\ -\x4e\x4e\x4e\xea\xea\xea\x68\x68\x68\x94\x94\x94\x61\x61\x61\x42\ -\x42\x42\xa8\xa7\xff\xe1\xe1\xe1\xfc\xfc\xfc\x63\x63\x63\xd8\xd8\ -\xd8\x2f\x2f\x2f\x84\x85\xff\x57\x57\x57\xe8\xe8\xe8\x3a\x3a\x3a\ -\x67\x67\x67\x8d\x8d\x8d\xdd\xde\xff\x24\x24\x24\x72\x72\x72\xbd\ -\xbd\xbd\x44\x44\x44\x43\x43\x43\xf2\xf2\xff\x5e\x5e\x5e\xf1\xf1\ -\xf1\x5b\x5b\x5b\xd6\xd6\xff\x73\x73\x73\xce\xce\xce\xcd\xcd\xcd\ -\x77\x77\x77\xdb\xdb\xff\x55\x55\x55\xe5\xe5\xe5\x3c\x3c\x3c\x90\ -\x90\x90\x73\x73\xff\x14\x14\x14\xf9\xf9\xff\x35\x35\x35\x49\x49\ -\x49\xe2\xe2\xe2\x98\x98\x98\xc4\xc4\xc4\xbe\xbe\xbe\x52\x52\x52\ -\x50\x50\x50\xdf\xdf\xdf\xfd\xfd\xfd\x8f\x8f\x8f\x8e\x8e\x8e\x94\ -\x94\xff\x9c\x9c\xff\x8a\x8a\x8a\xb8\xb8\xb8\x36\x36\x36\x9e\x9e\ -\x9e\xf4\xf4\xf4\xb4\xb4\xb4\xa9\xa9\xa9\x29\x29\x29\xed\xed\xff\ -\xa0\xa0\xa0\xab\xab\xab\xb6\xb6\xb6\x89\x89\x89\x0d\x0d\x0d\x4a\ -\x4a\xff\x5a\x5a\x5a\x73\x74\xff\x17\x17\x17\x05\x05\x05\xcf\xcf\ -\xff\x31\x31\x31\x63\x62\xff\xbf\xbf\xbf\x5d\x5d\xff\x9b\x9b\xff\ -\x48\x48\x48\xe0\xe0\xe0\x54\x54\x54\xc3\xc3\xff\x34\x34\x34\xdd\ -\xdd\xff\x3d\x3d\x3d\x16\x16\x16\xc6\xc6\xc6\x6d\x6d\x6d\xef\xef\ -\xff\x4c\x4c\x4c\x81\x81\x81\x65\x65\x65\xde\xde\xde\x01\x01\x01\ -\x0a\x0a\x0a\xc5\xc5\xc5\x95\x95\xff\xf7\xf7\xf7\xc9\xc9\xff\x8b\ -\x8b\x8b\x49\x49\xff\x86\x86\x86\xc9\xca\xff\xff\xff\xff\x75\x55\ -\xd2\x0b\x00\x00\x00\x8b\x74\x52\x4e\x53\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\x00\x85\xc3\x15\xef\x00\x00\x01\xbe\x49\x44\x41\ -\x54\x78\xda\x62\xe8\x22\x00\x00\x02\x88\x81\x90\x02\x80\x00\x22\ -\xa8\x00\x20\x80\x50\x14\x30\x31\x97\x7b\x34\xa6\xca\x69\x45\x22\ -\x89\x01\x04\x10\x92\x02\x6d\x55\x57\xe5\x44\x01\x36\x36\x7f\x29\ -\x06\xc5\x24\xb8\x28\x40\x00\x21\x14\xd4\xe6\xa8\x08\xc3\xd8\x66\ -\x0c\xce\xfa\x50\x26\x40\x00\xc1\x14\x68\x16\x33\x14\x21\xdb\xd6\ -\x26\x27\x0b\x61\x00\x04\x10\x4c\x81\x9e\xa4\x34\xaa\xe3\xd8\xd4\ -\x5c\xc0\x34\x40\x00\x41\x15\x18\x71\xb4\xa0\x3b\x5f\x1c\x22\x05\ -\x10\x40\x10\xb2\x9e\xdf\x07\xd3\x83\xaa\xe2\x20\x12\x20\x80\x20\ -\x0a\x0c\x53\xb0\x84\x80\x6c\x94\x18\x90\x04\x08\x20\xb0\x02\x0d\ -\x0e\x6b\x6c\x61\x64\xc3\x02\x24\x00\x02\x08\xac\xa0\xc3\x0a\x6b\ -\x20\xb2\xa4\x03\x09\x80\x00\x02\x2b\x90\x74\x83\x0b\x3a\x85\xab\ -\x5b\xc2\xd8\x4a\x3c\x40\x02\x20\x80\xc0\x0a\x78\xd9\x61\x62\xb6\ -\x69\xa2\x22\xb9\x9e\x30\x9e\x37\x10\x03\x04\x10\x58\x41\x16\x5c\ -\x41\x98\xbc\x71\x6b\x46\x36\x8c\x67\x00\xc4\x00\x01\x04\x56\xd0\ -\x00\x57\xa0\x2b\x5f\xd6\xe9\x5e\x02\xe3\xc9\x00\x31\x40\x00\x81\ -\x15\x48\x28\xc0\x84\x04\xdb\x45\x45\xf2\x13\x60\x3c\x35\x20\x06\ -\x08\x20\xb0\x02\xc6\x50\xb8\x23\x05\x0b\x9a\xab\x61\x6c\x4e\x45\ -\x20\x01\x10\x40\x60\x05\x75\xa6\x58\xbd\x29\x00\xf2\x05\x40\x00\ -\x81\x15\x54\xea\x08\x63\x53\x60\x27\x05\x24\x00\x02\x08\x12\xd4\ -\xe6\x2c\x58\xe4\xc5\xbc\x82\x80\x24\x40\x00\x41\x14\x24\xf3\x4a\ -\x63\x2a\x70\x54\x06\x91\x00\x01\x04\x8d\xee\x40\x66\x0c\xf9\x26\ -\x13\x56\x10\x05\x10\x40\x50\x05\x4a\x7c\x11\x68\xf2\xf6\x12\xb1\ -\x60\x1a\x20\x80\x60\x29\x8a\xa9\x22\x04\x45\xde\x21\xd3\x17\xc2\ -\x00\x08\x20\x78\xa2\x15\xe2\xb0\xf0\x83\x4b\xc7\x54\xf1\xc7\x41\ -\x99\x00\x01\x84\x48\xd5\x9c\xdc\x7c\x35\xd1\x4c\xc1\x5d\x5d\xac\ -\x79\xcc\x8c\x01\x42\x30\x61\x80\x00\x42\xce\x38\xac\x2a\x85\xf1\ -\xa5\x8c\x5c\x5c\x32\xdc\x0a\x08\x41\x80\x00\x22\x98\xf5\x00\x02\ -\x88\xa0\x02\x80\x00\x22\xa8\x00\x20\xc0\x00\x83\xa0\xeb\x38\x90\ -\x6f\x1b\x82\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x08\xb7\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x20\x00\x00\x00\x20\x08\x06\x00\x00\x00\x73\x7a\x7a\xf4\ -\x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ -\x00\x00\x08\x6e\x49\x44\x41\x54\x58\x85\xc5\x97\x7b\x70\x55\xd5\ -\x15\xc6\x7f\x7b\x9f\x73\x6e\x6e\x6e\xde\xb9\x97\x20\x84\x47\x5e\ -\x04\x12\x02\x11\x05\x05\x22\x05\x44\x0a\xa2\x28\xd6\xe2\x4c\xa7\ -\x75\xa8\x76\xac\xa3\x33\x3a\x63\x67\xb4\xd3\x87\x6d\x1c\x2b\x7d\ -\x4d\xff\x68\xcb\xd8\x56\x1d\x29\x76\xaa\x45\xc6\xe7\x0c\xa1\xa9\ -\xa0\x11\x5a\x01\x8d\x41\x1e\x01\xcc\x83\x24\x4a\x42\x42\x72\xf3\ -\xbc\xaf\xdc\xdc\xb3\x77\xff\x38\xe7\xde\x24\xd5\x19\x69\xa7\x33\ -\xee\x99\x35\xe7\xdc\x39\x77\xaf\xf5\x9d\x6f\x7d\x6b\xad\xb3\xe1\ -\x4b\x5e\xe2\x4a\xff\xb8\x6e\x5d\xad\xd9\x91\x61\xad\x11\x88\xed\ -\x02\xb1\x46\x43\x00\xc8\x71\x1f\x8f\x08\x18\x10\xe8\x23\x36\x7a\ -\x5f\x71\x78\xe2\x48\x43\x43\x6d\xe2\xff\x02\xa0\x68\x5b\x6d\xae\ -\x18\x4f\xfb\x31\x82\xfb\xca\xe6\xcd\xe0\xc6\x15\xa5\x59\x2b\xaa\ -\xe6\x8a\xab\x02\x59\xf8\xb3\xd3\x01\x18\x1c\x8d\xd2\x1b\x1c\xa3\ -\xb1\xf9\xa2\x3e\xf4\x41\xfb\x58\x6b\xe7\x65\xd0\x3c\xab\xd3\xc6\ -\x7f\xd6\xf9\x7a\xed\xf0\xff\x0c\xa0\x68\xf3\xce\x87\xa5\x21\x9e\ -\xdc\x5c\xb3\x28\xfd\xfe\xaf\xaf\xb4\xca\xe7\x05\xae\x88\xb3\xb6\ -\x4f\x83\x3c\xf3\xca\xf1\x89\xfd\x87\xcf\x46\x95\x96\x8f\x77\xd6\ -\x7d\xff\x77\xff\x15\x80\xa2\x75\xb5\x5e\xed\xf3\xfe\x75\x71\xe9\ -\xcc\x4d\x4f\x3e\xf8\xd5\x8c\xca\xe2\x02\x10\x8e\xe3\x7f\x9d\xfc\ -\x84\x13\x1f\x5f\x22\x38\x1a\x61\x70\x24\x86\x10\x10\xc8\x4d\xc7\ -\x9f\x93\xc1\xb2\xf2\x59\xac\x5e\x3a\x97\x92\x39\xf9\x00\x7c\xdc\ -\xd9\xcf\xe3\x4f\xbf\x15\x3e\xdd\xd6\x5b\x4f\x38\xfa\xcd\xce\x86\ -\xda\xd8\x17\x02\x58\xbc\xbd\xd6\x13\x0e\xa7\xbd\xbf\x6d\x7d\x55\ -\xc5\x4f\xee\xdf\xe0\x49\xf7\x58\xb4\x7e\x1a\xe4\xb9\x37\x1a\x69\ -\x3c\xdb\x8d\x34\x24\x86\x94\x08\x21\x90\x52\x20\x10\x29\x2f\x5a\ -\x6b\xb4\xd6\x2c\xaf\x28\x64\xc7\x2d\x57\x53\x3a\x27\x9f\x58\x3c\ -\xc1\x53\xcf\xbe\x1d\x7f\xe5\xd0\xe9\x73\x3e\x5f\xec\xba\xe6\x7d\ -\xb5\xf1\xa9\xf1\x8c\xe9\xe1\xb5\xc8\x2c\x3e\xf6\xea\xb6\x1b\xab\ -\x56\xee\x7c\x68\x53\x9a\x65\x1a\xbc\xde\x70\x96\x27\x9e\x7b\x9b\ -\x4b\x03\x21\x4c\xd3\xc0\x9a\x62\xa6\x61\x60\x9a\x06\x86\x29\x31\ -\x0c\x89\x69\x18\x48\x43\x72\x29\x18\xe2\xad\xe3\xed\x64\xf9\xd2\ -\xa8\x2c\x9e\xc1\x8d\x2b\x4a\x8d\xbe\xa1\x70\xde\xc9\x96\x81\x6b\ -\x86\x5b\x0e\xee\x85\x27\x3e\x1f\x40\xf1\x16\xef\xa3\xab\xae\x2e\ -\xba\xf7\x37\x8f\xdc\x92\x6e\x18\x92\xdf\xef\x3d\xca\x9e\xfd\x1f\ -\x61\x18\x06\xa6\x69\x62\x59\x6e\x70\xcb\xf9\x6d\x9a\xc6\xa4\x49\ -\x03\xc3\x65\xc7\x30\x24\x00\x8d\xe7\x7b\x08\x45\xe2\x5c\x5b\x31\ -\x9b\x35\xcb\x8a\xcd\x33\xed\x7d\x73\x43\xd9\xfb\xe3\x43\x2d\x07\ -\xdf\xfb\x4c\x0a\x0a\xef\xd8\xe9\xcf\xd4\x56\xcb\x9b\xbf\xfd\x76\ -\xfe\xbc\x99\xb9\xbc\x54\x7f\x92\xe7\xde\xf8\x10\xd3\x32\xa7\xbc\ -\xad\x64\x69\xd9\x0c\x96\x94\x14\x50\x51\x14\xa0\x20\xcf\x47\x6e\ -\x56\x3a\xe3\xf1\x04\x7d\x83\x61\x9a\x5a\xfa\xa8\x3f\xde\x41\x28\ -\x1a\xc7\x56\x0a\xdb\x56\x24\x6c\xc5\xdd\x9b\x97\x72\xe7\xfa\x0a\ -\x2e\x5e\x1e\x65\xeb\xc3\xbb\x07\x47\x89\x97\x77\xbf\xf6\xc3\xe0\ -\x34\x06\xfc\xa5\x37\xed\xba\xe7\xf6\xe5\xd7\xdf\xbc\x7a\xa1\x3c\ -\x73\xa1\x8f\x9f\xef\x39\x8c\x69\x18\x58\x2e\x00\xcb\x72\xee\x7f\ -\xfd\xc0\x57\x58\x34\xdf\xcf\x8c\x5c\x1f\x3e\xaf\x07\xc3\x90\xa4\ -\x79\x4c\xf2\xb2\xbc\x2c\x9a\x9f\xcf\xaa\xaa\xd9\x9c\x6a\xef\x27\ -\x16\xb7\x1d\x9d\x00\xa7\xda\xfb\xa8\x2e\x9b\x49\x49\x61\x1e\x13\ -\xb6\xf2\x9c\x38\xdd\x5d\x30\xd4\x72\xf0\x4d\x00\x09\xb0\x78\x5d\ -\x6d\xa6\x65\xca\xbb\xee\xb9\x6d\x85\xa1\xd1\x3c\xbd\xef\x38\x52\ -\xc8\x69\x14\x3b\x40\x4c\x84\x10\x08\xe1\x10\x17\x4f\x28\xe2\x09\ -\x85\x94\x12\xe9\x0a\x33\x3f\xcb\xcb\xf6\xf5\x0b\xb1\x2c\x13\x8f\ -\x65\x62\x59\x26\x86\x34\xd8\xbd\xff\x23\x34\x70\xf7\x96\x6b\x0c\ -\xd3\x34\xee\x5a\xbc\xae\x36\x13\xc0\x04\x88\xf8\xbc\x5b\xd6\x5e\ -\x5d\x2c\xf3\xb3\xd3\x39\xd5\xd6\x4b\xcb\x27\x41\x3c\x1e\x73\x8a\ -\xe8\xdc\x34\x98\x06\x09\x5b\x71\xfc\x7c\x3f\xef\x35\xf7\xd2\x3b\ -\x14\xc1\x10\x82\x0d\xcb\xe7\x73\x7b\x4d\x69\x0a\x5c\xe5\x7c\x3f\ -\x1e\xcb\x24\x21\x6d\x5c\xac\x74\x74\x0f\x73\xae\x73\x80\xca\xa2\ -\x00\x6b\xaf\x2d\x91\x07\x8f\xb6\x6e\x01\x5e\x96\x8e\xf6\xf5\x8e\ -\x9b\xae\x5b\xe0\x43\xc0\x91\x8f\xba\x1c\x31\x19\x4e\xde\x0d\x37\ -\xf7\x49\x30\xbf\x78\xe9\x04\xaf\xfd\xf3\x02\x03\xa3\xe3\xa4\x79\ -\x2c\x2c\x8f\xc5\xd9\xae\x41\xb4\xd6\x29\x31\x4b\x49\x8a\xb1\xa9\ -\x2c\xbe\xdf\xdc\x0d\xc0\xc6\x95\xe5\x3e\x21\xc5\x8e\x14\x03\x68\ -\x96\x55\x95\xcd\x04\xa0\xf5\x93\xe0\x34\x35\x9b\xc6\x64\x2a\x2c\ -\xd3\x24\x34\x6e\x63\x59\xa6\x0b\x4e\x22\xa5\x60\xcd\x92\xc2\x54\ -\x0f\xd0\x5a\x73\xb1\x3f\x8c\x65\x19\xae\x6b\x8d\xd6\xa0\x94\xe6\ -\xc2\xa5\x21\x00\x2a\x4b\x0a\xd0\x5a\x2f\x9b\x04\x20\xf0\x17\xe4\ -\x67\x02\x4e\x5f\x97\x6e\xf0\xa9\xe6\x00\x91\x93\x3d\xc0\xb5\x9a\ -\xc5\x57\xb1\xaa\x72\x66\x2a\xb8\xad\x14\xef\x9e\xee\xc3\x32\x0d\ -\xd0\x4e\x73\x52\x4a\x63\x18\x92\x91\xd0\x38\x08\xc1\x8c\xdc\x0c\ -\x34\xda\x3f\x95\x01\xf2\xdc\xc1\x32\x16\x89\xbb\xa2\x12\x93\x40\ -\xa4\x93\x12\xc3\x6d\x3c\xc9\xd2\x5c\xbb\x74\x16\x6b\x16\x4f\x09\ -\x6e\xdb\x1c\x68\xec\xa6\x67\x30\x8a\x69\x18\x4e\x60\x25\x31\x6c\ -\xc7\x4f\x24\xe6\x0c\xc8\x9c\xcc\xb4\x54\xfd\xcb\x64\x37\x18\x1a\ -\x8d\xba\x0f\xbd\x48\x21\xa6\x9b\x94\x18\x52\x4c\x63\xa3\xba\x38\ -\x9f\xd5\x8b\x02\x28\xa5\x50\x4a\x61\xdb\x36\x75\x1f\x5e\xe2\xe3\ -\xee\x31\xb7\x3b\x4e\x6f\x4c\x52\x0a\xb2\x33\xbd\x00\x8c\x84\xc7\ -\x53\x2d\x48\x02\x08\x4d\xb0\x7f\x28\x0c\x40\x20\xcf\x87\x10\x38\ -\x8a\x96\x8e\x49\x29\x10\x2e\x2b\x49\x87\xd7\x2f\xf4\xa7\x82\x2b\ -\xa5\x38\xd6\x32\x44\x57\x7f\xd4\x65\xca\x01\x9c\x64\x52\xb8\x2f\ -\x92\x9f\xed\x00\x08\x0e\x47\x10\x10\x9c\x04\x20\xe4\x89\xe6\x0b\ -\x97\x01\x58\x38\x2f\xe0\xd4\xb9\x10\x08\x40\x20\xdc\xf2\x22\x55\ -\x66\x52\x08\xb2\xd3\x8d\x14\xf5\x4a\x29\xda\xfa\x22\x29\x86\xa4\ -\x10\x08\x91\xbc\x3a\x7b\x11\x82\x92\xd9\x79\x00\x9c\xeb\xec\x47\ -\x48\x79\x22\xa5\x01\xad\xf5\x9e\x43\xc7\xdb\xd6\xdd\xb1\xbe\xd2\ -\x57\xb3\x74\x2e\x07\x8e\xb6\x4d\xf6\x68\x91\xbc\x4c\x19\x9c\x42\ -\xf0\x87\xfa\x0e\xd2\x3c\x16\x1e\x8f\x85\xc7\x63\xa2\x34\xa9\x06\ -\xe5\xe2\x9f\xb2\x9c\xdd\xcb\x17\xcd\x02\xe0\xad\x63\xad\x11\x65\ -\xab\x3d\x29\x00\xbe\x48\xac\xee\x48\xd3\x05\x35\x3c\x16\xa3\xb2\ -\xa4\x80\x05\xf3\xfc\x74\xf5\x8e\x90\x14\x68\xf2\x4d\xd1\xc9\xb2\ -\xd2\xec\x58\x3b\x27\x35\x92\x01\x5e\x3e\xd6\x8f\x52\xca\x61\x24\ -\xb9\x07\x8d\x76\x9d\x14\xcd\xca\x61\xc1\x9c\x7c\x46\x43\xe3\xbc\ -\xf3\x7e\xab\xf2\x45\x62\x75\xa9\x14\x34\x37\xd4\x86\x12\x09\xb5\ -\xf7\x2f\x75\x4d\xb6\x00\xee\xbd\x75\x19\x42\x08\xd7\x99\x53\xc7\ -\xc9\x7b\xa5\x34\xca\x56\x58\x06\x58\x06\x98\xd2\xb1\xf8\x44\x82\ -\x89\x84\x4d\xc2\x56\x28\x5b\x39\xff\x53\x1a\xad\x1c\xe0\x77\xad\ -\xaf\x00\x01\x7f\xfb\xc7\x49\xdb\xb6\xd5\xde\xe6\x86\xda\xd0\x64\ -\x15\x00\xf1\x84\xf5\xd8\xee\xd7\x3e\x18\xee\xee\x1f\x63\xd1\x7c\ -\x3f\xdf\xd8\x58\xe5\x04\x57\x4e\x8e\xed\xe4\xd5\x9d\x70\x53\x05\ -\xa8\x94\x22\x3e\x31\xc1\xc4\x84\x4d\x22\x61\x93\xb0\x6d\x6c\xdb\ -\x4e\x3d\xdb\x5a\x53\x46\x69\x61\x2e\x7d\xc1\x10\x7f\xda\x77\x6c\ -\x38\x16\xb7\x1e\x4b\xc6\x4d\x4d\xc3\xd1\xf6\xfa\x68\x4e\xd9\x06\ -\xdd\xd1\x33\x5c\xb3\xb9\xa6\xdc\xaa\x2c\x0a\x10\x8e\x4d\xd0\xde\ -\x3d\xe4\x28\x5a\x48\xa7\x22\x84\x93\xd1\x6b\x4a\x73\xb1\xb5\x26\ -\x61\x3b\xa0\x3e\x68\x1b\x26\x61\x2b\x07\x80\xcb\xc4\x44\xc2\xe6\ -\x86\x25\x85\x6c\x5d\x5d\x4a\xc2\x56\xfc\x60\x57\x7d\xa4\xad\xab\ -\xff\xa9\xae\x03\x8f\x1e\xfa\x0f\x89\x25\x97\x16\xa5\xb7\xfe\xea\ -\xf5\xed\x9b\xaa\x37\xfd\xf4\xbb\x1b\xd2\x84\x10\x1c\x6a\xec\x60\ -\xef\xa1\x73\x48\x29\xdd\xe9\x96\xfc\x18\x71\x9b\x93\x5b\x66\x5a\ -\x83\xd2\x0e\xfd\x09\x37\x05\xb7\xad\x2e\xe1\x86\x25\x85\x68\x60\ -\xe7\xf3\x0d\xe3\x2f\xd6\x35\xd5\xb7\xbd\xf9\xe8\x36\x10\xfa\x33\ -\x0c\x38\xeb\x09\x66\x57\xdf\xf0\xea\xc9\xf3\x03\x5b\x07\x46\xa2\ -\x81\x55\x4b\xe7\x19\x0b\xe6\xfa\xa9\x2e\x2b\x20\x38\x1a\xa5\x6f\ -\x28\xe2\x40\x76\xb7\x27\xf3\x3c\xf5\xe3\x23\x61\xdb\x2c\x28\xcc\ -\xe5\x5b\x1b\x2b\xa8\x2a\x0e\x30\x3e\x61\xf3\xcb\x17\x0e\xc7\x5f\ -\xac\x6b\x3a\x93\x96\x16\xd9\xdc\x7f\x76\xbd\x3d\xbd\x3e\x3e\x67\ -\xcd\x59\xf5\x48\xba\x95\x1f\x78\xa9\xba\x7c\xce\xc6\xa7\x1e\xba\ -\xd9\x57\xea\x7e\xe5\x5e\xbc\x3c\xca\xa9\xf6\x7e\x5a\xbb\x87\x18\ -\x0b\x4f\x30\x16\x8b\x23\x85\x20\x27\xd3\x4b\x8e\x2f\x8d\xb2\xc2\ -\x5c\xaa\x8a\xfd\xcc\x0e\x38\x73\xa5\xe3\xd2\x30\x3f\xda\xf5\xf7\ -\x68\x53\xf3\x85\x77\x7b\x8f\xbd\x70\x5f\x74\xb0\x2d\x08\x44\xbf\ -\x10\x00\xe0\x03\xb2\x0b\xd7\x7e\xef\x41\x6f\x76\xe0\x91\x3b\x37\ -\x2c\xf3\x7e\xe7\x6b\x2b\xcc\xf9\x57\xe5\x5e\xd1\x51\xea\xd3\xcb\ -\xa3\x3c\xff\x46\x63\xe2\xe5\x03\x1f\x8e\x47\x83\x5d\xcf\x74\x1f\ -\xfd\xe3\x6e\x60\xcc\xb5\x21\x40\x7d\x11\x80\x34\x20\x0b\xc8\x4a\ -\x2f\x28\x9d\xed\xaf\xdc\xf6\x80\x95\xe1\xdf\x56\x55\x3a\x4b\x6c\ -\x5e\x53\x99\x7e\x6d\x45\xa1\x98\x91\xe7\x23\x27\xc3\x0b\x02\x46\ -\x43\xe3\x0c\x8c\x44\x68\x3a\xdf\xa3\xeb\x0e\x37\xc7\xce\xb4\xf6\ -\xe8\xd8\x58\x4f\x7d\xf0\xd4\xab\x7f\x1e\x1f\xee\xe9\x75\x03\x87\ -\x80\x41\x20\x7c\x25\x0c\x80\x53\xa2\x19\x40\x36\x90\x61\x59\xbe\ -\xac\xec\xf2\x8d\x2b\x7d\x05\x0b\x36\x5a\xe9\xd9\xd5\x60\xe4\x68\ -\x21\x7d\x8e\x13\x15\x45\xdb\xa3\xf1\xf0\xd0\x99\x48\xdf\xf9\x77\ -\x46\xdb\xde\x69\x4a\x24\x62\x21\x1c\xba\x43\xc0\xb0\x0b\xe2\x33\ -\xe7\xc5\x2b\x3d\x9c\x1a\x80\x05\x78\x5d\x76\xbc\x38\x5d\xd4\x72\ -\x81\x2a\xc0\x06\xe2\xae\xc5\x80\x71\xf7\xfe\x8a\x0e\xa9\x5f\xda\ -\xfa\x37\x3d\x87\xb9\xea\xf9\xa4\xbb\x03\x00\x00\x00\x00\x49\x45\ -\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x01\x92\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x09\x12\ -\x0c\x29\x14\x44\x66\x98\x68\x00\x00\x01\x12\x49\x44\x41\x54\x38\ -\xcb\x63\xfc\xff\xff\x3f\x03\xa9\xc0\xd6\xd1\x1d\xae\x89\x85\x58\ -\x4d\x2f\x5e\xbc\xfc\x7f\xf8\xe8\x31\x86\xf3\x17\x2e\x31\x30\x30\ -\x30\x30\x1c\xde\xbf\x93\xd1\xd6\xd1\xfd\x3f\x23\x21\x17\xdc\xbe\ -\x73\xf7\xff\xfc\x85\x4b\x18\x4e\x9f\x39\xc7\xa0\xa2\xac\xc4\x60\ -\x6c\x64\xc0\xb0\x70\xf1\x32\x84\x82\xff\xff\xff\x63\xc5\x5f\xbe\ -\x7c\x61\x98\x34\x65\xfa\x7f\x07\x17\xaf\xff\xdd\xbd\x13\xff\xbf\ -\x7a\xfd\xfa\x0c\x36\x75\x58\xbd\xf0\xe5\xcb\x17\x86\xfc\xa2\xf2\ -\xff\xa2\xa2\x22\x0c\x2b\x96\xce\x67\x10\x17\x13\x63\xc4\xe9\x44\ -\x74\x13\x3f\x7f\xfe\xcc\x90\x9a\x91\xf3\x7f\xca\xb4\x99\xff\xbf\ -\x7e\xfd\x86\xd3\x85\x30\x8c\xa1\x39\x29\x35\xeb\x7f\xff\xa4\xa9\ -\xff\x09\x69\x84\x61\x26\x64\xd7\xcc\x5f\xb8\xe4\x3f\x37\x37\x17\ -\x43\x6a\x72\x02\x23\xd1\x71\x0a\x33\xe9\xd6\xed\x3b\xff\xdd\xbc\ -\xfc\x71\x06\x16\x41\x17\x2c\x5c\xb4\x94\xc1\xc3\xcd\x95\x41\x54\ -\x44\xc4\x84\xa4\x54\x05\x33\xe9\xe2\xa5\xcb\xff\x7f\xfc\xf8\xc1\ -\x40\x8a\xed\x28\x81\x68\xe3\xe0\xf6\x9f\x54\xcd\x18\x81\x48\x0e\ -\x60\xb4\x71\x70\xc3\x48\xcb\x87\xf7\xef\x64\x24\x36\x33\x91\xe5\ -\x05\x98\x5a\x1b\x07\xb7\xff\x64\x7b\x01\xe6\x0a\x00\x49\xb3\x7b\ -\xee\x02\x30\xa2\x1a\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\ -\x82\ -\x00\x00\x03\x33\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x20\x00\x00\x00\x20\x08\x03\x00\x00\x00\x44\xa4\x8a\xc6\ -\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xaf\xc8\x37\x05\x8a\xe9\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x01\x11\x50\x4c\x54\x45\xad\xad\xff\ -\x2a\x2a\x2a\xec\xec\xec\x29\x29\x29\xe5\xe5\xe5\x4e\x4e\x4e\x50\ -\x50\x50\x97\x97\xff\x31\x31\x31\xf7\xf7\xf7\x3c\x3c\x3c\xfb\xfb\ -\xfb\xf4\xf4\xf4\xf9\xf9\xf9\x39\x39\x39\xe4\xe4\xff\xef\xef\xef\ -\xfc\xfc\xfc\x63\x63\x63\xfd\xfd\xfd\xea\xea\xea\x57\x56\xfb\x24\ -\x24\x24\x99\x99\xeb\xad\xae\xff\xae\xad\xff\x6a\x6a\x6a\x53\x54\ -\x9e\x57\x57\xff\xb0\xb0\xf7\xf8\xf8\xf8\x4b\x4b\x4b\x68\x68\x68\ -\x69\x69\xff\x66\x66\x66\x97\x97\x97\xb5\xb5\xb5\x85\x84\xee\xa6\ -\xa6\xa6\x9d\x9d\x9d\xf6\xf6\xf6\x61\x61\x6a\x55\x55\x55\xe8\xe8\ -\xe8\x58\x58\xff\x40\x40\x40\x96\x96\xff\x72\x72\xff\x9f\x9f\x9f\ -\x3d\x3d\x3d\x87\x87\x87\x74\x73\xff\x3e\x3e\x3e\xf5\xf5\xf5\xfa\ -\xfa\xff\x41\x41\x7f\xeb\xeb\xeb\x3a\x3a\x3a\x5a\x5a\xff\x6c\x6c\ -\x6c\xf7\xf7\xff\xcb\xcb\xcb\x32\x33\x8c\x74\x74\x74\xc4\xc4\xec\ -\x7f\x7f\x7f\xf3\xf3\xf3\xf8\xf8\xff\x81\x81\x81\x25\x25\x25\x48\ -\x48\x8d\x4d\x4d\x4d\x28\x28\x28\x43\x43\x43\x26\x26\x26\x2c\x2c\ -\x2c\xe6\xe6\xe6\x7c\x7c\x7c\x52\x52\x52\xc2\xc2\xc2\xbf\xbf\xbf\ -\xdd\xdd\xdd\x70\x70\x70\xfb\xfb\xff\xc0\xc0\xc0\xd4\xd4\xd4\x6b\ -\x6b\xff\x6d\x6d\x6d\x9e\x9e\x9e\x86\x86\x86\xff\xff\xff\x8e\xf5\ -\x6e\x23\x00\x00\x00\x5b\x74\x52\x4e\x53\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\x00\x9c\xfa\xb8\xbc\x00\x00\x01\x41\x49\x44\x41\ -\x54\x78\xda\x62\x88\x22\x00\x00\x02\x88\x81\x90\x02\x80\x00\x22\ -\xa8\x00\x20\x80\x08\x2a\x00\x08\x20\x82\x0a\x00\x02\x88\xa0\x02\ -\x80\x00\xc2\xad\xc0\x3a\x00\x4c\x01\x04\x10\x4e\x05\xf6\x8c\x81\ -\x60\x1a\x20\x80\x70\x29\x08\x17\x63\x81\x30\x00\x02\x08\x87\x02\ -\x29\x2f\x1f\x28\x0b\x20\x80\xb0\x2b\x50\xf0\xd0\x86\x31\x01\x02\ -\x08\xae\x80\xd7\x25\x04\x2e\xaf\xc4\x28\x02\x67\x03\x04\x10\x5c\ -\x81\x9c\x2f\xb3\xa1\x01\x4f\x54\x94\x59\x70\x94\x90\xb7\x05\xc2\ -\x30\x80\x00\x42\xb2\xc2\x49\x9d\x8b\x31\x52\x5c\xdf\x58\xda\x92\ -\x09\xc9\x36\x80\x00\x42\x75\x83\xbf\x9b\xa8\xa4\x84\x8c\x03\xb2\ -\x10\x40\x00\xa1\x39\x92\x41\x8f\x9f\x9f\x5d\x55\x00\x49\x04\x20\ -\x80\xd0\x14\xc8\xea\x30\x30\x58\x99\x33\x07\x85\xc2\x45\x00\x02\ -\x08\x4d\x81\xa6\x5d\x98\x22\x7b\x94\xad\x90\x2b\xab\x8a\x30\x44\ -\x04\x20\x80\x50\x15\x68\xf1\x09\x38\xdb\x80\x18\x2c\x46\x1c\x1c\ -\x11\x60\x21\x80\x00\x42\x56\x20\xec\xc7\xc7\x03\xe7\x08\xaa\x29\ -\x83\x69\x80\x00\x42\x52\x20\xc8\xc6\x65\x8a\x19\xa8\x00\x01\x84\ -\x50\xc0\xcd\x6a\xa2\x81\x25\xd4\x01\x02\x08\xae\x80\x5b\x5e\x97\ -\x13\x5b\xb4\x00\x04\x10\x4c\x01\x13\x9b\x27\x56\xf9\x28\x80\x00\ -\x82\x29\x70\x74\xe7\xc5\x1e\xf1\x00\x01\x44\x30\x4d\x02\x04\x10\ -\x41\x05\x00\x01\x44\x50\x01\x40\x00\x11\x54\x00\x10\x40\x04\x15\ -\x00\x04\x10\x41\x05\x00\x01\x44\x50\x01\x40\x80\x01\x00\x5c\xf6\ -\x51\x14\x00\xf6\x6b\x00\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\ -\x60\x82\ -\x00\x00\x03\xc1\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x20\x00\x00\x00\x20\x08\x03\x00\x00\x00\x44\xa4\x8a\xc6\ -\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xaf\xc8\x37\x05\x8a\xe9\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x01\x71\x50\x4c\x54\x45\xea\xea\xff\ -\xd2\xd2\xff\x57\x57\x57\x7b\x7b\xff\xfc\xfc\xff\x74\x74\xff\x33\ -\x33\x33\x2a\x2a\x2a\x79\x79\xff\x84\x85\xff\xcb\xca\xff\x48\x48\ -\xff\x51\x51\xff\x78\x78\xff\xc5\xc5\xff\xee\xee\xee\xe5\xe5\xff\ -\x5c\x5c\x5c\x7a\x7a\xff\x53\x53\xfa\x7f\x7f\x7f\x41\x41\x41\x27\ -\x27\x27\xf1\xf1\xf1\xca\xca\xff\x69\x69\xff\x67\x67\xff\x62\x62\ -\xff\x8a\x8a\x8a\x3a\x3a\x3a\xa4\xa4\xff\x65\x65\xff\xd2\xd1\xff\ -\xae\xae\xff\xcf\xcf\xcf\x54\x54\x54\xf0\xef\xff\xac\xac\xac\xd3\ -\xd3\xd3\x53\x53\xb6\x37\x37\xaf\x56\x55\xff\x55\x56\xff\xba\xba\ -\xba\xc4\xc4\xff\xa3\xa3\xff\x5b\x5b\xd6\xfb\xfb\xff\x7d\x7d\x7d\ -\xa7\xa7\xa7\xe0\xe0\xe0\x85\x85\x85\xb0\xb0\xff\x72\x72\xff\xf8\ -\xf8\xff\xe8\xe8\xe8\x44\x44\x44\xed\xed\xff\x9a\x9a\xc3\x59\x59\ -\xfb\x29\x29\x29\xce\xce\xff\xde\xde\xe2\xc0\xc0\xff\x87\x87\xff\ -\xdb\xdb\xdb\x56\x56\xff\x96\x96\xff\x95\x95\xff\xf2\xf2\xff\xa3\ -\xa3\xa3\xf0\xf0\xff\xf3\xf3\xff\xf2\xf2\xf2\x9c\x9c\xff\xb1\xb1\ -\xff\x58\x58\xff\x3e\x3e\x3e\x7c\x7c\xff\xa6\xa6\xff\xd1\xd1\xff\ -\x8a\x8a\xfb\xaa\xaa\xaa\x35\x35\x35\xbe\xbe\xff\xf1\xf1\xff\x54\ -\x54\xff\x92\x92\xff\x6a\x6a\xf4\x53\x53\x53\xd7\xd7\xd7\xeb\xeb\ -\xeb\x81\x81\xff\x88\x88\xff\x52\x52\xff\x37\x37\x37\x57\x57\xff\ -\x85\x84\xff\xc6\xc6\xc6\x66\x65\xff\x39\x39\x39\xd1\xd1\xd1\xf6\ -\xf6\xf6\xb9\xb9\xd2\x71\x71\xc8\xb4\xb5\xff\xe3\xe3\xff\xbb\xbb\ -\xff\x60\x60\x60\x6b\x6b\xff\x9a\x9b\xff\x28\x28\x28\xd5\xd5\xda\ -\x7e\x7f\xff\x73\x73\xfa\x2e\x2e\x44\x6a\x6a\xff\xa2\xa2\xa2\xb1\ -\xb1\xb1\x31\x31\x31\xca\xcb\xff\xaf\xb0\xff\xff\xff\xff\x47\x6d\ -\xac\x67\x00\x00\x00\x7b\x74\x52\x4e\x53\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\x00\x93\xc3\xa8\xea\x00\x00\x01\x4f\x49\x44\x41\ -\x54\x78\xda\x62\xa8\x22\x00\x00\x02\x88\x81\x90\x02\x80\x00\x22\ -\xa8\x00\x20\x80\x08\x2a\x00\x08\x20\x5c\x0a\x04\x72\x93\x99\xcd\ -\x40\x0c\x80\x00\xc2\xa1\x80\x85\x55\xc8\x5b\xd2\x05\xc4\x02\x08\ -\x20\x1c\x0a\xdc\x4d\xf9\x42\x33\x99\x41\x2c\x80\x00\xc2\xa1\xc0\ -\x35\xcc\xb9\x82\xb3\x04\xc4\x02\x08\x20\x5c\x6e\x90\x10\x2e\xb2\ -\xce\x02\x31\x00\x02\x08\xa7\x2f\x0a\x8a\xed\xc0\x34\x40\x00\xe1\ -\x54\xe0\x59\x6e\x04\xa6\x01\x02\x08\xa7\x82\x34\xb6\x54\x30\x0d\ -\x10\x40\xb8\x03\x2a\x38\x09\x4c\x01\x04\x10\x6e\x05\x29\xda\x60\ -\x0a\x20\x80\x70\x2b\xb0\x08\x02\x53\x00\x01\x84\x5b\x01\x93\x1b\ -\x98\x02\x08\x20\x9c\x0a\x42\xd4\x33\x2c\x41\x34\x40\x00\xe1\x52\ -\x90\xcd\xe3\x5f\x28\x05\x62\x00\x04\x10\x2e\x05\x81\x72\x0c\x5c\ -\xac\x20\x06\x40\x00\x61\x55\x20\x5e\x96\xa3\x11\xcb\x58\x99\x00\ -\x62\x03\x04\x10\xa6\x02\x7e\x55\x41\xf6\x78\x83\x74\x2d\x3f\x9f\ -\x3c\x10\x17\x20\x80\xd0\x14\x44\x1b\x32\xd9\xc8\x8a\x28\x81\x24\ -\x02\x14\xc0\x22\x00\x01\x04\x51\xc0\xa2\x9b\x18\xee\x51\x65\x5e\ -\x1a\x99\xef\x6b\xac\x86\xaa\x05\x20\x80\x20\x0a\x62\xa4\x15\x79\ -\xf5\x94\xc5\x44\x65\xa2\x30\x6c\x04\x08\x20\x88\x02\x79\x13\x15\ -\x9d\x08\x2b\x47\x6c\x0e\x06\x08\x20\x88\x02\x6e\x07\x5b\x2f\x4d\ -\x7d\xac\xfe\x05\x08\x20\x88\x02\x46\x27\x8e\x38\x7b\xec\x01\x02\ -\x10\x40\x04\xf3\x05\x40\x00\x11\x54\x00\x10\x40\x04\x15\x00\x04\ -\x10\x41\x05\x00\x01\x44\x50\x01\x40\x80\x01\x00\x5a\x4a\xc7\xf4\ -\x5c\x60\x3e\x99\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\ -\x00\x00\x04\x70\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x18\x00\x00\x00\x18\x08\x03\x00\x00\x00\xd7\xa9\xcd\xca\ -\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x06\x09\x0d\x27\x21\x88\ -\xdc\x5c\xbd\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0a\xf0\x00\ -\x00\x0a\xf0\x01\x42\xac\x34\x98\x00\x00\x03\x00\x50\x4c\x54\x45\ -\x47\x47\x47\x2a\x2a\x2a\xd4\xd4\xd4\x78\x78\x78\xa6\xa6\xa6\xe7\ -\xe7\xe7\xc1\xc1\xc1\xcb\xcb\xcb\x39\x39\x39\xf8\xf8\xf8\x30\x30\ -\x30\x7b\x7b\x7b\x4b\x4b\x4b\x1e\x1e\x1e\x1c\x1c\x1c\x21\x21\x21\ -\xc0\xc0\xc0\xc5\xc5\xff\xda\xda\xda\xf3\xf3\xf3\x78\x78\xff\x90\ -\x90\xff\xf5\xf5\xf5\x99\x99\x99\x12\x12\x12\x3e\x3e\x3e\x85\x85\ -\x85\xfa\xfa\xfa\x38\x38\x38\xd9\xd9\xd9\x32\x32\x32\x91\x91\xff\ -\xcc\xcc\xcc\x4e\x4e\x4e\xea\xea\xea\x68\x68\x68\x94\x94\x94\x61\ -\x61\x61\x42\x42\x42\xa8\xa7\xff\xe1\xe1\xe1\xfc\xfc\xfc\x63\x63\ -\x63\xd8\xd8\xd8\x2f\x2f\x2f\x84\x85\xff\x57\x57\x57\xe8\xe8\xe8\ -\x3a\x3a\x3a\x67\x67\x67\x8d\x8d\x8d\xdd\xde\xff\x24\x24\x24\x72\ -\x72\x72\xbd\xbd\xbd\x44\x44\x44\x43\x43\x43\xf2\xf2\xff\x5e\x5e\ -\x5e\xf1\xf1\xf1\x5b\x5b\x5b\xd6\xd6\xff\x73\x73\x73\xce\xce\xce\ -\xcd\xcd\xcd\x77\x77\x77\xdb\xdb\xff\x55\x55\x55\xe5\xe5\xe5\x3c\ -\x3c\x3c\x90\x90\x90\x73\x73\xff\x14\x14\x14\xf9\xf9\xff\x35\x35\ -\x35\x49\x49\x49\xe2\xe2\xe2\x98\x98\x98\xc4\xc4\xc4\xbe\xbe\xbe\ -\x52\x52\x52\x50\x50\x50\xdf\xdf\xdf\xfd\xfd\xfd\x8f\x8f\x8f\x8e\ -\x8e\x8e\x94\x94\xff\x9c\x9c\xff\x8a\x8a\x8a\xb8\xb8\xb8\x36\x36\ -\x36\x9e\x9e\x9e\xf4\xf4\xf4\xb4\xb4\xb4\xa9\xa9\xa9\x29\x29\x29\ -\xed\xed\xff\xa0\xa0\xa0\xab\xab\xab\xb6\xb6\xb6\x89\x89\x89\x0d\ -\x0d\x0d\x4a\x4a\xff\x5a\x5a\x5a\x73\x74\xff\x17\x17\x17\x05\x05\ -\x05\xcf\xcf\xff\x31\x31\x31\x63\x62\xff\xbf\xbf\xbf\x5d\x5d\xff\ -\x9b\x9b\xff\x48\x48\x48\xe0\xe0\xe0\x54\x54\x54\xc3\xc3\xff\x34\ -\x34\x34\xdd\xdd\xff\x3d\x3d\x3d\x16\x16\x16\xc6\xc6\xc6\x6d\x6d\ -\x6d\xef\xef\xff\x4c\x4c\x4c\x81\x81\x81\x65\x65\x65\xde\xde\xde\ -\x01\x01\x01\x0a\x0a\x0a\xc5\xc5\xc5\x95\x95\xff\xf7\xf7\xf7\xc9\ -\xc9\xff\x8b\x8b\x8b\x49\x49\xff\x86\x86\x86\xc9\xca\xff\xff\xff\ -\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\xb7\x9b\xbf\xd3\x00\x00\x00\x8b\x74\x52\x4e\x53\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\x00\x85\xc3\x15\xef\x00\x00\x00\x6c\x49\ -\x44\x41\x54\x78\xda\x85\xd0\xc1\x0d\xc0\x20\x0c\x03\x40\xc6\xf0\ -\x82\xde\x83\x77\xa7\x2e\x86\xb6\x4a\x82\x51\x79\x21\x4e\x71\x48\ -\xda\x75\x38\xed\xbd\xf0\x00\x84\x87\x0e\x1f\xc5\x1a\xf4\x00\x01\ -\x56\x13\x00\xa3\xc4\xf4\xd0\x1b\x1c\x60\x7d\x16\xa6\x87\x9b\x24\ -\x40\x96\x08\x29\x2d\x41\xac\xc9\x10\xa4\x80\xd2\x68\x61\x4c\xc5\ -\x39\xda\x06\x1a\x75\xac\x62\x03\x6a\x6b\xdd\x45\xa9\xc6\xf6\x98\ -\x39\x0e\x40\xff\xdd\x6f\xcb\xa6\xc7\x0f\xdc\x40\xf7\x26\xb7\x62\ -\xeb\x3e\x34\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x02\x44\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0\x77\x3d\xf8\ -\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xaf\xc8\x37\x05\x8a\xe9\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x01\xd6\x49\x44\x41\x54\x78\xda\x62\ -\xfc\xff\xff\x3f\x03\x2d\x01\x40\x00\x31\x31\xd0\x18\x00\x04\x10\ -\x23\x10\x2b\x03\x71\x37\x11\x6a\x27\xfc\xfb\xf7\xff\x10\x13\x13\ -\x43\x23\x90\x2d\x01\x15\xfb\x02\xc4\xbb\xa1\xf8\x2f\xba\x06\x50\ -\xe0\x00\x04\x10\x88\xae\x01\xb1\x89\xc0\x47\x80\x16\x40\xb5\x61\ -\xe0\x87\x40\x1c\x82\xae\x05\x14\xfc\x00\x01\x04\x26\xb0\x61\x90\ -\x61\x68\xd8\x11\x99\xbf\x77\xef\xff\xff\x33\x66\xfc\xff\xaf\xad\ -\x8d\x62\xd1\x64\x74\x0b\x00\x02\x88\x14\x0b\x70\xe2\x96\x96\xff\ -\xff\x59\x59\xe1\x96\x94\x22\x5b\x00\x10\x40\x54\xb1\x00\x84\xcf\ -\x9c\xf9\xff\x9f\x99\x19\x6c\xe2\x4f\x20\x56\x84\x59\x00\x10\x40\ -\xf8\x2c\x60\x04\x62\x69\x28\x26\xca\x92\xa6\x26\xd4\xa0\x02\x99\ -\x03\x10\x40\x8c\xb8\xf2\x01\x50\x98\x1b\x48\xb1\x01\x31\xd0\x45\ -\x0c\xff\x80\xf8\x07\x31\xc9\x92\x9d\x9d\xe1\xff\xef\xdf\x0c\xf7\ -\x81\x4c\x25\x90\xd1\x00\x01\x84\x2f\x1f\xb0\x42\x0d\x07\xe1\x5f\ -\x40\xcc\x0c\x15\x17\xc3\x67\x81\xbb\x3b\x98\x02\x06\x11\x03\x27\ -\x88\x01\x10\x40\x4c\x04\xf2\xc8\x5f\xa8\xeb\x61\x18\x04\x5e\xe1\ -\xb3\x40\x43\x03\xce\x04\x5b\x00\x10\x40\xf8\x82\x48\x00\x48\x7d\ -\x03\xe2\xdf\xd0\x74\x07\x02\x2c\x40\xfc\x07\xab\x6b\x18\xb1\x5b\ -\x08\x10\x40\xf8\x7c\xf0\x17\xc9\x60\x18\xf8\x83\x4b\x31\x2c\x7a\ -\x19\x19\x19\x27\x00\xf1\x14\x20\x06\xfb\x00\x20\x80\xf0\xf9\x80\ -\x03\x6a\xc9\x6f\x34\x29\x66\x6c\xc5\x02\xdc\xc5\x4c\x8c\x1f\x80\ -\x14\x3f\x10\x0b\x00\xcd\xfe\x08\x10\x40\xf8\x7c\xf0\x13\xea\x43\ -\x66\xa4\x38\x61\x84\xfa\x8a\x95\xd8\xc2\x0e\x20\x80\xf0\x59\xf0\ -\xdf\xd5\x15\x9c\x4c\xff\xa2\x59\xc0\x80\x44\x13\x04\x00\x01\x84\ -\x2f\x88\x80\xde\x05\x26\x1d\x48\xda\xe1\x82\xa6\xa2\xff\x50\xc3\ -\x41\x3e\xe0\x05\xe2\x67\x84\x82\x08\x20\x80\x58\x88\x74\xc8\x37\ -\xa4\x3c\x00\xd2\xc3\x0e\xc4\x7c\xe8\x16\x30\x61\x09\x0f\x80\x00\ -\xc2\x69\x01\x28\xd9\x61\xf1\x1c\xde\x3c\x00\x49\x45\xa8\x62\x00\ -\x01\x44\xf3\x1a\x0d\x20\x80\x68\x6e\x01\x40\x00\xd1\xdc\x02\x80\ -\x00\xa2\xb9\x05\x00\x01\xc4\x48\xeb\x66\x0b\x40\x80\x01\x00\x92\ -\xef\x86\xb2\xf4\x0f\xd3\x1a\x00\x00\x00\x00\x49\x45\x4e\x44\xae\ -\x42\x60\x82\ -\x00\x00\x01\x9d\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x09\x12\ -\x0c\x2c\x0e\xc4\x73\x95\x57\x00\x00\x01\x1d\x49\x44\x41\x54\x38\ -\xcb\x63\xfc\xff\xff\x3f\x03\xa9\xc0\xd6\xd1\x1d\xae\x89\x85\x58\ -\x4d\x2f\x5e\xbc\xfc\x7f\xf8\xe8\x31\x86\xf3\x17\x2e\x31\x30\x30\ -\x30\x30\x1c\xde\xbf\x93\xd1\xd6\xd1\xfd\x3f\x23\x21\x17\xdc\xbe\ -\x73\xf7\xff\xfc\x85\x4b\x18\x4e\x9f\x39\xc7\xa0\xa2\xac\xc4\x60\ -\x6c\x64\xc0\xb0\x70\xf1\x32\x84\x82\xff\xff\xff\x63\xc5\x5f\xbe\ -\x7c\x61\x98\x34\x65\xfa\x7f\x07\x17\xaf\xff\xdd\xbd\x13\xff\xbf\ -\x7a\xfd\xfa\x0c\xb2\xbc\x8d\x83\xdb\xff\xff\xff\xff\x63\xf7\xc2\ -\x97\x2f\x5f\x18\xf2\x8b\xca\xff\x8b\x8a\x8a\x30\xac\x58\x3a\x9f\ -\x41\x5c\x4c\x8c\x11\x97\x0b\x59\xb0\x69\x2e\x2a\xad\xfc\x6f\x64\ -\xa8\xcf\x90\x18\x1f\xcb\xc8\xc5\xc5\x89\xd7\x8b\x2c\xd8\x6c\xd6\ -\xd5\xd5\x66\xc8\xce\x4c\x63\x24\x26\x70\x99\x90\x39\xf3\x17\x2e\ -\xf9\xcf\xcd\xcd\xc5\x90\x9a\x9c\xc0\x48\x74\x9c\xc2\x02\xe5\xd6\ -\xed\x3b\xff\xdd\xbc\xfc\x31\x02\x0b\x17\x86\x05\x22\xdc\x05\x0b\ -\x17\x2d\x65\xf0\x70\x73\x65\x10\x15\x11\x31\x21\x25\x51\xc1\xc3\ -\x20\x2c\x34\x88\x41\x5d\x4d\x95\x91\xd4\x54\x09\x77\x41\x76\x5e\ -\x31\x03\x3b\x3b\x3b\x03\xd9\x06\x90\x0b\x18\x6d\x1c\xdc\x30\xd2\ -\xf2\xe1\xfd\x3b\x19\x89\xcd\x4c\x18\xa1\x4a\x4a\x0c\xd8\x38\xb8\ -\xfd\x27\xdb\x0b\x30\x57\x00\x00\x1b\xd7\xce\x54\xc5\xa6\xb3\xdd\ -\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x02\x3b\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0\x77\x3d\xf8\ -\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xaf\xc8\x37\x05\x8a\xe9\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x01\xcd\x49\x44\x41\x54\x78\xda\x62\ -\xfc\xff\xff\x3f\x03\x2d\x01\x40\x00\x31\x11\xa3\x88\x91\x91\xf1\ -\x22\x10\xa7\x11\x56\x77\xf3\x3b\x23\xe3\x82\xc5\xc8\x62\x00\x01\ -\xc4\x44\x84\xe1\x47\x81\x94\x1e\x10\xbf\xc5\xaf\xee\xe2\x27\x06\ -\x06\x75\x0e\x06\x86\x1f\xcf\x91\xc5\x01\x02\x88\x01\x14\x44\xb8\ -\x30\x10\x6c\x02\x62\x10\xa3\x18\xbf\xba\xc3\x40\x43\x41\x8c\x25\ -\x9b\xd0\xe5\x00\x02\x08\x9f\xe1\xd3\xa1\x86\x77\xe0\x37\x7c\xeb\ -\x55\x88\xe1\x6b\x8f\x63\x93\x07\x08\x20\x5c\x86\x57\x43\x0d\x9f\ -\x8f\xdf\xf0\xe5\xbb\x20\x86\xef\xbc\x8d\x4b\x0d\x40\x00\x61\x33\ -\x3c\x01\x6a\xf8\x5e\xfc\x86\xcf\x9d\x03\x31\xfc\xf4\x3b\x7c\xea\ -\x00\x02\x08\x9b\x05\x0b\x81\xf8\x17\x3e\x4d\x10\x75\xbb\xee\x30\ -\x30\xfc\xfc\x4f\x48\x1d\x40\x00\x61\xb3\x40\x19\xea\x83\xad\xf8\ -\x2d\x68\x77\x80\xf8\xe0\xd8\x2b\x7c\xea\x00\x02\x88\x50\x1c\x84\ -\x13\x17\x07\x33\x27\xe0\x52\x03\x10\x40\xf8\x52\xd1\x75\x20\xfe\ -\x47\x38\xa8\xee\xfc\x64\x60\xf8\x8b\x33\xa8\x00\x02\x88\x11\x5f\ -\x51\x01\xcc\x64\xbf\x81\xd4\x67\xa0\x1a\x21\xfc\x99\xec\xcf\x7f\ -\x88\x32\x41\x8c\x8c\x0b\x10\x40\x84\x72\xb2\x05\x10\x0b\x02\x2d\ -\x5a\x81\x5f\xd9\xc4\x18\x88\xb2\x7d\x0f\xd1\x65\x00\x02\x88\x81\ -\x70\x10\x30\x4c\x06\xc5\x07\x61\x75\x9b\x2f\x42\x94\xa1\x8a\x03\ -\x04\x10\x31\x85\xdd\x03\x68\x70\x79\xe2\x57\xf6\xf1\x21\x44\xdd\ -\x94\x1a\x64\x51\x80\x00\x22\xc6\x07\xff\x80\xf8\x14\x61\x75\x7f\ -\x81\xc4\xa5\x2f\xe8\xe2\x00\x01\x44\xc8\xf0\xbb\x0c\x90\x24\x42\ -\x40\xdd\xc3\x5f\xb8\x52\x12\x40\x00\xe1\x33\x1c\x56\x64\x04\x13\ -\x57\x64\x4c\xef\xc2\x26\x0f\x10\x40\xf8\x2c\xf8\x0b\xca\x0b\xc4\ -\x05\xcd\x9d\x9f\xb8\xe4\x01\x02\x08\x97\xe1\x97\x89\xcb\x64\x37\ -\xbf\xe3\xcb\x64\x20\x0c\x10\x40\xd8\x0c\x0f\x86\x06\x4d\x02\x7e\ -\xc3\xa7\x77\x41\x82\x66\xee\x1c\x7c\xea\x00\x02\x08\x5b\x32\xfd\ -\x06\xc4\xdb\x80\x92\x0b\xf0\x27\xcb\x3f\xc0\x2a\xf2\xf8\xeb\xff\ -\xff\x93\x52\xf0\xa9\x02\x08\x20\x46\x5a\xb7\x2a\x00\x02\x88\x89\ -\x81\xc6\x00\x20\xc0\x00\x48\x89\xdd\xa8\x44\xcf\xda\x11\x00\x00\ -\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x02\xc2\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0\x77\x3d\xf8\ -\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xaf\xc8\x37\x05\x8a\xe9\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x02\x54\x49\x44\x41\x54\x78\xda\x62\ -\xfc\xff\xff\x3f\x03\x3e\xc0\xc8\xc8\x18\x02\xa4\x54\x80\xea\x3a\ -\x18\xc8\x00\x00\x01\xc4\x00\xb2\x00\x17\x06\x02\x90\xe1\xff\xa1\ -\xb8\x08\x9f\x5a\x5c\x18\x20\x80\x18\xf1\xf9\x00\xe8\x7a\xa0\xf4\ -\x7f\x20\xc5\xd8\x00\xe4\xd6\x43\xd9\x8d\x40\xb6\x04\x54\xc9\x17\ -\x20\xde\x0d\xc2\x40\xb9\xbf\xd8\xcc\x00\x08\x20\x92\x5d\x84\xe4\ -\x23\x64\xfc\x10\xe4\x5b\x6c\xea\x01\x02\x08\x9b\x01\x81\x20\x4d\ -\x58\x14\x3b\x22\x59\x10\xb2\x79\xf3\xe6\xff\x53\xa6\x4c\xf9\x6f\ -\x6e\x6e\x8e\x6c\xd1\x64\x74\x7d\x00\x01\x84\xd5\x85\x61\x61\x61\ -\x97\xf1\xf8\xa0\x01\xdd\x01\x2d\x2d\x2d\xff\x39\x39\x39\x61\x96\ -\x94\x22\xcb\x01\x04\x10\x8a\xcb\x49\x0c\xa6\xbd\xc8\x62\x07\x0f\ -\x1e\xfc\xcf\xc6\xc6\x06\x12\xff\x09\xc4\x8a\x30\x71\x80\x00\x82\ -\x69\xf0\xc3\x12\x2c\xa0\x18\x96\x86\x62\x74\xc3\x0f\x03\x31\x0f\ -\xba\xc5\x4d\x4d\x4d\x18\x41\x05\x10\x40\xb8\x0c\x07\x61\x6e\x20\ -\x16\x04\x62\x2e\x20\xe6\x40\xb3\x84\x07\x97\xef\xa0\x41\x75\x0f\ -\xc6\x07\x08\x20\x26\x20\x47\x17\x9a\x24\x1b\xd0\x12\x18\x2b\xd4\ -\xbb\x3f\x23\x22\x22\x4e\xb3\xb0\xb0\xc0\xd2\xb3\x18\x50\xe3\x17\ -\x5c\x49\xdb\xd7\xd7\x17\x44\x29\x02\xcd\xe3\x04\x31\x00\x02\x08\ -\xe6\xa2\x16\x2c\xbe\x00\xb9\x9e\x1d\x14\x54\x20\x39\xa0\x86\xc3\ -\xf8\x5c\x0e\xc3\xe5\xe5\xe5\xb0\x60\x12\x02\xf1\x01\x02\x08\x9f\ -\x62\x01\x20\x66\x83\xc6\x05\x83\x88\x88\x08\x2f\x90\x66\x21\x35\ -\x8f\x00\x04\x10\x2e\x45\x0d\x40\x36\x2f\x2b\x2b\xeb\x7f\xa8\x0f\ -\x60\x2e\x67\x82\x5a\x4a\x94\x25\x20\x3e\x40\x00\x61\x53\x30\x1f\ -\x24\xc9\xc1\xc1\x01\x4a\x76\xc7\xa1\x2e\x87\xa5\x2a\x10\x66\x26\ -\x26\x19\xc3\xd8\x00\x01\x84\x4d\x12\x6c\x00\x30\xcc\x8f\xc5\xc5\ -\xc5\x09\x43\x0d\x64\x84\x94\x43\x40\x2f\x00\xfd\x00\xf2\x18\xc8\ -\x0c\x42\x18\x64\x0e\x40\x00\x31\x61\x29\x9b\xfe\x02\x4d\x02\x19\ -\xea\xf6\xe1\xc3\x87\x5f\x40\x21\x16\x90\xa5\x90\x84\xc6\xc0\xf0\ -\xf7\x2f\x03\xf3\xaf\x5f\x0c\xec\x90\x82\x12\x13\x03\x95\xe9\x41\ -\x68\x08\x00\x08\x20\x82\xb9\xd6\xc1\xc1\x41\x02\x96\x17\xd8\xd9\ -\xc1\xae\x02\xe5\x09\x50\xb0\x49\xa1\xa9\x95\x47\x0d\x09\x88\x0f\ -\x00\x02\x88\x91\x50\x85\x03\x05\x20\x5f\xf0\x88\x8b\x33\xbc\x7f\ -\xf9\x92\x41\x1a\xc8\x66\x07\x62\x3e\x20\xbe\x88\xbb\xa8\x87\x54\ -\x07\x00\x01\xc4\x44\x64\xbd\xf4\x07\x88\x3f\xbc\x79\x03\x66\x7f\ -\xb6\xb3\xb3\x93\xb3\xb6\xb6\xe6\x07\x86\x99\x22\x21\x8d\x00\x01\ -\x44\xac\x0f\x50\x5c\x45\x8a\x5a\x80\x00\x62\x62\xa0\x31\x00\x08\ -\x20\x16\x52\x35\x80\x5c\x46\x8a\x7a\x80\x00\x62\x21\xad\x81\x40\ -\x5c\xf0\x20\x03\x80\x00\xa2\x79\x10\x01\x04\x18\x00\xc8\xc9\x50\ -\xa2\xec\x22\xa1\xf4\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\ -\x82\ -\x00\x00\x02\xda\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x18\x00\x00\x00\x18\x08\x03\x00\x00\x00\xd7\xa9\xcd\xca\ -\x00\x00\x00\x03\x73\x42\x49\x54\x08\x08\x08\xdb\xe1\x4f\xe0\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0d\xd7\x00\x00\x0d\xd7\x01\ -\x42\x28\x9b\x78\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\ -\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\x70\ -\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x01\x2f\x50\x4c\x54\ -\x45\xff\xff\xff\x55\x80\x55\x60\x80\x60\x5b\x92\x5b\x60\x8f\x60\ -\x59\x8c\x59\x5e\x8e\x5e\x59\x8c\x59\x5b\x8c\x5b\x5c\x8d\x5c\x5a\ -\x8a\x5a\x58\x8d\x58\x5c\x8a\x5c\x5a\x8d\x5a\x5b\x8d\x5b\x5a\x8b\ -\x5a\x59\x8c\x59\x5a\x8c\x5a\x59\x8e\x59\x5b\x8c\x5b\x58\x8b\x58\ -\x5b\x8d\x5b\x59\x8b\x59\x59\x8d\x59\x5b\x8b\x5b\x5a\x8b\x5a\x5b\ -\x8e\x5b\x5c\x8e\x5c\x5a\x8c\x5a\x5b\x8c\x5b\x5e\x8f\x5e\x5e\x90\ -\x5e\x5d\x90\x5d\x5d\x8f\x5d\x60\x91\x60\x68\x97\x68\x5d\x8f\x5d\ -\x5c\x8f\x5c\x62\x93\x62\x5c\x8d\x5c\x5d\x8e\x5d\x67\x97\x68\x5c\ -\x8e\x5c\x5d\x8f\x5d\x5d\x8e\x5d\x5f\x91\x5f\x5c\x8f\x5c\x5d\x8f\ -\x5d\x6c\x9e\x6c\x5b\x8d\x5b\x5d\x8f\x5d\x60\x92\x60\x63\x94\x63\ -\x6b\x9d\x6b\x5d\x8f\x5d\x5c\x8e\x5c\x71\x9e\x71\x72\xa0\x73\x5b\ -\x8d\x5b\x5c\x8e\x5c\x5e\x90\x5e\x78\xa1\x78\x6e\xa0\x6e\x73\xa5\ -\x73\x6c\x9a\x6d\x72\xa4\x72\x5b\x8c\x5b\x79\xab\x79\x76\xa9\x76\ -\x9d\xc1\x9e\x5a\x8c\x5a\x74\x9e\x74\x7b\xad\x7b\x7f\xb1\x7f\x80\ -\xb2\x80\x81\xb3\x81\x82\xb4\x82\x83\xb5\x83\x86\xb8\x86\x87\xb9\ -\x87\x89\xbb\x89\x8a\xbc\x8a\x8b\xb6\x8b\x8b\xbd\x8b\x8c\xbe\x8c\ -\x90\xac\x90\x90\xaf\x90\x91\xaa\x91\x96\xc4\x96\x99\xc6\x9a\x9f\ -\xc9\xa0\xa1\xca\xa2\xaa\xcb\xac\xab\xcc\xac\xad\xd0\xae\xaf\xd1\ -\xb0\xb0\xd1\xb2\xb2\xd3\xb3\xbd\xd8\xbf\xc2\xdd\xc4\xc3\xdd\xc4\ -\xdd\xbe\x37\x38\x00\x00\x00\x46\x74\x52\x4e\x53\x00\x06\x08\x0e\ -\x10\x14\x1b\x28\x2a\x2f\x30\x3a\x3d\x41\x43\x44\x45\x47\x48\x49\ -\x4b\x4c\x4d\x53\x65\x6c\x73\x75\x83\x83\xab\xae\xb7\xba\xbc\xc9\ -\xcd\xcf\xd6\xe9\xe9\xed\xef\xf1\xf2\xf2\xf3\xf3\xf3\xf4\xf4\xf4\ -\xf4\xf4\xf5\xf6\xf6\xf6\xf7\xf8\xf8\xf8\xf9\xf9\xfa\xfb\xfd\xfd\ -\xfe\xfe\xa1\x9f\xc0\x06\x00\x00\x00\xcb\x49\x44\x41\x54\x28\xcf\ -\x63\x60\xa0\x10\xc8\x8b\xe1\x90\xb0\xb2\x15\xc1\x21\x91\x64\x26\ -\x8c\x5d\x22\xd9\xc7\x4c\x10\x89\x2f\xae\xe4\xe0\xee\x06\x02\x36\ -\x29\x21\xde\x46\x02\x70\x71\x69\xcb\x98\xb8\xe8\x10\x30\x88\x0a\ -\x09\xf1\x36\xe3\x83\x8a\x73\x5b\x24\x46\x84\x20\x01\x2f\x33\x2e\ -\x88\x84\xac\x6b\x64\x08\x8a\x84\x36\x07\x44\x42\x39\x16\xc4\x0d\ -\x0b\x07\x82\x50\x20\xc3\x5b\x87\x1b\x6a\x94\x66\x7c\x48\x48\xb0\ -\xbd\xae\x13\xc8\xf2\xc0\x10\x6f\x3d\x1e\x98\xdd\x6a\x09\x21\x21\ -\x2e\xaa\x9c\x8c\x20\xe7\x06\xf8\xe8\xf1\xc2\x1d\x25\x17\x14\x12\ -\xe2\x28\x03\xf1\x87\xa7\x3e\x3f\xc2\x17\xc2\xc6\xc1\x21\xbe\x7a\ -\xec\x60\x09\x73\x21\x64\xff\xaa\xd8\x05\x87\x38\x6b\x48\xb2\x02\ -\x03\x51\x14\x25\x20\x98\x15\x4c\xfc\x42\x3c\x4d\x0d\x99\x30\xc3\ -\x48\xca\xdc\xc0\xc3\xdf\x9a\x05\x4b\xe8\xb1\x49\x28\x6a\xa9\x33\ -\x31\x0c\x12\x00\x00\x09\xe2\x2c\x4a\x71\x6a\x84\xc3\x00\x00\x00\ -\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x04\x64\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x18\x00\x00\x00\x18\x08\x03\x00\x00\x00\xd7\xa9\xcd\xca\ -\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x06\x09\x0d\x23\x30\x86\ -\x00\xb9\x4b\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0a\xf0\x00\ -\x00\x0a\xf0\x01\x42\xac\x34\x98\x00\x00\x03\x00\x50\x4c\x54\x45\ -\x47\x47\x47\x2a\x2a\x2a\xd4\xd4\xd4\x78\x78\x78\xa6\xa6\xa6\xe7\ -\xe7\xe7\xc1\xc1\xc1\xcb\xcb\xcb\x39\x39\x39\xf8\xf8\xf8\x30\x30\ -\x30\x7b\x7b\x7b\x4b\x4b\x4b\x1e\x1e\x1e\x1c\x1c\x1c\x21\x21\x21\ -\xc0\xc0\xc0\xc5\xc5\xff\xda\xda\xda\xf3\xf3\xf3\x78\x78\xff\x90\ -\x90\xff\xf5\xf5\xf5\x99\x99\x99\x12\x12\x12\x3e\x3e\x3e\x85\x85\ -\x85\xfa\xfa\xfa\x38\x38\x38\xd9\xd9\xd9\x32\x32\x32\x91\x91\xff\ -\xcc\xcc\xcc\x4e\x4e\x4e\xea\xea\xea\x68\x68\x68\x94\x94\x94\x61\ -\x61\x61\x42\x42\x42\xa8\xa7\xff\xe1\xe1\xe1\xfc\xfc\xfc\x63\x63\ -\x63\xd8\xd8\xd8\x2f\x2f\x2f\x84\x85\xff\x57\x57\x57\xe8\xe8\xe8\ -\x3a\x3a\x3a\x67\x67\x67\x8d\x8d\x8d\xdd\xde\xff\x24\x24\x24\x72\ -\x72\x72\xbd\xbd\xbd\x44\x44\x44\x43\x43\x43\xf2\xf2\xff\x5e\x5e\ -\x5e\xf1\xf1\xf1\x5b\x5b\x5b\xd6\xd6\xff\x73\x73\x73\xce\xce\xce\ -\xcd\xcd\xcd\x77\x77\x77\xdb\xdb\xff\x55\x55\x55\xe5\xe5\xe5\x3c\ -\x3c\x3c\x90\x90\x90\x73\x73\xff\x14\x14\x14\xf9\xf9\xff\x35\x35\ -\x35\x49\x49\x49\xe2\xe2\xe2\x98\x98\x98\xc4\xc4\xc4\xbe\xbe\xbe\ -\x52\x52\x52\x50\x50\x50\xdf\xdf\xdf\xfd\xfd\xfd\x8f\x8f\x8f\x8e\ -\x8e\x8e\x94\x94\xff\x9c\x9c\xff\x8a\x8a\x8a\xb8\xb8\xb8\x36\x36\ -\x36\x9e\x9e\x9e\xf4\xf4\xf4\xb4\xb4\xb4\xa9\xa9\xa9\x29\x29\x29\ -\xed\xed\xff\xa0\xa0\xa0\xab\xab\xab\xb6\xb6\xb6\x89\x89\x89\x0d\ -\x0d\x0d\x4a\x4a\xff\x5a\x5a\x5a\x73\x74\xff\x17\x17\x17\x05\x05\ -\x05\xcf\xcf\xff\x31\x31\x31\x63\x62\xff\xbf\xbf\xbf\x5d\x5d\xff\ -\x9b\x9b\xff\x48\x48\x48\xe0\xe0\xe0\x54\x54\x54\xc3\xc3\xff\x34\ -\x34\x34\xdd\xdd\xff\x3d\x3d\x3d\x16\x16\x16\xc6\xc6\xc6\x6d\x6d\ -\x6d\xef\xef\xff\x4c\x4c\x4c\x81\x81\x81\x65\x65\x65\xde\xde\xde\ -\x01\x01\x01\x0a\x0a\x0a\xc5\xc5\xc5\x95\x95\xff\xf7\xf7\xf7\xc9\ -\xc9\xff\x8b\x8b\x8b\x49\x49\xff\x86\x86\x86\xc9\xca\xff\xff\xff\ -\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ -\xb7\x9b\xbf\xd3\x00\x00\x00\x8b\x74\x52\x4e\x53\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\x00\x85\xc3\x15\xef\x00\x00\x00\x60\x49\ -\x44\x41\x54\x78\xda\xad\x91\xd1\x09\x00\x21\x0c\x43\x1d\x23\x0b\ -\x66\x8f\x7e\xeb\xd2\x57\xa5\x62\x0f\xc9\x1d\x82\x42\xc5\xe6\xd1\ -\xc6\x6a\xa9\x62\x15\x0d\xc0\x5d\x25\x14\xe0\x2f\xb0\x25\xa2\xc7\ -\x04\xcc\x00\x0b\x90\x2d\x01\x77\x0e\x73\x82\x66\x9e\xf7\x83\xc7\ -\xd8\xa2\x02\xb9\x62\x08\xe1\x81\xec\xc1\x64\x5e\xdb\xfb\x56\x72\ -\x0e\xdc\x03\xfa\xad\x70\xdc\x0a\x5f\x40\x7e\xed\x35\xf0\x00\x5b\ -\x56\x18\x52\xb8\x25\xeb\xb2\x00\x00\x00\x00\x49\x45\x4e\x44\xae\ -\x42\x60\x82\ -\x00\x00\x03\xfe\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x20\x00\x00\x00\x20\x08\x03\x00\x00\x00\x44\xa4\x8a\xc6\ -\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xaf\xc8\x37\x05\x8a\xe9\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x01\x6e\x50\x4c\x54\x45\xfc\xfc\xfc\ -\xef\xef\xef\x8c\x8c\x8c\xfb\xfb\xfb\xc0\xc0\xc0\xd8\xd8\xd8\x1a\ -\x1a\x1a\xca\xca\xca\xa9\xa9\xa9\xf8\xf8\xf8\xfd\xfd\xfd\xf5\xf5\ -\xf5\x82\x82\x82\xb0\xb0\xb0\xb3\xb3\xb3\xe8\xe8\xe8\x9e\x9e\x9e\ -\xb9\xb9\xb9\x6d\x6d\xff\xc7\xc7\xc7\x26\x26\x26\x3f\x3f\x3f\xdd\ -\xdd\xdd\xf1\xf1\xf1\xf3\xf3\xf3\x34\x34\x34\xc6\xc6\xc6\x68\x68\ -\x68\xde\xde\xde\xa0\xa0\xa0\x85\x85\x85\x6c\x6c\x6c\x60\x61\xff\ -\x6f\x6f\x6f\xa2\xa2\xa2\xca\xca\xff\x43\x43\x43\xa6\xa6\xa6\x49\ -\x49\x49\xd1\xd1\xd1\x47\x47\x47\x2e\x2e\x2e\xfa\xfa\xfa\xb5\xb5\ -\xff\x1f\x1f\x1f\x79\x79\x79\x7e\x7e\x7e\x65\x65\x65\xe3\xe3\xe3\ -\xe0\xe0\xe0\xe7\xe7\xe7\x15\x15\x15\x8e\x8e\xbe\x0c\x0c\x0c\xee\ -\xee\xee\xd3\xd3\xdc\x56\x56\x56\x93\x93\x93\x81\x81\xff\xf2\xf2\ -\xf2\xe6\xe6\xff\x27\x27\x82\x19\x19\x19\xf2\xf2\xff\x51\x51\xfe\ -\x99\x99\x99\x62\x62\xff\x84\x84\x84\x32\x32\x32\x93\x93\xff\xd0\ -\xd0\xff\xc2\xc2\xc2\x81\x81\x81\x67\x67\x67\x3e\x3e\x3e\x53\x53\ -\xff\x94\x94\xff\xb8\xb8\xff\x6e\x6d\xff\x2b\x2b\x2b\x58\x58\x58\ -\x1c\x1c\x1c\xf9\xf9\xf9\xe0\xe0\xff\x66\x66\xff\x5d\x5d\x5d\x28\ -\x28\x28\xdb\xdb\xdb\x77\x77\x77\xa1\xa1\xff\xf6\xf6\xff\x8a\x8a\ -\xb0\xdd\xdd\xff\xe5\xe5\xe5\x18\x18\x18\x30\x30\x30\x8b\x8b\x8b\ -\xf4\xf4\xf4\x45\x45\x45\x86\x86\x86\xf9\xf9\xff\xa4\xa4\xa4\x61\ -\x61\x61\x9e\x9e\xff\x2b\x2b\x50\xed\xed\xed\x6b\x6b\x6b\x20\x20\ -\x20\xc3\xc3\xc3\x9d\x9d\x9d\x4e\x4e\x4e\xc0\xc0\xff\xfd\xfd\xff\ -\xd7\xd7\xd7\xae\xae\xff\x8a\x8a\x8a\x33\x33\x33\x12\x12\x12\xaa\ -\xa9\xff\xf0\xf0\xf0\x6b\x6b\xff\xff\xff\xff\x3d\x6d\xa9\x5e\x00\ -\x00\x00\x7a\x74\x52\x4e\x53\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\x00\xe8\x3f\xe8\x26\x00\x00\x01\x90\x49\x44\x41\x54\x78\xda\x62\ -\xa8\x24\x00\x00\x02\x88\x81\x90\x02\x80\x00\x42\x53\xe0\xc8\x81\ -\xae\x00\x20\x80\xd0\x14\x14\x8b\xa2\x2b\x00\x08\x20\xa8\x82\x5c\ -\x5e\x08\x6d\x64\xac\x8e\xa6\x00\x20\x80\xa0\x0a\xf4\x4a\xa0\x7c\ -\x41\x66\x34\x05\x00\x01\x04\x55\x90\x29\x22\x0c\x61\x94\x3b\x07\ -\xa1\x2a\x00\x08\x20\x98\x1b\x3c\xf5\x21\xb4\x75\x1c\x1f\xaa\x02\ -\x80\x00\x82\x29\x90\xd2\x91\x80\x30\x22\x02\x50\x15\x00\x04\x10\ -\x4c\x01\x83\xa6\x2a\x84\x21\xce\x8f\xaa\x00\x20\x80\xe0\xde\x94\ -\x4b\x82\x32\x98\x04\x50\x14\x00\x04\x10\x5c\x81\xa1\x9d\x0c\x84\ -\x91\x10\xcf\x85\xac\x00\x20\x80\x10\x01\xa5\xe1\x01\xa1\x19\x73\ -\x50\x4c\x00\x08\x20\x84\x02\x0e\x7f\x28\xbb\x50\x16\x59\x01\x40\ -\x00\x21\x14\x24\x66\xb3\x40\x18\x7c\xa5\x8c\x48\x0a\x00\x02\x08\ -\x29\x2e\xa4\x43\x21\x34\x37\x13\x27\x92\x02\x80\x00\x42\x52\xc0\ -\x1e\x08\xd5\xc9\xec\x8e\xa4\x00\x20\x80\x90\x63\xd3\x05\xea\x41\ -\x56\xd3\x70\x84\x20\x40\x00\x21\x2b\x60\xf2\x82\x32\x92\x0d\x10\ -\x82\x00\x01\x84\xac\x20\x96\x8d\x15\x66\x9b\x16\x5c\x10\x20\x80\ -\x50\x12\x4c\x9e\x2e\x34\x28\xd8\x58\xe0\x62\x00\x01\x84\xa2\x80\ -\x37\x0c\xca\xc8\xb2\x84\x8b\x01\x04\x10\x6a\x92\xcb\x88\x56\x86\ -\x88\x8a\xc1\x83\x1b\x20\x80\x50\x14\x68\x3b\xf8\x28\x58\x81\x45\ -\x25\x53\x61\x62\x00\x01\x84\xac\x20\xa5\x22\x32\xd8\x37\x24\x06\ -\xc4\xe4\x49\x83\x09\x02\x04\x10\xb2\x82\x28\xa7\x32\x9b\x7c\x3f\ -\x37\x70\x68\xf2\xc3\x42\x13\x20\x80\x90\x15\x14\xb8\x7a\xa7\x0b\ -\x09\xd9\x83\xd9\xf2\x3c\x50\x41\x80\x00\x42\x75\x64\x91\x89\xad\ -\x39\x84\xa5\xa4\x06\x15\x02\x08\x20\xf4\xac\x67\xa1\x08\x8d\x0f\ -\x33\xa8\x00\x40\x00\xa1\x2b\x10\x54\x41\x13\x01\x08\x20\x82\x99\ -\x17\x20\x80\x08\x2a\x00\x08\x30\x00\x48\x52\xbb\xa3\x6c\xbd\x9b\ -\x89\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x02\xfb\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0\x77\x3d\xf8\ -\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x1c\x20\x00\x00\x1c\x20\ -\x01\xcd\x0f\x9b\x9e\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xb1\ -\x8e\x7c\xfb\x51\x93\x00\x00\x00\x20\x63\x48\x52\x4d\x00\x00\x7a\ -\x25\x00\x00\x80\x83\x00\x00\xf9\xff\x00\x00\x80\xe9\x00\x00\x75\ -\x30\x00\x00\xea\x60\x00\x00\x3a\x98\x00\x00\x17\x6f\x92\x5f\xc5\ -\x46\x00\x00\x02\x71\x49\x44\x41\x54\x78\xda\x62\xfc\xff\xff\x3f\ -\x03\xb5\x01\x23\x23\x23\xd0\xd8\xff\x8c\x20\x36\x40\x00\x31\x51\ -\xc3\x40\x39\x39\x19\xb8\x39\xe2\xe2\xe2\xff\x65\x65\xa5\xc1\x96\ -\x80\xf8\x00\x01\xc4\x00\xf2\x01\x25\x18\x68\x18\xc8\xb9\x0c\xaa\ -\xaa\x4a\x20\x83\x39\x41\x6c\x31\x31\x11\x10\x1b\x64\xc1\x7f\x80\ -\x00\xa2\x8a\xe1\x2a\x2a\x8a\xff\xa1\xfc\xff\x30\x39\x90\x25\x20\ -\x1a\x20\x80\x28\x36\x5c\x49\x49\x1e\x6e\x38\x08\x43\x5d\xcf\x0f\ -\x15\x13\x01\x08\x20\x72\x0d\x67\xc2\x65\x38\x88\x2f\x29\x09\x8e\ -\x07\xb0\x03\x00\x02\x88\x91\xd2\x54\x04\x8c\x60\xb0\x01\x3f\x7f\ -\xfe\x64\x78\xf9\xf2\x35\x23\xc8\xa2\xc7\x8f\x9f\x32\xc2\xe4\x01\ -\x02\x88\xec\x54\x04\x8d\x44\x06\x7c\x86\x83\x00\x40\x00\x91\xe5\ -\x03\x29\x29\x89\xff\x2c\x2c\x2c\x0c\xff\xfe\xfd\x63\x78\xf2\xe4\ -\x19\xd8\x40\x69\x69\xc9\xff\x4f\x9f\x3e\x67\x44\x57\x0b\x10\x40\ -\x4c\x24\x04\x05\x0b\x5c\x13\x13\x13\x9c\x86\x05\x11\x36\xc3\x41\ -\x00\x20\x80\x88\xb6\xe0\xd1\xa3\x27\x7f\x60\xec\xdf\xbf\x7f\x33\ -\xfc\xf9\xf3\x17\x1c\x34\x40\xc0\x8e\x4f\x1f\x40\x00\x31\x40\x83\ -\xe8\x3f\x31\x18\x47\x8a\xe2\xc5\x97\xe2\x00\x02\x08\x92\x94\x80\ -\xd9\x9a\x4d\xd1\x88\x81\x91\x8d\x83\x81\x89\x85\x83\x81\x91\x1d\ -\x88\x59\x39\x19\x18\xd8\x38\x19\x98\x80\x34\x23\x3b\x27\xc3\xa7\ -\xed\x93\x60\x0e\x62\x24\x25\xbe\x00\x02\x08\x1e\x44\xbf\xee\x9f\ -\x03\x5a\xc0\xc5\xc0\xc8\x01\xc4\xec\xdc\x60\xcc\x04\xc2\x1c\x10\ -\xb6\x60\x58\x13\xbc\x20\x83\xc6\x09\x2b\x31\x16\x00\x04\x10\x13\ -\xb2\xab\x98\xd8\x81\x86\xb3\x71\x23\x2c\x00\x1a\xce\x00\xb5\x88\ -\x01\xc8\x16\x4e\x9e\x06\xb7\x04\x18\x27\xbf\x89\xb1\x00\x20\x80\ -\x50\x22\xf9\xdb\x99\x4d\x40\x1f\x20\x0c\x87\xd3\x1c\xdc\x10\xcb\ -\x81\xb4\x58\xc1\x4a\x14\x9f\x10\x02\x00\x01\xc4\x84\x14\xd9\x60\ -\x5f\x60\x18\x0e\x0f\x2e\x1e\x88\x25\x40\x2c\x51\xb3\x9b\x68\x4b\ -\x00\x02\x08\x23\x99\x7e\xde\x31\x99\x81\x89\x0d\x6a\x38\x1b\xcc\ -\x70\x88\xc1\x0c\xa0\x38\x82\x62\xc9\xf6\x33\x44\x59\x02\x10\x40\ -\x4c\x68\x49\x16\xe2\x0b\x14\x97\x43\x30\xc8\x50\x26\xb8\x05\xc0\ -\xd4\xc6\xca\xc1\x20\x33\xf1\x16\x41\x4b\x00\x02\x08\x6b\x46\x63\ -\xe4\xe0\x41\x0a\x22\xa8\xa1\xec\x90\xe4\x0a\x32\x1c\x8c\x59\x81\ -\xf9\x8b\x85\x9d\x41\x6e\xc6\x33\xbc\x96\x00\x04\x10\x46\x59\x04\ -\x53\x28\x5a\xb8\x0a\x62\x10\x0b\xcc\x40\x50\x1e\x61\x07\xf2\xd9\ -\x80\x06\xb3\x41\x68\x66\x16\x06\x46\x26\x48\x09\xf2\x28\x55\x14\ -\x6b\x3e\x01\x08\x20\x26\x2c\x39\x1b\x9e\x64\x41\x41\x02\xa6\x59\ -\x39\xc1\x41\xc2\x00\x74\x35\x23\x1b\xc4\x12\x46\x16\x56\x06\x46\ -\x66\x56\x50\x81\x04\xc6\x72\x73\xdf\x62\x0d\x22\x80\x00\xc2\x5a\ -\x9a\x12\x9b\x04\x71\x14\x3d\x28\x3e\x00\x08\x20\x46\x5a\x34\x5b\ -\x90\x01\x40\x80\x01\x00\x1f\xeb\xae\x1c\x5a\x12\x48\x08\x00\x00\ -\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x02\xe6\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0\x77\x3d\xf8\ -\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xaf\xc8\x37\x05\x8a\xe9\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x02\x78\x49\x44\x41\x54\x78\xda\x62\ -\x60\xc0\x0f\x74\x81\xb8\x0e\x88\x0f\x03\xf1\x2b\x20\xfe\x0f\xc5\ -\x4f\x80\x78\x2f\x10\x57\x00\xb1\x22\x9a\x9e\xff\xc8\x1c\x80\x00\ -\x62\xc4\x61\xb0\x16\x10\xb7\x03\xb1\x1f\x88\xa3\xae\xae\xce\x60\ -\x6f\x6f\x0f\x97\xfc\xf5\xeb\x17\xc3\xc1\x83\x07\x19\xee\xdf\xbf\ -\x0f\x13\x5a\x00\xc4\xf5\x40\xfc\x10\xdd\x6c\x80\x00\xc2\x86\x32\ -\x81\xf8\x27\xc8\x25\xf9\xf9\xf9\xff\x1f\x3e\x7c\xf8\x1f\x08\x18\ -\xb0\xe1\x1b\x37\x6e\xfc\x8f\x8a\x8a\xfa\x8f\xe4\x33\x3f\x24\x36\ -\xd8\x27\x00\x01\x84\x8e\x7a\x41\x12\x8a\x8a\x8a\xff\x0f\x1c\x38\ -\x80\xd3\x60\x74\x0c\xd2\x23\x2a\x2a\x0a\x33\x34\x07\xd9\x12\x80\ -\x00\x42\x77\xf9\xff\x84\x84\x84\xff\x9f\x3f\x7f\x26\xc9\x70\x18\ -\x8d\x84\xbd\x61\x86\x03\x04\x10\x72\x98\xff\x74\x75\x75\x25\xda\ -\x60\x5c\x86\xeb\xea\xea\xc2\xd8\xa2\x20\x09\x80\x00\x82\xa1\x2d\ -\x3c\x3c\x3c\xff\xdf\xbe\x7d\x4b\xb6\xcb\xc5\xc4\x44\xe0\x7c\x66\ -\x66\x66\x10\x63\x32\x88\x03\x10\x40\x20\x64\x00\x12\x6c\x6f\x6f\ -\xa7\x8a\xe1\x20\x3a\x2d\x2d\xed\x3f\x34\xa1\x48\x00\x04\x10\x48\ -\xb0\x9d\x8d\x8d\x8d\x68\xd7\xa3\x85\x35\x86\xe1\x20\x7c\xf5\xea\ -\x55\x98\x7c\x26\x40\x00\x81\x24\xce\xd8\xd8\xd8\x90\x1c\xf6\x30\ -\x8c\x6e\x38\x0c\xcb\xc9\xc9\x81\x24\xd6\x01\x04\x10\x13\x28\x1f\ -\x99\x99\x99\x31\x10\x03\xc4\xc5\x45\xff\x33\x32\x32\x82\x0c\x83\ -\x67\x22\x74\x3e\x3c\xdc\x0d\x0c\xc0\x79\x14\x20\x80\x40\x16\xf0\ -\x00\xd3\x30\x51\x16\xbc\x7a\xf5\x06\xc3\x50\x6c\x86\x83\x80\xb4\ -\xb4\x34\x88\x12\x05\x08\x20\x26\x06\x22\x01\xc8\x50\x18\x1b\x18\ -\xee\x0c\xf8\x0c\x47\x02\x7f\x01\x02\x88\x05\x48\x7c\x79\xf7\xee\ -\x1d\x0f\x21\x0b\x40\x86\xe1\x0a\x0e\x6c\xe0\xe9\xd3\xa7\x20\xea\ -\x35\x40\x00\x81\x7c\x70\xf9\xd4\xa9\x53\x44\xf9\x82\x58\xc3\x41\ -\xe0\xc2\x85\x0b\x60\x0a\x20\x80\x40\x44\x0b\x27\x27\x27\x51\xc9\ -\x14\x5b\x6a\xc1\x86\x91\x92\x69\x1a\x40\x00\x81\x63\x1a\x88\xff\ -\x10\x93\xd1\x88\xb5\x00\x9a\xd1\x3e\x03\xb1\x10\x40\x00\xc1\x7c\ -\xb4\x88\xd4\xa2\x02\x5f\x11\x0e\x2d\x2a\x5a\x40\x06\x03\x04\x10\ -\x0c\xc9\x01\xf1\x37\x42\x85\x1d\x21\x1f\x7c\xfb\xf6\x0d\x56\xd8\ -\x81\x6a\x3c\x70\xc2\x01\x08\x20\x64\x14\x08\x2b\xae\x41\x0a\x49\ -\xb5\xe0\xd5\xab\x57\xff\x3d\x3c\x3c\x60\x65\x10\x3c\xe7\x02\x04\ -\x10\x3a\xca\x27\xa7\xc2\x59\xb7\x6e\x1d\xac\xc2\x01\x19\x1e\x8e\ -\x6c\x20\x40\x00\x61\x43\x1e\xd0\x08\xfa\x5f\x54\x54\xf4\xff\xc9\ -\x93\x27\xff\x71\xf9\xe0\xde\xbd\x7b\xc8\x55\xe6\x73\x6c\x2e\x07\ -\x08\x20\x5c\x08\x54\x76\x4c\x83\xd5\xcd\xc0\x72\x05\xa5\x04\x05\ -\xa5\x12\x15\x15\x15\x18\xff\x33\x34\x42\xf9\xb1\x19\x04\x10\x40\ -\x8c\x44\x58\xe4\x0d\xad\xcc\x41\x09\xc1\x18\x88\x41\x4d\x89\x77\ -\x40\x7c\x07\x88\xd7\x00\xf1\x6e\x20\xfe\x88\xcb\x00\x80\x00\x03\ -\x00\x85\x08\x34\xd0\x73\x8f\xa1\x2e\x00\x00\x00\x00\x49\x45\x4e\ -\x44\xae\x42\x60\x82\ -\x00\x00\x01\x8c\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x09\x12\ -\x0c\x2c\x2a\xf8\x70\x71\x86\x00\x00\x01\x0c\x49\x44\x41\x54\x38\ -\xcb\x63\xfc\xff\xff\x3f\x03\x03\x03\x03\x83\xad\xa3\xfb\xff\xc3\ -\xfb\x77\x32\x32\x10\x01\x6c\x1d\xdd\xff\xc3\xd8\x4c\xc8\x02\xc8\ -\x12\xe8\xe0\xc5\x8b\x97\xff\x57\xaf\x5d\xff\xbf\xaa\xb6\xf1\x3f\ -\x03\x03\x03\x03\xcc\x32\x46\x42\x2e\xb8\x7d\xe7\xee\xff\xf9\x0b\ -\x97\x30\x9c\x3e\x73\x8e\x41\x45\x59\x89\xc1\xd8\xc8\x80\x61\xe1\ -\xe2\x65\x08\x05\xff\xff\xff\x67\xf8\xff\xff\x3f\x83\x8d\x83\xdb\ -\x7f\x18\xfb\xff\xff\xff\x0c\x5f\xbe\x7c\x61\x98\x34\x65\xfa\x7f\ -\x07\x17\xaf\xff\xdd\xbd\x13\xff\xbf\x7a\xfd\xfa\x0c\xb2\x3c\x0c\ -\xb3\x60\x73\xee\x97\x2f\x5f\x18\xf2\x8b\xca\xff\x8b\x8a\x8a\x30\ -\xac\x58\x3a\x9f\x41\x5c\x4c\x0c\x67\xd8\xb0\x60\xd3\x5c\x54\x5a\ -\xf9\xdf\xc8\x50\x9f\x21\x31\x3e\x96\x91\x8b\x8b\x13\x6f\x80\xb2\ -\x60\xb3\x59\x57\x57\x9b\x21\x3b\x33\x8d\xa8\x18\x61\x42\xe6\xcc\ -\x5f\xb8\xe4\x3f\x37\x37\x17\x43\x6a\x72\x02\x51\x9a\x31\x02\xd1\ -\xcd\xcb\x1f\x67\x60\xe1\xc2\x28\x2e\xf0\x70\x73\x65\x10\x15\x11\ -\x31\x61\x20\x01\x30\xc1\x12\x89\xa3\x83\x1d\x43\x48\xb0\x3f\x03\ -\xc9\x00\x57\x3a\x20\xcb\x0b\xe4\x00\x46\x1b\x07\x37\x8c\xf4\x4f\ -\x6c\xa6\x1a\x1c\x5e\x00\x00\x16\xb4\x06\x84\xe1\xeb\x16\x69\x00\ -\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x02\x3c\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x09\x12\ -\x0d\x14\x38\x1d\x94\xd6\x02\x00\x00\x01\xbc\x49\x44\x41\x54\x38\ -\xcb\x8d\x93\xcf\x4e\x13\x51\x14\xc6\x7f\x77\x3a\x03\x2d\x42\x23\ -\x0b\xd8\x11\xf4\x15\x04\xd3\x85\x2d\x9d\x9a\xd2\xa1\x22\x41\x49\ -\xdc\x68\x34\x2a\x0f\xa0\x12\x34\x26\x06\x01\x29\x0b\x12\xd3\x87\ -\x50\xdf\x80\xb8\x20\x2c\x20\xe9\x1a\x70\xa1\x6e\x24\x94\x34\xfe\ -\xa1\x40\x62\xd2\x61\x00\x9d\x69\x8e\x2b\x6b\xa7\x32\x69\x4f\xf2\ -\xad\xce\xbd\xdf\xfd\xdd\x93\xef\x20\x22\x9c\xa5\xb8\x99\x91\xa0\ -\x5e\xbd\x94\x88\xd0\x58\x89\x94\x25\xde\xef\x53\x1c\xc7\xa1\xa3\ -\xe3\x1c\xb1\x58\x8c\x89\x89\x71\x92\x89\x2b\xea\xbf\xc3\xf5\x2f\ -\xfe\xd5\x4e\xb1\x84\xeb\x79\x88\x08\xae\xe7\xb1\x53\x2c\x71\xef\ -\xfe\xa4\x3c\x7a\xfc\x4c\x1a\x09\xb5\x7a\xb3\xc2\xda\x8a\x02\xe8\ -\xee\x3e\xcf\xc1\xe1\x4f\x4a\xdf\xca\x7c\xdf\x3b\x24\x1c\x89\x90\ -\xcf\xe7\xd5\x85\x8b\xfd\x4c\x4d\x3f\xf7\x21\xeb\x8d\xe8\x00\x5f\ -\x7f\x1c\xa4\x39\xa3\xac\x91\x6b\xea\xe5\xcc\x0b\xd9\x2b\xef\xfb\ -\x0d\x12\x29\x4b\xe6\x66\x67\xe8\xeb\xeb\x57\x34\xa9\xdb\x77\xee\ -\xb2\x5b\xdc\xad\x51\xa8\xb8\x99\x91\xaa\xfb\x8b\x37\x6f\xdf\xa9\ -\x50\x28\xd4\xec\x3e\xd5\x6a\x95\x07\x0f\x27\x6b\x06\x5a\x61\x6d\ -\x45\xd9\xb6\x4d\x24\xdc\x4e\x9b\xa1\x37\x55\x24\xdc\xee\x9b\x97\ -\x0e\xd0\xd9\xd5\x89\xa6\x69\xb4\x42\xe0\x7a\xae\x6f\x5e\x5a\xc2\ -\x1c\x96\xe4\xd0\x10\xdb\xdb\x5f\xa4\x15\x82\xcf\x1f\x3f\xc9\xeb\ -\xa5\xdc\x3f\x0a\x11\x21\x9e\x4c\x8b\x95\x1d\x13\xfb\xc8\xc1\x39\ -\x3e\x09\x54\xc5\x3e\x22\x33\x72\x5d\xca\xe5\x7d\x7f\x0e\x0a\xeb\ -\xab\xca\x39\x3e\x21\x3b\x3a\x2e\x56\x76\x4c\x0c\x43\xa7\x51\x9a\ -\xa6\x58\xc8\x2d\xca\xe0\xc0\x25\x7a\x7b\x7b\x6a\x5f\xf2\x07\x69\ -\x7d\x55\x01\x6c\x6d\x7d\x10\x05\x18\xba\x8e\x02\x36\x37\x36\xe5\ -\xc6\xcd\x5b\x62\x18\x6d\x2c\xe6\xe6\x55\x70\x90\xcc\x61\x01\x58\ -\x5e\x7e\xcf\x93\xa9\xa7\x52\xa9\x54\x88\x46\xa3\x5c\x4d\x99\x2c\ -\xbc\x9a\xe5\xf2\xe0\x40\xf0\x2e\xf8\x36\x31\x99\x6e\x69\x13\x45\ -\x84\xc0\x46\xab\x26\x7f\x00\xa4\x5f\x59\x5c\x15\x66\x0e\x6e\x00\ -\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x01\x43\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0\x77\x3d\xf8\ -\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xaf\xc8\x37\x05\x8a\xe9\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x00\xd5\x49\x44\x41\x54\x78\xda\x62\ -\xfc\xff\xff\x3f\x03\x2d\x01\x40\x00\x31\x31\xd0\x18\x00\x04\x10\ -\x4e\x0b\x18\x19\x19\x89\xf6\x1a\x3e\xb5\x00\x01\x44\x73\x1f\x00\ -\x04\x10\xcd\x2d\x00\x08\x20\x46\x50\x24\xe3\xf2\x22\x5c\xf0\xff\ -\x7f\x46\x68\x58\xfc\xc7\xc6\x67\xc4\x61\x38\xd0\x6c\x46\x80\x00\ -\x62\x82\x31\xd0\x31\xdc\x20\x18\x1b\x0f\x9f\x9f\x9f\x1f\xc4\x13\ -\xc0\x66\x06\x40\x00\x51\x25\x88\x3e\x7e\xfc\x88\x53\x0e\x20\x80\ -\x18\x71\xe5\x03\x50\xb0\xfd\x47\x76\x2d\x81\x54\x84\x4b\x2d\x40\ -\x00\xd1\x3c\x92\x01\x02\x88\xe6\x16\x00\x04\x10\xd5\x2c\x00\x06\ -\x13\x3f\x36\x71\x80\x00\xa2\x79\x1c\x00\x04\x10\x55\x7c\x00\x4a\ -\xa6\xb8\x7c\x00\x10\x40\x34\x8f\x03\x80\x00\xa2\xb9\x05\x00\x01\ -\x44\x73\x0b\x00\x02\x88\xe6\x16\x00\x04\x10\xcd\x0b\x3b\x80\x00\ -\xa2\x79\x32\x05\x08\x20\x9a\x07\x11\x40\x00\xd1\xdc\x02\x80\x00\ -\x62\xa4\x75\xab\x02\x20\x80\x68\xee\x03\x80\x00\x03\x00\x79\x8b\ -\x6f\x62\x4d\x30\xc4\x9b\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\ -\x60\x82\ -\x00\x00\x01\x5a\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0\x77\x3d\xf8\ -\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xaf\xc8\x37\x05\x8a\xe9\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x00\xec\x49\x44\x41\x54\x78\xda\x62\ -\xfc\xff\xff\x3f\x03\x39\x80\x91\x91\x11\xac\x11\xa8\x9f\x11\x9f\ -\x3a\x80\x00\x62\x62\xa0\x10\xc0\x2c\xc2\x05\x00\x02\x88\x6c\x0b\ -\x90\x5d\x8e\xcf\x12\x80\x00\xa2\xc8\x07\xc4\x58\x02\x10\x40\x14\ -\x07\x11\x21\x4b\x00\x02\x88\x62\x0b\x08\x59\x02\x10\x40\x54\x88\ -\x64\x86\xff\xf8\x2c\x01\x08\x20\x26\x6a\x18\x8e\xcf\x12\x80\x00\ -\xa2\x52\x10\x31\x30\xe2\x0a\x2e\x80\x00\x62\x24\x27\xa3\x81\x5c\ -\x8c\x6c\x28\xbe\xfc\x01\x10\x40\x24\x5b\x00\x0b\x0e\x74\x97\xe3\ -\x02\x00\x01\xc4\x44\x8d\x60\xc1\x07\x00\x02\x88\x89\xf4\xb2\x87\ -\x81\x91\x58\xc3\x41\x00\x20\x80\x98\x48\x31\x9c\x50\xb9\x83\x0d\ -\x00\x04\x10\x13\x29\x85\x19\xa1\x92\x13\x1b\x00\x08\x20\x26\x5a\ -\x1a\x0e\x02\x00\x01\xc4\x44\x4b\xc3\x41\x00\x20\x80\x98\x68\x69\ -\x38\x08\x00\x04\x10\x13\x2d\x0d\x07\x01\x80\x00\x62\xa2\xa5\xe1\ -\x20\x00\x10\x40\x4c\xb4\x34\x1c\x04\x00\x02\x88\x89\x96\x86\x83\ -\x00\x40\x00\x31\xe1\xaa\x38\xa8\x05\x00\x02\x88\x91\xdc\x66\x0b\ -\xb1\x00\x20\xc0\x00\x5e\xef\x65\x28\xb4\x00\x73\x1b\x00\x00\x00\ -\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x04\x12\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x20\x00\x00\x00\x20\x08\x03\x00\x00\x00\x44\xa4\x8a\xc6\ -\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xaf\xc8\x37\x05\x8a\xe9\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x01\x83\x50\x4c\x54\x45\xe4\xe4\xe4\ -\xfa\xfa\xfa\xea\xea\xea\xf6\xf6\xf6\x09\x09\x09\x0c\x0c\x0c\x11\ -\x11\x11\x13\x13\x13\xe6\xe6\xe6\xd6\xd6\xd6\xdd\xdd\xdd\x3f\x3f\ -\x3f\x38\x38\x38\x5d\x5d\x5d\x2f\x2f\x2f\x1d\x1d\x1d\xd8\xd8\xff\ -\xf7\xf7\xf7\x36\x36\x36\x26\x26\x26\xf1\xf1\xf1\x62\x62\x62\x20\ -\x20\x20\xed\xed\xed\x27\x27\x27\x00\x00\x00\x4e\x4e\x4e\xb9\xb9\ -\xb9\x66\x66\xff\x64\x64\xff\xd8\xd8\xd8\xef\xef\xef\xf9\xf9\xf9\ -\xf2\xf2\xf2\x58\x58\x58\xa0\x9f\xff\xf8\xf8\xf8\x58\x58\xff\x3a\ -\x3a\x3a\xd7\xd7\xd7\xaa\xaa\xaa\x4a\x4a\x4a\xf1\xf1\xff\xac\xad\ -\xff\xc4\xc4\xff\x0e\x0e\x0e\xcf\xcf\xcf\xeb\xeb\xeb\x29\x29\x29\ -\x19\x19\x19\xda\xda\xff\x1a\x1a\x1a\xd4\xd4\xd4\x30\x30\x30\xb4\ -\xb4\xb4\xc8\xc8\xc8\x47\x47\x47\x62\x62\xff\x16\x16\x16\xe2\xe2\ -\xe2\x3c\x3c\x3c\x8e\x8e\x8e\x72\x72\xff\xc8\xc8\xff\xa0\xa0\xa0\ -\xe7\xe7\xe7\x8c\x8c\xff\x8d\x8d\xff\x83\x83\x83\x79\x79\x79\x6d\ -\x6d\x6d\x76\x76\x76\x76\x76\xff\xd0\xd0\xd0\x59\x59\x59\x82\x82\ -\x82\x31\x31\x31\x24\x24\x24\x64\x64\x64\xc0\xc0\xc0\xfd\xfd\xfd\ -\x3b\x3b\x3b\xe8\xe8\xe8\xba\xba\xba\x45\x45\x45\x84\x84\xff\xfd\ -\xfd\xff\xbd\xbd\xbd\xd9\xd9\xd9\xf0\xf0\xf0\x25\x25\x25\x5e\x5e\ -\x5e\x53\x53\x53\xfb\xfb\xff\x4a\x4a\xff\x95\x95\xff\x9e\x9e\x9e\ -\xde\xde\xde\xb8\xb8\xb8\x2d\x2d\x2d\xa0\xa1\xff\xe3\xe3\xe3\xb2\ -\xb2\xff\x75\x75\xff\xd0\xd0\xff\xf5\xf5\xf5\x04\x04\x04\xea\xeb\ -\xff\x44\x44\x44\x5c\x5c\xff\x17\x17\x17\x6e\x6e\x6e\x99\x99\x99\ -\xb7\xb7\xb7\xc6\xc6\xc6\xa0\xa0\xff\x3e\x3e\x3e\x66\x66\x66\xfb\ -\xfb\xfb\xc2\xc2\xff\x8e\x8e\xff\x4b\x4b\x4b\xee\xee\xee\xce\xce\ -\xce\xdc\xdc\xff\xe1\xe1\xe1\x75\x75\x75\x78\x78\x78\xff\xff\xff\ -\xca\x0b\xda\xb4\x00\x00\x00\x81\x74\x52\x4e\x53\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x92\xc8\xe3\ -\x4f\x00\x00\x01\x88\x49\x44\x41\x54\x78\xda\x62\x68\x20\x00\x00\ -\x02\x88\x81\x90\x02\x80\x00\x22\xa8\x00\x20\x80\x08\x2a\x00\x08\ -\x20\x82\x0a\x00\x02\x88\xa0\x02\x80\x00\x42\x57\x50\x98\x80\x26\ -\x00\x10\x40\x68\x0a\x4c\x7c\x75\x93\x50\x45\x00\x02\x08\x55\x41\ -\x84\xb1\xb7\x06\x4b\x38\x8a\x10\x40\x00\xa1\x28\xa8\x15\xcb\x6f\ -\x68\x28\x60\x29\x42\x16\x03\x08\x20\x64\x05\x8e\x12\x7e\x20\xca\ -\x96\xb5\x1a\x49\x10\x20\x80\x90\x14\x88\x9b\x7a\x41\x18\x2e\x6c\ -\x9c\x08\x51\x80\x00\x42\x28\x10\x11\x92\x2a\x83\x32\xeb\xad\xb8\ -\xe0\xc2\x00\x01\x04\x57\xc0\xcc\x1d\xa2\x00\xa4\x6a\x32\x40\x1c\ -\x37\x7e\xb8\x38\x40\x00\xc1\x18\x8c\x16\x36\x99\x40\xca\xd9\xd2\ -\x43\x46\x0b\x48\x8b\x46\x31\x41\x25\x00\x02\x08\xaa\xa0\x2a\x3a\ -\x50\x1e\x48\x19\xe5\xa6\xd9\xa7\x17\x83\x04\x78\x79\x38\x20\x32\ -\x00\x01\x04\x55\xe0\x20\x69\x06\xa2\x74\x42\x05\xb2\x53\x2a\xc0\ -\xe2\x92\xae\x10\x19\x80\x00\x82\x2a\x60\x76\x67\xd7\x03\x52\x02\ -\x71\xf1\xda\xaa\xca\x40\x06\x13\x5f\x09\xd4\x04\x80\x00\x82\x3b\ -\x46\x94\x5d\x1d\x48\x96\xcb\xda\x39\x81\x7d\x94\x0c\x73\x03\x40\ -\x00\xc1\x15\x04\x28\x19\x26\x02\xa9\xb0\x58\x20\x21\xc8\x2d\x9c\ -\x0a\x13\x07\x08\x20\x44\x38\x30\x4a\x89\x41\x39\x2a\x9a\xfc\x88\ -\x70\x00\x08\x20\xa4\x90\x64\xce\x91\xd0\x07\xab\x8c\xc9\x93\x43\ -\x88\x02\x04\x10\x72\x5c\x28\xaa\xf1\x45\x82\x7c\xc8\xe6\x89\x24\ -\x08\x10\x40\x28\xb1\x29\xee\xc3\x23\xd8\x50\xca\x6a\x8e\x2c\x06\ -\x10\x40\xa8\xe9\x21\xc8\xa0\xb2\x8e\xc5\x1f\x45\x08\x20\x80\xd0\ -\x52\x94\xb5\x70\x96\x34\xaa\x08\x40\x00\xa1\xa7\x49\xce\x60\x34\ -\x01\x80\x00\x22\x98\xaa\x01\x02\x88\xa0\x02\x80\x00\x22\xa8\x00\ -\x20\x80\x08\x2a\x00\x08\x20\x82\x0a\x00\x02\x0c\x00\xe9\xc9\xd1\ -\x39\xb5\x6f\x18\xa2\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\ -\x82\ -\x00\x00\x82\x74\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x01\xa4\x00\x00\x01\xa4\x08\x03\x00\x00\x00\x49\x92\xa1\x07\ -\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xaf\xc8\x37\x05\x8a\xe9\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x03\x00\x50\x4c\x54\x45\xb9\xd5\xb9\ -\xa7\xca\xa7\x47\x48\x48\xd3\xd3\xd3\x37\x87\x37\xcb\xcb\xcb\xc3\ -\xc3\xc3\xbb\xbb\xbb\xb3\xb3\xb3\x07\x07\x07\xab\xab\xab\xa3\xa3\ -\xa3\x37\x37\x37\x9b\x9b\x9b\x86\xb6\x86\x93\x93\x93\x48\x92\x48\ -\x8b\x8b\x8b\x83\x83\x83\x7b\x7b\x7b\xff\xff\xff\x73\x73\x73\xc2\ -\xdb\xc2\x75\xac\x75\x6b\x6b\x6b\x99\xc2\x99\x63\x63\x63\x5b\x5b\ -\x5b\x53\x53\x53\x28\x28\x28\xf5\xf5\xf5\x26\x7d\x26\xf9\xf9\xf9\ -\xf1\xf1\xf1\x63\xa2\x63\xed\xed\xed\xe9\xe9\xe9\xe5\xe5\xe5\xe1\ -\xe1\xe1\x5b\x9d\x5b\xdd\xdd\xdd\xd9\xd9\xd9\xcb\xe0\xcb\x43\x8f\ -\x43\x6a\xa6\x6a\x54\x98\x54\x94\xbf\x94\x7b\x9b\x7b\x6d\x8a\x6d\ -\x7d\xb1\x7d\x2b\x80\x2b\xb1\xc2\xb1\x7b\x8c\x7b\xbf\xc8\xc0\x4e\ -\x6c\x4e\x6d\xa7\x6d\xc0\xc7\xc0\x9f\xb6\x9f\x51\x97\x51\x95\xab\ -\x95\x63\x78\x63\x8b\x99\x8b\x42\x57\x42\xa0\xa0\xa0\xd5\xd5\xd5\ -\x52\x81\x52\x95\x96\x95\xa5\xa5\xa5\x85\x85\x85\x2a\x39\x2a\x54\ -\x57\x54\xe6\xe7\xe6\x65\x65\x65\xc5\xc5\xc5\x75\x75\x75\xb5\xb5\ -\xb5\x90\x90\x90\xda\xdb\xda\xb0\xb0\xb0\xe2\xe3\xe2\x85\xa9\x85\ -\x68\xa5\x68\x4d\x94\x4d\x3c\x8b\x3c\x5d\x9e\x5d\x40\x8d\x40\xea\ -\xeb\xeb\x71\xaa\x71\x66\xa3\x66\x58\x9b\x59\xa2\xc7\xa2\xab\xcd\ -\xab\x3e\x42\x3f\x56\x9a\x56\x30\x30\x30\x4f\x96\x4f\x43\x43\x43\ -\x8c\xba\x8c\x90\xbc\x90\x9e\xc5\x9e\x6f\xa9\x6f\x62\x8e\x62\x32\ -\x84\x32\x23\x23\x23\x2f\x82\x2f\x15\x15\x15\xdf\xde\xde\xc1\xc1\ -\xc1\xef\xef\xef\xfc\xfd\xfd\xd0\xcf\xd0\xf4\xf3\xf4\xef\xf0\xef\ -\xe8\xe7\xe8\xf3\xf3\xf3\xd8\xd7\xd8\xe3\xe3\xe3\xc8\xc7\xc8\x78\ -\x78\x78\xeb\xec\xec\xdb\xda\xda\xeb\xeb\xeb\xf8\xf8\xf8\xf6\xf7\ -\xf6\xa0\x9f\xa0\xe4\xe3\xe4\xf7\xf6\xf6\xe3\xe4\xe4\x86\x86\x87\ -\x90\x8f\x90\xec\xec\xec\xd7\xd8\xd7\xf0\xf0\xf1\xfb\xfb\xfb\xa6\ -\xa6\xa7\xe8\xe8\xe8\xa7\xa6\xa6\xd6\xd5\xd6\xf0\xef\xf0\xae\xae\ -\xae\xdb\xdc\xdc\xe4\xe4\xe4\xf0\xf0\xf0\xbf\xbf\xbf\xf7\xf7\xf7\ -\xf8\xf8\xf9\x68\x67\x68\xc8\xc8\xc8\xeb\xea\xea\xdb\xdb\xdb\xc9\ -\xc9\xc9\xf3\xf4\xf4\xe7\xe7\xe7\xb0\xaf\xb0\xb8\xb7\xb8\x81\x81\ -\x81\xfb\xfc\xfc\xf8\xf7\xf7\xf3\xf2\xf2\xd8\xd8\xd8\xe0\xe0\xe0\ -\x58\x57\x58\xec\xeb\xeb\xce\xcd\xcd\xf4\xf4\xf4\x96\x96\x96\x69\ -\x69\x69\xdf\xe0\xdf\xef\xee\xee\x56\x55\x55\xe7\xe8\xe7\x71\x71\ -\x71\x98\x97\x98\x6e\x6e\x6d\xc6\xc6\xc7\xb6\xb6\xb7\xb9\xb9\xb9\ -\x99\x99\x99\x60\x5f\x60\xf3\xf4\xf3\x97\x97\x97\xb8\xb8\xb8\xd6\ -\xd7\xd7\xa8\xa8\xa8\x7f\x7f\x7f\xfd\xfc\xfd\xfd\xfd\xfc\x66\x66\ -\x67\x50\x4f\x50\x9e\x9f\x9e\xdc\xdb\xdb\xc6\xc6\xc6\xb7\xb7\xb6\ -\xe0\xdf\xdf\x89\x89\x89\x76\x76\x77\x61\x61\x61\xd1\xd1\xd1\x59\ -\x59\x59\x91\x91\x91\x4c\x4c\x4c\xce\xce\xce\xdf\xdf\xdf\x8e\x8e\ -\x8e\xa9\xa9\xa9\x60\x60\x60\xe7\xe6\xe6\xe3\xe2\xe2\xdc\xdc\xdc\ -\x9d\x9d\x9d\x87\x87\x86\xa1\xa1\xa1\xb1\xb1\xb1\xa8\xa7\xa8\x70\ -\x70\x70\x5e\x5e\x5d\x6f\x6f\x6f\x78\x77\x78\x58\x58\x58\x79\x79\ -\x79\x33\x33\x33\x7e\x7e\x7e\x6e\x6e\x6e\x98\x98\x98\x88\x88\x88\ -\x77\x76\x76\x66\x66\x66\xbe\xbe\xbe\xae\xae\xad\x8d\x8d\x8d\x5e\ -\x5e\x5e\xfb\xfb\xfa\x68\x68\x68\x2c\x2c\x2c\x4e\x4e\x4e\x51\x51\ -\x51\x56\x56\x56\x2e\x2e\x2e\x60\xa0\x60\x3e\x3e\x3e\x88\x87\x88\ -\x50\x50\x50\x61\xa0\x61\xd0\xd0\xd0\x3c\x3c\x3c\x40\x40\x40\x5f\ -\x9f\x5f\x2f\x2f\x2f\xb2\xd1\xb2\x5e\x9e\x5e\xaf\xcf\xaf\xaf\xd0\ -\xaf\x1c\x1c\x1c\x1f\x1f\x1f\x20\x20\x20\xff\xff\xff\x79\x7a\x58\ -\x7f\x00\x00\x01\x00\x74\x52\x4e\x53\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\x00\x53\xf7\x07\x25\x00\x00\x7d\ -\xee\x49\x44\x41\x54\x78\xda\x62\xf8\x3f\x0a\x06\x3d\x00\x08\x20\ -\x86\xd1\x20\x18\xfc\x00\x20\x80\x46\x23\x69\x08\x00\x80\x00\x1a\ -\x8d\xa4\x21\x00\x00\x02\x68\x34\x92\x86\x00\x00\x08\xa0\xd1\x48\ -\x1a\x02\x00\x20\x80\x46\x23\x69\x08\x00\x80\x00\x1a\x8d\xa4\x21\ -\x00\x00\x02\x68\x34\x92\x86\x00\x00\x08\xa0\xd1\x48\x1a\x02\x00\ -\x20\x80\x46\x23\x69\x08\x00\x80\x00\x1a\x8d\xa4\x21\x00\x00\x02\ -\x68\x34\x92\x86\x00\x00\x08\xa0\xd1\x48\x1a\x02\x00\x20\x80\x46\ -\x23\x69\x08\x00\x80\x00\x1a\x8d\xa4\x21\x00\x00\x02\x68\x34\x92\ -\x86\x00\x00\x08\xa0\xd1\x48\x1a\x02\x00\x20\x80\x46\x23\x69\x08\ -\x00\x80\x00\x1a\x8d\xa4\x21\x00\x00\x02\x68\x34\x92\x86\x00\x00\ -\x08\xa0\x61\x18\x49\x5a\x10\x20\x86\x0e\x18\x50\xc0\x50\xf2\x11\ -\x40\x00\x31\x0c\xbb\xc8\xc1\x1e\x43\x18\x91\x04\x02\x3f\x7e\x30\ -\x68\x0d\x05\x9f\x01\x04\xd0\xd0\x8c\xa4\x2a\x9c\xb1\x83\x33\x8e\ -\xb0\x45\x11\x18\x0c\x01\xef\x02\x04\xd0\x90\x8b\xa4\x05\x6c\x1b\ -\xb2\x2b\xfa\x14\x15\x44\xc0\xbc\xe5\x5a\xd8\x00\x31\x71\xf4\x63\ -\xa3\xb5\x25\x24\x96\x06\x7f\x10\x00\x04\xd0\x90\x8a\xa4\x93\x1b\ -\xb2\x35\xe6\x29\x2a\x2a\xf6\xf5\xe5\xe4\xb4\x54\x61\x8f\x20\xe2\ -\xe2\x08\x14\x39\xac\xac\x1b\xf6\xda\x5a\xff\xf8\xf1\xeb\xf7\x60\ -\xf7\x37\x40\x00\x0d\x95\x48\xaa\xdc\xcf\x5e\xaa\x5c\x34\x6f\x1e\ -\x2c\x8e\x72\x56\x28\x87\xf9\x13\x1b\x47\xb8\x4a\x3a\xd6\x69\xd3\ -\x26\xb3\x71\xf3\xdb\x26\x0f\xf2\x32\x0f\x20\x80\x18\x86\x46\x0e\ -\x5a\xbc\x64\xc9\x92\x7c\x68\x1c\x29\x16\x14\x14\x80\x22\x49\x59\ -\x79\xd5\x72\xa2\x22\x09\x57\x1c\x01\xc1\xe4\xc9\x93\xd7\xad\xf3\ -\xf4\xe4\xb6\xcd\x1e\xcc\xfe\x07\x08\xa0\x41\x1f\x49\xe7\x1e\xb3\ -\xa9\xd4\xc9\xc9\x01\x23\x09\x12\x47\x7d\x2d\x7d\x7d\x05\x2b\x80\ -\x40\xb9\xa9\xa9\xa9\xd2\x9d\xb2\x38\xfa\xfd\xfb\xf7\xfe\xfd\xfb\ -\x3d\xd9\x80\x60\xeb\x5e\x2e\xb5\xc1\x1a\x06\x00\x01\x34\xb8\x23\ -\x89\x99\xdd\x61\x42\x1d\x30\x8a\x40\x91\x54\x04\x8d\xa4\xbe\x2e\ -\x68\x3e\x6a\x5a\x54\x39\x65\xca\x14\x7f\x52\xa3\x88\x01\x29\x8a\ -\x7e\x01\xc1\x7e\x4f\x70\x24\x65\x67\x67\xf7\xb7\x5d\xf4\x1c\x94\ -\xc1\x00\x10\x40\x83\x38\x92\x58\x1f\x97\x4c\x98\x50\x0d\x8e\x23\ -\x60\x3e\xda\x0c\xaf\x90\x60\x71\xd4\x54\x59\x19\x36\x65\x8a\x0a\ -\x4a\x99\x27\x46\x42\x3e\x02\x66\x23\x50\x2c\x45\x47\x43\xe3\x28\ -\xbb\xff\xf1\xe3\xc7\xdd\xbc\xec\x83\x2f\x24\x00\x02\x68\xb0\x46\ -\xd2\xe7\xc7\xee\x55\x55\x55\x73\xeb\xc0\x19\x09\x1c\x47\x45\xa0\ -\x38\x6a\x69\xe9\xea\xcb\x59\x01\x8b\x23\x60\x46\x52\x69\x57\x29\ -\xac\xc7\x1d\x49\xf8\xf3\x11\x24\x8e\x80\x00\x1e\x47\x8f\xd9\xd9\ -\xd9\xbb\x79\x07\x5b\x05\x05\x10\x40\x83\x32\x92\xd4\x1e\x97\x28\ -\x28\x00\xe3\x68\x42\x5d\x35\xa4\xac\xcb\xdf\x9c\x8f\x9a\x8f\x9a\ -\x94\x9b\x2a\x81\xa5\x9d\x8a\x8a\x4a\x7b\x7b\xfb\x8c\x73\xcb\x89\ -\xcc\x47\x3f\x7e\xa0\x95\x75\x90\x38\x8a\x8e\x06\xc5\x51\x3f\x24\ -\x8e\xd8\x37\x6c\xd8\xb0\x9d\x57\x73\x30\x85\x07\x40\x00\x0d\xbe\ -\x48\xaa\x63\x63\x55\x00\x82\x89\x55\x73\x81\x85\x5d\x35\xb4\xcd\ -\x00\xca\x48\xc0\x7c\xd4\xd7\x07\xca\x47\x2b\x56\x34\x81\x1a\x0d\ -\xa0\xb2\x0e\x18\x47\xab\x66\x9c\x3b\xa7\xea\x4f\x54\x7d\x84\x14\ -\x47\xbf\x7e\xff\x46\x44\x51\x74\x34\x63\x34\x3c\x8e\xd8\x37\x6c\ -\xdd\x3a\xeb\xc0\xfa\xfb\x5c\x4b\x06\x4d\x90\x00\x04\xd0\x60\x8b\ -\xa4\x6d\x8f\x55\x9e\x43\xe2\xa8\x6a\x02\x30\x92\x20\x6d\x06\x60\ -\xa3\xa1\xa8\x48\x71\x1e\xa4\x87\xb4\x02\xda\x68\x00\x17\x76\x2a\ -\x2a\xa0\x38\x3a\xa7\xaa\xaa\xba\x9c\x94\x38\x42\xcd\x46\xa0\x38\ -\x62\x64\x84\x67\xa4\xad\xb3\x80\x71\xb4\xde\x9b\xe3\xaa\xd3\xb4\ -\x41\x12\x28\x00\x01\x34\xa8\x22\x69\x49\x3f\x73\xeb\x73\x70\x1c\ -\x29\x80\xf2\x11\xb0\x3e\x02\xb7\x1a\xf2\xf3\x21\x15\x52\x9f\x62\ -\x41\xce\x0a\x70\xa3\x41\xb9\x7c\x11\x24\x8a\x80\x71\xe4\x0e\x8a\ -\x22\xd5\xda\x7a\x8c\x58\xc2\xdb\xf2\x46\x8e\x23\x46\x28\x78\x0c\ -\x29\xeb\xb6\x6e\x05\xc7\x11\x10\x5c\xb9\x7f\xb9\x6f\x30\x84\x0b\ -\x40\x00\x0d\xa2\x48\x9a\xf1\x38\x67\x4e\x2b\x34\x92\xaa\xe6\x82\ -\x6a\x24\x60\x1c\x2d\x91\x5b\xb2\x19\x1a\x47\x2d\xc0\xfa\x68\x05\ -\xb4\x87\x04\xca\x48\xe0\x0a\x69\x06\x28\x23\xf5\xf6\xf6\xd6\x97\ -\x9c\x3f\xef\x8b\x2f\x92\x70\x46\x11\x3c\x8e\x18\x19\xc1\x91\xb4\ -\x15\x9c\x8f\x40\x91\x74\xe5\x8a\x9f\x5f\x1b\xff\xa9\x81\x0f\x19\ -\x80\x00\x1a\x34\x91\xd4\xcc\xa6\x00\x8a\xa2\x56\x50\x1c\x55\x4d\ -\x04\x97\x75\xd0\xd6\xf7\x74\x50\xe3\x1b\x94\x91\x60\x1d\x24\xe5\ -\x72\x50\x59\x07\x69\x33\xcc\x00\x97\x75\xbd\xb5\xc0\x38\x3a\xaf\ -\xa6\xa1\x45\x4c\x3e\xfa\xf5\x0b\x7b\x1c\x45\x31\x46\x81\x33\x12\ -\x2c\x8e\x38\x80\x71\x34\xb3\xbb\xfb\xc9\xf1\xc9\x03\x1d\x36\x00\ -\x01\x34\x48\x22\x69\xda\xc9\xdc\x39\x73\xc0\xf9\xe8\x39\xb8\x5d\ -\x07\x6e\x35\x40\x23\x09\x92\x8f\x80\x91\x94\x03\xad\x90\x80\x91\ -\x34\x05\xd6\x68\x00\x15\x76\xbd\xbd\x25\xbd\xc0\x28\x52\x53\x5b\ -\x70\x4a\x8b\x60\x1c\xa1\xe7\x23\xa4\x48\x02\x81\x0d\xb3\x10\x71\ -\x04\xcc\x49\xa0\x48\x7a\xc2\xe5\x73\x65\x60\x43\x07\x20\x80\x06\ -\x43\x24\xcd\x61\xd3\xd8\xb5\x13\x14\x45\x90\x38\x02\xc5\x12\xac\ -\x42\x5a\x82\x34\xd2\xd0\xd7\x05\x8e\x23\x50\xc3\x6e\x0a\x52\xc3\ -\xee\x1c\x28\x92\x4a\x40\x91\xb4\xe0\xe0\xc1\x53\xbe\x04\xda\xde\ -\xbf\x51\xf3\x11\x23\x6a\x14\x01\xc1\xac\xf5\xb0\x0a\xc9\xcf\xcf\ -\xaf\x1b\x18\x47\x5c\x40\x70\x66\x69\x4d\xeb\x00\x06\x10\x40\x00\ -\x31\x0c\x82\x28\x52\xdb\xb5\x2b\x77\x67\xeb\x1c\xa4\x38\x9a\x0b\ -\xca\x46\xe0\x0a\x69\x3a\x34\x1f\x41\x3a\x48\xe0\x8c\x54\xde\x04\ -\x2d\xec\x0a\xc1\x0d\x3b\x60\x85\x54\xeb\x5f\x02\x8a\xa3\x05\xa7\ -\x4e\x65\x69\xf8\xe2\xab\x8f\x30\x5a\x75\x18\x71\x14\x15\x05\xcd\ -\x47\xc0\x48\x9a\x39\x13\x16\x47\x67\xb6\x6f\xdf\x72\x79\xe0\xa2\ -\x09\x20\x80\x06\x3c\x92\xd8\x4e\xed\xda\xb5\x73\xe7\x1c\x68\x46\ -\x42\xca\x47\x90\xfa\x68\x33\xac\xac\xeb\x83\xd6\x47\xca\xe5\xe5\ -\xd0\x38\x5a\x55\x08\x6e\x7c\x83\xf2\x11\x3c\x8e\xb2\x34\x34\x2e\ -\xe0\xcb\x46\x68\xbd\x23\xcc\x28\x8a\x4a\x4e\x4e\xf6\x46\x2a\xeb\ -\x20\x71\x74\x66\xfb\xd5\xb6\x36\xfe\xcb\x03\x15\x46\x00\x01\x34\ -\xc0\x91\xc4\x96\x25\xb2\x13\x14\x49\xad\xad\x48\x71\x04\x6e\xd7\ -\x41\x1a\xdf\xa0\x0e\xd2\x3c\x68\x3e\x02\x15\x76\xa0\x91\x06\x58\ -\x3e\x6a\x07\xb7\x19\x54\xc1\x0d\x3b\x50\x1c\x2d\x3f\x75\x4a\x03\ -\x18\x47\x17\xa6\x6a\x11\x59\x1d\xe1\x88\xa3\xe4\x64\x78\x61\xf7\ -\x04\x96\x91\xae\x76\xb4\xb5\x39\x73\x1f\xe7\x1e\x98\x50\x02\x08\ -\xa0\x01\x8d\x24\xcf\xa9\x22\xb9\xb9\xa0\xb2\x6e\x0e\x72\x59\x07\ -\x2c\xec\xa0\x71\x04\x9a\x42\x02\xe5\xa4\x16\x50\x9b\x01\x3a\xaa\ -\xba\xa8\x32\x0c\xdc\x41\x6a\x9f\x71\x0e\xd2\x68\x00\xb5\xec\xd4\ -\xc0\x19\x09\x94\x8f\x40\x91\x54\xb1\x4d\x0c\x5b\x24\xfd\xc6\x91\ -\x8d\x18\xd1\xa2\x08\x14\x4b\xa0\xd6\x37\x3c\x8e\xb6\x9f\x01\xe6\ -\x23\x67\x67\x6e\x20\x38\xed\x3c\x10\xe1\x04\x10\x40\x03\x18\x49\ -\xcc\xcc\x22\x22\x22\xbb\x80\x00\x5c\x1f\x41\xf2\xd1\xc4\x89\x73\ -\x81\xed\xba\x3a\xe8\x48\x43\x51\x3e\xa4\xb0\x83\x44\x12\xb8\xcd\ -\x00\x6e\xd8\x4d\x01\xb7\xbe\xdd\xc1\x85\x1d\x30\x1f\x95\x80\xf3\ -\xd1\xc1\x53\xa0\xb2\x0e\x14\x47\x15\x9a\xcd\x62\xd8\xe2\x08\x7b\ -\x93\x01\x33\x8a\x92\x25\x25\xc1\x15\x12\x3c\x1f\x01\x0b\xbb\x0e\ -\x48\x24\x5d\xb6\x7f\x30\x00\xa3\xaf\x00\x01\x34\x60\x91\xa4\xea\ -\x09\x8a\xa2\x5c\x50\x24\x81\xeb\x23\x48\x24\x55\x41\x46\x83\xa0\ -\x6d\x6f\x78\xab\xa1\x00\x1c\x47\x40\x00\x1d\x55\x85\xf4\x62\xc1\ -\x1d\xa4\xfa\x12\x7f\x70\x46\x5a\x0e\xca\x48\xd0\x38\xd2\x44\xc4\ -\x12\x8e\x36\x03\x96\x6c\x04\x8f\x21\x60\x1c\x49\x4a\xce\x9c\x09\ -\xcd\x48\xb0\x38\x02\x47\xd1\x65\x7b\xfb\x9a\xbd\x2e\x74\x1f\x7c\ -\x05\x08\xa0\x01\x8a\xa4\xba\xfe\x09\x22\x90\x7c\x04\x8c\x26\x68\ -\x3e\x7a\x0e\xee\xc5\xc2\x86\xbe\x81\x8d\x86\x7c\x58\x1c\x75\x75\ -\xe5\x40\xa7\x62\xcb\x51\x86\x55\x55\x7b\x61\x15\x12\x3c\x1f\xed\ -\x9b\x0a\x8c\xa3\xf9\xf3\x9b\xa1\x25\x1e\xd2\x80\x2a\xfe\x96\x37\ -\x22\x8e\xa0\x00\xa9\x61\x77\x15\x98\x8d\xa0\x91\x54\x53\x53\xb3\ -\xf7\xe2\x16\xc1\x22\xfa\x86\x16\x40\x00\x0d\x4c\x24\x79\xaa\x8a\ -\x88\x40\x32\x12\xb4\x61\xf7\x1c\x96\x8f\xaa\x20\x8d\x6f\xf0\xf4\ -\x04\xb0\x42\x82\x34\xec\xba\x60\x15\x12\xac\x87\xb4\x6a\x06\x34\ -\x8e\x6a\x4b\xce\x43\xf3\x11\xb4\xd1\x00\xce\x47\xf3\xe7\x6f\xeb\ -\xec\x04\xc6\xd2\x0f\x06\x94\x38\xc2\xd3\x62\x48\x46\x8f\x23\x20\ -\xe0\x82\x54\x48\xdb\x61\x15\xd2\x65\x60\x46\xaa\xd9\xbb\xf7\xe2\ -\x45\x5e\xde\xa7\xf4\x6d\xe8\x01\x04\xd0\x40\x44\xd2\x7c\x66\x11\ -\x08\x00\xe5\xa3\x39\xd0\xc2\x0e\xde\xb0\x9b\x00\x8e\x22\x39\xf8\ -\xba\x93\x16\x50\x1c\xe5\xc0\x7b\xb1\xe0\xd1\xa0\x42\x70\x61\xd7\ -\x0b\xee\xc5\x82\xe2\xe8\x20\xa8\x87\x04\x89\x24\x70\x14\x15\x03\ -\x23\xc9\xe1\x33\xae\x7c\x14\x4d\x38\x1f\x01\x81\x9e\x1e\x34\x8e\ -\xae\xb6\x39\xc3\x0b\x3b\x48\x1c\xf1\xf2\x6e\x14\xa2\xe7\x44\x3b\ -\x40\x00\xd1\x3f\x92\x96\x64\xef\x82\xc7\xd1\xae\x9d\xbb\x20\xd9\ -\x08\x6d\xa0\x01\x18\x45\x4b\x60\x9d\x58\xa4\xe9\xf2\x26\x68\x3e\ -\x2a\x84\x97\x75\xfe\x90\x91\x06\x68\xc3\xae\x07\x92\x91\xc0\x51\ -\xe4\xc0\xcc\x0c\x8f\x25\x42\xf9\x08\x4b\x1c\xe9\x81\x00\x17\xa4\ -\x83\x04\x8b\xa3\xcb\xe0\x7c\x04\x8a\xa4\x8d\xf7\xef\x6f\x69\xc8\ -\xa1\x5b\x90\x01\x04\x10\xdd\x23\xa9\xb4\x1d\x16\x45\xa0\x0e\xd2\ -\x1c\x68\x2f\x16\x9a\x8f\xaa\xea\x60\xeb\x4e\x8a\xe0\x23\x0d\x39\ -\x90\xe9\x89\xa6\x26\x78\x0f\x09\x36\xaa\x0a\x6b\x7c\x43\x2a\x24\ -\x0d\x50\x7d\x04\xad\x90\x80\x91\xc4\xcc\x0c\x8b\x25\xd4\xee\x11\ -\x9e\xa2\x0e\x3d\x8e\xf4\xf4\x80\x19\xa9\xa3\xcd\x19\x5a\xd8\xd9\ -\x83\x0b\x3b\x70\x3e\xda\xb8\x71\xcb\xd2\xa5\x87\xe8\xd6\x1c\x07\ -\x08\x20\x3a\x47\x52\x53\xa9\x08\x1c\x80\x2b\x24\xc4\x68\x10\x78\ -\x9a\x0f\x5a\xd8\xa1\x94\x75\x2b\x20\x23\x76\xe5\x8b\xc2\x60\x43\ -\xdf\xe7\x90\x2b\x24\xd0\x48\xc3\x29\x50\xc3\x6e\xea\x54\x5f\x50\ -\x1c\x35\x6f\xdb\xb6\x0d\x12\x47\xc0\x58\x42\xb4\xeb\x48\xaa\x8e\ -\xf4\x60\x00\xa5\xb0\x83\x56\x48\xc0\x38\xba\xbf\x65\xcb\x16\x27\ -\x27\xfe\xbb\xb5\xf4\x09\x35\x80\x00\xa2\x6f\x24\xb1\xcd\x83\x47\ -\xd1\x4e\x70\xdb\x1b\x94\x91\x60\xd3\x7c\xf0\xd9\x09\xf0\xc8\x77\ -\xd1\x3c\xe4\x46\x43\x13\xf2\xc8\x37\x24\x23\xd5\xf7\x82\x1b\xdf\ -\x0b\x60\x85\x5d\x0f\x34\x1f\x15\x6f\x83\x96\x76\xcc\xcc\x47\x8f\ -\x9e\x24\x3e\x1f\x61\x89\x22\x3d\xbd\xa4\x0e\xcc\x7c\x04\x8e\xa4\ -\xa5\x4b\x9d\xf8\xf9\xf9\x05\x2f\xd2\x25\xd8\x00\x02\x88\x9e\x91\ -\xd4\xc4\x2a\x82\x12\x47\xa0\x81\x06\x50\x46\x52\x78\x8e\x18\x0d\ -\x92\x83\x8e\x06\x41\x46\xec\x5a\xe0\xd3\xe5\xb0\x91\x06\x50\xdb\ -\xdb\x5d\x55\x15\x3e\xf2\x0d\x8c\xa4\x2c\xf0\x88\x1d\x30\x23\x01\ -\x23\x09\x2d\x8e\x3e\xe7\x9d\xfc\x81\xbf\x07\x8b\x27\x17\x01\xa3\ -\x08\x08\xb0\xe5\x23\x50\x1c\x81\x23\xe9\xf8\xf1\xbb\x25\x74\x08\ -\x38\x80\x00\xa2\x63\x24\xb1\x2a\x23\xe2\x08\xd4\xf8\x9e\x83\x34\ -\xaa\x0a\x5e\x76\x02\x1b\xb1\x83\x57\x48\x8a\x5d\x5d\x28\x6b\x1a\ -\x60\x43\xdf\x90\x11\xbb\x12\x78\x27\x16\x54\xd8\xed\xab\x00\x67\ -\x24\x50\x1c\x39\xc0\xe3\xe8\x33\x24\x96\xd0\xe3\x88\x60\x8b\x01\ -\x12\x45\xe0\x48\x4a\xc2\x68\xd8\x81\x0a\x3b\x70\x14\x01\x23\xc9\ -\xa7\xb1\xe1\x0c\xed\x43\x0e\x20\x80\xe8\x16\x49\x75\x6c\x22\x48\ -\x71\x04\x1a\x54\x45\x1e\xb1\x53\x40\x9e\x8a\x85\xad\x69\x50\x2c\ -\x80\x8c\x06\x81\xf3\x11\x64\x4d\x03\xb0\xd1\x00\x6d\xd9\x41\x06\ -\x1a\xd4\x4e\x41\x1b\x76\xd0\x38\x82\x34\x1a\x20\x35\x12\x38\x23\ -\x9d\x5c\xcc\xfa\x03\x67\x24\xe1\x8d\x22\x18\x48\x84\xe5\xa3\xbd\ -\xb0\x86\xdd\x1a\x44\x1c\xf9\x34\x9e\x3e\x7d\x77\x1e\xad\xc3\x0e\ -\x20\x80\xe8\x15\x49\xcc\xa7\x44\x50\x23\x09\xda\x43\x42\xcc\x20\ -\x81\x46\xec\xea\xa0\x23\xdf\x90\x25\x0d\x7d\xb0\x19\x24\xf0\x02\ -\x2e\xe8\x1a\x3b\x77\x68\xeb\xdb\x1f\x65\x7a\xe2\x02\xb4\xd1\x00\ -\x6f\xd8\x81\xf3\x51\x1e\x38\x92\x58\x7f\xe1\xc8\x47\xc9\x44\x44\ -\x52\x22\x10\x70\x03\xe3\xa8\x06\xda\xf8\xe6\x85\xe4\x23\x70\x24\ -\x1d\x3f\x0e\x8a\xa3\xa7\x4f\x85\x69\xbd\xe8\x15\x20\x80\xe8\x13\ -\x49\x73\xfa\x15\x90\xe3\x28\x17\x98\x91\x72\xa1\x15\x12\xa2\x61\ -\x07\x1e\x55\x85\xb6\xec\x60\x8d\x06\xe8\x54\xec\x22\x58\xe3\x7b\ -\x15\x6c\x4d\x83\x3f\xac\xd1\x00\x6e\x7d\xef\x83\x76\x90\x80\x85\ -\x9d\x03\x2c\x1f\x41\xe2\x08\x14\x49\xd3\x7e\x11\xd9\x81\xc5\x92\ -\x8d\xc0\x00\xd8\x68\xa8\xb9\x88\xa8\x90\x80\x0d\x3b\x27\x68\x1c\ -\x81\x22\x49\x50\xf0\xd0\x25\xda\x4e\x08\x02\x04\x10\x5d\x22\x69\ -\x06\x33\x6a\x36\xda\x09\x99\xe7\x6b\x45\x0c\x07\x4d\x80\x37\x1a\ -\xc0\xbb\x27\xc0\x6b\xec\xba\x60\x6b\x1a\xa0\xeb\x4e\x80\x8d\x86\ -\x55\xee\xf0\x0a\x49\x0d\x5c\x21\x41\x1a\x0d\x3d\xe0\x7c\x04\x6e\ -\x7d\x3b\xc0\x2b\xa4\xa3\x79\xc0\x48\x5a\xbc\x18\x18\x49\xac\x93\ -\xb1\x8c\xd6\xa1\x8e\xa7\xe2\xcc\x45\x10\x00\x2e\xec\x10\xf5\x11\ -\x3c\x1f\x35\x82\xa2\x08\x08\x0e\xdd\x9c\x4a\xcb\xf0\x03\x08\x20\ -\x7a\x44\xd2\x49\x15\xe4\x38\xda\x29\x02\xcf\x47\xd0\x28\x52\x00\ -\x37\xec\xaa\x61\x23\x0d\xd0\xe9\x89\x82\x02\x58\x0f\xa9\x12\xbc\ -\x7e\xab\x1d\x7d\x54\x55\x0d\x51\xd6\x21\x46\xec\x10\x8d\x06\x48\ -\x3e\x02\xc5\x11\xeb\xb4\xc9\x38\x0b\x3b\x5c\x8d\x3a\x94\x38\x4a\ -\x4c\x44\x6a\x34\x00\x1b\x76\xd0\x38\x82\x14\x76\xa0\x38\x7a\xf0\ -\xe0\xee\x4c\x1a\x06\x20\x40\x00\xd1\x21\x92\xfa\x77\x89\xa0\x02\ -\xa4\xd9\x09\x70\x24\xcd\x9d\x0b\x1f\x0d\x82\xac\x69\x80\x0e\x35\ -\xac\x40\xde\x3c\xa1\xd2\x5e\x58\x08\xa9\x8f\x54\x81\x71\xe4\x0f\ -\x2a\xeb\x16\xa0\x8c\x06\x81\x5a\xdf\x88\xc6\x37\x24\x92\x60\x71\ -\x34\xcd\x34\x9a\xe8\x26\x43\x12\x96\x38\x4a\xe4\x03\x45\xd2\x46\ -\x48\xeb\x1b\x5a\xd6\x81\x2a\xa4\xa7\xe0\x38\x12\x3c\x74\xe8\xc1\ -\x25\x97\x07\xb4\x0b\x41\x80\x00\xa2\x79\x24\x2d\x99\x8c\x19\x45\ -\x3b\xc1\xeb\xb7\x20\x6b\xec\x26\xa2\xac\x0d\x9a\x0e\xdf\xcd\xd7\ -\x05\x5d\xbe\x05\xac\x90\xc2\xc2\x10\xa3\x41\xe7\x60\xa3\xaa\x0b\ -\xd4\x50\x47\xec\x40\x8d\xef\x6d\x9d\x48\x15\x12\xa4\x3e\x02\xc5\ -\xd1\xb4\x69\x93\x4b\x2d\xc8\x8e\x23\xbe\x44\x3e\x10\x80\x17\x76\ -\x90\x96\x1d\x30\x1f\xf9\xc0\xf2\x11\x30\x23\x5d\x6a\x70\x71\x11\ -\x5d\x41\xab\x30\x04\x08\x20\x5a\x47\xd2\x85\x05\x98\x71\x04\xec\ -\x22\xb5\xc2\x1b\x0d\x55\xc8\xeb\x4e\xf2\xa1\x23\x0d\xe0\x81\x06\ -\xd0\x54\x2c\x10\x41\xf2\x91\x0a\xa8\xd1\xe0\x0e\x6e\x7b\xd7\x42\ -\x47\x1a\xa0\x23\xdf\xe0\x0a\x69\x3e\x10\xc0\x1b\x0d\x47\x8f\x42\ -\x22\x09\x16\x47\x93\x4b\x27\x97\xae\x33\xc5\x17\x47\xf8\xb2\x11\ -\x1f\x1f\x34\x92\x36\xde\x07\x15\x76\x90\xc6\x37\x28\x92\x4e\x9f\ -\x3e\x0d\xcf\x47\x0d\xc0\x48\x12\x7a\x58\x4a\xa3\x40\x04\x08\x20\ -\x1a\x47\x92\xe7\x12\xb4\x28\x02\xcf\x4f\x20\xcd\x4e\x4c\x84\xb4\ -\xec\xea\xe0\x6d\x06\xc8\x14\x52\x17\x74\xd1\x77\xd3\x22\xc8\x70\ -\x10\x6c\x76\x02\xd8\xae\xab\x3d\x0f\x19\xb1\x5b\x00\x99\x42\x02\ -\xc5\x51\x05\xb8\x83\x84\x94\x8f\x20\x3d\xa4\xc5\xd0\x38\x9a\x5c\ -\x0a\xda\x73\x69\xcc\x48\x78\x94\x01\x33\x8e\xf8\xe0\x00\xa9\xac\ -\x83\xb6\xbe\xa1\x65\x1d\x30\x1f\x5d\x72\x01\xc6\x91\x90\x90\xf0\ -\x7d\xda\x84\x22\x40\x00\xd1\x36\x92\xfa\xd1\xb2\x11\xa8\xf1\x8d\ -\xb2\xee\xa4\x0a\x69\x6d\x10\x7c\xb1\x6a\x1f\xbc\x87\xd4\xb4\xa8\ -\xa9\x12\x3a\x5f\x5e\x08\x89\x23\xc8\x68\xd0\x02\xe8\xf4\x04\x68\ -\x2a\xd6\x17\x3a\x1a\xd4\x89\xe8\x21\x41\x7b\xb1\xc8\x91\xe4\xe9\ -\xc9\x66\x8c\x23\x92\xf0\xb5\x18\xf8\x90\xc0\xfd\xfb\xf0\xb2\x0e\ -\xdc\x41\x82\x17\x76\x1f\x80\x85\x9d\x10\x08\xcc\x9e\x2d\x4c\x93\ -\x60\x04\x08\x20\x5a\x46\x52\x5d\x29\x7a\x1c\x89\x40\x46\xec\x10\ -\x23\x0d\xc0\x7c\x54\x55\x3d\x01\x12\x45\xd3\x97\xc0\xa7\x27\xa0\ -\xa3\x41\x4d\x88\xd1\xa0\x76\x68\x07\xa9\x04\x69\x9a\x0f\xd4\xb2\ -\x03\xe5\x23\x50\x24\xa1\x8e\xd8\x81\xf3\x11\xb4\xb4\x03\xc6\xd1\ -\x3a\xc8\xbe\x58\x46\xec\xf9\x08\x77\x1c\xf1\xa1\x44\x12\xdf\x9a\ -\xa5\xc8\x8d\x06\x58\x1c\x1d\x02\x57\x48\xe0\x28\x9a\xbd\x63\xc7\ -\x6a\x5a\x0c\x3f\x00\x04\x10\x0d\x23\x49\x85\x19\x4b\x14\xe5\x42\ -\x3b\x48\xad\xd0\x4e\x2c\x30\x8e\x10\xbb\x62\x11\x19\x09\xda\xb0\ -\x2b\x87\x75\x90\x66\x40\x76\xb8\x80\xe3\x08\xb6\xa6\x01\xdc\x43\ -\xaa\x80\xb4\xbe\x51\x47\xec\x90\x1a\xdf\xa0\x1a\x69\x3f\x6c\xef\ -\x32\x23\x11\x71\x84\x23\x1b\x81\x01\x6a\x1c\x41\x7a\x48\x87\x3e\ -\x40\xe3\x08\x12\x49\x77\x6f\x68\x50\x3f\x24\x01\x02\x88\x76\x91\ -\xb4\x40\x15\x6b\x3e\xca\x45\x1f\x56\xad\xae\x86\xcf\x4e\x14\x41\ -\x7a\xb1\x39\x39\xc8\xdb\x62\x91\x17\x14\xc3\x96\x06\x1d\x84\x8d\ -\xaa\x4e\x85\xcc\x97\xcf\x47\x69\x7c\x7f\x86\xc7\x11\x28\x1f\x4d\ -\x06\xc5\x91\x27\x78\xef\x72\x3f\x66\x9b\x81\xe8\x6c\x04\x04\x86\ -\x86\x4e\x88\x11\x3b\x58\x59\x77\x09\xd2\x68\x80\xc5\xd1\x5d\xe1\ -\xd5\xac\x54\x0f\x4a\x80\x00\xa2\x59\x24\xe5\xad\xc0\xa8\x8f\x72\ -\x11\x85\x1d\x28\x92\xaa\xc0\x2d\xbb\x6a\xe8\xda\xa0\x25\xf9\xf3\ -\x60\xeb\xb7\xa0\xad\x6f\x70\x27\x16\x69\xbe\xbc\x57\x15\xb6\x79\ -\x02\xd6\x8b\x85\x4d\xc5\x6e\x83\x8f\x34\xc0\x47\xec\xe0\xf9\x08\ -\x29\x23\x65\x67\x3f\x8e\xc2\xd5\xae\xc3\xd3\x62\x80\x47\x91\xa1\ -\x21\x3f\xca\x68\x10\xa4\xf1\x0d\x6d\x34\x40\xe3\xe8\xae\xb0\xb0\ -\x28\xd5\xfb\xb5\x00\x01\x44\xab\x48\x62\x53\x10\x11\xc1\x92\x93\ -\x72\xc1\xb9\x08\x52\xd8\x41\x47\x55\xe1\x6b\x83\x90\x1b\x0d\xca\ -\xb0\x73\x1a\x20\x6d\x06\xc8\x48\x43\x3d\x74\x2a\x16\x58\x1f\xc1\ -\xd7\x06\x41\x7a\x48\x9d\x0e\x9d\xd0\x38\x42\xee\x21\x81\xa2\x08\ -\x18\x47\x9e\x88\x83\x1a\xfa\x8d\xb1\x97\x75\x58\xfa\xaf\xd8\xe2\ -\x08\x14\x4b\x68\x8d\x06\x60\x3e\x6a\x40\x8d\x23\x61\xe1\xc3\x3e\ -\x54\x0e\x4c\x80\x00\x62\xa0\x53\xb3\x0e\x3e\xaa\x0a\x1a\x0c\x82\ -\x54\x48\xb0\x39\x24\xd0\xd2\x20\xd8\x31\x0d\xf0\x86\x1d\xd2\x92\ -\x06\x50\x85\x04\x5b\x4f\x8c\xb4\x0e\x12\xba\x36\x48\x53\xb3\x19\ -\xb9\x61\x87\x94\x8f\xc0\x3d\x24\xe4\x7c\x04\xda\xbb\x6c\x8c\x27\ -\x1b\x25\xe1\xcf\x46\x90\x58\x82\x16\x76\xd0\xc6\xf7\x03\x44\xc3\ -\x6e\x36\x2c\x8e\x6e\xdf\x2e\x9b\x4d\xdd\xd0\x04\x08\x20\xda\x44\ -\x52\x36\x66\x1c\xed\x84\xce\x97\x23\x16\x7d\xcf\xad\x42\xcc\x20\ -\x6d\x86\xd7\x47\x05\x88\x35\x76\x90\xc6\x77\xe1\x0c\xe8\xf4\x44\ -\x49\xfd\x79\xe4\xb5\x41\x3d\x3d\xe0\x38\x9a\x5f\x8c\x3c\xaa\xfa\ -\x19\x75\x34\x08\x12\x49\xc8\x87\x69\x80\x0a\x3c\x1c\x4d\x6f\x42\ -\xb9\x08\x02\x50\xe2\x08\xa9\xd1\xb0\x03\x11\x47\xb7\xcb\x1e\x96\ -\x51\x35\x38\x01\x02\x88\x81\x4e\x71\x04\x6b\x7c\xc3\xa7\x90\x26\ -\x22\x86\xbe\xc1\x8b\x55\xe1\x53\x48\x48\x6b\xec\xa0\xdb\x62\x21\ -\xeb\x4e\x4a\xe0\xeb\x89\x4f\x41\x1b\x0d\xbe\xd0\x0a\x09\xa5\xf1\ -\x9d\x87\x94\x8f\x40\x71\x04\xc9\x47\xa0\x56\x03\xf4\x10\x80\x64\ -\x92\xab\x23\x43\x14\x00\x8f\x23\x44\xe3\x1b\x94\x8f\x60\x19\x09\ -\x18\x45\x65\x37\x0f\x1f\x5e\x4d\xcd\xf0\x04\x08\x20\x1a\x44\xd2\ -\x1c\x4f\xec\x71\x94\x8b\xd4\xf8\x06\x4d\xc5\xce\x85\x67\xa4\xcd\ -\x48\xd3\x7c\xd0\x46\x03\x68\x17\x12\x38\x1f\xb5\x43\x47\xbe\xa1\ -\x91\x04\x9d\x41\xba\x00\x6d\x34\x00\x23\xa9\x13\x54\x21\x31\x63\ -\x6d\x34\xc0\x3a\x48\xf0\xc2\x0e\xb4\xbf\xdc\x18\x6b\x1c\xe1\x8c\ -\x22\x43\x74\x00\x1d\x55\x85\x8c\xd8\x35\x20\x35\xbe\xc1\xf9\x48\ -\x18\x18\x47\x65\x0f\x0f\x7b\x79\xad\xa5\xe2\x14\x13\x40\x00\x51\ -\x3f\x92\x14\x26\x63\x29\xeb\x76\xe5\xee\xca\xc5\xd8\x15\x0b\xee\ -\xc5\x2e\x41\xcc\xc5\xc2\x3b\xb1\xca\xb0\xb2\x0e\xb4\x53\x6c\x86\ -\x3b\x74\xe7\x32\xd2\x88\x1d\x24\x8e\x2a\xc0\x1d\x24\x60\x46\x62\ -\x66\x46\xe9\x21\x21\xc5\x11\x52\x59\x07\x3f\x4c\xc3\x18\x5f\x3e\ -\x42\x6f\x78\x1b\x1a\x62\x89\x25\xa4\x7c\x84\xde\x68\x00\x65\xa4\ -\xc3\xc0\x38\x12\x15\x9d\x54\x47\xb5\x20\x05\x08\x20\xaa\x47\x52\ -\xdd\x62\x1c\xf9\x08\x69\x26\x16\xd4\xb0\x9b\x0b\x19\xb1\x5b\x82\ -\xd8\x16\xdb\xa2\xd8\x85\x68\x34\x4c\x81\x34\xbe\x21\x3b\xc5\x40\ -\xf9\xe8\x3c\x6c\x0a\x09\xd4\xb0\x83\x35\x1a\xa0\x4b\x1a\x1c\x30\ -\xe2\x08\x14\x49\xa5\xf0\x8c\x94\x0d\x2d\xec\x20\x07\x9e\x6c\x48\ -\xa6\x28\x8e\xc4\xc5\xa1\xa3\xaa\x1f\x90\x3b\x48\x88\xc2\xee\xe6\ -\x4d\x70\x1c\x89\xae\xf6\xa0\xda\xe0\x03\x40\x00\x51\x3b\x92\xea\ -\x3e\x63\x8b\x23\xc8\x3a\xc8\x56\xd8\x11\x00\x90\x51\x55\xd8\xb9\ -\x41\x4b\x60\x6b\xec\x72\x90\xce\xdf\x42\x9c\x1b\x04\x5d\xd2\x70\ -\x1e\xb1\xe8\xfb\x02\x7c\xfd\xd6\x7c\xa4\x51\x55\xb4\x91\x06\xd4\ -\x0e\x52\x3f\xec\x74\x27\xf6\x0d\x5b\x37\x1c\x20\xbf\xac\x03\xc5\ -\x11\x38\x96\x50\x0a\xbb\x1d\xf0\xc2\x0e\x58\x1f\xdd\x04\x16\x76\ -\xa2\xa0\x48\x5a\x2d\xa5\x48\xa5\x40\x05\x08\x20\x2a\x47\x92\x02\ -\xab\x08\xd6\xc6\xf7\xae\x5c\x44\x27\x16\x34\x85\x04\x3e\xef\x04\ -\xd2\x66\x00\x0d\x35\x80\x66\x62\x5b\x60\x3b\x97\x41\xa3\x41\x95\ -\xb0\x85\xf9\xee\xe0\x91\x06\xd8\x5a\x55\x58\x61\x37\x15\x63\x6d\ -\x10\x6c\x54\x15\x73\xc4\x0e\x2d\x8e\x40\x07\x9e\x78\xe3\x68\x7b\ -\x13\x8e\x22\x70\x1c\x89\xc3\xe3\x08\xa3\xb0\x03\x37\x1a\xa0\x71\ -\x74\xed\x9a\x14\x95\x4a\x3c\x80\x00\xa2\x6e\x24\xcd\xc1\x52\x1f\ -\x81\x2b\x24\xf8\xe6\x09\xc4\x68\x10\x62\x8d\x5d\x11\x6c\xa3\x18\ -\x30\x8a\x20\x85\x5d\x18\x2c\x8e\x60\x47\x69\x40\x1a\x76\xa7\xc0\ -\x6b\x1a\x2e\x68\x4c\x9d\xea\xab\x09\x6d\xd8\xa1\xae\xb1\x43\x6d\ -\xd8\xed\xc7\x2c\xeb\x40\x27\x07\x1d\x38\xb0\xde\x12\x25\x1f\x11\ -\x1d\x47\xe2\x30\x00\x9d\xe6\x13\xc2\xac\x90\x40\xf9\xc8\x0b\x12\ -\x47\x37\x6e\xdc\xf3\xa0\x4e\xeb\x01\x20\x80\xa8\x1a\x49\x73\xf6\ -\x8b\x60\x8d\x24\xc4\x2e\x24\x58\x59\x07\xdb\x84\x04\x9e\xe7\x43\ -\xd9\xe0\x02\x6c\xd8\x2d\x42\xcc\x4e\x40\xd7\xd8\x21\xa6\xcb\x41\ -\x85\x5d\x4f\x0f\x38\x1f\x35\x17\x23\xad\xb1\xc3\x18\x55\xc5\x5a\ -\xd8\xc1\x0e\x0e\xf2\x96\x84\xc7\x11\xce\x41\x06\xdc\x51\x04\x04\ -\xe0\xd1\x20\x2c\x0d\x3b\xa4\x38\xba\x76\xe3\xde\xbd\xb5\xd4\x89\ -\x25\x80\x00\xa2\x6a\x24\xb1\x61\x8d\xa3\x5c\xc8\xba\x93\x56\xe4\ -\xb3\x9d\x60\xc7\x34\x40\x57\xe6\x43\x1b\xdf\xd0\x91\xef\xca\x4a\ -\x15\x58\x46\x52\x85\x8e\x06\xa9\x41\xb6\x97\x83\x3a\xb1\x1a\xfb\ -\xa0\xbb\xf9\xb6\xa1\x0d\x7d\xe7\xa1\x4f\xf3\x61\x6b\x34\x80\x4e\ -\x0e\x02\x1d\xd4\x00\x5f\xa0\x8a\x63\x98\xc1\x10\x6f\x1c\x89\x7f\ -\x40\x8c\xd8\xcd\x46\x2d\xeb\x1e\xc2\x0a\x3b\x60\x46\x5a\xbb\x56\ -\xe2\x11\x35\xc2\x15\x20\x80\x18\x68\x1d\x47\xe0\x61\x55\x94\x38\ -\x82\x8c\xaa\x82\x97\x34\x14\x2d\x81\xe5\x23\x58\x27\x16\xb1\x7e\ -\xab\x10\xbe\x36\x08\xd2\xb0\x5b\x0e\xeb\x21\x61\x9b\x8a\xfd\x8c\ -\x18\xfa\x86\x97\x75\x9e\x68\x1d\x24\x58\x46\x02\x1f\xa6\x61\x89\ -\x37\x8a\x08\xc5\x91\x78\x38\x66\x7d\x24\x0c\x6f\x7c\x23\xc5\x91\ -\x84\xc4\x0b\x2a\x04\x2c\x40\x00\x51\x31\x92\xfa\xb1\xc5\x11\x78\ -\xfd\xd6\x2e\xe8\xb2\x13\xf8\x56\x31\xc4\x11\x00\xd0\x38\x02\xaf\ -\xdf\x82\x2e\x3b\x81\x34\xec\x0a\xdb\x91\xd7\xaa\x2e\x00\xad\xb1\ -\x3b\x05\xd9\x84\x04\xcc\x47\x15\xf3\xd1\xd6\x6f\x7d\x46\x1a\x69\ -\x98\x3c\x6d\x32\xae\xc6\x37\xd2\xc9\x41\x96\x14\xc4\x51\x38\x10\ -\x20\x8f\x06\xdd\x45\xe9\x20\x01\xe3\xe8\x1a\x3c\x8e\x24\x96\xad\ -\xa5\x3c\x64\x01\x02\x88\x7a\x91\x94\x8d\x23\x8e\x20\xeb\x20\x9f\ -\x23\x0e\x49\x9b\x0b\xdd\x28\x86\x98\x2e\x6f\x41\xec\x8a\x85\xef\ -\x70\x99\x01\x5f\xd3\xe0\x0f\xdf\x84\x94\x05\x59\xf4\xad\x09\x19\ -\xb2\x43\x1e\x55\x45\x5e\x77\x02\x9d\xe6\xc3\xc8\x48\xe0\x7c\x74\ -\x00\x7e\xba\x13\x9e\xb6\x37\xa1\x6c\x04\x02\x42\xd8\x1a\x76\x5e\ -\x48\x8d\x86\x7b\x90\x38\x5a\xf6\x42\x88\xe2\xa0\x05\x08\x20\xaa\ -\x45\xd2\xb4\x5d\xd8\xca\xba\x9d\x90\x76\xdd\x1c\xa4\xa9\xd8\xb9\ -\xf0\xa3\x9d\x96\xe4\x43\x37\xb8\x14\xe4\xc0\xd6\x9d\x00\xe3\xa8\ -\x52\x05\x39\x8e\x60\xeb\x20\xa1\xd3\x7c\x1a\xc0\x8c\x04\x8e\xa2\ -\x66\x78\x7d\x04\x1f\xfa\x86\x37\xec\x26\xc3\x2a\xa4\x6c\xb4\xa3\ -\x55\x67\xcd\x82\x1e\xee\x04\x3a\x4c\xc3\x12\x57\xb3\x0e\x7f\x36\ -\x0a\x87\x80\x94\x14\x8c\x38\x2a\x83\x37\x1a\x56\x83\xe2\x48\x02\ -\x1a\x47\x93\x1e\xf1\x52\x1a\xb6\x00\x01\x44\xad\x48\x62\x9e\x80\ -\x63\xa0\x01\x3e\x62\x87\x58\x77\x32\x01\xb6\x36\xa8\x08\xfd\x10\ -\x69\xf8\xf9\x5b\x85\xb0\x85\xf9\xbd\xe0\x75\x27\x0b\x20\xd3\x13\ -\xf0\x69\xbe\xe2\x66\x8c\xf5\x5b\x48\x0b\xb8\x70\xe6\xa3\x59\xf0\ -\x23\x21\x41\x47\xd9\x59\xe2\x18\x66\x20\x58\xd2\x85\xa7\x00\xe3\ -\x28\x25\x05\x4b\x07\x09\xa9\x61\x27\xb1\x16\x12\x47\xc0\x48\x92\ -\xe2\xa0\x30\x70\x01\x02\x88\x4a\x91\xb4\x40\x19\x7b\x1c\xed\x82\ -\x9d\x91\x06\xcb\x48\x48\xf9\xa8\x08\xf9\x6c\x27\x68\x3e\x82\xee\ -\x14\x6b\x47\x6c\x70\x81\x2f\xcc\xd7\xc8\x82\xec\xe6\x83\xee\x14\ -\x73\xe8\x44\x19\x69\x40\x9e\x8a\xc5\xde\x8b\x05\x97\x75\xb0\x23\ -\x21\x41\xa7\x3b\x3d\x49\x22\xb9\xa8\x83\x67\xa3\x94\x14\x73\x73\ -\xb4\x0e\xd2\x4d\xa4\x46\x03\xb0\x3e\x82\xc6\xd1\xa4\x49\x8f\x3c\ -\x3c\x9e\x51\x38\xa3\x0e\x10\x40\xd4\x89\xa4\xa6\x05\x22\xb8\x46\ -\x1a\x76\x21\x35\xec\x14\x90\xd6\xaa\x22\xa6\x62\x0b\xa0\xad\x6f\ -\x65\xe8\x51\x1a\xb0\x03\x21\x7b\x7b\x91\x0b\x3b\xf0\x88\x1d\x74\ -\xe4\x1b\x75\xa7\x58\xde\x67\xd8\xc2\x7c\x56\xd8\x54\x2c\x1b\xf2\ -\x14\x12\xac\xd1\x00\x3f\x5a\x95\x03\x7a\x02\x97\x25\xc9\xd5\x11\ -\x22\x1b\x99\x03\x01\xf2\x88\x1d\xb0\xd1\x80\xdc\x8b\xbd\x07\x29\ -\xeb\x80\x91\xb4\x1b\x18\x49\x52\xd2\x0b\x28\x0a\x5e\x80\x00\xa2\ -\x4a\x24\xd5\x31\xe3\x68\x34\xa0\x6c\x42\x9a\x08\x6e\x33\xa0\x4c\ -\x4f\x40\x0f\xed\x5c\x81\x88\x23\xc8\xc2\xfc\x73\x33\x54\x55\x51\ -\xa6\x90\xc0\xbb\x27\x7a\x2e\xc0\xd6\x34\xe0\x1c\x55\x45\x1a\xb1\ -\x83\x65\x24\x44\xe3\xfb\x80\x37\xec\x68\xd5\x99\x90\x93\x83\x92\ -\x08\xd6\x47\x58\x8a\x3a\x58\x14\x81\x62\x09\x39\x1f\x3d\x84\xb5\ -\xec\x10\x6d\x06\x48\x1c\x49\x01\xc1\x31\x8a\x86\xf1\x00\x02\x88\ -\x2a\x91\x34\x19\x6b\x14\x01\x23\x69\x4e\x2e\xca\x14\x52\x55\x55\ -\x1d\xd2\x79\x90\xe0\x93\xbe\x5b\x20\x27\x7d\xa3\x54\x48\xb0\x51\ -\xd5\x5a\xf8\xf6\x72\xf8\xa2\x6f\xf0\xc8\x77\x71\xe7\x36\xac\x6b\ -\xec\xd0\x47\x55\xd1\x46\x1a\x0e\x78\xc3\xf3\x11\xf4\x48\xc8\x33\ -\x04\xf2\x11\x96\x46\x5d\x38\x22\x8a\xcc\x75\x50\xcb\x3a\x58\x3e\ -\xba\x07\x6b\x7c\x83\xea\xa3\x47\x90\x48\x3a\x22\x4d\x49\xf8\x02\ -\x04\x10\x35\x22\x09\x7b\x07\x49\x04\x9e\x8f\x60\x87\xdf\x82\x2b\ -\x24\x39\xe8\x41\x76\x90\x38\x82\xcc\xf2\x41\x8f\xcc\x87\x65\x24\ -\xf8\x71\x27\xe7\x51\x8e\x3b\x41\x5a\x1b\x84\x7d\xf3\x04\xd6\x46\ -\x03\x52\x85\xe4\x8d\x68\x34\x40\x4f\xe0\xb2\xe4\x23\x36\x8e\xc2\ -\xd1\xa3\x48\x07\x04\xe0\x91\x84\x88\x23\x78\x0f\x09\x18\x47\xa0\ -\x48\x82\xc4\xd1\x91\xb3\xa2\x14\x04\x30\x40\x00\x51\x21\x92\x58\ -\x77\xe1\x6a\x34\xe4\xa2\x6c\x14\xc3\xb2\x30\x1f\x7a\x8a\xb4\x32\ -\x6c\x01\x17\xb8\xcd\x50\xe8\x8e\xbc\xa0\x78\xc1\x02\xe8\xd9\x4e\ -\x3d\xc8\x3b\x2e\x11\x8d\x06\xd4\xcd\x13\xeb\xb0\x8d\x34\x6c\x40\ -\x1a\x69\x80\xc6\x11\xec\x74\xa7\x44\x12\xe2\x28\x05\xb9\xa8\x03\ -\xc6\x50\xa0\x4e\x20\x3c\x8e\xe0\x8d\xef\x6b\xf0\x4e\x2c\x30\x92\ -\x1e\x81\xea\x23\x48\x1c\x6d\xba\xbe\x97\xfc\x10\x06\x08\x20\xca\ -\x23\x69\xc1\x0a\x1c\xd9\x68\xe7\x2e\x44\x3e\x02\xf7\x90\xc0\xe7\ -\x6f\xd5\x41\xfa\xb0\x88\xdd\x13\xb0\x35\x76\xf0\x9d\xcb\x90\xd9\ -\x09\xd8\x74\x39\xac\xf1\x7d\x01\xda\xb0\x6b\xc6\x93\x8f\x26\xc3\ -\x46\xbe\xd1\x46\x55\xa1\x23\x76\xde\x88\x7c\xd4\x0d\x3d\xb6\xd3\ -\x9a\x84\x38\x42\x44\x91\x0e\x38\x1f\x05\x02\x01\x78\xdd\x09\x6c\ -\x54\x55\x14\xd4\x41\xba\x01\x6b\xd8\xc1\x1a\x0d\xa0\x48\x3a\x7b\ -\x76\xd3\xb3\x63\xe4\x9f\xd7\x0f\x10\x40\x14\x47\x52\xdd\x54\x11\ -\x1c\x43\xdf\xbb\x76\xa2\xcc\xc5\x4e\x80\xad\xdf\x92\x03\x47\x92\ -\x22\x4a\x1c\x35\xc1\x1a\xdf\x85\xf0\xc6\x37\xb4\x3e\x82\xed\x42\ -\x82\xee\xb8\x44\x9b\xe6\x43\x1a\x69\xc0\x18\x55\x45\xb9\x7b\x02\ -\xde\x68\x80\x1d\x23\x0d\x39\xb5\xf3\x6a\x22\x31\x4d\x06\xa4\x76\ -\x37\xbc\xa4\x0b\x84\x00\x58\xc3\x4e\x14\xde\x43\x42\x34\x1a\x1e\ -\x21\xc7\xd1\xb3\xeb\x2b\xc9\x6e\x3c\x00\x04\x10\xc5\x91\xe4\x29\ -\x82\x6b\xd9\x09\xea\xa1\x9d\xa0\x28\xaa\x46\x29\xeb\x10\x3b\xc5\ -\x10\xbb\xf9\x10\x6b\x83\xc0\xc3\xaa\xd0\x23\x69\x10\xdb\xcb\x51\ -\xd6\x06\x1d\xfd\x8c\xb6\x36\x68\xbf\x27\x96\x35\x0d\x90\x11\x3b\ -\x6f\x6f\xa4\x73\xa4\x91\x8e\x84\x74\x26\x9c\x8f\xc2\x51\xe3\x48\ -\x07\x29\x8e\x22\x22\x50\x47\x83\xd0\xe3\x08\x5a\xd6\x01\xe3\xe8\ -\xfa\xf5\xeb\xd2\xc7\xc8\x0d\x63\x80\x00\xa2\x34\x92\xb2\x71\x94\ -\x75\xbb\xe6\xec\x44\xda\x15\x5b\x85\x36\xaa\x0a\xbf\xd6\x00\x7e\ -\xb4\x13\x74\x17\xd2\xaa\x19\xb0\x1d\x2e\xe7\xe1\xd3\x13\xa8\x85\ -\x1d\xfa\x11\x00\x58\x32\x12\x7a\x1c\x6d\x45\xbe\xd7\x00\x71\xd6\ -\xf7\x76\xf0\x91\x90\xd6\xd8\xe2\x08\xfb\x18\x03\x22\x1f\xc1\xa2\ -\x08\x08\xca\x6e\xa2\x8c\x7c\x23\x46\x83\xe0\x91\x74\x76\xd3\x26\ -\x60\x46\x92\x06\x82\xc3\x64\x06\x32\x40\x00\x51\x18\x49\x47\x77\ -\xe1\x1b\x55\x45\x2e\xeb\xaa\xe1\x67\xd2\x20\x2f\xe0\x42\x5d\x07\ -\x09\x3a\x31\x1f\xba\x58\xf5\xfc\x79\xa4\x23\x00\xa0\x5b\x97\xc1\ -\x23\x76\x9d\x0e\x58\x16\xab\xe2\x6b\xd8\x01\x2b\x24\xc4\x99\xf9\ -\x7e\x18\xc7\x76\x3a\xe3\x8f\xa3\x70\x71\x5c\x45\x1d\x38\x8a\x22\ -\x94\x90\x46\x1a\x90\x47\x55\xe1\xf5\x11\x28\x92\x80\x65\xdd\x75\ -\x50\x24\xdd\xba\x4a\x5e\x28\x03\x04\x10\x65\x91\xb4\x42\x15\x47\ -\x3e\xda\x05\x59\x77\xd2\xda\x0a\x9b\x41\x82\xe7\x23\xc4\xd9\x4e\ -\x88\xb3\x06\x17\x4d\x81\x4d\xc5\x22\x37\xec\x20\xdb\xcb\xc1\x8d\ -\xef\x1e\xc8\xc1\xaa\xa8\x8d\x06\xa4\x81\x06\xd0\x8e\x4b\xe4\xf5\ -\x5b\xd9\xc8\x23\x76\x88\x86\x9d\x1f\xa4\xf1\x0d\x89\xa3\x33\x57\ -\xb7\x83\x4f\x49\xb3\xc6\x57\x1f\xe1\x2e\xea\xa0\x71\xa4\xa4\x84\ -\xdc\xb0\x43\x8e\x23\xd4\xc2\x0e\x14\x47\xc7\x8e\xc9\x9c\x27\x2b\ -\x98\x01\x02\x88\xb2\x48\x9a\x8c\xa3\x13\x0b\x3a\xe9\x1b\xbc\xe8\ -\x1b\x7e\x88\x74\xf5\x04\xa4\x38\x02\x66\xa4\xbe\x3e\xe8\x11\x00\ -\x4d\xca\xe5\x8b\xa0\x1d\x24\x68\x3e\x52\x45\x3e\x8d\x5d\x03\xb4\ -\xe8\x7b\xdf\x54\xa4\x73\x83\x50\x76\xc5\x22\x7a\xb1\x58\xa7\x62\ -\xe1\x71\x84\x34\x62\x87\x74\x44\x31\xe4\x94\x34\x42\xd5\x51\x0a\ -\x4a\x1c\x05\x22\x15\x75\x4a\x20\x80\x32\xcd\x27\x01\xeb\xc5\x42\ -\x47\x1a\xa4\x80\x51\x84\x88\xa3\x63\xb7\x56\x92\x15\xcc\x00\x01\ -\x44\x51\x24\xb1\xe1\x6a\xd7\xa1\x2d\xb2\x43\x64\x24\xf8\x69\xec\ -\x8a\x7d\xb0\xcd\x7c\xe5\xf0\xf5\x5b\xf0\x23\x69\x90\x1b\x76\xc0\ -\x38\x9a\x3a\x15\xdb\xd9\x4e\x48\x71\x84\xb4\xec\x04\xe5\x76\x3e\ -\x50\x24\x1d\x40\xee\xc5\x22\xca\x3a\x60\x24\xc1\x8e\x84\xb4\x46\ -\x8b\x23\x43\xcc\xa6\x37\x7a\xa3\x0e\x1e\x43\x40\xf0\x09\x6d\x2a\ -\x56\x02\x54\x1f\xed\x46\xea\x21\x01\x0b\x3b\x69\x70\x61\x77\xeb\ -\xd6\xc2\x37\x77\xc9\x09\x67\x80\x00\xa2\x24\x92\xb2\x26\x88\xe0\ -\x5c\xf4\x0d\x3b\x6c\x70\xa2\x02\xd2\x1a\x3b\x39\xa4\x73\xec\x20\ -\x8b\x83\xe0\x27\x7d\x43\xd7\x6f\x41\xca\x3a\xd8\xa1\x9d\xa7\x90\ -\xcf\x76\x6a\x6e\x46\x5d\x1b\x74\x12\x7d\xd1\x37\x9b\x27\x96\x38\ -\x9a\x85\x12\x47\xb0\x8c\xc4\x85\x72\xb4\x2a\x1f\xa1\x66\x1d\xbc\ -\x6f\x14\x88\x5a\xd2\x81\xe3\xe8\x1d\x66\xa3\xe1\x05\x2c\x1f\xc1\ -\x1b\x76\x90\x7c\xb4\xf0\xcd\x1b\x19\x72\xce\x6e\x05\x08\x20\x0a\ -\x22\x49\x61\x3e\xae\x0a\x29\x77\x0e\xda\x3a\x48\xe4\xa5\x41\xf0\ -\x35\x0d\x88\x35\x76\xf0\x1e\x12\xac\x61\x07\x19\x69\xc8\x82\x9e\ -\xc6\x3e\x15\x7c\x92\x06\x72\x1c\x1d\x45\x5f\x1b\xb4\x0e\xd1\xb0\ -\x43\xb4\x19\xd8\x41\x8d\xef\x03\xd0\x91\x6f\x0e\x44\x3e\x82\xf4\ -\x90\x10\x47\xab\x5a\x13\xa8\x8f\xb0\x35\xea\x60\x51\xf4\xe9\xd3\ -\xbb\x77\xb0\x69\x3e\xa4\x91\x86\x47\xc8\xf9\x08\x5a\xd8\xdd\x5a\ -\xb8\xf0\xcd\xca\x95\x27\xe4\x48\x0f\x69\x80\x00\x62\xa0\x6e\xeb\ -\x1b\xdc\x3f\xda\x09\xd9\x85\x04\x5f\xf5\x3d\x01\xba\x73\x19\x7c\ -\x8a\x34\x7c\x17\x12\x64\x7a\x02\xb6\x51\x0c\x7e\x8a\x74\x3d\xb4\ -\x83\xb4\x1c\x7e\xb6\xd3\x54\x68\xa3\xa1\x13\x7d\x83\x0b\xf2\x54\ -\x2c\xd2\xc8\xf7\x63\xa4\x5e\xec\x56\x78\x07\x09\xe5\x12\x97\x33\ -\x57\x11\xe7\x48\xdb\xd7\xd4\x04\xe0\xad\x8f\xb0\xc4\x11\x3c\x17\ -\x7d\x7a\xf7\xed\xdb\x37\xf4\x38\x82\x37\x1a\xce\x9e\x3d\x8b\x1c\ -\x47\x6f\x80\x71\x24\x23\xb3\x89\xf4\x90\x06\x08\x20\xf2\x23\x89\ -\x55\x04\xf7\xe6\x09\x58\xeb\x7b\x22\xca\xf5\x20\xf0\x63\x83\x5a\ -\xe0\x27\xe6\x97\x37\x21\xd6\x34\x80\x77\x5c\xc2\x47\x1a\x16\x40\ -\x36\xb8\xf4\xa0\x9c\x1b\x84\x68\x7c\xe7\x21\x8f\x7c\xaf\x83\xef\ -\x8a\x45\x1b\x0d\x3a\x80\x98\x8a\xbd\x82\x7c\xaf\xc1\xd5\xab\x1d\ -\x28\x47\xab\xe2\xca\x47\x98\xd5\x11\x6a\x36\x7a\x07\x04\xdf\x7e\ -\x86\x20\x37\x1a\x90\x7a\xb1\x28\x8d\x06\x70\x1c\xc9\x80\x00\xe9\ -\xb3\xe9\x00\x01\x44\x76\x24\x29\xb6\xe3\xaa\x8f\x76\x42\x0a\x3b\ -\xe8\x91\x34\xa0\x13\x8a\xe5\xe0\x8d\x06\x78\x85\x04\x1f\x55\x85\ -\x9d\xc6\x0e\xb9\x3f\x11\xba\x30\x1f\xbc\xe3\x12\xbc\xbb\xbc\x07\ -\xb2\xa6\x01\xdb\x88\x1d\x72\x0f\x09\xdb\x62\xd5\xad\x5b\x51\x46\ -\x1a\x60\x8d\x6f\xe4\xf3\xd8\xb9\xa1\x47\xab\x06\xe0\x8d\x23\x44\ -\xc3\x1b\xb5\x32\x02\x47\xd1\x37\x60\x24\x85\xdc\xb8\x81\xc8\x47\ -\xa0\xa9\x58\x78\x2f\x16\xd2\x89\x05\xc5\xd1\xad\x37\xd0\x48\x7a\ -\x7d\x62\x06\xa9\x61\x0d\x10\x40\x0c\x54\x6d\xd9\xe5\x22\x5d\x57\ -\x85\x28\xeb\xa0\xa7\xb1\x4f\x47\x1c\xed\x04\x9d\x9e\x28\x87\x9f\ -\xed\xd4\xee\xee\x0e\x3d\xa0\x18\xb9\xd1\x70\x01\xb9\x61\x87\x7a\ -\x94\x06\x62\x21\x64\x29\xc6\x14\x12\xca\x1a\x3b\xa4\x51\x55\x44\ -\x46\x6a\x43\x3d\xa2\x98\x97\x17\x57\x59\x87\x99\x8d\x50\x73\x11\ -\x30\x86\x80\x71\x14\x82\x3c\xf4\x0d\x6d\xd8\x1d\x39\x72\x64\xd3\ -\xa6\x4d\xd7\x91\x32\xd2\x4a\x50\x61\x27\xf3\xfa\xf5\x6b\x92\x87\ -\x87\x00\x02\x88\xdc\x48\xca\xc3\xb5\xc6\x0e\x75\xa7\x18\xd2\x96\ -\x4b\xe8\x09\x00\xb0\x0b\x7d\x51\x76\x21\xc1\xd6\x06\x41\x1a\x76\ -\x90\xdd\xe5\xe0\xa3\x34\xc0\x9b\x27\x34\x51\xd6\x9d\x20\xad\x83\ -\xc4\xb2\x79\x02\x6d\x34\x68\x3d\xf2\x0d\x8a\x48\x97\x21\xa1\x1f\ -\x7f\x1b\x80\x11\x47\xa8\x03\x41\x68\x45\x1d\x38\x8a\xbe\x41\xb3\ -\x91\x7a\x88\x3a\x4a\x59\x87\xa5\xd1\x70\x0b\x56\x21\x81\x22\xe9\ -\xe3\x47\x52\xaf\x61\x02\x08\x20\x32\x23\xa9\x6e\x01\xfe\x29\x24\ -\xe8\xba\x13\xc8\x68\x50\x1d\xfc\xa4\x6f\xd8\xc1\xaa\xd0\x75\x90\ -\xe5\x48\x07\xab\x42\xe3\x08\xbc\xa2\x18\x7d\xc7\x65\xf1\x36\x1c\ -\xeb\x20\xd1\x76\x5c\x22\x37\x1a\x36\xc0\xa7\xcb\xd1\x1a\x0d\xc8\ -\x97\x4f\x80\x8e\xed\x04\x47\xd2\x46\x8c\xb2\x0e\x7b\x93\x01\xd6\ -\xa4\xfb\x04\x89\x22\x60\x26\x0a\x51\x07\x81\xb5\x6b\x91\x0a\x3b\ -\xa4\x91\x6f\x69\xd4\x46\x03\x30\x8e\x3e\x7e\xfc\xf8\x8a\x89\xc4\ -\xdb\x6b\x01\x02\x88\x81\x7a\x2d\xbb\x5c\xd8\x29\x0d\x90\x0a\x09\ -\x76\x68\x27\xf2\x48\x03\xf4\x10\xe9\x1c\x68\x61\x07\x5f\xbf\x35\ -\x03\xba\x2b\xb6\xc4\x5f\xed\x3c\xfc\x94\x06\x8d\xa9\x53\xa7\x22\ -\xad\x3b\x41\x3a\xfc\x16\xf9\x28\x0d\x94\x23\x00\x1e\xa3\x5e\x8d\ -\x8d\x6d\x9a\x0f\xfd\xa2\x1d\xe8\x69\x83\x01\x98\x65\x1d\xf2\xec\ -\x1e\x52\x14\xc1\xf2\xd1\xb7\x9f\x3f\x61\x51\xa4\xae\x4e\x20\x1f\ -\xc1\x5b\x0d\xa0\x7c\xb4\xe7\xd5\x2b\x12\x5b\x78\x00\x01\x44\x5e\ -\x24\x75\xe2\x98\x8c\x9d\x83\x38\xc7\x0e\xba\xe8\x1b\xba\xa4\x21\ -\x1f\xe9\xac\x41\x78\x2f\x76\x4a\x18\xf2\x69\xec\x90\x91\x6f\xc4\ -\xf4\x04\xb0\x61\x07\xda\xe1\x32\x1f\xf5\xd0\x4e\x94\x11\x3b\xe8\ -\x68\x10\x1b\x66\x61\xb7\x15\xe9\xda\x65\xa4\xc2\xee\x0c\x52\x85\ -\x84\x94\x8f\x80\x91\x64\x8b\x91\x8f\x70\xb5\xbc\x81\x51\xf4\xe9\ -\xdb\x37\x48\x36\x82\x46\x51\xa4\x7a\x24\x6c\x7a\xc2\x03\x5a\x21\ -\xa1\x96\x75\x0b\xe1\x65\xdd\x6b\x50\x1c\x9d\x38\x41\xda\x4a\x3c\ -\x80\x00\x62\xa0\x56\x37\x16\xba\x10\x12\xb1\x0e\x12\xbc\x7e\x0b\ -\x32\xcf\x07\xdf\x84\x04\xcc\x47\x5d\x88\x7b\xdf\x2a\x51\xd7\xd8\ -\xf5\x22\x76\xc5\x42\x4e\xcc\xf7\x45\x1b\xb1\xc3\x7a\x4c\x83\x27\ -\xae\x69\x3e\xc4\x88\xdd\x4c\x44\x85\x84\x18\x69\xb8\x8c\x74\xd6\ -\x37\xe8\x94\xb4\x00\x6c\x65\x1d\x96\x36\x03\x34\x1b\x7d\x0b\x41\ -\xe4\xa3\x48\x20\x80\xad\x3b\x41\x34\x1a\xa0\x85\x1d\x72\x59\x27\ -\x03\x8b\xa3\x13\x4c\x24\x75\x69\x01\x02\x88\xac\x48\xea\xc7\xbe\ -\x79\x22\x37\x17\xe9\xd0\xce\x89\x73\x27\xcc\x45\x3a\x32\x7f\x1e\ -\xf2\x4e\x31\xe4\xe3\x4e\x20\x17\x63\x83\x47\xec\x4a\x10\x37\x4f\ -\x80\x46\xbe\x11\xeb\xb7\xd0\x4f\x91\x46\x34\xec\xd6\x79\x62\x1c\ -\x01\x00\x69\x7c\xaf\x47\xae\x90\x90\xe3\x08\x7c\x83\x22\x3c\x23\ -\x21\x8e\x56\xb5\xc5\x92\x91\xb0\x44\x11\x38\x92\xc0\xed\x05\x68\ -\x1c\x45\x82\xe3\x28\x32\x16\x69\x7a\xe2\x08\xa4\xf1\x0d\x1f\x55\ -\x85\x45\x12\x3c\x1f\x01\x81\x04\x29\xe1\x0d\x10\x40\xe4\x44\xd2\ -\x8c\x25\x58\xe3\x08\x65\x0a\x49\x01\x7e\xef\x1b\xd2\x61\xec\x2d\ -\xf0\x4b\x2e\xc1\xb7\x1a\x4c\x51\x69\x87\x0d\xab\xf6\x42\x2f\xc6\ -\x46\x5c\xb3\x03\x5f\x63\x87\x7c\x4c\x03\xb8\x42\x82\x0f\x35\x94\ -\x62\xac\xb1\xc3\x98\xe6\x83\xc6\x51\x37\xe2\xc2\xaa\x8e\x36\x58\ -\x07\x09\x71\x89\x0b\xf8\x3c\x76\x02\xf9\x08\x79\x8c\xe1\x27\xb8\ -\x51\x07\xcb\x44\xe0\x38\x8a\x7d\x01\x6f\x34\x1c\x01\x4d\x4f\x40\ -\x0b\x3b\x50\x14\xdd\x42\x8d\x23\x48\x24\x31\x31\x93\x10\xe0\x00\ -\x01\x44\x4e\x24\x79\x62\x99\x9e\x80\xac\xf9\x9e\x83\xd4\x68\x80\ -\x8e\x06\x2d\x41\x5c\x0f\x02\x1f\x0d\x42\x9c\x49\xb3\x0a\x36\x15\ -\x5b\x0f\xbb\xf7\xed\x14\x6c\xeb\xb2\x2f\xf4\xa4\xef\x4e\xac\x8d\ -\x06\x70\x85\x84\xfd\x08\x00\x48\xc3\x6e\x3d\xc6\xfa\x2d\xc8\x48\ -\x03\x96\x46\x03\xe4\x48\x48\x5b\xf4\xb1\x20\x6c\x45\xdd\x27\x58\ -\xf7\x15\xa9\xa4\x03\x45\x11\x10\x20\x4f\x21\x21\x1a\x76\x88\x38\ -\x82\x34\xec\x60\x71\xc4\xf4\x8a\x84\x00\x07\x08\x20\x32\x22\xe9\ -\x28\xf6\x7d\xb1\xa8\xf7\xf9\xce\x45\xba\x18\x7b\x33\x6c\x26\xb6\ -\x0f\x76\x15\x52\x79\x25\xe8\xf0\xdb\x76\x15\xd8\xae\x58\x55\xc4\ -\x34\x1f\x68\x17\x12\x6c\x34\x08\x75\xe4\x1b\x79\x7a\x02\xff\x4e\ -\xb1\x03\xd8\xa7\x90\x40\x8d\x06\x44\x07\x09\x76\x1e\x3b\x28\x23\ -\x81\xef\x07\xc1\x53\xd6\x29\xa1\xc4\x51\x08\x4a\x1c\x81\x63\x48\ -\x57\x57\x77\xd2\x6e\xcc\xd9\x89\x63\xe0\x1e\x12\x2c\x8e\x10\x85\ -\x1d\x13\x10\xb4\x11\x1f\xe2\x00\x01\x44\x7a\x24\xcd\x99\x8f\x75\ -\xd1\xf7\x2e\xb4\x23\x69\xaa\x90\xa6\x62\x61\x6b\x83\x0a\x90\xa6\ -\xcb\x91\x6f\x9e\x40\xf4\x62\x0f\xc2\xce\xdf\x82\xdd\x9f\x88\xb2\ -\xa6\xe1\x33\x28\x8a\x16\x23\x8f\xaa\x62\xdd\x71\x89\xd6\xb0\x83\ -\xce\x4e\x40\x47\xbe\x91\x2f\xda\x81\xc5\x11\xe4\x68\x55\x5b\x48\ -\x27\x16\x5f\x1c\x7d\x7b\x87\x94\x8d\x90\x72\x91\x2e\x28\x96\x50\ -\xe2\x08\x56\xd8\x21\x35\xbe\x5f\xef\x41\x14\x76\x40\x90\x40\xfc\ -\x76\x5a\x80\x00\x62\xa0\xbc\x8b\xb4\x0b\x7c\x66\x3e\x24\x8a\x20\ -\x53\xb1\x0a\x48\x71\x54\x84\x58\xd2\xd0\x07\x3f\x45\xba\x12\x7c\ -\xf5\x04\x30\x23\xb9\xc3\xae\x14\xf3\x47\x5a\x50\x7c\x61\xea\xbe\ -\xa9\xd8\xd6\x6f\xe5\xa1\x4e\x21\xad\xf3\xdc\x8f\xb5\x61\x77\x00\ -\x5b\x07\x09\x6d\x7a\x02\xf9\x52\xb1\x35\x6b\xb6\x40\x4e\x84\x44\ -\xe9\x20\x41\x23\x29\x02\xa9\x3a\x82\xf7\x8e\x90\xab\x23\x68\x3e\ -\x42\x8a\x24\xe4\x11\xbb\x85\x68\x1d\xa4\x57\xb0\x28\x02\x02\x2f\ -\xa2\x83\x1c\x20\x80\x48\x8e\xa4\x25\x2a\x58\x8a\x3a\xd8\x79\x90\ -\xf0\x9d\x62\xf0\xc5\xaa\xf9\xb0\xdb\xcb\x11\x0b\x8a\xe1\x0b\x21\ -\xa1\x15\x12\x62\xf3\x04\xd2\x61\x83\x9a\x04\xa6\x62\x27\x63\x1f\ -\xb1\x63\x47\xcf\x47\x33\x61\x8d\x06\xd4\x38\x42\x54\x48\xf7\x11\ -\x17\xed\xe8\x23\xe2\x08\x65\x48\x15\x31\xca\x00\x6a\x33\x84\xa8\ -\x23\x1a\x75\x88\x18\x02\x45\x12\x22\x8e\x60\x15\x12\x52\xc3\xee\ -\x23\x28\x92\x4e\x9c\x80\x67\x24\xa6\x84\xaf\x95\xc4\x86\x39\x40\ -\x00\x31\x50\xde\xfc\xde\x05\x89\x24\x50\x3e\x42\x1c\x36\x58\x8d\ -\xbe\xec\x04\x92\x8f\x56\x20\xce\xec\x54\x81\x4f\xc5\xf6\x22\xee\ -\x21\x85\x8e\x06\x41\x8f\xd2\xe8\x44\x99\xe6\xc3\x3c\x45\x1a\x4b\ -\xa3\x61\x2b\xf2\xf4\x04\xe6\x88\x9d\x33\xbc\x61\x07\x6f\x34\xac\ -\x81\x9f\x99\x6f\x8b\xd2\x66\x80\x15\x76\xc8\xd5\xd1\x4f\x78\xc3\ -\x3b\x12\x3d\x8e\xac\xac\xac\xb0\x8c\x06\x41\x33\x12\x72\x3e\x82\ -\x65\xa4\x84\x04\xa2\x17\xf1\x03\x04\x10\xa9\x91\xb4\x40\x01\x6b\ -\x24\xa1\x56\x48\xb0\x5d\xb1\xa8\xb7\x1a\xe4\xc0\x77\x5c\xa2\x1e\ -\x49\x53\x5b\x0f\x3d\x45\xfa\x14\x6c\x0a\x69\x2a\xec\xf0\x5b\xe4\ -\xed\xe5\x79\x27\x17\x9f\x44\x3e\x8d\x1d\x69\x8d\xdd\x63\xa4\xcd\ -\x13\x5b\x0f\xac\xc7\x3e\x62\xd7\x86\xd6\x41\x02\x47\xd2\xfd\x8d\ -\xf7\x91\xce\xcc\x17\xc7\xd5\x68\x40\x6a\x32\xa0\x96\x74\xba\xb1\ -\xf0\x28\x02\x45\x12\xe6\xa8\xea\x1b\x58\xc3\x6e\xcf\x1e\xd4\x38\ -\x02\x46\xd2\x57\x62\xa7\xd2\x01\x02\x88\x81\xd2\xe6\xf7\xce\xdc\ -\x5d\x28\x3b\x2e\x27\x42\x4e\xed\x94\x93\x43\x9a\x42\x6a\x41\xda\ -\x72\x59\x8e\xbe\xa0\xb8\x16\xa9\xac\x83\x5d\x5e\x0e\x5b\x07\x09\ -\xeb\x21\x7d\x46\x39\xa6\x01\x63\xa4\x01\xb1\x36\x68\x16\xf2\x8e\ -\xcb\x99\x28\x15\x12\xe2\x4a\xdf\x1a\x58\x2f\x76\x23\xb4\xb0\x73\ -\x82\x1e\x7f\x6b\x8b\x16\x47\x48\x6d\x06\xe8\xbc\x84\x3a\x22\x92\ -\x62\x11\x71\x04\x8e\xa2\xf8\xf8\x78\xb4\x86\x1d\x22\x8e\x90\x3a\ -\x48\x27\x60\x51\x04\x04\x27\x88\x0c\x74\x80\x00\x22\x31\x92\x2e\ -\xa0\x0d\xda\xed\x84\xee\x70\x41\x8e\x23\x6c\x6b\x55\x91\xcf\xd2\ -\x80\x1e\xda\xb9\x0a\xba\xa4\x01\x72\xcd\x0e\x7c\x9a\xef\x02\xd6\ -\x03\x21\xf3\xd0\x8e\xd2\x58\xb7\x1f\xcb\x48\x03\xac\xf1\x8d\xb5\ -\xd1\xd0\x81\x74\xed\x32\x72\x85\x84\x38\x8f\xdd\xa7\xf1\x34\x6a\ -\x85\x84\x3a\xea\xfd\xf3\x1b\xca\x18\x43\x2c\xa2\xac\x83\xc5\x51\ -\x3c\xca\x1a\xbb\x63\xc8\x8d\x06\x78\x3e\x42\x14\x76\x40\xf0\x75\ -\x03\x71\xa1\x0e\x10\x40\x24\x46\xd2\x64\xcc\xe5\x5b\x90\xe9\x09\ -\xf8\x85\xbe\xd0\x33\xd2\x40\x23\x76\xa0\x69\xbe\x22\x45\xd4\xad\ -\xcb\xb0\xfb\x13\x21\x73\xb1\xbd\xf0\xeb\x41\x10\xeb\x89\x61\x71\ -\xd4\x89\x76\x1a\xfb\x49\xec\x8d\x06\x6c\xc7\x9d\x40\x46\xec\xfc\ -\xb0\xe4\x23\xf0\x54\x2c\xfc\xc2\x2a\xc4\x6d\x48\xd0\x4b\x5c\xf4\ -\x53\x70\xe7\xa3\x90\x9f\xe8\xd5\x51\x2c\x3c\x86\x20\x71\x14\x14\ -\xb4\x09\xb1\x36\x08\x31\xaa\x2a\x83\xb5\x42\x02\xc5\xd1\xd7\x04\ -\xe2\x42\x1d\x20\x80\x48\x8b\xa4\x4e\xcc\x35\x76\x28\xa3\xaa\x13\ -\x51\xd6\xd8\x4d\x87\xf5\x62\xbb\x90\xce\x48\x9b\x82\x14\x47\x98\ -\x23\x76\x17\x10\x77\x8a\x61\xf6\x62\x91\xd6\x34\xc0\x23\xa9\x1f\ -\x65\x7a\x02\x79\xd1\x37\x3c\x1f\x71\xa1\x4d\xc5\xc2\xaf\x22\xbd\ -\x0f\xbf\x0c\x09\x72\xfc\xed\xd3\xa7\x4f\x91\xe6\xf8\x10\xed\x3a\ -\xc8\xfc\x1e\x4a\xf7\x08\x9e\x8d\x10\x51\x04\x8c\xa3\x20\xf4\x69\ -\x3e\xbc\xf9\x08\x14\x4b\x7e\x44\x05\x3b\x40\x00\x91\x16\x49\xac\ -\x58\xa6\xcb\x73\x51\x2f\x2f\x9f\x5b\x07\x3e\x02\x60\x09\xfc\xac\ -\x41\xc5\x96\xae\x2e\xc4\x3d\xa4\xf0\x13\x8a\xc1\x71\xa4\x5a\x0b\ -\xbd\x7a\x62\x39\x3c\x8e\x10\x8d\x86\x4e\xe4\x7b\xdf\x50\xcf\x48\ -\x43\x3b\xfc\x16\xb6\xee\x64\x2b\x7c\xe7\xb2\x9f\xdf\x4c\xd4\xc5\ -\xaa\xce\x48\xd3\x13\x17\xa1\xd7\xc5\x22\x2e\x08\x81\x9f\x99\xaf\ -\x8f\xdc\x41\x52\x42\x44\x12\x7c\xf2\x08\xad\x55\x87\x1a\x47\x41\ -\x88\x91\x6f\xd0\x9a\x06\xe4\x7c\x84\x52\x1f\xc1\x32\x12\x10\x10\ -\x15\xec\x00\x01\x44\x52\x24\x31\xe3\x8a\x23\xe4\x05\xc5\xb0\xf5\ -\x5b\xd0\xd1\x20\xe8\x9a\x06\xf8\x59\x83\xb0\xcb\x62\x51\x3a\x48\ -\xb0\x9b\x27\x60\xbb\xf9\x50\xef\xe6\x43\x3b\xdb\x69\x1d\xce\x63\ -\x1a\xd6\xa3\x34\x1a\x50\xe3\x88\x1b\x52\xd8\x81\x1b\x0d\xbc\xb0\ -\xc2\x0e\x7e\xf9\x04\xec\xcc\x7c\x7d\xe4\x4e\x6c\x04\x6a\xdb\x5b\ -\x1d\x47\x1c\xc1\xa3\x08\x14\x4b\xd8\x1a\x0d\x1f\xb1\x35\x1a\xc0\ -\x71\xf4\xfe\x3d\x51\x17\xa0\x02\x04\x10\x49\x91\xf4\x19\xb3\xb0\ -\x43\x3d\x4a\x63\x2e\x68\xc2\x1c\x7a\x22\xcd\x74\xf8\x0d\xf3\xb0\ -\x43\x3b\xcb\x11\xb7\x83\xc0\xd6\xd8\xc1\x2f\xc6\x46\xda\x29\x06\ -\x3b\x31\x1f\x5a\x1f\x1d\xcd\x43\x3d\x58\xb5\x14\x65\xfd\x16\xf2\ -\x34\xdf\x7a\xec\x53\xb1\x57\x11\x8d\x06\x44\x85\xc4\x8b\x72\xd1\ -\x0e\xfc\xc2\x2a\x41\x73\xb4\x81\x06\xd0\xb0\xf7\x3b\x7c\x71\x84\ -\x1c\x45\x41\x02\xd2\xc8\xa3\x41\x48\x3d\x24\xcc\xb2\x0e\x12\x47\ -\xef\xdf\x13\x13\xee\x00\x01\xc4\x40\x41\x61\x87\xb9\x2b\x16\xe5\ -\x60\xd5\x79\xc8\xbd\x58\xd8\x2e\x24\x94\x38\xea\x3d\x8f\x7c\x20\ -\x24\x7c\x37\x5f\x33\xfc\xc4\x7c\x66\xcc\x85\xf9\x18\x8d\x6f\xd8\ -\xf4\xc4\x01\xd4\xf5\x5b\xdd\x88\x51\x55\xe4\x86\x1d\x7c\x9a\x6f\ -\xe3\x1a\xf4\x8b\x76\xc0\x47\xab\xea\x63\x54\x48\xdf\xde\xa1\x4e\ -\x4c\x60\xc4\x51\x10\x3c\x8e\x80\x91\x84\xd6\x68\x80\x56\x48\xd8\ -\x23\x09\x14\x47\x5f\xee\x13\x11\xf0\x00\x01\x44\x7e\x24\xed\x14\ -\x01\x8f\xd8\xc1\xe7\xcb\xab\x14\xe0\x07\x42\x22\x96\x34\xc0\xa7\ -\xf9\x9a\x94\xcb\xe1\xbd\xd8\x42\xf8\xba\x13\x48\x1c\x2d\xcf\x82\ -\x37\x1a\x30\x0e\x84\x3c\xfa\x19\x75\xa3\x18\x8e\xb5\x41\x5b\x37\ -\x6c\x45\x9f\x8a\xe5\xc2\x33\xcd\x07\x1a\x68\xb8\xbf\x14\xf9\x4a\ -\x5f\xd8\x39\xd2\x26\x98\x8d\x06\xd4\x3e\x2c\x4a\x93\x01\x35\x1b\ -\x05\x09\x08\x08\x80\x23\x69\x21\xf2\xba\x93\x3d\xf8\x32\xd2\x97\ -\x2f\xc4\x64\x25\x80\x00\x22\x21\x92\xe6\xa3\x8f\x33\x40\xae\xc6\ -\x86\xdf\x71\x89\xb8\x18\x5b\x6e\x49\x11\xca\x1a\x3b\xf0\x09\x00\ -\x4d\x8b\x50\xa6\xcb\x61\xbb\xf9\x60\x8d\x6f\x0d\x78\xc3\x0e\x6d\ -\xe4\x3b\x0f\x65\xf3\x44\x29\xce\xf5\x5b\x48\xbb\x90\x50\x16\xab\ -\x76\x20\x5a\xdf\x35\xa8\xa3\x41\x4b\x51\xae\xf4\x85\xde\x3d\xa1\ -\x83\x19\x47\x48\x6d\xef\x58\x58\xd3\x1b\xb9\xe1\x0d\x8b\x24\x01\ -\x70\x24\x41\x16\x42\x22\x4f\x97\x63\x69\x7c\x27\xc0\xe2\xe8\xcb\ -\x17\x22\x96\x3b\x00\x04\x10\x03\x99\x83\x0d\xbb\x90\xaf\xab\x82\ -\x6f\xb9\x84\x8d\xd8\x21\xce\x0d\x2a\xe8\x42\xef\xc5\xb6\xa3\x8d\ -\x34\x20\x8d\xd8\x69\x42\xe2\x08\xe7\xf6\xf2\x52\xec\x6b\x1a\x20\ -\x1d\x24\xa4\x0a\xa9\x1b\x69\xeb\x32\xd2\x68\x10\xf2\x88\x1d\xfa\ -\x75\xb1\xf0\xb3\xbe\xf5\xd1\xcb\x3a\xf4\x7c\x84\x5a\xd4\xa1\x46\ -\x11\x30\x92\xd0\xd6\xd8\xa1\xc5\x51\x02\x6a\x85\x04\x8a\x24\x26\ -\xc2\x21\x0f\x10\x40\xc4\x47\x92\xea\x2e\xf4\x25\x0d\x48\x47\x69\ -\x28\xc0\x16\x7d\x83\x2b\xa4\xa2\x7c\xf8\xb0\x6a\x0e\xea\x79\x27\ -\xe0\x38\x82\xde\x57\x05\xbf\x1d\x04\x34\xcd\x07\x3d\xdb\x09\x3a\ -\x62\x87\x7c\x1a\xfb\x62\xe4\x5d\xb1\xc8\x23\x76\x88\x5e\xec\x56\ -\x94\xe9\x72\xe4\x29\x24\x70\x0f\x09\xde\xb0\x43\x54\x48\xf0\x38\ -\xe2\x47\xdc\x72\x09\x39\xeb\xfb\x12\xd2\x48\x03\x78\x2c\x08\x7d\ -\x48\x15\xa5\xe5\x8d\x1e\x47\x02\x02\xa8\xd3\x13\x38\xeb\x23\x70\ -\x1c\x81\x23\xe9\xcb\x49\x82\x41\x0f\x10\x40\xc4\x47\x52\x36\xd6\ -\xe3\x4e\x90\x0f\xed\x9c\x80\xb6\xc6\xae\xaf\xaf\x0b\x76\x26\xcd\ -\xa2\x45\xe0\x83\x55\x21\x8d\x6f\xf0\xb2\x13\xa4\x3b\x2e\x21\x15\ -\x52\x85\x2f\xfa\xa1\x9d\xc8\xeb\xb7\x50\x2b\x24\xf4\xd1\xa0\x59\ -\x48\x1b\x5c\xfc\x50\x76\xc5\x5e\x45\x2d\xeb\x90\x33\x12\x24\x1f\ -\x1d\x47\x34\x1a\x40\x97\x21\x35\xb8\x44\xa0\x16\x76\x21\x90\xf1\ -\x3a\x68\x1c\xe9\x62\x8f\x23\x01\x04\xb8\xb5\x10\xb9\x61\x87\xb5\ -\xf1\x8d\xa8\x90\x80\x80\x87\x47\x86\x60\xd0\x03\x04\x10\xd1\x91\ -\xa4\xb8\x04\xf5\x62\x6c\x48\xa3\x01\xe3\x9a\x1d\xd4\x83\xec\x60\ -\x6b\x83\x10\xbb\xf9\xa0\x27\x14\xd7\xc2\xce\x48\x5b\x00\x6c\x33\ -\x64\x5d\xb8\x80\x38\x7f\x0b\x73\x34\x08\x79\x9a\x0f\xfb\x91\x34\ -\x48\x23\x0d\xc8\xeb\xb7\xce\x20\xe7\x23\xb4\xa9\x58\xc4\xd0\x37\ -\xa2\xd1\x00\xb9\xc4\xc5\x04\xb1\x76\x0b\x31\x55\x0e\xcd\x46\xba\ -\xc8\x65\x5d\x10\xb6\x38\x12\xd0\xc6\xb6\x36\x08\x77\x61\x07\x8c\ -\x24\x9e\x73\x84\xc2\x1e\x20\x80\x88\x8e\xa4\x7e\xf4\x75\x90\xb9\ -\xf0\xc3\xd8\xc1\xe7\x06\xc1\xd7\x34\xe4\x23\xce\x1a\xec\x5a\x01\ -\x59\xf4\x5d\xbe\x08\xda\xf8\x5e\x05\xbd\x74\x19\xe3\x34\xf6\x7d\ -\xe0\x93\xbe\x35\xe7\xa3\xc4\x11\xfa\x89\xf9\xeb\xf0\x8d\xd8\x21\ -\x1d\xd3\x80\x98\x9e\xe8\x40\xdd\x3c\x71\x11\x63\xa0\x01\x71\x3b\ -\x1f\x2c\x8e\x84\x5c\x10\x85\x1d\x6a\xc3\x0e\x79\xb4\x2e\x1e\x47\ -\x3e\xd2\xd6\xd6\x86\x2f\xcc\xff\x88\xa3\x42\x4a\x40\x8a\x23\x1e\ -\x10\x38\x42\x28\xec\x01\x02\x88\xd8\x48\x52\x50\x41\xed\x1f\xa1\ -\x9c\x50\x0c\xde\xb9\x0c\xaf\x90\x60\xbb\x27\x60\x6d\x06\x48\xa3\ -\x01\xa5\x61\x07\x5b\xbf\xb5\x00\xed\x28\x8d\x62\x78\x59\xc7\x0c\ -\xb9\x60\x1e\x69\x41\x71\x29\xe2\x6e\x3e\xe4\x35\x76\xb8\x86\xbe\ -\x21\x3b\xc5\xa0\x23\x0d\xf6\x97\x51\xd6\x06\x21\x06\x1a\x90\x1b\ -\x76\x1f\xa0\x17\x84\x20\x0f\x06\x61\x69\x7b\xe3\xae\x8e\xc0\x71\ -\xa4\x8d\xda\x43\xc2\x9f\x8f\x78\xc0\xb1\x74\x67\x09\x81\xc0\x07\ -\x08\x20\x62\x23\xa9\x14\xfd\x2c\x0d\xc4\x40\x03\xf8\xac\xc1\x3a\ -\x58\x61\x37\x1d\x31\x17\x0b\x3b\x58\x15\x3a\x18\xa4\x02\x3d\x7f\ -\x0b\xe9\x6c\x27\xe4\x4b\x97\xd1\x47\x1a\x50\x76\xf3\xa1\x37\xec\ -\x90\xa6\xf9\x66\x61\xec\x5c\x86\x9f\x49\x83\xd2\xf8\x46\x9d\x9e\ -\x70\xc2\x68\x7c\xc3\xcf\xcc\x37\x41\xe4\x23\xe4\x5e\x6c\x2c\xea\ -\xdc\x11\xae\x38\xd2\xc6\xb6\x7e\x0b\x4b\xa3\xe1\xcb\x17\x78\x4e\ -\xe2\x21\x74\x71\x19\x40\x00\x11\x1b\x49\x27\xb1\x5e\x85\xd4\x8a\ -\x7c\x1a\x3b\xfc\x10\x69\xb4\x5d\x48\x90\xc3\x06\xdb\x61\xd7\x55\ -\xc1\x66\x27\xc0\x27\x7d\x23\x1f\x77\x52\x8c\x7e\x04\x40\x1e\xb6\ -\x03\x8a\xb3\xd1\x47\xec\x50\xce\xdf\x42\x3e\x92\x66\x3b\xca\xda\ -\x20\xa4\x46\x03\x30\x8a\xd0\xe2\x08\x1c\x49\xf0\xfb\x41\x20\x91\ -\x04\x5e\xcf\x80\x1c\x47\xf0\x48\x42\xee\xc2\xa2\x47\x11\x10\xc8\ -\x60\x1f\x0d\x62\xc2\x28\xeb\xa0\x51\x04\x04\x04\x02\x1f\x20\x80\ -\x88\x8c\x24\xc4\x4e\x97\x5d\x3b\x11\x07\xab\xa2\x5c\xba\x0c\xeb\ -\x20\xc1\xa7\xf9\xa0\x9b\x90\x16\xc1\x8e\xa4\x81\x4f\x21\x81\x36\ -\x4f\x40\xce\x48\x83\x5c\xb3\x03\x5d\x98\x8f\x6d\xd1\xf7\x62\xbc\ -\xa3\xaa\xec\xb0\x4d\x48\x48\xa3\x41\xb0\xf3\xb7\xe0\x15\x12\xea\ -\xfa\x2d\xe4\x29\x24\x48\xe3\xfb\x29\xbc\x61\x07\xbb\x41\x11\x3c\ -\xb0\x0a\x59\x92\x8f\xb5\xb0\xc3\x59\x1d\xc1\x23\x09\x67\xa3\xe1\ -\xeb\x57\xf4\x0a\x09\x54\xdc\xdd\x61\xc3\x1f\xfa\x00\x01\xc4\x40\ -\x6a\xb3\x01\x7e\xb6\x13\xa4\x61\x57\x85\xe8\x20\x21\x1d\xa5\x01\ -\xbf\x87\xb4\x09\xbe\x30\x5f\x05\x7e\xfe\x56\x3d\x24\x1f\x21\x46\ -\x55\x41\xd3\x13\xb0\xcd\x13\xcc\x28\x57\x4f\xb0\xe2\xdc\xcd\x87\ -\x74\x04\x00\x62\x34\xc8\x0f\x79\xc7\xe5\x55\x1c\xa3\xaa\x5b\xe0\ -\xa3\x41\x8d\x48\xd7\x97\x7f\x80\x5e\x3e\x01\x3a\xa2\xd8\x00\x52\ -\x23\xfd\xfc\x09\xcf\x47\xe8\xf5\x51\x3c\xee\x28\x02\x45\x12\x96\ -\x38\x42\xcd\x47\xa8\x19\xe9\xce\x1d\x02\xad\x70\x80\x00\x22\x2e\ -\x92\x14\x94\xd1\xd6\x41\xb6\x22\xdf\x29\x06\x2f\xec\xf2\xf3\xa1\ -\x65\x9d\x22\xca\xc5\xd8\xe0\xb9\x58\xd0\xbd\x6f\xaa\x48\xf7\x55\ -\x41\x47\x1a\x60\x07\x14\x6b\xe2\xdb\x71\x89\xd6\x68\x78\x8c\xbd\ -\x61\x37\x13\xd4\x8b\xe5\x42\xac\x3b\x41\x9a\x8a\xdd\x8b\xd1\x41\ -\x02\x66\xa4\xc6\x46\xcc\x9b\x48\xc1\x91\x64\x02\x59\xbe\xf5\x13\ -\xde\xf8\x46\x1f\xaf\xc3\xc8\x46\x88\x18\x0a\x05\x82\xd7\x98\x53\ -\x48\x4c\x58\x3a\xb1\x88\x38\xba\x13\x87\xbf\xe9\x00\x10\x40\x0c\ -\x24\x36\x1b\xe0\x65\x1d\x62\x9a\xaf\x0a\x79\x6d\x10\xfc\xea\x09\ -\x78\x7d\x34\x05\x65\x9a\x0f\x3e\x85\xb4\x1c\xbe\x2b\x16\x32\xf4\ -\x5d\xdc\x89\x6b\xd1\x37\x68\xf3\xc4\x7e\xac\x67\xa4\x6d\xc5\xb1\ -\x30\x1f\xf5\x98\x06\xfb\xbd\xf0\x85\x90\xf0\xd6\xf7\x71\x68\x07\ -\x09\xcb\x0d\x8a\x3b\xee\xde\xfd\x04\x9d\x9e\x80\x2d\x68\x40\xcb\ -\x48\x78\x8a\x3a\x78\x24\x11\x1c\x0d\x42\x89\xa4\x3b\xf8\x4f\xe1\ -\x07\x08\x20\xe2\x22\x69\x31\x3c\x1f\x41\x16\x7d\xcf\x41\x8c\xd8\ -\x21\xdd\x9f\x08\x5b\x1b\x04\x3e\xb4\x13\xba\xe8\x1b\xbc\xc3\x05\ -\xda\x68\x80\x37\xec\xce\x43\xd6\xaa\x82\x86\xec\xc0\x71\x34\x5f\ -\x13\xa9\x61\x87\xb9\x30\xbf\xb4\x14\xc7\xb9\x41\xb3\x66\xad\xf7\ -\xc6\xb6\xc6\xee\x0c\x7c\x2a\x96\x1b\x3c\x3b\x81\x3a\xcd\x07\x1f\ -\x55\x45\x8e\x23\x94\x0b\x42\x0c\xa0\x43\x0d\x68\x8d\x06\xd4\xfe\ -\x11\xb6\x38\x0a\x05\x03\x42\x8d\x86\xf7\xe8\x51\x74\xe7\x0e\xfe\ -\xa6\x03\x40\x00\x11\x15\x49\x0b\x76\x21\x0d\xab\xce\x41\xb9\xbc\ -\x1c\x75\xc7\xe5\x3c\xf8\xbd\x6f\x48\x8d\x06\x48\xdb\xdb\x1d\x7a\ -\xab\x01\xec\xdc\x20\xd8\xce\x65\xf0\xd6\x65\xd4\x33\xd2\x30\xa6\ -\x27\xd6\xe1\x58\x98\x3f\x0b\xf5\x34\xf6\x6e\x8c\x38\x02\x55\x48\ -\xf6\x7b\x2f\x22\xa6\x90\xb6\xc0\x47\x55\x7d\xd0\x1a\x76\xc8\x17\ -\x84\x18\x7c\x83\xcd\xf3\xa1\x17\x76\xf1\x58\x1a\x0d\x68\x51\x04\ -\x04\x7b\xf6\xe0\x9e\x8a\xc5\x2c\xeb\x40\xc5\x5d\x1c\xde\xa6\x03\ -\x40\x00\x31\x90\x34\x6c\x07\xdf\x5e\x0e\xa9\x8f\xc0\xbb\xcb\x27\ -\xd4\xa1\x4e\x21\x41\xef\x7d\x53\x46\x9d\x8a\x05\x9d\xbf\x85\x7c\ -\xa7\x18\xe4\xba\x2a\xc4\xda\x20\xa4\xf5\x5b\x28\xdb\x62\x27\xe3\ -\xdc\xcd\x07\x3e\xdb\xc9\x1b\x65\xdd\xc9\x13\xb4\x0a\x09\xd6\xb0\ -\x83\xcd\x4e\xac\x41\x34\x1a\x7c\x4e\x23\x46\x1a\x2e\x5d\x42\xbf\ -\x68\x07\xbe\xe2\x1b\x77\xe3\x1b\x47\x51\x07\x02\xc1\x78\xa7\xf9\ -\xb0\x47\x12\xde\xa6\x03\x40\x00\x11\x15\x49\x25\x48\xf9\x68\x67\ -\x2b\xfc\x40\x48\x05\x85\xb9\xb0\x35\xdf\xc8\xa7\x48\x83\xda\x75\ -\xf0\x46\x03\xb8\xb0\x2b\x2c\x84\xef\xe6\x83\x9e\x1b\x04\x3d\x7f\ -\x6b\x5f\x05\x6c\xa0\x01\x79\xc4\xee\x33\xce\x63\x1a\x50\xcf\x1a\ -\x3c\x80\xb6\xee\x04\x69\xe4\xbb\x0d\xed\x08\x00\x94\x86\x1d\xec\ -\x8a\x79\xd8\xf4\x44\x03\x4a\x85\x04\x8a\xa4\x54\xd4\x96\x1d\x31\ -\x71\x04\x8b\x20\x10\xc0\xd5\xb0\xfb\x8a\xb5\x3e\x02\x46\x51\x5c\ -\xdc\x4b\x7c\xe1\x0f\x10\x40\xc4\x44\xd2\x51\xd4\xb5\xaa\x73\x50\ -\xee\x14\x83\x35\xbe\xa1\x4b\x1a\x5a\x14\xbb\x10\x57\x21\x41\xda\ -\x75\x2a\xf0\x1d\x97\x90\x73\xec\x10\xa7\xb1\x43\x8f\x00\xd8\x86\ -\x6b\xf3\x04\xfc\x7a\x10\x36\xac\x87\x76\x1e\x40\x3a\xb4\x73\x26\ -\xd6\xb3\x9d\x50\x1b\x76\x4b\x61\x43\x76\xc0\x38\x6a\x44\x1b\xb1\ -\x43\xbd\xf9\xcd\x00\x69\x5c\x15\x65\xa4\x21\x08\x57\xc3\x2e\x14\ -\x4b\x24\x11\x19\x47\xa0\x48\xfa\xfe\xdd\x1e\x4f\x04\x00\x04\x10\ -\x31\x91\xc4\x06\x1f\x0c\xda\x89\x72\x6e\xd0\x84\x09\x55\xb0\x7b\ -\x48\xc1\xb7\x1a\xc0\x66\x90\x40\xe7\x06\x35\x41\xd6\x34\xa8\xc0\ -\x3b\xb1\xd0\x7b\x76\xc0\x53\xb1\x07\x21\x3b\xc5\xa6\x82\xcf\xdf\ -\xc2\x72\x04\x00\xb6\x13\xf3\x31\x32\x12\xc6\x34\x1f\x7c\x6d\x50\ -\x07\xa2\x61\x57\x03\xaf\x90\x90\xa7\x62\x1b\x61\xd3\x13\xe0\x38\ -\xba\x84\x71\xed\xf2\x6d\x61\xc4\x7a\x62\x44\xcb\x0e\x5b\x7d\x84\ -\x35\x8e\x82\x83\x89\x1b\x0d\x42\xe4\xa3\xef\xdf\xdf\xe2\x9b\xfb\ -\x03\x08\x20\x22\x22\x69\xce\x0a\xc4\x21\x69\x48\x87\x76\x42\xae\ -\x07\xa9\x86\xe7\xa3\x22\xe8\xdd\x7c\x05\x48\xb3\x7c\xb0\xe3\x4e\ -\xdc\x91\x4f\x91\x5e\xbe\xfc\x14\x7c\x37\x1f\xf4\xb2\x58\xb4\xcd\ -\x13\x88\x46\x03\xf2\x4e\xb1\x7e\x94\xb5\x41\x38\x16\x14\x6f\x47\ -\x3a\xef\x84\x88\xa9\xd8\x07\xf0\x2b\x7d\x51\xef\x98\xff\x89\x65\ -\xe8\x9b\x70\x1c\x05\x07\x23\x45\x12\xd6\x91\x6f\x94\x86\x1d\x52\ -\x24\xbd\x7d\xfb\xf2\x39\xee\x18\x00\x08\x20\x22\x22\x89\x15\xe9\ -\xf0\xdb\x56\x58\x61\x57\x05\x3b\xc7\x0e\x5c\xda\x15\xe5\x63\x74\ -\x62\x2b\x17\x85\x85\x4d\x81\xcd\x20\x81\xf2\x51\x49\x49\x09\xca\ -\xc8\x37\xd2\xa8\x2a\x72\x27\x36\x0f\xd7\x39\x76\xc8\x67\x0d\x62\ -\x9c\xc6\x8e\xdc\xb0\xeb\x40\x99\x9e\xe0\xc5\x1c\x55\x45\x6e\xd8\ -\x5d\x42\xbd\x76\x19\x76\x39\x5f\x2a\x96\xa1\x06\x2c\xbd\x58\x1c\ -\x71\x14\x8c\xde\xf8\xfe\x8a\xbb\xd1\x00\xcd\x48\x2f\x5f\x3e\xc5\ -\x1d\x03\x00\x01\xc4\x40\x5c\x69\x97\xbb\x13\x71\xa1\x2f\x62\xf3\ -\x44\x35\xac\x17\x0b\x1b\xf9\x46\xba\xd0\x17\x76\xf3\x44\x61\xe1\ -\x0c\x8c\xc3\x6f\xb3\x90\xae\xd9\x41\x99\x8a\x45\xe9\x21\xe1\x3b\ -\x8d\x1d\x63\x8d\x1d\xf6\xa9\x58\xa4\xf5\x5b\x88\xa9\x58\xa4\x1e\ -\x12\xbc\x61\x27\x84\x76\x7d\xb9\x01\x4a\xab\xc1\x4a\x17\xb9\x13\ -\x1b\x84\xaf\xcd\x80\x16\x49\x58\xa7\x62\xd1\xea\x23\x48\x1c\xbd\ -\xc4\xb3\x2e\x1c\x20\x80\x18\x88\x2c\xed\x76\x21\xce\xa4\x79\x8e\ -\xb2\xa4\x41\x0e\x75\x54\x15\x7a\x02\x40\x79\x39\x64\xc7\x25\xbc\ -\x42\x82\x8f\x34\xc0\x8e\xd2\xb8\x80\x6b\x37\x1f\xce\xf3\xb7\x90\ -\x3b\x48\xeb\xd1\xa6\x27\x60\x65\xdd\xf6\x8e\xb6\x36\xd8\x54\x2c\ -\xf2\x51\x1a\x48\x65\x1d\xfa\x14\x92\x10\xfa\x4d\xa4\xa0\xcb\x27\ -\xca\x50\x26\x63\x51\x46\x1a\x04\x08\xc7\x51\x30\x0b\x69\x8d\x86\ -\xb7\xa0\x48\x92\xc5\x5d\xde\x01\x04\x10\x03\x71\x93\x14\x3b\x77\ -\xa1\x4e\x21\x21\x8d\xd8\x41\x0e\x1b\x54\x9c\x07\xcb\x47\xb0\x59\ -\xbe\x30\xf8\xd5\x13\x48\xbd\x58\xb5\x83\x68\xeb\xb7\xd0\x4e\x63\ -\x47\x3f\xc7\xce\x13\xff\x69\xec\x18\xa3\x41\x38\x46\xec\x10\x91\ -\x84\x68\x7c\x0b\x82\xe3\x08\xb3\xd1\x00\x39\x33\x1f\x75\x71\x10\ -\x4a\xab\x01\x6b\x1c\x05\xa3\xc4\x11\x0b\x13\xc1\x5e\x2c\x3c\x92\ -\xa0\x71\xf4\xf2\x25\x3f\xce\x28\x00\x08\x20\x06\x62\x7a\xb2\xb9\ -\xc8\x23\x0d\xa8\xe7\x06\x41\x46\x55\x21\xc7\x34\x74\xc1\xef\x7d\ -\x03\xf6\x8f\x2a\xa1\xb3\x13\xb0\xab\xf9\x20\x8d\x6f\xc8\x14\x12\ -\x78\xc4\x4e\xb3\x02\x36\xaa\x8a\x63\xfd\xd6\xe4\x75\xd8\xe2\x08\ -\x65\x2a\x96\x03\xa3\xf1\x8d\xbc\x7e\x6b\x2f\xd6\x46\x03\x52\x07\ -\x09\x4b\x1c\xc1\x6e\xb9\x4c\x45\x6b\x7d\xe3\xec\x20\x61\xe4\x23\ -\x16\x10\x20\x72\xa4\x01\x94\x91\xde\x42\xe3\x08\x4f\x79\x07\x10\ -\x40\x84\x23\xa9\x17\xd2\x43\x42\x5e\xbf\x55\x85\xbc\xec\x04\x76\ -\xde\x09\x7c\x77\x39\xa8\xb0\x0b\x83\x0c\xab\xce\x40\x9c\xbf\x05\ -\x9e\x9e\x38\x08\x5f\x1b\x34\x15\x71\x31\x36\x33\xd2\x8e\x4b\xdc\ -\x8b\x55\x71\xed\x8a\x45\xdf\xcd\x87\xa5\xf1\xbd\x65\x0b\xf2\xb2\ -\x13\xe4\xd1\x20\x17\xcc\x0a\x09\x7a\x83\xa2\x81\x3a\x4a\x24\x61\ -\x36\x1a\x70\x96\x75\x88\x48\x4a\xc0\x3d\xcd\x77\x07\xb9\x61\x07\ -\x89\x24\x59\x59\x9c\x51\x00\x10\x40\x04\x23\x29\x0b\x7a\x48\xda\ -\xce\xd6\x39\x48\x77\x5c\xc2\x47\x1a\x20\x8b\xbe\x31\x0e\x84\x44\ -\x5d\x07\x89\x76\xd2\x37\xf4\xc4\xfc\xf9\x9a\xcd\x28\x67\xd2\xa0\ -\x2e\x69\x98\x8c\x7b\x9a\x0f\x9e\x8f\xd0\x2a\x24\x50\x07\x09\xde\ -\x68\xa8\xc1\xd6\x41\x42\x6a\xd8\x3d\x80\xf7\x62\xd1\xaf\xc6\x06\ -\xdf\x6b\xe0\x85\xba\xc8\x8e\x40\x85\x84\x1e\x47\xe0\x58\xc2\xb2\ -\xc6\x8e\x07\x57\x85\x04\x8a\x23\x59\x6f\x5c\x71\x00\x10\x40\x0c\ -\x44\x94\x76\xe0\x63\x1a\xe0\xb3\x13\x13\x61\x47\x00\x20\x66\x27\ -\xa0\xf7\x27\xae\x80\xed\xe6\x9b\x12\x86\x38\x8d\xfd\x1c\xd2\x82\ -\x62\xf8\xf9\x5b\x15\x18\x17\x63\xc3\x7a\xb1\xac\xd8\x47\x83\x10\ -\xeb\x20\x91\xef\x4f\x44\x5c\xe1\x72\x06\xcb\xa1\x9d\xd0\xc2\x6e\ -\x0d\x62\xf3\x04\x52\xc3\xee\xc1\x03\xf4\xd1\x20\x68\x61\x07\xbb\ -\x9c\x0f\x75\xf0\x1b\x1a\x49\x04\xe3\x88\x85\x05\x1e\x49\xa8\x53\ -\xb1\xb8\x1a\x0d\xb0\xb2\x0e\x14\x49\x38\x77\xa3\x03\x04\x10\xc1\ -\x48\x62\x05\x5f\x30\xbf\xb3\x15\x7e\xfe\x56\x15\x6c\xdd\x09\x7c\ -\xe4\x1b\x71\x5d\xd5\x0a\xe4\xd1\xa0\x55\x85\x48\x8d\x06\xe4\x75\ -\x90\xe0\xe9\x72\xb4\x35\x76\x28\xbb\xf9\x26\x23\xad\xdf\x62\x43\ -\x1d\x68\xd8\xb0\x15\x39\x1f\xf9\xe1\x38\xe9\xdb\x1e\xeb\x74\x39\ -\xea\xa2\x6f\xe4\xc6\x37\xd2\x2d\x97\x0f\xa1\x97\x21\xa5\x46\x22\ -\x2d\xfa\x46\xcf\x48\x28\x71\x14\x1c\x4c\x20\x92\xb0\x8f\x34\xc0\ -\x7b\xb1\xb0\x38\x92\xc5\x39\x7e\x07\x10\x40\x84\x22\xa9\xae\x0e\ -\x3e\x15\x0b\xef\x20\x4d\x98\x0b\xdf\x15\x8b\x18\xf9\x86\xdd\xfb\ -\x06\x6b\xd8\x21\x8e\x00\x28\x81\xae\x0d\x82\x2d\x3b\x81\x9e\x49\ -\x03\x59\x77\xc2\x8c\xed\x60\x55\xf0\xda\xa0\xfd\x58\x17\xe6\xcf\ -\x42\xab\x90\x30\x77\x8a\xa1\x8d\xd8\x61\x9f\xe6\x83\xac\x0d\x42\ -\xbb\x63\x1e\xdc\xb0\x83\x5d\xbb\x9c\x1a\x8b\xa5\x46\x22\x36\x1f\ -\x01\x23\x09\xcb\xc8\x37\x4a\x46\x8a\xbb\x03\xef\x21\x41\xe3\x48\ -\x56\xf6\x14\x8e\x48\x00\x08\x20\x06\xa2\x32\x12\xca\xba\x13\xd8\ -\xd0\xf7\x12\xb9\x7c\x50\xa3\x01\x3a\x62\x07\x8d\x24\xd8\x6e\xbe\ -\x76\x68\xa3\x01\x36\xf2\xad\x06\xdf\x71\x79\x01\x7e\x37\x1f\xd2\ -\x1d\x97\xb8\x76\xc5\x62\xec\xb8\x44\x6a\xd8\x5d\x99\x89\x3a\xd2\ -\x80\x98\x9e\xd8\x8b\x68\x34\xdc\xc7\xd2\xb0\x83\x2d\x28\xc6\xd6\ -\xb0\x83\x5f\x8d\x6d\x43\xb8\xb0\xc3\x13\x47\x2c\xc4\x36\x1a\x90\ -\xe3\x48\xb6\x0c\x47\x24\x00\x04\x10\xa1\x48\xca\xce\x85\x1e\xa5\ -\x01\x59\xac\x0a\xad\x8f\xa0\xbb\x90\xa6\x23\x5f\xb3\x03\x3f\x36\ -\x08\x5c\xd6\xa9\xac\x5a\x05\xac\x8f\xce\xc1\x76\x2e\xc3\x2f\xf4\ -\x05\x77\x90\x2a\x20\x3b\xc5\x50\x8e\x69\x38\x89\x7b\xc7\x65\x3f\ -\x8e\x51\x55\xa4\x46\xc3\x55\xd4\x93\xbe\x91\xe2\x08\xfb\x34\x1f\ -\xf2\x68\xd0\x5d\xcc\x38\x12\x5d\x7d\xed\x1a\xee\x56\x03\x8e\x86\ -\x1d\x52\x14\xa5\xa5\xa5\xe1\x5e\xbf\x85\x5c\x21\xbd\x45\x8e\x23\ -\x59\x5c\x3b\x68\x01\x02\x88\x50\x24\xd5\x82\xe6\xf9\xe0\xcb\x4e\ -\x20\x6b\xbe\xab\x11\xe7\x34\xc0\x36\xb8\x20\xf6\x4e\x20\x1d\xa5\ -\x81\xa8\x8f\x50\x2a\x24\x78\xa3\x01\x12\x47\xf0\xfa\x88\x15\xd6\ -\xb2\x2b\xc5\xb1\xe8\x1b\x65\x2a\x16\xed\xf0\xdb\x0e\x67\x8c\x63\ -\x1a\x36\x62\xac\x83\xc4\x33\xcd\x87\x79\x35\xb6\x6e\x2c\x8e\xc2\ -\x4e\x80\x60\x3e\x4a\x83\x46\x12\xee\x38\x8a\x43\x6b\xd8\x41\x40\ -\x3a\x8e\x48\x00\x08\x20\x02\x91\x74\x7e\xd7\x9c\x5d\x73\x5a\xe1\ -\x85\xdd\x44\xc4\x9d\x62\xc0\x7c\x34\x1d\xb6\xea\xbb\xab\x0b\x76\ -\xd6\x60\x13\x62\xc7\xa5\x3b\xb4\x87\x04\x9d\x8a\x05\x9f\x35\x08\ -\x3a\x58\x15\x3a\xac\x8a\xb4\xe3\xf2\x28\xc6\xfa\x2d\x1c\x8d\xef\ -\xad\x68\xd3\xe5\x88\x03\x21\xaf\x62\x6b\xd8\x61\xee\x8a\xc5\x6c\ -\x34\xa0\x34\xec\x6e\x22\xf2\xd1\x8d\x7b\xa9\xb8\xfa\xb1\x58\x1b\ -\x0d\x2c\xc1\xa8\x71\x94\x46\x70\x9a\x0f\x4b\x1c\xa5\x97\x62\x8f\ -\x05\x80\x00\x22\x10\x49\x6c\xf0\x0d\x2e\xb0\x9b\xb1\xd1\xa6\xcb\ -\xe7\x41\xe2\x08\xba\xee\xa4\x12\x7e\xde\x89\x3b\xf2\xd9\x4e\x18\ -\x47\x69\x34\x63\xb9\x18\x1b\xc7\x69\xec\xa8\xf9\xc8\x1b\xfb\x62\ -\xd5\xab\x68\xa3\x41\x17\x61\x47\x69\x38\x2d\xc5\x52\x21\x61\x9b\ -\xe6\x03\xc7\xd1\xc3\x87\x48\xb7\x5c\xda\xe0\x6f\x35\xe0\xe8\x1f\ -\x21\x45\x12\xfe\x86\xdd\x77\xf4\x0a\x09\x18\x47\xe9\x38\x4e\x57\ -\x03\x08\x20\x02\x91\x94\xbd\x13\x79\x01\xd7\x44\xf4\xa9\x58\x94\ -\xa3\x9d\x60\xe7\x06\x41\xa6\xcb\xdd\x91\xee\x4f\x54\x3b\x05\xdd\ -\xcd\x77\x01\xf9\x56\x03\xd4\xd1\x20\xd4\x05\xc5\x04\xd6\x6f\x41\ -\x36\x4f\x70\x21\xee\x4f\x84\x9f\xbf\x85\xbc\x36\x68\xcd\x16\x94\ -\xa9\xd8\xd3\x88\x45\xdf\x0d\xd8\x1a\x76\x65\x28\xb7\x5c\x82\x23\ -\x09\x79\xe9\x89\x00\xde\x8c\x84\x16\x45\xc0\x48\xc2\xbb\xa4\x01\ -\xb3\xd1\x00\x8e\x24\x1c\x1b\x68\x01\x02\x88\x40\x24\x69\xe6\x22\ -\xed\xb8\x9c\x08\xdf\x5e\x2e\x87\x7c\xb0\x6a\x0e\xac\x17\x5b\x0e\ -\xbf\x74\x79\x06\x64\x41\x71\x7d\x09\x72\x2f\x16\x76\xfe\x16\xd6\ -\x8b\xb1\x11\x0b\xf3\xf7\xe3\x3c\x31\x1f\x6d\x2a\x16\x51\xd6\x75\ -\xa0\x2e\xfa\x46\x4c\x97\x63\xc9\x47\x58\x2b\x24\x70\x1c\xdd\x44\ -\xbb\x1a\x1b\x16\x49\x58\xc7\x1a\x50\x6b\x24\x16\xcc\x48\x4a\x23\ -\x90\x8f\xa0\x91\x24\x8b\x1c\x47\xe9\x38\x2a\x25\x80\x00\xc2\x1f\ -\x49\x2b\x9e\xe7\x22\x2f\xdf\x42\xbe\x1e\x04\x74\xd4\x37\x62\x30\ -\x68\x05\xe2\x4c\x9a\xf6\xf6\xc2\x55\x90\xc6\x77\x6d\x3d\xe8\x56\ -\x83\x05\xf0\x5b\x0d\x60\x71\x54\x8c\xb2\xc6\x0e\xc7\x88\x1d\xda\ -\xfd\x89\x1b\x90\xa7\x90\xae\x20\xdd\x71\x79\xe6\x2a\xd2\x68\x10\ -\xd2\x01\xc5\x1b\x51\xe2\xa8\x11\xc7\x34\x1f\xac\x42\x42\x6d\xd8\ -\x41\xee\x98\xc7\xd7\x8f\xc5\xd9\xb0\x83\x45\x51\x5a\x06\xbe\x46\ -\xc3\x77\xb4\x0e\x12\x2c\x8e\xd2\xb3\xb0\x46\x03\x40\x00\xe1\x8f\ -\xa4\x69\xa8\xbb\x62\x11\x97\x97\x17\x15\x21\x46\x55\xd1\xa7\xf9\ -\x60\x6d\x06\xd0\xfa\x2d\x35\xf8\xda\x20\xd8\xd5\x7c\xa8\x53\x48\ -\x9f\x71\x1e\x7e\x8b\x36\xd2\x80\xb8\xc2\x05\xf5\x7a\x10\x8c\xab\ -\x90\xb0\x6e\x70\x41\xda\xcd\xf7\xa0\x01\x7b\xc3\xee\x30\x52\xc3\ -\x0e\x72\x61\x55\xaa\x2e\x64\x31\x24\x96\x1a\x29\x54\x9b\x50\x1c\ -\x41\x23\x09\x57\xa3\x01\xbd\xf1\x0d\x8b\xa4\x43\x58\xa3\x01\x20\ -\x80\xf0\x47\xd2\x63\xa4\xd9\x09\x50\x61\x57\x8d\xb6\xe1\x12\x7a\ -\xb6\x13\xd2\xfa\x2d\x15\x15\xe8\xed\x20\xf0\xa1\x6f\xa4\x6b\x76\ -\x90\x36\x4f\xa0\x5e\xe1\x82\xbc\x0e\x72\x3f\x96\x69\xbe\xad\x18\ -\x53\x48\x58\xae\xd9\xb1\x47\x6a\x7c\x6f\xbc\xbf\x06\xb1\x36\x08\ -\x79\x7a\xe2\x03\x62\xa4\x01\xad\xf1\x0d\xeb\xc5\xc2\x6f\x50\x74\ -\xc4\xd9\x6a\x08\xd5\xc6\x55\xd8\x21\x47\x12\x81\xe9\x72\x6c\x71\ -\x94\x8e\x7d\x39\x0a\x40\x00\xe1\x8f\xa4\x3c\xf0\x68\x10\xfc\xa0\ -\x6f\xf8\x15\x2e\x45\x48\xa7\xb1\xa3\x1c\x77\x82\x94\x91\xa0\x47\ -\x00\xa8\x2d\x58\x8e\x31\x62\x87\x34\xaa\xfa\x19\x73\x54\xd5\x13\ -\xcb\xce\x65\xa4\x2b\x5c\xfc\x50\x0e\x84\xbc\x8a\x3c\x3d\x81\xba\ -\xe8\x7b\x29\xfa\x34\x1f\xd2\x6e\x3e\xd4\x0a\xe9\x36\xfc\x6a\x6c\ -\x51\xa4\x5b\x2e\x6d\x70\x47\x12\xa1\xc2\x2e\x03\x08\xde\xe3\xcc\ -\x48\x98\x8d\x6f\x58\x1c\xfd\xc3\x3e\x5d\x01\x10\x40\x78\x23\xa9\ -\x2e\x1f\x79\x61\x3e\xda\xc1\xaa\xd0\x85\x90\x39\x48\xb7\x1a\x4c\ -\x51\x41\xdc\x71\x09\xdd\xe1\x02\x3e\x6c\x10\xb1\xc6\x4e\x73\x7e\ -\x33\xca\xda\xa0\xcf\xa8\xc7\x34\xac\xc3\x76\x85\xcb\x06\xf4\x1d\ -\x97\xdd\x28\x71\x04\x3f\xdb\xc9\x1e\xa9\xac\x5b\x83\x3c\xaa\xda\ -\x88\xb5\xf1\x3d\x1b\x4b\x61\x07\xbd\xbe\x1c\x72\x61\x95\x0d\x66\ -\x8d\x44\xa0\x65\x87\x1c\x47\x19\x38\x1b\x76\xdf\xb1\x36\xec\x40\ -\x51\x04\x04\x39\xd8\xe2\x01\x20\x80\xf0\x46\x12\x33\xf2\x99\x9d\ -\x55\x88\x86\xdd\x66\x58\xeb\x1b\x5e\xd6\x35\x55\x86\xc1\x76\x4f\ -\x40\xd6\x06\x41\xee\x4f\x84\x34\xbe\x11\xd3\xe5\xe0\x43\x3b\x1d\ -\x50\xd7\x06\xa1\xac\x3b\xc1\x3a\x15\x8b\xbc\xe3\xd2\xef\xca\xcc\ -\x6e\x94\x8b\xb1\xb1\xee\x8a\x45\x4c\xc5\xfa\x1c\x47\x5a\x63\x07\ -\x5f\xbf\x35\x1b\x6d\x9a\x0f\xd1\x68\x80\xdf\xa0\x28\xb1\x6c\x19\ -\x71\x7d\x24\x1c\x71\x04\x8c\x25\xdc\xeb\x4e\xde\x62\x8b\x23\x70\ -\x2c\x61\xbd\xff\x0a\x20\x80\xf0\x46\x52\x7f\x2b\xda\xee\x72\xe8\ -\xe5\xe5\xf3\x60\x23\x76\x05\xc8\xf5\xd1\x14\xe8\x14\x92\x2a\xa4\ -\x87\x04\xbf\x53\x0c\x71\xf3\x04\xb8\xb0\x73\xe8\x74\x20\xb0\x7e\ -\x0b\xdb\x69\xec\x48\x65\xdd\x4c\xe4\x38\x82\x1d\x01\x80\x7c\xb6\ -\x13\xf2\x92\x06\x9f\xc6\x46\xd4\x63\x1a\x84\x10\xeb\xb7\x60\x71\ -\x54\x06\x9d\x9d\x80\x5d\x8d\x0d\xb9\xb0\x6a\xd9\x0b\x8a\x32\x52\ -\x46\x06\xee\x86\xdd\x5b\xf4\x4e\x2c\x3c\x8e\xfe\x7a\x60\x8b\x07\ -\x80\x00\xc2\x1b\x49\xd9\x88\x65\x27\xd0\x91\x06\xd0\xd5\x7c\x9b\ -\x11\xc7\x34\xac\x80\xad\xcc\x9f\x82\x76\xcd\x4e\x09\xf4\x28\x8d\ -\xe5\x59\xd0\x73\xec\x10\x87\x76\xa2\x4f\xf3\x2d\x86\xc7\xd1\x3a\ -\x7c\xa3\x41\x1c\xd8\x1a\x0d\x57\xb1\x1d\x01\xb0\x71\xcd\x1a\x1c\ -\x8d\x6f\x5c\x23\x0d\xf0\x5e\xec\x0d\x58\x85\x04\xbe\x54\x2c\xc8\ -\x8a\x88\x1a\x09\x4b\xe3\x1b\x29\x92\xf0\x4f\xc5\xa2\xd6\x47\xa0\ -\x38\xfa\x8b\x75\x0f\x0c\x40\x00\x11\x2a\xee\xd0\x2a\x24\xe4\xbb\ -\x90\x0a\x20\x87\x0d\x36\x21\xee\xb8\x6c\x5f\xe5\x0e\xd9\x85\x54\ -\xef\x0f\xdb\xcd\x07\xdf\xe0\xa2\x09\x99\x42\xc2\xb1\x36\x08\x72\ -\xb0\xea\x7e\xac\x17\x63\x23\x5f\x85\x84\x34\x1a\x84\x79\xc7\xe5\ -\x45\xf4\x36\x03\x8e\xed\xe5\x68\xf5\x11\x6c\x9a\x0f\xd2\x41\x82\ -\x5e\x73\x09\xba\xbe\xdc\x11\xfb\x02\x21\xe2\x22\xc9\xc8\x08\x6b\ -\xc3\x2e\xee\x2d\xce\x46\x03\x38\x92\xb0\x76\x67\x01\x02\x08\x5f\ -\x24\xcd\x29\x82\xe5\x24\xf0\x4e\x31\xf4\x25\x0d\x7d\xb0\x45\xdf\ -\x95\x90\xeb\x41\x40\x9d\x58\x68\x2f\xb6\x16\x71\xb0\x2a\xda\x49\ -\xdf\x38\xae\xab\x9a\x36\x19\x63\xa4\x81\x1d\xb1\x53\x8c\x50\xe3\ -\x1b\x63\x9a\x6f\x29\x6c\x76\xc2\x07\xd1\xf8\xc6\x9e\x8f\x6e\xdf\ -\xbc\xf9\x10\xf9\x8a\xf9\x7b\x48\xd7\x97\xdb\xa0\x8d\x08\x21\x95\ -\x76\x58\xc6\x55\xa1\x31\x04\x8f\x23\x23\xac\x15\x12\x81\x38\xfa\ -\xfb\x07\xdb\xea\x3b\x80\x00\xc2\x17\x49\x53\x91\x0b\x3b\xf0\x81\ -\x90\x72\xd3\xe1\x3b\xc5\xba\xe0\xf7\x55\x2d\x5a\x04\x3f\x58\x15\ -\x71\x94\x86\x1a\x64\xd1\x37\xf2\xa1\x9d\xc5\xf3\xb7\x61\x4c\x97\ -\xb3\x22\x1d\xac\x8a\x28\xec\xb2\x1f\x23\xdd\x6a\xb0\x15\x57\x1c\ -\x61\x3b\x58\x15\x18\x45\x38\xa6\xf9\x60\x3b\x97\x91\x7b\x48\xb7\ -\x21\x19\x09\x6d\xa4\x01\x7e\x83\xa2\x0d\xd6\x51\x3b\xec\x35\x12\ -\x7a\x3e\x02\xe7\x24\xcc\x7c\x84\xd1\x43\x4a\x47\x8d\xa3\xbf\x7f\ -\x0e\x60\x89\x08\x80\x00\xc2\x17\x49\xfb\x61\x27\x00\x54\xc1\xa7\ -\x90\x8a\xa6\x83\xef\xb8\x6c\x69\xe9\xeb\xc2\xd8\xcd\x07\x5d\xf4\ -\xdd\x5b\x5f\xeb\x0f\x3e\xe9\x1b\x32\xcd\x77\x01\x7c\xb6\x13\xea\ -\x11\x00\x78\xa6\x62\xd1\x1a\xdf\xa8\x0b\xf3\x67\xce\xc4\xba\xee\ -\xc4\x1e\xed\xb8\x13\xc4\x69\xec\x48\x65\xdd\x83\x4b\x28\x1d\x24\ -\x61\x61\xd4\x35\x0d\xab\x57\x23\xae\x5d\x06\x36\x1a\x26\x81\x6e\ -\x7e\x7b\x81\x5a\x25\xe1\x6d\x35\xa0\x55\x48\x46\x46\x58\x23\x09\ -\x5f\xa3\xe1\x2f\x18\xfc\x11\xc6\x12\x11\x00\x01\x84\x2f\x92\x1e\ -\x43\xb6\x97\xcf\x9d\x8b\x58\x50\x9c\x0f\x1e\xb1\x03\xc6\x51\x1f\ -\xfc\x84\xe2\x4a\xc4\x19\x69\x33\x54\x51\x4f\xbf\x85\x34\xec\x7a\ -\x10\xa3\xaa\xb8\xee\x7d\x9b\xbc\x0e\xfb\x82\x62\x8c\xcd\x13\x4f\ -\x10\xa3\x41\x6d\xc8\x87\x76\xc2\x0f\x28\xde\x82\xb4\xee\x04\x65\ -\x34\x08\xdb\xfa\x2d\x50\x61\x07\x6f\xd9\x81\x33\x12\xec\x96\x4b\ -\xc8\xd5\xd8\x38\x9b\x0d\x04\x32\x92\x11\x04\x10\x6e\x34\xa0\xe7\ -\xa3\xbf\x7f\xfe\xec\xc1\x12\x11\x00\x01\x84\x2f\x92\xd8\x60\x97\ -\x97\x83\x2a\x24\x50\x2c\xe5\x6f\x86\x0d\x7d\x77\x21\x76\x4f\x20\ -\x76\xb8\xb8\xc3\x1b\x0d\x6a\xf0\x0b\x7d\xa1\x97\xc5\xce\xc7\x98\ -\x42\x42\xbd\x2c\x76\x1d\xce\xc6\xf7\x01\xf8\xa8\x2a\xca\x54\xec\ -\xf6\x36\xfc\xd3\x7c\x28\x8b\xbe\x1f\x1c\xfa\x70\x09\x63\x37\x9f\ -\x30\x7a\x59\x07\xbf\xbe\xfc\x05\xec\x76\xbe\x20\xcc\x8c\xa4\x8d\ -\xbf\xd5\x80\x1c\x47\x46\x38\xa7\xcb\x5f\x62\x69\x7c\x83\x2b\x24\ -\x20\x88\xc3\x12\x11\x00\x01\x84\x2f\x92\xb6\xc1\x4e\x28\x86\x8c\ -\x34\xc8\xe5\x4f\x87\x6d\x9e\xe8\x43\x3a\x8d\x1d\xba\x9b\xaf\x10\ -\xf9\x22\x52\x44\xc3\x0e\x76\xb0\x2a\xfa\x1a\x3b\xb4\x0b\x7d\x3d\ -\xf7\x63\x1d\x55\x9d\x35\x0b\xed\xfe\x44\x5c\x57\x21\x21\xc7\x11\ -\xf2\x19\x69\x82\x18\x0d\xbb\x1d\xd8\xd6\x34\x20\x8d\xd8\x41\x1b\ -\x0d\xe0\x9b\xdf\x1c\x09\x74\x92\xf0\xc7\x91\x11\x8e\xb2\x0e\x6b\ -\x9b\x01\x16\x47\x7f\xfe\x62\x89\x08\x80\x00\xc2\x17\x49\x39\xd0\ -\xc6\x37\x62\xf7\xc4\x3c\xa4\x11\xbb\x15\x48\xf7\x90\xc2\xee\x7d\ -\x83\x8d\x06\xc1\xcf\x76\xea\xc1\xbc\xc2\x05\xb5\xac\x83\x5f\x6b\ -\x80\x6d\x2a\x16\x71\xd8\x20\xda\x1d\x97\x88\x05\xc5\xf6\xc8\x3b\ -\x5c\xc0\x0b\x8a\xf9\xb1\x1c\xac\x8a\x36\x1a\x24\x8c\xbc\xa0\x18\ -\x5a\xd6\xdd\xc3\xbc\xbe\x1c\x67\x24\xe1\x2c\xed\x32\x30\x73\x12\ -\x52\xa3\x01\xc7\x68\x10\xa2\xd1\x00\x06\x58\x22\x02\x20\x80\xf0\ -\x44\x52\x13\xb4\x61\x57\x0d\x3d\x31\xbf\x68\x09\x2c\x8e\x72\x10\ -\xf5\x11\x24\x1f\x15\xb6\xc3\x1a\xdf\xb0\xdd\x7c\xd0\x23\x00\xa0\ -\xa3\x41\xc5\x88\x7b\xdf\xa0\x27\x7d\x23\x5f\xe8\x0b\xea\xc5\x62\ -\xdd\x15\xbb\x15\xdb\x88\x1d\xea\x6e\x3e\xa4\x91\x86\xfb\xf7\xd7\ -\x2c\xdd\x82\xbc\xa0\x18\xb1\xee\x04\x73\x54\x15\xb9\xb0\x5b\x8d\ -\x98\x9e\x80\x64\x24\xd8\x0d\x8a\x8e\xf8\xdb\x76\x2c\xf8\xe3\xc8\ -\x88\xc0\xa8\x2a\x96\xb2\x0e\x04\x2a\x30\x63\x02\x20\x80\xf0\x44\ -\x12\xb3\x42\x15\xfc\x1e\x52\xa4\xf9\x72\xd8\x26\x24\xc4\x5d\x48\ -\xb0\xdd\x13\xbd\xd0\x53\xa4\x17\x80\xce\xdf\x3a\x85\x74\xf3\x04\ -\xd6\x29\x24\x94\xeb\xaa\xb0\x2d\x28\x06\x46\xd1\x01\x6c\xf7\x27\ -\x6e\x47\xde\xcd\x57\x53\x83\x75\x8d\xdd\xf1\xc6\x46\xd4\xb5\x41\ -\x42\x58\x16\x7d\x1f\xc6\x8c\x23\xd8\xf5\xe5\x90\xab\x48\x31\x22\ -\x09\x7f\x3f\x36\x03\x33\x92\xee\xe0\x99\x9e\xc0\x9a\x8f\xfe\xfc\ -\xc1\x32\x7a\x07\x10\x40\x78\x22\x89\xad\x0a\x3e\xd2\xb0\x04\x7a\ -\x33\xb6\x22\xea\xa1\x9d\x4d\x53\xc2\x90\x7b\x48\xa0\xa9\x58\x7f\ -\xe8\xc2\x7c\xf0\xc8\x77\x4f\x0f\xe2\x08\x00\x07\x66\x5c\x3b\xc5\ -\xd6\xe1\x58\x77\x72\x00\x79\x6d\x90\x1f\xca\x59\x83\xa8\x0d\x3b\ -\x58\x1c\xdd\x5f\x8a\x7a\x04\x00\x52\x85\x24\x84\x7f\xfd\xd6\xb5\ -\x7b\x18\x8d\x06\x29\xa9\xb3\x67\x6d\xf0\xb7\xed\xf0\xd7\x48\xa8\ -\x91\x44\x60\x34\x08\x11\x47\x7f\x6e\x62\xc6\x04\x40\x00\xe1\x89\ -\x24\xf6\x2a\xf8\xb6\x58\xc8\xcd\x13\x45\x8a\xf3\x60\x23\xdf\xd0\ -\x0a\x09\xe9\x0a\x17\xd0\xc1\xaa\xb5\x68\x87\x0d\x42\xd7\xd8\x21\ -\xe5\x23\x66\x2c\x27\xe6\xe3\x38\x8d\x7d\x16\xea\x3d\xa4\xa8\xf7\ -\xbe\x61\x19\x55\xdd\x82\x7a\x4c\x03\xfc\x8c\xb4\x0f\x0d\x58\x47\ -\x83\xca\x10\x23\x76\xc8\x8d\x06\x94\x6b\x97\xcd\xf0\x8f\x36\x60\ -\x8b\x24\x44\x14\xc9\xcb\x63\xab\x90\xb0\x15\x76\x28\x71\xf4\x07\ -\xcb\xb1\x1b\x00\x01\x84\x27\x92\x1e\x57\x61\x1e\x77\x02\x59\x98\ -\x9f\xb3\x02\xed\x2a\x24\xd8\x48\x83\x3f\xca\xc5\xd8\x53\xa1\x15\ -\x52\x71\x27\xda\x89\xf9\xa8\x27\x14\xa3\x1e\xda\x89\xf5\x60\xd5\ -\x99\xc8\x53\x48\x58\xa7\xcb\x37\xde\x47\x9c\x98\x7f\x1c\x63\x2a\ -\x56\x08\xf7\x7c\x39\xa4\xac\xbb\x07\x1b\xf9\x9e\x04\xbf\x76\xf9\ -\xec\xd9\x4d\x47\xf0\xf6\x64\xd1\x07\x84\xd0\x33\x92\x3c\x96\x4e\ -\xec\x4b\x1c\x71\x84\x14\x49\x58\x86\x58\x01\x02\x08\x4f\x24\x95\ -\xce\xad\x82\xce\xc4\x82\x3a\x48\x88\xed\xe5\x2b\x60\x97\xc5\x4e\ -\x01\xf7\x62\x67\xac\x2a\x84\xde\x15\xdb\x0b\x5b\x63\x07\x3b\x8d\ -\x1d\xda\xfa\x46\x39\xfc\x16\xa5\x42\x2a\x9d\xbc\x1f\xb1\xee\x04\ -\x7d\x49\xc3\x7a\x2c\x71\xb4\xfd\x0c\xca\x68\xd0\x5e\x6c\xbb\xf9\ -\x7c\x50\x4f\x63\xbf\x84\xb2\x53\x4c\x18\x1e\x47\x88\xa9\x58\xa4\ -\x86\x1d\xb0\xcd\x00\x6d\x34\x9c\x3d\x7b\xf6\xd9\x75\xac\xbd\x24\ -\x62\xfa\xb1\xa0\x8c\x24\x2f\x8f\xb1\xa4\x01\xf7\x68\x10\x1c\x64\ -\x62\x99\x9c\x05\x08\x20\x3c\x91\xa4\x89\xb4\xee\x04\x5e\x21\x21\ -\xce\x76\x82\x1d\x01\xd0\x8e\xba\xa6\x01\x3e\x15\x7b\xa1\x07\x76\ -\xab\x01\xca\xfa\xad\x93\x79\xc8\x57\xb8\xac\xc3\x71\xe9\x32\xf2\ -\xc1\xaa\x57\xfc\x50\xd6\x06\xa1\xdc\x43\xba\x17\xcb\xae\x58\xe4\ -\x51\x55\xe4\x9d\x62\xa8\x71\x04\xaf\x90\x10\x65\x1d\x72\xc3\x0e\ -\x72\x3b\x1f\x31\xc5\x1d\x8e\x56\x03\x5a\x24\x11\xd7\x68\x00\xc6\ -\x51\x26\x96\x36\x38\x40\x00\xe1\x89\x24\xc5\x09\xb0\x9b\x27\xa6\ -\x23\xad\xb1\x83\x8e\x34\x94\x2f\x82\x5f\x85\x34\xc3\x1d\xdc\x41\ -\x42\x5c\x6b\x00\x3d\xa6\x01\xb6\x10\xb2\x13\x69\x34\x28\x0f\x65\ -\x0a\x69\xf2\x7e\xec\x8b\x55\xf1\xdc\x71\x89\x63\x7a\x02\xdb\x11\ -\x00\xe8\x67\xa4\xdd\x45\xda\xba\x8c\x68\xd8\xdd\x40\x6d\xd8\x3d\ -\x82\x5f\xe9\x0b\xba\xb0\x0a\x4f\x46\x0a\xc6\xda\xb4\x43\x89\x23\ -\x79\xb4\x38\x92\xc5\x33\x1a\x84\x88\xa4\x4c\xcc\x98\x00\x08\x20\ -\xdc\x91\xa4\x0c\x3f\x4a\x03\x32\xf2\x3d\x0f\xe5\x6e\xbe\x72\xd8\ -\xc2\x7c\xe8\x5c\x2c\xf4\x14\x69\xa4\xeb\x41\x2a\xe0\x8b\x55\xb1\ -\x4f\x4f\xe0\x3b\x23\x0d\x65\x9a\xcf\xaf\x1b\x75\x2a\x16\x76\xc7\ -\x25\xd2\x4e\x31\xec\xbb\x62\xc1\x3b\x2e\x1b\x84\x30\x8f\x69\x40\ -\x8c\x06\x41\xd6\x34\x48\x60\x54\x48\xd0\xab\x48\x1d\x51\x73\x52\ -\x28\x9e\xd2\x0e\x35\x92\xe4\x11\x91\xf4\xfd\x3b\xf1\x8d\x06\x50\ -\x1c\x65\x62\x76\x94\x00\x02\x08\x77\x24\x69\xc0\x07\x1a\xa0\x23\ -\x76\x7d\x48\x57\xb8\x20\xa6\xcb\x51\x77\x21\xc1\x2f\x2f\x07\x8d\ -\xaa\x56\xa0\x8d\x06\xa1\x1d\x49\x53\x8a\x31\x62\x87\x74\xb0\xea\ -\x7a\x8c\x11\x3b\x8c\x4b\x97\x11\xa3\x41\x6b\xee\x63\x99\xe6\x13\ -\x84\x2e\x28\xc6\x5c\x76\x52\x76\x13\x31\xcd\x77\x0d\xb9\xb0\xdb\ -\x0d\x29\xeb\xa0\x57\xfa\x02\x73\x92\x23\xee\xd2\x0e\xb5\xd9\x80\ -\xad\xb0\x03\x45\x12\x31\x15\x12\x5a\x1c\x65\x62\x5e\x03\x03\x10\ -\x40\xb8\x23\xe9\x28\x6c\xc7\x25\xb4\xd1\xd0\xa2\x88\xd8\x5e\xbe\ -\x08\x79\xa7\x18\x6c\xd1\x37\xec\xf2\x72\x60\xa3\x61\x5f\xcf\x54\ -\x44\x07\x09\x2d\x23\xc1\xa7\xf9\x26\x97\x62\x5d\xf4\xbd\x15\x34\ -\x85\x84\x7a\x62\x3e\xd7\x13\xd8\x54\x6c\x07\xe6\xc5\xd8\xbc\x88\ -\x4b\x97\xc1\x0b\xb8\xa0\x1d\x24\x2c\x0b\xf3\xef\x22\x35\xbe\x91\ -\xa6\xf9\x24\x10\x71\x04\xaf\x90\xce\x42\x6e\x50\x74\xc4\xcc\x48\ -\xa1\x44\x75\x92\xa0\x91\x44\xca\x48\x03\x3c\x92\x8e\x63\x44\x05\ -\x40\x00\xe1\x8e\xa4\xd2\xba\x3a\xe4\x6b\x76\xfa\x60\x23\x76\x4d\ -\xf0\xed\xe5\x88\xd3\xd8\xeb\xeb\xcf\xfb\x83\x3b\x48\x07\x61\x47\ -\x69\xc0\x8f\x00\x40\xb9\x9b\x0f\x98\x91\x58\xb1\x2e\x28\xee\x7f\ -\xdc\x8f\x73\xd1\x37\x2c\x1f\x9d\xb9\xda\xd1\xd1\x81\x99\x8f\xd0\ -\x8f\x69\x78\x8a\xb2\x9b\x0f\x73\x6d\xd0\x4d\x58\x2f\x16\xbd\x83\ -\x34\x09\x56\x1f\x9d\x05\x67\x24\xd0\xa5\x62\xc4\x47\x12\xd6\x9c\ -\x04\x8d\xa3\xb7\x44\xc6\x51\x26\x04\x88\x62\x44\x05\x40\x00\xe1\ -\x8e\xa4\x7e\xc4\x46\x31\xf8\x6e\xbe\x1c\xa4\xd6\x77\xbb\x0a\xd2\ -\xad\x06\x88\x53\xa4\x11\xb7\x1a\x20\x1f\xa5\xc1\x0c\x59\x07\x89\ -\xed\xe6\x09\x8c\x29\x24\x94\xa9\x58\xd4\x9d\x62\xf0\x5e\x6c\x0d\ -\x81\xdd\x7c\x87\x50\x47\x83\x60\xb3\x13\xe8\xa3\xaa\x48\xeb\x4e\ -\x10\x71\x04\xcc\x48\xb0\x6b\x97\xcd\x82\x88\x1c\x6e\xc0\x1a\x47\ -\xf2\x48\x8b\xbe\x65\x71\x4d\xf3\x61\x46\xd2\x2d\x8c\xa8\x00\x08\ -\x20\xdc\x91\xc4\x5e\x07\xbf\xbc\x5c\x11\xda\x8b\x85\x35\x1a\x20\ -\x83\x41\x2a\xab\xe0\x27\xe6\x9f\x87\xf6\x62\x41\xf7\xbe\x5d\x80\ -\x1d\x64\x37\x1f\xfd\x8e\x4b\xe4\x38\xc2\xb5\x0b\x69\xeb\xd6\xad\ -\xe8\xe7\x6f\x21\x4f\xf3\x21\xe2\x08\xd7\xad\x06\xa7\x91\xeb\x23\ -\x17\x2c\xbb\x62\x51\xf2\x11\xd2\xa8\xea\x6e\xa4\x0e\x12\xec\x2a\ -\x52\xdc\x91\x84\x75\xdc\x0e\x4b\x24\xbd\xc5\x33\x1a\x84\x92\x91\ -\x32\xe1\x00\x73\xa9\x31\x40\x00\xe1\x89\x24\xe8\x71\x27\xd0\x91\ -\x6f\xe8\x42\x48\x70\x7d\x54\x09\x8d\x23\xf4\xa3\x34\xb2\x60\x57\ -\x21\x41\xef\xe6\x43\x8c\x34\x7c\x3e\x8a\xbe\x30\x1f\xfd\x08\x00\ -\x76\x2c\xbd\x58\xf4\xe9\xf2\x0e\xcc\xbb\xf9\x50\xae\x42\x42\xb9\ -\xc2\xa5\x01\x69\x2a\x16\xb1\xa6\xe1\x26\x6a\x85\x84\xbc\xa4\x01\ -\xb9\xd1\x00\xbd\x2e\x16\x25\x92\xf0\x94\x76\xd8\x33\x92\xfc\x77\ -\xbc\x71\x84\xd9\xf8\x86\x80\x2f\x18\x51\x01\x10\x40\xb8\x23\xe9\ -\x31\xac\xf1\x3d\x0f\x7e\xed\x1b\xfc\xf4\x5b\x94\xa9\xd8\x5e\xd8\ -\x21\xd2\xe0\x46\x83\x46\xcf\x05\xf8\x7a\x62\x44\x61\xf7\x19\x75\ -\xd1\x37\x8e\x8b\xb1\xd9\xc1\x77\x5c\xae\xc7\x96\x8f\xf0\xad\x0d\ -\x42\x99\x8a\x15\x44\xda\x29\x86\x79\xb6\xd3\x4d\xd0\x59\x1a\xa2\ -\xd0\x96\xdd\x3d\x58\xa3\xe1\xc5\x8b\x17\xd0\x91\x86\x23\x48\x71\ -\x04\x8a\x24\x8c\xd1\x55\x52\x32\x92\x7c\xdc\x5b\xe2\x1b\x0d\x88\ -\x9c\xf4\x1d\x23\x2a\x00\x02\x08\x77\x24\x65\xc3\xe2\x68\x9e\x62\ -\x1f\xe2\x02\x45\xf8\x74\x79\x21\xec\xa4\x6f\xf0\xd6\xe5\xf3\x90\ -\x63\x1a\xb2\x70\x5c\xe1\x82\xba\x58\x15\xdc\x66\xc0\x75\x1a\xfb\ -\x01\x6c\x6b\x83\x50\x77\xf3\xe1\x38\x4a\x03\x69\x49\x03\xce\xe9\ -\x72\xd0\xfa\x2d\x78\x61\x77\x63\x2d\x52\xc3\x0e\xde\x68\x40\x5c\ -\x8d\x7d\xec\x16\xce\x48\x22\x32\x27\xe1\x5a\xf4\x9d\x8e\x27\x8e\ -\xb0\x8c\x0b\x01\x04\x10\xee\x48\x9a\x26\x07\x8b\xa3\x16\xf8\x59\ -\x83\xd0\x86\x1d\x64\xdd\x09\x64\xc7\x65\x49\x2d\x64\x4d\x03\xec\ -\xf2\xf2\x1e\xc8\x92\x06\x94\xf5\x5b\xc8\x67\x3b\x4d\xc3\x79\xb6\ -\x13\xf8\x28\x0d\x44\x59\x77\x05\x69\x57\xec\x99\xab\x57\xb1\x9d\ -\x91\x76\x7f\x23\x62\x83\x0b\xea\xb9\x41\x68\x8d\x06\xd8\x39\x76\ -\x65\x37\x91\x17\xe6\xdf\xc3\xd6\xb0\x03\xdf\x31\x0f\x8e\xa2\x63\ -\xb7\x16\x9a\xe1\xea\xca\x12\x19\x49\x2f\xb1\xae\x3b\xf9\x87\x6d\ -\x34\x08\x01\x30\xc7\x85\x00\x02\x08\x77\x24\x9d\x84\x8c\xd8\x21\ -\xf7\x62\x91\x76\xf3\x41\x67\x27\x80\x19\xe9\x3c\x7c\xd1\x37\x62\ -\xa7\xd8\xfc\xe6\xf9\xe0\xc6\x37\xf4\x08\x80\xa3\x68\x8b\xbe\x71\ -\x6e\x5d\x3e\x80\x7d\x34\x08\x74\xd2\x77\x07\x4a\x07\xe9\x22\x96\ -\x51\xd5\xd3\x04\xa6\xf9\x90\xa7\x62\x91\xe2\x08\x98\x91\x1e\xc1\ -\xa6\xf9\x90\x1b\x0d\xa0\xeb\xcb\xa5\x49\x89\x24\x8c\x38\x92\x7f\ -\x4b\x6c\x1c\xa1\x44\x12\xe6\xb8\x10\x40\x00\xe1\x8e\x24\x66\xe8\ -\xa8\x6a\x0b\xd2\x6e\xbe\x4a\xd8\x31\x0d\x28\xb7\x1a\x40\x8f\x00\ -\xd0\xd0\x40\xdc\xcd\x07\xda\xe0\xd2\x89\x36\xd2\x00\x5f\xf4\x8d\ -\x7a\x4c\x43\x3f\x96\xd1\x20\xf4\xd3\xd8\xb1\x1f\xa5\x81\x72\xab\ -\x81\x4f\x23\xea\xf9\x5b\x42\xb8\xa7\xcb\x91\xa7\xf9\x40\x6b\xec\ -\x1e\x3d\x42\x9a\x9e\x78\x06\xbb\xbe\x1c\x74\x7f\xb9\x34\x91\x75\ -\x12\xb6\x31\x21\x20\x78\x49\x68\xfd\x16\xb6\x28\xc2\x12\x49\x00\ -\x01\x84\x3b\x92\x2e\x14\xc1\x7b\xb1\x39\x88\x45\xdf\x28\xd7\x1a\ -\xc0\xcf\x3b\x81\x16\x76\x1a\xfb\x10\xd7\xbe\xa1\x4c\xf3\x21\x5f\ -\x72\x89\x7b\x34\x08\xbe\x7e\x0b\x7c\x85\x0b\x62\x7a\x02\xd8\xfa\ -\x76\xc6\xb6\x30\x1f\xa9\xf1\x8d\x72\xb0\xea\x07\xd4\xc3\x6f\xe1\ -\x43\xdf\x0f\x91\x1b\x76\x6b\x91\xa7\x27\xe0\xd3\x7c\x88\x86\x1d\ -\xf8\x96\x4b\x37\x42\x23\x77\x64\x44\xd2\x3f\x7c\x8d\x06\xec\x91\ -\x04\x10\x40\xb8\x23\xe9\x3c\x7c\xfd\x56\x0e\xa2\xd1\x00\xdf\x16\ -\x8b\xda\xf8\x5e\x00\x6d\xd8\xc1\x16\x14\xa3\x76\x62\x71\x34\xec\ -\x30\x77\xc5\x1e\xc0\xd1\xb0\xc3\x3a\x62\x07\xb9\xbc\x1c\xf3\xfc\ -\x2d\x8c\x46\x03\xf2\x81\x90\xd0\x1d\x2e\x28\x0b\xf3\x27\xc1\x1b\ -\x76\x9b\x60\x15\x12\xec\xda\x65\x37\x42\x7d\x59\xac\x83\x42\x28\ -\x91\x04\x8f\x22\x7c\xd3\x13\x04\x22\x09\x20\x80\x70\x47\xd2\x0c\ -\xc8\x6e\x3e\xa4\xfb\x13\x2b\x21\xd3\x7c\x88\xd1\xa0\x12\xd8\x55\ -\x48\x0b\xa0\xe7\x6f\x41\xee\x8a\x9d\x8f\xda\xf8\x46\x39\x58\x15\ -\x72\xfc\x16\xca\x4e\xb1\xc7\x18\x3b\x2e\x31\xce\xdf\x82\x47\x52\ -\x0d\xda\x8e\x4b\xac\x87\xdf\x62\x16\x76\x90\x45\xdf\x5e\x48\x5b\ -\x97\xe1\x4b\x1a\x5e\xec\xf6\xf0\x40\xea\xc5\x42\x1b\xdf\xb7\xc0\ -\x71\x84\x3b\x92\x88\x18\x13\x42\x8f\x24\x22\x1a\xdf\xb8\x22\x09\ -\x20\x80\xf0\x44\x12\x64\x58\xb5\x0f\xde\xb0\xab\x84\x1d\xc7\x0e\ -\xb9\xd5\x00\xbc\x51\x0c\x36\xd2\x00\xbb\x87\xb4\x02\xb5\x13\xcb\ -\x8c\x6d\xa7\x18\xda\x89\xf9\xd8\x1a\xdf\x28\x27\x7d\xb7\x21\xce\ -\xdf\xaa\x41\x5a\xbf\x75\x1f\x7d\x4d\x03\x7c\xa4\x01\xc7\xfa\xad\ -\x87\x48\x6b\x83\x90\xa7\xf9\x90\xe3\xe8\x19\xac\x3e\x02\xc6\x11\ -\x11\x91\xc4\x82\x6d\x75\x83\x3c\x72\x24\x61\xe9\xc5\xe2\xcf\x46\ -\xd8\x22\x09\x20\x80\x70\x47\x52\x3b\x74\x49\x03\x6c\x2a\xb6\x72\ -\xd1\x14\xb4\x51\x55\xd8\x01\xc5\xd0\xed\xe5\x3d\x53\x2b\x30\xb7\ -\x2e\xa3\xad\xf9\x2e\xdd\xbf\x0e\xcb\xf9\x5b\xd0\x36\xc3\x7a\xec\ -\x17\x63\x5f\xc5\x76\x7f\x22\xa2\x61\xc7\x8f\x76\x94\x46\x03\xf6\ -\xa9\xd8\x9b\x48\xd3\x7c\x48\x53\x48\xbb\x61\x15\x12\xa4\xf1\x2d\ -\x7d\x1d\x92\x8f\xa0\x37\xfa\xba\xe1\x18\x70\x20\xae\x4a\x92\x27\ -\x62\x34\x88\xa8\x48\x02\x08\x20\xdc\x91\x04\xdf\x15\x0b\x19\x0d\ -\x82\xaf\x0d\x82\x9d\x98\x0f\x8c\x24\x7f\x35\xc4\xb9\x41\x1a\xfb\ -\xa0\x0b\x8a\xe7\xa3\x2c\xcc\xcf\x43\x5b\x1b\xb4\x0e\xc7\xe1\xb7\ -\xb3\x50\xa6\xcb\x91\xef\x7d\xc3\xb6\x0e\x12\x57\xc3\x0e\xed\x98\ -\x06\x94\xed\xe5\x0f\x31\xa7\xf9\x20\xf5\x11\xf2\x34\x1f\x34\x23\ -\xc1\xaf\xc6\x26\xae\x2f\x8b\x33\x27\x11\x1e\x0d\xca\xfc\x43\x44\ -\x24\x01\x04\x10\xee\x48\x52\x84\xb6\xbe\x51\x36\xb8\xc0\x1a\x0d\ -\xbd\x88\x2b\xc5\x16\x20\x9f\xed\x84\x52\x21\xc1\xaf\xd9\xc1\xd1\ -\x68\xe8\x47\x5b\x63\x87\x54\xd6\x75\x3f\xc1\x76\x37\x1f\xf2\x89\ -\xf9\xd8\x2f\xf4\x45\x3f\xb4\x53\x18\x7d\x0a\x09\x31\xcd\x07\x8a\ -\xa3\x17\x28\x8d\xef\xeb\xd0\x5e\x2c\x3c\x23\xe1\x8c\x24\xbc\xa3\ -\x42\x88\x48\xc2\xd5\x68\xc0\x9b\x8f\xb0\x44\x12\x40\x00\xe1\xc9\ -\x49\xa0\x46\x03\xa4\x17\x8b\x98\x8a\x55\x01\x1f\x01\x70\x0e\xba\ -\x7e\xeb\x3c\xe2\x8c\x34\xd8\x48\x03\xe6\xf9\x5b\xe8\xa7\xb1\x63\ -\x99\x2e\xc7\x9c\x42\x7a\x82\x38\x31\x1f\xe7\x35\x3b\xf0\xfb\x13\ -\x1b\x91\x37\xb8\x34\xa0\xc7\x11\x7a\xc3\x0e\x6d\x2a\x16\x9e\x8f\ -\x36\xc1\x1b\xdf\xe0\x38\x82\xdc\x16\x4b\x54\x24\x65\xe0\x6a\x38\ -\xe0\x98\x9e\xf8\x43\x6a\x24\x01\x04\x10\xde\x9c\x04\xaa\x8f\x72\ -\xa0\xc7\x34\x84\x4d\x41\x69\x7c\x43\x6e\x98\x47\x5c\x5e\x7e\x01\ -\x34\x3b\x01\xbd\x18\xbb\x13\xeb\x5a\xd5\x69\x88\x05\xc5\x58\xee\ -\x21\xc5\x76\xab\x01\x68\x9a\x0f\x6d\x37\xdf\x45\xd4\xd9\x09\xcc\ -\xd3\xd8\x5d\xb0\x4e\x4f\x20\xf7\x90\xb0\xad\xdf\x3a\x82\x34\xaa\ -\x0a\x8a\xa3\x95\x2b\x31\x23\x29\x94\xec\x9c\x84\xbe\xc6\xee\x2f\ -\xde\x28\xc2\x12\x49\x00\x01\x84\xa7\x75\xd7\x82\xb8\xc2\xa5\x69\ -\x51\x25\x22\x8e\x20\x67\x3b\x95\xf8\x43\x0e\xbf\x3d\x05\x99\x42\ -\xea\x81\xdd\xcd\x87\xb4\x30\x1f\xef\x51\x1a\x68\x27\x7d\xaf\xc7\ -\x7e\x3d\x48\x47\x07\x4a\xc3\x0e\x73\xc7\x25\xea\xa2\x6f\xe4\x38\ -\xda\x81\x7d\x9a\x0f\xd1\x41\x7a\x31\x09\x65\x54\x15\x36\xd2\x00\ -\xca\x47\x90\x8c\x24\xf3\xfa\x23\x45\x03\x0e\xf2\x04\x46\x83\x32\ -\x89\x8d\x24\x80\x00\xc2\x1d\x49\xbd\x7d\xf0\x91\x86\x72\xc4\xf9\ -\x5b\xf0\xc6\x77\x09\xea\x35\x3b\xe0\x38\x6a\x46\x6e\xd8\x7d\x46\ -\x39\xe9\x1b\xba\xe3\x92\x0d\xcb\x51\x1a\x68\x8d\x06\xe4\x5e\x6c\ -\x07\xe6\xa2\x6f\x5e\x8c\x5b\x0d\x4e\xc3\xf2\xd1\x83\x06\xf8\xba\ -\x93\x1d\xc8\xbb\xf9\xd0\x16\xe6\xc3\x7b\xb1\xf0\xa9\x58\xe8\xb2\ -\x13\xc4\x48\x03\xec\x8a\xf9\x8f\xe8\xcb\x50\x88\x99\x3b\x47\x8b\ -\xa4\x74\x12\x7a\x48\x38\x22\x09\x20\x80\xf0\x45\x12\xd2\x99\x34\ -\xf0\x83\x55\xdd\xa1\x3b\x5c\xe0\x0b\xf3\x91\x0f\x56\x2d\x46\xa9\ -\x90\xd0\xd6\xd8\xc1\x36\x21\x61\x39\x10\xf2\x00\x52\x07\x69\x26\ -\xd2\xc2\x7c\x5c\x17\x63\x23\xd6\x06\x21\xe7\xa3\x0f\x38\x47\x55\ -\x0f\x23\x4f\xf3\x61\xae\x83\x3c\x82\x34\x1a\x74\xec\x16\x72\x1c\ -\xbd\xc6\xd1\x4f\x22\xa5\xb8\xc3\x39\xd0\x80\x2b\x8e\x38\x31\xa2\ -\x02\x20\x80\x70\x47\xd2\x29\xd8\xc1\xaa\xe0\x3b\x97\x41\x19\xa9\ -\x10\x71\x3d\x08\xd2\xad\x06\xc8\x87\x76\x22\xef\xe6\xcb\x43\xdf\ -\x71\xe9\x89\x65\x2a\x16\x75\x5b\x2c\xca\xa8\xea\x55\xc4\xfd\x89\ -\x38\x2f\x2f\x6f\x44\x5d\xbf\xe5\x42\x60\x4d\x03\x62\xc7\xe5\x32\ -\xe4\x7c\x84\x34\x85\x84\xd4\xb0\x03\x5d\x8d\xfd\x11\x7d\xec\x8e\ -\x88\xa5\x42\xf2\xa8\x91\x44\xdc\xf4\x04\x72\x1c\x61\xe6\x24\x80\ -\x00\xc2\x1d\x49\xf3\x73\xd0\xb7\x2e\x17\xa2\xc4\x91\x1a\x52\xe3\ -\x1b\x72\xd2\x37\xea\xba\x93\x3c\x9c\xeb\x4e\x50\xa7\xcb\x91\x77\ -\x5c\x22\x46\x1a\xa0\x0d\x3b\x8c\x91\x06\xd4\x0b\xe6\x51\x8f\xd2\ -\xc0\x72\xd2\xf7\xed\xb2\x9b\x28\xf9\x08\xb1\x7e\x6b\x12\x62\xd1\ -\x37\x2c\x8e\x50\x1a\xdf\xe0\x1b\x7d\x71\x45\x52\x30\xd1\x39\x29\ -\x3d\x9d\x84\xa1\x06\x50\x14\x71\x62\x89\x24\x80\x00\xc2\x33\x9f\ -\x04\x5f\x1a\x04\x3e\x4a\x43\xa5\x1d\xb1\xee\x04\x5c\x1f\xa9\xc1\ -\x1a\x76\xd0\xf3\xb7\x90\xd6\x6f\xa1\x9f\xbf\x85\x74\xb6\x13\xfa\ -\x11\x00\x07\x30\xee\x4f\x7c\x02\x1f\x55\xc5\x7a\x0f\xe9\x7d\xd4\ -\xc5\xaa\xa7\x9f\x62\x3f\x31\x5f\x18\xf3\x8c\x34\xd4\xc2\x6e\x37\ -\xfa\x1a\x3b\x58\x1c\xc1\x0b\xbb\x3d\xaf\x5e\xbd\x26\xa1\x33\x8b\ -\x37\x92\x40\x51\x44\x54\xab\x01\x6b\x24\x01\x04\x10\xee\x48\x62\ -\x85\xf7\x62\xa1\x53\x48\x85\xf0\xd6\x37\x78\x37\x1f\x64\xdd\xc9\ -\x05\xf8\x99\x34\xdb\x30\xd6\x06\x9d\x44\x9e\xe6\x23\x62\xc4\x0e\ -\xc7\x31\x0d\x97\x71\xec\xe6\x43\x99\x42\x42\x5e\x63\x77\x17\x29\ -\x1f\xc1\xa7\xcb\x21\xd3\x13\xb0\x61\xd5\xdd\x88\x69\x3e\x70\x3e\ -\xba\x0e\x6f\x34\xc0\xf3\x11\xe8\xfa\x72\x82\x91\xc4\x82\x77\xc4\ -\x01\xcf\xe6\x09\x92\x22\x09\x20\x80\x70\x47\x12\x1b\x74\xba\x3c\ -\xac\x12\xbe\x09\x09\xba\xe3\xb2\xe4\x3c\xfc\x28\x0d\xd0\x14\x12\ -\xec\x8c\x34\xe4\xd3\xd8\xf3\x50\xcf\x0d\xda\x8f\x67\x6d\x10\xe6\ -\xfa\xad\x33\x90\x5d\x48\x6d\x88\x86\x1d\xea\xfd\x89\x38\x6e\x35\ -\x80\xad\x0d\xba\x8b\xd2\xf8\x7e\x88\xdc\x41\x5a\x8b\x94\x8f\x3c\ -\xe0\x8d\x06\xe4\xd9\x09\x44\xa3\x01\x74\x35\x36\x29\x03\xac\xd8\ -\x3a\xb3\x44\xac\x0d\xc2\x88\x23\x2c\x91\x04\x10\x40\x78\x96\x74\ -\x21\xaf\x3b\x51\x99\x01\xb9\x66\x07\xb1\xe8\x7b\x79\xd6\x29\xa4\ -\x75\x90\xd0\x46\x83\x03\x96\xc3\x6f\xd1\xd6\x06\x65\x63\xbd\xc2\ -\xe5\x0a\xce\x2b\x5c\x50\x0f\xed\xbc\x8f\xb8\x18\x1b\xf9\xfc\x2d\ -\x94\x0d\x2e\xd8\x17\x14\x5f\xbb\xb1\x16\xb1\x7b\x02\x69\x3d\x31\ -\xb6\x0a\x09\x1a\x47\xf0\x48\x22\x72\xb9\x3e\x46\x24\x11\x3d\xcd\ -\x87\x14\x45\x9c\x9c\xff\x30\xa2\x02\x20\x80\x70\x47\xd2\x56\xf0\ -\x26\x24\xe8\x68\x50\x61\xe1\x0c\x77\x55\xd4\x69\x3e\xa4\xc6\x37\ -\x74\xa4\x01\x6d\x37\x1f\x2b\xca\x99\x34\x04\x4e\x63\x47\x6a\xd8\ -\x71\x61\xec\xe6\xdb\x8b\x6b\x41\x31\xe6\x05\xf3\x48\x6b\x83\x10\ -\x15\x12\xca\xc2\x7c\xe4\x0d\x2e\x67\x31\x46\x1a\x20\xf9\x08\x5c\ -\x21\x9d\x38\x71\xc2\x8e\xb2\x99\x59\x5c\x85\x5d\x26\xde\x48\xc2\ -\x5c\x2d\x04\x10\x40\xb8\x23\x69\x3d\xb6\xe9\x72\xf4\xc3\x6f\x7b\ -\x2a\xa6\x22\xee\x4f\x84\xc5\x11\xca\xe5\xe5\xd3\x50\x46\xbe\xd1\ -\xef\x4f\x44\x9d\xe6\xe3\xc2\x76\xb0\x2a\x96\xf3\xb7\x40\x23\x76\ -\x3e\xb0\x45\xdf\x0f\x70\x5d\xb3\x83\x74\x8e\x1d\x62\xc4\x4e\x02\ -\x79\xfd\xd6\x11\xa4\x7c\x84\xa8\x90\xc0\x71\xb4\x07\x7c\x33\xb6\ -\x1d\x49\x3b\xc8\xb0\x47\x12\xf1\x65\x1d\x34\x92\x30\xf7\x63\x02\ -\x04\x10\xde\x48\x02\x9d\xc6\x0e\x3d\x58\x75\x06\x7c\x41\x31\xe2\ -\xf0\x5b\xc8\xda\x20\xf8\x91\xf9\xc8\x87\xdf\xa2\xae\x0d\xf2\x44\ -\xb9\x66\x87\x1d\xf9\x60\x55\x0e\xf8\x0d\xf3\x68\xb7\x1a\xa0\xf7\ -\x62\x21\x17\xcc\x2f\x45\x3e\xee\x04\x71\xf8\x2d\x96\x75\x27\x88\ -\xb2\x4e\x14\xf5\x08\x80\x49\xf0\x46\x03\x64\xe8\x5b\x1a\x5b\x07\ -\xe9\x15\xe4\xfa\x72\x0a\x97\x74\xe1\xd8\x15\x8b\x37\x1f\x71\x72\ -\x62\x5e\xc0\x08\x10\x40\x78\xea\xa4\x26\xe8\x31\x0d\xc0\xc2\x0e\ -\x76\xf8\x6d\x09\x74\x0a\xe9\x20\x6c\x77\xb9\xaf\x2f\x2c\x8a\x50\ -\x46\xec\x16\x23\x1a\x76\xe0\x46\x03\x1b\xd6\x0b\x7d\x71\x4c\xc5\ -\x22\x9d\xc6\x7e\x19\x75\x6d\xd0\x16\xd8\xc8\xf7\x71\x1f\x1c\x27\ -\xe6\xc3\x1a\x76\xc2\x68\x57\xb8\x40\x2b\x24\xe4\xdd\x7c\xe8\xeb\ -\x4e\xb0\xc4\x11\x96\x48\x22\x69\x60\x88\xb8\x25\x0d\xa8\x51\xc4\ -\xc9\x89\xb9\xfd\x1c\x20\x80\xf0\xb4\xee\xe0\xd7\xec\x40\x77\x5c\ -\xaa\xf6\xd6\x9e\x2f\x01\x8f\x7c\x83\x2f\x2f\x87\x9d\x77\x32\x1f\ -\x7a\xf5\x04\xe6\x89\xf9\xd3\xf0\x5e\x0f\xb2\x15\xfb\xc5\xd8\xc8\ -\xe7\x6f\x81\x16\xab\x5e\xc4\xd8\x5e\x0e\x1b\xb1\x83\x9f\xc6\xee\ -\x82\x65\xc4\xae\x0c\xa5\x17\x7b\x0f\x69\xf3\xc4\x6e\x0f\xa4\x38\ -\xba\x8e\x34\x62\xb7\x12\xd6\x68\x80\x96\x75\x44\xe5\x24\x16\x02\ -\x91\x44\xc2\x48\x03\x0c\xdc\xc3\x88\x0a\x80\x00\xc2\x1d\x49\x79\ -\x95\xc8\xd7\xec\xa8\x42\xcf\xa4\x01\x6f\x42\xc2\x76\x4c\x03\xea\ -\xa1\x9d\x48\x67\x3b\xe1\xde\xcd\x87\xd9\x68\x38\x83\x72\x04\x00\ -\xea\xa1\x9d\xf7\xb7\x38\x21\xa6\x90\x90\xae\xd9\x81\x35\x1a\x84\ -\x90\x6f\x35\xb8\x5d\x86\xd2\xf8\x46\x3a\x4a\xe3\x11\xee\x38\x92\ -\x41\x6e\xd8\x81\x6f\xc6\x36\x13\xa0\xa8\x0d\x4e\x42\x3e\x42\xca\ -\x49\x8d\x18\x51\x01\x10\x40\xb8\x23\x69\x2a\xf2\x91\x34\xe7\x90\ -\xce\xb1\x5b\x8e\xda\xb0\xdb\x86\xb6\xc6\x0e\x79\xba\xbc\x14\xa3\ -\xd1\xc0\x8e\x7e\x8a\xf4\x15\xa4\x3b\x2e\xe1\x23\xdf\xce\xd8\x47\ -\x1a\x90\x3b\xb1\x38\x0e\xed\x44\x1e\xb1\x13\x45\x1f\xb1\x43\x9d\ -\x9e\x40\x5a\x1b\x84\x34\xf2\x0d\x8e\x24\x68\x46\x82\x47\x92\x00\ -\x79\x5b\x5f\xc8\x29\xec\x38\x39\xfd\x30\xa2\x02\x20\x80\x70\x47\ -\x12\xa8\xd1\x80\x58\x77\x02\xe9\xc5\x82\xeb\xa3\x05\xb0\x4b\x97\ -\xc1\xbb\x62\xe7\xa3\xe5\xa3\xbc\x3c\x1c\x17\x63\xf7\xe3\x18\x69\ -\xf0\x43\x3f\xb4\xb3\x03\x63\xc7\x25\xce\x2b\x5c\x50\x47\x55\x51\ -\x46\x83\xb0\x9e\x1b\x84\xd2\xb0\x43\x5e\xf4\x8d\x98\xe6\x43\x54\ -\x48\xa0\x9c\x04\xdd\xd8\x4c\xb0\x79\x87\x79\x1c\x0a\x08\x90\x51\ -\xd6\x01\x81\x26\x46\x54\x00\x04\x10\x9e\x23\x02\x7a\xc1\x39\x69\ -\x15\x74\x61\x7e\x2d\xea\xae\x58\xd8\x68\x50\x73\x31\x52\xa3\x01\ -\xf9\x60\x55\xf0\x4e\x31\xe4\xa3\x34\xb2\x91\xd6\xd8\xcd\x9a\x85\ -\x7e\x20\x24\xae\x23\x00\x90\xea\x23\x6c\x53\x48\x58\x4f\x63\x07\ -\x1d\x7e\x8b\x7c\xd2\xf7\x5a\x94\x23\x00\xd0\x46\xec\x60\xeb\x20\ -\x91\x46\x83\x60\x51\xc4\xc4\xe4\x18\x24\x40\xc2\xc0\x10\x46\x4e\ -\x22\x7a\x34\x08\x39\x8e\x38\x31\x8f\xca\x05\x08\x20\x3c\x91\x74\ -\x12\xbc\x79\xa2\x10\xbe\x58\xd5\x1f\x32\x62\x07\x3d\x93\x66\x1f\ -\x62\xdd\x09\x6c\x61\x3e\xea\x39\x76\xd0\x85\xf9\x6c\xd8\xa6\x62\ -\x67\xa1\xdf\x9f\x88\xa8\x90\x9c\xb1\x2c\xcc\xc7\xb9\x36\x08\xb9\ -\xb0\xbb\x8b\xd4\x68\x40\x3e\x31\x1f\x3e\x62\xb7\xec\xc5\x23\xcc\ -\x7c\x04\x6e\x7c\xc3\x2a\x24\x19\x70\xa3\xe1\x15\x3c\x8e\x98\xb0\ -\xec\x3e\xc7\x7b\x8c\x03\x5a\x34\x11\x3d\x17\x8b\x12\x49\x98\x31\ -\x01\x10\x40\xf8\xce\x16\x82\x2e\xcc\x07\x2f\x3b\xe9\xf5\x47\xbe\ -\x74\x19\x7e\x24\x4d\x31\xe6\xba\x13\x56\xdc\x97\x2e\x93\x79\x94\ -\x06\xce\xe9\x09\x7c\x67\x3b\x61\xec\xe6\x5b\x36\x09\x79\xb1\x2a\ -\x8e\x86\x1d\xa2\x65\xc7\x84\x23\x27\x11\x79\x46\x17\x34\x92\xfe\ -\x92\x1e\x47\x58\x4e\x44\x01\x08\x20\x3c\x91\xe4\x0d\x29\xec\xce\ -\x21\x1d\x49\x03\x9d\xe6\x03\x2d\xfa\xae\x40\x9b\x42\x82\x1f\xda\ -\xb9\x18\xfb\x88\x1d\xea\xc1\xaa\xa8\x3b\x2e\xb9\x08\x9c\xed\x84\ -\x7d\xdd\x09\x24\x1f\xb9\xa0\x1f\xda\x79\x1b\x65\x54\x15\xa5\x42\ -\x42\xee\xc4\xa2\xae\x0d\x5a\x89\xd6\xb0\x83\x46\x52\x10\xee\xc3\ -\x36\x08\x9c\xb6\x61\x84\x25\x8e\x88\xa9\x90\x38\x39\xdf\x62\xc6\ -\x04\x40\x00\xe1\x8d\xa4\x19\x33\xe0\x71\x84\x98\xe6\x03\xb7\xec\ -\x20\x6b\x55\xd1\x0f\xed\x3c\x89\xb2\x79\x02\xc7\x8e\x4b\xb4\x45\ -\xdf\xc8\xeb\xb7\xda\xb0\x35\xec\xd0\x77\xc5\x3e\xc5\x72\x15\x12\ -\xea\x69\xec\x28\xa3\x41\x88\x75\x27\xbb\xd1\x8e\x3b\xc1\x33\xd2\ -\x00\x89\x22\xd2\x22\x09\x33\x2b\x11\xb1\x0e\x12\x33\x8e\x38\x4f\ -\x60\xc6\x04\x40\x00\xe1\x2b\xee\xe0\x17\x63\x43\xa7\x90\xc0\xf5\ -\x51\x16\xd2\x81\x90\xdb\x50\x4e\xfa\x06\x75\x62\x4f\x62\x5d\xab\ -\x8a\xbc\x30\x7f\x03\xfa\xc2\xfc\x6e\xfc\x17\x63\x83\xaf\xd9\x59\ -\x8a\x79\x04\x00\xd6\x86\x9d\x30\xe6\xa8\xaa\x04\x62\xe4\xfb\x11\ -\xe6\xa8\xea\x31\xd4\xa9\xd8\x3d\x28\xf9\x88\xc9\x0e\xdb\x99\x84\ -\xb8\x5a\x0e\xe8\x67\x12\x42\x23\xe9\x0f\x69\x85\x1d\x27\x27\xe6\ -\x31\x0e\xff\x01\x02\x08\x5f\xc3\x01\xbe\x2b\x16\x3a\xf4\x0d\x3b\ -\x23\x0d\x76\xef\xdb\x7c\xcc\x3b\x2e\x91\xce\x48\x5b\xe7\x89\xbd\ -\xd1\x70\x00\x71\x6e\x10\xf2\xe6\x09\x94\x75\x27\xc8\xeb\x20\xd7\ -\xe0\x9c\x42\xc2\xb6\x30\xff\x66\xd9\x61\xd4\x53\xa4\x91\x8f\x69\ -\xf0\x40\x6f\xd8\xdd\x42\x99\x42\x42\x8b\x22\x94\x48\x12\xc0\x7d\ -\x28\x38\xf6\xa6\x83\xbc\x91\x3c\xe9\x0d\x3b\x10\xb8\x88\x19\x13\ -\x00\x01\x84\x27\x92\xa6\xcc\x38\x37\x43\x15\x79\x6d\x10\xac\xac\ -\xc3\xb8\x66\x87\x19\xfd\xba\x2a\xd0\x15\x2e\x58\x4e\x63\xdf\x00\ -\x69\x34\xac\xc7\x32\xcd\x07\x39\x93\x86\x1b\xf3\xd2\xe5\x35\x58\ -\xcf\x76\x42\x39\x8d\x1d\x65\x34\xe8\x21\xc6\xdd\x7c\xf0\x13\xf3\ -\x31\xd6\x06\xdd\x42\x5e\xac\x8a\x88\x24\x58\x1c\x25\x98\xe1\x8d\ -\x24\xfc\xfd\x59\x20\x20\x27\x1f\x61\xeb\x26\xfd\x07\x08\x20\x7c\ -\x47\x4e\x6b\x40\xa7\xcb\xfd\x51\x2e\xf4\x85\x5d\x29\x36\x1f\xf9\ -\x8e\xcb\xa3\xa8\xeb\xb7\xc0\x23\x0d\xd8\x1b\xdf\xa8\x57\xb8\x3c\ -\x41\x9a\x2e\xef\x68\x43\x1c\x7e\x8b\x38\x7f\x0b\x71\x04\xc0\x71\ -\xe4\x5d\xb1\xe8\x27\x7d\x23\xef\x42\x42\xba\xc2\x05\xcb\x19\x69\ -\xf8\x1a\x0d\xc8\x19\x29\x21\x01\x1c\x49\x41\x84\x0f\x33\xc6\xb5\ -\x4d\x89\xc8\xe9\x72\x34\x80\x25\x22\x00\x02\x08\x5f\x24\x4d\x46\ -\x39\xa6\x61\x39\xec\x4e\xb1\x0a\xc8\x61\x83\xcd\xa8\xe7\xd8\xe5\ -\xe1\x9a\x8a\xc5\x35\x5d\x7e\x05\xf9\x40\x48\xac\x27\xe6\x6f\xc4\ -\x68\xd8\x21\x0e\x56\x85\x36\x1a\x66\xa3\x8e\x34\xdc\x44\xaa\x90\ -\x90\xcf\x0d\x7a\x81\xb2\x79\x02\x79\x61\x3e\xa2\xf1\xfd\x11\xad\ -\xb0\x83\x45\x12\xd1\xe7\x7b\xc2\xf2\x12\x3c\x9a\xc8\xc9\x47\x58\ -\x23\x09\x20\x80\xf0\x45\xd2\x7a\xc8\xf4\x04\x38\x23\xa9\xc1\x4e\ -\xcc\x87\x36\xbe\x31\x1b\x76\x27\x91\xae\x42\x2a\xc5\xb6\x79\x82\ -\x1d\x7d\xc7\xe5\x4c\xe4\x5d\xb1\xf0\x38\xba\x8c\x7e\x94\x06\x72\ -\x85\x24\x88\x65\x0a\xe9\x2e\xea\x14\x12\xd2\x68\x90\x04\xda\xae\ -\x58\x48\x2f\xf6\xfa\x75\x8c\x05\xc5\x58\x1a\x76\xa0\x0b\xe6\x71\ -\x9c\x0b\x4e\xe4\xd9\xed\x46\x7f\xc9\xc8\x47\x58\x26\xcf\xff\xff\ -\x07\x08\x20\x7c\x91\xc4\x81\x58\xbf\x85\x72\x04\x00\x7c\xc4\x0e\ -\x79\x6d\x10\x72\x07\x09\xeb\x19\x69\xec\xe8\x57\x8a\xa1\x36\xec\ -\x3a\x50\xa6\xf9\xa0\x17\x63\x6f\xd9\x82\x79\xa7\x18\xfa\x8e\x4b\ -\xb4\x5d\xb1\x88\xc2\x0e\xcb\x39\x76\x47\x50\x16\x7d\xdf\x22\x10\ -\x47\x09\xc4\x1e\xde\x8e\xa3\xed\x40\x4e\x1c\x61\x99\xf2\xfb\xff\ -\x1f\x20\x80\xf0\x45\x12\x3b\x68\xa4\x01\x72\xa1\xef\x72\xb4\x3b\ -\x2e\x91\xaf\x14\x3b\x8a\x7e\x94\xc6\x7e\x6c\x23\x0d\xf0\x4e\xac\ -\x37\xd2\xae\x58\xc4\xc2\x7c\xcc\xcd\x13\xd0\xb9\x58\xf4\x11\x3b\ -\x41\xe8\xe1\xb7\x42\xe8\xbb\x90\xd0\xd7\xaa\x42\x1b\x76\x2f\xd0\ -\xa7\xf9\xae\x5f\xc7\xd2\xf8\xfe\x88\x2d\x8e\x12\x04\x82\xf0\x67\ -\x25\x7c\x17\xf3\x20\x67\x24\x12\x0a\x3b\xce\xd5\x58\x22\x02\x20\ -\x80\xf0\x45\xd2\x7c\xc4\xae\x58\xd8\x48\x03\x7c\x6d\x50\xa7\x03\ -\xae\x29\x24\x50\x7d\xb4\x1f\xeb\x54\x2c\xc6\xb9\x41\xc8\x67\xa4\ -\x61\x59\xab\xba\x06\xe7\x88\x9d\x0b\x96\xeb\x41\x6e\xdf\x3c\x8c\ -\xba\x53\x0c\x7e\x68\x27\xbc\x83\x74\x04\x6d\x34\x08\xbe\x79\x02\ -\xbd\x61\x07\x89\xa3\x84\x78\x52\xef\xaa\x40\x29\xf0\xc8\x8a\x23\ -\x4e\x0e\x2c\x11\x01\x10\x40\xf8\x22\xa9\xea\x54\x09\xf4\x8e\xcb\ -\x05\x88\x43\x3b\xd1\x17\xe6\x7f\x46\xad\x90\xa6\xa1\x35\xec\xfa\ -\xd1\x4e\x91\x46\x5a\x98\x8f\xb8\x61\x1e\x69\xc4\xce\x1e\x69\x54\ -\xf5\xfe\x16\xa7\xa5\x58\xee\xe6\xbb\x84\xe5\x0a\x17\xc8\xb9\x41\ -\x0f\xe1\x71\x84\x72\xa5\x18\xf2\xc8\x37\xd6\x85\xf9\xe8\x9d\x58\ -\x68\x24\xd9\xe1\xba\x2d\x2e\x98\xa8\x61\x07\x92\x7b\x48\x60\x50\ -\x84\x25\x22\x00\x02\x08\xff\xed\x98\x68\xd3\x7c\x3d\xf0\xc6\x37\ -\xc6\x1a\x3b\xdc\x0b\x8a\x31\xce\x48\x43\xdd\x71\x09\x5e\xf4\x8d\ -\x75\x61\x3e\xca\x85\xbe\xc8\xd3\xe5\x97\x30\x36\x4f\xa0\x5c\xb3\ -\x73\x0d\x65\x83\xcb\x0b\xf8\x88\x1d\x38\x1f\x3d\x43\x5e\x50\x8c\ -\xde\x41\x42\x89\xa3\xaf\x5f\xed\x48\xbf\x1b\x33\x03\xa9\xc0\x23\ -\x27\x1f\x61\x59\x19\x09\x04\x00\x01\x84\x37\x92\x38\x30\x16\xe6\ -\x83\x67\x27\xd0\x46\x1a\x4e\xa2\x9d\xc6\x8e\x75\x57\x2c\xb8\x87\ -\xb4\x1e\x1a\x47\x57\xc0\x9d\xd8\x27\xe8\xeb\xb7\xec\x51\xa6\x90\ -\xd6\x20\x8d\x7c\xfb\xc0\x0f\x56\x3d\x84\xbd\x61\x77\x1b\xf9\x56\ -\x83\x6b\x58\xa7\x62\x8f\xe0\x1e\x0d\xc2\x5a\x21\x7d\xfd\x6a\x86\ -\xf3\x06\xd3\x60\x02\x0d\x3c\x70\x3b\x9c\xf4\x46\x03\xd6\x45\x77\ -\x40\x00\x10\x40\x78\x23\xc9\x1b\x72\x40\x31\x68\xfd\x56\x96\x06\ -\xf2\x1d\x97\xa8\xeb\x4e\x16\xe3\xdb\x71\xf9\x18\xfb\x31\x0d\x48\ -\xa3\x41\x57\xdb\x50\xd6\x41\x22\x5f\x44\xea\x84\x31\xf2\xfd\xe1\ -\x12\xd6\x69\x3e\xec\xbb\xf9\xc0\x8b\xbe\x91\x0f\x84\x44\x1e\x55\ -\x45\x8c\x34\xe0\x88\x23\x1c\x91\x84\xbf\x81\x87\x94\x97\xc8\x28\ -\xeb\x38\x39\xa5\xb1\xc5\x03\x40\x00\xe1\x8d\xa4\x93\xd0\x35\x76\ -\x28\x67\x3b\xe1\xbe\x3f\x11\x69\x8d\x1d\xb6\x46\x03\x7c\x41\x31\ -\xca\xf9\x5b\x88\xcb\xcb\x2f\xd7\xd8\xd7\x5c\xc4\x72\x59\x2c\xd6\ -\x69\x3e\x8c\x8b\xb1\x91\x46\xbe\x6f\xac\xc5\x72\x04\x00\x78\xfd\ -\x16\x46\x3e\x02\x65\xa4\x57\xe8\xa3\x41\xd0\x38\xfa\xea\x68\x85\ -\xf3\xe6\x45\xc2\x2d\xbc\x0c\x23\x7c\x65\x1d\xce\x48\xaa\xc1\x16\ -\x0f\x00\x01\x84\x37\x92\xe4\x2a\x16\xa0\xec\x14\x83\xae\x83\x84\ -\x35\xec\x98\xd1\x47\xec\x70\x9d\x98\x0f\x8d\x24\xac\x53\xb1\xf0\ -\xd9\x09\xf4\x83\x55\x9d\xb0\xad\xdf\xba\x84\xfd\xde\x37\xc2\x57\ -\x21\x21\x37\x1a\x50\x46\x1a\x50\x1b\x0d\x27\x90\xe2\xe8\x7d\x90\ -\x15\xe1\x8b\x66\x71\xe7\x25\x72\x0a\x3b\x4e\xce\x15\xd8\xe2\x01\ -\x20\x80\xf0\x46\xd2\x7f\x76\xc4\xdd\x7c\x88\x38\xc2\xbb\xe8\x1b\ -\xfb\x54\xec\x01\x94\xeb\x41\x66\x3e\xe9\xc6\x3c\x02\xc0\x1e\xe3\ -\x08\x00\x27\xf8\xc2\xfc\xd3\x48\xd7\xec\x60\x3b\xb4\x13\xbe\x36\ -\x48\x14\xf5\xde\x37\xf4\x03\x21\xb1\x34\xbe\xf7\x20\x4f\xc5\x9e\ -\x30\xf9\x65\x69\xe2\x06\x8d\xa4\xf7\x31\x88\xbb\xcf\x83\x88\xb9\ -\xfb\x1c\x5b\x24\x91\x1a\x47\x58\xdb\x0d\xff\x01\x02\x08\x7f\x24\ -\xcd\x44\x5a\x77\x02\x59\x50\xbc\x0d\xf9\x0a\x97\x3c\xb4\xa9\xd8\ -\x75\xd8\x0f\xed\x44\x4c\x21\x41\x47\x55\xbb\xb1\x5c\xba\x8c\x32\ -\x1a\x84\x76\xd8\x20\x62\x9a\xef\x92\x0b\xae\xb3\x9d\x30\xee\xf3\ -\x05\x15\x76\x28\xeb\x89\x31\xd6\xd8\xa1\xaf\x3b\x39\x6b\xf9\xfb\ -\x07\x10\x58\xca\x80\xe3\xe8\xbd\x9d\x15\x3c\x27\xe1\x6b\xe0\xe1\ -\xea\x2c\xe1\x8e\x24\x4e\xdc\xe0\x0b\xd6\x68\x00\x08\x20\xfc\x91\ -\x94\x8d\x58\x07\x09\x3b\x58\x15\x57\xe3\x7b\x1a\xbe\x38\x5a\xef\ -\xbd\x1e\x79\xfd\x16\xe2\x48\x9a\x0e\x2c\xeb\xb7\xee\x83\xaf\x07\ -\x41\xac\x83\xc4\xb9\x53\x0c\x7d\x83\x8b\x28\xf2\x48\x03\xf2\x89\ -\xf9\x90\xf5\xc4\x98\x53\xb1\x28\x0d\x3b\x60\x36\xfa\x05\x8e\xa4\ -\x1f\xd6\x09\xa0\x38\x02\x45\x12\x7a\x2c\xe1\x8d\x24\x44\x2c\xa5\ -\x41\x23\x89\xd4\x7c\xc4\xc9\x29\x8c\x35\x1a\x00\x02\x08\x7f\x24\ -\x35\x2d\x80\xe6\xa3\x7d\xbe\x15\x88\xf3\x4e\x60\xf7\x27\xa2\xae\ -\x3b\x01\x6f\x5d\x66\xc3\x3c\x31\x1f\xe9\xf2\x72\x0e\xe4\x91\x6f\ -\xa4\x46\x03\xca\x51\x1a\xe0\xc2\x0e\xcb\xfa\xad\x07\x1f\x1e\x5c\ -\x72\xc1\xd6\xf8\x2e\x7b\xe8\x85\x92\x91\x24\xb0\x1e\x01\x80\x6d\ -\x61\xfe\x6b\x78\x3e\x3a\xc1\x74\xdd\x32\x1a\x16\x49\x0c\xd7\xc0\ -\x91\x64\x66\x85\x2b\x2b\xe1\x18\x0c\x47\x8d\x25\x52\x7b\x48\x60\ -\x70\x01\x6b\x34\x00\x04\x10\xfe\x48\xfa\x9f\x8d\x34\xaa\x8a\x7c\ -\x48\xda\x51\x2c\xd7\x83\xe0\x18\xb1\xdb\x80\x71\x94\x06\x17\x5a\ -\x61\x87\x79\x15\x12\x96\x93\xbe\x71\x4d\xc5\x96\xc1\xd6\xd8\x41\ -\xa6\x27\xd6\xe2\x58\xf4\x8d\xbc\x30\x1f\x75\x37\x1f\x38\x8a\x4e\ -\x98\x30\x22\xe2\x88\x21\x9a\x09\x18\x47\xef\x1d\xad\x90\x62\x09\ -\x4b\x24\x61\x89\x25\xe4\x68\x22\x27\x8e\x38\xb1\xc7\x02\x40\x00\ -\x11\x88\xa4\x6e\xf0\xa8\x2a\x7c\xa4\x01\xbe\xbb\x9c\x19\x6d\xc7\ -\xe5\xb4\x52\x7c\x27\xe6\xa3\x2e\x56\xe5\xc2\x76\x94\x46\x0d\x96\ -\xf3\xb7\x7c\x50\x76\xb8\x5c\xc2\x72\x20\x24\x30\x1b\xa1\x1e\xda\ -\x89\x79\x1a\x3b\xda\xa8\x2a\xfa\x62\x55\x78\x36\x8a\x46\x8a\x24\ -\x06\x5b\x60\x1c\x7d\x09\xc2\x12\x49\x04\xb2\x12\x0b\xe1\x38\xc2\ -\x17\x45\x9c\x2f\xb1\xc7\x02\x40\x00\x11\x88\xa4\xad\x59\x90\xbd\ -\x13\x88\xcd\x13\x0e\x58\xce\x48\x9b\x8c\x7b\xc7\x25\xf2\x54\x2c\ -\xea\x82\x62\x9c\x67\x3b\x6d\x41\x3e\x4a\x03\x75\x9a\x0f\xdb\x49\ -\xdf\x58\xcf\xdf\x42\x6a\x7c\x63\x9e\x91\x06\x5f\xf4\x0d\x2b\xec\ -\x80\xd9\x08\x35\x92\x18\xce\xbe\x7f\x1f\xa3\xab\xab\x6b\x85\xb7\ -\x81\x87\xad\x1d\x0e\x8f\xa6\x3f\x64\xc4\x11\xe7\x32\xec\xb1\x00\ -\x10\x40\x04\x22\x09\xb1\xee\x04\x7a\x85\x0b\x33\xd6\x23\x00\x4a\ -\x51\x17\x7d\x3f\xc6\x7a\xd2\x37\xf2\x11\x00\xd0\xb2\x0e\xe9\x34\ -\x76\x5e\xd8\x68\xd0\x52\xd4\x86\x1d\xf2\x89\xf9\x2e\xd8\xce\x48\ -\xc3\x72\xe9\xf2\x0b\x94\xc6\x37\x52\x0f\x09\xe5\x48\x1a\x58\xa3\ -\xe1\xba\x35\x38\x8e\x60\x91\x04\x8a\x23\x06\xe3\xaf\x5f\xec\x60\ -\x91\x84\x9a\x95\x04\xb0\x45\x12\x96\x7a\x89\x9c\x8c\xc4\xc9\x86\ -\x3d\x16\x00\x02\x88\x40\x24\xfd\x2f\x85\xad\x0d\x6a\x9e\x8f\xe3\ -\x40\x48\x56\xe4\xd1\x20\x36\xf4\x43\xa4\x51\xb6\x2e\x77\xa3\xed\ -\xe6\xc3\x76\x79\x39\x96\x51\x55\x41\xe8\x34\x1f\x96\x91\x06\x58\ -\xeb\x1b\xed\xba\xaa\x17\xa8\xe7\x6f\xa1\xec\x14\x03\x37\xbe\x65\ -\x10\x71\x64\x12\xc5\x08\x8b\x24\x78\x46\x62\x60\x30\xf9\x62\xa6\ -\x8b\x1a\x4b\x78\x16\xb3\x62\xcb\x4a\xe4\xc4\x11\x8e\x2a\xe9\x3f\ -\x40\x00\x11\x8a\xa4\x6e\xe4\x23\x00\x50\x1a\x76\x8b\x91\x7a\xb1\ -\xeb\x50\xae\x42\x02\xc6\xd1\x63\xe4\xd1\x20\x6f\xcc\x63\x1a\x50\ -\x77\xc5\xd6\x5c\xc4\x6c\x34\xf8\xe0\x3b\xb4\x13\x73\xba\x1c\x65\ -\xe7\xf2\x0b\xa4\x1d\x97\xc8\xb7\x1a\x20\x32\x12\x3c\x8e\xa4\xad\ -\x19\x19\x19\x31\x4a\x3b\x20\x70\x73\x84\xc7\x11\xee\x5a\x09\x4f\ -\x34\x91\x13\x47\x38\xaa\xa4\xff\x00\x01\x44\x28\x92\x36\x4c\x85\ -\x35\xbe\xb7\x21\x5d\xba\x9c\x87\x3e\x3d\x81\xe3\x8c\xb4\x59\xa8\ -\x17\x63\xe3\xba\xbc\x1c\xcb\x51\x1a\x3e\x3e\x38\xcf\x48\x43\x1d\ -\x55\x15\x45\x3a\xe9\x5b\x02\x7d\x8d\xdd\x59\x94\xcd\x13\xe8\x85\ -\x1d\x30\x8e\x0c\x92\x19\x11\x91\x84\x9c\x93\x18\x2c\x83\xa0\x39\ -\x09\xbd\x56\x22\x9c\x97\x58\xc8\xcd\x48\x12\x38\x22\x01\x20\x80\ -\x08\x45\x52\x93\x26\xec\x76\x10\x5c\x07\x42\xe2\xb8\x79\x02\x74\ -\xe9\x32\xfa\xc2\xfc\xee\x27\x58\xe2\x08\xe5\xfe\x44\xc4\x31\x0d\ -\x3e\xa7\x09\x9c\x1b\x54\x76\x1b\x65\x61\xfe\x3d\xd4\xb3\x9d\xb0\ -\x2c\xfa\x5e\x88\x36\xcd\xf7\x0a\x58\x1b\x45\x45\xc1\x22\x09\x98\ -\x91\x50\x72\x12\x43\x6c\xac\x2e\xbe\x5a\x09\x5f\x56\x62\x21\x2f\ -\x23\xe1\xe8\x25\xfd\xff\x0f\x10\x40\x84\x22\xe9\x3f\x07\xea\x06\ -\x17\x6c\xe7\x6f\xed\xf7\xdc\xef\x89\x73\x2a\xd6\x1b\x6d\xd9\x09\ -\x17\x8e\x1d\x97\xc8\x15\x12\x3f\x6a\x07\xe9\x03\xd6\x05\xc5\x65\ -\x88\xd1\x20\xe4\x85\xf9\xc8\x4b\x1a\xd0\x16\x7d\x2f\x5c\x89\x3a\ -\xd2\x60\x90\x1c\x05\x89\x24\x48\x95\x84\x1a\x49\x62\xf0\x48\xb2\ -\x22\xa7\xc0\x23\x27\x8e\x32\x71\xc5\x01\x40\x00\x11\x8c\xa4\x27\ -\x90\x0e\xd2\x36\xd4\x93\xbe\xb1\x1d\xac\x8a\x7e\xb6\x13\xca\xc1\ -\xaa\x28\x8d\x6f\x2c\xd7\x55\xa1\xde\x6a\x00\xee\x20\x9d\x86\xde\ -\x9f\x78\x09\xe5\xb2\x58\xa4\x86\x1d\xf2\x35\x3b\xf0\xf5\x5b\x93\ -\x60\x53\x48\xb0\x35\x76\xe8\xd3\x13\xf0\x23\x69\x52\xcd\x75\x80\ -\x20\x30\x30\x30\x22\x22\x42\x49\x49\xe9\xd3\xa7\x4f\xdf\xbe\xfd\ -\x0c\x09\x09\x51\x57\x57\x8f\x04\x82\x58\x70\x2c\x59\x21\x17\x78\ -\xf8\x63\x09\x39\x2b\xb1\x90\x13\x49\x4c\xb8\xe2\x00\x20\x80\x08\ -\x46\x12\x33\xda\x71\x27\xd0\xf5\x5b\xac\x58\xef\xe6\xc3\x9a\x8f\ -\xd0\x4f\xcc\x87\x5f\x8c\xcd\x8d\x3a\x1a\xb4\x05\x7a\x37\x1f\xf2\ -\xae\x58\xe4\x11\x3b\x21\xb4\x2b\xc5\x1e\x12\x5c\x63\x77\x16\xbe\ -\x7e\x0b\x6d\xf3\x04\x30\x92\x6c\xcc\x81\x00\x12\x49\x81\xe0\x48\ -\x52\xfa\xf4\xee\xdd\xbb\x6f\xdf\xc0\x91\x04\x8d\x25\x5d\x1c\x0d\ -\x3c\x22\xf2\x12\xe9\x51\xc4\xc9\xd9\x8d\x2b\x0e\x00\x02\x88\x60\ -\x24\xfd\x67\xc3\x7a\xe9\x32\x7c\xc7\x25\xd6\x38\x42\xbf\xe3\x72\ -\x26\xd2\x69\xec\x57\x91\x2e\x22\xc5\xb8\x9b\x8f\x1f\xe9\xb0\x41\ -\xfc\x0d\xbb\x32\xb4\x6b\x76\x60\x3b\xc5\x76\xa3\x1e\x36\x88\x92\ -\x8f\x60\x19\xe9\xd5\x2b\xb7\xd4\x14\x73\x48\x2c\x21\x45\x12\x38\ -\x96\xd0\xb3\x92\x2e\xee\xa6\x03\x46\xe3\x21\x18\x5f\x46\x22\x1c\ -\x49\x38\xa3\x00\x20\x80\x08\x47\x12\x57\x31\xd2\x8e\x4b\x8c\x73\ -\xec\xd6\xed\x47\x5b\x1b\x84\x74\xdc\xc9\x7a\xac\x23\x0d\x48\x6b\ -\xec\x90\x76\x2e\x6f\x5c\x03\xca\x47\x4e\x18\xbb\x90\x70\x36\xbe\ -\xd1\x4e\xcc\xc7\x76\x94\xc6\x33\x44\x61\x87\x76\x04\x80\x8d\x79\ -\x4a\x4a\x0a\xf6\xac\xf4\x13\x9e\x95\x10\x91\x84\xbf\x85\x87\x2d\ -\x9a\xc8\x88\x22\x9c\x0d\xf0\xff\xff\x01\x02\x88\x70\x24\x65\xa3\ -\x6f\x2f\x47\x69\xd8\x95\xe2\xda\x71\xb9\x1e\x65\xc4\xee\x49\x37\ -\x7c\xe4\xfb\x2a\xd6\xc6\xf7\x9a\x35\x48\xad\x6f\x2c\x53\xb1\x98\ -\x9b\x27\x30\x76\xf3\x61\x1e\x77\x72\x1d\x65\xfd\x16\xf4\xdc\x20\ -\x60\x59\x97\x1a\x1e\x1e\x0e\x8f\xa5\x40\xa4\x5a\x09\x52\xde\x85\ -\xa0\x94\x77\x56\xd8\xca\x3b\xd4\xbc\x84\x5c\xe2\x05\x63\x8d\x24\ -\x22\xe2\x88\xf3\x01\xce\x28\x00\x08\x20\xc2\x91\x34\x67\x1a\xca\ -\x42\x48\xd4\x33\xd2\xd6\x21\x9f\x1b\x84\x18\x69\x40\x1d\x0d\x9a\ -\x49\xe0\x8c\x34\xd4\x11\x3b\xe4\xd1\xa0\x43\xd8\x6f\x35\x40\x3e\ -\xdb\x09\xe5\x98\x86\x49\x8f\x50\xd7\x9d\xc0\x47\x55\x17\xc2\xa6\ -\x62\x41\xa3\xaa\x6e\xe1\xf0\x48\x42\x69\x3a\x28\x81\x23\x09\xa5\ -\x56\x8a\x45\x8f\x25\xb4\xe1\x70\xcc\x58\x22\x3f\x92\x9e\xe3\x8c\ -\x02\x80\x00\x22\x1c\x49\xff\xcf\xe0\x5a\xbf\x45\xd4\x31\x0d\x90\ -\x11\x3b\x6c\x97\x97\xd7\xa0\xde\xfb\x06\x8b\xa4\x46\xd4\x2b\x5c\ -\xb0\x1c\x08\x09\x8a\x23\xd8\xe5\xe5\x78\x4e\x63\x47\xca\x47\x0b\ -\x51\x1a\x0d\xa9\xe2\xe1\xe0\x68\x42\x94\x77\xd0\x48\x42\x8e\xa5\ -\x48\x22\xf3\x52\x28\xb6\xbc\x44\x4e\x1c\xc9\xe2\x8e\x01\x80\x00\ -\x22\x22\x92\x1e\x63\x3f\x58\x15\xbc\xee\x04\x79\x61\x3e\x7c\x0a\ -\x69\x03\xfa\x31\x0d\xdd\x38\xce\xdf\xc2\xb8\x87\x14\xb5\xd1\x80\ -\x6b\xf3\xc4\x4d\x94\x0e\xd2\x8d\xb5\xe8\xd7\x55\xa1\x8f\xd8\xa1\ -\x1d\x08\xe9\x26\x2e\x2e\x8e\x2b\x2b\x7d\x02\x46\xd2\xcf\x9f\xa8\ -\x39\x09\xb5\x81\x47\x38\x96\x30\x22\x89\x93\x28\x30\x1b\x77\x0c\ -\x00\x04\x10\x11\x91\x34\x67\x3f\xd2\x82\xe2\xc5\x44\xdc\x71\x89\ -\x32\x3d\x31\x13\x47\x1c\xd5\xa0\x34\x1a\x90\x3a\xb1\x38\xf2\x11\ -\xd6\x93\xbe\x51\x2f\x5d\x46\x3d\xa6\xe1\x99\xf4\x75\x8c\xab\x27\ -\xc0\x8d\x6f\x03\x48\x24\x21\x62\x09\x12\x49\xf0\xb6\xc3\xcf\x9f\ -\xe8\xcd\x70\xe8\x10\x5e\x3c\x71\xd5\x12\x30\x96\xc8\xc8\x48\x58\ -\xd7\x17\x43\x01\x40\x00\x11\x11\x49\xff\xb7\x23\xaf\x69\x60\x5d\ -\x8c\x73\xa7\x18\xc6\xa8\x2a\x87\x1f\xda\xf9\x5b\x1d\xce\xd8\x47\ -\x1a\x96\x3a\x21\x9d\xf4\x8d\xb4\xe3\xd2\x05\xeb\xa2\xef\xc3\x28\ -\xa3\x41\xf7\xb0\x1c\x77\xb2\x09\xe9\xd0\xce\x85\x48\xbd\x58\xf0\ -\x68\x10\x2c\x92\xb0\xc5\x12\xf6\xb6\x03\x51\x8d\x07\xe4\x78\x22\ -\x23\x8e\xf0\x94\x76\xff\x01\x02\x88\x98\x48\x5a\x87\xbc\x7e\x0b\ -\x69\x21\x24\xb4\x83\x84\xa7\x17\x8b\x7a\xa5\x18\xd2\xcd\x13\xa8\ -\x97\x2e\x2f\x5d\x8a\x2d\x8e\x70\x4c\xf3\xc1\xe7\xcb\xa1\x3b\x2e\ -\xd7\xc2\x2e\x2f\xc7\xb3\xe3\x12\x65\x0a\xc9\x2d\x1c\x9e\x95\x50\ -\x5a\xe1\xf0\xb6\xc3\x4f\x8c\x66\x38\x81\x6a\x09\xbd\xc8\x23\xa3\ -\xb0\xe3\x3c\x8e\x27\x02\x00\x02\x88\x98\x48\xfa\xff\x18\xed\x4c\ -\x1a\xf4\x6b\x76\xd0\x8f\xd2\xf0\x46\x3e\x7f\x0b\xbe\x79\x02\x79\ -\x0a\x09\xdb\xb9\x41\x68\x97\x97\xe3\x3c\x7f\x0b\x75\x34\x08\x31\ -\xf4\x8d\x98\x8a\x45\xbe\x2c\x76\x21\xe6\xf6\x72\x1b\x71\x2c\xb1\ -\x14\x81\xdc\xc2\x43\x89\xa5\x58\x9c\x9d\x25\xf4\x36\x5e\x28\x05\ -\x91\x84\x2f\xfc\x01\x02\x88\xa8\x48\xda\x0e\x59\xab\x9a\x87\xe3\ -\x6e\x3e\xd4\x86\xdd\x2c\xa4\x7b\xdf\xe0\xa3\xaa\x5c\xa0\x75\x90\ -\x58\x76\x8a\x41\x17\xe6\xf3\x63\x3d\xa6\x01\xcb\x89\xf9\x90\x86\ -\x1d\x62\x57\x2c\xc6\x51\x1a\x52\x90\x3b\x2e\x9f\x61\x5d\xbf\x05\ -\x5b\xd3\x10\x81\xbf\x56\x82\x47\x92\x3a\x8e\xc6\x83\x00\xae\x58\ -\xc2\x12\x49\x44\xc6\xd1\x17\x7c\xe1\x0f\x10\x40\x44\x45\x92\x26\ -\xc6\xd9\x4e\x68\xa3\x41\x88\x91\x06\xd4\x86\x5d\x37\x72\x85\x74\ -\x15\xcb\xda\x20\xe8\xc1\xaa\x4e\xf0\xed\xe5\x58\xa7\x62\xd1\xca\ -\x3a\xc4\xd9\x4e\x88\x85\x90\xa8\x47\x69\x3c\xc3\x3c\x7f\x0b\x79\ -\x41\xb1\x9b\x38\x81\x06\x1e\x34\x96\x22\x71\x55\x4b\x58\xea\x25\ -\xe4\xcc\x44\x7a\x1c\x71\xb2\xe3\x0b\x7f\x80\x00\x22\x2a\x92\xfe\ -\x73\xa1\x9d\xf4\x8d\x73\x0a\x69\x2b\x46\xe3\x1b\xcb\x35\x3b\xc8\ -\xbb\xf9\xb0\x37\x1a\x50\x0e\x28\x9e\x8d\x34\x3b\x81\xe3\x42\xdf\ -\x49\x58\xcf\x0d\xba\x85\x72\xd8\x20\xf2\xa2\x6f\x1b\x3c\xe5\x9d\ -\xd2\x27\x48\x5f\x09\x7b\x67\x09\x16\x4b\x41\xb8\x4a\xbc\x50\xb2\ -\x32\x52\x26\xde\xe0\x07\x08\x20\xe2\x22\x69\x6b\x1e\xea\x69\xec\ -\xeb\x90\x86\xec\xb0\x6f\x9e\xe0\x00\x6f\x9e\x40\xdc\x71\xb9\x1d\ -\x65\xba\x1c\x79\x7a\x02\xf5\x56\x83\xa7\x58\x2e\x5d\xbe\x8b\x79\ -\x42\x31\xca\x3a\xc8\x65\x48\x27\xe6\x9f\xc5\xba\xe3\x12\xfd\x98\ -\x86\x13\xe6\x68\xe5\x1d\x8e\x1e\x2d\x9e\x58\x12\xc0\xdd\xc8\x23\ -\x3d\x1f\x61\x3b\xab\x06\x09\x00\x04\x10\x71\x91\x54\x95\x9d\x87\ -\xeb\xd0\x4e\xe4\x75\x27\xc8\xe7\x06\xf9\xe1\x18\x0d\x42\xdc\x29\ -\x06\x3d\xa0\x18\x73\x61\x3e\x62\x34\x08\x6d\x57\x2c\x72\x3e\x42\ -\xba\xe3\x12\x63\x61\xfe\x75\xf8\xfa\x2d\x1c\x9b\x27\x98\xcc\xf0\ -\x37\xf0\xde\xa1\xd5\x4a\x18\xb3\xb4\xc8\x91\x84\x11\x4b\xa4\xc7\ -\x11\xbe\x4e\x12\x10\x00\x04\x10\x71\x91\xf4\xdf\x19\xe5\xd2\xe5\ -\x52\xf4\x69\xbe\xc7\x98\x97\x97\x23\x0e\x56\xe5\x42\xbd\x3f\xd1\ -\x1e\xcb\x89\xf9\xc7\x91\x17\x14\x3f\xc0\x38\xdb\x09\x3e\xd2\x70\ -\xf8\x21\xe6\x34\xdf\x0b\xd4\xfb\x13\xd1\x76\xc5\xe2\x3a\x02\x20\ -\x15\x56\x2b\xa1\xcc\x58\x20\xe6\x2c\xd0\xb2\x12\x96\xbc\x84\xa5\ -\xf5\xa0\x8d\x1a\x47\xc4\x47\xd2\x1d\xfc\xa1\x0f\x10\x40\x44\x46\ -\x92\x26\xce\xc6\xf7\x63\xe4\xf3\xb7\xd6\x7b\xe3\x98\xe6\xeb\x80\ -\xed\x14\x43\x19\x0d\x5a\xb3\x14\xeb\x31\x0d\xb8\xd7\x06\x21\x9f\ -\xed\x84\xbc\xc1\x05\x69\xe8\xfb\x3a\x96\x13\x8a\xb1\xec\xb8\x3c\ -\x91\x02\xcd\x4a\xe6\xe6\x58\x9b\xe1\x18\x79\x09\xb3\xf1\x80\xa5\ -\x25\x0e\x8e\x26\x32\x32\x12\x3b\xfe\xd0\x07\x08\x20\x22\x23\xe9\ -\xff\x19\x82\x77\xf3\x81\x1b\x76\xeb\x91\xd7\x9d\x70\xc1\x77\xc5\ -\xa2\xac\x69\x40\x6a\x7c\x6f\xc1\xb2\x7e\x0b\xb4\x0b\x09\xd1\x68\ -\x40\xae\x8f\x90\x17\x7d\xa3\xed\x14\x93\xc2\xb2\x9b\x0f\xed\x14\ -\x69\xb4\xdd\x7c\x66\xf0\x61\x07\xd4\x79\x25\xe8\x4c\x3a\x4a\x67\ -\x09\xbd\x4f\x1b\x8f\xb3\xc4\x43\x8e\x24\xe2\xe3\x28\x93\x40\xe0\ -\x03\x04\x10\xb1\x91\xc4\x01\x6f\xd8\xad\x43\x6e\xd8\xc1\xf2\x11\ -\xf4\x14\x69\xd4\x5d\xb1\x98\x77\x5c\x5e\x46\xdb\xb9\x8c\xe5\x8c\ -\x34\xdc\x47\x00\x1c\xc6\xb6\x36\x08\x69\xc7\x25\xf2\x21\xd2\x68\ -\xa3\xaa\xaf\xb0\x6c\x8b\x4d\x45\x6e\x3b\xa0\x0f\x0e\x7d\x42\x9f\ -\x49\x87\x44\x12\x6c\x89\x17\xee\xd6\x83\x36\x39\x19\x49\x82\x40\ -\xe0\x03\x04\x10\xb1\x91\x54\xc5\x8e\xde\x68\x60\xc3\x75\xfe\x16\ -\x46\xa3\xa1\x03\xf9\x98\x06\xcc\x73\x83\x8e\x43\xa7\xf9\x30\x77\ -\xc5\x22\xef\xe6\xbb\xf9\x10\xf5\x34\x76\x78\xe3\x1b\xe5\xd2\x65\ -\xcc\xf3\x4e\xd0\x1a\xdf\x88\x5d\xb1\xaf\xc4\xd1\x62\x29\x10\xb9\ -\x85\xf7\x09\x32\x93\x8e\x1e\x4d\x56\x56\xb8\x5a\x0f\xf0\x78\x22\ -\x39\x8a\x08\x35\x1b\xfe\xff\x07\x08\x20\x62\x23\xe9\x7f\x1b\xb4\ -\xb0\xdb\x8f\xbd\x61\x87\x58\x4f\x8c\x31\xcd\xd7\xd6\xe6\x8c\xed\ -\xa4\xef\x35\x28\xbb\xf9\x10\x23\x76\x58\x0e\xbf\x45\x19\x69\x40\ -\x5b\xbf\x85\x72\xf8\x2d\xf2\x69\xec\x2b\x31\x0f\x56\x45\x3d\xef\ -\x04\xde\xa1\xc5\xd6\xc2\x03\x97\x77\xa8\x0b\x1e\x50\x62\x09\x77\ -\xc5\x44\x7a\x24\x25\x10\x0a\x7b\x80\x00\x22\x3a\x92\xda\xd9\x50\ -\x4e\x63\x67\xc3\xdc\x71\x89\x18\x55\x45\x9b\xe6\x6b\x43\x99\x42\ -\x42\x3a\x93\x06\xb6\x09\xc9\x07\x71\xb0\x2a\xca\x62\xd5\xbb\x58\ -\x4f\x63\x47\x3e\x23\x0d\xa9\xf1\xbd\x09\xb1\xee\x04\xbc\xc6\x0e\ -\xbe\xa0\xf8\xe3\x1e\x6c\xc7\x9d\x7c\xfd\x8a\x1a\x4b\x3a\x68\x6d\ -\x07\xb4\x31\x3c\x22\x62\x49\x00\x39\x92\x48\xc9\x48\xf3\x09\x85\ -\x3d\x40\x00\x11\x1d\x49\xff\x2f\x23\x9f\xed\x84\xb2\xc1\x85\x1d\ -\x7a\x92\x06\xf6\xd1\x20\x68\x2f\x96\xdb\x1e\xe9\xdc\xa0\xfb\xf7\ -\xd7\xe0\xb8\xae\x0a\xeb\xfd\x89\x28\xa3\x41\x18\x0b\x8a\x71\xed\ -\x42\x42\xaa\x90\xb0\x1f\x01\x80\x32\x38\x84\x94\x95\x90\x86\x87\ -\x20\x25\x5e\x24\x72\x2c\xa1\xce\x2e\x61\x14\x79\xa4\xc7\x91\x2c\ -\xc1\xa0\x07\x08\x20\xe2\x23\x89\x19\x7d\xc4\xee\x31\xfa\x2e\x24\ -\xcc\xfb\x13\x71\x5c\x85\x84\x31\x15\x0b\x6f\xd8\x3d\x68\xc0\x18\ -\xb1\x43\x3b\x8d\x1d\x47\x1c\x1d\x41\x5a\x1b\x84\x6d\xe4\x1b\xf3\ -\x28\x8d\xaf\xef\xdf\x8b\x63\x1f\x77\x40\x8e\x24\xb4\xbc\x14\x8b\ -\x25\x96\x50\x32\x93\x00\xe9\x91\xc4\x41\x30\xe8\x01\x02\x88\xf8\ -\x48\xfa\xcf\x5d\x5a\x8a\xe3\x4c\x1a\xd4\x38\xf2\x43\x5e\x98\xdf\ -\x86\x7d\x61\x3e\xe2\x0a\x17\xa4\xb5\x41\xa8\x71\x84\xf5\x34\x76\ -\x94\xb3\x9d\x90\xa7\xcb\xcf\xe2\xc8\x47\x7b\x30\x0f\xed\x84\x1d\ -\x01\x00\x8e\x24\xbc\x05\x1e\xa2\x5a\xc2\x68\xe3\xc5\x63\x2f\xf1\ -\xc8\x88\xa3\x4c\xc2\x21\x0f\x10\x40\x24\x44\x12\xce\xab\x90\x36\ -\x60\xbd\x0a\x09\xe5\xd0\x4e\x6e\xc4\x48\x03\xf2\xfd\x89\xb0\xdd\ -\x7c\xd8\x1a\x0d\x3b\x50\x0f\x1b\x14\x45\x3f\xb4\x73\x19\xd6\xbb\ -\xf9\x8e\xa1\x2c\xfa\xc6\x71\xd8\x20\x24\x1f\xbd\x47\xca\x4a\xb0\ -\x1e\xad\x0e\x5a\x0b\x0f\x6b\x13\x0f\x5f\x5e\x22\x39\x8e\x38\xf7\ -\x12\x0e\x79\x80\x00\x22\x21\x92\xfe\x73\x63\x3d\x02\x60\x2b\xda\ -\x65\xb1\xa8\x15\x92\x33\xd6\x53\xa4\x97\x62\xbb\xd5\xe0\x10\xf6\ -\x2b\x5c\x10\x0d\x3b\x51\x94\xc6\xf7\x0b\xb4\xc6\xf7\x33\x2c\x6b\ -\xec\x70\x14\x76\xd0\x8c\xf4\xfe\xcb\x17\x71\x2c\x9d\x25\xac\x23\ -\xad\x28\x23\x0f\x90\xd6\x83\x15\xb6\x58\x22\x39\x8e\x88\xc8\x48\ -\xff\x01\x02\x88\x94\x48\x7a\x8c\x76\x88\xf4\x63\xf8\x75\x55\xc8\ -\xc7\x9d\xa0\xed\x8a\x75\x86\x9f\xbf\x85\x28\xec\xb0\xdf\x6a\x80\ -\xde\xb0\x43\x1c\xa5\x81\x74\x85\x0b\xd2\xb0\xea\x6e\xc4\x7a\xe2\ -\xb3\xc8\x47\x00\xa0\x8e\x06\x61\x8f\xa3\xf7\x90\x38\xfa\x02\x9f\ -\xa3\x35\xc7\xda\x76\x40\x9a\xb5\x40\xad\x96\x30\x66\x2e\x82\xe0\ -\x71\xc4\x49\x5a\x24\x3d\x20\x22\xe0\x01\x02\x88\x94\x48\xfa\xcf\ -\x8d\xe5\x62\x6c\x8c\x45\xdf\xd8\xee\x7d\xbb\x8c\xb5\x83\xc4\x8f\ -\x98\x8a\x45\x3e\xb4\x13\xfd\xc4\xfc\x87\x88\xf3\x4e\x90\xf3\xd1\ -\x24\x94\x85\xf9\xd7\x31\xa7\x90\x90\xe2\xe8\x04\x66\xa3\x01\x1c\ -\x47\x58\xb2\x12\xd6\x76\x38\x7c\x14\x2f\x12\x35\x2f\x59\xa1\xc7\ -\x12\x4d\x32\xd2\x7f\x80\x00\x22\x29\x92\xb2\xf1\x9e\xbf\xc5\x81\ -\x3e\xcd\x87\x16\x47\xc8\x57\xb8\x60\x2e\x56\xc5\xd5\x68\xb8\x79\ -\x18\x5b\x85\x04\x59\x07\xe9\x81\x7e\x1a\x3b\xca\x54\x2c\x8e\x91\ -\x86\x04\xe4\x38\xfa\x22\x4e\xb0\x5a\xfa\x86\xd2\x5d\xc2\x18\x22\ -\x42\x6d\x3e\x90\x1a\x47\x78\xd7\x9f\xc0\x01\x40\x00\x91\x14\x49\ -\xff\x2f\xe3\x39\x8d\x1d\xdf\x48\x03\x52\xe3\x7b\x0b\xd6\xf3\xb7\ -\x1e\x20\x4f\xf3\x61\xbd\x88\x14\xed\xea\x09\xf4\xd1\x20\xcc\xf3\ -\xb7\x5e\xe3\x3c\x6c\x10\x11\x47\x3c\x5f\x78\xc4\x51\x06\x5a\x31\ -\xaa\x25\xc8\x7e\x98\x9f\xa8\xf3\x16\x48\x99\x09\xa5\xc8\x0b\x22\ -\x35\x8a\x88\xcb\x48\xff\x01\x02\x88\xb4\x48\x62\xc5\x76\x1a\x3b\ -\xea\xa2\x6f\x2e\xbc\xa3\x41\xc0\xc6\xf7\x52\xd4\xcd\x13\x82\x84\ -\xa6\x90\xb0\x1c\x08\x09\xda\x5e\x8e\x7a\x7f\xe2\x75\xa4\xcd\x13\ -\xf8\x4f\x28\xfe\x8a\xa8\x90\x80\x91\xc4\xc3\x23\x8e\x5a\xe0\x41\ -\xc7\xf0\xd0\xba\x4b\x3f\x43\xb0\xc5\x12\x66\x34\x91\x1c\x49\x7b\ -\x89\x0a\x76\x80\x00\x22\x2d\x92\xfe\xef\xc5\xb3\xe3\x12\x65\xa4\ -\x61\x7b\x47\x07\xd6\x2b\x5c\x80\x8d\x06\x8c\xc3\x6f\x05\x31\x2f\ -\xf4\x15\xc6\xdc\x5e\x8e\xd8\x3c\x81\x58\xf4\x8d\x9a\x8f\x8e\x2d\ -\x24\xdc\xb0\x43\x89\x23\x50\x24\xa1\xc7\x92\x0e\xfa\x8a\x07\xd8\ -\xe4\x12\xbe\x58\x82\x0d\x8b\x93\x1a\x49\x7f\x88\x0b\x75\x80\x00\ -\x22\x31\x92\xf2\x1e\x23\x0e\xbf\x85\x1f\x64\x87\x3e\x15\x0b\x5a\ -\x08\x89\xb4\x7e\x0b\x3c\xf2\x7d\x11\x71\xc7\x25\xe6\x31\x0d\x48\ -\x17\x91\xa2\x5c\x8c\x8d\xfd\x08\x80\x65\xc8\xeb\xb7\x50\x0e\x1b\ -\x5c\x88\x6d\x6d\x10\x8e\xc6\x37\x34\x8a\x20\x91\x84\xaf\x1d\x0e\ -\x29\xf0\x50\x1b\x79\x91\x18\x79\x09\x1c\x4b\xa4\xe6\x23\xdc\x7b\ -\xfb\x50\x01\x40\x00\x91\x18\x49\xff\x2f\x66\xa3\x5d\xb3\x83\x6d\ -\x34\xe8\xcc\x55\xf4\xd1\x20\x8c\x91\x6f\xb4\xb5\x41\x58\x2e\x5d\ -\x86\x36\xbe\x31\x46\x55\x97\x61\x5d\xd2\x00\x3f\xdb\x69\x25\xbe\ -\x11\x3b\xd4\x46\x03\x0f\x22\x96\x50\x0b\x3c\x94\x86\x38\x6c\x15\ -\x3f\x7a\xeb\x01\xad\xc7\x04\x8a\x26\x52\xe3\x88\xc8\x8c\xf4\x1f\ -\x20\x80\x48\x8d\x24\x66\x94\xe9\x72\x94\xab\x27\x50\x2f\xf4\x6d\ -\x43\x1e\x0d\x82\x9c\x49\x83\xbc\xc6\x0e\xe9\xc4\x7c\xc4\xf6\xf2\ -\xd9\xa8\x67\xd2\x20\x3a\x48\xd7\x90\x3b\x48\x28\x53\x48\xcf\xb0\ -\xdc\x9f\x88\x7a\x57\x2c\xce\x46\x03\x14\xdc\x81\x67\xa5\x70\x9c\ -\xb1\xf4\x0d\xb2\xf6\x18\xb5\x25\x8e\x9c\x99\xc0\xd1\x44\x72\x46\ -\xda\x4a\x64\xa0\x03\x04\x10\xa9\x91\xf4\x9f\x17\x96\x91\xb6\x22\ -\x5f\x3d\x81\xb6\x79\xe2\x2a\xd6\xe9\xf2\xfb\x88\x0e\xd2\x71\xe4\ -\x11\x3b\xdc\x9b\x27\x90\xee\x14\x5b\x8b\x38\x10\x72\x37\xca\xae\ -\x58\xa4\x0a\x09\xde\x68\xc0\x5e\xd6\x7d\xc5\x96\x8f\x78\xee\x40\ -\x62\x49\x1c\xa3\x1d\x8e\x5e\x2f\x85\x7c\x43\x8a\x25\x8c\x8a\x09\ -\x18\x4d\xa4\xc6\xd1\x4b\x62\xc3\x1c\x20\x80\x48\x8e\x24\x15\x3f\ -\x2c\xd3\x13\xb8\xa6\x62\x91\x8f\xd2\x40\xec\x5c\x86\x1d\x08\x09\ -\xcf\x47\x97\xb0\xed\xe6\x43\x59\xf4\x8d\xe5\x6c\xa7\x23\x28\xeb\ -\xb7\x90\x47\x55\xf1\x0f\x7d\xa3\x67\xa4\x3b\x90\x58\x42\xce\x4a\ -\x48\x4d\x3c\x25\x25\xa4\x11\x22\x58\x89\xa7\x0e\xad\x97\x60\xd1\ -\x04\x89\x27\x52\xe3\x08\xe7\xd9\x1a\x18\x00\x20\x80\x48\x8e\xa4\ -\xff\x6d\xd8\x2e\x5d\x46\x69\xd8\xb5\x61\xbf\xe3\x12\x69\x21\x24\ -\x78\x37\x1f\xb6\x6b\x76\xb0\x9c\x49\x83\x34\xf2\x8d\x71\xe9\x32\ -\xfc\x2a\x24\x8c\xdd\x7c\xaf\x48\x88\xa3\x3b\xc8\x6d\x87\x14\xec\ -\x79\xe9\xdd\x3b\xc4\x9e\x18\x8c\x31\x22\x48\x2c\x91\x1a\x47\xc7\ -\x88\x0e\x72\x80\x00\x22\x3d\x92\x5a\xb9\xd1\x47\xec\x30\x76\xf3\ -\xb5\xe1\xd8\xcd\xb7\x14\xfd\xba\x2a\x58\xc3\x0e\x5b\x1c\xc1\x77\ -\xf3\xa1\x8e\xd8\x4d\x42\xd9\x15\x8b\x7c\x4c\xc3\x1b\x1c\x71\x94\ -\x90\x80\xbb\xd1\x00\x8e\xa3\xb8\x38\x5c\xd5\x12\x72\x89\x07\x5d\ -\x40\x84\x56\xe4\xe9\x22\xa2\x89\xd4\x48\x7a\x4e\x74\x90\x03\x04\ -\x10\xe9\x91\xf4\x3f\x1b\x72\x46\xda\x7a\x94\x3b\xc5\x50\xef\x4f\ -\xe4\xc6\x9c\x42\xda\x82\x7a\xc7\x25\xec\x60\x55\x1c\x17\x63\x97\ -\x21\x2e\x8b\x45\x34\xbe\x41\x85\xdd\xa3\xdd\x88\x86\x1d\xce\x43\ -\x3b\xf7\xe0\xc9\x47\xef\x31\xe2\x08\x18\x4b\xe2\x58\xda\xe1\xa8\ -\x2d\x71\xf8\xd8\x03\x72\x91\x87\x92\x9b\x48\x8d\x23\x27\xe2\x43\ -\x1c\x20\x80\xc8\x88\xa4\xff\x1b\x91\x47\x55\x39\xd0\xa6\x62\xb7\ -\xb7\x39\x63\x69\x34\x6c\x44\xaa\x90\x50\x77\xf3\x35\x60\xbb\x9b\ -\x0f\xf5\x62\xec\x7b\x6b\xef\x61\x99\xe6\x43\xde\x71\x89\x72\x8a\ -\x34\xce\xd1\xa0\xaf\x58\xf3\x11\x30\x23\xc5\x61\xc6\x12\xae\xd6\ -\x03\x22\x33\x45\x46\xa2\x46\x13\x89\x71\xf4\x8f\x84\x00\x07\x08\ -\x20\x72\x22\x69\xc1\x15\x5c\xa3\x41\x88\x38\x42\xde\xcd\xb7\x11\ -\xcb\x14\x12\xfe\xb5\x41\x65\xf0\x6b\x76\xd0\x8e\x00\x80\xdf\x29\ -\x86\xd2\x41\xba\xf5\xe6\x0d\xa1\xe9\x09\xac\x0d\x3b\x1e\x58\x1c\ -\x21\x45\x12\xee\x7a\x09\x3a\x44\x84\x3a\xfa\x00\x8f\x24\x52\x33\ -\xd2\x62\x12\x02\x1c\x20\x80\xc8\x89\xa4\xff\x7b\x67\x21\x9d\xc6\ -\x3e\x13\xe5\xd2\x65\xb4\x63\x1a\x78\x31\x0e\x84\x44\x89\xa3\x4b\ -\xd8\xf3\x11\xca\xfd\x89\xf7\x50\xae\x70\x79\x84\x79\xfe\x16\xa8\ -\xb0\x5b\x89\x6b\x4d\x43\x02\xa1\x46\x03\x28\x8a\xbe\x7f\x7f\x2b\ -\x8e\xb5\xf1\x00\x8f\xa5\x08\x6c\x79\x09\x39\x33\x91\x1a\x49\x4c\ -\xa4\x84\x37\x40\x00\x91\x15\x49\x13\x6a\x90\x2e\x5d\x46\x59\x1b\ -\x84\x34\xcd\x87\x7e\x31\x36\xb6\x05\xc5\x48\x53\x48\xb3\x51\x46\ -\xec\x90\xd7\xd8\xad\x45\x3a\x8d\x1d\x63\x54\xf5\x18\xfe\x46\x03\ -\xce\x11\x3b\xe4\xc2\x0e\x18\x47\xd0\x48\xc2\x3a\xf4\x80\x51\xe2\ -\xc1\xa7\xd4\x91\x72\x13\xa9\x19\xa9\x88\x94\xf0\x06\x08\x20\xb2\ -\x22\xe9\xbf\x27\x4a\x1c\x3d\xc1\x72\x7f\xe2\x65\x94\xa3\x34\x96\ -\x62\xbb\xd5\x00\xeb\xa8\x2a\xa8\x3e\x2a\x3b\x8c\x74\xb0\xea\x0d\ -\xf8\xdd\x7c\xf0\x13\xf3\xa5\x8e\x60\x5d\xab\x4a\x4e\xe3\x1b\x12\ -\x47\xc0\x48\x7a\x89\x1c\x4b\x98\xb3\x4b\x48\x73\xb5\xb0\x81\x3c\ -\xe4\xc6\x78\x64\x2c\x4d\x46\xbf\x61\x00\x20\x80\xc8\x8b\xa4\xff\ -\x6b\xe0\xa3\x41\xdd\x58\x4f\x63\x47\x8e\x23\xc8\x31\x0d\x58\xb6\ -\x97\xa3\x9d\x1b\x24\x8c\xb1\x79\x02\x71\xcd\x0e\x4a\x2f\xf6\x2c\ -\xe1\xb5\x41\xb8\x47\x55\x31\x0a\x3b\x50\x14\xbd\x7d\xf9\x12\x2d\ -\x2b\xa1\x96\x78\xe8\x4d\x71\x44\x2b\x0f\x9a\x99\x48\x8c\xa3\x3b\ -\xa4\x85\x36\x40\x00\x91\x19\x49\x7d\x35\xf0\x8c\xf4\xa4\x1b\xeb\ -\x89\xf9\xd8\x76\xf3\xf9\xa0\x4c\x97\x3f\x40\xdd\x71\x79\x17\x6d\ -\x54\x15\x63\xa4\x01\xc7\x1d\x97\xb8\x76\xf3\xa1\xe4\xa3\xaf\xb8\ -\xe3\x28\x0e\x1c\x47\x31\xe2\x38\x0a\x3c\xb4\x7a\x09\x32\x59\x8b\ -\x34\xfc\x00\x89\x25\x12\x23\xa9\x9d\xb4\xd0\x06\x08\x20\x32\x23\ -\xe9\x3f\x3b\x38\x92\x66\xa2\x5e\x0f\x82\x32\xd2\xb0\x17\xfd\x6c\ -\x27\x94\xc2\x0e\xf3\x34\x76\xd8\xa2\x6f\xe4\xdd\x7c\xf7\x90\xef\ -\xb8\xc4\x7d\xb0\xea\x4a\x22\xd6\xd8\x61\xef\x20\x01\x23\x09\x1c\ -\x47\x2f\x53\xc5\xc5\x91\xa3\x09\x14\x4b\x29\x58\xeb\x25\x68\x23\ -\x0f\x79\xed\x83\x3a\xa9\x71\x24\x48\x62\x60\x03\x04\x10\xb9\x91\ -\xf4\x9f\x1f\x56\x21\x61\x9b\x8a\x45\x9e\xe6\x83\x9e\x07\x89\xd1\ -\xf8\x26\xe2\x60\xd5\x7b\xb8\x76\x5c\x5e\x27\x66\x37\x1f\x9e\x38\ -\x42\x6d\x34\x00\xe3\x28\xc6\xd0\x10\x25\x92\x30\x7b\xb5\xa8\x4d\ -\x71\x70\x91\x87\xc8\x4c\xa4\xc5\x91\x2c\xa9\x61\x0d\x10\x40\x64\ -\x47\x52\x3b\x37\xc6\xda\x20\x58\xe3\xfb\x32\xf2\x91\x34\xc8\x1b\ -\x5c\xf0\xec\xe6\x83\xe5\xa3\x32\xd4\x33\xd2\xd6\x62\xbb\x18\x1b\ -\xb1\x2b\x16\xf5\x08\x00\x9c\x8b\xbe\xf1\x37\xec\x80\x71\xf4\xd2\ -\xc6\x10\x3d\x96\x52\xd0\xdb\x78\x58\x46\x1f\xc0\xed\x87\x10\x92\ -\x23\xe9\x02\xa9\x61\x0d\x10\x40\x64\x47\xd2\x7f\x6f\xa4\x5d\xb1\ -\x48\x3b\xc5\xd0\x46\x1a\x90\x1a\xdf\x8d\x38\x2f\xf4\x45\x3a\xc8\ -\x0e\xe5\x4a\xb1\x7b\x58\xa7\xf9\x36\xa1\x4d\xf3\xe1\x68\x34\x10\ -\x13\x47\xb0\xc2\x0e\x98\x91\x0c\x71\xe5\x25\x73\xac\x79\x09\x5c\ -\x33\x41\x4f\xb8\x09\x21\x2d\x8e\x84\x48\x0e\x6a\x80\x00\x22\x3f\ -\x92\xfe\xf3\x23\x1f\x01\x80\x3c\x15\x6b\x8f\xed\x9a\x1d\x1f\xd4\ -\x8b\xb1\x91\x7b\xb1\x3b\xb0\xdd\x71\x89\x7c\x7f\x22\x72\x85\xb4\ -\x09\xeb\x74\x39\xd2\xb2\x13\x8c\x05\xc5\x5f\xb1\x56\x48\x71\xd0\ -\x48\x02\xe5\xa3\x97\xa9\x86\x44\xc7\x12\x7a\x91\x07\xcc\x4d\xa4\ -\xc5\xd1\x5b\xd2\x43\x1a\x20\x80\x28\x88\xa4\x09\xf7\x51\x4e\x91\ -\xc6\x76\xf5\xc4\x96\x35\x5b\x30\x46\x55\x91\xef\xe6\x43\x6f\xd8\ -\xa1\xae\xb1\xbb\x87\xed\xd2\xe5\x4d\xd8\x2f\x5d\x86\x17\x76\x84\ -\xf3\x11\x62\xa4\x21\x0e\x52\xd6\x41\x33\x12\x72\x24\x61\xc6\x12\ -\xa2\xc7\xa4\x84\x54\xe6\x81\x23\x8a\x86\xdd\x58\x08\x00\x08\x20\ -\x0a\x22\xe9\xff\xc9\xed\x58\xcf\x76\xda\xbb\x17\xc7\xa8\x2a\xf2\ -\xba\x13\x8c\xe9\x72\xc8\x31\x0d\x0f\x91\xa7\xf9\xb0\x4d\x97\x63\ -\xbb\xd0\x17\x67\xe3\x3b\x01\x77\xe3\x1b\xa9\x61\xf7\xf2\xa5\x81\ -\xa1\xa1\x21\xde\xbc\x84\xc8\x4c\xe8\x65\x1e\x10\x90\x16\x47\x1c\ -\x64\x04\x34\x40\x00\x51\x12\x49\xff\x2f\x13\x98\x42\xda\x82\x76\ -\x46\x1a\xf2\xa2\x6f\x1c\x77\x5c\xc2\xcf\x76\xba\x81\x7c\x4c\x03\ -\x62\xc4\x0e\x69\x7b\x39\x89\xbd\x58\x1e\xcc\x0a\x09\x5a\xd8\x31\ -\x19\x62\x89\x24\x48\x2c\x61\x0c\x11\x61\x64\xa6\x6f\xa4\x45\x92\ -\x34\x39\xe1\x0c\x10\x40\x14\x45\xd2\x7f\x1f\x44\x85\x04\x6f\x7c\ -\xd7\x20\xaf\xb1\x43\x9c\x77\x82\xb8\x18\xfb\x01\x96\x43\x3b\x51\ -\x77\xf3\xa1\x67\x24\x94\x29\x24\x69\x22\x1a\x0d\x84\xe2\x28\x0e\ -\x3a\x1a\xf4\x12\x2d\x23\x61\x2d\xf1\xb0\x55\x4c\x88\x68\xfa\x44\ -\x83\x15\xab\xe8\x00\x20\x80\x28\x8b\xa4\xf6\x8b\xa8\x1b\x5c\x2e\ -\xa3\x4e\xc5\x6e\x59\x8a\xb9\x7e\x0b\xa5\x87\x84\x7a\x8a\x34\xd2\ -\x89\xf9\x68\x97\x2e\x4b\x61\x5e\xe1\x02\x8a\x24\x19\xc4\xae\x58\ -\x0a\xe2\xc8\xce\x10\x05\x88\xe3\x68\x8a\x63\x2d\xf2\x94\x48\x8c\ -\xa4\x0b\x64\x05\x33\x40\x00\x51\x16\x49\xff\xb7\x6e\x47\xba\x60\ -\x1e\x73\xc7\x25\xb6\x63\x1a\x40\x71\x24\x84\x7e\x04\x00\xf2\x69\ -\xec\xe0\x45\xdf\xf7\x10\x23\x0d\x28\x17\x63\x43\x7a\x48\xa8\xb7\ -\x1a\xe0\x6e\xd8\xe1\x6b\x7c\x43\x7b\x48\x2f\x5f\xea\xe3\x8d\x24\ -\xd4\xbc\x84\x34\x48\x04\x8d\x26\x9a\x0e\x35\x40\x01\x40\x00\x51\ -\x18\x49\xff\x37\xa2\x1e\x7e\x0b\x9f\xe6\x43\x3a\xee\x04\x69\xc7\ -\xa5\x20\xce\x7c\x74\x13\xfb\xc2\xfc\x49\xa8\x47\x00\x60\xdf\xb9\ -\x8c\x73\xc4\xee\x3d\xce\x8c\x84\xc8\x47\xb2\x66\x7c\x7c\xf8\x63\ -\x09\xb5\xf9\x80\x9c\x99\x22\x48\x8c\xa4\x57\x64\x06\x32\x40\x00\ -\x51\x1a\x49\xff\x05\xdb\x70\xdc\x9f\x88\x18\xf9\x46\x9b\x9e\xc0\ -\xb9\x30\x5f\x14\x75\x41\xf1\x32\x9c\x67\x3b\xa1\x1e\xac\xfa\x91\ -\xc4\x86\x5d\x1c\x72\xc3\x4e\xf6\x8e\x21\x7a\x24\x21\xc7\x53\x38\ -\x96\xcc\x14\x88\x5c\x35\xd1\x62\xc1\x2a\x06\x00\x08\x20\x8a\x23\ -\xa9\x88\x1f\x31\x1a\x84\x76\xf8\x2d\x3f\xb6\x69\x3e\xf8\xd0\xf7\ -\x0e\xf4\x23\x00\xc0\x23\x0d\xd7\x50\x8f\x69\x98\xe4\xf1\x08\xeb\ -\x06\x97\x85\x48\x8d\xef\x3d\xc4\x6c\x9e\x40\x9b\x2e\x87\x97\x75\ -\xb2\x36\x7c\x20\x80\x33\x2f\xa1\x15\x79\x68\x63\x79\x11\x11\x34\ -\x1c\xfb\x46\x00\x80\x00\xa2\x38\x92\xfe\x33\x5f\x44\x3e\x45\x1a\ -\xdb\x48\x43\xe3\x53\x7c\x3b\x2e\x91\x37\x4f\xe0\x69\xd8\x81\x0a\ -\x3b\x69\xf4\xfb\x13\xb1\xec\x14\x23\x38\x85\x74\x07\xa5\xb0\xfb\ -\xf2\x09\x58\x64\x45\x04\x06\xea\xe8\x98\xa7\xa4\x84\x83\xa3\x05\ -\x1c\x4f\xe0\xa8\x4b\x4c\x4c\x4c\x4a\xd2\xd3\xd3\x93\x94\x4c\x4e\ -\x86\x5c\x88\x05\xbe\x0c\x0b\x7a\x11\x96\x18\x08\x90\x10\x47\xec\ -\x64\x07\x31\x40\x00\x51\x1e\x49\xff\xfd\x30\x2e\xf4\x45\x59\x98\ -\x8f\xba\xee\xc4\x05\xff\x19\x69\xa8\x71\xb4\x1b\xf7\x1d\x97\xb8\ -\xce\xdf\xc2\xd3\xb0\x43\x9f\xe6\x03\xc5\x91\xac\xe3\xbb\x4f\x9f\ -\x60\xb1\x64\x9e\x12\x1e\x0e\x8b\x24\x43\xd4\x48\x92\x44\x8d\x24\ -\x72\x62\x69\x36\xf9\x21\x0c\x10\x40\x54\x88\xa4\xff\xc7\xa1\x23\ -\x76\x17\x61\x19\x69\x0d\xd2\xe1\xb7\x28\xc7\x34\x5c\xc2\x3c\xb4\ -\x13\xe5\x98\x06\xe4\xa1\xef\x17\x68\xbb\xf9\x50\xd6\x34\x20\x5d\ -\x0f\x82\xa7\x61\xf7\x1e\x67\x85\x04\xcd\x47\xb2\x31\x21\x3f\x41\ -\xb1\x14\x81\x92\x97\x88\xc8\x4a\x3f\x48\x8e\xa4\xaf\x14\x04\x30\ -\x40\x00\x51\x23\x92\xfe\x3f\x40\x39\x7f\xeb\xfe\xfd\x2d\x28\x27\ -\x7d\x23\x35\xbe\xe1\xad\x6f\xb4\x6b\x76\x0e\xa3\xdf\x71\x89\x6d\ -\xdd\x09\xde\x9d\x62\x27\x88\x9e\x42\x42\xce\x47\x2f\x83\xd4\xd5\ -\xbf\x7d\x7b\x07\x2d\xf1\x60\x59\x49\x1c\x39\x2b\x81\x63\x89\xf2\ -\xac\x94\x49\x49\xf8\x02\x04\x10\x55\x22\xa9\xe8\x10\xc6\x35\x3b\ -\xe8\x0d\x3b\x60\x24\x7d\xc0\x32\xf2\x0d\x5b\x63\xe7\x85\xb1\x73\ -\x19\x75\xa4\x01\x7b\xc3\x6e\xcf\x47\xac\xdb\xcb\x13\xf0\x4e\xf3\ -\xc5\xbd\xfd\x0e\x8b\x23\x59\xbb\x48\x75\x75\xf5\x9f\xdf\xde\x41\ -\xf3\x12\xd6\x58\xc2\x28\xf0\xc0\x57\x9d\x92\x1a\x49\x2b\x28\x09\ -\x5f\x80\x00\xa2\x4a\x24\xfd\x57\x3d\x8d\xda\xb0\x5b\x4a\xc4\xda\ -\x20\xac\xd3\x13\xf7\x90\xf3\xd1\x23\xcc\x29\x24\xd4\xe3\x4e\x88\ -\xdb\x15\x8b\x59\x21\xc1\x0a\x3b\x57\xdd\xc8\xc8\x48\xf5\x90\x10\ -\x68\x2c\x41\xaa\x25\xf4\x02\x2f\x09\x35\x2b\x31\xa2\x97\x77\x44\ -\xc5\xd2\x7c\x8a\x82\x17\x20\x80\xa8\x13\x49\xff\x99\xf9\x91\x77\ -\x5c\x3a\x61\x4e\x21\x3d\xc0\xb6\xe3\x12\xcf\x1a\x3b\x94\x43\x3b\ -\xd1\x0f\xbf\x45\x3f\x6c\x90\xf0\xda\x20\xcc\x51\x55\x50\x46\x72\ -\xd4\x8d\x8d\x05\xc7\xd2\x3b\x60\x89\x07\xc9\x4b\xe8\xd5\x12\xa2\ -\xbc\x03\xc7\x12\x23\x4a\x56\x82\x44\x92\x16\x2d\x1b\x76\x60\x00\ -\x10\x40\x54\x8a\xa4\xff\xfd\xf7\xa1\x15\x12\xb6\x43\x3b\xd1\xa6\ -\xcb\x67\x63\xdb\xe0\x02\x8c\x23\xa4\xb3\x9d\x90\x2e\x2f\x3f\xb2\ -\x09\xe9\xe6\x09\x7c\x67\xa4\x11\x1b\x47\x6f\xa1\x51\x24\x6b\x67\ -\xa5\xab\x0b\x8a\x24\x75\x50\xeb\x01\x1c\x4b\x58\x5b\x78\x49\x58\ -\x6b\x25\x44\x79\xa7\x45\xe5\x55\x76\x98\x00\x20\x80\xa8\x15\x49\ -\xff\xaf\xc2\xa7\x90\x9c\xe0\x67\x3b\x9d\xc6\x5c\x77\x82\xd6\xb0\ -\x43\x5f\x1b\x84\x65\x37\x1f\xce\x73\xec\xf6\xec\x21\x6d\xfd\x16\ -\xca\x14\x12\x30\x8e\xe2\x82\xac\x80\xb1\x04\x8c\x26\x50\x2c\x7d\ -\xfb\x84\xda\x78\xc0\x52\x2b\x25\x23\xb2\x12\x4a\x79\xa7\x45\x28\ -\x96\x6e\x50\x1a\xb6\x00\x01\x44\xb5\x48\xfa\xef\x03\x39\x6c\x90\ -\xb8\xb3\x9d\xf0\xc6\x11\xf2\x54\x2c\xf2\x4e\x31\x94\x5b\x0d\xf0\ -\x9f\xed\x84\xbb\x13\x0b\x8f\x23\x59\xb3\xf8\x78\x50\x2c\xc5\xc6\ -\xc2\xf2\x12\xa4\x5a\x42\x6a\x3b\xc0\x9b\xe1\x18\x7d\xa5\x1f\x24\ -\x64\x25\x26\x8a\x83\x16\x20\x80\xa8\x17\x49\xff\x1f\xa0\xdf\x30\ -\x8f\x63\xc4\x0e\xe5\xd0\x4e\x6c\x47\x00\x20\x5d\x85\x04\x1e\xf9\ -\xc6\xbe\x53\xec\x15\xe9\xd3\x7c\xc8\xf9\x48\x36\x26\x28\x28\x08\ -\x18\x4d\xb0\x12\x0f\xdc\x78\xc0\x93\x95\x24\xd1\xb3\x12\x03\x91\ -\x59\x29\x8e\xf2\x90\x05\x08\x20\x2a\x46\xd2\x7f\x21\xa4\x03\x21\ -\x51\x6f\x9e\xc0\x9e\x8f\x50\x1a\x0d\xf7\x90\xae\xd9\x99\x84\x39\ -\x3b\x81\x9a\x91\x3e\xe2\x58\x63\x87\xd2\xb2\xe3\xc1\x39\xf4\x0d\ -\x8a\xa3\x97\xa1\x02\xa0\x58\xb2\x82\xc5\x12\xb8\xbb\x84\xde\x76\ -\x40\xed\x2b\x91\x55\xde\xc9\x52\x21\x60\x01\x02\x88\x9a\x91\xf4\ -\x5f\x68\xe9\x16\x9c\x97\x2e\xe3\x5f\x07\x79\x0d\xde\xb0\x7b\x81\ -\x7a\xb0\x2a\xf2\x19\x69\x6f\x90\xb6\x97\xbf\x22\x67\x61\x3e\xa2\ -\xf1\x0d\x2c\xec\x04\x40\x91\x04\x8e\x25\x48\x81\x87\xe8\x2d\xe1\ -\xa9\x95\xb0\x96\x77\x5a\x78\x62\x29\x9d\x1a\xe1\x0a\x10\x40\x54\ -\x8d\xa4\xd6\xd9\x4b\xf9\x9d\x50\xaf\xab\x22\x6e\x61\x3e\xf2\x15\ -\x2e\x2f\x90\xae\xd9\x41\xde\xe1\x82\xdc\xf8\xfe\x88\x77\x8d\x1d\ -\xf6\x7c\x14\x87\x52\x21\xc5\x68\x0b\xc0\x62\x09\x56\x2d\x21\xb7\ -\x1d\xd0\x6a\x25\xb2\xcb\xbb\x3f\xcf\xa9\x11\xae\x00\x01\x44\xd5\ -\x48\xfa\x3f\x61\x07\xe2\x98\x86\xa7\x48\x47\x69\x34\x60\x56\x48\ -\xa0\x48\xc2\xd8\x15\x0b\x1e\xf9\x46\xda\xcd\x87\xba\xe8\x1b\x65\ -\xc7\x25\x81\xe3\x4e\x08\xc4\x91\x6b\xa8\x36\x28\x96\x04\x60\xd5\ -\x12\x28\x96\x20\xed\x70\x44\xdb\x01\x47\x2b\x1c\x6b\x79\x87\x23\ -\x96\xfe\x14\x51\x25\x58\x01\x02\x88\xba\x91\xf4\xbf\xee\x2e\xfa\ -\x55\x48\x87\x60\x71\x84\xd6\x41\x2a\x3b\x8c\xd6\x8b\xc5\xba\x79\ -\x02\xf3\x6c\x27\xdc\x15\x12\x72\x61\xc7\x83\xbb\xb0\x03\xc5\x91\ -\x6c\x06\xe8\x04\x6f\x68\x2c\x41\x0a\xbc\x48\x78\x81\x87\xad\x56\ -\x42\xef\x2a\xa1\xf6\x67\x71\x45\x52\x26\x75\xe2\xe8\x3f\x40\x00\ -\x51\x39\x92\xfe\x17\xdd\x45\xe9\xc5\x3e\xc0\xba\x7e\x4b\x18\xf5\ -\x8e\xcb\x6b\x58\x4f\x63\x3f\x82\xb6\xc6\x6e\xe5\x1b\x19\xfc\x27\ -\xe6\x13\x57\x21\x81\xe3\xc8\x2e\x34\x14\x1e\x49\x88\x6a\xe9\x1b\ -\x96\xac\x84\x54\xde\xe9\xe1\xe9\xcf\x62\x8d\xa5\xcc\x15\x54\x0a\ -\x54\x80\x00\xa2\x76\x24\xfd\x2f\x12\x46\xdf\x71\x89\xe5\xd2\xe5\ -\xdb\x65\x48\x57\x21\x21\x2f\xfa\x7e\x81\x58\x98\xbf\x09\xd6\x8b\ -\xc5\x38\x4a\xe3\x15\xc1\x69\x3e\xac\x71\x84\x54\xd8\x05\x07\x83\ -\x63\x49\x1b\xa9\xc0\x83\x67\x25\x44\xad\x64\x88\x3d\x2b\x11\x5b\ -\xde\x51\x2d\x8e\xfe\x03\x04\x10\xd5\x23\xe9\x7f\x9d\x30\xf2\x51\ -\x1a\x0f\xb0\x2d\x69\xb8\x0d\x3f\x92\x06\xb4\x9b\xef\x1a\xb6\xa9\ -\x58\x68\x3e\xba\x4e\xec\x89\xf9\xf8\x77\xf3\xa1\x8c\xd8\xc9\xbe\ -\x4c\x0b\x86\xc6\x12\x22\x2b\xe9\x42\x3b\x4b\xe0\xac\x84\xde\x74\ -\xc0\x56\xde\x11\x8c\x24\x2a\xd5\x47\x20\x00\x10\x40\xd4\x8f\xa4\ -\xff\xad\x77\xb1\x9f\x91\x86\xdc\x8b\xc5\x72\x5d\x95\x04\xea\x4e\ -\x31\xb4\xb5\xaa\xb0\x0e\xd2\xeb\x3d\x7b\xc8\x89\xa3\xb7\x28\x85\ -\x1d\x4b\x30\x7a\x2c\xa1\x67\xa5\x94\x14\x8c\xa6\x03\x8e\x46\x38\ -\x2c\x92\xd0\x63\x29\xfd\x39\xf5\x42\x14\x20\x80\x68\x10\x49\xff\ -\xff\xef\x20\x70\xcd\x4e\xd9\x61\xcc\xa9\x58\xd8\x1a\x3b\xc4\x01\ -\xc5\xa8\x87\xa4\xe1\x58\xbf\xc5\x44\x52\xa3\x01\xd2\xfa\x66\x81\ -\x46\x12\x3c\x96\xa0\x5d\x5a\x50\x8f\xf6\x13\x72\x56\xc2\x2c\xef\ -\x90\x1a\xe1\x3f\xf0\x56\x4a\x2f\xa9\x19\x9e\x00\x01\x44\x93\x48\ -\xfa\x2f\x84\x6f\xd1\xf7\xed\x9b\xf0\x69\x3e\xcc\xab\x90\xa4\x60\ -\x15\x12\x96\x69\x3e\xbc\xc7\x34\xe0\x59\x63\x17\x87\x12\x47\xae\ -\x2c\x2c\x2c\x28\x59\x49\x00\x25\x2b\xc1\x86\x1d\x50\x9a\x0e\xd8\ -\x2a\xa5\x5f\xf8\x22\x89\x87\xaa\xc1\x09\x10\x40\xb4\x89\xa4\xff\ -\x0f\xa0\x47\xd2\x34\x60\x5e\xb3\x53\x86\x3c\x3d\x01\x1e\x55\x85\ -\x8c\x34\x80\x22\x09\x69\x54\x15\x71\x46\xda\x2d\xe8\x51\x1a\xaf\ -\x21\x8b\xbe\x4f\x90\xba\x7e\x0b\x35\x1f\xbd\xcc\x48\x43\x8f\x25\ -\x48\x81\x07\xe9\x2b\x7d\x83\x35\x1d\x50\xba\x4a\x38\x5a\x0e\x38\ -\x23\x89\x89\xba\xa1\x09\x10\x40\x34\x8a\xa4\xff\x4e\x0f\x0e\x7d\ -\xb8\x84\x7f\x6d\xd0\x6a\x1c\x77\xf3\xa1\x1c\xd3\x70\x0b\xf9\xde\ -\xb7\xd7\x24\x2f\xcc\x47\x5d\xbf\x05\x02\xf2\x69\x69\x28\xb1\x84\ -\x96\x95\xde\xe1\x2a\xef\x48\xe9\x29\x49\x53\x39\x30\x01\x02\x88\ -\x56\x91\xf4\xff\x8c\x10\x72\x85\x04\x6b\x7c\x0b\x97\x21\x1f\xac\ -\x8a\x74\x46\x1a\x46\xa3\xe1\xfa\x75\x3c\x23\x0d\xb8\x0b\x3b\x1e\ -\x5c\xdb\xcb\x61\x8d\x86\x8c\x34\x44\x2c\x85\x42\x63\x09\xd1\x57\ -\x0a\xf9\x89\xab\x7d\x47\xb0\xe5\x40\xc9\x7e\x4b\x02\x00\x20\x80\ -\x68\x16\x49\xff\xf7\x0b\xe3\xba\x66\xe7\x21\xf6\xfb\x13\x3d\x90\ -\x0e\x56\x45\x1d\x0d\x5a\x89\x7d\x34\x28\x81\x98\xdd\x7c\xa8\x85\ -\x9d\x6b\x06\x38\x92\xd2\x60\x59\x09\xb9\x56\x82\x46\x12\x6c\xd4\ -\x81\x60\xa5\x84\x3a\x87\xae\x45\xea\x11\xc5\x24\x00\x80\x00\xa2\ -\x5d\x24\xfd\xcf\x12\xc5\x5c\x1b\x74\xbb\x0c\xcb\xa2\x6f\xd4\xc6\ -\x37\xea\xa2\xef\x37\x48\x8d\x86\x8f\x44\x2f\xcc\x47\xc9\x47\x48\ -\x65\x9d\xab\x91\x51\x06\x38\x9a\xe0\x59\x09\xd1\xc0\x83\xce\xd1\ -\x62\xef\xcf\x12\xaa\x94\x90\x72\xd2\x51\xea\x87\x24\x40\x00\xd1\ -\x30\x92\xfe\xcf\xf3\xc2\xcc\x48\x0f\x11\x87\x76\xde\x40\x5e\xac\ -\x8a\x3c\xd2\x00\xbf\x66\xe7\x16\xfc\x08\x80\xd7\x38\xcf\xd2\xf8\ -\x4a\xc2\x68\xd0\x4b\x79\x23\x50\x2c\x21\xb2\x12\x52\x79\x07\xcb\ -\x4a\xdf\x88\xae\x94\xb0\x2e\x74\xf8\xb3\x82\x06\x01\x09\x10\x40\ -\xb4\x8c\xa4\xff\xff\xef\x62\x9c\x63\x87\x32\xcd\x87\x38\x23\x0d\ -\xd1\xf8\xc6\x71\x0f\xe9\x47\xec\x0d\x3b\x78\x3e\x7a\x8f\x6b\xe7\ -\x32\x62\xdd\x09\xb0\xd1\x60\x64\x84\x3d\x2b\xc1\x3b\xb4\xb0\xfe\ -\x2c\x78\xfc\x0e\x7f\xa5\x84\xd6\x06\xa7\x45\xd3\x1b\x06\x00\x02\ -\x88\xb6\x91\xf4\x7f\xe3\x5d\x8c\xa9\x58\x51\xa4\x5b\x0d\x60\x19\ -\x69\x37\xb6\x6b\x76\x20\x3b\x2e\xf1\x9f\x07\x99\x40\xc2\x34\x9f\ -\xac\xbc\x3c\x22\x96\x90\x22\x49\x00\xa5\xbc\xfb\x89\x88\xa4\x70\ -\x82\x91\x84\xbe\xda\x58\x82\x36\xa1\x08\x10\x40\x34\x8e\xa4\xff\ -\x6c\xa2\x38\x2e\x5d\x46\x1e\xb1\x7b\x84\xd8\x29\x86\x32\x3d\xf1\ -\x06\xcb\xd9\x4e\x4c\x38\x7b\x48\x58\xa6\x90\xde\xbe\x44\xca\x47\ -\x76\xd0\x48\x32\x82\x65\x25\xa4\x56\x38\x72\x7f\x16\x67\xcb\x01\ -\x79\xcd\x10\xd6\x48\xea\xa6\x51\x20\x02\x04\x10\xad\x23\xe9\xff\ -\x94\x1b\x18\xe7\x6f\x41\x1b\xdf\xf7\xe0\x0d\xbb\xdd\x98\xa3\x41\ -\x58\xd6\x06\x91\x7c\x6e\x50\x1c\x4a\xe3\xdb\x55\x5e\x1e\x77\x56\ -\x42\x6a\x84\xc3\x07\x59\xd1\xba\xb3\xe8\x13\x7f\x28\x91\x04\x1e\ -\xf5\x2e\xa1\x55\x18\x02\x04\x10\xcd\x23\xe9\xff\xff\xd9\xc2\xe8\ -\x87\x76\xa2\xae\x3b\x41\xbf\x3f\x11\xe9\xd0\x4e\xf8\x5a\xd5\x8f\ -\xf8\x46\xec\xbe\x10\x53\xd8\x41\xe2\x48\x5e\x1e\xb9\x56\x42\xaf\ -\x94\x20\x2b\x52\xa0\x91\x84\x18\x63\xc5\x36\x7a\x87\xda\x51\x02\ -\xc5\xd1\x17\xda\x85\x20\x40\x00\xd1\x21\x92\xfe\x73\x89\x42\xb7\ -\x2e\x23\xdd\x6a\x80\x14\x47\x93\x90\xaf\x14\x83\x9f\xc6\xbe\x10\ -\xed\x6c\xa7\x13\x38\xd7\x06\x7d\x21\xa6\x61\x07\x8b\x23\x79\xe4\ -\xf2\x8e\x05\xb3\xbc\x83\xb7\x1c\x88\x68\x83\xc3\x23\x09\x14\x47\ -\x5e\x5a\xb4\x0b\x40\x80\x00\xa2\x47\x24\xfd\x77\x90\x40\x3a\x45\ -\x1a\xfd\xfc\xad\xdd\xbb\xb1\x4e\xc5\xde\x22\x6f\xa7\xd8\x1d\x8c\ -\x1d\x97\xa8\x71\x24\x2f\x8f\x5a\x29\x85\xa2\x47\x12\x62\xf8\x0e\ -\x6f\x24\x21\x4d\xce\x82\xab\x23\x6b\x1a\xc6\xd1\x7f\x80\x00\xa2\ -\x4b\x24\xfd\x6f\xbd\x7b\xf3\x26\x62\x17\xd2\x0d\x94\xd3\xd8\xb1\ -\x9f\xbf\x75\x0b\xb9\xd1\x40\xfc\x34\xdf\x1d\x2c\xdb\xcb\x51\xe3\ -\x48\xde\x08\x3d\x96\xf0\x46\x92\x38\xc1\xde\x2c\xb8\xe5\x2d\x46\ -\xcb\x38\xfa\x0f\x10\x40\xf4\x89\xa4\xff\xff\x0f\x48\x20\x35\x1a\ -\x70\x1c\xda\xf9\x0c\x69\xa7\xd8\x1b\x2c\xd3\x7c\x24\x4d\x97\x23\ -\x67\x24\x1e\xcb\x68\x46\x28\x88\x82\x81\x64\x08\x90\x44\x00\x3d\ -\x18\x48\x82\x80\x44\x18\xe0\xe4\x44\x6f\x83\x23\x22\x09\x9c\x8d\ -\x4c\x68\x1c\x78\x00\x01\x44\xaf\x48\xfa\xbf\xe2\x06\xe6\x54\xec\ -\x0b\x94\x35\x76\x48\xeb\x20\x6f\xe1\x39\x58\x95\x89\xa8\x85\xf9\ -\xc8\x8d\x86\x3b\x96\xd1\xf0\x48\xc2\x88\x25\x44\x3c\xe9\xa1\xc7\ -\x52\x12\x2c\x8e\x38\xd1\x87\x1c\xe0\x91\x04\x3e\x2b\xdf\x98\xd6\ -\x61\x07\x10\x40\x74\x8b\xa4\xff\xff\x79\x25\x30\xef\x14\x9b\x84\ -\xf5\x9a\x1d\xd4\x1d\x97\x7b\x08\x5e\xb3\xf3\x05\xcb\x7a\x62\xa4\ -\x38\xb2\x8e\x06\x01\x9c\xb1\x84\x3b\x2f\x81\xa3\x09\x9c\x57\x70\ -\x44\x12\x48\xea\x3a\xed\x83\x10\x20\x80\xe8\x18\x49\xff\x2f\x3c\ -\x42\x69\x7c\x2f\x83\x1f\xd3\x80\xba\xc8\x0e\xe5\x4c\x9a\x3d\x24\ -\x1c\x08\x89\x7d\x4d\x03\x34\x8e\xf0\xc4\x12\x96\xbc\x84\xc8\x4c\ -\xd0\x31\x39\x6c\x91\x04\xee\x1c\x59\xd3\x21\x04\x01\x02\x88\x9e\ -\x91\xf4\xff\xff\x69\x09\xe4\xc6\x37\xfa\xad\x06\xd2\x88\x69\x3e\ -\xc4\xec\x04\x89\xa7\xb1\xc7\xa1\xaf\xdf\xba\x63\xfd\xeb\x57\x34\ -\x6a\x34\x11\x93\x97\x60\xb1\xc4\x89\x88\xa4\x44\xb4\x3a\x09\x24\ -\x2c\x45\x97\x60\x03\x08\x20\xfa\x46\xd2\xff\xf9\x1e\xc8\x53\x48\ -\xbb\x51\x46\xec\xa4\x11\x8d\x06\xcc\x73\xec\x88\x3c\x45\x3a\x0e\ -\x3d\x1f\xf1\x58\xfe\xfa\x85\x3b\x96\xf0\x35\x1f\xf4\x90\xe3\x88\ -\x93\x13\xbd\x09\x0e\xc9\x46\xf4\x09\x35\x80\x00\xa2\x73\x24\xfd\ -\xff\xbf\xc5\x03\x7e\x0f\x29\x96\xbb\xf9\xa4\x51\xa6\x62\x5f\x13\ -\xb1\x79\x02\xf7\x4e\x31\x50\xbb\xee\x8b\xe5\xef\x5f\x58\x62\x09\ -\x5f\x66\x42\x8e\x26\x94\xc5\x25\x28\xad\x3b\x90\x80\x1b\xbd\xc2\ -\x0c\x20\x80\xe8\x1e\x49\xff\x55\x30\xaf\xd9\x41\xbb\x9b\x0f\xf3\ -\x60\x55\x12\xd6\x06\xa1\xe4\xa3\x2f\x96\x3f\x7e\xff\xfe\x4d\x54\ -\x2c\x61\x2b\xf3\xd0\x96\xd2\x21\x22\x09\xbc\xb0\xce\x92\x6e\x41\ -\x06\x10\x40\xf4\x8f\xa4\xff\xff\x37\x9c\x45\x99\xe6\x3b\x8b\x72\ -\xd2\x37\xda\x51\x1a\xd8\x77\x8a\xe1\x89\x23\xf8\xa8\x2a\xe8\xbc\ -\x93\xa8\x1f\x3f\x7e\xfc\x46\xc9\x4b\x98\x4d\x71\xdc\x99\x09\xcb\ -\xf2\x6e\x70\x71\x07\x66\x19\xd0\x31\xc0\x00\x02\x68\x20\x22\xe9\ -\xff\xff\x07\x67\xd1\x8e\xd2\x40\x9e\x2f\x47\x2c\x3b\xd9\x83\x63\ -\x57\x2c\xae\x91\xef\x3b\xa8\xbb\xf9\x98\x18\x7f\x80\xc1\x2f\xfc\ -\x79\x09\x57\xfb\x01\xef\x3e\x58\xc6\x68\x3a\x06\x17\x40\x00\x0d\ -\x4c\x24\xfd\x57\x5e\x2d\x85\x72\xb6\x93\x34\xc6\x68\x10\xa1\x86\ -\xdd\x7b\xbc\x27\xe6\x83\x22\xe9\xfa\xaf\x1f\x50\x00\x2e\xf2\xa2\ -\x49\xcb\x4b\x78\x2f\x88\x4d\xa2\x6b\x1c\xfd\x07\x08\xa0\x01\x8a\ -\xa4\xff\xff\x99\xa5\x30\xf2\x11\xf2\x8e\xcb\x8f\x64\x5e\x85\x84\ -\xa8\x90\x6c\x18\x18\x10\x91\x84\x2d\x2b\xe1\xad\x98\xf0\x1d\x13\ -\xa4\x4f\xef\xb0\x02\x08\xa0\x01\x8b\xa4\xff\xff\x39\x8e\x61\x4c\ -\x97\x13\x75\x68\x27\x51\x07\x42\xbe\x34\x01\x0d\x50\xc3\x62\x09\ -\x47\x89\x87\x25\x96\x92\x09\xc6\x91\x4d\x32\xdd\x43\x0a\x20\x80\ -\x06\x30\x92\xfe\xff\x7f\x7a\x0b\x6d\x34\x68\xe1\x4a\xe2\x7a\xb1\ -\x5f\x08\x34\x1a\x80\x5d\x58\xf0\x1a\x11\x44\x2c\xa1\x66\xa6\x68\ -\x02\xb1\x84\x3b\x8a\x24\x06\x22\x9c\x00\x02\x68\x40\x23\xe9\xff\ -\xff\x4b\x6f\x36\xa1\x6d\x2f\x27\xb4\xee\xe4\x3d\x31\xd3\x7c\x3c\ -\x96\x0c\x30\x80\x52\x2f\x61\xc6\x12\xd6\x22\x0f\x67\x14\x49\x0d\ -\x4c\x28\x01\x04\xd0\x00\x47\xd2\xff\xd6\x86\x37\xd2\x58\x0a\x3b\ -\x52\x47\x55\x51\xd7\x13\x7f\x34\x66\xc0\x88\x24\x5c\x79\x09\x4b\ -\x34\xe1\x8c\xa2\xe7\x03\x14\x48\x00\x01\x34\xd0\x91\x04\x3a\x57\ -\x40\x06\xbe\x09\x09\x3e\x85\x44\xe6\x8e\x4b\x48\xc3\xce\x86\x01\ -\x19\xa0\xc5\x12\x46\x87\x09\xbd\x99\x87\x2b\x8a\xae\x3f\x1f\xb0\ -\x20\x02\x08\xa0\x81\x8f\xa4\xff\xff\xe5\x66\xbf\x26\xe6\x6e\x3e\ -\x3c\x47\x00\x20\x77\x90\xbe\xdb\x8a\x31\xe0\x8a\x25\x02\x15\x13\ -\x28\x96\x70\x44\x51\x4c\xd1\x00\x06\x10\x40\x00\x0d\x86\x48\x02\ -\x82\xa5\xaf\x09\x5e\xe1\x42\xe4\x34\x5f\x8c\xa5\x18\xae\x48\x02\ -\x46\x13\xa1\x12\x2f\x0a\x7b\x14\xd9\x0d\x6c\xe8\x00\x04\xd0\x20\ -\x89\xa4\xff\xff\xbd\x17\xca\xc0\xa6\x90\xb0\xae\xdf\xfa\x9a\x80\ -\xf3\x42\x5f\xe4\x75\x27\xd7\xc1\xcb\xe0\x18\x18\x48\x2c\xf1\x18\ -\xf1\x44\x51\xba\xf3\x40\x87\x0d\x40\x00\x0d\x9a\x48\xfa\xff\xbf\ -\x53\xea\x23\xee\x2b\x5c\xbe\xe2\x2e\xec\x90\x1a\xdf\x71\x26\x90\ -\x8d\x28\xe8\xb1\xc4\x80\x3d\x96\xd0\x5b\x79\xd8\xa2\x88\x67\xf1\ -\xc0\x87\x0c\x40\x00\x0d\xa2\x48\xfa\xff\xbf\x49\xf8\x04\x99\x97\ -\x2e\x43\x0e\xbf\x65\x32\x16\x13\xc3\x1e\x4b\xc8\xf5\x12\xce\x32\ -\x0f\xcb\xe0\x82\x74\xfb\x60\x08\x17\x80\x00\x1a\x54\x91\x04\x1a\ -\x86\x90\x3e\x41\xf4\x14\x12\xfa\x46\x31\x61\x31\x24\xc0\x40\x5c\ -\x91\x87\x88\x24\x2c\x27\xd5\x39\x0f\x92\x40\x01\x08\xa0\xc1\x16\ -\x49\xff\xff\xf7\x09\x31\x91\x7c\x3d\x08\x28\x92\x40\x2d\x06\xe2\ -\x62\xe9\x17\xb6\x22\x0f\x23\x13\x49\x10\xdc\x69\xf4\x83\x81\x4e\ -\xa1\x07\x10\x40\x83\x2f\x92\x80\x80\xed\x08\x13\x13\x91\xc7\x34\ -\xc4\xc1\xee\xb8\x7c\x24\x86\x01\x70\x37\xf2\x50\xdb\x0f\x40\x84\ -\x1e\x45\x5f\x36\xe0\x75\x20\x6c\xfd\x2a\x9d\xc2\x03\x20\x80\x06\ -\x65\x24\x01\x41\xb7\x74\x02\xfe\x9d\xcb\x48\x0b\xb8\x80\x8d\x86\ -\x04\x6b\x2d\x2d\x2d\x02\xb1\x84\xd1\x16\x47\x64\x26\xb4\xb6\x82\ -\xfd\x20\x0b\x0c\x80\x00\x1a\xac\x91\x04\x04\x5c\xd2\x09\x44\x56\ -\x48\xdf\x6f\x83\x0f\x8e\xd1\x22\x10\x49\xb8\x2a\x26\xd4\x3c\x74\ -\x71\xf0\x85\x04\x40\x00\x0d\xe2\x48\x02\x02\x67\xe9\xf7\x44\xac\ -\xdf\x92\x31\xd6\xd2\xc2\x1a\x49\x98\xb1\xc4\x80\xd9\xca\xcb\x44\ -\x8a\xa1\xf7\x5b\x70\x3a\x05\xb4\x54\x68\xa0\x82\x01\x20\x80\x06\ -\x77\x24\x01\x01\xf3\xed\x84\xf7\x38\x3a\xb1\x90\xc2\x8e\xc7\x56\ -\x0b\x01\x48\x29\xf1\x20\xb1\x84\x38\x55\xcb\x6e\x32\x4e\x47\x80\ -\x96\x73\x0d\x60\x18\x00\x04\xd0\xa0\x8f\x24\x10\xe0\x95\xfe\x82\ -\xf3\xe6\x89\x6b\x0c\x5a\x5a\x78\x62\x89\x01\x77\x2b\x0f\x34\x13\ -\x98\x09\xc9\x47\x99\xae\x36\xd1\xbf\x70\xdb\x9f\x38\xc0\x91\x04\ -\x10\x40\x43\x22\x92\x80\x40\xe3\xf4\x42\x6c\x6d\x86\xb3\xc6\x5a\ -\x68\x40\x8c\x84\xcc\x94\x09\x8a\xa3\xcc\x38\x33\xd0\x42\x64\x7c\ -\x01\x91\x98\x38\xb0\x9e\x07\x08\xa0\xa1\x12\x49\x60\xc0\xed\xf1\ -\xf5\x0e\xf2\x48\xc3\x47\x4b\x2d\x4c\x40\xb0\x62\x42\x8a\xa2\x3f\ -\x31\x36\x90\x06\xc4\xaf\x41\xed\x6f\x80\x00\x1a\x52\x91\x04\x02\ -\xd3\x5c\x56\x7e\x81\xc4\x11\xd3\xcc\xff\xff\x97\x13\x13\x4b\xd8\ -\xf3\xd2\x9d\xeb\xb6\xf0\x66\xde\x8f\xc1\xed\x67\x80\x00\x1a\x72\ -\x91\x04\x19\x8b\xbd\x78\x6d\x25\x77\x15\x8c\x47\x30\x92\xf0\xd4\ -\x4b\xe0\x66\xde\x60\xf7\x2e\x40\x00\x0d\xcd\x48\xc2\x0a\x88\xcf\ -\x4a\x48\xd1\x34\x24\xfc\x0f\x10\x40\xc3\x28\x92\x90\x23\x8b\x70\ -\x34\x0d\x25\x1f\x01\x04\xd0\x30\x8c\xa4\xe1\x07\x00\x02\x68\x34\ -\x92\x86\x00\x00\x08\xa0\xd1\x48\x1a\x02\x00\x20\x80\x46\x23\x69\ -\x08\x00\x80\x00\x1a\x8d\xa4\x21\x00\x00\x02\x68\x34\x92\x86\x00\ -\x00\x08\xa0\xd1\x48\x1a\x02\x00\x20\x80\x46\x23\x69\x08\x00\x80\ -\x00\x1a\x8d\xa4\x21\x00\x00\x02\x68\x34\x92\x86\x00\x00\x08\xa0\ -\xd1\x48\x1a\x02\x00\x20\x80\x46\x23\x69\x08\x00\x80\x00\x1a\x8d\ -\xa4\x21\x00\x00\x02\x68\x34\x92\x86\x00\x00\x08\xa0\xd1\x48\x1a\ -\x02\x00\x20\x80\x46\x23\x69\x08\x00\x80\x00\x1a\x8d\xa4\x21\x00\ -\x00\x02\x68\x34\x92\x86\x00\x00\x08\xa0\xd1\x48\x1a\x02\x00\x20\ -\x80\x46\x23\x69\x08\x00\x80\x00\x1a\x8d\xa4\x21\x00\x00\x02\x68\ -\x34\x92\x86\x00\x00\x08\xa0\xd1\x48\x1a\x02\x00\x20\xc0\x00\x5c\ -\x1c\xc4\x98\x2a\xd6\x64\x23\x00\x00\x00\x00\x49\x45\x4e\x44\xae\ -\x42\x60\x82\ -\x00\x00\x02\x88\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x09\x12\ -\x0d\x12\x24\x5f\xcf\x2d\xcb\x00\x00\x02\x08\x49\x44\x41\x54\x38\ -\xcb\xa5\x93\xdd\x4b\x93\x61\x18\xc6\xaf\x77\xbe\x6f\x7f\xc1\x3c\ -\xc8\xb3\x4d\xa2\x3a\xd9\x59\x22\xb5\x68\x1d\xec\x83\x4d\xb4\x2c\ -\x41\x42\x02\x23\x25\xa9\xb3\xa0\xfe\x84\x0e\x82\xc0\x03\x25\x0b\ -\x44\x50\x3c\xd8\x68\x34\x3f\x32\x77\xe2\x81\x27\x8e\x2d\xab\xb5\ -\x56\x21\x4c\xd7\xd6\x04\xc7\xd2\x77\xef\xeb\xfc\x18\xcf\xae\x0e\ -\x62\x5b\x6b\x33\x06\xdd\xf0\x3b\xb9\x9f\xfb\xb9\xb8\x78\xee\xeb\ -\x91\x48\xa2\x51\x5d\xbe\xea\xac\x1c\xac\xae\x2c\x4b\x27\xcc\xc0\ -\xf0\x77\x53\x55\xf3\x18\x1d\x9b\xe0\x9f\x17\x6f\xdd\x1e\xe2\xe8\ -\xd8\x04\x55\x35\x5f\x27\x52\x23\xe0\xf3\xcf\xb1\x7f\x60\x90\x5a\ -\x5e\xaf\x71\xf1\xf8\xd1\x43\xe4\x72\x3f\xd1\x3f\x30\x48\x9f\x7f\ -\xae\xd6\x32\x49\x90\x84\xcf\x1f\xa0\xa7\xa7\x8f\xb1\x2f\x1b\xde\ -\x72\x4f\x08\x81\x62\xb1\x08\x21\x04\x48\xe2\xd3\xe7\x6f\x11\x87\ -\xfb\x1a\x7d\xfe\x00\xcb\x33\x20\x89\x3d\x55\x85\xab\xab\x97\xf1\ -\xaf\x1b\xc3\xfb\x85\x03\x64\x73\xbb\x48\x67\x76\xb0\x95\xda\xc6\ -\xe6\xf7\x0c\xb6\x52\xdb\x48\x67\x76\x90\xcd\xed\x22\xb2\x1e\xa5\ -\xab\xab\x97\x7b\xaa\x0a\x92\x90\x01\x60\x66\xd6\xcb\x8e\x8e\x0b\ -\x30\x1a\x8d\x2f\xb2\xb9\x3d\x1c\x1e\x1d\x9b\x1b\x3d\x9a\x5e\x38\ -\x80\xb1\xb5\x55\xb2\x58\x2c\x9c\x99\xf5\xf2\xfe\xbd\xbb\x92\x01\ -\x00\xd6\x42\x61\x38\xed\x76\x68\x7a\x01\x42\x94\xa0\xc8\x72\xe2\ -\x24\x84\x28\xc1\x66\xbb\x82\xb5\x50\x18\x00\x7e\x3b\x48\x26\x93\ -\x68\x6b\x3b\x2d\xc9\xb2\x8c\x66\xea\x4c\x7b\xbb\x94\x4c\x26\x59\ -\x11\x20\x89\x96\x16\x03\x14\xa5\x39\x01\x21\x44\xed\x1a\xcd\x66\ -\x33\x52\xe9\x1f\x54\x14\x19\xcd\x90\xd8\x4c\xd0\x64\x32\x55\x05\ -\xac\x97\x3a\x31\xbf\xb0\x80\x53\x8a\xd2\x14\xaf\x03\xf3\xb0\x5e\ -\xec\xac\xe6\x40\xd3\x74\x78\x7a\xfa\xb8\xfe\x21\xca\xc3\xa3\x63\ -\xfc\x8b\x50\xf8\x1d\xdd\xdd\x37\xa9\x69\x7a\x35\x07\x24\xb1\xb8\ -\x14\xa4\xbb\xfb\x06\x3f\x46\x63\x14\xa2\x84\x46\x84\x23\xef\xe9\ -\xf2\x5c\xe7\xe2\x52\xb0\x12\x24\xc9\x6a\x73\x60\x75\x65\x19\x00\ -\xf0\xe6\x6d\x90\xe3\xcf\x5f\xe2\xfc\xb9\xb3\x95\x35\x01\xc0\xb3\ -\xa7\x4f\xf0\xca\x1f\x40\x2c\x1e\xc7\x83\x91\x61\xb8\x9c\x76\xa9\ -\x2e\xca\x65\x74\x5d\xc7\xe4\xd4\x34\xad\x36\x07\x49\xc2\x6a\x73\ -\xf0\xce\xd0\x08\x27\xa7\xa6\xa9\xeb\xfb\x75\xf3\xd2\xff\x7c\x67\ -\x00\xf8\x05\xa3\x97\x6b\x00\xba\x95\x23\x65\x00\x00\x00\x00\x49\ -\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x01\xee\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x0b\x11\ -\x0e\x13\x0b\x87\x36\x63\x04\x00\x00\x01\x6e\x49\x44\x41\x54\x38\ -\xcb\xa5\x93\x5b\x6f\xda\x40\x14\x84\xbf\x63\xd6\x71\x0c\xaa\x48\ -\x20\xad\x14\x25\x2f\x69\x9f\x08\x49\xff\xff\x8f\x48\x25\x28\xa1\ -\xc4\xc8\x6a\x89\x02\xbc\x54\x10\x6c\x84\xcd\xd5\xa7\x0f\x80\x05\ -\xb9\x50\x4b\x59\x69\x5e\xce\xee\x8e\x66\x76\x66\x45\x55\xf9\xc8\ -\xb2\xde\xdb\x18\x05\x21\x22\xa2\x22\xa2\xa3\x20\x7c\x9f\x41\x55\ -\x5f\xa1\x56\x6f\x00\xa8\xd7\xf6\xf1\xda\x3e\x80\xd6\xea\x8d\x37\ -\xcf\xbe\x1e\x80\x02\x1a\x45\x71\x3a\x8b\xa2\x38\x9d\xbf\x3c\x9f\ -\x5a\x08\x36\x92\x37\xaa\x64\x95\x28\x7e\xa7\x87\xdf\xe9\xb1\x4a\ -\x14\x55\x15\x00\x11\xd1\x60\xc7\x92\xa8\x2a\x8d\xfb\x26\xdf\x6f\ -\x6f\x74\x30\x7c\x16\xc7\x39\xe6\xef\xe0\x99\x49\x34\xdd\xb3\x5a\ -\xc8\x1f\xf3\xb9\x7c\xca\x6c\x3a\xa5\x5c\x3e\xd5\x9f\x8d\xa6\xdc\ -\xde\x5c\x23\x1b\x69\x0c\x86\x23\x99\xc4\x33\xe2\x78\x76\xf0\xd5\ -\x5d\xd7\xa1\xe0\x3a\x94\x4b\x27\x0a\xac\x09\xc2\x70\x2c\xc3\x60\ -\x42\x92\x24\xd9\xa2\xb3\x2c\xbe\x9c\x15\xc9\xbb\xae\x1a\x80\xd6\ -\x83\xc7\xd5\xd7\x6f\xcc\xe6\x8b\x4c\x04\xce\x91\x4d\xde\x75\x01\ -\x30\xcd\x66\x4b\xaa\xd5\x8a\xfe\x7a\xf0\xa4\x54\x3a\xcb\x46\xe0\ -\xd8\x00\xfa\xf4\xd4\x15\x53\xad\x56\xce\x8b\xc5\x13\x96\xf3\x39\ -\xb6\x31\x99\x08\xc6\xe1\x18\x80\xcb\xcb\x8b\x75\x0a\xdb\xf8\x82\ -\xf1\x44\xb2\x10\x14\x3f\x15\xd2\xb8\x2d\x80\xbb\xbb\x1f\x02\x60\ -\x72\x16\xb6\xc9\x1d\x84\xc9\xad\xab\xd3\xed\xf5\x64\xaf\xca\x80\ -\xfe\xe9\x3c\xb2\x58\x2c\x0f\xc2\xf7\x7f\xb3\xdb\x48\xd9\xfe\xc6\ -\xad\x8d\xc5\x72\x75\xd0\x86\x6d\x72\xa9\xfc\x3d\x05\x5e\xbb\x9d\ -\xf6\xfd\x7f\xe8\xf7\xfb\xe9\xbd\x7f\x30\xf3\x1b\x09\xc3\xe7\x90\ -\xbe\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x02\xa1\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x09\x12\ -\x0d\x1b\x0f\x22\xb1\x6f\xc2\x00\x00\x02\x21\x49\x44\x41\x54\x38\ -\xcb\x95\x92\xdd\x4b\x93\x71\x18\x86\xaf\xdf\x5e\xb7\x45\x52\xa6\ -\x18\xa8\xd3\xfc\xe8\xe3\x40\x22\x0a\x3b\x68\xea\x81\x9a\x76\x90\ -\x9a\x60\x20\x15\x7e\x94\x62\x86\x5f\x79\x26\xe4\x41\x14\xe4\x18\ -\x68\x64\x0a\x91\x35\x8c\x0a\x2a\x69\x19\x81\x26\xd9\x49\x9e\x05\ -\x6a\x98\xe8\x41\xb4\x5e\x9d\x0d\x43\x8c\x7d\x24\x8a\x7b\x37\x7f\ -\x1d\x05\x2b\x70\xb3\xeb\x0f\xb8\xb8\xef\xfb\x79\xc4\xaa\x47\xc5\ -\xe3\xf1\x66\xdc\xbc\x71\xdb\x91\x96\x9e\x42\x62\x52\x02\x85\x05\ -\xb9\x0d\xb1\x71\x7b\xfa\xd9\x06\x3a\x80\xa9\xa9\x19\x87\xdb\xb7\ -\xce\xf9\x8b\xf5\x93\x85\xc5\xe5\x45\x43\xc3\xe3\xf7\x27\x26\xa6\ -\xe5\x76\x04\x62\xd5\xa3\x02\xe0\xf5\xfa\x78\xfc\xe4\xa5\x4c\x4a\ -\xd9\x4f\x59\x79\xb9\xb5\xa7\xab\xbb\xbd\xad\xb5\x4a\x28\x8a\x12\ -\x3e\x41\x20\x10\x00\x20\x26\x66\x37\x2d\xcd\xb5\x62\x66\xfa\x13\ -\x00\x79\x85\xf9\x38\x17\x5c\x11\x53\xe8\x4a\xcf\x5c\x92\x9d\x96\ -\x3e\xb9\xb4\xb4\x2c\xbd\x5e\x1f\xfb\x52\x53\x01\x30\x18\xf4\xa8\ -\xea\x42\xe4\x0d\x0a\xf2\x73\xa8\x6d\x68\x64\xf2\xb3\xca\xd3\xe7\ -\x23\xf2\x42\x75\x8d\x00\x30\x1a\x0d\x3c\x7b\xf1\x86\x8d\x0d\x7f\ -\x78\x41\x53\x73\x8d\xb8\xd7\xdb\x8b\x39\x3b\xdb\xda\xd0\xd8\x64\ -\x15\x3a\x5d\xc6\x8f\x65\x57\xbb\x5f\xdb\x60\x7d\x7d\x0d\xb3\xb9\ -\x58\x3a\xbe\xcd\xcb\xb0\x23\x6a\x5a\x00\xbb\x7d\x58\x7e\x75\x38\ -\x79\x3b\x32\xc6\xd1\x63\x47\x30\x18\x75\xbc\x1b\xfd\x40\x7c\x5c\ -\x34\xb9\x39\x87\x29\x2d\xab\xe0\x84\xf9\xb8\xd8\xf2\x0a\xa1\xb8\ -\x5c\x4b\xb2\xa4\xa4\x8a\x84\xbd\xd1\xd8\x1f\x9d\x45\x4a\x85\x81\ -\xc1\x2f\xc4\xc4\x67\x52\x59\x59\x21\x84\x10\x7f\xff\xc1\xbf\x98\ -\x4c\x89\xc2\xfe\xca\x46\x56\xd6\x41\x20\x88\x5e\x2f\xa9\x3d\x77\ -\x88\xd8\x1d\x4e\x2c\x9d\xdd\xd2\xef\xd7\xc2\x0b\x00\x32\xd2\x53\ -\x45\xfd\x95\x36\x2c\x7d\xb3\x04\x83\x7e\xa2\x94\x4d\x4e\xe6\x26\ -\x71\x3a\x6f\x27\x1d\x1d\xd7\xe5\xcf\x15\xf7\xe0\x96\x15\x42\x99\ -\x9f\x5f\x94\xb6\xfe\x1e\xae\xb5\x64\x12\x15\xa5\x27\x10\xd4\xe1\ -\xf6\x68\xdc\xb1\xcd\x52\x5d\x53\x17\x59\x10\x2a\xe9\x68\xcd\x44\ -\x51\xf4\xbc\x1e\x75\xb2\xf0\x7d\x8d\x15\xb7\xb6\x75\x85\x50\xd2\ -\xd2\x52\x44\xdd\xe5\xab\xdc\xea\x9d\x23\x18\xd4\x18\xff\xe8\x61\ -\x57\xdc\x01\x7e\xad\xfa\xb7\x97\xe0\x0f\xaa\xea\x94\x03\x0f\xee\ -\x62\x34\x6c\x32\x34\x32\x87\xed\x61\xd7\xff\x09\x00\x16\x17\x5d\ -\xf2\xfd\xd8\x18\x45\xa7\x8a\x48\x4e\x36\x89\xdf\x4d\x02\xe5\x38\ -\x9f\x9e\xe2\xf8\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\ -\x00\x00\x01\x99\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x09\x12\ -\x0c\x2a\x29\x37\x23\x87\xba\x00\x00\x01\x19\x49\x44\x41\x54\x38\ -\xcb\x9d\x93\x4d\x4b\xc3\x40\x10\x86\xdf\xf4\xa7\xa8\x37\x51\x44\ -\xaf\x29\xa4\x20\x29\xf4\xe2\x7f\x10\x0a\x22\x0a\x82\xa2\x60\x51\ -\xf0\x2c\x7e\x85\x68\xf1\x24\x82\xbf\x41\x10\x04\x85\xa2\x17\x8b\ -\xd5\x7a\xb3\xe9\x3d\xdb\x1e\x37\x91\x9c\x7c\x3d\xad\x66\xc9\x62\ -\x93\x0e\x2c\x0c\xcc\x3c\xcf\x0c\xcb\xae\x45\x12\x45\xa3\x5c\xa9\ -\xfe\xe6\x56\x5e\x41\x18\x0a\xb6\x9e\x9e\xd1\x79\xeb\x42\x88\x01\ -\x82\xa0\x8f\x6f\x72\xb4\xa0\x17\xf4\x79\x75\x7d\x83\x97\xf6\x2b\ -\xa6\x26\x27\xb0\x30\x3f\x87\xd9\x99\x69\x6c\x6e\x37\xf0\x78\x7f\ -\x6b\x81\xa4\xf1\x44\x51\x04\xcf\x6f\xd2\x59\xac\xf1\xf0\xe8\x8c\ -\x83\xe1\xb0\xad\x6a\xb6\xe3\x52\xe5\x46\x58\x4a\x89\xe5\xfa\x2a\ -\x77\x76\xf7\x19\x0a\xc1\x74\x2d\x0d\x1b\x05\x52\x4a\xd4\x57\xd6\ -\xe8\x5f\x5c\x32\x8e\xbf\xf0\x1f\x9c\x11\xa8\xc9\x27\xde\x79\xa6\ -\xd1\x04\x67\x04\x9e\xdf\xe4\xfa\xc6\x16\xa3\x38\xce\x05\x6b\x82\ -\xcf\x5e\x40\xb7\xb6\xa4\x5d\xd6\x28\x58\x13\x34\xf6\x0e\x78\x7c\ -\xea\xb3\x08\xac\x09\xde\xbb\x1f\x4c\x92\xa4\x10\xac\x09\xd2\x40\ -\x5e\x98\x24\x4a\xa6\xd7\xd7\x7a\xb8\xb3\xf2\xfe\x0b\xcb\x76\x5c\ -\x16\x15\x94\x2b\xd5\x3f\x66\x9c\xb5\x55\xaf\xed\xb8\x2c\x61\xcc\ -\x50\x5b\xfc\x00\x78\x31\xf9\x56\x9e\x08\xe8\xfe\x00\x00\x00\x00\ -\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x01\x7f\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0\x77\x3d\xf8\ -\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xaf\xc8\x37\x05\x8a\xe9\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x01\x11\x49\x44\x41\x54\x78\xda\x62\ -\xfc\xff\xff\x3f\x03\x2d\x01\x40\x00\x31\x31\xd0\x18\x00\x04\x10\ -\xcd\x2d\x00\x08\x20\x16\x10\xc1\xc8\xc8\x08\x0f\x27\x60\x90\x31\ -\x22\xf3\x71\x88\x09\x00\xc5\x3e\x12\x63\x01\x40\x00\x31\x21\x1b\ -\x02\xc2\xc8\x6c\x6c\x62\xfc\xfc\xfc\x24\xf9\x00\x20\x80\x48\x0e\ -\xa2\x8f\x1f\x3f\x92\xa4\x1e\x20\x80\x18\xc9\x49\x45\xe8\x41\x8a\ -\x4f\x2d\x40\x00\x91\x1b\xc9\x02\x30\x83\x41\x96\xa1\xc7\x19\x32\ -\x00\x08\x20\x26\x74\x17\x11\xe9\x7a\x3e\x28\x57\x8b\x90\x45\x00\ -\x01\x44\xb2\x0f\x90\x23\x19\x68\xf8\x75\xf4\x04\x02\xb4\x84\x03\ -\x59\x3d\x40\x00\x31\x40\xe3\xe0\x3f\x0c\xa3\xf3\x71\x88\xf1\x83\ -\xc4\x60\x18\x5d\x3d\x4c\x0f\x08\x03\x04\x10\x23\x35\x8a\x0a\xa0\ -\xab\x75\x80\xd4\x65\x6c\x11\x0f\x10\x40\x54\xc9\xc9\x40\x03\xaf\ -\x20\xe7\x17\x20\x30\x82\xc9\x01\x04\x10\x23\xad\x0b\x3b\x80\x00\ -\x62\x21\x2d\x28\x18\xfe\x93\xe6\x33\x06\x46\x80\x00\x62\x21\x3d\ -\x38\x18\x18\x49\x71\x0c\x40\x00\xd1\xbc\x34\x05\x08\x20\x9a\x5b\ -\x00\x10\x40\x34\xb7\x00\x20\x80\x68\x6e\x01\x40\x00\xd1\xdc\x02\ -\x80\x00\x62\x21\x3d\xd7\x92\x96\x54\x01\x02\x88\x85\x16\x49\x14\ -\x19\x00\x04\x10\xcd\x83\x08\x20\xc0\x00\x1b\xbb\x8c\x5b\xa1\x7e\ -\xe8\x48\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x02\x7b\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x09\x12\ -\x0d\x14\x11\x5f\x26\x4e\x6e\x00\x00\x01\xfb\x49\x44\x41\x54\x38\ -\xcb\xa5\x53\xbd\x6b\x53\x71\x14\x3d\xbf\xf4\xc5\xbf\x20\x05\xd3\ -\xf1\x2d\xea\x92\xcd\x52\x34\x81\x66\x30\x09\x49\x69\xb5\x5a\x28\ -\x52\x3f\x22\x36\x35\xe8\x26\xe8\x26\xe8\xe2\x20\x08\x1d\x5a\xac\ -\x42\x29\xb4\x74\x48\x30\x98\x7e\x58\x93\x25\x43\x16\x4b\x62\xd5\ -\x18\xa3\xd2\xc1\x62\x62\x0a\x0d\xb1\x79\x79\xaf\x9f\xe1\x79\x3a\ -\x25\x4d\x6d\x5a\x02\x1e\x38\xcb\xb9\xf7\x77\xb9\x9c\xdf\xb9\x82\ -\x24\x1a\xc1\x66\x77\xd6\x0a\xf1\x58\x44\xe0\x08\x18\xfe\x15\x14\ -\xa5\x8c\xe1\x91\x31\xd6\x3f\xbc\x7a\x7d\x90\xc3\x23\x63\x54\x94\ -\xf2\xf1\x03\x82\xa1\x19\xf6\x0f\x78\xa9\x96\xb5\x03\x5b\x3c\x7c\ -\x70\x1f\xc5\xe2\x1f\xf4\x0f\x78\x19\x0c\xcd\x1c\x5c\x99\x24\x48\ -\x22\x18\x0a\xd3\xd3\xd3\xc7\xf4\xb7\xe5\x40\x55\xd3\x75\x1d\x95\ -\x4a\x05\xba\xae\x83\x24\xbe\x7c\xfd\x91\x74\xb8\x2f\x32\x18\x0a\ -\xb3\xda\x03\x92\x28\x29\x0a\x5c\x5d\xbd\xcc\x7c\x5f\xf6\x6d\x6c\ -\x6e\xa1\x50\x5c\x47\x2e\xbf\x86\x95\xec\x2a\x7e\xfe\xca\x63\x25\ -\xbb\x8a\x5c\x7e\x0d\x85\xe2\x3a\x92\x4b\x29\xba\xba\x7a\x59\x52\ -\x14\x90\x84\x04\x00\x53\xd3\x01\xb6\xb7\x9f\x85\xc9\x64\x7a\x59\ -\x28\x96\xb0\xbd\xb3\x2b\x37\x32\x4c\xdb\xdc\x82\xa9\xb5\x55\x58\ -\x2c\x16\x4e\x4d\x07\x78\xf7\xce\x6d\x21\x48\xe2\x9a\x77\x88\xfe\ -\x21\x1f\x4e\x9a\xcd\x47\xba\x5d\x8f\x1b\x37\xbd\x35\x1f\x24\x9b\ -\xdd\x09\x21\x04\xda\xda\xcc\x42\x92\x24\x34\x8b\x78\x2c\x22\x6c\ -\x76\x27\x0d\xf1\x58\x04\x24\xd1\xd2\x62\x80\xd1\x28\x35\xc5\xfa\ -\x1f\x32\x00\x80\x2c\xcb\xc8\xe6\x7e\xb3\xd9\x01\x4f\x1e\x3f\x82\ -\x2c\xcb\x88\xc7\x22\xc2\x00\x00\xd6\xf3\x1d\x98\x9d\x9b\xc3\x09\ -\xa3\xb1\x29\xbe\x09\xcf\xc2\x7a\xae\x63\x3f\x07\xaa\xaa\xc1\xd3\ -\xd3\xc7\xa5\x4f\x29\x6e\xef\xec\xe2\x38\x2e\x26\x3e\xd0\xdd\x7d\ -\x85\xaa\xaa\xed\xe7\x80\x24\xe6\x17\xa2\x74\x77\x5f\xe6\xe7\x54\ -\x9a\xba\xfe\x17\x8d\x98\x48\x7e\xa4\xcb\x73\x89\xf3\x0b\xd1\x5a\ -\x90\x44\xfd\x31\xbd\x7d\x17\xe5\xe8\x8b\x57\x38\x73\xfa\x14\xde\ -\x2f\x26\x6a\xfa\xf3\x67\x4f\xf1\x3a\x14\x46\x3a\x93\xc1\x3d\xbf\ -\x0f\x2e\xe7\x05\x71\x28\xca\x55\x6a\x9a\x86\xf1\x89\x49\x5a\x3b\ -\x1d\x24\x09\x6b\xa7\x83\xb7\x06\xfd\x1c\x9f\x98\xa4\xa6\x6d\x1c\ -\xea\x17\xff\x7b\xce\x7b\x69\x44\x5d\x3a\x10\x0a\xfe\xf7\x00\x00\ -\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x01\x99\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x18\x00\x00\x00\x18\x08\x03\x00\x00\x00\xd7\xa9\xcd\xca\ -\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xaf\xc8\x37\x05\x8a\xe9\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x00\x2a\x50\x4c\x54\x45\x00\x00\xff\ -\x00\x00\x00\xf6\xf6\xff\xfc\xfc\xfc\xc9\xc9\xc9\xcf\xcf\xcf\x00\ -\x00\xcf\xed\xed\xff\xf3\xf3\xf3\x00\x00\xed\xed\xed\xed\xf9\xf9\ -\xff\xe1\xe1\xff\xff\xff\xff\x46\x5f\x4b\x12\x00\x00\x00\x0e\x74\ -\x52\x4e\x53\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\x00\x45\xc0\xdc\xc8\x00\x00\x00\xdb\x49\x44\x41\x54\x78\xda\x62\ -\xe0\x45\x01\x8c\x8c\x30\x16\x40\x00\x31\xe0\x10\xe7\x05\x08\x20\ -\x34\x09\x04\x13\x20\x80\x70\x4a\x00\x04\x10\x03\x8a\x41\x8c\x08\ -\xb3\x00\x02\x88\x01\xd5\x0a\x84\x16\x80\x00\x82\x4a\x30\x40\x00\ -\x23\x42\x1d\x40\x00\x31\x40\xc5\x79\x91\x15\x80\x58\x00\x01\xc4\ -\x80\xc5\x09\x60\x1e\x40\x00\xe1\x94\x00\x08\x20\x06\x14\x93\x90\ -\x24\x00\x02\x88\x01\x53\x03\x44\x1d\x40\x00\x31\x60\x6a\x80\x28\ -\x04\x08\x20\x28\x06\x79\x00\xe1\x0b\x90\x20\x40\x00\x41\x30\x54\ -\x08\x2a\xcb\x01\x32\x02\x20\x80\x18\xc0\x26\x31\xa2\x86\x17\x13\ -\x03\x0f\x2f\x40\x00\x31\x00\xc5\xb9\x51\x6d\xe0\x62\x64\x01\xaa\ -\x05\x08\x20\xa0\x04\x23\x23\x2f\x7a\x6c\x31\x33\xf0\x02\x04\x10\ -\x03\x13\x86\x38\x2f\x33\x23\x2f\x03\x3b\x40\x00\xa1\x9a\x0f\x05\ -\xac\xbc\x9c\x0c\x00\x01\x84\x55\x02\x68\x18\x03\x40\x00\x31\xf0\ -\x62\x05\x8c\x6c\x00\x01\x84\x43\x82\x97\x11\x20\x80\x70\x49\xf0\ -\x02\x04\x18\x00\xf6\x56\x18\x9c\xa2\xaf\x44\xde\x00\x00\x00\x00\ -\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x02\x91\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x20\x00\x00\x00\x20\x08\x03\x00\x00\x00\x44\xa4\x8a\xc6\ -\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xaf\xc8\x37\x05\x8a\xe9\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x00\xb7\x50\x4c\x54\x45\xf6\xf6\xf6\ -\xfc\xfc\xfc\xfd\xfd\xfd\x5e\x5e\x5e\x4c\x4c\x4c\x9f\x9f\xff\xf0\ -\xf0\xf0\x62\x62\xff\xf8\xf8\xf8\x52\x52\x52\xb7\xb7\xff\xf7\xf7\ -\xff\x36\x36\x36\xf2\xf2\xf2\xf1\xf1\xf1\x4b\x4b\xf9\x35\x35\x35\ -\x73\x73\xff\x42\x42\x42\x41\x41\x41\x72\x72\xff\x4b\x4b\x4b\x40\ -\x40\x40\x71\x71\xf9\xf7\xf7\xf7\xeb\xeb\xeb\xd8\xd9\xff\x4d\x4c\ -\xff\x40\x40\x78\x85\x85\xff\xeb\xeb\xff\x33\x33\x33\x3f\x3f\x3f\ -\xc9\xc9\xff\x4f\x4f\x4f\x3a\x3a\x3a\x4e\x4e\x4e\xb8\xb8\xff\x3d\ -\x3e\xa6\x90\x90\xff\x93\x93\xff\x5a\x5a\x5a\xae\xae\xff\xa3\xa3\ -\xff\xf8\xf8\xff\xe7\xe7\xe7\xfc\xfc\xff\x3e\x3e\x3e\x38\x38\x38\ -\x59\x59\x59\x94\x95\xf5\x5c\x5c\x5c\xe7\xe7\xf5\xd4\xd4\xff\x63\ -\x63\x63\xf3\xf3\xf3\x7d\x7d\x7d\x65\x65\xff\x5b\x5b\x5b\x4a\x4a\ -\x56\xff\xff\xff\xd2\x5c\x7a\xeb\x00\x00\x00\x3d\x74\x52\x4e\x53\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x09\x2f\xac\ -\x5f\x00\x00\x01\x17\x49\x44\x41\x54\x78\xda\x62\xb0\x21\x00\x00\ -\x02\x88\x81\x90\x02\x80\x00\x22\xa8\x00\x20\x80\x08\x2a\x00\x08\ -\x20\x82\x0a\x00\x02\x88\xa0\x02\x80\x00\xc2\xa9\x40\x97\x19\x42\ -\x03\x04\x10\x2e\x05\x6c\xca\x66\x10\x06\x40\x00\xe1\x50\xc0\x26\ -\x0f\x35\xc0\x06\x20\x80\xb0\x2b\xe0\x13\x30\x86\x31\x01\x02\x08\ -\xab\x02\x5e\x1e\x2b\x38\x1b\x20\x80\xb0\x29\x30\xe7\xd1\x44\x70\ -\x00\x02\x08\x43\x01\xb7\x0d\x83\x81\x21\x12\x1f\x20\x80\xd0\x14\ -\xe8\x69\x88\x88\xcb\x30\x23\x8b\x00\x04\x10\x9a\x02\x2d\x76\x2e\ -\x56\x7e\x13\x64\x11\x80\x00\x42\x53\xa0\xae\x2d\x27\x25\xab\x88\ -\x2c\x02\x10\x40\x68\x0a\x8c\x2c\xb9\x58\xa5\x4d\x91\x45\x00\x02\ -\x08\x55\x01\x83\xb5\x9a\x20\xbb\x2a\x8a\x10\x40\x00\xa1\x28\x60\ -\xd0\xe7\x64\xe0\xd6\x41\x35\x13\x20\x80\x90\x15\x30\x28\x28\x31\ -\x61\x04\x0a\x40\x00\x21\x29\x90\x10\x53\xc1\x94\xb7\x01\x08\x20\ -\x84\x02\x0e\x61\x16\x2c\xf2\x36\x00\x01\x04\x57\xc0\x21\xc4\xc2\ -\x88\x2d\x5e\x00\x02\x08\xa6\x40\x52\x14\xbb\xbc\x0d\x40\x00\xc1\ -\x14\x58\x70\x62\x97\xb7\x01\x08\x20\x82\x69\x12\x20\x80\x08\x2a\ -\x00\x08\x20\x82\x0a\x00\x02\x88\xa0\x02\x80\x00\x22\xa8\x00\x20\ -\x80\x08\x2a\x00\x08\x20\x82\x0a\x00\x02\x0c\x00\x57\x57\xe4\x83\ -\xba\x2f\xe6\xf5\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\ -\x00\x00\x02\xc9\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x18\x00\x00\x00\x18\x08\x03\x00\x00\x00\xd7\xa9\xcd\xca\ -\x00\x00\x00\x03\x73\x42\x49\x54\x08\x08\x08\xdb\xe1\x4f\xe0\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0d\xd7\x00\x00\x0d\xd7\x01\ -\x42\x28\x9b\x78\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\ -\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\x70\ -\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x01\x29\x50\x4c\x54\ -\x45\xff\xff\xff\x80\x80\x80\x66\x99\x66\x5a\x87\x5a\x5d\x8b\x5d\ -\x58\x8d\x58\x58\x8f\x58\x5a\x8a\x5a\x57\x8f\x57\x5c\x8d\x5c\x58\ -\x8d\x58\x5a\x8c\x5a\x5a\x8e\x5a\x58\x8b\x58\x5b\x8d\x5b\x59\x8b\ -\x59\x58\x8d\x58\x5b\x8a\x5b\x59\x8c\x59\x5b\x8b\x5b\x59\x8c\x59\ -\x5b\x8c\x5b\x5b\x8c\x5b\x5a\x8d\x5a\x59\x8c\x59\x5b\x8d\x5b\x5c\ -\x8e\x5c\x5c\x8e\x5c\x5c\x8e\x5c\x5d\x8f\x5d\x5c\x8f\x5c\x5d\x8f\ -\x5d\x5e\x8f\x5e\x5c\x8e\x5c\x5d\x8f\x5d\x5c\x8f\x5c\x5d\x8f\x5d\ -\x5c\x8f\x5c\x5c\x8e\x5c\x5b\x8d\x5b\x5c\x8e\x5c\x5c\x8e\x5c\x5c\ -\x8f\x5c\x5d\x8e\x5d\x64\x97\x64\x5c\x8f\x5c\x5e\x91\x5e\x66\x95\ -\x66\x5e\x8f\x5e\x60\x92\x60\x69\x96\x69\x69\x9b\x69\x6e\xa0\x6e\ -\x61\x93\x61\x65\x97\x65\x68\x96\x68\x5c\x8e\x5c\x7a\xa7\x7a\x5f\ -\x91\x5f\x7a\xaa\x7b\x72\xa4\x72\x5a\x8c\x5a\x86\xb3\x87\x8a\xb5\ -\x8b\x5a\x8d\x5a\x5a\x8c\x5a\x5c\x8d\x5c\x71\x9d\x72\x76\xa8\x76\ -\x7b\xad\x7b\x7c\xae\x7c\x7d\xaf\x7d\x7e\xb0\x7e\x7f\xb1\x7f\x81\ -\xb3\x81\x84\xb6\x84\x85\xb4\x85\x87\xb9\x87\x88\xba\x88\x8a\xbc\ -\x8a\x8c\xbe\x8c\x90\xad\x90\x91\xaa\x91\x91\xc0\x92\x93\xc2\x93\ -\x97\xc1\x98\x97\xc4\x97\x99\xc6\x9a\xa3\xcb\xa4\xa8\xce\xa8\xa9\ -\xcf\xaa\xaf\xd2\xb0\xb0\xd3\xb1\xb3\xd1\xb5\xb3\xd4\xb4\xb4\xd6\ -\xb6\xb5\xd3\xb7\xb6\xd3\xb7\xb7\xd4\xb8\x04\xb7\xa8\x69\x00\x00\ -\x00\x41\x74\x52\x4e\x53\x00\x02\x05\x11\x16\x1d\x20\x25\x29\x2f\ -\x31\x33\x36\x37\x38\x39\x3a\x3b\x3c\x51\x59\x5f\x68\x69\x6d\x86\ -\x8e\x9e\xb9\xbf\xc1\xc2\xc4\xc7\xdc\xe3\xe4\xe8\xef\xf0\xf0\xf1\ -\xf1\xf2\xf2\xf3\xf3\xf3\xf4\xf4\xf4\xf4\xf4\xf5\xf5\xf5\xf6\xf6\ -\xf7\xf9\xfa\xfb\xfc\xfc\xfe\x65\x15\xb1\xed\x00\x00\x00\xc5\x49\ -\x44\x41\x54\x28\xcf\x63\x60\x20\x1b\x08\xc9\x60\x17\x17\xd4\x77\ -\xc2\x2a\x2e\x60\x94\xe4\x8c\x4d\x9c\xdf\x28\x31\xc2\x11\x8b\x38\ -\x9f\x79\x42\x44\x80\x95\x23\x18\xd8\xca\x8b\xc2\xc5\x79\x41\xe2\ -\x30\xe0\xef\x6a\x2a\x05\x15\x67\x37\x88\x45\x88\x03\x81\x9f\xa1\ -\x30\x44\x82\x4d\xc3\x27\x00\x05\xb8\x48\x43\xb5\x70\x6a\xba\x03\ -\xb9\x81\x41\x40\x10\x18\x1d\x1e\x10\xe0\xa5\x04\xb3\x84\x4b\xcb\ -\x23\x00\x6c\xb9\x83\x9e\x75\x5c\x58\x80\xbf\x2d\xdc\x7a\x6e\x6d\ -\x8f\x00\x90\x73\x99\x38\xe4\xec\xa3\x02\xbc\x55\x11\x0e\xe6\xd1\ -\xf6\x84\xf8\x43\xc2\x32\x26\xc0\x4d\x01\xd9\x2b\xba\x60\x09\x56\ -\xb5\xd0\xc8\x00\x1d\x11\x94\xc0\x02\x06\x22\xb3\x98\x8a\x5d\x7c\ -\x88\x8d\x12\x23\x7a\x08\xa8\x1b\xbb\xc7\x04\x9b\x29\xb2\x60\x04\ -\x8d\xa3\xaf\x9b\x89\x85\x24\x23\x66\x98\x29\x6b\xca\x8a\xb3\x31\ -\x0c\x02\x00\x00\xb1\xe8\x2a\xb8\x7c\xb7\x13\xb6\x00\x00\x00\x00\ -\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x01\x40\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0\x77\x3d\xf8\ -\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xaf\xc8\x37\x05\x8a\xe9\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x00\xd2\x49\x44\x41\x54\x78\xda\x62\ -\xfc\xff\xff\x3f\x03\x2d\x01\x40\x00\x31\x31\xd0\x18\x00\x04\x10\ -\xcd\x2d\x00\x08\x20\xa2\x2d\x60\x64\x64\x84\x87\xa5\x09\x23\x03\ -\xd1\xe1\x0a\x10\x40\x34\xf7\x01\x40\x00\x31\x91\xea\x7a\x52\x7d\ -\x01\x10\x40\x34\xf7\x01\x40\x00\x31\x91\xe3\x7a\x52\x7c\x01\x10\ -\x40\x44\xf9\x00\x98\x57\x18\xd1\xc5\xce\xfc\x67\x60\x24\x46\x2f\ -\x40\x00\x31\x91\xeb\x7a\x62\x7d\x01\x10\x40\x4c\xe4\xb8\x9e\x14\ -\x5f\x00\x04\x10\x13\x25\xae\x27\xc6\x17\x00\x01\xc4\x44\xae\xeb\ -\x89\xf5\x05\x40\x00\x31\x51\xea\x7a\x42\xbe\x00\x08\x20\x26\x4a\ -\x5c\x4f\x8c\x2f\x00\x02\x88\x89\x1a\xae\xc7\xe7\x0b\x80\x00\x62\ -\xa2\xd4\xf5\x84\x7c\x01\x10\x40\x4c\xd4\x72\x3d\x2e\x5f\x00\x04\ -\x10\x13\x35\x5c\x8f\xcf\x17\x00\x01\x44\xf3\xc2\x0e\x20\x80\x58\ -\xc8\x8d\x64\x62\x8b\x6b\x80\x00\x62\xa4\x75\xa5\x0f\x10\x40\x34\ -\x0f\x22\x80\x00\xa2\xb9\x05\x00\x01\x06\x00\xb1\x51\x36\xcd\x1a\ -\xf7\x80\x62\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x01\xaf\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x09\x12\ -\x0c\x28\x18\x54\xcb\xe5\x02\x00\x00\x01\x2f\x49\x44\x41\x54\x38\ -\xcb\x63\xfc\xff\xff\x3f\x03\xa9\xc0\xd6\xd1\x1d\xae\x89\x85\x58\ -\x4d\x2f\x5e\xbc\xfc\x7f\xf8\xe8\x31\x86\xf3\x17\x2e\x31\x30\x30\ -\x30\x30\x1c\xde\xbf\x93\xd1\xd6\xd1\xfd\x3f\x23\x21\x17\xdc\xbe\ -\x73\xf7\xff\xfc\x85\x4b\x18\x4e\x9f\x39\xc7\xa0\xa2\xac\xc4\x60\ -\x6c\x64\xc0\xb0\x70\xf1\x32\x84\x82\xff\xff\xff\x63\xc5\x5f\xbe\ -\x7c\x61\x98\x34\x65\xfa\x7f\x07\x17\xaf\xff\xdd\xbd\x13\xff\xbf\ -\x7a\xfd\xfa\x0c\x36\x75\x58\xbd\xf0\xe5\xcb\x17\x86\xfc\xa2\xf2\ -\xff\xa2\xa2\x22\x0c\x2b\x96\xce\x67\x10\x17\x13\x63\xc4\xe9\x44\ -\x74\x13\x3f\x7f\xfe\xcc\x90\x9a\x91\xf3\x7f\xca\xb4\x99\xff\xbf\ -\x7e\xfd\x86\x21\x6f\x6c\x6c\xfc\x1f\x99\x8f\xa1\x39\x29\x35\xeb\ -\x7f\xff\xa4\xa9\xff\xb1\x39\xd7\xd8\xd8\xf8\xff\xea\xf5\xdb\x70\ -\x1b\x30\x69\xca\xf4\xff\xb9\x05\x25\xff\xbf\x7c\xfd\x8a\xd7\x66\ -\x64\x43\xe0\x0a\x6e\xdd\xbe\xf3\xdf\xcd\xcb\x1f\x6b\x60\xa1\x3b\ -\x1b\xab\x01\xd5\xb5\x8d\xff\xfb\x26\x4c\xf9\x4f\x48\x33\xba\x21\ -\xf0\x74\x70\xe9\xf2\x95\xff\xea\x6a\xaa\x8c\xec\xec\xec\xf0\x00\ -\x36\x31\x31\xf9\x5f\x51\xd3\x8c\x33\x02\x42\x02\x3c\x19\xe1\x26\ -\xda\x38\xb8\xfd\xc7\x95\x26\xf0\x61\x26\x06\x0a\x01\xa3\x8d\x83\ -\x1b\x46\x5a\x3e\xbc\x7f\x27\x23\xb1\x99\x89\x2c\x2f\xc0\xd4\xda\ -\x38\xb8\xfd\x27\xdb\x0b\x30\x57\x00\x00\x3b\xe8\xa6\x6e\xf8\xeb\ -\xa8\x8a\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x01\x6e\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x18\x00\x00\x00\x18\x08\x03\x00\x00\x00\xd7\xa9\xcd\xca\ -\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xaf\xc8\x37\x05\x8a\xe9\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x00\x0f\x50\x4c\x54\x45\x16\x16\x14\ -\x00\x00\x00\x00\x00\xff\xff\x00\x00\xff\xff\xff\xb5\x14\xd0\x48\ -\x00\x00\x00\x05\x74\x52\x4e\x53\xff\xff\xff\xff\x00\xfb\xb6\x0e\ -\x53\x00\x00\x00\xd4\x49\x44\x41\x54\x78\xda\x62\x60\x41\x02\xcc\ -\x70\x82\x85\x05\x20\x80\x18\xe0\x04\x54\x10\x2a\xce\x02\x10\x40\ -\x20\x31\x06\x06\x84\x0c\x4c\x9c\x05\x20\x80\x50\x75\x20\x49\x00\ -\x04\x10\x9a\x51\x8c\x70\xa3\x00\x02\x08\x45\x82\x99\x85\x11\x6e\ -\x09\x40\x00\x21\x49\x30\x32\x32\x82\x24\xa0\x00\x20\x80\x90\x25\ -\xa0\x18\x02\x00\x02\x08\x2a\xc1\xc8\xc8\x82\x06\x00\x02\x08\xea\ -\x5c\x0c\x71\x16\x80\x00\x82\xe8\x60\xc2\x10\x67\x01\x08\x20\x88\ -\xf1\x4c\x30\x19\x84\x4e\x80\x00\x82\xba\x14\x26\x83\x90\x00\x08\ -\x20\x98\x17\xa0\x32\x08\x09\x80\x00\x82\x7b\x1a\x22\x83\x90\x00\ -\x08\x20\x44\x30\x31\xa1\xba\x00\x20\x80\x10\x12\x68\x32\x00\x01\ -\x84\x24\x01\x92\x41\x18\x05\x10\x40\xc8\x12\x40\x19\x84\x04\x40\ -\x00\xa1\x48\xb0\x20\x49\x00\x04\x10\xaa\x04\xcc\x28\xa0\x28\x40\ -\x00\xa1\x4a\x30\xc0\x01\x0b\x40\x00\x31\x60\x0b\x21\x10\x05\x10\ -\x40\x0c\x2c\x38\x00\x40\x80\x01\x00\x9e\x0c\x08\x2c\x71\x90\x09\ -\xe0\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x01\x93\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0\x77\x3d\xf8\ -\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xaf\xc8\x37\x05\x8a\xe9\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x01\x25\x49\x44\x41\x54\x78\xda\x62\ -\xfc\xff\xff\x3f\x03\x2d\x01\x40\x00\x31\xd2\xda\x02\x80\x00\x62\ -\xa2\xd4\x00\x39\x39\x19\xb0\x19\x8c\x8c\x8c\x58\x5d\x0a\x10\x40\ -\x0c\x20\x1f\xc0\xb0\x98\x98\xc8\x7f\x64\x3e\xb1\x18\x08\xfe\xc3\ -\x30\xba\x1c\x40\x00\xc1\x7d\x20\x2e\x2e\xfa\x1f\x99\x26\xc2\xe5\ -\x8c\x30\x97\x03\x01\x23\x3c\xcc\xd1\x7c\x02\x10\x40\x14\xf9\x00\ -\xe6\x62\x64\x1a\xdd\x27\x00\x01\x44\x76\x1c\xc0\x5c\x0e\x73\x31\ -\xb2\x4f\x90\x7d\x04\x10\x40\x4c\xd4\x30\x1c\x66\x28\x7a\x70\x81\ -\x00\x40\x00\x31\x51\x6a\x38\x30\x58\x71\x1a\x0e\x02\x00\x01\xc4\ -\x44\xa9\xe1\x2f\x5f\xbe\xc6\x69\x38\x08\x00\x04\x10\x13\x29\x86\ -\x23\xd3\xc4\x18\x0e\x02\x00\x01\x04\xcf\xc9\xc8\xc9\x13\xa4\x11\ -\x9f\x25\xf8\xc2\x1c\x1d\x00\x04\x10\x13\x36\x43\xd1\xf3\x02\x88\ -\x8f\x2d\xbd\x13\x32\x1c\x04\x00\x02\x08\x6b\x59\x04\xb3\x00\x66\ -\x29\x39\x2e\x87\x01\x80\x00\xc2\x59\xd8\x21\xfb\xe2\xd5\xab\x37\ -\x0c\xc8\xe1\x4e\x4a\xc2\x00\x08\x20\xbc\xa5\x29\x31\xf1\x42\x08\ -\x00\x04\x10\xc1\xe2\x80\xdc\x02\x10\x86\x01\x02\x88\x89\x81\xc6\ -\x00\x20\x80\x68\x6e\x01\x40\x00\x51\xc5\x02\x7c\x45\x3c\x40\x00\ -\x11\x1d\xc9\xc4\x00\x6c\x09\x01\x20\x80\xa8\x12\xc9\xf8\xd4\x00\ -\x04\x10\xcd\x2b\x7d\x80\x00\xa2\xb9\x05\x00\x01\x06\x00\x87\x21\ -\x99\x4f\x5f\x76\x4f\x3d\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\ -\x60\x82\ -\x00\x00\x01\xaf\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x09\x12\ -\x0c\x20\x39\xd0\x7b\x7f\x54\x00\x00\x01\x2f\x49\x44\x41\x54\x38\ -\xcb\x63\xfc\xff\xff\x3f\x03\xa9\xc0\xd6\xd1\x1d\xae\x89\x89\x14\ -\x8d\x47\x8e\x1e\xff\x3f\x79\xda\xcc\xff\x0c\x0c\x0c\x0c\x87\xf7\ -\xef\x64\x64\x60\x60\x60\x60\x21\xa4\xe9\xcb\x97\x2f\x0c\xab\xd7\ -\x6e\xf8\xbf\x61\xe3\x16\x06\x3e\x3e\x5e\x06\x7d\x3d\x5d\x54\x57\ -\xfc\xff\xff\x1f\x27\x3e\x74\xf8\xe8\x7f\xbf\xc0\xf0\xff\x85\x25\ -\x15\xff\x2f\x5f\xb9\xf6\x1f\x9b\x1a\x9c\x9a\xdb\x3a\x7b\xfe\x7b\ -\xfb\x87\xfe\x3f\x72\xf4\xf8\x7f\x7c\x96\xe0\xd4\x9c\x9d\x57\xfc\ -\xff\xd1\xe3\xc7\x28\x9a\x8d\x8d\x8d\xff\x4f\x9a\xb1\xe0\x3f\x5e\ -\x03\xda\x3a\x7b\xfe\x47\xc6\x24\xfd\x7f\xfd\xe6\xcd\x2a\x74\xcd\ -\xff\xff\xff\x67\xc0\x6b\xc0\xe1\x23\xc7\xfe\x87\x45\xc6\xff\x7f\ -\xfe\xe2\xe5\x7f\x6c\x9a\x61\x18\xd9\x10\xb8\xe0\xe7\xcf\x9f\x19\ -\xbc\xfd\x43\xff\x1f\x3e\x7a\x0c\xaf\x66\x74\x03\x18\x61\x09\x69\ -\xfe\xc2\x25\xff\xaf\x5f\xbf\xc9\xd0\xd6\x52\xcf\xc8\xc2\x02\x89\ -\x5d\x13\x13\x93\xff\xf1\xa9\xb9\x38\xa3\x38\x37\x3d\x9e\x11\x6e\ -\xaa\x97\x6f\xf0\xff\x6b\xd7\x6f\xe2\x0d\x71\x6c\x98\x09\x96\xc2\ -\xd8\x39\xd8\x19\xd4\xd5\x55\x19\xc9\x4a\xca\xe7\x2f\x5e\x62\x30\ -\x37\x33\x65\x60\x62\x64\x24\x39\x5f\x30\xda\x38\xb8\xc1\x4d\x83\ -\xa5\x6f\x62\x6c\x86\x03\x1b\x07\xb7\xff\xff\xff\xff\x87\xd3\xc4\ -\x60\x64\xb5\x4c\x38\x4d\x26\x12\x00\x00\xac\x16\xa5\xa1\xce\xc9\ -\xa2\xf1\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x03\x09\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0\x77\x3d\xf8\ -\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xaf\xc8\x37\x05\x8a\xe9\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x02\x9b\x49\x44\x41\x54\x78\xda\x62\ -\xf8\xcf\xc0\xf0\x09\x88\xbf\x00\xb1\xec\xff\xff\xff\x19\xa8\x82\ -\x19\x18\xf4\x80\xf8\x27\x10\xbf\x06\x08\x20\x10\xe7\x37\x10\xff\ -\xa7\x9a\x25\x10\xc3\x7f\x40\xcd\xfc\x09\x10\x40\x20\x01\x0b\xaa\ -\x59\x82\x66\x38\x88\x0f\x10\x40\x30\x09\x8b\xdf\x0c\x0c\xff\x40\ -\x12\x5f\x81\x58\x16\x64\x33\xf1\xf8\x04\x16\xc3\x7f\x80\xf9\x40\ -\x16\x40\x00\xc1\x6d\xb7\x60\x60\xf8\x8b\xe4\x93\xaf\xc4\xfa\x04\ -\x08\x3e\xe3\x32\x1c\x84\x01\x02\x08\x59\xe1\x5f\xb4\xe0\x22\xca\ -\x12\x03\x88\x3a\xac\x86\x83\x30\x40\x00\xa1\x5a\x00\xf1\xaa\x31\ -\xd1\x96\x00\x0d\xfb\x01\x51\x87\xd5\x70\x10\x06\x08\x20\x4c\x0b\ -\x88\xb5\x04\x4f\xb0\x20\x63\x80\x00\xc2\x6e\x01\x76\x4b\x94\x81\ -\x6a\x52\x80\xf8\x0a\x30\x58\x3e\xfd\x84\xba\x1c\xea\x03\x3d\x5c\ -\xbe\x04\x08\x20\xdc\x16\x20\x2c\xf9\x05\x32\xe8\x1b\x30\x95\xd5\ -\x45\x45\xbd\x3f\xdf\xd5\xb5\xea\x3f\x23\x23\x28\x09\xfe\xff\xc7\ -\xcc\xfc\xa7\x31\x38\xf8\x1f\x1f\x1f\xdf\x47\x06\x90\x5a\x2c\x16\ -\x00\x04\x10\x7e\x0b\x80\x38\x84\x81\x61\xf6\x2f\x46\x46\x58\x38\ -\x7f\x83\xa6\x6f\x78\xb0\x80\x52\xd1\xd5\xab\x57\xb3\x54\x54\x54\ -\xfe\x02\xd9\x4d\xe8\xfa\x01\x02\x08\xaf\x05\x20\x57\xb1\xb3\xb3\ -\xff\x7f\xd0\xde\x3e\x09\xe6\x13\xe4\x4c\x04\x4f\xa6\x40\xfa\xc6\ -\x8d\x1b\x29\xbc\xbc\xbc\xff\xd1\x7d\x02\x10\x40\xc8\x86\x3d\xc6\ -\x92\x89\xfe\x27\x25\x25\xfd\xd7\x02\xb2\x7f\x22\x0c\xff\xff\x1d\ -\x88\x95\xd1\x33\x1a\x10\x57\x57\x57\x83\xcc\x78\x85\x6c\x01\x40\ -\x00\xe1\xcb\x40\x9c\x20\x0b\x2e\x4d\x9c\x38\x1f\x2d\xfb\xff\x42\ -\x0a\x2e\x65\x64\x3d\x97\x2e\x5d\x2a\x04\xe9\x01\xe9\x85\x89\x01\ -\x04\x10\x3e\x0b\xc2\x35\x40\x06\x41\x23\x14\x6a\x89\x06\x52\x49\ -\x89\x61\xc9\x9f\x3f\x7f\x38\xd9\xd8\xd8\x40\x9a\xe3\x60\x62\x00\ -\x01\xc4\xc4\x80\x03\x04\x31\x30\xa8\x5d\x00\x31\xfe\xff\x67\x83\ -\x06\x85\x01\x90\x7d\x03\x88\x2f\x01\xd9\xa6\x40\xfc\x0b\xea\xcb\ -\xcb\x0c\x8c\x8c\xa0\x24\xcc\xc0\xcc\xcc\xfc\x1d\xaa\x9d\x0d\x66\ -\x0e\x40\x00\xe1\xca\xa1\x1a\xff\xa0\xae\x04\x26\xc5\xdf\x60\x97\ -\xe3\x2e\xf3\xe1\x3e\xb9\x77\xef\x5e\x38\x34\x88\x94\x60\xea\x00\ -\x02\x08\xab\xe1\xb0\x30\x07\x65\xa2\x9e\x84\x84\x57\x44\x16\xcf\ -\xdf\xa6\xe4\xe5\x3d\x62\x00\x65\x4e\x24\x35\x00\x01\x84\xd3\x70\ -\x10\x6d\xc5\xc0\xb0\x81\x93\x93\xf3\x3f\x28\x09\x12\x63\xc9\x77\ -\x60\x7e\x01\xe9\x41\x96\x07\x08\x20\x74\xc3\xbf\xa3\x44\x28\x24\ -\xb2\xdf\x28\x2a\x2a\xfe\xbb\x7d\xfb\x76\x1c\x2e\x4b\xde\xb5\xb6\ -\xe6\xff\x44\xcd\x8c\xf0\x20\x05\x08\x20\xbc\x86\x23\xa5\xa8\x8b\ -\xa0\x4c\x54\x51\x51\xf1\x14\x14\xce\x30\xf1\x87\x0f\x1f\xfa\x37\ -\x36\x36\xde\x13\x10\x10\xf8\x0f\x2c\x9f\x6e\x20\x07\x17\xcc\x0c\ -\x80\x00\x22\x68\x38\x72\xb2\x05\xe2\x0f\xd0\x48\x44\xc6\x20\xb1\ -\x70\x2c\x41\x0c\xb6\x04\x20\x80\x18\x88\x31\x9c\xc4\x7a\x19\xc5\ -\x12\x80\x00\x02\x09\x5c\x83\x5a\xa2\x41\xc5\x66\x0b\x2c\x54\xbe\ -\x03\x04\x18\x00\x36\xab\xec\xed\x45\x2b\x85\xb6\x00\x00\x00\x00\ -\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x01\x91\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0\x77\x3d\xf8\ -\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xaf\xc8\x37\x05\x8a\xe9\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x01\x23\x49\x44\x41\x54\x78\xda\x62\ -\xfc\xff\xff\x3f\x03\x2d\x01\x40\x00\x31\x31\xd0\x18\x00\x04\x10\ -\x4d\x2c\x60\x64\x64\x84\x07\x0b\x40\x00\x31\xc2\x82\x08\x59\x90\ -\x5a\x00\x68\x36\x23\x40\x00\xb1\xa0\x0b\x50\xd1\x07\x02\x20\x36\ -\x40\x00\x31\x51\x2b\x28\xb0\xb8\xfe\x23\x88\x06\x08\x20\x26\x4a\ -\x0d\x27\x14\xb4\x00\x01\xc4\x44\xae\xe1\xc8\xc1\x89\xcd\x12\xa0\ -\x18\x3f\x88\x06\x08\x20\x26\x72\x0d\x27\x64\x09\x2c\x88\x00\x02\ -\x88\x89\x5c\xc3\xd1\x7d\x82\x2b\x81\x00\x04\x10\x13\xb9\x86\xc3\ -\x0c\x45\xf7\x09\x3a\x00\x08\x20\x26\x72\x0c\x17\x13\x13\x21\x68\ -\x38\x2c\x0e\x00\x02\x08\xa4\x90\x01\x9a\xd9\xfe\xc3\xd8\xc8\x18\ -\x26\x0e\xa2\x41\x18\x68\xf8\x7f\x7c\xea\xd1\xe5\x00\x02\x08\xaf\ -\x05\x30\x43\x49\x31\x1c\x84\xf9\xf9\xf9\x41\x8a\xf8\x41\x6c\x80\ -\x00\x62\x22\x94\xd5\x91\xf9\x2f\x5f\xbe\x26\x18\xe6\xe8\x00\x20\ -\x80\x70\xfa\x00\xe4\x5a\xf4\xe0\x21\xe4\x72\x6c\x66\x01\x04\x10\ -\x4e\x0b\xd0\x0d\x25\xd6\x70\xf4\x20\x02\x08\x20\x82\x16\xc0\xc2\ -\x9d\x14\x8c\x6c\x16\x40\x00\xb1\x10\x1b\xfe\xa4\x00\xa0\x0f\xc0\ -\xc9\x14\x94\x9b\x01\x02\x88\xe6\x35\x1a\x40\x00\xb1\x90\x52\x32\ -\x92\x03\x00\x02\x08\xa5\x46\xa3\x56\x85\x83\x0c\x00\x02\x88\x91\ -\xd6\xad\x0a\x80\x00\xa2\x79\x1c\x00\x04\x18\x00\x6e\xaa\x89\xcb\ -\xfd\xf1\xd9\x23\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\ -\x00\x00\x04\x84\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x20\x00\x00\x00\x20\x08\x03\x00\x00\x00\x44\xa4\x8a\xc6\ -\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xaf\xc8\x37\x05\x8a\xe9\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x01\xaa\x50\x4c\x54\x45\xec\xec\xec\ -\x32\x32\x32\x6d\x6d\x6d\x33\x33\x33\x2c\x2c\x2c\x00\x00\x00\x3c\ -\x3c\x3c\xc3\xc3\xc3\xf6\xf6\xf6\x77\x77\x77\x47\x47\x47\xbf\xbf\ -\xbf\x8a\x8a\x8a\x73\x73\x73\xa2\xa2\xa2\x78\x78\x78\x48\x48\x48\ -\x3b\x3b\x3b\x49\x49\x49\x2b\x2b\x2b\xd9\xd9\xd9\x81\x81\x81\x7b\ -\x7b\x7b\xda\xda\xda\xdb\xdb\xdb\x68\x68\x68\xc8\xc8\xc8\x67\x67\ -\x67\x22\x22\x22\xaf\xaf\xaf\x6e\x6e\x6e\x6a\x6a\x6a\x4b\x4b\x4b\ -\xf3\xf3\xf3\x37\x37\x37\x94\x94\x94\x29\x29\x29\x3d\x3d\x3d\xb1\ -\xb1\xf6\x2a\x2a\x2a\x9b\x9b\x9b\xb8\xb9\xff\xf2\xf2\xff\xa7\xa7\ -\xa7\x57\x57\x57\xa5\xa5\xa5\xdb\xda\xff\x5f\x5f\x5f\xc4\xc4\xc4\ -\x88\x88\xff\x79\x79\x79\xd7\xd7\xd7\x36\x36\x36\xca\xcb\xff\x1e\ -\x1e\x1e\x75\x75\xff\x5d\x5d\xd5\xf7\xf7\xf7\x66\x66\xfe\x67\x67\ -\x77\x5a\x5a\x5a\x54\x54\xff\xc8\xc8\xff\xe0\xe0\xe0\xc1\xc1\xc1\ -\xce\xce\xce\x5a\x5a\xff\x2e\x2e\x2e\xf5\xf5\xf5\x82\x82\xff\x42\ -\x42\x42\x1c\x1c\x64\x13\x13\x13\x18\x18\x18\x35\x35\x35\x5b\x5b\ -\x5b\x43\x43\x43\x7f\x7f\xff\x78\x78\xff\xfd\xfd\xfd\xb0\xb0\xb0\ -\xb4\xb4\xb4\xe9\xe9\xe9\x26\x26\x26\x64\x64\xff\xe5\xe5\xe5\xe4\ -\xe4\xe4\x41\x41\x41\x6c\x6c\x6c\xf8\xf8\xf8\xc9\xc9\xc9\x2d\x2d\ -\x2d\x25\x25\x25\x58\x58\x58\xf2\xf2\xf2\x44\x44\x44\x52\x52\x89\ -\xc5\xc5\xc5\x8b\x8b\xff\xe3\xe3\xff\xe8\xe8\xff\xbc\xbc\xbc\x71\ -\x71\x71\xd2\xd2\xff\x94\x94\xff\xa1\xa1\xff\xde\xde\xde\x11\x11\ -\x11\xbe\xbe\xbe\x40\x40\x40\x4a\x4a\xff\xfc\xfc\xfc\x8b\x8b\x8b\ -\x63\x63\x63\xf9\xf9\xf9\xe8\xe8\xe8\x50\x50\x50\xb8\xb8\xb8\xf1\ -\xf1\xf1\x75\x75\x75\x3a\x3a\x3a\x79\x79\xfb\x72\x72\x72\x52\x52\ -\x52\xe9\xe9\xff\xf0\xf0\xf0\xb1\xb1\xb1\xd0\xd0\xff\x85\x85\x85\ -\x7e\x7e\x7e\x99\x99\x99\xc6\xc6\xc6\xc0\xc0\xc0\xb5\xb5\xb5\x7f\ -\x7f\x7f\x65\x65\x65\xb4\xb3\xff\x31\x31\x31\x61\x61\x61\xd5\xd5\ -\xd5\xa6\xa6\xff\xff\xff\xff\x9e\x29\xbf\xe8\x00\x00\x00\x8e\x74\ -\x52\x4e\x53\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\x00\x9b\x9e\xf4\x7a\x00\x00\x01\xc6\x49\x44\x41\x54\x78\xda\x62\ -\xe8\x25\x00\x00\x02\x88\x01\x53\x88\x43\x36\xb7\x1c\xc1\x03\x08\ -\x20\x24\x05\xa6\xae\xbe\x1d\xbd\x96\x2d\x9c\x82\x5e\x92\x89\x08\ -\x51\x80\x00\x42\x28\x48\xce\xcb\xe8\xb1\x4d\xf0\x64\x65\x65\xea\ -\x46\x36\x0f\x20\x80\x10\x0a\xd4\x92\xf4\x52\x32\x2d\x98\x78\xf9\ -\x39\xc3\x85\x34\x38\xe0\xc2\x00\x01\x04\x55\x10\xc6\x67\xe3\x6e\ -\xa5\x69\xe7\x67\x08\xe2\x44\xa6\x7a\xb3\xd5\xc1\x14\x00\x04\x10\ -\x48\x81\xbd\xb2\x80\xb0\x30\xab\x75\x65\x88\xb9\x53\x0d\x54\xd8\ -\x91\x8d\x17\xca\x02\x08\x20\xa0\x82\x6a\x99\x78\x65\x29\x16\xb1\ -\xde\xde\xfa\x74\x2d\xb8\xc9\x65\x4c\x72\x10\x06\x40\x00\x01\x15\ -\xb0\x67\xf5\xf6\xfa\x54\x61\xf8\x36\x56\x14\x4c\x01\x04\x10\xc4\ -\x0d\xfc\x25\x58\x42\xa8\x42\x0a\x44\x01\x04\x10\x58\x81\x84\x4a\ -\x10\x96\x30\x0c\xe0\x02\x91\x00\x01\x04\x56\xc0\xd4\x88\x35\x94\ -\xdd\xd8\x81\x04\x40\x00\x81\x14\xb8\x30\x8a\x60\x55\x50\x20\x0f\ -\x24\x00\x02\x08\xa4\x20\xaa\x13\x7b\x3c\x89\x83\xec\x00\x08\x20\ -\x90\x82\x26\x69\x1c\x31\x29\x04\xc4\x00\x01\x04\x52\x20\x2a\x8f\ -\x43\x81\x12\x10\x03\x04\x10\x48\x81\x18\x2e\x13\x98\x81\x18\x20\ -\x80\x40\x0a\x1a\xe4\x70\x28\x10\x04\x62\x80\x00\x02\x29\xd0\x56\ -\xc5\x2e\x5f\xa4\x00\x24\x00\x02\x08\xa4\xc0\x58\xc5\x1f\xab\x02\ -\x03\x90\x2f\x00\x02\x08\xa4\x20\xdf\x44\x16\xab\x02\xde\x36\x20\ -\x01\x10\x40\xe0\x90\xe4\x51\xc0\x26\xcf\xc1\x2c\x01\x24\x01\x02\ -\x08\xac\x40\xd1\x2c\x10\x8b\x02\xce\x76\x10\x09\x10\x40\x90\xd8\ -\xd4\x65\x89\xc3\x90\x67\x97\x29\x06\x51\x00\x01\x04\x4d\x72\x92\ -\x8c\x8a\x68\xf2\xcd\xd9\xa5\x60\x1a\x20\x80\x60\x89\x56\x87\x19\ -\x35\x87\x38\x78\xf0\x41\x18\x00\x01\x04\x17\x36\x0a\xe6\xa9\x45\ -\x24\x16\x7d\xf5\x56\x28\x13\x20\x80\x10\xfa\xb8\xd9\x62\xd2\xb8\ -\x41\x5c\xf1\x9c\x2e\xe7\xc2\x50\x98\x30\x40\x00\x21\x1b\xcc\x1d\ -\xc1\xc8\xcc\x18\xad\xc4\x22\xc0\x83\x94\x3e\x00\x02\x88\x81\x50\ -\xe6\x05\x08\x20\x82\x0a\x00\x02\x88\xa0\x02\x80\x00\x03\x00\x3d\ -\x27\xf1\xa5\x1e\x6b\xd7\x3e\x00\x00\x00\x00\x49\x45\x4e\x44\xae\ -\x42\x60\x82\ -\x00\x00\x03\xc9\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x20\x00\x00\x00\x20\x08\x03\x00\x00\x00\x44\xa4\x8a\xc6\ -\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xaf\xc8\x37\x05\x8a\xe9\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x01\x62\x50\x4c\x54\x45\x8a\x8a\x8a\ -\x2f\x2f\x2f\xd3\xd3\xd3\xef\xef\xff\x25\x25\x25\x52\x52\x52\xe9\ -\xe9\xff\xcc\xcc\xcc\xfb\xfb\xff\x85\x85\xff\x86\x86\x86\x95\x95\ -\x95\xce\xce\xff\xae\xae\xff\x33\x33\x33\xa5\xa5\xa5\x35\x35\x35\ -\x79\x79\xff\x69\x69\x69\x29\x29\x29\x8f\x8f\x8f\x6c\x6c\x6c\x81\ -\x82\xff\x61\x60\xff\x40\x40\x40\x3e\x3e\x3e\x4e\x4e\xff\x5d\x5d\ -\x5d\xa9\xa9\xa9\x63\x63\xff\xc8\xc8\xff\xc0\xc0\xc0\x6e\x6e\x6e\ -\xfa\xfa\xfa\x54\x54\x54\xa0\xa0\xff\xd6\xd6\xff\xbb\xbb\xbb\xdd\ -\xdd\xdd\x2a\x2a\x2a\x26\x26\x26\x5d\x5d\xff\xb1\xb1\xff\xb6\xb6\ -\xff\x74\x74\xff\xb8\xb8\xb8\x6b\x6a\xff\x9c\x9c\xa8\x7e\x7e\x7e\ -\x43\x43\x43\x49\x49\x49\x40\x40\x54\xeb\xeb\xeb\x5a\x5a\xc6\x54\ -\x54\xfa\xb7\xb7\xb7\x53\x53\xff\x7b\x7b\x7b\xa5\xa5\xff\xb7\xb7\ -\xff\x5f\x5f\x5f\x4b\x4b\x4b\x6a\x6a\x6a\x6d\x6d\x6d\x64\x64\xff\ -\x7f\x7f\x7f\xeb\xeb\xff\xa0\xa1\xff\x23\x23\x23\x47\x47\x47\x65\ -\x65\xff\x60\x60\xff\x80\x80\xff\xe8\xe8\xe8\xe9\xe9\xe9\xfc\xfc\ -\xfc\x9e\x9e\x9e\xd3\xd3\xff\x63\x62\xff\x7d\x7d\xff\x76\x76\x76\ -\x71\x72\xfa\xad\xad\xff\xe0\xe0\xe0\x4f\x4f\x4f\xb3\xb3\xb3\xec\ -\xec\xec\x2d\x2d\x2d\xae\xae\xae\xd5\xd5\xd5\xc6\xc6\xc6\x57\x57\ -\xff\x7c\x7c\xff\x84\x84\xff\x65\x65\x65\x72\x72\xff\xf5\xf5\xf5\ -\xfd\xfd\xfd\x9b\x9b\x9b\xa1\xa2\xff\xd1\xd1\xd1\xf1\xf1\xf1\xfc\ -\xfc\xff\xf7\xf7\xff\xaf\xaf\xaf\x51\x51\xff\x45\x45\x45\x2e\x2e\ -\x2e\x30\x30\x30\xe7\xe7\xff\xc7\xc7\xff\x5e\x5e\x5e\xe5\xe5\xe5\ -\x87\x87\x87\x48\x48\xff\x6f\x6f\xd4\x71\x71\x71\xff\xff\xff\x64\ -\xa8\xdd\x48\x00\x00\x00\x76\x74\x52\x4e\x53\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\x00\x01\x62\xa9\x47\x00\x00\x01\x6b\x49\x44\x41\x54\x78\xda\x62\ -\x28\x25\x00\x00\x02\x88\x81\x90\x02\x80\x00\x22\xa8\x00\x20\x80\ -\x08\x2a\x00\x08\x20\x5c\x0a\x38\x62\x65\x1d\x54\x40\x0c\x80\x00\ -\xc2\xa5\x20\x59\x9c\x97\xd3\x0f\xc4\x00\x08\x20\x5c\x0a\x3c\x82\ -\x98\x79\x04\x41\x0c\x80\x00\xc2\xa5\x40\x27\x46\xce\x59\x0a\xc4\ -\x00\x08\x20\x5c\x0a\xd8\x34\x03\xcd\xac\x41\x0c\x80\x00\xc2\xe9\ -\x8b\x74\x63\x7d\x30\x0d\x10\x40\xb8\xbd\xc9\xea\x03\xa6\x00\x02\ -\x08\xae\x40\xcd\x20\x0a\x55\x81\x34\x44\x0a\x20\x80\xe0\x0a\x4a\ -\x5c\xc2\x0d\x65\x14\x91\x14\x08\x59\x82\x29\x80\x00\x82\x2b\x60\ -\x4d\x4a\xe0\x16\x60\xe4\x2a\x80\x2b\x08\x10\x05\x53\x00\x01\x04\ -\x57\x20\x91\x01\x24\x74\x43\x58\xe2\xd8\xa1\x02\x5c\x36\x60\x0a\ -\x20\x80\x60\x0a\x12\xb3\x21\x12\x4c\x0a\x1a\xae\x11\xde\x20\x16\ -\xb7\x12\x58\x00\x20\x80\x60\x0a\x4c\xd4\x83\xa1\xac\x30\x11\xbe\ -\x1c\x06\xaf\xd2\x52\x7e\x23\x30\x17\x20\x80\x60\x0a\x22\x19\x53\ -\x11\xee\x0b\xb5\x65\x11\x62\x52\x95\x04\xb3\x01\x02\x08\xa6\x40\ -\x9e\x0f\xc5\x8f\x29\x76\xc2\xc2\x02\x60\x16\x40\x00\xc1\x14\xf0\ -\x67\xa1\x85\x93\x27\x83\x39\x98\x06\x08\x20\x98\x82\xc2\x7c\x1c\ -\x01\x0a\x10\x40\x30\x05\xf6\x8e\x38\x14\x00\x04\x10\x44\x41\x9a\ -\x95\x69\xb1\x13\x76\x05\x00\x01\x04\x51\x20\xe6\xae\xe5\xaf\xc7\ -\x81\x55\x01\x40\x00\x41\x14\xb8\xf1\x32\xe7\xc5\xe7\x62\x55\x00\ -\x10\x40\x10\x05\x45\x9c\x3c\xca\x99\x6c\x58\x15\x00\x04\x10\x44\ -\x81\x6f\xb4\xa0\x85\x36\x76\x37\x00\x04\x10\xc1\x7c\x01\x10\x40\ -\x04\x15\x00\x04\x10\x41\x05\x00\x01\x44\x50\x01\x40\x00\x11\x54\ -\x00\x10\x60\x00\x9a\x73\xb0\x3e\xce\x8b\x81\xfb\x00\x00\x00\x00\ -\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x02\x8c\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0\x77\x3d\xf8\ -\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xaf\xc8\x37\x05\x8a\xe9\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x02\x1e\x49\x44\x41\x54\x78\xda\x62\ -\xfc\xff\xff\x3f\x03\x36\xc0\xc8\xc8\x08\x94\xfa\xcf\xc8\x40\x22\ -\x40\xd7\x07\x10\x40\xd8\x0c\xc0\xb0\x91\x58\x8b\x40\x86\xa3\xeb\ -\x01\x08\x20\x26\x2c\xea\xb2\x80\xf8\x17\x4c\x11\x29\x86\xa3\xb8\ -\x1c\x6a\x19\x40\x00\xa1\xa3\x5e\x90\xb9\x8a\x8a\x8a\xff\x21\xe6\ -\xff\x67\x20\x06\xc3\xd4\x22\xd3\x30\x0c\x10\x40\xc8\x86\x67\x82\ -\x04\x12\x12\x12\xfe\x7f\xfe\xfc\x99\x2c\xc3\x61\x18\x59\x1c\x20\ -\x80\x60\x86\x6b\x01\xf1\x4f\x57\x57\x57\xa2\x0d\x26\xc6\x70\x10\ -\x06\x08\x20\x98\x05\x5b\x78\x78\x78\xfe\xbf\x7d\xfb\x96\x6c\x97\ -\x8b\x89\x89\x60\x18\x0e\xc2\x00\x01\x04\x12\x30\x00\x09\xb6\xb7\ -\xb7\x53\xdd\x70\x10\x06\x08\x20\x90\x60\x3b\x1b\x1b\x1b\xd1\xae\ -\x47\x8b\x40\xbc\x86\x83\x30\x40\x00\x81\x24\xce\xd8\xd8\xd8\x90\ -\x1c\xf6\xb8\xc2\x1c\x1d\x03\x04\x10\x28\x1f\xa8\x9b\x99\x99\x11\ -\x95\x4b\xc5\xc5\x45\xff\x63\x4b\xef\xf8\xf2\x0a\x40\x00\x81\x2c\ -\xe0\x11\x15\x15\x25\xca\x82\x57\xaf\xde\x60\x18\x8a\xcb\x70\x58\ -\x46\x03\x08\x20\x26\x06\x32\x00\x30\xdc\x89\x2e\x42\x00\x02\x88\ -\x05\x88\xbf\xbc\x7b\xf7\x8e\x87\x18\x83\x89\x31\x10\xb9\x88\x00\ -\xb1\x01\x02\x08\xe4\x83\xcb\xa7\x4e\x9d\x62\xa0\x16\x40\x2b\xbf\ -\x16\x03\x04\x10\xc8\x82\x7d\x20\x0b\x80\xbe\xf8\x4f\x2d\x4b\xae\ -\x5d\xbb\x06\x33\xeb\x08\x40\x00\x81\x08\x75\x20\xfe\x43\x4a\x46\ -\x23\x84\xd3\xd2\xd2\x40\x16\x7c\x06\x62\x21\x80\x00\x82\x59\xba\ -\x88\xd4\xa2\x02\x17\xbe\x71\xe3\xc6\x7f\x66\x66\x66\x90\x05\x2d\ -\x20\x83\x01\x02\x08\x86\xe4\x80\xf8\x1b\xa9\x85\x1d\x3a\xfe\xf6\ -\xed\xdb\x7f\x5d\x5d\x5d\x90\xe1\x4f\x40\xc9\x1f\x64\x30\x40\x00\ -\x21\xa3\x40\x58\x71\x0d\x52\x48\xaa\xe1\xaf\x5e\xbd\xfa\xef\xe1\ -\xe1\x01\x32\xfc\x27\x10\xc3\x73\x2e\x40\x00\xa1\xa3\x7c\x58\x85\ -\x73\xe0\xc0\x01\xa2\x2d\x59\xb7\x6e\xdd\x7f\x60\x66\x85\x19\x1e\ -\x8e\x6c\x20\x40\x00\x61\x43\x1e\xd0\x08\xfa\x5f\x54\x54\xf4\xff\ -\xc9\x93\x27\x38\x2d\xba\x77\xef\xde\xff\xa8\xa8\x28\x58\xb9\xf4\ -\x1c\x9b\xcb\x01\x02\x08\x57\xc6\x01\x95\x1d\x8d\x40\x9c\x0c\xc4\ -\x6c\x06\x06\x06\x0c\xa0\xf2\x0a\x58\xea\x32\xfc\xfa\x05\xae\xae\ -\x19\xf6\xed\xdb\xc7\x70\xe7\xce\x1d\x10\xf3\x0b\x10\x4f\x04\xe2\ -\x6e\x20\xfe\x88\x6e\x10\x40\x00\x11\xca\x99\x20\x8b\xbc\x81\xd8\ -\x0f\x9a\x10\x24\xa0\x0d\x82\x77\x40\x0c\x32\x7d\x0d\x10\xef\xc6\ -\x66\x30\x0c\x00\x04\x18\x00\x62\xc0\x0d\x29\x86\x9e\x75\xa5\x00\ -\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x01\xd7\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0\x77\x3d\xf8\ -\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xaf\xc8\x37\x05\x8a\xe9\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x01\x69\x49\x44\x41\x54\x78\xda\x62\ -\xfc\xff\xff\x3f\x03\x2d\x01\x40\x00\x31\x31\xd0\x18\x00\x04\x10\ -\x0b\xb1\x0a\x19\x19\x19\x84\x80\x94\x3a\x14\x5f\x06\xe2\x6b\x40\ -\xcf\x7f\x27\xa4\x0f\x20\x80\x18\x40\x41\x44\x08\x33\x30\xfc\xef\ -\x02\xe2\x9f\x40\xfc\x1f\x09\x7f\x00\xe2\x34\x42\x7a\x01\x02\x88\ -\x18\xc3\x1b\x61\x86\x82\xf8\xf3\xe7\xcf\x67\xbe\x71\xe3\x06\x37\ -\x92\x45\x7e\xf8\xf4\x03\x04\x10\x31\x16\x9c\x81\x19\x8e\x45\x0e\ -\x64\xc1\x7c\x7c\xfa\x01\x02\x88\x98\x48\x66\x43\x8d\x0b\xc6\xff\ -\xf8\xe4\xd1\x01\x40\x00\xd1\x3c\x15\x01\x04\x10\xcd\x2d\x00\x08\ -\x20\x9a\x5b\x00\x10\x40\x34\xb7\x00\x20\x80\x68\x6e\x01\x40\x00\ -\xb1\x20\xe5\x54\x10\xd0\x02\xe2\x28\x20\x16\x45\x52\x23\x4d\xc0\ -\x0c\x33\xa0\xde\x99\x50\xf6\x6b\x20\x5e\x06\xcd\xe5\x60\x00\x10\ -\x40\xc8\x69\x5a\x0b\x9a\x3b\xff\x63\xc3\x08\x75\x0c\xff\xd1\xf2\ -\xc1\x7f\x2c\x39\x5c\x0b\xa6\x06\x20\x80\x90\x2d\x58\x8a\x2b\x43\ -\xa1\x66\x2e\x86\xff\x04\x32\x26\xc8\x92\xa5\x30\x3e\x40\x00\x21\ -\x17\x76\x4e\x40\x3e\x23\xa5\x61\x0e\x32\x03\x18\x64\xcf\x61\x7c\ -\x80\x00\x62\x84\xd5\x07\x40\xc1\x9f\x40\x26\x3b\x8e\xdc\x4a\xa4\ -\xe1\xff\x19\xa1\x66\xfd\x87\x39\x16\x20\x80\x90\x7d\xf0\x14\x5d\ -\x21\x05\x00\x6e\x16\x40\x00\x21\x27\xd3\xdd\x20\x9b\x29\x0d\x22\ -\xa8\x19\x5b\x61\x7c\x80\x00\x42\x8e\x1c\x45\x20\x7e\x85\x2b\x15\ -\x91\x80\x41\x66\x28\xc2\xcc\x05\x08\x20\xe4\x38\x00\x01\x39\x20\ -\x4e\x20\x22\xed\xe3\x0b\x9a\x05\x40\xfc\x08\x96\x0f\x00\x02\x88\ -\x91\xd6\x95\x3e\x40\x00\xd1\xbc\xa8\x00\x08\x20\x9a\x5b\x00\x10\ -\x60\x00\xd6\x65\x9e\x47\xf3\x54\xc8\x7c\x00\x00\x00\x00\x49\x45\ -\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x05\x5d\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x20\x00\x00\x00\x20\x08\x03\x00\x00\x00\x44\xa4\x8a\xc6\ -\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xaf\xc8\x37\x05\x8a\xe9\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x02\x2e\x50\x4c\x54\x45\x3b\x3b\x3b\ -\xaf\xaf\xaf\x37\x37\x37\x9a\x9a\xff\x4d\x4d\xff\x42\x42\x42\x3d\ -\x3d\x3d\xf7\xf7\xf7\xea\xea\xff\xbc\xbc\xbc\xfd\xfd\xff\xe2\xe2\ -\xff\xfb\xfb\xff\x3a\x3a\x3a\xe5\xe5\xff\x95\x95\xff\x6f\x6f\xff\ -\xc3\xc3\xff\xfc\xfc\xfc\xf5\xf5\xf5\xd7\xd7\xd7\x74\x74\xff\xc0\ -\xc0\xff\x67\x67\xff\xc1\xc1\xff\x24\x24\x24\x98\x98\xff\xd5\xd5\ -\xd5\x76\x76\x76\x85\x85\x85\x91\x91\x91\xf6\xf6\xf6\xb1\xb1\xb1\ -\x8c\x8c\xff\xf4\xf4\xf4\x2e\x2e\x2e\xa4\xa4\xff\x4e\x4f\xff\xf8\ -\xf8\xf8\x6e\x6e\xff\x7c\x7c\xb7\xdd\xdd\xdd\xb7\xb7\xff\xca\xca\ -\xcd\x3e\x3e\x3e\xf0\xf0\xf0\x47\x47\x47\x96\x95\xc4\x5e\x5e\xff\ -\x50\x50\x96\x52\x52\x52\xab\xab\xff\xd9\xd9\xff\x94\x94\xff\x76\ -\x76\xff\x6a\x6a\xf0\xa9\xa9\xff\x79\x78\xff\x53\x53\x53\xdb\xdb\ -\xdb\x2b\x2b\x2b\xd1\xd1\xd1\x6d\x6d\xec\x6c\x6c\xff\xc2\xc2\xc2\ -\x7c\x7c\xff\x7f\x7f\xff\xca\xca\xca\xf6\xf6\xfe\xc9\xc9\xc9\xa6\ -\xa6\xff\x48\x48\x48\xfe\xfe\xff\xa6\xa6\xa6\xf4\xf4\xff\x31\x31\ -\x31\xf1\xf1\xff\xac\xac\xff\xec\xec\xec\xd7\xd7\xff\x35\x35\x35\ -\xde\xde\xde\xe4\xe3\xff\x3f\x3f\x5d\x94\x94\x94\xa4\xa3\xff\x7e\ -\x7e\xb7\xce\xce\xff\x64\x63\xda\xcc\xcc\xcc\x67\x67\x7c\x9e\x9e\ -\x9e\xf9\xf9\xf9\x37\x37\x88\xb0\xb0\xff\x20\x20\x20\x87\x87\x87\ -\x44\x44\x44\xdf\xdf\xff\x52\x51\xff\xbb\xbb\xbb\x66\x66\xff\x5d\ -\x5d\xff\x50\x51\xff\x54\x55\xff\x12\x12\x12\xb2\xb2\xff\x26\x26\ -\x26\xb5\xb5\xb5\x68\x68\xff\xe0\xe0\xe0\x7a\x7a\xff\x72\x72\x72\ -\xd9\xd9\xdb\x21\x21\x21\x4b\x4b\x4b\xf1\xf1\xf1\x5c\x5c\xff\xee\ -\xee\xfe\x73\x73\xff\x19\x19\x19\x5f\x5f\x5f\xd0\xd0\xd0\xcb\xcb\ -\xcb\x6e\x6f\xff\x70\x71\xff\x41\x41\x41\xee\xee\xee\xeb\xeb\xeb\ -\x68\x68\xae\x55\x55\xff\x66\x66\x66\xb3\xb3\xb3\x68\x68\xa7\x6d\ -\x6d\xff\xb4\xb4\xb4\x6b\x6b\xf0\x69\x69\x69\xc5\xc6\xff\x18\x18\ -\x18\x9d\x9d\x9d\x60\x61\xff\xc4\xc4\xc4\xd0\xd0\xff\x40\x40\x8d\ -\x54\x54\xff\x72\x72\xff\x73\x73\x73\xf0\xf0\xff\x7a\x7b\xff\xa5\ -\xa5\xa5\xcd\xcd\xe4\xdb\xda\xff\xef\xef\xff\x51\x51\x51\xe9\xe9\ -\xe9\x8d\x8d\x8d\x57\x57\xff\x4f\x4f\x6a\x8b\x8b\x8b\x69\x69\xec\ -\x64\x64\xff\x64\x64\x64\x8e\x8e\x8e\xba\xba\xff\x45\x45\x45\x7b\ -\x7b\x7b\x3e\x3e\x8c\xc9\xc9\xff\xb1\xb1\xff\xe3\xe3\xff\x9e\x9e\ -\xc9\x6b\x6b\xff\x9c\x9b\xff\xb3\xb3\xd7\x9b\x9b\xff\x7a\x7a\x7a\ -\x69\x69\xff\xba\xbb\xff\x4b\x4b\xc6\x9b\x9b\x9b\x86\x86\x86\xeb\ -\xeb\xff\xb7\xb7\xd9\x7c\x7c\x7c\xff\xff\xff\xf4\x9f\xc0\x82\x00\ -\x00\x00\xba\x74\x52\x4e\x53\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\x00\xbb\x69\x24\x75\x00\x00\x01\xef\x49\x44\x41\x54\x78\xda\x62\ -\xd8\x49\x00\x00\x04\x10\x03\x21\x05\x00\x01\x84\xa6\x80\x43\x40\ -\x5c\x80\x03\x45\x04\x20\x80\x50\x15\x14\xda\x89\xc6\x89\x2e\x70\ -\x96\x47\x12\x02\x08\x20\x24\x05\x4a\x72\x89\xc1\xe6\x12\x3e\x12\ -\x1d\xf3\xd8\x0a\x44\xe0\xa2\x00\x01\x84\x50\x30\x4d\x67\x91\x76\ -\xd9\x44\xfe\x70\xfe\x26\x17\xeb\xc5\x4c\x32\x6a\x50\x61\x80\x00\ -\x82\x2b\x68\xe6\xed\x03\x92\x82\xaa\x53\x55\x05\x81\x74\xc9\x06\ -\x26\x4d\x88\x38\x40\x00\xc1\x14\x58\x31\xb0\x83\x69\xbe\x24\x3e\ -\x88\x80\x02\x6b\x20\x98\x06\x08\x20\xa8\x02\x19\x36\x35\x74\xff\ -\x29\x04\x08\x83\x28\x80\x00\x82\x28\xe0\x94\x94\xc7\x0c\x81\x65\ -\x93\x41\x24\x40\x00\x81\x15\x08\xf1\xe6\x60\x09\x22\x76\x6f\x69\ -\x20\x09\x10\x40\x60\x05\xed\xac\x58\x03\x71\x4e\x27\x90\x00\x08\ -\x20\xb0\x82\xca\x1e\xac\x0a\x1a\x18\x84\x76\xee\x04\x08\x20\x90\ -\x02\xff\xe5\xfa\x3c\xd8\x14\x4c\x9f\xb0\x9a\x6b\x27\x40\x00\x01\ -\x15\xf8\xb2\x48\xd5\x94\x63\x91\xcf\x4a\x66\xde\xe8\xb4\x13\x20\ -\x80\x80\x0a\x4a\xd7\x73\x8b\xd9\xcf\xc0\x90\xf7\x98\x14\x1a\xb4\ -\x44\x7c\x27\x40\x00\x01\x15\x64\xac\xe5\x16\x5b\xd3\x8f\xa1\x80\ -\x6b\xa1\xca\xaa\x4d\xa9\x3b\x01\x02\x08\xa8\x80\x99\x45\x4a\xbd\ -\xcd\x0b\xd3\x0a\x95\x74\xe6\x5c\xb3\x9d\x00\x01\x04\x72\xe4\xf6\ -\xd8\x30\xac\x8e\x5c\x67\xa8\xb1\x73\x27\x40\x00\x81\xbd\xa9\xc7\ -\x89\x23\x35\xd5\xef\xdc\x09\x10\x40\x60\x05\x21\xd8\x03\x8a\xb1\ -\x18\x48\x00\x04\x10\x58\x81\x9f\x64\x15\x36\x05\x4b\x19\x81\x04\ -\x40\x00\x41\x22\x4b\x96\x09\x8b\xfc\x96\x3a\x10\x09\x10\x40\x10\ -\x05\x31\x0c\x5b\x31\xe4\x23\xe3\x6d\x41\x14\x40\x00\x41\xd3\x43\ -\x5e\xb7\x2c\x9a\x7c\x75\xa6\x27\x98\x06\x08\x20\x58\x8a\x12\x29\ -\xda\xa1\x04\xa2\x2d\xf2\x1d\x57\x80\xe8\x96\x8a\x68\x88\x04\x40\ -\x00\xc1\xd3\xe4\x6c\xa3\x6c\x39\xdd\x9d\x5a\x73\x8d\x4d\x59\x66\ -\xee\x74\x75\x57\x76\x80\x8a\x03\x04\x10\x52\xb2\x67\x64\xb3\x89\ -\x8a\x70\xdb\x66\xa2\xd8\x38\x4b\x39\x41\x18\x26\x0a\x10\x40\x28\ -\x19\x47\x7a\xfe\x66\xcb\xae\x95\x06\xad\x29\xec\x08\x31\x80\x00\ -\x42\xcb\x7a\x53\x7a\x6b\xd3\x14\x51\x44\x00\x02\x88\x60\xe6\x05\ -\x08\x20\x82\x0a\x00\x02\x0c\x00\xd0\x75\x81\x3b\x49\xd4\xa2\xce\ -\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x01\x6c\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x18\x00\x00\x00\x18\x08\x03\x00\x00\x00\xd7\xa9\xcd\xca\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x4e\x50\x4c\x54\x45\x5a\x5a\x5a\x00\x00\x00\xff\x00\x00\xff\x35\ -\x35\x4b\x4b\x4b\xb4\xb4\xb4\x96\x96\x96\x69\x69\x69\x1c\x1c\x1c\ -\xcc\xcc\xcc\xf0\xf0\xf0\x2b\x2b\x2b\x30\x30\x30\x54\x54\x54\xe7\ -\xe7\xe7\xe1\xe1\xe1\x4e\x4e\x4e\xff\x1c\x1c\xae\xae\xae\x75\x75\ -\x75\xe4\xe4\xe4\xf6\xf6\xf6\x03\x03\x03\x0f\x0f\x0f\xab\xab\xab\ -\xff\xff\xff\x2b\x01\xcd\x0f\x00\x00\x00\x01\x74\x52\x4e\x53\x00\ -\x40\xe6\xd8\x66\x00\x00\x00\x01\x62\x4b\x47\x44\x00\x88\x05\x1d\ -\x48\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\ -\x13\x01\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\ -\x04\x04\x0f\x29\x27\x74\x12\x8b\xd5\x00\x00\x00\x8a\x49\x44\x41\ -\x54\x28\xcf\x95\x92\x59\x12\x80\x20\x08\x40\xc1\x16\xdb\xd7\x99\ -\xea\xfe\x27\x0d\x97\x14\x4a\x3f\xf2\x43\xe9\x3d\x64\x48\x05\xa5\ -\x20\x31\x88\xaa\x94\xb1\x30\x61\x3c\xfa\x98\x00\x28\x50\x45\xc0\ -\x05\x4b\x14\x82\x7f\xac\x32\x29\x0a\x8d\xbc\x2c\x62\x15\x05\xc6\ -\x46\x4a\x29\x6a\x42\xfa\x32\xd3\x5b\xd8\xe2\x66\xdb\x47\x80\xe3\ -\x3f\x44\xaa\xd4\x8c\xbe\x2d\x33\x71\x01\xbc\x5d\x4a\xea\x82\xb8\ -\xc4\x0f\x0e\x2d\x3f\x1f\x8d\x36\xea\x29\xdc\x39\x6f\xf0\x19\xf2\ -\xdc\x60\x7a\xcc\xb1\xf3\xb2\x66\xd9\x16\xc2\xe7\xd8\xe6\x2f\x30\ -\x7f\xe5\x0e\xe6\x9e\xcf\x0d\x2f\xe4\x03\x29\x56\x67\x87\xde\x00\ -\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x02\xec\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x20\x00\x00\x00\x20\x08\x03\x00\x00\x00\x44\xa4\x8a\xc6\ -\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xaf\xc8\x37\x05\x8a\xe9\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x00\xea\x50\x4c\x54\x45\xaa\xaa\xff\ -\x96\x96\xff\x8b\x8b\x8b\x9d\x9d\x9d\x61\x61\xff\xfb\xfb\xfb\xfd\ -\xfd\xfd\x75\x75\x75\xd6\xd6\xd6\x77\x77\x77\x71\x71\x71\x79\x79\ -\x79\x7b\x7b\x7b\x53\x53\xff\xf1\xf1\xf1\x66\x66\xff\xe4\xe4\xff\ -\xbb\xbb\xbb\xfc\xfc\xfc\x94\x94\x94\xec\xec\xec\x97\x97\x97\x56\ -\x56\x56\xbc\xbc\xbc\xac\xad\xff\xa5\xa5\xff\xa1\xa1\xff\xf4\xf4\ -\xf4\x7e\x7e\x7e\xf6\xf6\xff\xcf\xcf\xcf\xc0\xc0\xc0\xb9\xb9\xb9\ -\xc1\xc0\xff\x3e\x3e\x3e\xab\xab\xab\xab\xab\xff\xf8\xf8\xf8\x84\ -\x84\xff\xde\xde\xff\x63\x63\xff\x88\x88\x88\x6f\x6f\x6f\xd7\xd7\ -\xd7\x64\x64\xff\x56\x56\xff\x89\x89\x89\x93\x93\xb6\xbd\xbd\xbd\ -\x73\x73\x73\x50\x50\x50\x8f\x8f\x8f\xb7\xb7\xff\x6b\x6b\x6b\xb6\ -\xb6\xb6\xf8\xf8\xff\x7a\x7a\xff\x91\x91\x91\xf7\xf7\xf7\x6a\x6a\ -\xff\xb7\xb8\xff\xe6\xe6\xff\xa0\xa0\xa0\xfa\xfa\xfa\x9a\x9a\x9a\ -\x81\x81\x81\xd9\xd9\xd9\xb0\xb0\xb0\x6e\x6e\x6e\xcc\xcc\xff\xe5\ -\xe5\xff\x75\x75\xc4\xe2\xe2\xe2\x86\x86\x86\xf5\xf5\xf5\x42\x42\ -\x42\xf3\xf3\xff\xff\xff\xff\xa9\x0c\xd3\x32\x00\x00\x00\x4e\x74\ -\x52\x4e\x53\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\x00\xac\x4d\xfb\xce\x00\x00\x01\x2e\x49\x44\x41\x54\x78\xda\x62\ -\xf0\x25\x00\x00\x02\x88\x81\x90\x02\x80\x00\x22\xa8\x00\x20\x80\ -\x08\x2a\x00\x08\x20\x7c\x0a\x54\x41\x04\x40\x00\xe1\x52\xe0\x2a\ -\xa1\xe8\xcb\x06\x62\x00\x04\x10\x0e\x05\x52\xbc\x8c\x2c\x6a\x60\ -\x16\x40\x00\x61\x57\x20\x6b\xcd\xe0\x66\xc2\x6f\x0b\x62\x02\x04\ -\x10\x76\x05\x3e\x1a\x0c\x02\x36\x3a\xea\x20\x26\x40\x00\xe1\xb0\ -\xc2\x5d\x97\x91\xc5\xc2\x1c\xc4\x02\x08\x20\xec\x0a\xe4\x8d\xf4\ -\x55\x24\x21\x4c\x80\x00\xc2\xaa\x80\x4f\x41\x0e\xce\x06\x08\x20\ -\x6c\x0a\x04\xc5\xac\x10\x1c\x80\x00\xc2\xa2\xc0\x4c\x89\x03\x89\ -\x07\x10\x40\x58\x14\x88\x3b\x21\xf3\x00\x02\x08\x43\x81\xb3\x27\ -\x2a\x1f\x20\x80\xd0\x15\x28\x7b\x6b\xa3\x0a\x00\x04\x10\x9a\x02\ -\x2f\x53\x0f\x34\x1d\x00\x01\x84\xaa\xc0\x40\x04\xc3\x45\x00\x01\ -\x84\xa2\xc0\xce\x45\x1a\x43\x01\x40\x00\x21\x2b\x60\xd6\xb2\xc7\ -\xf4\x13\x40\x00\x21\x29\x70\xe0\x62\xc5\x12\x6a\x00\x01\x84\x50\ -\x20\x6a\x28\x84\x2d\xd8\x01\x02\x08\xae\x40\x98\x9d\x0d\x6b\xbc\ -\x01\x04\x10\x4c\x81\x25\x27\x8e\xb4\x07\x10\x40\x50\x05\xc6\xdc\ -\xb8\x52\x2e\x40\x00\x41\x14\x30\xf1\xe0\x4c\xda\x00\x01\x04\x56\ -\xa0\x27\x83\x3b\xed\x03\x04\x10\x50\x01\x2b\x93\x23\x9e\xcc\x01\ -\x10\x40\x40\x05\xcc\x9a\xf8\x72\x16\x40\x00\x11\xcc\x7a\x00\x01\ -\x44\x50\x01\x40\x80\x01\x00\x45\xfe\x26\x4f\xeb\x46\xd0\xf1\x00\ -\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x01\xe3\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x12\x00\x00\x00\x12\x08\x06\x00\x00\x00\x56\xce\x8e\x57\ -\x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\ -\xa7\x93\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0a\xf0\x00\x00\ -\x0a\xf0\x01\x42\xac\x34\x98\x00\x00\x00\x07\x74\x49\x4d\x45\x07\ -\xdf\x06\x16\x0f\x1b\x0b\xe0\x36\xac\x96\x00\x00\x00\x1d\x69\x54\ -\x58\x74\x43\x6f\x6d\x6d\x65\x6e\x74\x00\x00\x00\x00\x00\x43\x72\ -\x65\x61\x74\x65\x64\x20\x77\x69\x74\x68\x20\x47\x49\x4d\x50\x64\ -\x2e\x65\x07\x00\x00\x01\x47\x49\x44\x41\x54\x38\xcb\xbd\x94\xbd\ -\x4b\xc3\x50\x14\xc5\xcf\x4b\x93\x42\x05\xa5\x83\x2d\x96\x0e\x8a\ -\x4b\x5d\x9d\x44\x49\x26\x0b\x0e\x0e\x8a\xf5\x6f\x70\x10\x44\x74\ -\x16\xaa\x90\xc9\x41\x14\x9c\x45\x54\xc4\x41\x41\xea\x26\x28\x42\ -\x10\x17\x37\x45\x94\x80\x38\xf6\x43\x30\x36\x52\x13\xd0\x1e\x07\ -\xad\x88\x09\x34\xad\xc5\x03\x97\xf7\x86\x7b\x0f\xbf\xc7\xbd\xf7\ -\x09\x92\x68\x85\xa4\xbf\x1a\xac\xa5\xe3\x9f\x24\x24\x9b\x8e\xd5\ -\xe1\x18\xef\x8f\xba\x49\xb2\x79\xa2\xa5\xf1\x38\x07\xf4\x1c\xac\ -\x93\x2a\x9a\x26\x5a\x1c\x8b\x91\x24\x4d\xd3\xe4\xd7\x3d\x98\xd1\ -\xed\xf5\x15\xf5\x99\x29\xce\x4e\x8e\x72\xa4\xaf\xd7\x63\x42\x12\ -\xa2\x5e\xd7\x2e\xcf\x0d\x1e\xaf\xcf\x23\x19\x29\x20\x6f\xc9\xb8\ -\x29\xb5\xa3\xf0\xf8\x82\xc1\x94\x8d\xec\x61\x51\xd4\xf2\xea\x1a\ -\xcd\x4d\xa4\xd9\x1f\xbd\x43\xd9\x91\xf1\xe4\x28\xb0\x9d\x10\xf2\ -\x95\x28\xb6\xce\x2e\x44\xe0\xf6\x1f\x6c\x6e\x30\x21\x3d\xc0\x76\ -\x15\x58\x6e\x18\xb6\xab\xa0\xf8\x0c\x74\x76\x25\xfd\xe7\x48\xd3\ -\x34\x0f\x56\x6e\x77\x87\xa7\xdb\xcb\x68\x0b\xbf\xc3\x72\x64\x94\ -\x5f\x25\x94\xec\x10\x2a\x6f\x11\xac\xec\xed\x7f\xd3\xd4\x6a\x65\ -\x3f\x12\x3d\x93\x22\x00\x24\xa2\x80\xe5\x86\x01\x00\x1d\x91\x2a\ -\x7a\x86\x32\x98\x5e\xc8\x0a\x5f\x7c\x92\x50\x55\x95\x3f\xcf\x20\ -\xf1\xbb\x46\x90\xf4\x7d\x5a\x23\x32\x0c\x43\xb4\x8c\x48\xf2\x38\ -\x37\x42\xd1\xca\xed\x0f\x3c\x90\xff\xf6\x1f\xd5\xf4\x01\x15\x02\ -\x61\x2f\x83\x75\x34\xa9\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\ -\x60\x82\ -\x00\x00\x03\x23\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x20\x00\x00\x00\x20\x08\x03\x00\x00\x00\x44\xa4\x8a\xc6\ -\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xaf\xc8\x37\x05\x8a\xe9\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x01\x0b\x50\x4c\x54\x45\xe7\xe7\xe7\ -\x23\x23\x23\x83\x83\x83\x53\x53\x53\x3b\x3b\x3b\x9f\x9f\x9f\x3f\ -\x3f\x3f\x28\x28\x28\xec\xec\xec\xc4\xc4\xff\x8a\x8a\x8a\xdc\xdc\ -\xdc\x88\x88\x88\x99\x99\x99\x48\x48\x48\x24\x24\x24\x82\x82\x82\ -\xd7\xd7\xd7\xfd\xfd\xff\xd6\xd6\xd6\xe1\xe1\xff\xa3\xa3\xff\xad\ -\xad\xad\x54\x54\x7a\xcc\xcc\xff\xfd\xfd\xfd\x22\x22\x22\xe1\xe1\ -\xe1\xcb\xcb\xff\xf2\xf1\xff\x9a\x9a\xff\xd5\xd5\xd5\x95\x95\xff\ -\xdb\xdb\xdb\xe3\xe3\xe3\x8b\x8b\xff\xe3\xe3\xea\x90\x90\xff\xb3\ -\xb3\xf0\x31\x30\x52\xab\xab\xab\xc8\xc8\xc8\x54\x54\x54\xdf\xdf\ -\xff\x50\x50\x50\xeb\xeb\xeb\x76\x76\xff\x26\x26\x26\x43\x43\x43\ -\x41\x41\x41\x3c\x3c\x3c\x53\x53\xfb\x3a\x3a\x3a\x92\x92\x92\x58\ -\x58\x58\x6e\x6e\xff\x57\x57\x57\xa9\xa9\xa9\x45\x45\x45\xf2\xf2\ -\xf2\x6f\x6f\xfa\x67\x67\x96\x9e\x9e\xff\x55\x55\x55\x96\x96\x96\ -\xd8\xd8\xd8\x37\x37\x37\x77\x77\xff\x69\x6a\xe2\x27\x27\x27\xa2\ -\xa2\xa2\x8a\x8a\xff\x5f\x5f\xff\xf1\xf1\xff\xcf\xcf\xcf\x89\x89\ -\x89\xa6\xa6\xa6\xe5\xe5\xe5\x3d\x3d\x3d\xd9\xd9\xff\x62\x62\x77\ -\x4e\x4d\xf9\xee\xee\xee\x74\x73\xfc\x4a\x4a\x4a\x91\x91\x91\xf6\ -\xf6\xf6\xa3\xa3\xa3\xff\xff\xff\x1c\x38\x9a\x32\x00\x00\x00\x59\ -\x74\x52\x4e\x53\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x4f\x85\x01\ -\x0f\x00\x00\x01\x39\x49\x44\x41\x54\x78\xda\x62\x88\x20\x00\x00\ -\x02\x88\x81\x90\x02\x80\x00\x22\xa8\x00\x20\x80\x08\x2a\x00\x08\ -\x20\x84\x02\x49\x2e\xac\x8a\x01\x02\x08\x49\xd0\x8f\x15\x9b\x02\ -\x80\x00\x42\x52\x60\xca\x87\x4d\x01\x40\x00\x21\x29\x50\x92\xf2\ -\xc5\xa2\x00\x20\x80\x90\xed\xb5\x72\xc0\xa2\x00\x20\x80\x90\x15\ -\xf0\x1a\x60\x51\x00\x10\x40\xc8\x0a\x18\x18\xa5\x31\x15\x00\x04\ -\x10\x8a\xd7\x42\x42\x31\x15\x00\x04\x10\x8a\x02\x56\x36\x4c\x05\ -\x00\x01\x84\xa2\x40\x97\x91\x1b\x43\x01\x40\x00\xa1\x86\x9e\x0e\ -\x17\x86\x02\x80\x00\x42\x55\xe0\xc3\x82\xa1\x00\x20\x80\x50\x15\ -\x04\xf1\x0b\xa2\x2b\x00\x08\x20\xb4\x08\xb2\x67\x42\x57\x00\x10\ -\x40\x68\x0a\xc4\x9c\xd0\x15\x00\x04\x10\x9a\x02\x6b\x57\x47\x18\ -\x33\x0c\x42\x01\x04\x10\x7a\x1a\x08\xb0\xe5\x14\x02\x52\x1c\x96\ -\x16\x26\x10\x01\x80\x00\x42\x53\x20\x62\xec\x6e\x1e\xac\x12\xce\ -\xcc\x6e\x24\xe0\x05\x11\x01\x08\x20\x34\x05\x72\xca\xfe\x9c\x36\ -\xea\x86\xde\xc2\x70\x11\x80\x00\x42\x55\xa0\xe8\xa2\xa0\x2d\xa3\ -\xa7\x86\x2c\x04\x10\x40\x48\x0a\xe4\x79\xd8\xf4\xc5\x03\x55\x9d\ -\x3d\x3c\x91\x15\x00\x04\x10\x4c\x81\x26\x13\x0b\x3b\xb3\x1b\x43\ -\x84\x84\xa8\x9d\x2c\x8a\xa1\x00\x01\x04\x53\xa0\x65\xa6\xc1\x81\ -\x35\xd9\x03\x04\x10\xc1\x7c\x01\x10\x40\x04\x15\x00\x04\x10\x41\ -\x05\x00\x01\x44\x50\x01\x40\x00\x11\x54\x00\x10\x60\x00\x19\x71\ -\x4c\xdb\x78\x5c\x95\x6e\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\ -\x60\x82\ -\x00\x00\x01\xb4\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x09\x12\ -\x0c\x2b\x1d\x0f\x8c\x42\x4e\x00\x00\x01\x34\x49\x44\x41\x54\x38\ -\xcb\x63\xfc\xff\xff\x3f\x03\xa9\xc0\xd6\xd1\x1d\xae\x89\x85\x58\ -\x4d\xcf\x9e\x3f\xff\x7f\xe4\xc8\x71\x86\xf3\x17\x2e\x31\x30\x30\ -\x30\x30\x1c\xde\xbf\x93\xd1\xd6\xd1\xfd\x3f\x23\x21\x17\xdc\xba\ -\x7d\xe7\xff\xfc\x05\x4b\x18\x4e\x9f\x3d\xcb\xa0\xa2\xac\xcc\x60\ -\x64\xa8\xcf\xb0\x78\xe9\x0a\x84\x82\xff\xff\xff\x63\xc5\x9f\x3f\ -\x7f\x61\x98\x30\x69\xda\x7f\x3b\x27\x8f\xff\x9d\x3d\xfd\xff\x5f\ -\xbe\x7c\xf5\x0e\x9b\x3a\xac\x5e\xf8\xf4\xf9\x33\x43\x41\x51\xf9\ -\x7f\x11\x11\x61\x86\x95\x4b\x17\x32\x48\x48\x88\x31\xe2\x74\x22\ -\xba\x89\x1f\x3f\x7d\x62\x48\x4e\xcb\xfe\x3f\x69\xea\x8c\xff\x5f\ -\xbe\x7c\xc1\xe9\x42\x18\xc6\xd0\x9c\x98\x92\xf9\xbf\x6f\xc2\x94\ -\xff\xb8\x34\x18\x1b\x1b\xff\xc7\x69\xc0\x84\x49\xd3\xfe\x67\xe7\ -\x15\xff\xff\xfc\xf9\x33\x4e\xcd\xab\xd7\x6f\xc3\x6e\xc0\xcd\x5b\ -\xb7\xff\xbb\x78\xf8\xe1\x0c\x2c\x64\x9b\x91\x0d\x61\x82\x85\xc5\ -\x82\x45\x4b\x19\x3c\xdc\x5d\x18\xc4\xc4\x44\x85\xd0\xc3\xc9\xc4\ -\xc4\xe4\xff\x99\x33\x67\x18\xf1\x06\xe2\x85\x8b\x97\xff\x7f\xff\ -\xfe\x9d\xa0\x9f\xd1\x5d\x01\x4f\x48\xb6\x8e\xee\xff\x0f\xef\xdf\ -\xc9\x88\x6e\x73\x45\x4d\x33\xce\x18\x0c\x09\xf0\x64\xc4\x9b\x94\ -\x71\x3a\x1b\x09\x30\xda\x38\xb8\x61\xa4\x65\x74\x97\xe0\x05\x30\ -\x3f\xd9\x38\xb8\xfd\x27\x94\x68\xb0\x61\x26\x06\x0a\x01\x00\x61\ -\x40\x82\xb5\x7e\x34\xa3\xd7\x00\x00\x00\x00\x49\x45\x4e\x44\xae\ -\x42\x60\x82\ -\x00\x00\x02\x7b\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0\x77\x3d\xf8\ -\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xaf\xc8\x37\x05\x8a\xe9\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x02\x0d\x49\x44\x41\x54\x78\xda\x62\ -\xf8\xff\xff\x3f\x03\x21\x2c\x26\x26\xf2\x9f\x18\x75\x40\x80\xa1\ -\x0e\x20\x80\x18\xa1\x12\x0c\x8c\x8c\x8c\x0c\xd8\x00\xc8\x70\x18\ -\xfb\xe5\xcb\xd7\xd8\x15\x41\xf4\xc3\xd5\x01\xcd\x84\xab\x03\x08\ -\x20\x64\xdb\x71\x81\x33\x50\x4b\xe6\x10\x72\x39\x88\x86\x61\x98\ -\x1c\x40\x00\xe1\xb3\x40\x08\x88\x1f\x03\xf1\x47\x20\x8e\x04\xe2\ -\xdf\xd8\x2c\x41\x36\x1c\x9b\x25\x00\x01\x84\xcb\x02\x65\x20\x7e\ -\x0f\xb5\x40\x08\x2a\x66\x07\xb5\x64\x06\x21\x97\x23\xfb\x00\x20\ -\x80\xb0\x59\x60\x01\xc4\xbf\x80\xf8\x22\xb2\x20\x54\x0d\xdc\x12\ -\x62\x0c\x07\x61\x80\x00\x42\xb7\x20\x11\x88\xff\x02\xf1\x2a\x74\ -\x2f\x21\xa9\xb3\x83\x1a\x38\x03\x66\x30\x2c\x95\x61\x4b\x45\x00\ -\x01\x84\x92\x14\x81\x0a\xfe\x01\x71\x0f\xb6\x08\x41\x0e\x16\x20\ -\x76\x26\xc6\x70\x10\x06\x08\x20\xb8\xe1\x30\x8c\x2b\x29\xa1\x07\ -\x07\xcc\x70\x20\x3d\x01\x97\xe1\x20\x0c\x10\x40\x30\x8d\x3c\x50\ -\xc5\x6c\xc4\x64\x26\x24\x0c\xf3\xc9\x04\x5c\xea\x01\x02\x08\x59\ -\x23\x28\xec\xf5\xf0\xe5\x66\x6c\x11\x0b\xc4\x1e\xd0\x88\xc7\x6a\ -\x09\x40\x00\x21\x5b\xf0\x15\x14\xc9\xc4\xb8\x1c\x23\x29\x22\x2c\ -\xe9\x41\xd7\x07\x10\x40\xc8\x06\x3c\x07\xe2\x0e\x42\x16\xe0\x2a\ -\x97\xa0\x96\xfc\x41\xb7\x04\x20\x80\x90\x15\x80\xd2\xfd\x3a\x62\ -\x0a\x35\x3c\x8e\x00\x5b\x82\xec\x08\x80\x00\x62\x42\x4a\x28\x20\ -\x1f\xc8\x31\x50\x00\x80\x06\xee\x00\x1a\xce\x0c\x62\x8b\x8b\x8b\ -\x82\x6d\x04\x08\x20\x64\x0b\xee\x00\xb1\x28\x25\x16\x00\x4b\xd4\ -\x83\xaf\x5e\xbd\xf9\x8d\x5c\xf2\x02\x04\x10\xb2\x05\xb7\x80\x58\ -\x80\x02\xc3\xaf\x00\x29\x53\x20\x36\x41\x2e\xd6\x01\x02\x88\x05\ -\x49\xcd\x55\x20\xe6\x22\xc3\x60\x1e\x20\x75\x19\x5a\x28\xaa\x03\ -\x83\xe9\x31\xb2\x3c\x40\x00\x21\x47\x10\xd1\x99\x0d\x49\x0f\xc8\ -\xd0\x97\x50\x2c\x84\x4d\x0d\x40\x00\x31\x21\xd5\x5c\x9f\xa1\xf4\ -\x4f\xa0\xab\xee\x03\xf1\x3a\x20\xce\x82\xba\x10\x9b\xcb\x65\x81\ -\xd4\x7d\x20\xfe\x06\x2a\xde\x81\x86\xbd\xc3\xa6\x0e\x20\x80\xb0\ -\xe5\xd6\x70\x20\x5e\x00\xc4\x37\xa1\x9a\xff\x43\xe9\x9b\x50\xf1\ -\x70\xa8\x3a\x90\xd8\x15\x42\xbe\x04\x08\x20\x46\x02\xd5\x25\x2c\ -\x8c\xe3\x80\xd8\x05\x88\x0d\x81\x86\x2b\x10\x53\x47\xc3\x00\x40\ -\x00\x11\xb4\x00\x57\x05\x8f\x52\xb1\xe3\x01\x00\x01\x44\x96\x05\ -\xa4\x00\x80\x00\x03\x00\xa7\xd4\xe9\xdd\xbd\x52\x19\x26\x00\x00\ -\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x01\x88\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x09\x12\ -\x0c\x0a\x3a\x26\x19\xe2\xc6\x00\x00\x01\x08\x49\x44\x41\x54\x38\ -\xcb\x63\xfc\xff\xff\x3f\x03\xa9\xc0\xd6\xd1\x1d\xae\x89\x89\x14\ -\x8d\x47\x8e\x1e\xff\x3f\x79\xda\xcc\xff\x0c\x0c\x0c\x0c\x87\xf7\ -\xef\x64\x64\x60\x60\x60\x60\x21\xa4\xe9\xcb\x97\x2f\xff\x57\xaf\ -\xdd\xc0\xb0\x61\xe3\x16\x06\x3e\x3e\x5e\x06\x7d\x3d\x5d\x54\x57\ -\xfc\xff\xff\x1f\x27\x3e\x74\xf8\xe8\x7f\xbf\xc0\xf0\xff\x85\x25\ -\x15\xff\x2f\x5f\xb9\xf6\x1f\x9b\x1a\x16\x7c\xfe\xe3\xe7\xe7\x67\ -\xa8\x2c\x2b\x62\xb0\xb6\xb2\x60\xc4\xe9\x44\x74\x13\x6d\x1c\xdc\ -\xfe\xc3\xe8\x47\x8f\x1f\xff\xc7\xe7\xc2\xff\xff\xff\x63\x0f\x44\ -\x98\x2b\x64\x65\x64\x18\x09\x86\x2c\xb2\x69\x87\x8f\x1c\xfb\x1f\ -\x16\x19\xff\xff\xf9\x8b\x97\x04\x6d\x86\x61\x38\xe3\xf3\xe7\xcf\ -\xff\xbd\xfd\x43\xff\x1f\x3e\x7a\x8c\x68\xcd\x28\x5e\x58\xbd\x76\ -\x03\x83\x96\x86\x3a\x83\x85\x99\x29\x23\x49\xa9\x0a\x66\x92\x97\ -\x6f\xf0\xff\x6b\xd7\x6f\x92\x64\x3b\xdc\x05\x47\x8e\x1e\xff\xcf\ -\xce\xc1\xce\xa0\xae\xae\xca\x48\x6a\xb2\x66\x62\x60\x60\x60\x38\ -\x7f\xf1\x12\x83\xb9\x99\x29\x03\x13\x23\xc9\xfa\x19\x18\x6d\x1c\ -\xdc\xe0\x09\x07\x96\xbe\x49\x76\x01\x39\x1a\x51\x0c\x40\x4e\xbe\ -\xa4\x02\x00\xad\xa2\x1b\xde\x5e\x31\x49\x9d\x00\x00\x00\x00\x49\ -\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x01\xb4\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x09\x12\ -\x0c\x26\x25\x92\x20\x84\x9d\x00\x00\x01\x34\x49\x44\x41\x54\x38\ -\xcb\x95\x92\xcb\x4a\xc3\x50\x10\x86\xff\xe4\x39\x2a\xd4\xcb\x4e\ -\x14\xd1\x85\x28\x15\x22\x48\x84\x6c\xfa\x0e\x42\x41\x44\x41\x50\ -\x14\x2c\x0a\xae\xc5\x5b\x88\x16\x57\x22\xf8\x0c\x82\x20\x28\x54\ -\xeb\xc2\xa2\xa1\x4b\x9b\x6e\xec\xa2\x49\xbb\x4c\xe2\xd2\xdf\x8d\ -\x09\x4d\x13\xdb\x93\x81\x03\x03\x33\xdf\x37\xcc\x61\x24\x92\x48\ -\x1b\x0b\x8b\xcb\x61\x2e\x89\x0a\x6c\xdb\x61\xf9\xa5\x82\x0f\xb3\ -\x06\xc7\x69\xc3\xb2\x1a\xf8\x21\x07\x0b\xea\x56\x83\xd7\x37\xb7\ -\x78\xab\xbe\x63\x6c\x74\x04\x33\xd3\x53\x98\x9c\x18\xc7\xd6\x4e\ -\x11\x4f\x0f\x77\x12\x48\x26\x3e\xcf\xf3\xa0\x1b\x25\x2a\x4b\x1a\ -\x8f\x8e\xcf\xd9\xee\x74\xaa\x41\x2d\xa7\xa8\x0c\xf2\x44\xd8\x75\ -\x5d\xac\x14\xd6\xb8\xbb\x77\x40\xdb\x71\xd8\x5d\xeb\x86\x13\x05\ -\xae\xeb\xa2\xb0\xba\x4e\xe3\xf2\x8a\xbe\xff\x8d\x7e\x70\x4c\x10\ -\x4c\x3e\xd5\x2f\x62\x8d\x49\x70\x4c\xa0\x1b\x25\x6e\x6c\x6e\xd3\ -\xf3\x7d\x21\x38\x22\xf8\xac\x5b\x54\xb5\x7c\xe4\xb3\x06\xc1\x11\ -\x41\x71\xff\x90\x27\x67\x06\xd3\xc0\xa1\xa0\xd5\xb2\xa9\x6a\x79\ -\x7e\x35\x9b\x4c\x03\x93\x84\x0c\x00\xcf\x95\x57\x0c\x67\xb3\x18\ -\xca\x64\xa4\xbf\x53\x65\xf9\xf1\x5e\xea\x73\xca\xe1\xf5\xc9\x00\ -\x60\x9a\x35\xcc\xcf\xcd\x42\x04\xee\x0d\x29\xa7\xa8\x91\x5b\x16\ -\x9d\x1c\x46\xb0\xab\xe8\xce\xbd\xbd\xf2\xbf\x66\xc1\xf8\x05\xc0\ -\x3d\xd9\x8c\xc7\x90\xf9\x30\x00\x00\x00\x00\x49\x45\x4e\x44\xae\ -\x42\x60\x82\ -\x00\x00\x02\xc9\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x18\x00\x00\x00\x18\x08\x03\x00\x00\x00\xd7\xa9\xcd\xca\ -\x00\x00\x00\x03\x73\x42\x49\x54\x08\x08\x08\xdb\xe1\x4f\xe0\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0d\xd7\x00\x00\x0d\xd7\x01\ -\x42\x28\x9b\x78\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\ -\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\x70\ -\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x01\x29\x50\x4c\x54\ -\x45\xff\xff\xff\x80\x80\x80\x66\x99\x66\x5a\x87\x5a\x5d\x8b\x5d\ -\x58\x8d\x58\x58\x8f\x58\x5a\x8a\x5a\x57\x8f\x57\x5c\x8d\x5c\x58\ -\x8d\x58\x5a\x8c\x5a\x5a\x8e\x5a\x58\x8b\x58\x5b\x8d\x5b\x59\x8b\ -\x59\x58\x8d\x58\x5b\x8a\x5b\x59\x8c\x59\x5b\x8b\x5b\x59\x8c\x59\ -\x5b\x8c\x5b\x5b\x8c\x5b\x5a\x8d\x5a\x59\x8c\x59\x5b\x8d\x5b\x5c\ -\x8e\x5c\x5c\x8e\x5c\x5c\x8e\x5c\x5d\x8f\x5d\x5c\x8f\x5c\x5d\x8f\ -\x5d\x5e\x8f\x5e\x5c\x8e\x5c\x5d\x8f\x5d\x5c\x8f\x5c\x5d\x8f\x5d\ -\x5c\x8f\x5c\x5c\x8e\x5c\x5b\x8d\x5b\x5c\x8e\x5c\x5c\x8e\x5c\x5c\ -\x8f\x5c\x5d\x8e\x5d\x64\x97\x64\x5c\x8f\x5c\x5e\x91\x5e\x66\x95\ -\x66\x5e\x8f\x5e\x60\x92\x60\x69\x96\x69\x69\x9b\x69\x6e\xa0\x6e\ -\x61\x93\x61\x65\x97\x65\x68\x96\x68\x5c\x8e\x5c\x7a\xa7\x7a\x5f\ -\x91\x5f\x7a\xaa\x7b\x72\xa4\x72\x5a\x8c\x5a\x86\xb3\x87\x8a\xb5\ -\x8b\x5a\x8d\x5a\x5a\x8c\x5a\x5c\x8d\x5c\x71\x9d\x72\x76\xa8\x76\ -\x7b\xad\x7b\x7c\xae\x7c\x7d\xaf\x7d\x7e\xb0\x7e\x7f\xb1\x7f\x81\ -\xb3\x81\x84\xb6\x84\x85\xb4\x85\x87\xb9\x87\x88\xba\x88\x8a\xbc\ -\x8a\x8c\xbe\x8c\x90\xad\x90\x91\xaa\x91\x91\xc0\x92\x93\xc2\x93\ -\x97\xc1\x98\x97\xc4\x97\x99\xc6\x9a\xa3\xcb\xa4\xa8\xce\xa8\xa9\ -\xcf\xaa\xaf\xd2\xb0\xb0\xd3\xb1\xb3\xd1\xb5\xb3\xd4\xb4\xb4\xd6\ -\xb6\xb5\xd3\xb7\xb6\xd3\xb7\xb7\xd4\xb8\x04\xb7\xa8\x69\x00\x00\ -\x00\x41\x74\x52\x4e\x53\x00\x02\x05\x11\x16\x1d\x20\x25\x29\x2f\ -\x31\x33\x36\x37\x38\x39\x3a\x3b\x3c\x51\x59\x5f\x68\x69\x6d\x86\ -\x8e\x9e\xb9\xbf\xc1\xc2\xc4\xc7\xdc\xe3\xe4\xe8\xef\xf0\xf0\xf1\ -\xf1\xf2\xf2\xf3\xf3\xf3\xf4\xf4\xf4\xf4\xf4\xf5\xf5\xf5\xf6\xf6\ -\xf7\xf9\xfa\xfb\xfc\xfc\xfe\x65\x15\xb1\xed\x00\x00\x00\xc5\x49\ -\x44\x41\x54\x28\xcf\x63\x60\x20\x1b\x08\xc9\x60\x17\x17\xd4\x77\ -\xc2\x2a\x2e\x60\x94\xe4\x8c\x4d\x9c\xdf\x28\x31\xc2\x11\x8b\x38\ -\x9f\x79\x42\x44\x80\x95\x23\x18\xd8\xca\x8b\xc2\xc5\x79\x41\xe2\ -\x30\xe0\xef\x6a\x2a\x05\x15\x67\x37\x88\x45\x88\x03\x81\x9f\xa1\ -\x30\x44\x82\x4d\xc3\x27\x00\x05\xb8\x48\x43\xb5\x70\x6a\xba\x03\ -\xb9\x81\x41\x40\x10\x18\x1d\x1e\x10\xe0\xa5\x04\xb3\x84\x4b\xcb\ -\x23\x00\x6c\xb9\x83\x9e\x75\x5c\x58\x80\xbf\x2d\xdc\x7a\x6e\x6d\ -\x8f\x00\x90\x73\x99\x38\xe4\xec\xa3\x02\xbc\x55\x11\x0e\xe6\xd1\ -\xf6\x84\xf8\x43\xc2\x32\x26\xc0\x4d\x01\xd9\x2b\xba\x60\x09\x56\ -\xb5\xd0\xc8\x00\x1d\x11\x94\xc0\x02\x06\x22\xb3\x98\x8a\x5d\x7c\ -\x88\x8d\x12\x23\x7a\x08\xa8\x1b\xbb\xc7\x04\x9b\x29\xb2\x60\x04\ -\x8d\xa3\xaf\x9b\x89\x85\x24\x23\x66\x98\x29\x6b\xca\x8a\xb3\x31\ -\x0c\x02\x00\x00\xb1\xe8\x2a\xb8\x7c\xb7\x13\xb6\x00\x00\x00\x00\ -\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x01\x90\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x09\x12\ -\x0c\x24\x3b\x5a\x19\xdb\x7c\x00\x00\x01\x10\x49\x44\x41\x54\x38\ -\xcb\x63\xfc\xff\xff\x3f\x03\xa9\xc0\xd6\xd1\x1d\xae\x89\x89\x14\ -\x8d\x47\x8e\x1e\xff\x3f\x79\xda\xcc\xff\x0c\x0c\x0c\x0c\x87\xf7\ -\xef\x64\x64\x60\x60\x60\x60\x21\xa4\xe9\xcb\x97\x2f\x0c\xab\xd7\ -\x6e\xf8\xbf\x61\xe3\x16\x06\x3e\x3e\x5e\x06\x7d\x3d\x5d\x54\x57\ -\xfc\xff\xff\x1f\x27\x3e\x74\xf8\xe8\x7f\xbf\xc0\xf0\xff\x85\x25\ -\x15\xff\x2f\x5f\xb9\xf6\x1f\x9b\x1a\x9c\x9a\xdb\x3a\x7b\xfe\x7b\ -\xfb\x87\xfe\x3f\x72\xf4\xf8\x7f\x7c\x96\xe0\xd4\x9c\x9d\x57\xfc\ -\xff\xd1\xe3\xc7\x78\x35\x63\x35\xa0\xad\xb3\xe7\x7f\x64\x4c\xd2\ -\xff\xd7\x6f\xde\xac\x22\xa4\x19\xc3\x80\xc3\x47\x8e\xfd\x0f\x8b\ -\x8c\xff\xff\xfc\xc5\xcb\xff\xc4\x68\x46\x31\xe0\xf3\xe7\xcf\x0c\ -\xde\xfe\xa1\xff\x0f\x1f\x3d\x46\xb4\xe6\xff\xff\xff\x23\xd2\xc1\ -\xea\xb5\x1b\xfe\x6b\x69\xa8\x33\x58\x98\x99\x32\x92\x94\xaa\x60\ -\x26\x79\xf9\x06\xff\xbf\x76\xfd\x26\x49\xb6\xc3\x5d\x70\xe4\xe8\ -\xf1\xff\xec\x1c\xec\x0c\xea\xea\xaa\x8c\x64\x25\xe5\xf3\x17\x2f\ -\x31\x98\x9b\x99\x32\x30\x31\x32\x92\x9c\x2f\x18\x6d\x1c\xdc\xe0\ -\xa6\xc1\xd2\x37\x31\x36\xc3\x81\x8d\x83\xdb\xff\xff\xff\xff\xc3\ -\x69\x62\x30\xb2\x5a\x26\x9c\x26\x13\x09\x00\x75\xdd\xa2\x2d\xa5\ -\xc8\x65\x49\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x03\xac\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0\x77\x3d\xf8\ -\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xaf\xc8\x37\x05\x8a\xe9\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x03\x3e\x49\x44\x41\x54\x78\xda\x62\ -\x60\xc0\x0f\x74\x81\xb8\x0e\x88\x0f\x03\xf1\x2b\x20\xfe\x0f\xc5\ -\x4f\x80\x78\x2f\x10\x57\x00\xb1\x22\x9a\x9e\xff\xc8\x1c\x80\x00\ -\x62\xc4\x61\xb0\x16\x10\xb7\x03\xb1\x1f\x88\xa3\xae\xae\xce\x60\ -\x6f\x6f\x0f\x97\xfc\xf5\xeb\x17\xc3\xc1\x83\x07\x19\xee\xdf\xbf\ -\x0f\x13\x5a\x00\xc4\xf5\x40\xfc\x10\xdd\x6c\x80\x00\xc2\x86\x32\ -\x81\xf8\x27\xc8\x25\xf9\xf9\xf9\xff\x1f\x3e\x7c\xf8\x1f\x08\x18\ -\xb0\xe1\x1b\x37\x6e\xfc\x8f\x8a\x8a\xfa\x8f\xe4\x33\x3f\x24\x36\ -\xd8\x27\x00\x01\x84\x8e\x7a\x41\x12\x8a\x8a\x8a\xff\x0f\x1c\x38\ -\x00\x37\x18\x08\x94\x80\x38\x0d\x88\xcb\xa1\x18\xc4\x56\x42\x92\ -\xff\x2f\x2a\x2a\x0a\x33\x34\x07\xd9\x12\x80\x00\x42\x77\xf9\xff\ -\x84\x84\x84\xff\x9f\x3f\x7f\xfe\x0f\xd5\x68\x0c\xc4\xbb\xd1\x34\ -\x20\x63\xb0\x1c\xcc\x12\x24\xec\x0d\x33\x1c\x20\x80\x90\xc3\xfc\ -\xa7\xab\xab\x2b\xba\xab\xdf\x41\x15\x82\xe8\x99\x48\x3e\x58\x85\ -\x26\x07\x37\x5c\x57\x57\x17\xc6\x16\x05\x19\x02\x10\x40\x30\xb4\ -\x85\x87\x87\xe7\xff\xdb\xb7\x6f\x91\x2d\x98\x09\x55\x78\x06\x88\ -\x05\xd1\xc3\x1f\x2a\x77\x17\x66\xb0\x98\x98\x08\xdc\x27\xcc\xcc\ -\xcc\x20\xc6\x64\x10\x07\x20\x80\x40\xc8\x00\x24\xd8\xde\xde\xfe\ -\x1f\xcd\x00\x98\xcb\x42\xb1\x19\x8e\x1c\x2c\x20\xc3\x41\x8e\x80\ -\x89\xa7\xa5\xa5\xfd\x87\x26\x14\x09\x80\x00\x02\x29\x6a\x67\x63\ -\x63\x43\x71\x3d\x5a\x98\x86\xe2\x10\x47\x36\x1c\x1e\x17\x20\x7c\ -\xf5\xea\x55\x98\x7c\x26\x40\x00\x81\x34\x9c\xb1\xb1\xb1\xf9\x8f\ -\xc5\x95\x67\x70\x05\x11\xb2\x05\xc8\x3e\x42\xc6\x72\x72\x72\x20\ -\x89\x75\x00\x01\xc4\x04\xca\x47\x66\x66\x66\xd8\x92\x6c\x27\x94\ -\x06\xa5\xa4\xbb\x8c\x8c\x8c\x33\xc5\xc5\x45\xff\x03\x69\x90\x61\ -\xf0\x4c\x84\xce\x87\x87\xbb\x81\x01\x38\x8f\x02\x04\x10\xc8\x02\ -\x1e\x60\x1a\xc6\x30\x1d\xa8\x69\x35\x90\x4a\x07\xe2\xf7\xd0\xf0\ -\x4d\x7b\xf5\xea\x0d\xdc\x50\x98\x03\xb0\x19\x0e\x02\xd2\xd2\xd2\ -\x20\x4a\x14\x20\x80\x98\xf0\x15\x44\x40\xcd\xb3\x80\x94\x32\xb4\ -\xcc\xd9\x03\x13\x07\x86\x3b\x03\x34\xb9\x82\x2c\xdb\x0d\xc4\x82\ -\x38\x8c\xf8\x0b\x10\x40\x20\x0b\xbe\xbc\x7b\xf7\x0e\x9f\x25\xef\ -\x81\xb8\x13\x88\x5d\xa1\xae\x75\x05\xfa\x04\xe4\xfa\x7b\x50\x25\ -\x2e\xa0\x48\x46\xb7\xe4\xe9\xd3\xa7\x20\xea\x35\x40\x00\x81\x88\ -\x63\xc0\x82\x0c\x67\x79\x83\x0f\x43\x7d\x01\x8b\xf0\x72\x2c\x91\ -\xbc\x08\x20\x80\x40\x8a\x5a\x38\x39\x39\xd1\x33\x19\xac\x78\xe8\ -\x20\xc2\x12\x58\x86\xdc\x8d\x25\x99\xa6\x01\x04\x10\x28\x88\x16\ -\x7f\xff\xfe\xfd\xef\xac\x59\xb3\x90\x7d\x78\x16\xc9\xfb\x84\xc0\ -\x3d\x74\x81\x89\x13\x27\x82\xa8\x2f\x40\xbc\x06\x20\x80\x60\x62\ -\x8b\x90\x8b\x0a\xa8\xc1\x70\x57\x10\xf0\xc1\x5d\x64\xdf\x82\x8a\ -\x70\x68\x51\xd1\x02\x92\x04\x08\x20\x18\x92\x03\xe2\x6f\x68\x85\ -\xdd\x2a\xe4\xf0\xc5\x92\xd9\x94\x90\xd4\x80\x52\x89\xd2\xb7\x6f\ -\xdf\x60\x85\x1d\xa8\xc6\xe3\x01\x29\x02\x08\x20\x64\x14\x08\x2b\ -\xae\x41\x0a\xa1\x69\xff\x0c\x96\xe2\x79\x37\x16\x71\x60\x1e\x79\ -\xf5\xdf\xc3\xc3\x03\x56\x06\xc1\x73\x2e\x40\x00\xa1\xa3\x7c\xf4\ -\x0a\x07\xea\xfa\xbb\x38\xea\x03\x90\x0f\x8c\xd7\xad\x5b\x07\xab\ -\x70\x40\x86\x87\x23\x1b\x08\x10\x40\xd8\x90\x07\x10\x7f\x06\x19\ -\x50\x54\x54\xf4\xff\xc9\x93\x27\x30\x8b\x04\xa1\x71\x03\xc6\x20\ -\xb1\x7b\xf7\xee\x21\x57\x99\xcf\xb1\xb9\x1c\x20\x80\x70\x55\xcc\ -\xa0\xb2\xa3\x11\x88\x93\x81\x98\x0d\x54\xae\x80\xca\x2b\x60\xa9\ -\x0b\xae\xf0\x41\x60\xdf\xbe\x7d\x0c\x77\xee\xdc\x81\xa5\x16\x50\ -\xb2\xe9\x06\xe2\x8f\xe8\x06\x01\x04\x10\x23\x01\xdf\x88\x42\xab\ -\x3f\x3f\x68\x42\x90\x00\x35\x2a\xa0\x91\x7a\x07\x9a\x0c\x77\x63\ -\x33\x18\x06\x00\x02\x0c\x00\x8c\x09\x7e\xe9\xbb\x04\xf1\x3f\x00\ -\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x03\xf8\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0\x77\x3d\xf8\ -\x00\x00\x00\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\ -\xa7\x93\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\ -\x0b\x13\x01\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\ -\xe0\x01\x04\x0a\x35\x39\x98\x31\xc0\xad\x00\x00\x03\x85\x49\x44\ -\x41\x54\x48\xc7\xb5\x95\x7f\x48\x9d\x55\x18\xc7\x3f\xe7\xbd\xbf\ -\x76\xef\xeb\xee\xf5\x07\x13\x02\x29\x43\xfd\x4b\x07\x26\x86\x8b\ -\x02\x9b\xe8\x4d\xdd\x46\xe9\x82\x8a\x88\xa8\xa0\x11\x51\xa3\x06\ -\x61\xad\xb5\xa2\x02\xa3\xb5\x6a\x11\x04\xfd\x5d\x54\xe6\xe0\xfa\ -\x87\x0c\xca\xdc\x5c\x5d\xae\xab\x61\xa4\x0d\x12\x87\x91\xde\x14\ -\x41\xec\x72\x6f\xde\xf7\xde\xf7\x3d\xe7\xf4\x87\xf9\x32\x7c\x4d\ -\xc7\x72\x07\xce\x3f\x0f\xe7\x79\xbe\xcf\xf7\xfb\xfc\x38\x42\x6b\ -\xcd\x8d\x1c\x21\x04\x00\xdb\xf9\x1b\xfc\xcf\xb3\x0e\xb4\xe3\x00\ -\xd7\x66\xbe\x15\x88\xd8\x48\xb1\xaf\xaf\xcf\xf3\xa8\xac\xac\x8c\ -\x91\x91\x11\x2d\x84\x20\x9f\xcf\x63\x9a\x26\x4d\x4d\x4d\x42\x4a\ -\xe9\x79\xdb\xdf\xdf\xbf\x3d\x03\x21\x04\xa6\x69\xea\x6c\x36\xab\ -\x4b\x4b\x4b\xb5\xe3\x38\xba\xa6\xa6\x86\xf7\xde\x3f\xc3\xc7\x9f\ -\x7c\x4a\x3a\x9d\x26\x14\x0a\xe9\x99\x99\x19\x3d\x3d\x3d\xad\x63\ -\xb1\x98\xbe\x6e\x89\x84\x10\xc4\x62\x31\x9d\x4c\x26\xe9\xe9\x39\ -\xcc\xf2\xf2\x32\x89\x44\x82\x74\x3a\x4d\xa1\x50\xa4\x58\x28\x12\ -\x08\x04\x18\x1e\x1e\xa6\x61\xef\x5e\x9e\x78\xf2\x29\x92\xc9\x24\ -\xe5\xe5\xe5\x5a\x08\xe1\x91\xcb\xbf\x11\xc0\xef\xf7\x33\x31\x31\ -\xc1\xeb\x6f\xbc\x49\x28\x6c\xd2\x7d\xe8\x01\x76\xef\x2e\xa5\xf3\ -\xc0\x21\xa4\x5a\x4b\xf4\xdd\xd3\x1f\x72\xf1\xfc\x28\x5d\x07\xef\ -\xc7\xef\xf7\xd1\xd2\xb2\x8f\x85\x85\x05\x22\x91\x88\x9b\xe4\xba\ -\xf4\x1e\x06\x3e\x9f\x8f\x68\x34\xca\x89\x13\xc7\xc9\xe7\x2d\x76\ -\xed\x8a\xd0\xd1\xd9\x8d\x94\xd2\xbd\xe1\xb0\x49\xbc\xeb\x20\x52\ -\x4a\xae\x4c\x4d\x32\x34\x94\x20\x16\x8b\x61\x18\x86\xa7\xf0\x1e\ -\x00\x29\x25\xb5\xb5\xb5\xfc\xb5\xb2\xc2\xd8\x85\xef\x50\x4a\xa1\ -\x94\xc2\x76\x6c\x72\x7f\x67\xb1\xf2\x79\x94\x94\xae\xfd\xe2\xd8\ -\x18\x15\x15\x15\x04\x83\x41\x94\x52\x9e\xee\xf2\x48\x34\x35\x35\ -\xa5\x85\x10\x54\x57\x57\x73\xf7\x3d\xad\x48\xa5\x50\xd2\xe1\xca\ -\xaf\x93\x0c\x0e\x7c\x45\x30\x14\xe2\xe9\x23\xcf\x52\xb1\x67\x0f\ -\x00\xad\x6d\x6d\x7c\xfd\xe5\xe7\x5c\xbe\x7c\x19\x9f\xcf\xa7\xd7\ -\x3b\x73\x9d\x81\xa7\x4d\xbb\xba\xba\xf4\xdb\xfd\xa7\x00\x58\x5d\ -\xcd\xe3\x38\x0e\xb6\x74\x38\xfe\xd2\x8b\x34\x34\x34\xb0\xb8\xb8\ -\x48\x38\x62\xf2\xdc\xd1\x63\x00\x04\x02\x01\x22\x91\x30\x42\xc0\ -\xc9\x57\x5f\x26\x91\x48\x88\x2d\x8b\x3c\x37\x37\xc7\xca\x4a\x66\ -\x43\xab\x19\x6e\x30\x29\x25\x42\x80\x94\xea\x5f\x49\x0b\x58\x56\ -\x01\x80\xd9\xd9\xd9\xed\x07\xad\xad\xad\x4d\x2f\x2d\x2d\x11\x08\ -\x04\x78\xe7\xf4\x47\xae\xfd\xcf\xb9\x3f\x38\x37\x3c\x44\x38\x1c\ -\xe1\x91\xc7\x1e\x07\xe1\x03\xe0\xf7\xab\x33\x9c\xf9\xe0\x14\x4a\ -\x29\xa2\xd1\x28\xa9\x54\x6a\x6b\x06\x9d\x9d\x9d\xc2\x71\x1c\x3d\ -\x38\x38\xc8\xe8\xb7\xe7\x68\xbd\xb7\x03\x80\x5b\xaa\x6e\xe5\xc8\ -\x33\x47\x91\x5a\xe3\xd8\x36\xb0\x36\xc5\xe3\xa9\x24\x95\x95\x95\ -\xc4\xe3\x71\x0c\xc3\x10\xdb\x0e\x9a\x65\x59\xcc\xcf\xcf\x63\x9a\ -\x25\x34\x35\xdf\x85\x54\x1a\xa9\x34\x8e\xed\xb0\x6a\x15\x28\x14\ -\x8a\xae\x4d\x2a\x4d\xf3\x9d\x2d\x64\x32\x19\xd7\x77\x5b\x00\xdb\ -\xb6\xc9\x64\x32\xf4\x3c\xf8\x30\xa6\x59\x42\x2e\x97\xe5\xc2\xf9\ -\x6f\x90\x4a\xb9\x37\x97\xcb\x72\x29\xf5\x3d\x52\x29\x6e\xbb\xbd\ -\x96\x8e\xfb\x0e\x60\xdb\xf6\xa6\x00\xfe\xcd\x56\x45\x5d\x5d\x1d\ -\x67\x07\xbe\xe0\xf0\x43\x8f\x72\x76\xe0\x33\xac\x7c\x9e\x1f\xc7\ -\x53\x3c\xff\xc2\xda\x22\xec\x7f\xeb\x35\x0c\xc3\x60\xf2\x97\x9f\ -\xb9\xa3\x79\x1f\x3f\x5d\x4a\x12\x8f\xc7\xaf\x8f\x81\xd6\x9a\x60\ -\x30\x28\xf6\xef\x6f\x65\xfc\x87\x51\x1a\xea\xeb\xe9\xee\xee\xc6\ -\x34\x23\x18\x3e\x1f\xc2\x10\x28\xa5\xe8\xed\xed\xa5\xc4\x8c\x70\ -\xf5\xb7\x29\xda\xdb\xdb\x29\x14\x0a\x62\xb3\xcf\xc7\xc3\xc0\x71\ -\x1c\x2c\xcb\x22\x1c\x0e\x8b\xc6\xc6\x46\x97\x55\x2e\x9b\xd3\x27\ -\x5f\x39\x86\xd6\x8a\xaa\xaa\x2a\xfc\x7e\xbf\xa8\xaf\xaf\x77\x7d\ -\x8a\xc5\x22\x4a\xa9\xad\xdb\xf4\xda\x4d\x78\xa3\x5f\xe9\x7f\x4a\ -\x74\x33\x82\xbb\x00\x37\x2b\xb8\xa7\xc8\x3b\x1d\x7c\xd3\x55\xb1\ -\xd3\xe7\x1f\xf7\xc0\xb4\xb4\x97\x12\xdf\xed\x00\x00\x00\x00\x49\ -\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x03\x78\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x20\x00\x00\x00\x20\x08\x03\x00\x00\x00\x44\xa4\x8a\xc6\ -\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xaf\xc8\x37\x05\x8a\xe9\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x01\x3e\x50\x4c\x54\x45\xf7\xf7\xf7\ -\xfa\xfa\xff\xdc\xdc\xdc\xf2\xf2\xf2\xd0\xd0\xd0\xfc\xfc\xfc\x34\ -\x34\x34\xba\xba\xba\x5d\x5d\x5d\x1c\x1c\x1c\x3a\x3a\x3a\xf0\xf0\ -\xff\x66\x66\x66\x7d\x7d\xff\xce\xce\xce\x36\x36\x36\x92\x92\x92\ -\x1f\x1f\x1f\x33\x33\x33\x30\x30\x30\x70\x70\x70\x6d\x6d\xff\x1b\ -\x1b\x19\xe1\xe1\xe1\x9a\x9a\x9a\xa3\xa3\xa3\xe8\xe8\xe8\x53\x53\ -\x53\x15\x15\x15\x76\x76\xc2\x07\x07\x07\x64\x64\x64\xbd\xbd\xbd\ -\xe9\xe9\xe9\x05\x05\x1c\x18\x17\x12\x74\x74\xff\xef\xef\xef\x7b\ -\x7b\x7b\x7c\x7c\xff\x6e\x6e\x6e\x6b\x6c\xff\x03\x03\x24\xc1\xc1\ -\xc1\xa6\xa6\xff\xf8\xf8\xff\xf3\xf3\xf3\x68\x69\xaa\x8b\x8b\xff\ -\x83\x83\xd0\x97\x97\xc7\x8d\x8d\x8d\x6e\x6e\xff\x7e\x7e\x7e\x5f\ -\x5f\xff\x16\x16\x13\x37\x37\x37\x21\x21\x21\xeb\xeb\xff\x69\x69\ -\x9e\x78\x78\x78\x42\x42\x42\x79\x79\x79\x43\x43\x43\x2f\x30\xbf\ -\x40\x40\xdf\x45\x45\x45\x87\x88\xff\x00\x00\x00\x56\x56\x56\x37\ -\x37\x8e\x1d\x1d\x1d\xed\xed\xed\x68\x68\x68\x84\x84\x84\xde\xde\ -\xde\x67\x67\x67\x87\x87\xd7\xeb\xeb\xeb\x3f\x3f\x3f\xf3\xf3\xff\ -\x17\x17\x12\x3b\x3b\x3b\x6f\x6e\xff\x93\x93\x93\x6e\x6e\xe0\x9b\ -\x9b\x9b\x74\x74\xc5\x98\x98\x98\x96\x96\x96\x81\x81\x81\x7b\x7b\ -\xc6\x70\x70\xb6\x14\x14\x14\x58\x58\x58\xe6\xe6\xe6\x13\x13\x13\ -\xad\xad\xad\xb8\xb8\xb8\x23\x23\x23\x71\x71\x71\x61\x61\xa4\x2e\ -\x2e\x2e\xd1\xd1\xd1\x7e\x7e\xff\xff\xff\xff\xda\x7b\x6d\x7f\x00\ -\x00\x00\x6a\x74\x52\x4e\x53\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\ -\x00\xbc\xe4\xdc\xe3\x00\x00\x01\x4a\x49\x44\x41\x54\x78\xda\x62\ -\xc8\x24\x00\x00\x02\x88\x81\x90\x02\x80\x00\x22\xa8\x00\x20\x80\ -\x08\x2a\x00\x08\x20\x82\x0a\x00\x02\x88\xa0\x02\x80\x00\x22\xa8\ -\x00\x20\x80\x08\x2a\x00\x08\x20\x82\x0a\x00\x02\x88\xa0\x02\x80\ -\x00\x22\xa8\x00\x20\x80\xb0\x29\x60\x72\x97\xf6\x83\x73\x00\x02\ -\x08\x4d\x01\xa3\x0e\x6f\x86\x11\xbf\x08\x47\x9a\x44\x66\xa6\x07\ -\x58\x04\x20\x80\xd0\x14\x18\x38\xca\x86\x6b\xa5\x66\x66\xb2\x73\ -\x05\xf9\xc8\x83\x45\x00\x02\x08\x55\x41\x80\xa8\x6f\xa0\xb9\x7e\ -\x30\x37\x90\x19\xc6\xc9\xc6\x02\x12\x02\x08\x20\x54\x05\xdc\x9a\ -\xd6\x62\xca\xd1\x66\x56\x20\x36\x73\x94\xa5\x67\x7c\x66\x26\x40\ -\x00\xa1\x59\xe1\xa6\x64\x18\xe3\xa0\xae\x0b\xe1\x78\x73\x08\x0a\ -\x64\x02\x04\x10\xaa\x02\x0d\x8b\x50\x15\x13\x67\x04\x5f\x21\x32\ -\x13\x20\x80\x90\x15\xb0\xc6\x09\x4b\x65\x32\x32\xa2\x9a\x09\x10\ -\x40\x48\x0a\x98\xed\x6d\xf5\x30\x03\x05\x20\x80\x60\x0a\x58\xec\ -\x98\x84\x5c\x59\xb1\x84\x1a\x40\x00\xc1\x14\xa8\x72\xc9\xa5\x60\ -\x0d\x6a\x80\x00\x82\x29\xe0\x4b\xf6\xc2\x1e\x17\x00\x01\x04\x55\ -\x90\x14\x1b\x81\x23\xb2\x00\x02\x08\xa2\x40\x32\x21\x11\x57\x6c\ -\x02\x04\x10\x58\x81\xb1\x8c\x36\xce\xe8\x06\x08\x20\x06\x45\x86\ -\x4c\x1b\xce\x74\xdc\xe9\x01\x20\x80\x18\x4c\xfd\x79\xd8\xc4\xf1\ -\x24\x18\x80\x00\x62\x60\x50\x73\x61\xc7\x97\xa2\x00\x02\x08\xe8\ -\x86\x10\x27\x7c\xe9\x0e\x20\x80\x08\xa6\x49\x80\x00\x22\xa8\x00\ -\x20\xc0\x00\xe5\x7f\x88\xd8\x3e\x97\x56\x5a\x00\x00\x00\x00\x49\ -\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x02\x2c\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0\x77\x3d\xf8\ -\x00\x00\x00\x04\x67\x41\x4d\x41\x00\x00\xaf\xc8\x37\x05\x8a\xe9\ -\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\x74\x77\x61\x72\x65\ -\x00\x41\x64\x6f\x62\x65\x20\x49\x6d\x61\x67\x65\x52\x65\x61\x64\ -\x79\x71\xc9\x65\x3c\x00\x00\x01\xbe\x49\x44\x41\x54\x78\xda\x62\ -\xfc\xff\xff\x3f\x03\x2d\x01\x40\x00\x31\x31\xd0\x18\x00\x04\x10\ -\xcd\x2d\x00\x08\x20\x9a\x5b\x00\x10\x40\x2c\xf8\x24\x19\x19\x19\ -\x39\x81\x54\x0e\x10\xab\x60\x91\x7e\x0a\x8c\xbf\x26\x42\x16\x00\ -\x04\x10\x03\x28\x92\xb1\x61\x20\x50\x04\xe2\xe7\x40\x7c\x0b\x88\ -\xff\xa3\x63\x0e\x0e\xf9\xf7\x8e\x8e\xff\xbf\x02\xb1\x31\x2e\x33\ -\x40\x18\x20\x80\x58\x70\xb8\x9c\x19\x48\xad\x00\x62\x09\x20\x7e\ -\x00\xc4\x3d\xe8\x6a\x94\x94\x5a\xdb\x80\x14\x17\x10\x4f\x04\x62\ -\x1b\x5c\x1e\x00\x08\x20\x5c\xae\x6f\x81\xba\xf4\x21\x10\x0b\x61\ -\x53\x03\x74\x79\x31\x10\xff\x07\xe2\xbf\x40\xcc\x83\xcb\x07\x00\ -\x01\xc4\x84\xc5\xf5\xf6\x40\xaa\x02\x88\xff\x02\x71\x22\x50\xd1\ -\x3b\x1c\x6e\xeb\x05\xe2\x5f\xd0\x84\xd2\x82\xcb\x03\x00\x01\x84\ -\xee\x72\x21\xa8\xab\x41\x9c\x16\x7c\x61\x0b\x74\x35\x08\xaf\xc2\ -\xa7\x06\x84\x01\x02\x08\xdd\x82\x15\x50\xc3\x4f\x02\x31\x1b\x3e\ -\x8d\xff\xfe\xe1\x37\x18\x86\x01\x02\x08\xd9\xf0\x64\xa8\xe1\x9f\ -\x41\xc9\x92\x90\x46\x62\x2d\x00\x08\x20\x16\x68\xb8\xab\x03\xa9\ -\x09\xd0\x50\xcb\x02\x4a\xdc\x21\x25\x33\x39\x39\xc1\x93\xaf\xe4\ -\xbe\x7d\x0c\x2f\x91\xe5\x00\x02\x88\x09\x68\x38\x1b\x90\x5e\x04\ -\xc4\x3c\x40\xbc\x12\x68\xf8\x62\x62\x0c\x75\x76\x46\xe1\x82\x7c\ -\xcd\x88\xe4\x48\x38\x00\x08\x20\x50\x0a\x28\x05\x62\x33\x20\x7e\ -\x04\xc4\xe9\x64\x96\x08\x8b\xa0\x74\x00\xba\x04\x40\x00\x31\x41\ -\x33\xca\x32\x20\x8e\x06\xba\xfe\x23\x99\x16\x80\x92\xf5\x3f\x20\ -\xe6\x00\x06\x57\x1e\xb2\x04\x40\x00\x11\x15\x51\xc4\x60\x60\x92\ -\x3d\x8e\x4d\x1c\x20\x80\x18\x69\x5d\xe1\x00\x04\x10\xcd\x8b\x6b\ -\x80\x00\x62\xa1\xb6\x81\xd0\x24\xfb\x07\x88\xb9\x81\x49\xf6\x17\ -\x40\x00\x31\x51\xd9\x70\x50\x92\xff\x0d\x75\x38\xb8\xae\x00\x08\ -\x20\xaa\x5a\x00\x72\x31\x90\xda\x05\xe5\xa6\x80\x08\x80\x00\xa2\ -\x45\x1c\x94\x40\x69\x50\x92\x15\x02\x08\x20\xaa\x25\x53\xb4\x24\ -\xab\x07\x63\x03\x04\x18\x00\xbb\xa5\xf2\xb7\x5a\xd1\x98\xc5\x00\ -\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ -\x00\x00\x02\x63\ -\x89\ -\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ -\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ -\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ -\x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\ -\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\ -\x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xde\x09\x12\ -\x0d\x13\x23\xd8\xb0\x89\x29\x00\x00\x01\xe3\x49\x44\x41\x54\x38\ -\xcb\xa5\x53\x6d\x4b\x14\x51\x18\x3d\x77\x76\x46\x77\xcd\x16\xfd\ -\x90\xdf\xc4\xfa\x05\x61\x6a\x8b\x34\x5b\xb3\x21\x33\x1a\x9b\xa5\ -\x60\x50\x91\x99\xfe\x00\x43\xa2\x17\xca\x52\x72\xe9\xdb\xfe\x86\ -\xb2\x7e\xc1\xd2\x87\x36\xa1\x89\x36\x48\x2d\xb5\x5f\xe0\xca\xd2\ -\x8b\xab\xc2\xca\x8e\xd3\xdb\xcc\x72\xfa\xb4\xc3\x8c\x6e\x2f\xe0\ -\x85\x03\x97\xe7\x39\x3c\x9c\x73\xee\x7d\x04\x49\xec\xe7\x48\x7f\ -\x6a\xc4\x13\xc6\x7f\x4d\x16\xb5\x14\xc4\x13\x06\xdd\x5f\x3f\x60\ -\xdb\x36\x1a\x1a\x0e\x20\x16\x8b\x61\x70\xf0\x1c\x4e\xc5\x4f\x88\ -\x3d\x64\x92\x20\x09\x55\xd3\x59\xc5\x6a\xbe\x00\xc7\x75\x41\x12\ -\x8e\xeb\x62\x35\x5f\xc0\xf0\xc8\x18\xc7\xaf\xdf\xa4\x9f\x4f\x32\ -\x68\x21\x67\x66\x05\x00\x34\x37\x37\x61\x73\xab\x84\xc2\xe7\x22\ -\xbe\xac\x6f\x21\x1c\x89\x20\x9d\x4e\x8b\xc3\x47\xda\x30\x71\xe3\ -\x76\x40\xb2\x5c\xcb\xf7\xa7\xaf\x9b\x3d\xb5\xfc\x1a\xbd\x67\xc4\ -\xfd\xc9\xbb\x5c\x2f\x6e\x04\x07\xc4\x13\x06\xa7\x1e\x4c\xa2\xb5\ -\xb5\x4d\xfc\x2b\xb4\x4b\x97\xaf\x60\x2d\xbf\xe6\xa9\x10\xaa\xa6\ -\xb3\xe2\xfc\xc4\xec\xd3\x67\x22\x14\x0a\x79\xc4\xe1\xab\x23\x1e\ -\x69\xf6\xc9\x63\x6f\x70\xa5\x52\xc1\xb5\xd1\x31\xaf\x27\xe5\xcc\ -\xac\xb0\x2c\x0b\x91\x70\x3d\xea\x14\xd9\x83\x3f\x13\x7f\x3d\x12\ -\xae\x0f\xf4\x64\x00\x68\x3c\xd8\x08\x49\x92\xe0\x57\xe0\xcf\x44\ -\x51\x14\xaf\xe6\xb8\x4e\xf0\x9f\x90\xc4\xad\x3b\xf7\xf8\xe6\xed\ -\x3b\x96\xb6\x2d\x94\xb6\x2d\xa8\x9a\xee\xdd\x77\xe3\xe5\x9c\xc9\ -\xf9\x85\x45\x56\x9f\x11\x24\x31\xbf\xb0\x48\xa3\xef\x2c\xad\x1d\ -\x1b\xaa\xa6\xd3\xfe\xf6\x1d\xb5\x50\xb6\x76\xa0\xf7\x26\x59\x2c\ -\x6e\x78\xff\x40\x06\x80\xd8\xf1\x2e\xd1\xde\x7e\x94\x7d\xc9\x01\ -\xce\xbd\xc8\x88\xdd\x56\xaa\xe1\x4d\x4d\xa7\xd8\xd9\x71\x0c\x2d\ -\x2d\x87\xf6\xee\xc2\xa3\xd4\x43\x71\x52\xed\xc6\xd0\x85\x8b\x5c\ -\x59\xf9\x48\x01\x40\x91\x65\x08\x00\xcb\x4b\xcb\x3c\x3f\x30\x44\ -\x45\xa9\x43\x6a\x66\x5a\xfc\x75\x17\xde\x7f\x58\x62\x26\xf3\x1c\ -\xaf\xcc\xd7\x28\x97\xcb\x88\x46\xa3\x38\x9d\xd0\xd0\xdf\x9f\x44\ -\x57\x67\x87\xf0\x07\x9c\x33\xb3\x42\xec\x77\x9d\x7f\x03\x9f\x33\ -\x06\x25\x20\x1a\x11\xed\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\ -\x60\x82\ -" - -qt_resource_name = "\ -\x00\x07\ -\x07\x3b\xe0\xb3\ -\x00\x70\ -\x00\x6c\x00\x75\x00\x67\x00\x69\x00\x6e\x00\x73\ -\x00\x03\ -\x00\x00\x77\x74\ -\x00\x71\ -\x00\x61\x00\x64\ -\x00\x05\ -\x00\x6f\xa6\x53\ -\x00\x69\ -\x00\x63\x00\x6f\x00\x6e\x00\x73\ -\x00\x18\ -\x06\x3b\x18\x67\ -\x00\x63\ -\x00\x69\x00\x72\x00\x63\x00\x6c\x00\x65\x00\x42\x00\x79\x00\x43\x00\x65\x00\x6e\x00\x74\x00\x65\x00\x72\x00\x52\x00\x61\x00\x64\ -\x00\x69\x00\x75\x00\x73\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x08\ -\x00\x48\x59\x27\ -\x00\x6c\ -\x00\x69\x00\x6e\x00\x65\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0b\ -\x0c\x83\x98\xe7\ -\x00\x63\ -\x00\x6f\x00\x70\x00\x79\x00\x45\x00\x6e\x00\x74\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x17\ -\x00\xd2\x5c\xc7\ -\x00\x73\ -\x00\x65\x00\x74\x00\x63\x00\x75\x00\x72\x00\x72\x00\x6c\x00\x61\x00\x79\x00\x65\x00\x72\x00\x62\x00\x79\x00\x67\x00\x72\x00\x61\ -\x00\x70\x00\x68\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0c\ -\x01\x19\xca\x07\ -\x00\x6c\ -\x00\x65\x00\x6e\x00\x67\x00\x74\x00\x68\x00\x65\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x1a\ -\x05\xdb\x9d\x27\ -\x00\x63\ -\x00\x69\x00\x72\x00\x63\x00\x6c\x00\x65\x00\x42\x00\x79\x00\x43\x00\x65\x00\x6e\x00\x74\x00\x65\x00\x72\x00\x44\x00\x69\x00\x61\ -\x00\x6d\x00\x65\x00\x74\x00\x65\x00\x72\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x10\ -\x00\x81\x61\x07\ -\x00\x6f\ -\x00\x73\x00\x6e\x00\x61\x00\x70\x00\x5f\x00\x65\x00\x78\x00\x74\x00\x49\x00\x6e\x00\x74\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x09\ -\x08\x28\xa4\x67\ -\x00\x73\ -\x00\x63\x00\x61\x00\x6c\x00\x65\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0d\ -\x09\xfd\x5a\xc7\ -\x00\x6f\ -\x00\x73\x00\x6e\x00\x61\x00\x70\x00\x5f\x00\x6e\x00\x65\x00\x61\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x08\ -\x0c\xf7\x58\x07\ -\x00\x74\ -\x00\x65\x00\x78\x00\x74\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x08\ -\x06\x7c\x5a\x07\ -\x00\x63\ -\x00\x6f\x00\x70\x00\x79\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0d\ -\x05\x08\x5a\x27\ -\x00\x6f\ -\x00\x73\x00\x6e\x00\x61\x00\x70\x00\x5f\x00\x63\x00\x65\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x08\ -\x0c\x33\x5a\x87\ -\x00\x68\ -\x00\x65\x00\x6c\x00\x70\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x1d\ -\x00\xee\x27\x47\ -\x00\x61\ -\x00\x72\x00\x63\x00\x42\x00\x79\x00\x53\x00\x74\x00\x61\x00\x72\x00\x74\x00\x43\x00\x65\x00\x6e\x00\x74\x00\x65\x00\x72\x00\x45\ -\x00\x6e\x00\x64\x00\x50\x00\x6f\x00\x69\x00\x6e\x00\x74\x00\x73\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0d\ -\x05\x1e\x5a\x27\ -\x00\x6f\ -\x00\x73\x00\x6e\x00\x61\x00\x70\x00\x5f\x00\x69\x00\x6e\x00\x74\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0d\ -\x09\x0e\x5a\x27\ -\x00\x6f\ -\x00\x73\x00\x6e\x00\x61\x00\x70\x00\x5f\x00\x65\x00\x6e\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0e\ -\x04\x87\x12\xc7\ -\x00\x64\ -\x00\x69\x00\x6d\x00\x41\x00\x6c\x00\x69\x00\x67\x00\x6e\x00\x65\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0a\ -\x02\xca\xf7\x27\ -\x00\x66\ -\x00\x69\x00\x6c\x00\x6c\x00\x65\x00\x74\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x17\ -\x01\xbb\x42\xc7\ -\x00\x61\ -\x00\x72\x00\x63\x00\x42\x00\x79\x00\x53\x00\x74\x00\x61\x00\x72\x00\x74\x00\x45\x00\x6e\x00\x64\x00\x52\x00\x61\x00\x64\x00\x69\ -\x00\x75\x00\x73\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0a\ -\x09\xc9\xee\xe7\ -\x00\x6f\ -\x00\x66\x00\x66\x00\x73\x00\x65\x00\x74\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0a\ -\x08\xab\x7a\x07\ -\x00\x72\ -\x00\x6f\x00\x74\x00\x61\x00\x74\x00\x65\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x08\ -\x0b\xb2\x58\x47\ -\x00\x72\ -\x00\x65\x00\x64\x00\x6f\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0d\ -\x0d\x81\x36\x67\ -\x00\x64\ -\x00\x69\x00\x6d\x00\x4c\x00\x69\x00\x6e\x00\x65\x00\x61\x00\x72\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0d\ -\x0b\xcc\x5a\x47\ -\x00\x6f\ -\x00\x73\x00\x6e\x00\x61\x00\x70\x00\x5f\x00\x70\x00\x61\x00\x72\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x21\ -\x0a\x4b\xf7\x47\ -\x00\x73\ -\x00\x65\x00\x74\x00\x63\x00\x75\x00\x72\x00\x72\x00\x75\x00\x70\x00\x64\x00\x61\x00\x74\x00\x65\x00\x61\x00\x62\x00\x6c\x00\x65\ -\x00\x6c\x00\x61\x00\x79\x00\x65\x00\x72\x00\x62\x00\x79\x00\x67\x00\x72\x00\x61\x00\x70\x00\x68\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\ -\x00\x0a\ -\x0a\x2d\x16\x47\ -\x00\x63\ -\x00\x69\x00\x72\x00\x63\x00\x6c\x00\x65\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x14\ -\x0a\xd6\x40\x87\ -\x00\x61\ -\x00\x72\x00\x63\x00\x42\x00\x79\x00\x53\x00\x74\x00\x61\x00\x72\x00\x74\x00\x45\x00\x6e\x00\x64\x00\x54\x00\x61\x00\x6e\x00\x2e\ -\x00\x70\x00\x6e\x00\x67\ -\x00\x11\ -\x04\xd1\xe4\x87\ -\x00\x63\ -\x00\x69\x00\x72\x00\x63\x00\x6c\x00\x65\x00\x42\x00\x79\x00\x33\x00\x54\x00\x61\x00\x6e\x00\x73\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\ -\x00\x08\ -\x09\x00\x58\x27\ -\x00\x74\ -\x00\x72\x00\x69\x00\x6d\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0a\ -\x0c\x4a\xc8\x07\ -\x00\x65\ -\x00\x78\x00\x74\x00\x65\x00\x6e\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0d\ -\x08\x1e\x5a\xc7\ -\x00\x6f\ -\x00\x73\x00\x6e\x00\x61\x00\x70\x00\x5f\x00\x6e\x00\x6f\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x07\ -\x07\x77\x57\xa7\ -\x00\x71\ -\x00\x61\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x13\ -\x0e\x1a\x34\x67\ -\x00\x63\ -\x00\x69\x00\x72\x00\x63\x00\x6c\x00\x65\x00\x42\x00\x79\x00\x32\x00\x50\x00\x6f\x00\x69\x00\x6e\x00\x74\x00\x73\x00\x2e\x00\x70\ -\x00\x6e\x00\x67\ -\x00\x0b\ -\x0e\xcf\x90\xa7\ -\x00\x70\ -\x00\x6f\x00\x6c\x00\x79\x00\x67\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0d\ -\x03\xdf\x23\xe7\ -\x00\x64\ -\x00\x73\x00\x65\x00\x74\x00\x74\x00\x69\x00\x6e\x00\x67\x00\x73\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x1a\ -\x0a\x88\x5b\x67\ -\x00\x61\ -\x00\x72\x00\x63\x00\x42\x00\x79\x00\x53\x00\x74\x00\x61\x00\x72\x00\x74\x00\x43\x00\x65\x00\x6e\x00\x74\x00\x65\x00\x72\x00\x4c\ -\x00\x65\x00\x6e\x00\x67\x00\x74\x00\x68\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x08\ -\x06\xc8\x59\xc7\ -\x00\x6d\ -\x00\x6f\x00\x76\x00\x65\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x13\ -\x0e\x08\x34\x67\ -\x00\x63\ -\x00\x69\x00\x72\x00\x63\x00\x6c\x00\x65\x00\x42\x00\x79\x00\x33\x00\x50\x00\x6f\x00\x69\x00\x6e\x00\x74\x00\x73\x00\x2e\x00\x70\ -\x00\x6e\x00\x67\ -\x00\x0b\ -\x0a\x6e\x26\xc7\ -\x00\x73\ -\x00\x74\x00\x72\x00\x65\x00\x74\x00\x63\x00\x68\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0d\ -\x0e\xbe\x5a\xc7\ -\x00\x6f\ -\x00\x73\x00\x6e\x00\x61\x00\x70\x00\x5f\x00\x6d\x00\x69\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x05\ -\x00\x78\x57\x47\ -\x00\x75\ -\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0a\ -\x09\x6b\xd6\x67\ -\x00\x6d\ -\x00\x69\x00\x72\x00\x72\x00\x6f\x00\x72\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x19\ -\x0b\x66\x4a\x07\ -\x00\x61\ -\x00\x72\x00\x63\x00\x42\x00\x79\x00\x53\x00\x74\x00\x61\x00\x72\x00\x74\x00\x43\x00\x65\x00\x6e\x00\x74\x00\x65\x00\x72\x00\x41\ -\x00\x6e\x00\x67\x00\x6c\x00\x65\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x09\ -\x0b\x07\xba\x27\ -\x00\x70\ -\x00\x65\x00\x64\x00\x69\x00\x74\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x09\ -\x00\x48\xb9\x27\ -\x00\x70\ -\x00\x6c\x00\x69\x00\x6e\x00\x65\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x19\ -\x0e\x64\xab\x07\ -\x00\x61\ -\x00\x72\x00\x63\x00\x42\x00\x79\x00\x43\x00\x65\x00\x6e\x00\x74\x00\x65\x00\x72\x00\x53\x00\x74\x00\x61\x00\x72\x00\x74\x00\x41\ -\x00\x6e\x00\x67\x00\x6c\x00\x65\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x09\ -\x08\x98\x8e\x47\ -\x00\x65\ -\x00\x72\x00\x61\x00\x73\x00\x65\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0d\ -\x0f\x55\x06\x27\ -\x00\x72\ -\x00\x65\x00\x63\x00\x74\x00\x61\x00\x6e\x00\x67\x00\x6c\x00\x65\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0d\ -\x07\x48\x5a\x47\ -\x00\x6f\ -\x00\x73\x00\x6e\x00\x61\x00\x70\x00\x5f\x00\x74\x00\x61\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x11\ -\x05\x36\x57\x07\ -\x00\x6f\ -\x00\x73\x00\x6e\x00\x61\x00\x70\x00\x5f\x00\x65\x00\x6e\x00\x64\x00\x4c\x00\x69\x00\x6e\x00\x65\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\ -\x00\x07\ -\x08\x86\x57\x87\ -\x00\x61\ -\x00\x72\x00\x63\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0b\ -\x0c\x22\x60\xc7\ -\x00\x6d\ -\x00\x62\x00\x75\x00\x66\x00\x66\x00\x65\x00\x72\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0d\ -\x05\xfd\x5a\x47\ -\x00\x6f\ -\x00\x73\x00\x6e\x00\x61\x00\x70\x00\x5f\x00\x71\x00\x75\x00\x61\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x11\ -\x04\x4c\xd7\xa7\ -\x00\x6f\ -\x00\x73\x00\x6e\x00\x61\x00\x70\x00\x5f\x00\x64\x00\x69\x00\x73\x00\x61\x00\x62\x00\x6c\x00\x65\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\ -\x00\x0d\ -\x01\xbe\x5a\x27\ -\x00\x6f\ -\x00\x73\x00\x6e\x00\x61\x00\x70\x00\x5f\x00\x65\x00\x78\x00\x74\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0c\ -\x05\x48\x02\x27\ -\x00\x64\ -\x00\x69\x00\x6d\x00\x53\x00\x74\x00\x79\x00\x6c\x00\x65\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0d\ -\x0b\x8c\x5a\x47\ -\x00\x6f\ -\x00\x73\x00\x6e\x00\x61\x00\x70\x00\x5f\x00\x70\x00\x65\x00\x72\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x16\ -\x0d\x33\xa6\x67\ -\x00\x61\ -\x00\x72\x00\x63\x00\x42\x00\x79\x00\x53\x00\x74\x00\x61\x00\x72\x00\x74\x00\x45\x00\x6e\x00\x64\x00\x41\x00\x6e\x00\x67\x00\x6c\ -\x00\x65\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0c\ -\x04\xef\x90\xa7\ -\x00\x6d\ -\x00\x70\x00\x6f\x00\x6c\x00\x79\x00\x67\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x10\ -\x06\x40\x9d\x27\ -\x00\x61\ -\x00\x72\x00\x63\x00\x42\x00\x79\x00\x33\x00\x50\x00\x6f\x00\x69\x00\x6e\x00\x74\x00\x73\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x1a\ -\x0a\xe6\x4b\xc7\ -\x00\x61\ -\x00\x72\x00\x63\x00\x42\x00\x79\x00\x43\x00\x65\x00\x6e\x00\x74\x00\x65\x00\x72\x00\x53\x00\x74\x00\x61\x00\x72\x00\x74\x00\x4c\ -\x00\x65\x00\x6e\x00\x67\x00\x74\x00\x68\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x08\ -\x04\xb2\x58\xc7\ -\x00\x75\ -\x00\x6e\x00\x64\x00\x6f\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x17\ -\x02\x27\x13\xa7\ -\x00\x61\ -\x00\x72\x00\x63\x00\x42\x00\x79\x00\x43\x00\x65\x00\x6e\x00\x74\x00\x65\x00\x72\x00\x53\x00\x74\x00\x61\x00\x72\x00\x74\x00\x45\ -\x00\x6e\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0a\ -\x0c\x99\x5c\x67\ -\x00\x69\ -\x00\x6e\x00\x73\x00\x65\x00\x72\x00\x74\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0c\ -\x06\xb9\x49\xa7\ -\x00\x76\ -\x00\x61\x00\x72\x00\x69\x00\x61\x00\x62\x00\x6c\x00\x65\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x0c\ -\x08\xa1\xc7\x87\ -\x00\x6f\ -\x00\x73\x00\x6e\x00\x61\x00\x70\x00\x5f\x00\x70\x00\x72\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x09\ -\x0b\x7e\x84\x47\ -\x00\x62\ -\x00\x72\x00\x65\x00\x61\x00\x6b\x00\x2e\x00\x70\x00\x6e\x00\x67\ -\x00\x17\ -\x09\x44\x03\xa7\ -\x00\x63\ -\x00\x69\x00\x72\x00\x63\x00\x6c\x00\x65\x00\x42\x00\x79\x00\x32\x00\x54\x00\x61\x00\x6e\x00\x73\x00\x52\x00\x61\x00\x64\x00\x69\ -\x00\x75\x00\x73\x00\x2e\x00\x70\x00\x6e\x00\x67\ -" - -qt_resource_struct = "\ -\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ -\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ -\x00\x00\x00\x14\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\ -\x00\x00\x00\x20\x00\x02\x00\x00\x00\x44\x00\x00\x00\x04\ -\x00\x00\x00\x66\x00\x00\x00\x00\x00\x01\x00\x00\x02\xf2\ -\x00\x00\x06\x14\x00\x00\x00\x00\x00\x01\x00\x00\xf4\x64\ -\x00\x00\x05\x9a\x00\x00\x00\x00\x00\x01\x00\x00\xed\x2e\ -\x00\x00\x01\x24\x00\x00\x00\x00\x00\x01\x00\x00\x0f\x36\ -\x00\x00\x00\x98\x00\x00\x00\x00\x00\x01\x00\x00\x05\xf5\ -\x00\x00\x01\xe4\x00\x00\x00\x00\x00\x01\x00\x00\x28\x12\ -\x00\x00\x00\xcc\x00\x00\x00\x00\x00\x01\x00\x00\x08\x6c\ -\x00\x00\x02\xa0\x00\x00\x00\x00\x00\x01\x00\x00\x37\x60\ -\x00\x00\x07\x5c\x00\x00\x00\x00\x00\x01\x00\x01\x0f\xe1\ -\x00\x00\x08\x80\x00\x00\x00\x00\x00\x01\x00\x01\x22\x27\ -\x00\x00\x02\x86\x00\x00\x00\x00\x00\x01\x00\x00\x35\x18\ -\x00\x00\x04\xc2\x00\x00\x00\x00\x00\x01\x00\x00\xe0\xb8\ -\x00\x00\x07\x34\x00\x00\x00\x00\x00\x01\x00\x01\x0e\x71\ -\x00\x00\x02\x64\x00\x00\x00\x00\x00\x01\x00\x00\x30\xa4\ -\x00\x00\x08\x6a\x00\x00\x00\x00\x00\x01\x00\x01\x1f\x5a\ -\x00\x00\x03\xee\x00\x00\x00\x00\x00\x01\x00\x00\x50\xc7\ -\x00\x00\x07\xec\x00\x00\x00\x00\x00\x01\x00\x01\x19\x97\ -\x00\x00\x01\xae\x00\x00\x00\x00\x00\x01\x00\x00\x1a\xe3\ -\x00\x00\x02\x24\x00\x00\x00\x00\x00\x01\x00\x00\x29\xa8\ -\x00\x00\x06\xbc\x00\x00\x00\x00\x00\x01\x00\x01\x00\xd8\ -\x00\x00\x07\x7c\x00\x00\x00\x00\x00\x01\x00\x01\x12\xd1\ -\x00\x00\x00\xea\x00\x00\x00\x00\x00\x01\x00\x00\x0c\x1e\ -\x00\x00\x07\x14\x00\x00\x00\x00\x00\x01\x00\x01\x09\x10\ -\x00\x00\x00\x30\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ -\x00\x00\x08\x0a\x00\x00\x00\x00\x00\x01\x00\x01\x1c\x16\ -\x00\x00\x01\x98\x00\x00\x00\x00\x00\x01\x00\x00\x18\xed\ -\x00\x00\x08\xce\x00\x00\x00\x00\x00\x01\x00\x01\x27\x6b\ -\x00\x00\x05\x1c\x00\x00\x00\x00\x00\x01\x00\x00\xe4\xfa\ -\x00\x00\x06\x9c\x00\x00\x00\x00\x00\x01\x00\x00\xfc\x50\ -\x00\x00\x04\x66\x00\x00\x00\x00\x00\x01\x00\x00\x59\xc2\ -\x00\x00\x04\x46\x00\x00\x00\x00\x00\x01\x00\x00\x55\xac\ -\x00\x00\x01\x4a\x00\x00\x00\x00\x00\x01\x00\x00\x12\xb5\ -\x00\x00\x06\xe4\x00\x00\x00\x00\x00\x01\x00\x01\x04\xa5\ -\x00\x00\x06\x64\x00\x00\x00\x00\x00\x01\x00\x00\xf7\xae\ -\x00\x00\x08\xec\x00\x00\x00\x00\x00\x01\x00\x01\x2b\x67\ -\x00\x00\x02\xee\x00\x00\x00\x00\x00\x01\x00\x00\x3b\x40\ -\x00\x00\x04\x16\x00\x00\x00\x00\x00\x01\x00\x00\x53\x07\ -\x00\x00\x02\x44\x00\x00\x00\x00\x00\x01\x00\x00\x2c\xdf\ -\x00\x00\x09\x22\x00\x00\x00\x00\x00\x01\x00\x01\x31\x13\ -\x00\x00\x05\xaa\x00\x00\x00\x00\x00\x01\x00\x00\xef\xfb\ -\x00\x00\x02\xd4\x00\x00\x00\x00\x00\x01\x00\x00\x39\x01\ -\x00\x00\x01\x62\x00\x00\x00\x00\x00\x01\x00\x00\x13\xf9\ -\x00\x00\x03\xa6\x00\x00\x00\x00\x00\x01\x00\x00\x4c\x4d\ -\x00\x00\x03\x5e\x00\x00\x00\x00\x00\x01\x00\x00\x49\x4e\ -\x00\x00\x05\x5e\x00\x00\x00\x00\x00\x01\x00\x00\xe8\xfc\ -\x00\x00\x04\xe2\x00\x00\x00\x00\x00\x01\x00\x00\xe3\x5d\ -\x00\x00\x03\xc0\x00\x00\x00\x00\x00\x01\x00\x00\x4f\x37\ -\x00\x00\x08\x30\x00\x00\x00\x00\x00\x01\x00\x01\x1d\xa2\ -\x00\x00\x05\xfc\x00\x00\x00\x00\x00\x01\x00\x00\xf2\xf2\ -\x00\x00\x05\xc4\x00\x00\x00\x00\x00\x01\x00\x00\xf1\x3f\ -\x00\x00\x09\x0a\x00\x00\x00\x00\x00\x01\x00\x01\x2e\xe3\ -\x00\x00\x07\x9a\x00\x00\x00\x00\x00\x01\x00\x01\x14\xb8\ -\x00\x00\x03\x08\x00\x00\x00\x00\x00\x01\x00\x00\x3e\x06\ -\x00\x00\x03\x3e\x00\x00\x00\x00\x00\x01\x00\x00\x45\x4c\ -\x00\x00\x06\xf8\x00\x00\x00\x00\x00\x01\x00\x01\x07\x35\ -\x00\x00\x01\xce\x00\x00\x00\x00\x00\x01\x00\x00\x1f\x57\ -\x00\x00\x04\x2c\x00\x00\x00\x00\x00\x01\x00\x00\x54\x4e\ -\x00\x00\x00\x7c\x00\x00\x00\x00\x00\x01\x00\x00\x04\x8b\ -\x00\x00\x08\xb4\x00\x00\x00\x00\x00\x01\x00\x01\x23\xbb\ -\x00\x00\x01\x82\x00\x00\x00\x00\x00\x01\x00\x00\x17\x58\ -\x00\x00\x07\xba\x00\x00\x00\x00\x00\x01\x00\x01\x17\xdf\ -\x00\x00\x03\x1e\x00\x00\x00\x00\x00\x01\x00\x00\x40\xe4\ -\x00\x00\x05\x32\x00\x00\x00\x00\x00\x01\x00\x00\xe6\x7d\ -\x00\x00\x04\x7a\x00\x00\x00\x00\x00\x01\x00\x00\xdc\x3a\ -\x00\x00\x06\x2c\x00\x00\x00\x00\x00\x01\x00\x00\xf5\xfb\ -\x00\x00\x05\x7a\x00\x00\x00\x00\x00\x01\x00\x00\xea\x99\ -\x00\x00\x04\xa6\x00\x00\x00\x00\x00\x01\x00\x00\xde\xc6\ -\x00\x00\x06\x7c\x00\x00\x00\x00\x00\x01\x00\x00\xfa\xbb\ -" - -def qInitResources(): - QtCore.qRegisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) - -def qCleanupResources(): - QtCore.qUnregisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) - -qInitResources() +# -*- coding: utf-8 -*- + +# Resource object code +# +# Created by: The Resource Compiler for PyQt5 (Qt v5.15.2) +# +# WARNING! All changes made in this file will be lost! + +from PyQt5 import QtCore + +qt_resource_data = b"\ +\x00\x00\x07\x45\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x67\x72\x61\x79\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\ +\x68\x3a\x31\x37\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6c\ +\x69\x6e\x65\x6a\x6f\x69\x6e\x3a\x72\x6f\x75\x6e\x64\x3b\x73\x74\ +\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\ +\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\ +\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\x6e\x65\x7d\x0d\ +\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x32\x20\x7b\x66\x69\x6c\x6c\ +\x3a\x23\x46\x46\x39\x39\x33\x33\x7d\x0d\x0a\x20\x20\x20\x20\x2e\ +\x66\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x77\x68\x69\x74\x65\ +\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x33\x20\x7b\x66\x69\ +\x6c\x6c\x3a\x23\x34\x36\x38\x32\x43\x38\x3b\x66\x69\x6c\x6c\x2d\ +\x72\x75\x6c\x65\x3a\x6e\x6f\x6e\x7a\x65\x72\x6f\x7d\x0d\x0a\x20\ +\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\x6c\x65\ +\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\x3c\x67\ +\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\x30\x32\ +\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\x61\ +\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\x72\x70\ +\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\x72\x22\ +\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\x61\x73\ +\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\x78\x3d\x22\x2d\x30\x2e\x30\ +\x31\x22\x20\x79\x3d\x22\x2d\x30\x22\x20\x77\x69\x64\x74\x68\x3d\ +\x22\x32\x37\x30\x2e\x39\x33\x22\x20\x68\x65\x69\x67\x68\x74\x3d\ +\x22\x32\x37\x30\x2e\x39\x33\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x67\ +\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\ +\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\ +\x74\x73\x3d\x22\x39\x37\x2e\x33\x36\x2c\x32\x33\x32\x2e\x38\x33\ +\x20\x39\x37\x2e\x33\x36\x2c\x39\x37\x2e\x33\x35\x20\x32\x33\x32\ +\x2e\x38\x34\x2c\x39\x37\x2e\x33\x35\x20\x32\x33\x32\x2e\x38\x34\ +\x2c\x32\x33\x32\x2e\x38\x33\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x20\ +\x3c\x70\x61\x74\x68\x20\x69\x64\x3d\x22\x5f\x31\x22\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\x20\x64\x3d\x22\x4d\x39\ +\x37\x2e\x33\x36\x20\x32\x33\x32\x2e\x38\x33\x6c\x30\x20\x2d\x31\ +\x33\x35\x2e\x34\x38\x20\x31\x33\x35\x2e\x34\x38\x20\x30\x20\x30\ +\x20\x31\x33\x35\x2e\x34\x38\x20\x2d\x31\x33\x35\x2e\x34\x38\x20\ +\x30\x7a\x6d\x31\x37\x2e\x36\x34\x20\x2d\x31\x31\x37\x2e\x38\x34\ +\x6c\x30\x20\x31\x30\x30\x2e\x32\x20\x31\x30\x30\x2e\x32\x20\x30\ +\x20\x30\x20\x2d\x31\x30\x30\x2e\x32\x20\x2d\x31\x30\x30\x2e\x32\ +\x20\x30\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\ +\x20\x20\x3c\x67\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x61\x74\x68\x20\ +\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x33\x22\x20\x64\x3d\x22\ +\x4d\x32\x36\x32\x2e\x38\x31\x20\x31\x36\x2e\x39\x31\x6c\x30\x20\ +\x39\x37\x2e\x33\x36\x20\x2d\x31\x37\x2e\x36\x34\x20\x30\x20\x30\ +\x20\x2d\x39\x37\x2e\x33\x36\x20\x38\x2e\x38\x32\x20\x2d\x38\x2e\ +\x38\x32\x20\x38\x2e\x38\x32\x20\x38\x2e\x38\x32\x7a\x6d\x2d\x38\ +\x2e\x38\x32\x20\x2d\x38\x2e\x38\x32\x6c\x38\x2e\x38\x32\x20\x30\ +\x20\x30\x20\x38\x2e\x38\x32\x20\x2d\x38\x2e\x38\x32\x20\x2d\x38\ +\x2e\x38\x32\x7a\x6d\x2d\x32\x33\x37\x2e\x30\x38\x20\x30\x6c\x32\ +\x33\x37\x2e\x30\x38\x20\x30\x20\x30\x20\x31\x37\x2e\x36\x34\x20\ +\x2d\x32\x33\x37\x2e\x30\x38\x20\x30\x20\x2d\x38\x2e\x38\x32\x20\ +\x2d\x38\x2e\x38\x32\x20\x38\x2e\x38\x32\x20\x2d\x38\x2e\x38\x32\ +\x7a\x6d\x2d\x38\x2e\x38\x32\x20\x38\x2e\x38\x32\x6c\x30\x20\x2d\ +\x38\x2e\x38\x32\x20\x38\x2e\x38\x32\x20\x30\x20\x2d\x38\x2e\x38\ +\x32\x20\x38\x2e\x38\x32\x7a\x6d\x30\x20\x32\x33\x37\x2e\x30\x36\ +\x6c\x30\x20\x2d\x32\x33\x37\x2e\x30\x36\x20\x31\x37\x2e\x36\x34\ +\x20\x30\x20\x30\x20\x32\x33\x37\x2e\x30\x36\x20\x2d\x38\x2e\x38\ +\x32\x20\x38\x2e\x38\x32\x20\x2d\x38\x2e\x38\x32\x20\x2d\x38\x2e\ +\x38\x32\x7a\x6d\x38\x2e\x38\x32\x20\x38\x2e\x38\x32\x6c\x2d\x38\ +\x2e\x38\x32\x20\x30\x20\x30\x20\x2d\x38\x2e\x38\x32\x20\x38\x2e\ +\x38\x32\x20\x38\x2e\x38\x32\x7a\x6d\x39\x38\x2e\x37\x32\x20\x30\ +\x6c\x2d\x39\x38\x2e\x37\x32\x20\x30\x20\x30\x20\x2d\x31\x37\x2e\ +\x36\x34\x20\x39\x38\x2e\x37\x32\x20\x30\x20\x30\x20\x31\x37\x2e\ +\x36\x34\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\ +\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\ +\x69\x6c\x30\x20\x73\x74\x72\x30\x22\x20\x64\x3d\x22\x4d\x31\x32\ +\x36\x2e\x39\x39\x20\x32\x35\x33\x2e\x39\x38\x6c\x31\x36\x2e\x36\ +\x36\x20\x30\x6d\x31\x30\x31\x2e\x37\x38\x20\x30\x6c\x31\x36\x2e\ +\x36\x36\x20\x30\x6d\x2d\x34\x36\x2e\x32\x37\x20\x30\x6c\x31\x36\ +\x2e\x36\x36\x20\x30\x6d\x2d\x34\x36\x2e\x32\x37\x20\x30\x6c\x31\ +\x36\x2e\x36\x36\x20\x30\x6d\x2d\x34\x36\x2e\x32\x37\x20\x30\x6c\ +\x31\x36\x2e\x36\x36\x20\x30\x6d\x38\x30\x2e\x35\x20\x2d\x31\x32\ +\x36\x2e\x36\x34\x6c\x30\x20\x31\x36\x2e\x36\x36\x6d\x30\x20\x31\ +\x30\x31\x2e\x37\x38\x6c\x30\x20\x31\x36\x2e\x36\x36\x6d\x30\x20\ +\x2d\x34\x36\x2e\x32\x37\x6c\x30\x20\x31\x36\x2e\x36\x36\x6d\x30\ +\x20\x2d\x34\x36\x2e\x32\x37\x6c\x30\x20\x31\x36\x2e\x36\x36\x6d\ +\x30\x20\x2d\x34\x36\x2e\x32\x37\x6c\x30\x20\x31\x36\x2e\x36\x36\ +\x22\x2f\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\ +\x67\x3e\x0d\x0a\ +\x00\x00\x08\x04\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\ +\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x34\x20\x7b\ +\x66\x69\x6c\x6c\x3a\x23\x34\x36\x38\x32\x43\x38\x7d\x0d\x0a\x20\ +\x20\x20\x20\x2e\x66\x69\x6c\x35\x20\x7b\x66\x69\x6c\x6c\x3a\x23\ +\x42\x33\x42\x33\x42\x33\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\ +\x6c\x33\x20\x7b\x66\x69\x6c\x6c\x3a\x23\x43\x43\x43\x43\x43\x43\ +\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x32\x20\x7b\x66\x69\ +\x6c\x6c\x3a\x23\x46\x46\x39\x39\x33\x33\x7d\x0d\x0a\x20\x20\x20\ +\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x77\x68\x69\ +\x74\x65\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\x3c\ +\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\x73\ +\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\x64\ +\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\x3c\ +\x6d\x65\x74\x61\x64\x61\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\x72\ +\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\x2d\ +\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\x65\x63\ +\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\x78\ +\x3d\x22\x2d\x30\x2e\x30\x31\x22\x20\x79\x3d\x22\x2d\x30\x22\x20\ +\x77\x69\x64\x74\x68\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x20\x68\ +\x65\x69\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x2f\x3e\ +\x0d\x0a\x20\x20\x3c\x67\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x6f\x6c\ +\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\ +\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x33\x37\x2e\x38\x39\x2c\ +\x32\x30\x37\x2e\x34\x32\x20\x31\x38\x32\x2e\x32\x32\x2c\x32\x30\ +\x37\x2e\x34\x32\x20\x31\x38\x32\x2e\x32\x32\x2c\x36\x33\x2e\x34\ +\x39\x20\x31\x33\x31\x2e\x34\x2c\x31\x32\x2e\x36\x38\x20\x33\x37\ +\x2e\x38\x39\x2c\x31\x32\x2e\x36\x38\x20\x22\x2f\x3e\x0d\x0a\x20\ +\x20\x20\x3c\x70\x61\x74\x68\x20\x69\x64\x3d\x22\x5f\x31\x22\x20\ +\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\x20\x64\x3d\x22\ +\x4d\x34\x38\x2e\x34\x37\x20\x31\x39\x36\x2e\x38\x34\x6c\x31\x32\ +\x33\x2e\x31\x37\x20\x30\x20\x30\x20\x2d\x31\x32\x38\x2e\x39\x37\ +\x20\x2d\x34\x34\x2e\x36\x32\x20\x2d\x34\x34\x2e\x36\x31\x20\x2d\ +\x37\x38\x2e\x35\x35\x20\x30\x20\x30\x20\x31\x37\x33\x2e\x35\x38\ +\x7a\x6d\x2d\x31\x30\x2e\x35\x38\x20\x31\x30\x2e\x35\x38\x6c\x31\ +\x34\x34\x2e\x33\x33\x20\x30\x20\x30\x20\x2d\x31\x34\x33\x2e\x39\ +\x33\x20\x2d\x35\x30\x2e\x38\x32\x20\x2d\x35\x30\x2e\x38\x31\x20\ +\x2d\x39\x33\x2e\x35\x31\x20\x30\x20\x30\x20\x31\x39\x34\x2e\x37\ +\x34\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\x20\ +\x20\x3c\x67\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\ +\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x33\x22\x20\x70\ +\x6f\x69\x6e\x74\x73\x3d\x22\x31\x32\x32\x2e\x39\x35\x2c\x31\x32\ +\x2e\x36\x38\x20\x31\x33\x31\x2e\x34\x2c\x31\x32\x2e\x36\x38\x20\ +\x31\x38\x32\x2e\x32\x32\x2c\x36\x33\x2e\x34\x39\x20\x31\x38\x32\ +\x2e\x32\x32\x2c\x37\x31\x2e\x39\x36\x20\x31\x32\x32\x2e\x39\x35\ +\x2c\x37\x31\x2e\x39\x36\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\ +\x70\x61\x74\x68\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\x22\x20\x63\ +\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\x20\x64\x3d\x22\x4d\ +\x31\x32\x32\x2e\x39\x35\x20\x31\x32\x2e\x36\x38\x6c\x38\x2e\x34\ +\x35\x20\x30\x20\x35\x30\x2e\x38\x32\x20\x35\x30\x2e\x38\x31\x20\ +\x30\x20\x38\x2e\x34\x37\x20\x2d\x35\x39\x2e\x32\x37\x20\x30\x20\ +\x30\x20\x2d\x35\x39\x2e\x32\x38\x7a\x6d\x34\x36\x2e\x34\x34\x20\ +\x35\x30\x2e\x34\x36\x6c\x2d\x33\x37\x2e\x36\x32\x20\x2d\x33\x37\ +\x2e\x36\x31\x20\x30\x20\x33\x37\x2e\x36\x31\x20\x33\x37\x2e\x36\ +\x32\x20\x30\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\x3e\x0d\ +\x0a\x20\x20\x3c\x67\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x6f\x6c\x79\ +\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\ +\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x38\x38\x2e\x37\x31\x2c\x32\ +\x35\x38\x2e\x32\x34\x20\x32\x33\x33\x2e\x30\x34\x2c\x32\x35\x38\ +\x2e\x32\x34\x20\x32\x33\x33\x2e\x30\x34\x2c\x31\x31\x34\x2e\x33\ +\x31\x20\x31\x38\x32\x2e\x32\x32\x2c\x36\x33\x2e\x35\x20\x38\x38\ +\x2e\x37\x31\x2c\x36\x33\x2e\x35\x20\x22\x2f\x3e\x0d\x0a\x20\x20\ +\x20\x3c\x70\x61\x74\x68\x20\x69\x64\x3d\x22\x5f\x31\x5f\x31\x22\ +\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x34\x22\x20\x64\x3d\ +\x22\x4d\x31\x30\x32\x2e\x38\x32\x20\x32\x34\x34\x2e\x31\x33\x6c\ +\x31\x31\x36\x2e\x31\x31\x20\x30\x20\x30\x20\x2d\x31\x32\x33\x2e\ +\x39\x37\x20\x2d\x34\x32\x2e\x35\x36\x20\x2d\x34\x32\x2e\x35\x35\ +\x20\x2d\x37\x33\x2e\x35\x35\x20\x30\x20\x30\x20\x31\x36\x36\x2e\ +\x35\x32\x7a\x6d\x2d\x31\x34\x2e\x31\x31\x20\x31\x34\x2e\x31\x31\ +\x6c\x31\x34\x34\x2e\x33\x33\x20\x30\x20\x30\x20\x2d\x31\x34\x33\ +\x2e\x39\x33\x20\x2d\x35\x30\x2e\x38\x32\x20\x2d\x35\x30\x2e\x38\ +\x31\x20\x2d\x39\x33\x2e\x35\x31\x20\x30\x20\x30\x20\x31\x39\x34\ +\x2e\x37\x34\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\x3e\x0d\ +\x0a\x20\x20\x3c\x67\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x6f\x6c\x79\ +\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x35\x22\ +\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x31\x37\x33\x2e\x37\x37\x2c\ +\x36\x33\x2e\x35\x20\x31\x38\x32\x2e\x32\x32\x2c\x36\x33\x2e\x35\ +\x20\x32\x33\x33\x2e\x30\x34\x2c\x31\x31\x34\x2e\x33\x31\x20\x32\ +\x33\x33\x2e\x30\x34\x2c\x31\x32\x32\x2e\x37\x38\x20\x31\x37\x33\ +\x2e\x37\x37\x2c\x31\x32\x32\x2e\x37\x38\x20\x22\x2f\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x70\x61\x74\x68\x20\x69\x64\x3d\x22\x5f\x31\x5f\ +\x32\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x34\x22\x20\ +\x64\x3d\x22\x4d\x31\x37\x33\x2e\x37\x37\x20\x36\x33\x2e\x35\x6c\ +\x38\x2e\x34\x35\x20\x30\x20\x35\x30\x2e\x38\x32\x20\x35\x30\x2e\ +\x38\x31\x20\x30\x20\x38\x2e\x34\x37\x20\x2d\x35\x39\x2e\x32\x37\ +\x20\x30\x20\x30\x20\x2d\x35\x39\x2e\x32\x38\x7a\x6d\x34\x36\x2e\ +\x34\x34\x20\x35\x30\x2e\x34\x36\x6c\x2d\x33\x37\x2e\x36\x32\x20\ +\x2d\x33\x37\x2e\x36\x31\x20\x30\x20\x33\x37\x2e\x36\x31\x20\x33\ +\x37\x2e\x36\x32\x20\x30\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\ +\x67\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\ +\x3e\x0d\x0a\ +\x00\x00\x05\x33\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x34\x36\x38\x32\x43\x38\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x37\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\ +\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x30\ +\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\x6e\x65\x7d\x0d\x0a\x20\x20\ +\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x77\x68\ +\x69\x74\x65\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\ +\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\ +\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\ +\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\ +\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\ +\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\ +\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\x65\ +\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\ +\x78\x3d\x22\x2d\x30\x2e\x30\x31\x22\x20\x79\x3d\x22\x2d\x30\x22\ +\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x20\ +\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x2f\ +\x3e\x0d\x0a\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\ +\x3d\x22\x66\x69\x6c\x31\x20\x73\x74\x72\x30\x22\x20\x64\x3d\x22\ +\x4d\x31\x33\x35\x2e\x34\x31\x20\x32\x33\x38\x2e\x35\x37\x6c\x31\ +\x30\x30\x2e\x33\x38\x20\x2d\x31\x30\x30\x63\x31\x31\x2e\x37\x37\ +\x2c\x2d\x31\x31\x2e\x37\x34\x20\x31\x38\x2e\x31\x37\x2c\x2d\x32\ +\x37\x2e\x33\x34\x20\x31\x38\x2e\x32\x2c\x2d\x34\x33\x2e\x39\x35\ +\x20\x30\x2e\x30\x32\x2c\x2d\x31\x36\x2e\x36\x32\x20\x2d\x36\x2e\ +\x33\x36\x2c\x2d\x33\x32\x2e\x31\x38\x20\x2d\x31\x38\x2e\x31\x31\ +\x2c\x2d\x34\x33\x2e\x39\x34\x20\x2d\x31\x31\x2e\x37\x36\x2c\x2d\ +\x31\x31\x2e\x37\x35\x20\x2d\x32\x37\x2e\x32\x37\x2c\x2d\x31\x38\ +\x2e\x31\x36\x20\x2d\x34\x33\x2e\x38\x39\x2c\x2d\x31\x38\x2e\x31\ +\x36\x20\x2d\x31\x36\x2e\x36\x32\x2c\x30\x20\x2d\x33\x32\x2e\x31\ +\x39\x2c\x36\x2e\x34\x37\x20\x2d\x34\x33\x2e\x39\x35\x2c\x31\x38\ +\x2e\x32\x32\x6c\x2d\x31\x32\x2e\x35\x20\x31\x32\x2e\x35\x20\x2d\ +\x31\x32\x2e\x36\x20\x2d\x31\x32\x2e\x36\x31\x63\x2d\x31\x31\x2e\ +\x37\x36\x2c\x2d\x31\x31\x2e\x37\x35\x20\x2d\x32\x37\x2e\x33\x38\ +\x2c\x2d\x31\x38\x2e\x32\x38\x20\x2d\x34\x34\x2e\x30\x31\x2c\x2d\ +\x31\x38\x2e\x32\x38\x20\x2d\x31\x36\x2e\x35\x35\x2c\x30\x20\x2d\ +\x33\x32\x2e\x31\x33\x2c\x36\x2e\x34\x37\x20\x2d\x34\x33\x2e\x38\ +\x33\x2c\x31\x38\x2e\x31\x38\x20\x2d\x31\x31\x2e\x37\x35\x2c\x31\ +\x31\x2e\x37\x34\x20\x2d\x31\x38\x2e\x31\x39\x2c\x32\x37\x2e\x33\ +\x31\x20\x2d\x31\x38\x2e\x31\x37\x2c\x34\x33\x2e\x39\x33\x20\x30\ +\x2e\x30\x33\x2c\x31\x36\x2e\x36\x32\x20\x36\x2e\x35\x32\x2c\x33\ +\x32\x2e\x31\x34\x20\x31\x38\x2e\x32\x37\x2c\x34\x33\x2e\x39\x6c\ +\x31\x30\x30\x2e\x32\x31\x20\x31\x30\x30\x2e\x32\x31\x7a\x22\x2f\ +\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\ +\x0d\x0a\ +\x00\x00\x06\x15\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x67\x72\x61\x79\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\ +\x68\x3a\x31\x37\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\ +\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\ +\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x73\x74\x72\x32\x20\x7b\x73\ +\x74\x72\x6f\x6b\x65\x3a\x23\x34\x36\x38\x32\x43\x38\x3b\x73\x74\ +\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x31\x37\x2e\x36\x34\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x63\x61\x70\x3a\ +\x73\x71\x75\x61\x72\x65\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\ +\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\ +\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x73\x74\x72\x31\x20\x7b\x73\x74\ +\x72\x6f\x6b\x65\x3a\x23\x46\x46\x39\x39\x33\x33\x3b\x73\x74\x72\ +\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x31\x37\x2e\x36\x34\x3b\ +\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x63\x61\x70\x3a\x73\ +\x71\x75\x61\x72\x65\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\ +\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\ +\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\ +\x6c\x3a\x6e\x6f\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\ +\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x67\x72\x61\x79\x7d\x0d\x0a\ +\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\x6c\ +\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\x3c\ +\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\x30\ +\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\ +\x61\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\x72\ +\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\x72\ +\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\x61\ +\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\x78\x3d\x22\x2d\x30\x2e\ +\x30\x31\x22\x20\x79\x3d\x22\x2d\x30\x22\x20\x77\x69\x64\x74\x68\ +\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x20\x68\x65\x69\x67\x68\x74\ +\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\ +\x70\x6f\x6c\x79\x6c\x69\x6e\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\ +\x66\x69\x6c\x30\x20\x73\x74\x72\x30\x22\x20\x70\x6f\x69\x6e\x74\ +\x73\x3d\x22\x31\x33\x35\x2e\x34\x36\x2c\x32\x35\x34\x20\x32\x31\ +\x31\x2e\x36\x36\x2c\x32\x35\x34\x20\x32\x31\x31\x2e\x36\x2c\x31\ +\x36\x39\x2e\x33\x33\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\ +\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\ +\x30\x20\x73\x74\x72\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\ +\x31\x36\x2e\x39\x38\x2c\x32\x35\x33\x2e\x39\x34\x20\x31\x30\x31\ +\x2e\x36\x35\x2c\x32\x35\x33\x2e\x39\x34\x20\x31\x30\x31\x2e\x36\ +\x35\x2c\x31\x36\x39\x2e\x32\x37\x20\x31\x36\x2e\x39\x38\x2c\x31\ +\x36\x39\x2e\x32\x37\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\ +\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\ +\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x32\x33\x39\x2e\x30\ +\x39\x2c\x31\x36\x39\x2e\x33\x33\x20\x32\x31\x31\x2e\x36\x2c\x31\ +\x31\x38\x2e\x35\x32\x20\x31\x38\x34\x2e\x31\x31\x2c\x31\x36\x39\ +\x2e\x33\x33\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\ +\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\ +\x73\x74\x72\x32\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x31\x36\ +\x39\x2e\x32\x37\x2c\x31\x30\x31\x2e\x36\x35\x20\x32\x35\x33\x2e\ +\x39\x34\x2c\x31\x30\x31\x2e\x36\x35\x20\x32\x35\x33\x2e\x39\x34\ +\x2c\x31\x36\x2e\x39\x38\x20\x31\x36\x39\x2e\x32\x37\x2c\x31\x36\ +\x2e\x39\x38\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\ +\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\ +\x73\x74\x72\x32\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x35\x30\ +\x2e\x38\x33\x2c\x31\x30\x31\x2e\x36\x35\x20\x31\x33\x35\x2e\x35\ +\x2c\x31\x30\x31\x2e\x36\x35\x20\x31\x33\x35\x2e\x35\x2c\x31\x36\ +\x2e\x39\x38\x20\x35\x30\x2e\x38\x33\x2c\x31\x36\x2e\x39\x38\x20\ +\x22\x2f\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\ +\x67\x3e\x0d\x0a\ +\x00\x00\x06\x71\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x31\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x34\x36\x38\x32\x43\x38\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x36\x2e\x39\x33\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\ +\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x73\x74\x72\x32\ +\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\x67\x72\x61\x79\x3b\x73\x74\ +\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x31\x34\x2e\x31\x31\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\ +\x3a\x72\x6f\x75\x6e\x64\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\ +\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\ +\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\ +\x72\x6f\x6b\x65\x3a\x23\x46\x46\x39\x39\x33\x33\x3b\x73\x74\x72\ +\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x31\x37\x2e\x36\x34\x3b\ +\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3a\ +\x72\x6f\x75\x6e\x64\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\ +\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\ +\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\ +\x6c\x3a\x6e\x6f\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\ +\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x67\x72\x61\x79\x7d\x0d\x0a\ +\x20\x20\x20\x20\x2e\x66\x69\x6c\x32\x20\x7b\x66\x69\x6c\x6c\x3a\ +\x23\x46\x46\x39\x39\x33\x33\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\x3e\ +\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\x3c\ +\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\x22\ +\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\x3e\ +\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\x69\x64\ +\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\x43\ +\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\x20\ +\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\ +\x6c\x30\x22\x20\x78\x3d\x22\x2d\x30\x2e\x30\x31\x22\x20\x79\x3d\ +\x22\x2d\x30\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\x30\x2e\ +\x39\x33\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\ +\x39\x33\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x6c\x69\x6e\x65\x20\x63\ +\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x30\x22\ +\x20\x78\x31\x3d\x22\x32\x36\x32\x2e\x34\x36\x22\x20\x79\x31\x3d\ +\x22\x34\x32\x2e\x33\x34\x22\x20\x78\x32\x3d\x22\x32\x36\x32\x2e\ +\x34\x36\x22\x20\x79\x32\x3d\x20\x22\x32\x32\x38\x2e\x36\x22\x20\ +\x2f\x3e\x0d\x0a\x20\x20\x3c\x6c\x69\x6e\x65\x20\x63\x6c\x61\x73\ +\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x30\x22\x20\x78\x31\ +\x3d\x22\x34\x32\x2e\x34\x32\x22\x20\x79\x31\x3d\x22\x32\x36\x32\ +\x2e\x33\x38\x22\x20\x78\x32\x3d\x22\x32\x32\x38\x2e\x36\x39\x22\ +\x20\x79\x32\x3d\x20\x22\x32\x36\x32\x2e\x33\x38\x22\x20\x2f\x3e\ +\x0d\x0a\x20\x20\x3c\x63\x69\x72\x63\x6c\x65\x20\x63\x6c\x61\x73\ +\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x31\x22\x20\x63\x78\ +\x3d\x22\x31\x33\x35\x2e\x35\x36\x22\x20\x63\x79\x3d\x22\x31\x33\ +\x35\x2e\x34\x38\x22\x20\x72\x3d\x22\x31\x32\x32\x2e\x37\x36\x22\ +\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\ +\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\ +\x74\x73\x3d\x22\x35\x33\x2e\x34\x33\x2c\x35\x33\x2e\x33\x33\x20\ +\x37\x33\x2e\x38\x37\x2c\x35\x38\x2e\x38\x20\x39\x34\x2e\x33\x31\ +\x2c\x36\x34\x2e\x32\x38\x20\x37\x39\x2e\x33\x35\x2c\x37\x39\x2e\ +\x32\x35\x20\x36\x34\x2e\x33\x38\x2c\x39\x34\x2e\x32\x31\x20\x35\ +\x38\x2e\x39\x2c\x37\x33\x2e\x37\x37\x20\x22\x2f\x3e\x0d\x0a\x20\ +\x20\x3c\x6c\x69\x6e\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\ +\x6c\x30\x20\x73\x74\x72\x32\x22\x20\x78\x31\x3d\x22\x31\x33\x35\ +\x2e\x35\x36\x22\x20\x79\x31\x3d\x22\x31\x33\x35\x2e\x34\x36\x22\ +\x20\x78\x32\x3d\x22\x37\x38\x2e\x38\x31\x22\x20\x79\x32\x3d\x20\ +\x22\x37\x38\x2e\x37\x31\x22\x20\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\ +\x6f\x6c\x79\x67\x6f\x6e\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\x22\ +\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\x20\x70\x6f\ +\x69\x6e\x74\x73\x3d\x22\x31\x35\x36\x2e\x37\x33\x2c\x31\x31\x34\ +\x2e\x33\x32\x20\x31\x31\x34\x2e\x34\x2c\x31\x31\x34\x2e\x33\x32\ +\x20\x31\x31\x34\x2e\x34\x2c\x31\x35\x36\x2e\x36\x35\x20\x31\x35\ +\x36\x2e\x37\x33\x2c\x31\x35\x36\x2e\x36\x35\x20\x22\x2f\x3e\x0d\ +\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\x0a\ +\ +\x00\x00\x06\x3b\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x31\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x39\x39\x39\x39\x39\x39\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x32\x36\x2e\x34\x36\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\ +\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x73\x74\x72\x30\ +\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\x23\x34\x36\x38\x32\x43\x38\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x32\x36\ +\x2e\x34\x36\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\ +\x6f\x69\x6e\x3a\x72\x6f\x75\x6e\x64\x3b\x73\x74\x72\x6f\x6b\x65\ +\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\ +\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x30\x20\ +\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\x6e\x65\x7d\x0d\x0a\x20\x20\x20\ +\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x23\x32\x30\ +\x38\x44\x42\x32\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x32\ +\x20\x7b\x66\x69\x6c\x6c\x3a\x23\x45\x32\x35\x37\x34\x43\x7d\x0d\ +\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\ +\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\ +\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\ +\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\ +\x64\x61\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\ +\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\ +\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\x78\x3d\x22\x2d\x30\ +\x2e\x30\x31\x22\x20\x79\x3d\x22\x2d\x30\x22\x20\x77\x69\x64\x74\ +\x68\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x20\x68\x65\x69\x67\x68\ +\x74\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x2f\x3e\x0d\x0a\x20\x20\ +\x3c\x6c\x69\x6e\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\ +\x30\x20\x73\x74\x72\x30\x22\x20\x78\x31\x3d\x22\x32\x37\x30\x2e\ +\x39\x32\x22\x20\x79\x31\x3d\x22\x32\x31\x31\x2e\x36\x37\x22\x20\ +\x78\x32\x3d\x22\x31\x32\x32\x2e\x37\x35\x22\x20\x79\x32\x3d\x20\ +\x22\x32\x31\x31\x2e\x36\x37\x22\x20\x2f\x3e\x0d\x0a\x20\x20\x3c\ +\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\ +\x20\x73\x74\x72\x31\x22\x20\x64\x3d\x22\x4d\x32\x39\x2e\x35\x39\ +\x20\x32\x31\x31\x2e\x36\x37\x6c\x31\x37\x2e\x30\x32\x20\x30\x6d\ +\x2d\x34\x36\x2e\x36\x33\x20\x30\x6c\x31\x37\x2e\x30\x33\x20\x30\ +\x6d\x37\x31\x2e\x37\x39\x20\x30\x6c\x31\x37\x2e\x30\x34\x20\x30\ +\x6d\x2d\x34\x36\x2e\x36\x35\x20\x30\x6c\x31\x37\x2e\x30\x34\x20\ +\x30\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x67\x20\x69\x64\x3d\x22\x5f\ +\x31\x36\x37\x35\x38\x38\x38\x30\x33\x39\x38\x38\x38\x22\x3e\x0d\ +\x0a\x20\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\ +\x22\x66\x69\x6c\x31\x22\x20\x64\x3d\x22\x4d\x31\x35\x35\x2e\x34\ +\x34\x20\x33\x33\x2e\x37\x34\x6c\x2d\x33\x31\x2e\x39\x36\x20\x33\ +\x31\x2e\x39\x36\x20\x34\x37\x2e\x39\x20\x34\x37\x2e\x38\x39\x20\ +\x33\x31\x2e\x39\x36\x20\x2d\x33\x31\x2e\x39\x36\x63\x36\x2e\x35\ +\x38\x2c\x2d\x36\x2e\x35\x38\x20\x36\x2e\x35\x38\x2c\x2d\x31\x37\ +\x2e\x33\x34\x20\x30\x2c\x2d\x32\x33\x2e\x39\x32\x6c\x2d\x32\x33\ +\x2e\x39\x37\x20\x2d\x32\x33\x2e\x39\x37\x63\x2d\x36\x2e\x35\x38\ +\x2c\x2d\x36\x2e\x35\x38\x20\x2d\x31\x37\x2e\x34\x38\x2c\x2d\x36\ +\x2e\x34\x35\x20\x2d\x32\x33\x2e\x39\x33\x2c\x30\x7a\x22\x2f\x3e\ +\x0d\x0a\x20\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\ +\x3d\x22\x66\x69\x6c\x32\x22\x20\x64\x3d\x22\x4d\x36\x37\x2e\x35\ +\x39\x20\x31\x32\x31\x2e\x35\x39\x6c\x35\x35\x2e\x38\x39\x20\x2d\ +\x35\x35\x2e\x38\x39\x20\x34\x37\x2e\x39\x20\x34\x37\x2e\x38\x39\ +\x20\x2d\x35\x35\x2e\x38\x39\x20\x35\x35\x2e\x38\x39\x63\x2d\x36\ +\x2e\x35\x32\x2c\x36\x2e\x35\x32\x20\x2d\x31\x37\x2e\x33\x35\x2c\ +\x36\x2e\x35\x38\x20\x2d\x32\x33\x2e\x39\x33\x2c\x30\x6c\x2d\x32\ +\x33\x2e\x39\x37\x20\x2d\x32\x33\x2e\x39\x36\x63\x2d\x36\x2e\x35\ +\x38\x2c\x2d\x36\x2e\x35\x38\x20\x2d\x36\x2e\x35\x38\x2c\x2d\x31\ +\x37\x2e\x33\x35\x20\x30\x2c\x2d\x32\x33\x2e\x39\x33\x7a\x22\x2f\ +\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\ +\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x07\x29\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\ +\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x33\x20\x7b\ +\x66\x69\x6c\x6c\x3a\x23\x30\x30\x39\x39\x33\x33\x7d\x0d\x0a\x20\ +\x20\x20\x20\x2e\x66\x69\x6c\x32\x20\x7b\x66\x69\x6c\x6c\x3a\x23\ +\x36\x36\x36\x36\x36\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\ +\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x77\x68\x69\x74\x65\x7d\x0d\ +\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\ +\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\ +\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\ +\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\ +\x64\x61\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\ +\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\ +\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\x78\x3d\x22\x30\x2e\ +\x30\x31\x22\x20\x79\x3d\x22\x2d\x30\x2e\x30\x31\x22\x20\x77\x69\ +\x64\x74\x68\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x20\x68\x65\x69\ +\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x2f\x3e\x0d\x0a\ +\x20\x20\x3c\x67\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x6f\x6c\x79\x67\ +\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\ +\x70\x6f\x69\x6e\x74\x73\x3d\x22\x32\x35\x30\x2e\x30\x34\x2c\x32\ +\x36\x32\x2e\x36\x36\x20\x31\x31\x30\x2e\x33\x31\x2c\x32\x36\x32\ +\x2e\x36\x36\x20\x32\x30\x2e\x38\x35\x2c\x31\x35\x36\x2e\x33\x32\ +\x20\x31\x36\x30\x2e\x35\x38\x2c\x31\x35\x36\x2e\x33\x32\x20\x22\ +\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x61\x74\x68\x20\x69\x64\x3d\ +\x22\x5f\x31\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\ +\x22\x20\x64\x3d\x22\x4d\x32\x35\x30\x2e\x30\x34\x20\x32\x36\x32\ +\x2e\x36\x36\x6c\x2d\x31\x33\x39\x2e\x37\x33\x20\x30\x20\x2d\x38\ +\x39\x2e\x34\x36\x20\x2d\x31\x30\x36\x2e\x33\x34\x20\x31\x33\x39\ +\x2e\x37\x33\x20\x30\x20\x38\x39\x2e\x34\x36\x20\x31\x30\x36\x2e\ +\x33\x34\x7a\x6d\x2d\x31\x33\x34\x2e\x30\x36\x20\x2d\x31\x32\x2e\ +\x33\x35\x6c\x31\x30\x37\x2e\x36\x31\x20\x30\x20\x2d\x36\x38\x2e\ +\x36\x38\x20\x2d\x38\x31\x2e\x36\x34\x20\x2d\x31\x30\x37\x2e\x36\ +\x31\x20\x30\x20\x36\x38\x2e\x36\x38\x20\x38\x31\x2e\x36\x34\x7a\ +\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\x20\x20\x3c\ +\x67\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\ +\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\ +\x6e\x74\x73\x3d\x22\x32\x35\x30\x2e\x30\x34\x2c\x32\x31\x36\x2e\ +\x31\x33\x20\x31\x31\x30\x2e\x33\x31\x2c\x32\x31\x36\x2e\x31\x33\ +\x20\x32\x30\x2e\x38\x35\x2c\x31\x30\x39\x2e\x37\x39\x20\x31\x36\ +\x30\x2e\x35\x38\x2c\x31\x30\x39\x2e\x37\x39\x20\x22\x2f\x3e\x0d\ +\x0a\x20\x20\x20\x3c\x70\x61\x74\x68\x20\x69\x64\x3d\x22\x5f\x31\ +\x5f\x30\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\ +\x20\x64\x3d\x22\x4d\x32\x35\x30\x2e\x30\x34\x20\x32\x31\x36\x2e\ +\x31\x33\x6c\x2d\x31\x33\x39\x2e\x37\x33\x20\x30\x20\x2d\x38\x39\ +\x2e\x34\x36\x20\x2d\x31\x30\x36\x2e\x33\x34\x20\x31\x33\x39\x2e\ +\x37\x33\x20\x30\x20\x38\x39\x2e\x34\x36\x20\x31\x30\x36\x2e\x33\ +\x34\x7a\x6d\x2d\x31\x33\x34\x2e\x30\x36\x20\x2d\x31\x32\x2e\x33\ +\x35\x6c\x31\x30\x37\x2e\x36\x31\x20\x30\x20\x2d\x36\x38\x2e\x36\ +\x38\x20\x2d\x38\x31\x2e\x36\x34\x20\x2d\x31\x30\x37\x2e\x36\x31\ +\x20\x30\x20\x36\x38\x2e\x36\x38\x20\x38\x31\x2e\x36\x34\x7a\x22\ +\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\x20\x20\x3c\x67\ +\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\ +\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\ +\x74\x73\x3d\x22\x32\x35\x30\x2e\x30\x34\x2c\x31\x36\x39\x2e\x36\ +\x20\x31\x31\x30\x2e\x33\x31\x2c\x31\x36\x39\x2e\x36\x20\x32\x30\ +\x2e\x38\x35\x2c\x36\x33\x2e\x32\x36\x20\x31\x36\x30\x2e\x35\x38\ +\x2c\x36\x33\x2e\x32\x36\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\ +\x70\x61\x74\x68\x20\x69\x64\x3d\x22\x5f\x31\x5f\x31\x22\x20\x63\ +\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\x20\x64\x3d\x22\x4d\ +\x32\x35\x30\x2e\x30\x34\x20\x31\x36\x39\x2e\x36\x6c\x2d\x31\x33\ +\x39\x2e\x37\x33\x20\x30\x20\x2d\x38\x39\x2e\x34\x36\x20\x2d\x31\ +\x30\x36\x2e\x33\x34\x20\x31\x33\x39\x2e\x37\x33\x20\x30\x20\x38\ +\x39\x2e\x34\x36\x20\x31\x30\x36\x2e\x33\x34\x7a\x6d\x2d\x31\x33\ +\x34\x2e\x30\x36\x20\x2d\x31\x32\x2e\x33\x35\x6c\x31\x30\x37\x2e\ +\x36\x31\x20\x30\x20\x2d\x36\x38\x2e\x36\x38\x20\x2d\x38\x31\x2e\ +\x36\x34\x20\x2d\x31\x30\x37\x2e\x36\x31\x20\x30\x20\x36\x38\x2e\ +\x36\x38\x20\x38\x31\x2e\x36\x34\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\ +\x3c\x2f\x67\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\ +\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x33\x22\x20\x70\x6f\ +\x69\x6e\x74\x73\x3d\x22\x31\x30\x38\x2e\x39\x31\x2c\x39\x30\x2e\ +\x39\x20\x31\x33\x32\x2e\x38\x36\x2c\x36\x36\x2e\x39\x35\x20\x31\ +\x36\x32\x2e\x37\x38\x2c\x39\x36\x2e\x38\x37\x20\x32\x32\x35\x2e\ +\x31\x32\x2c\x33\x34\x2e\x35\x34\x20\x32\x34\x39\x2e\x30\x37\x2c\ +\x35\x38\x2e\x34\x39\x20\x31\x36\x32\x2e\x37\x38\x2c\x31\x34\x34\ +\x2e\x37\x37\x20\x22\x2f\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\ +\x3c\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x10\xa4\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\ +\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x32\x20\x7b\ +\x66\x69\x6c\x6c\x3a\x23\x33\x33\x33\x33\x33\x33\x7d\x0d\x0a\x20\ +\x20\x20\x20\x2e\x66\x69\x6c\x34\x20\x7b\x66\x69\x6c\x6c\x3a\x23\ +\x34\x44\x34\x44\x34\x44\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\ +\x6c\x33\x20\x7b\x66\x69\x6c\x6c\x3a\x75\x72\x6c\x28\x23\x69\x64\ +\x30\x29\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\ +\x66\x69\x6c\x6c\x3a\x75\x72\x6c\x28\x23\x69\x64\x31\x29\x7d\x0d\ +\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\ +\x6c\x65\x3e\x0d\x0a\x20\x20\x3c\x6c\x69\x6e\x65\x61\x72\x47\x72\ +\x61\x64\x69\x65\x6e\x74\x20\x69\x64\x3d\x22\x69\x64\x30\x22\x20\ +\x67\x72\x61\x64\x69\x65\x6e\x74\x55\x6e\x69\x74\x73\x3d\x22\x75\ +\x73\x65\x72\x53\x70\x61\x63\x65\x4f\x6e\x55\x73\x65\x22\x20\x78\ +\x31\x3d\x22\x31\x33\x35\x2e\x34\x36\x22\x20\x79\x31\x3d\x22\x32\ +\x33\x39\x2e\x31\x37\x22\x20\x78\x32\x3d\x22\x31\x33\x35\x2e\x34\ +\x36\x22\x20\x79\x32\x3d\x22\x31\x32\x30\x2e\x36\x32\x22\x3e\x0d\ +\x0a\x20\x20\x20\x3c\x73\x74\x6f\x70\x20\x6f\x66\x66\x73\x65\x74\ +\x3d\x22\x30\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x74\x6f\x70\ +\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\x20\x73\x74\x6f\x70\ +\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x30\x30\x33\x33\x39\x39\x22\x2f\ +\x3e\x0d\x0a\x20\x20\x20\x3c\x73\x74\x6f\x70\x20\x6f\x66\x66\x73\ +\x65\x74\x3d\x22\x30\x2e\x34\x33\x31\x33\x37\x33\x22\x20\x73\x74\ +\x79\x6c\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\ +\x79\x3a\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\ +\x23\x36\x36\x39\x39\x46\x46\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\ +\x73\x74\x6f\x70\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\x30\x2e\x37\ +\x30\x39\x38\x30\x34\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x74\ +\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\x20\x73\x74\ +\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x46\x45\x46\x45\x46\x45\ +\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x73\x74\x6f\x70\x20\x6f\x66\ +\x66\x73\x65\x74\x3d\x22\x30\x2e\x38\x37\x30\x35\x38\x38\x22\x20\ +\x73\x74\x79\x6c\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\ +\x69\x74\x79\x3a\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\ +\x72\x3a\x23\x37\x46\x39\x39\x43\x43\x22\x2f\x3e\x0d\x0a\x20\x20\ +\x20\x3c\x73\x74\x6f\x70\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\x31\ +\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\ +\x61\x63\x69\x74\x79\x3a\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\ +\x6c\x6f\x72\x3a\x23\x30\x30\x33\x33\x39\x39\x22\x2f\x3e\x0d\x0a\ +\x20\x20\x3c\x2f\x6c\x69\x6e\x65\x61\x72\x47\x72\x61\x64\x69\x65\ +\x6e\x74\x3e\x0d\x0a\x20\x20\x3c\x72\x61\x64\x69\x61\x6c\x47\x72\ +\x61\x64\x69\x65\x6e\x74\x20\x69\x64\x3d\x22\x69\x64\x31\x22\x20\ +\x67\x72\x61\x64\x69\x65\x6e\x74\x55\x6e\x69\x74\x73\x3d\x22\x75\ +\x73\x65\x72\x53\x70\x61\x63\x65\x4f\x6e\x55\x73\x65\x22\x20\x67\ +\x72\x61\x64\x69\x65\x6e\x74\x54\x72\x61\x6e\x73\x66\x6f\x72\x6d\ +\x3d\x22\x6d\x61\x74\x72\x69\x78\x28\x31\x2e\x30\x30\x30\x31\x20\ +\x2d\x30\x20\x2d\x30\x20\x31\x2e\x30\x30\x30\x32\x20\x30\x20\x30\ +\x29\x22\x20\x63\x78\x3d\x22\x31\x33\x34\x2e\x31\x39\x22\x20\x63\ +\x79\x3d\x22\x39\x36\x2e\x39\x37\x22\x20\x72\x3d\x22\x36\x33\x2e\ +\x34\x38\x22\x20\x66\x78\x3d\x22\x31\x33\x34\x2e\x31\x39\x22\x20\ +\x66\x79\x3d\x22\x39\x36\x2e\x39\x37\x22\x3e\x0d\x0a\x20\x20\x20\ +\x3c\x73\x74\x6f\x70\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\x30\x22\ +\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\ +\x63\x69\x74\x79\x3a\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\ +\x6f\x72\x3a\x23\x30\x30\x33\x33\x39\x39\x22\x2f\x3e\x0d\x0a\x20\ +\x20\x20\x3c\x73\x74\x6f\x70\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\ +\x30\x2e\x36\x33\x31\x33\x37\x33\x22\x20\x73\x74\x79\x6c\x65\x3d\ +\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\ +\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x30\x30\x33\ +\x33\x39\x39\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x73\x74\x6f\x70\ +\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\x30\x2e\x38\x31\x31\x37\x36\ +\x35\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\ +\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\ +\x6f\x6c\x6f\x72\x3a\x23\x46\x45\x46\x45\x46\x45\x22\x2f\x3e\x0d\ +\x0a\x20\x20\x20\x3c\x73\x74\x6f\x70\x20\x6f\x66\x66\x73\x65\x74\ +\x3d\x22\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x74\x6f\x70\ +\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\x20\x73\x74\x6f\x70\ +\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x30\x30\x33\x33\x39\x39\x22\x2f\ +\x3e\x0d\x0a\x20\x20\x3c\x2f\x72\x61\x64\x69\x61\x6c\x47\x72\x61\ +\x64\x69\x65\x6e\x74\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\x73\x3e\ +\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\x64\x61\ +\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\ +\x65\x74\x61\x64\x61\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\x72\x65\ +\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\x2d\x4c\ +\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\x65\x63\x74\ +\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\x78\x3d\ +\x22\x2d\x30\x2e\x30\x31\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\ +\x37\x30\x2e\x39\x33\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\ +\x37\x30\x2e\x39\x32\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x67\x20\x69\ +\x64\x3d\x22\x5f\x32\x37\x36\x37\x37\x33\x30\x36\x34\x35\x32\x30\ +\x30\x22\x3e\x0d\x0a\x20\x20\x20\x3c\x67\x3e\x0d\x0a\x20\x20\x20\ +\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\ +\x6c\x31\x22\x20\x64\x3d\x22\x4d\x37\x31\x2e\x39\x38\x20\x31\x32\ +\x39\x2e\x30\x38\x6c\x30\x20\x2d\x33\x33\x2e\x38\x34\x63\x30\x2c\ +\x2d\x33\x35\x2e\x30\x37\x20\x32\x38\x2e\x34\x32\x2c\x2d\x36\x33\ +\x2e\x34\x39\x20\x36\x33\x2e\x34\x38\x2c\x2d\x36\x33\x2e\x34\x39\ +\x20\x33\x35\x2e\x30\x36\x2c\x30\x20\x36\x33\x2e\x34\x38\x2c\x32\ +\x38\x2e\x34\x38\x20\x36\x33\x2e\x34\x38\x2c\x36\x33\x2e\x34\x39\ +\x6c\x30\x20\x33\x33\x2e\x38\x34\x20\x2d\x32\x39\x2e\x35\x38\x20\ +\x30\x20\x30\x20\x2d\x33\x33\x2e\x38\x34\x63\x30\x2c\x2d\x32\x31\ +\x2e\x30\x37\x20\x2d\x31\x32\x2e\x38\x34\x2c\x2d\x33\x33\x2e\x39\ +\x31\x20\x2d\x33\x33\x2e\x39\x2c\x2d\x33\x33\x2e\x39\x31\x20\x2d\ +\x32\x31\x2e\x30\x36\x2c\x30\x20\x2d\x33\x33\x2e\x39\x2c\x31\x32\ +\x2e\x39\x34\x20\x2d\x33\x33\x2e\x39\x2c\x33\x33\x2e\x39\x31\x6c\ +\x30\x20\x33\x33\x2e\x38\x34\x20\x2d\x32\x39\x2e\x35\x38\x20\x30\ +\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x20\x3c\x70\x61\x74\x68\x20\ +\x69\x64\x3d\x22\x5f\x31\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\ +\x69\x6c\x32\x22\x20\x64\x3d\x22\x4d\x37\x31\x2e\x39\x38\x20\x31\ +\x32\x39\x2e\x30\x38\x6c\x30\x20\x2d\x33\x33\x2e\x38\x34\x63\x30\ +\x2c\x2d\x33\x35\x2e\x30\x37\x20\x32\x38\x2e\x34\x32\x2c\x2d\x36\ +\x33\x2e\x34\x39\x20\x36\x33\x2e\x34\x38\x2c\x2d\x36\x33\x2e\x34\ +\x39\x20\x33\x35\x2e\x30\x36\x2c\x30\x20\x36\x33\x2e\x34\x38\x2c\ +\x32\x38\x2e\x34\x38\x20\x36\x33\x2e\x34\x38\x2c\x36\x33\x2e\x34\ +\x39\x6c\x30\x20\x33\x33\x2e\x38\x34\x20\x2d\x32\x39\x2e\x35\x38\ +\x20\x30\x20\x30\x20\x2d\x33\x33\x2e\x38\x34\x63\x30\x2c\x2d\x32\ +\x31\x2e\x30\x37\x20\x2d\x31\x32\x2e\x38\x34\x2c\x2d\x33\x33\x2e\ +\x39\x31\x20\x2d\x33\x33\x2e\x39\x2c\x2d\x33\x33\x2e\x39\x31\x20\ +\x2d\x32\x31\x2e\x30\x36\x2c\x30\x20\x2d\x33\x33\x2e\x39\x2c\x31\ +\x32\x2e\x39\x34\x20\x2d\x33\x33\x2e\x39\x2c\x33\x33\x2e\x39\x31\ +\x6c\x30\x20\x33\x33\x2e\x38\x34\x20\x2d\x32\x39\x2e\x35\x38\x20\ +\x30\x7a\x6d\x30\x2e\x30\x31\x20\x2d\x33\x33\x2e\x38\x34\x6c\x30\ +\x20\x33\x33\x2e\x38\x33\x20\x32\x39\x2e\x35\x36\x20\x30\x20\x30\ +\x20\x2d\x33\x33\x2e\x38\x33\x63\x30\x2c\x2d\x31\x30\x2e\x35\x20\ +\x33\x2e\x32\x33\x2c\x2d\x31\x39\x20\x39\x2e\x30\x37\x2c\x2d\x32\ +\x34\x2e\x38\x35\x20\x35\x2e\x38\x33\x2c\x2d\x35\x2e\x38\x33\x20\ +\x31\x34\x2e\x33\x32\x2c\x2d\x39\x2e\x30\x37\x20\x32\x34\x2e\x38\ +\x34\x2c\x2d\x39\x2e\x30\x37\x20\x31\x30\x2e\x35\x33\x2c\x30\x20\ +\x31\x39\x2e\x30\x33\x2c\x33\x2e\x32\x32\x20\x32\x34\x2e\x38\x36\ +\x2c\x39\x2e\x30\x35\x20\x35\x2e\x38\x33\x2c\x35\x2e\x38\x34\x20\ +\x39\x2e\x30\x35\x2c\x31\x34\x2e\x33\x34\x20\x39\x2e\x30\x35\x2c\ +\x32\x34\x2e\x38\x37\x6c\x30\x20\x33\x33\x2e\x38\x33\x20\x32\x39\ +\x2e\x35\x36\x20\x30\x20\x30\x20\x2d\x33\x33\x2e\x38\x33\x63\x30\ +\x2c\x2d\x31\x37\x2e\x35\x20\x2d\x37\x2e\x31\x32\x2c\x2d\x33\x33\ +\x2e\x33\x39\x20\x2d\x31\x38\x2e\x36\x31\x2c\x2d\x34\x34\x2e\x38\ +\x38\x20\x2d\x31\x31\x2e\x34\x37\x2c\x2d\x31\x31\x2e\x34\x38\x20\ +\x2d\x32\x37\x2e\x33\x35\x2c\x2d\x31\x38\x2e\x36\x20\x2d\x34\x34\ +\x2e\x38\x36\x2c\x2d\x31\x38\x2e\x36\x20\x2d\x31\x37\x2e\x35\x32\ +\x2c\x30\x20\x2d\x33\x33\x2e\x34\x2c\x37\x2e\x31\x32\x20\x2d\x34\ +\x34\x2e\x38\x38\x2c\x31\x38\x2e\x35\x39\x20\x2d\x31\x31\x2e\x34\ +\x38\x2c\x31\x31\x2e\x34\x38\x20\x2d\x31\x38\x2e\x35\x39\x2c\x32\ +\x37\x2e\x33\x37\x20\x2d\x31\x38\x2e\x35\x39\x2c\x34\x34\x2e\x38\ +\x39\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x67\x3e\x0d\x0a\x20\x20\x20\x20\x3c\x70\x61\x74\ +\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x33\x22\x20\x64\ +\x3d\x22\x4d\x36\x33\x2e\x34\x38\x20\x31\x32\x30\x2e\x36\x32\x6c\ +\x31\x34\x33\x2e\x39\x36\x20\x30\x63\x36\x2e\x39\x38\x2c\x30\x20\ +\x31\x32\x2e\x36\x39\x2c\x35\x2e\x37\x20\x31\x32\x2e\x36\x39\x2c\ +\x31\x32\x2e\x36\x38\x6c\x30\x20\x39\x33\x2e\x31\x39\x63\x30\x2c\ +\x36\x2e\x39\x38\x20\x2d\x35\x2e\x37\x31\x2c\x31\x32\x2e\x36\x38\ +\x20\x2d\x31\x32\x2e\x36\x39\x2c\x31\x32\x2e\x36\x38\x6c\x2d\x31\ +\x34\x33\x2e\x39\x36\x20\x30\x63\x2d\x36\x2e\x39\x38\x2c\x30\x20\ +\x2d\x31\x32\x2e\x36\x39\x2c\x2d\x35\x2e\x37\x20\x2d\x31\x32\x2e\ +\x36\x39\x2c\x2d\x31\x32\x2e\x36\x38\x6c\x30\x20\x2d\x39\x33\x2e\ +\x31\x39\x63\x30\x2c\x2d\x36\x2e\x39\x38\x20\x35\x2e\x37\x31\x2c\ +\x2d\x31\x32\x2e\x36\x38\x20\x31\x32\x2e\x36\x39\x2c\x2d\x31\x32\ +\x2e\x36\x38\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x20\x3c\x70\x61\ +\x74\x68\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\x22\x20\x63\x6c\x61\ +\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\x20\x64\x3d\x22\x4d\x36\x33\ +\x2e\x34\x38\x20\x31\x32\x30\x2e\x36\x32\x6c\x31\x34\x33\x2e\x39\ +\x36\x20\x30\x63\x36\x2e\x39\x38\x2c\x30\x20\x31\x32\x2e\x36\x39\ +\x2c\x35\x2e\x37\x20\x31\x32\x2e\x36\x39\x2c\x31\x32\x2e\x36\x38\ +\x6c\x30\x20\x39\x33\x2e\x31\x39\x63\x30\x2c\x36\x2e\x39\x38\x20\ +\x2d\x35\x2e\x37\x31\x2c\x31\x32\x2e\x36\x38\x20\x2d\x31\x32\x2e\ +\x36\x39\x2c\x31\x32\x2e\x36\x38\x6c\x2d\x31\x34\x33\x2e\x39\x36\ +\x20\x30\x63\x2d\x36\x2e\x39\x38\x2c\x30\x20\x2d\x31\x32\x2e\x36\ +\x39\x2c\x2d\x35\x2e\x37\x20\x2d\x31\x32\x2e\x36\x39\x2c\x2d\x31\ +\x32\x2e\x36\x38\x6c\x30\x20\x2d\x39\x33\x2e\x31\x39\x63\x30\x2c\ +\x2d\x36\x2e\x39\x38\x20\x35\x2e\x37\x31\x2c\x2d\x31\x32\x2e\x36\ +\x38\x20\x31\x32\x2e\x36\x39\x2c\x2d\x31\x32\x2e\x36\x38\x7a\x6d\ +\x31\x34\x33\x2e\x39\x36\x20\x30\x2e\x30\x31\x6c\x2d\x31\x34\x33\ +\x2e\x39\x36\x20\x30\x63\x2d\x33\x2e\x34\x38\x2c\x30\x20\x2d\x36\ +\x2e\x36\x36\x2c\x31\x2e\x34\x34\x20\x2d\x38\x2e\x39\x35\x2c\x33\ +\x2e\x37\x32\x20\x2d\x32\x2e\x32\x39\x2c\x32\x2e\x32\x39\x20\x2d\ +\x33\x2e\x37\x33\x2c\x35\x2e\x34\x37\x20\x2d\x33\x2e\x37\x33\x2c\ +\x38\x2e\x39\x35\x6c\x30\x20\x39\x33\x2e\x31\x39\x63\x30\x2c\x33\ +\x2e\x34\x38\x20\x31\x2e\x34\x34\x2c\x36\x2e\x36\x36\x20\x33\x2e\ +\x37\x33\x2c\x38\x2e\x39\x35\x20\x32\x2e\x32\x39\x2c\x32\x2e\x32\ +\x38\x20\x35\x2e\x34\x37\x2c\x33\x2e\x37\x32\x20\x38\x2e\x39\x35\ +\x2c\x33\x2e\x37\x32\x6c\x31\x34\x33\x2e\x39\x36\x20\x30\x63\x33\ +\x2e\x34\x38\x2c\x30\x20\x36\x2e\x36\x36\x2c\x2d\x31\x2e\x34\x34\ +\x20\x38\x2e\x39\x35\x2c\x2d\x33\x2e\x37\x32\x20\x32\x2e\x32\x39\ +\x2c\x2d\x32\x2e\x32\x39\x20\x33\x2e\x37\x33\x2c\x2d\x35\x2e\x34\ +\x37\x20\x33\x2e\x37\x33\x2c\x2d\x38\x2e\x39\x35\x6c\x30\x20\x2d\ +\x39\x33\x2e\x31\x39\x63\x30\x2c\x2d\x33\x2e\x34\x38\x20\x2d\x31\ +\x2e\x34\x34\x2c\x2d\x36\x2e\x36\x36\x20\x2d\x33\x2e\x37\x33\x2c\ +\x2d\x38\x2e\x39\x35\x20\x2d\x32\x2e\x32\x39\x2c\x2d\x32\x2e\x32\ +\x38\x20\x2d\x35\x2e\x34\x37\x2c\x2d\x33\x2e\x37\x32\x20\x2d\x38\ +\x2e\x39\x35\x2c\x2d\x33\x2e\x37\x32\x7a\x22\x2f\x3e\x0d\x0a\x20\ +\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\x20\x20\x20\x3c\x67\x3e\x0d\x0a\ +\x20\x20\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\ +\x22\x66\x69\x6c\x34\x22\x20\x64\x3d\x22\x4d\x31\x32\x30\x2e\x32\ +\x34\x20\x31\x36\x33\x2e\x38\x6c\x30\x2e\x30\x34\x20\x30\x63\x30\ +\x2c\x2d\x38\x2e\x34\x32\x20\x36\x2e\x38\x2c\x2d\x31\x35\x2e\x32\ +\x34\x20\x31\x35\x2e\x32\x2c\x2d\x31\x35\x2e\x32\x34\x20\x38\x2e\ +\x34\x2c\x30\x20\x31\x35\x2e\x32\x34\x2c\x36\x2e\x38\x32\x20\x31\ +\x35\x2e\x32\x34\x2c\x31\x35\x2e\x32\x34\x6c\x30\x20\x33\x32\x2e\ +\x31\x37\x63\x30\x2c\x38\x2e\x34\x32\x20\x2d\x36\x2e\x38\x32\x2c\ +\x31\x35\x2e\x32\x32\x20\x2d\x31\x35\x2e\x32\x34\x2c\x31\x35\x2e\ +\x32\x32\x20\x2d\x38\x2e\x34\x2c\x30\x20\x2d\x31\x35\x2e\x32\x2c\ +\x2d\x36\x2e\x38\x20\x2d\x31\x35\x2e\x32\x2c\x2d\x31\x35\x2e\x32\ +\x32\x6c\x2d\x30\x2e\x30\x34\x20\x30\x20\x30\x20\x2d\x33\x32\x2e\ +\x31\x37\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x20\x3c\x70\x61\x74\ +\x68\x20\x69\x64\x3d\x22\x5f\x31\x5f\x31\x22\x20\x63\x6c\x61\x73\ +\x73\x3d\x22\x66\x69\x6c\x32\x22\x20\x64\x3d\x22\x4d\x31\x32\x30\ +\x2e\x32\x34\x20\x31\x36\x33\x2e\x38\x6c\x30\x2e\x30\x34\x20\x30\ +\x63\x30\x2c\x2d\x38\x2e\x34\x32\x20\x36\x2e\x38\x2c\x2d\x31\x35\ +\x2e\x32\x34\x20\x31\x35\x2e\x32\x2c\x2d\x31\x35\x2e\x32\x34\x20\ +\x38\x2e\x34\x2c\x30\x20\x31\x35\x2e\x32\x34\x2c\x36\x2e\x38\x32\ +\x20\x31\x35\x2e\x32\x34\x2c\x31\x35\x2e\x32\x34\x6c\x30\x20\x33\ +\x32\x2e\x31\x37\x63\x30\x2c\x38\x2e\x34\x32\x20\x2d\x36\x2e\x38\ +\x32\x2c\x31\x35\x2e\x32\x32\x20\x2d\x31\x35\x2e\x32\x34\x2c\x31\ +\x35\x2e\x32\x32\x20\x2d\x38\x2e\x34\x2c\x30\x20\x2d\x31\x35\x2e\ +\x32\x2c\x2d\x36\x2e\x38\x20\x2d\x31\x35\x2e\x32\x2c\x2d\x31\x35\ +\x2e\x32\x32\x6c\x2d\x30\x2e\x30\x34\x20\x30\x20\x30\x20\x2d\x33\ +\x32\x2e\x31\x37\x7a\x6d\x30\x2e\x30\x34\x20\x30\x2e\x30\x31\x6c\ +\x2d\x30\x2e\x30\x33\x20\x30\x20\x30\x20\x33\x32\x2e\x31\x35\x20\ +\x30\x2e\x30\x33\x20\x30\x63\x30\x2c\x30\x20\x30\x2e\x30\x31\x2c\ +\x30\x2e\x30\x31\x20\x30\x2e\x30\x31\x2c\x30\x2e\x30\x31\x20\x30\ +\x2c\x34\x2e\x32\x20\x31\x2e\x37\x31\x2c\x38\x2e\x30\x31\x20\x34\ +\x2e\x34\x35\x2c\x31\x30\x2e\x37\x36\x20\x32\x2e\x37\x34\x2c\x32\ +\x2e\x37\x34\x20\x36\x2e\x35\x35\x2c\x34\x2e\x34\x35\x20\x31\x30\ +\x2e\x37\x34\x2c\x34\x2e\x34\x35\x20\x34\x2e\x32\x2c\x30\x20\x38\ +\x2e\x30\x32\x2c\x2d\x31\x2e\x37\x31\x20\x31\x30\x2e\x37\x37\x2c\ +\x2d\x34\x2e\x34\x36\x20\x32\x2e\x37\x35\x2c\x2d\x32\x2e\x37\x34\ +\x20\x34\x2e\x34\x36\x2c\x2d\x36\x2e\x35\x35\x20\x34\x2e\x34\x36\ +\x2c\x2d\x31\x30\x2e\x37\x35\x6c\x30\x20\x2d\x33\x32\x2e\x31\x37\ +\x63\x30\x2c\x2d\x34\x2e\x32\x20\x2d\x31\x2e\x37\x32\x2c\x2d\x38\ +\x2e\x30\x32\x20\x2d\x34\x2e\x34\x37\x2c\x2d\x31\x30\x2e\x37\x36\ +\x20\x2d\x32\x2e\x37\x35\x2c\x2d\x32\x2e\x37\x35\x20\x2d\x36\x2e\ +\x35\x37\x2c\x2d\x34\x2e\x34\x37\x20\x2d\x31\x30\x2e\x37\x36\x2c\ +\x2d\x34\x2e\x34\x37\x20\x2d\x34\x2e\x31\x39\x2c\x30\x20\x2d\x38\ +\x2c\x31\x2e\x37\x31\x20\x2d\x31\x30\x2e\x37\x34\x2c\x34\x2e\x34\ +\x36\x20\x2d\x32\x2e\x37\x34\x2c\x32\x2e\x37\x35\x20\x2d\x34\x2e\ +\x34\x35\x2c\x36\x2e\x35\x37\x20\x2d\x34\x2e\x34\x35\x2c\x31\x30\ +\x2e\x37\x37\x20\x30\x2c\x30\x20\x2d\x30\x2e\x30\x31\x2c\x30\x2e\ +\x30\x31\x20\x2d\x30\x2e\x30\x31\x2c\x30\x2e\x30\x31\x7a\x22\x2f\ +\x3e\x0d\x0a\x20\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\x20\x20\x3c\x2f\ +\x67\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\ +\x3e\x0d\x0a\ +\x00\x00\x06\x49\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x32\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x39\x39\x39\x39\x39\x39\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x34\x2e\x31\x31\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3a\x72\x6f\x75\x6e\x64\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\ +\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\ +\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\x23\ +\x34\x36\x38\x32\x43\x38\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\ +\x64\x74\x68\x3a\x31\x37\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\x65\ +\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3a\x72\x6f\x75\x6e\x64\x3b\ +\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\ +\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\ +\x2e\x73\x74\x72\x31\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\x23\x46\ +\x46\x39\x39\x33\x33\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\ +\x74\x68\x3a\x31\x37\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\x65\x2d\ +\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3a\x72\x6f\x75\x6e\x64\x3b\x73\ +\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\ +\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\ +\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\x6e\x65\x7d\ +\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\x69\x6c\ +\x6c\x3a\x23\x34\x36\x38\x32\x43\x38\x7d\x0d\x0a\x20\x20\x20\x5d\ +\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\ +\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\ +\x3d\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\ +\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\ +\x69\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\ +\x30\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\ +\x0a\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\ +\x66\x69\x6c\x30\x22\x20\x78\x3d\x22\x2d\x30\x2e\x30\x31\x22\x20\ +\x79\x3d\x22\x2d\x30\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\ +\x30\x2e\x39\x33\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\ +\x30\x2e\x39\x33\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x6c\x69\x6e\x65\ +\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\ +\x30\x22\x20\x78\x31\x3d\x22\x35\x35\x2e\x38\x31\x22\x20\x79\x31\ +\x3d\x22\x31\x31\x30\x2e\x30\x38\x22\x20\x78\x32\x3d\x22\x32\x31\ +\x35\x2e\x31\x32\x22\x20\x79\x32\x3d\x20\x22\x31\x31\x30\x2e\x30\ +\x38\x22\x20\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\ +\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\ +\x6f\x69\x6e\x74\x73\x3d\x22\x31\x38\x2e\x35\x37\x2c\x31\x31\x30\ +\x2e\x30\x38\x20\x34\x32\x2e\x34\x2c\x39\x36\x2e\x33\x32\x20\x36\ +\x36\x2e\x32\x33\x2c\x38\x32\x2e\x35\x36\x20\x36\x36\x2e\x32\x33\ +\x2c\x31\x31\x30\x2e\x30\x38\x20\x36\x36\x2e\x32\x33\x2c\x31\x33\ +\x37\x2e\x35\x39\x20\x34\x32\x2e\x34\x2c\x31\x32\x33\x2e\x38\x34\ +\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\ +\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\ +\x69\x6e\x74\x73\x3d\x22\x32\x35\x32\x2e\x33\x35\x2c\x31\x31\x30\ +\x2e\x30\x38\x20\x32\x32\x38\x2e\x35\x32\x2c\x31\x32\x33\x2e\x38\ +\x34\x20\x32\x30\x34\x2e\x36\x39\x2c\x31\x33\x37\x2e\x36\x20\x32\ +\x30\x34\x2e\x36\x39\x2c\x31\x31\x30\x2e\x30\x38\x20\x32\x30\x34\ +\x2e\x36\x39\x2c\x38\x32\x2e\x35\x37\x20\x32\x32\x38\x2e\x35\x32\ +\x2c\x39\x36\x2e\x33\x32\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x6c\ +\x69\x6e\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\ +\x73\x74\x72\x31\x22\x20\x78\x31\x3d\x22\x38\x2e\x34\x38\x22\x20\ +\x79\x31\x3d\x22\x31\x36\x39\x2e\x33\x35\x22\x20\x78\x32\x3d\x22\ +\x32\x36\x32\x2e\x34\x34\x22\x20\x79\x32\x3d\x20\x22\x31\x36\x39\ +\x2e\x33\x35\x22\x20\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x61\x74\x68\ +\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\ +\x32\x22\x20\x64\x3d\x22\x4d\x38\x2e\x34\x38\x20\x32\x30\x33\x2e\ +\x31\x39\x6c\x30\x20\x2d\x31\x33\x35\x2e\x34\x36\x6d\x32\x35\x33\ +\x2e\x39\x36\x20\x31\x33\x35\x2e\x34\x36\x6c\x30\x20\x2d\x31\x33\ +\x35\x2e\x34\x36\x22\x2f\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\ +\x3c\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x0d\xdc\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\ +\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\ +\x66\x69\x6c\x6c\x3a\x23\x34\x36\x38\x32\x43\x38\x3b\x66\x69\x6c\ +\x6c\x2d\x72\x75\x6c\x65\x3a\x6e\x6f\x6e\x7a\x65\x72\x6f\x7d\x0d\ +\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x32\x20\x7b\x66\x69\x6c\x6c\ +\x3a\x75\x72\x6c\x28\x23\x69\x64\x30\x29\x7d\x0d\x0a\x20\x20\x20\ +\x20\x2e\x66\x69\x6c\x33\x20\x7b\x66\x69\x6c\x6c\x3a\x75\x72\x6c\ +\x28\x23\x69\x64\x31\x29\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\ +\x6c\x34\x20\x7b\x66\x69\x6c\x6c\x3a\x75\x72\x6c\x28\x23\x69\x64\ +\x32\x29\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\x3c\ +\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\x20\x3c\x6c\x69\x6e\x65\ +\x61\x72\x47\x72\x61\x64\x69\x65\x6e\x74\x20\x69\x64\x3d\x22\x69\ +\x64\x30\x22\x20\x67\x72\x61\x64\x69\x65\x6e\x74\x55\x6e\x69\x74\ +\x73\x3d\x22\x75\x73\x65\x72\x53\x70\x61\x63\x65\x4f\x6e\x55\x73\ +\x65\x22\x20\x78\x31\x3d\x22\x31\x37\x39\x2e\x32\x32\x22\x20\x79\ +\x31\x3d\x22\x35\x36\x2e\x31\x34\x22\x20\x78\x32\x3d\x22\x32\x31\ +\x34\x2e\x37\x38\x22\x20\x79\x32\x3d\x22\x39\x31\x2e\x37\x22\x3e\ +\x0d\x0a\x20\x20\x20\x3c\x73\x74\x6f\x70\x20\x6f\x66\x66\x73\x65\ +\x74\x3d\x22\x30\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x74\x6f\ +\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\x20\x73\x74\x6f\ +\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x46\x41\x39\x45\x30\x44\x22\ +\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x73\x74\x6f\x70\x20\x6f\x66\x66\ +\x73\x65\x74\x3d\x22\x30\x2e\x33\x32\x31\x35\x36\x39\x22\x20\x73\ +\x74\x79\x6c\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\ +\x74\x79\x3a\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\ +\x3a\x23\x46\x45\x46\x45\x46\x45\x22\x2f\x3e\x0d\x0a\x20\x20\x20\ +\x3c\x73\x74\x6f\x70\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\x31\x22\ +\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\ +\x63\x69\x74\x79\x3a\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\ +\x6f\x72\x3a\x23\x44\x39\x37\x33\x30\x30\x22\x2f\x3e\x0d\x0a\x20\ +\x20\x3c\x2f\x6c\x69\x6e\x65\x61\x72\x47\x72\x61\x64\x69\x65\x6e\ +\x74\x3e\x0d\x0a\x20\x20\x3c\x6c\x69\x6e\x65\x61\x72\x47\x72\x61\ +\x64\x69\x65\x6e\x74\x20\x69\x64\x3d\x22\x69\x64\x31\x22\x20\x67\ +\x72\x61\x64\x69\x65\x6e\x74\x55\x6e\x69\x74\x73\x3d\x22\x75\x73\ +\x65\x72\x53\x70\x61\x63\x65\x4f\x6e\x55\x73\x65\x22\x20\x78\x31\ +\x3d\x22\x32\x31\x37\x2e\x37\x35\x22\x20\x79\x31\x3d\x22\x31\x37\ +\x2e\x36\x32\x22\x20\x78\x32\x3d\x22\x32\x35\x33\x2e\x33\x22\x20\ +\x79\x32\x3d\x22\x35\x33\x2e\x31\x37\x22\x3e\x0d\x0a\x20\x20\x20\ +\x3c\x73\x74\x6f\x70\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\x30\x22\ +\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\ +\x63\x69\x74\x79\x3a\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\ +\x6f\x72\x3a\x23\x32\x34\x32\x39\x32\x45\x22\x2f\x3e\x0d\x0a\x20\ +\x20\x20\x3c\x73\x74\x6f\x70\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\ +\x30\x2e\x33\x32\x31\x35\x36\x39\x22\x20\x73\x74\x79\x6c\x65\x3d\ +\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\ +\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x42\x30\x42\ +\x46\x42\x46\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x73\x74\x6f\x70\ +\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\x31\x22\x20\x73\x74\x79\x6c\ +\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\ +\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x32\ +\x34\x32\x39\x32\x45\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x6c\x69\ +\x6e\x65\x61\x72\x47\x72\x61\x64\x69\x65\x6e\x74\x3e\x0d\x0a\x20\ +\x20\x3c\x6c\x69\x6e\x65\x61\x72\x47\x72\x61\x64\x69\x65\x6e\x74\ +\x20\x69\x64\x3d\x22\x69\x64\x32\x22\x20\x67\x72\x61\x64\x69\x65\ +\x6e\x74\x55\x6e\x69\x74\x73\x3d\x22\x75\x73\x65\x72\x53\x70\x61\ +\x63\x65\x4f\x6e\x55\x73\x65\x22\x20\x78\x31\x3d\x22\x31\x34\x32\ +\x2e\x31\x38\x22\x20\x79\x31\x3d\x22\x31\x30\x35\x2e\x30\x33\x22\ +\x20\x78\x32\x3d\x22\x31\x38\x33\x2e\x36\x37\x22\x20\x79\x32\x3d\ +\x22\x31\x32\x32\x2e\x38\x31\x22\x3e\x0d\x0a\x20\x20\x20\x3c\x73\ +\x74\x6f\x70\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\x30\x22\x20\x73\ +\x74\x79\x6c\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\ +\x74\x79\x3a\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\ +\x3a\x23\x33\x38\x32\x42\x32\x36\x22\x2f\x3e\x0d\x0a\x20\x20\x20\ +\x3c\x73\x74\x6f\x70\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\x30\x2e\ +\x33\x32\x31\x35\x36\x39\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\ +\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\x20\x73\ +\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x45\x33\x45\x33\x44\ +\x45\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x73\x74\x6f\x70\x20\x6f\ +\x66\x66\x73\x65\x74\x3d\x22\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\ +\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\ +\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x33\x38\x32\ +\x42\x32\x36\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x6c\x69\x6e\x65\ +\x61\x72\x47\x72\x61\x64\x69\x65\x6e\x74\x3e\x0d\x0a\x20\x3c\x2f\ +\x64\x65\x66\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\x22\x43\ +\x61\x6d\x61\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\x3e\x0d\ +\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\x69\x64\x3d\ +\x22\x43\x6f\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\x43\x6f\ +\x72\x65\x6c\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\x20\x20\ +\x3c\x72\x65\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\ +\x30\x22\x20\x78\x3d\x22\x2d\x30\x2e\x30\x31\x22\x20\x79\x3d\x22\ +\x2d\x30\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\x30\x2e\x39\ +\x33\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\x39\ +\x33\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x64\x3d\x22\x4d\x38\ +\x2e\x34\x36\x20\x32\x36\x31\x2e\x34\x39\x6c\x35\x39\x2e\x32\x33\ +\x20\x2d\x31\x38\x37\x2e\x35\x32\x20\x32\x31\x2e\x38\x33\x20\x30\ +\x20\x36\x32\x2e\x37\x32\x20\x31\x38\x37\x2e\x35\x32\x20\x2d\x32\ +\x33\x2e\x32\x36\x20\x30\x20\x2d\x31\x37\x2e\x36\x38\x20\x2d\x35\ +\x36\x2e\x38\x33\x20\x2d\x36\x34\x2e\x38\x32\x20\x30\x20\x2d\x31\ +\x36\x2e\x36\x33\x20\x35\x36\x2e\x38\x33\x20\x2d\x32\x31\x2e\x33\ +\x39\x20\x30\x7a\x6d\x34\x33\x2e\x39\x35\x20\x2d\x37\x37\x6c\x35\ +\x32\x2e\x36\x35\x20\x30\x20\x2d\x31\x36\x2e\x31\x31\x20\x2d\x35\ +\x31\x2e\x39\x34\x63\x2d\x34\x2e\x39\x38\x2c\x2d\x31\x35\x2e\x39\ +\x33\x20\x2d\x38\x2e\x36\x34\x2c\x2d\x32\x38\x2e\x39\x20\x2d\x31\ +\x30\x2e\x39\x36\x2c\x2d\x33\x38\x2e\x38\x39\x20\x2d\x32\x2e\x30\ +\x31\x2c\x31\x32\x2e\x30\x34\x20\x2d\x34\x2e\x38\x2c\x32\x33\x2e\ +\x39\x36\x20\x2d\x38\x2e\x34\x32\x2c\x33\x35\x2e\x38\x33\x6c\x2d\ +\x31\x37\x2e\x31\x36\x20\x35\x35\x7a\x6d\x31\x38\x35\x2e\x39\x36\ +\x20\x36\x30\x2e\x32\x38\x63\x2d\x36\x2e\x33\x33\x2c\x36\x2e\x35\ +\x39\x20\x2d\x31\x32\x2e\x38\x38\x2c\x31\x31\x2e\x35\x33\x20\x2d\ +\x31\x39\x2e\x36\x39\x2c\x31\x34\x2e\x38\x34\x20\x2d\x36\x2e\x38\ +\x35\x2c\x33\x2e\x33\x36\x20\x2d\x31\x33\x2e\x39\x32\x2c\x35\x2e\ +\x30\x32\x20\x2d\x32\x31\x2e\x32\x36\x2c\x35\x2e\x30\x32\x20\x2d\ +\x31\x31\x2e\x36\x31\x2c\x30\x20\x2d\x32\x30\x2e\x38\x36\x2c\x2d\ +\x33\x2e\x34\x39\x20\x2d\x32\x37\x2e\x37\x36\x2c\x2d\x31\x30\x2e\ +\x35\x32\x20\x2d\x36\x2e\x39\x34\x2c\x2d\x36\x2e\x39\x38\x20\x2d\ +\x31\x30\x2e\x33\x39\x2c\x2d\x31\x36\x2e\x34\x31\x20\x2d\x31\x30\ +\x2e\x33\x39\x2c\x2d\x32\x38\x2e\x32\x38\x20\x30\x2c\x2d\x37\x2e\ +\x38\x36\x20\x31\x2e\x36\x32\x2c\x2d\x31\x34\x2e\x38\x20\x34\x2e\ +\x38\x39\x2c\x2d\x32\x30\x2e\x38\x37\x20\x33\x2e\x32\x33\x2c\x2d\ +\x36\x2e\x30\x36\x20\x37\x2e\x36\x38\x2c\x2d\x31\x30\x2e\x38\x32\ +\x20\x31\x33\x2e\x33\x32\x2c\x2d\x31\x34\x2e\x32\x33\x20\x35\x2e\ +\x36\x33\x2c\x2d\x33\x2e\x34\x20\x31\x34\x2e\x31\x38\x2c\x2d\x35\ +\x2e\x39\x33\x20\x32\x35\x2e\x36\x32\x2c\x2d\x37\x2e\x35\x35\x20\ +\x31\x35\x2e\x33\x36\x2c\x2d\x32\x2e\x31\x38\x20\x32\x36\x2e\x36\ +\x37\x2c\x2d\x34\x2e\x38\x20\x33\x33\x2e\x38\x33\x2c\x2d\x37\x2e\ +\x38\x36\x6c\x30\x2e\x31\x33\x20\x2d\x35\x2e\x38\x39\x63\x30\x2c\ +\x2d\x39\x2e\x33\x34\x20\x2d\x31\x2e\x36\x36\x2c\x2d\x31\x35\x2e\ +\x38\x39\x20\x2d\x34\x2e\x39\x38\x2c\x2d\x31\x39\x2e\x36\x38\x20\ +\x2d\x34\x2e\x36\x37\x2c\x2d\x35\x2e\x34\x36\x20\x2d\x31\x31\x2e\ +\x38\x37\x2c\x2d\x38\x2e\x32\x31\x20\x2d\x32\x31\x2e\x35\x36\x2c\ +\x2d\x38\x2e\x32\x31\x20\x2d\x38\x2e\x37\x38\x2c\x30\x20\x2d\x31\ +\x35\x2e\x33\x32\x2c\x31\x2e\x38\x33\x20\x2d\x31\x39\x2e\x36\x39\ +\x2c\x35\x2e\x34\x36\x20\x2d\x34\x2e\x33\x32\x2c\x33\x2e\x36\x32\ +\x20\x2d\x37\x2e\x35\x31\x2c\x31\x30\x2e\x34\x37\x20\x2d\x39\x2e\ +\x35\x36\x2c\x32\x30\x2e\x34\x37\x6c\x2d\x31\x38\x2e\x38\x35\x20\ +\x2d\x33\x2e\x34\x31\x63\x32\x2e\x36\x36\x2c\x2d\x31\x34\x2e\x34\ +\x34\x20\x37\x2e\x39\x38\x2c\x2d\x32\x34\x2e\x39\x32\x20\x31\x36\ +\x2e\x30\x36\x2c\x2d\x33\x31\x2e\x35\x31\x20\x38\x2e\x30\x37\x2c\ +\x2d\x36\x2e\x35\x39\x20\x31\x39\x2e\x36\x34\x2c\x2d\x39\x2e\x38\ +\x37\x20\x33\x34\x2e\x36\x36\x2c\x2d\x39\x2e\x38\x37\x20\x31\x31\ +\x2e\x37\x2c\x30\x20\x32\x30\x2e\x38\x36\x2c\x32\x2e\x31\x39\x20\ +\x32\x37\x2e\x35\x34\x2c\x36\x2e\x35\x39\x20\x36\x2e\x36\x33\x2c\ +\x34\x2e\x33\x37\x20\x31\x30\x2e\x38\x36\x2c\x39\x2e\x38\x33\x20\ +\x31\x32\x2e\x36\x31\x2c\x31\x36\x2e\x32\x39\x20\x31\x2e\x37\x34\ +\x2c\x36\x2e\x35\x20\x32\x2e\x36\x31\x2c\x31\x35\x2e\x39\x33\x20\ +\x32\x2e\x36\x31\x2c\x32\x38\x2e\x33\x37\x6c\x30\x20\x33\x30\x2e\ +\x36\x38\x63\x30\x2c\x32\x31\x2e\x34\x38\x20\x30\x2e\x34\x2c\x33\ +\x35\x2e\x30\x31\x20\x31\x2e\x32\x33\x2c\x34\x30\x2e\x36\x20\x30\ +\x2e\x37\x38\x2c\x35\x2e\x35\x34\x20\x32\x2e\x34\x2c\x31\x31\x20\ +\x34\x2e\x38\x2c\x31\x36\x2e\x32\x38\x6c\x2d\x31\x39\x2e\x36\x33\ +\x20\x30\x63\x2d\x32\x2e\x30\x31\x2c\x2d\x34\x2e\x36\x37\x20\x2d\ +\x33\x2e\x33\x32\x2c\x2d\x31\x30\x2e\x32\x36\x20\x2d\x33\x2e\x39\ +\x33\x2c\x2d\x31\x36\x2e\x37\x32\x7a\x6d\x2d\x31\x2e\x34\x34\x20\ +\x2d\x35\x31\x2e\x33\x37\x63\x2d\x36\x2e\x39\x2c\x33\x2e\x34\x20\ +\x2d\x31\x37\x2e\x32\x2c\x36\x2e\x32\x38\x20\x2d\x33\x30\x2e\x38\ +\x36\x2c\x38\x2e\x36\x34\x20\x2d\x37\x2e\x39\x35\x2c\x31\x2e\x33\ +\x35\x20\x2d\x31\x33\x2e\x34\x39\x2c\x32\x2e\x39\x32\x20\x2d\x31\ +\x36\x2e\x36\x33\x2c\x34\x2e\x36\x37\x20\x2d\x33\x2e\x31\x39\x2c\ +\x31\x2e\x37\x35\x20\x2d\x35\x2e\x36\x34\x2c\x34\x2e\x32\x33\x20\ +\x2d\x37\x2e\x33\x38\x2c\x37\x2e\x34\x32\x20\x2d\x31\x2e\x37\x35\ +\x2c\x33\x2e\x31\x39\x20\x2d\x32\x2e\x36\x32\x2c\x36\x2e\x39\x20\ +\x2d\x32\x2e\x36\x32\x2c\x31\x31\x2e\x30\x39\x20\x30\x2c\x36\x2e\ +\x34\x36\x20\x32\x2e\x30\x31\x2c\x31\x31\x2e\x36\x35\x20\x36\x2e\ +\x30\x32\x2c\x31\x35\x2e\x35\x34\x20\x34\x2e\x30\x32\x2c\x33\x2e\ +\x38\x38\x20\x39\x2e\x35\x36\x2c\x35\x2e\x38\x20\x31\x36\x2e\x36\ +\x33\x2c\x35\x2e\x38\x20\x36\x2e\x37\x37\x2c\x30\x20\x31\x32\x2e\ +\x38\x38\x2c\x2d\x31\x2e\x37\x34\x20\x31\x38\x2e\x33\x38\x2c\x2d\ +\x35\x2e\x32\x34\x20\x35\x2e\x35\x2c\x2d\x33\x2e\x34\x39\x20\x39\ +\x2e\x36\x35\x2c\x2d\x38\x2e\x31\x36\x20\x31\x32\x2e\x33\x35\x2c\ +\x2d\x31\x34\x2e\x30\x31\x20\x32\x2e\x37\x35\x2c\x2d\x35\x2e\x38\ +\x20\x34\x2e\x31\x31\x2c\x2d\x31\x34\x2e\x33\x32\x20\x34\x2e\x31\ +\x31\x2c\x2d\x32\x35\x2e\x34\x39\x6c\x30\x20\x2d\x38\x2e\x34\x32\ +\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x67\x20\x69\x64\x3d\x22\x5f\ +\x32\x37\x37\x39\x37\x32\x30\x37\x39\x39\x34\x37\x32\x22\x3e\x0d\ +\x0a\x20\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\ +\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\x20\x70\x6f\x69\x6e\x74\x73\ +\x3d\x22\x32\x33\x39\x2e\x39\x37\x2c\x36\x36\x2e\x35\x31\x20\x31\ +\x38\x39\x2e\x35\x39\x2c\x31\x31\x36\x2e\x38\x38\x20\x31\x35\x34\ +\x2e\x30\x34\x2c\x38\x31\x2e\x33\x33\x20\x32\x30\x34\x2e\x34\x31\ +\x2c\x33\x30\x2e\x39\x35\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\ +\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\ +\x69\x6c\x33\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x32\x33\x31\ +\x2e\x30\x38\x2c\x34\x2e\x32\x38\x20\x32\x30\x34\x2e\x34\x31\x2c\ +\x33\x30\x2e\x39\x35\x20\x32\x33\x39\x2e\x39\x37\x2c\x36\x36\x2e\ +\x35\x31\x20\x32\x36\x36\x2e\x36\x34\x2c\x33\x39\x2e\x38\x34\x20\ +\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\ +\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x34\x22\x20\x70\x6f\ +\x69\x6e\x74\x73\x3d\x22\x31\x35\x34\x2e\x30\x33\x2c\x38\x31\x2e\ +\x33\x34\x20\x31\x38\x39\x2e\x35\x38\x2c\x31\x31\x36\x2e\x38\x39\ +\x20\x31\x34\x38\x2e\x31\x31\x2c\x31\x32\x32\x2e\x38\x31\x20\x22\ +\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\x20\x3c\x2f\x67\ +\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x06\x4e\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x31\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x39\x39\x39\x39\x39\x39\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x34\x2e\x31\x31\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3a\x72\x6f\x75\x6e\x64\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\ +\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\ +\x20\x2e\x73\x74\x72\x32\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\x23\ +\x34\x36\x38\x32\x43\x38\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\ +\x64\x74\x68\x3a\x31\x37\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\x65\ +\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3a\x72\x6f\x75\x6e\x64\x3b\ +\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\ +\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\ +\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\x23\x46\ +\x46\x39\x39\x33\x33\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\ +\x74\x68\x3a\x31\x37\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\x65\x2d\ +\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3a\x72\x6f\x75\x6e\x64\x3b\x73\ +\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\ +\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\ +\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\x6e\x65\x7d\ +\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\x69\x6c\ +\x6c\x3a\x23\x34\x36\x38\x32\x43\x38\x7d\x0d\x0a\x20\x20\x20\x5d\ +\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\ +\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\ +\x3d\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\ +\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\ +\x69\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\ +\x30\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\ +\x0a\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\ +\x66\x69\x6c\x30\x22\x20\x78\x3d\x22\x2d\x30\x22\x20\x79\x3d\x22\ +\x2d\x30\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\x30\x2e\x39\ +\x33\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\x39\ +\x33\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x6c\x69\x6e\x65\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x30\x22\x20\ +\x78\x31\x3d\x22\x37\x39\x2e\x37\x32\x22\x20\x79\x31\x3d\x22\x32\ +\x34\x34\x2e\x36\x39\x22\x20\x78\x32\x3d\x22\x32\x34\x34\x2e\x36\ +\x39\x22\x20\x79\x32\x3d\x20\x22\x37\x39\x2e\x37\x32\x22\x20\x2f\ +\x3e\x0d\x0a\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\ +\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x31\x22\x20\x64\x3d\x22\ +\x4d\x32\x36\x35\x2e\x38\x34\x20\x31\x30\x30\x2e\x38\x37\x6c\x2d\ +\x39\x35\x2e\x37\x39\x20\x2d\x39\x35\x2e\x37\x39\x6d\x2d\x36\x39\ +\x2e\x31\x38\x20\x32\x36\x30\x2e\x37\x36\x6c\x2d\x39\x35\x2e\x37\ +\x39\x20\x2d\x39\x35\x2e\x37\x39\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\ +\x6c\x69\x6e\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\ +\x20\x73\x74\x72\x32\x22\x20\x78\x31\x3d\x22\x31\x36\x30\x2e\x36\ +\x37\x22\x20\x79\x31\x3d\x22\x36\x37\x2e\x39\x35\x22\x20\x78\x32\ +\x3d\x22\x36\x37\x2e\x39\x36\x22\x20\x79\x32\x3d\x20\x22\x31\x36\ +\x30\x2e\x36\x36\x22\x20\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\ +\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\ +\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x31\x39\x30\x2e\x31\x34\ +\x2c\x33\x38\x2e\x34\x38\x20\x31\x36\x33\x2e\x35\x36\x2c\x34\x35\ +\x2e\x36\x20\x31\x33\x36\x2e\x39\x39\x2c\x35\x32\x2e\x37\x32\x20\ +\x31\x35\x36\x2e\x34\x34\x2c\x37\x32\x2e\x31\x38\x20\x31\x37\x35\ +\x2e\x39\x2c\x39\x31\x2e\x36\x33\x20\x31\x38\x33\x2e\x30\x32\x2c\ +\x36\x35\x2e\x30\x36\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\ +\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\ +\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x33\x38\x2e\x34\x39\ +\x2c\x31\x39\x30\x2e\x31\x33\x20\x36\x35\x2e\x30\x37\x2c\x31\x38\ +\x33\x2e\x30\x31\x20\x39\x31\x2e\x36\x34\x2c\x31\x37\x35\x2e\x38\ +\x39\x20\x37\x32\x2e\x31\x39\x2c\x31\x35\x36\x2e\x34\x33\x20\x35\ +\x32\x2e\x37\x33\x2c\x31\x33\x36\x2e\x39\x38\x20\x34\x35\x2e\x36\ +\x31\x2c\x31\x36\x33\x2e\x35\x35\x20\x22\x2f\x3e\x0d\x0a\x20\x3c\ +\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x05\x10\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x31\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x34\x36\x38\x32\x43\x38\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x36\x2e\x39\x33\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\ +\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x73\x74\x72\x30\ +\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\x23\x46\x46\x39\x39\x33\x33\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x31\x37\ +\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\ +\x6f\x69\x6e\x3a\x72\x6f\x75\x6e\x64\x3b\x73\x74\x72\x6f\x6b\x65\ +\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\ +\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x30\x20\ +\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\x6e\x65\x7d\x0d\x0a\x20\x20\x20\ +\x5d\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\ +\x0a\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\ +\x64\x3d\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\ +\x31\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\ +\x20\x69\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\ +\x5f\x30\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\ +\x0d\x0a\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\ +\x22\x66\x69\x6c\x30\x22\x20\x78\x3d\x22\x2d\x30\x2e\x30\x31\x22\ +\x20\x79\x3d\x22\x2d\x30\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\ +\x37\x30\x2e\x39\x33\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\ +\x37\x30\x2e\x39\x33\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x6c\x69\x6e\ +\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\ +\x72\x30\x22\x20\x78\x31\x3d\x22\x39\x35\x2e\x38\x38\x22\x20\x79\ +\x31\x3d\x22\x35\x2e\x39\x38\x22\x20\x78\x32\x3d\x22\x36\x2e\x30\ +\x38\x22\x20\x79\x32\x3d\x20\x22\x39\x35\x2e\x37\x38\x22\x20\x2f\ +\x3e\x0d\x0a\x20\x20\x3c\x6c\x69\x6e\x65\x20\x63\x6c\x61\x73\x73\ +\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x30\x22\x20\x78\x31\x3d\ +\x22\x32\x36\x32\x2e\x34\x36\x22\x20\x79\x31\x3d\x22\x34\x32\x2e\ +\x33\x34\x22\x20\x78\x32\x3d\x22\x32\x36\x32\x2e\x34\x36\x22\x20\ +\x79\x32\x3d\x20\x22\x32\x32\x38\x2e\x36\x22\x20\x2f\x3e\x0d\x0a\ +\x20\x20\x3c\x6c\x69\x6e\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\ +\x69\x6c\x30\x20\x73\x74\x72\x30\x22\x20\x78\x31\x3d\x22\x34\x32\ +\x2e\x34\x32\x22\x20\x79\x31\x3d\x22\x32\x36\x32\x2e\x33\x38\x22\ +\x20\x78\x32\x3d\x22\x32\x32\x38\x2e\x36\x39\x22\x20\x79\x32\x3d\ +\x20\x22\x32\x36\x32\x2e\x33\x38\x22\x20\x2f\x3e\x0d\x0a\x20\x20\ +\x3c\x63\x69\x72\x63\x6c\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\ +\x69\x6c\x30\x20\x73\x74\x72\x31\x22\x20\x63\x78\x3d\x22\x31\x33\ +\x39\x2e\x37\x39\x22\x20\x63\x79\x3d\x22\x31\x33\x39\x2e\x37\x31\ +\x22\x20\x72\x3d\x22\x31\x31\x32\x2e\x31\x37\x22\x2f\x3e\x0d\x0a\ +\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x05\xfa\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x31\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x67\x72\x61\x79\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\ +\x68\x3a\x31\x34\x2e\x31\x31\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6c\ +\x69\x6e\x65\x6a\x6f\x69\x6e\x3a\x72\x6f\x75\x6e\x64\x3b\x73\x74\ +\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\ +\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x73\ +\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\x23\x42\x33\x42\ +\x33\x42\x33\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\ +\x3a\x31\x37\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\ +\x6e\x65\x63\x61\x70\x3a\x72\x6f\x75\x6e\x64\x3b\x73\x74\x72\x6f\ +\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3a\x72\x6f\x75\x6e\ +\x64\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\ +\x6d\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\ +\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\ +\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\ +\x66\x69\x6c\x6c\x3a\x23\x34\x36\x38\x32\x43\x38\x7d\x0d\x0a\x20\ +\x20\x20\x20\x2e\x66\x69\x6c\x32\x20\x7b\x66\x69\x6c\x6c\x3a\x67\ +\x72\x61\x79\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x33\x20\ +\x7b\x66\x69\x6c\x6c\x3a\x23\x46\x46\x39\x39\x33\x33\x7d\x0d\x0a\ +\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\x6c\ +\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\x3c\ +\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\x30\ +\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\ +\x61\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\x72\ +\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\x72\ +\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\x61\ +\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\x78\x3d\x22\x2d\x30\x2e\ +\x30\x31\x22\x20\x79\x3d\x22\x2d\x30\x22\x20\x77\x69\x64\x74\x68\ +\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x20\x68\x65\x69\x67\x68\x74\ +\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\ +\x63\x69\x72\x63\x6c\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\ +\x6c\x31\x22\x20\x63\x78\x3d\x22\x37\x36\x2e\x31\x38\x22\x20\x63\ +\x79\x3d\x22\x31\x39\x34\x2e\x37\x34\x22\x20\x72\x3d\x22\x32\x35\ +\x2e\x34\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x63\x69\x72\x63\x6c\x65\ +\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\ +\x30\x22\x20\x63\x78\x3d\x22\x37\x36\x2e\x31\x38\x22\x20\x63\x79\ +\x3d\x22\x31\x39\x34\x2e\x37\x34\x22\x20\x72\x3d\x22\x35\x30\x2e\ +\x38\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\ +\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\x20\x70\x6f\ +\x69\x6e\x74\x73\x3d\x22\x31\x32\x37\x2c\x31\x34\x33\x2e\x39\x32\ +\x20\x31\x34\x37\x2e\x34\x34\x2c\x31\x33\x38\x2e\x34\x35\x20\x31\ +\x36\x37\x2e\x38\x38\x2c\x31\x33\x32\x2e\x39\x37\x20\x31\x35\x32\ +\x2e\x39\x32\x2c\x31\x31\x38\x20\x31\x33\x37\x2e\x39\x35\x2c\x31\ +\x30\x33\x2e\x30\x34\x20\x31\x33\x32\x2e\x34\x37\x2c\x31\x32\x33\ +\x2e\x34\x38\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x6c\x69\x6e\x65\ +\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\ +\x31\x22\x20\x78\x31\x3d\x22\x32\x31\x37\x2e\x35\x39\x22\x20\x79\ +\x31\x3d\x22\x35\x33\x2e\x33\x33\x22\x20\x78\x32\x3d\x22\x31\x35\ +\x32\x2e\x33\x38\x22\x20\x79\x32\x3d\x20\x22\x31\x31\x38\x2e\x35\ +\x34\x22\x20\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\ +\x6e\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\x22\x20\x63\x6c\x61\x73\ +\x73\x3d\x22\x66\x69\x6c\x33\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\ +\x22\x32\x30\x33\x2e\x31\x32\x2c\x32\x35\x2e\x34\x36\x20\x32\x34\ +\x35\x2e\x34\x35\x2c\x32\x35\x2e\x34\x36\x20\x32\x34\x35\x2e\x34\ +\x35\x2c\x36\x37\x2e\x37\x39\x20\x32\x30\x33\x2e\x31\x32\x2c\x36\ +\x37\x2e\x37\x39\x20\x22\x2f\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\ +\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x05\x10\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x34\x36\x38\x32\x43\x38\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x37\x2e\x30\x36\x3b\x73\x74\x72\x6f\x6b\x65\ +\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\ +\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x30\x20\ +\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\x6e\x65\x7d\x0d\x0a\x20\x20\x20\ +\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x23\x43\x43\ +\x43\x43\x43\x43\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\ +\x20\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\ +\x61\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\ +\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\x69\x64\x3d\x22\x43\ +\x6f\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\ +\x6c\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\ +\x65\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\ +\x20\x78\x3d\x22\x2d\x30\x2e\x30\x31\x22\x20\x79\x3d\x22\x2d\x30\ +\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\ +\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\ +\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\x61\x73\ +\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\x78\x3d\x22\x36\x37\x2e\x37\ +\x32\x22\x20\x79\x3d\x22\x36\x37\x2e\x37\x33\x22\x20\x77\x69\x64\ +\x74\x68\x3d\x22\x31\x33\x35\x2e\x34\x37\x22\x20\x68\x65\x69\x67\ +\x68\x74\x3d\x22\x31\x33\x35\x2e\x34\x37\x22\x2f\x3e\x0d\x0a\x20\ +\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\ +\x6c\x31\x20\x73\x74\x72\x30\x22\x20\x64\x3d\x22\x4d\x31\x32\x36\ +\x2e\x39\x39\x20\x39\x37\x2e\x33\x36\x63\x2d\x32\x38\x2e\x30\x36\ +\x2c\x30\x20\x2d\x35\x30\x2e\x38\x2c\x32\x32\x2e\x37\x34\x20\x2d\ +\x35\x30\x2e\x38\x2c\x35\x30\x2e\x38\x20\x30\x2c\x32\x38\x2e\x30\ +\x36\x20\x32\x32\x2e\x37\x34\x2c\x35\x30\x2e\x38\x20\x35\x30\x2e\ +\x38\x2c\x35\x30\x2e\x38\x6c\x36\x37\x2e\x37\x32\x20\x30\x20\x30\ +\x20\x34\x32\x2e\x33\x33\x20\x2d\x36\x37\x2e\x37\x32\x20\x30\x63\ +\x2d\x35\x31\x2e\x34\x33\x2c\x30\x20\x2d\x39\x33\x2e\x31\x33\x2c\ +\x2d\x34\x31\x2e\x37\x20\x2d\x39\x33\x2e\x31\x33\x2c\x2d\x39\x33\ +\x2e\x31\x33\x20\x30\x2c\x2d\x35\x31\x2e\x34\x33\x20\x34\x31\x2e\ +\x37\x2c\x2d\x39\x33\x2e\x31\x33\x20\x39\x33\x2e\x31\x33\x2c\x2d\ +\x39\x33\x2e\x31\x33\x20\x30\x2e\x30\x34\x2c\x30\x20\x30\x2e\x30\ +\x38\x2c\x30\x20\x30\x2e\x30\x33\x2c\x30\x6c\x32\x35\x2e\x33\x37\ +\x20\x30\x20\x2d\x38\x2e\x34\x36\x20\x2d\x32\x35\x2e\x34\x20\x39\ +\x33\x2e\x31\x33\x20\x34\x36\x2e\x35\x36\x20\x2d\x39\x33\x2e\x31\ +\x33\x20\x34\x36\x2e\x35\x37\x20\x38\x2e\x34\x36\x20\x2d\x32\x35\ +\x2e\x34\x20\x2d\x32\x35\x2e\x34\x20\x30\x7a\x22\x2f\x3e\x0d\x0a\ +\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x05\x89\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x31\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x34\x36\x38\x32\x43\x38\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x37\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\ +\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x73\x74\x72\x30\ +\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\x23\x39\x39\x39\x39\x39\x39\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x31\x37\ +\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\ +\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\ +\x20\x20\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\ +\x6e\x6f\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x31\ +\x20\x7b\x66\x69\x6c\x6c\x3a\x23\x46\x46\x39\x39\x33\x33\x7d\x0d\ +\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\ +\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\ +\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\ +\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\ +\x64\x61\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\ +\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\ +\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\x78\x3d\x22\x2d\x30\ +\x2e\x30\x31\x22\x20\x79\x3d\x22\x2d\x30\x22\x20\x77\x69\x64\x74\ +\x68\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x20\x68\x65\x69\x67\x68\ +\x74\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x2f\x3e\x0d\x0a\x20\x20\ +\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\ +\x30\x20\x73\x74\x72\x30\x22\x20\x64\x3d\x22\x4d\x37\x38\x2e\x39\ +\x38\x20\x37\x38\x2e\x39\x35\x6c\x2d\x31\x31\x2e\x39\x37\x20\x2d\ +\x31\x31\x2e\x39\x37\x6d\x2d\x38\x2e\x39\x37\x20\x2d\x38\x2e\x39\ +\x37\x6c\x2d\x31\x31\x2e\x39\x38\x20\x2d\x31\x31\x2e\x39\x38\x6d\ +\x37\x34\x2e\x38\x32\x20\x37\x34\x2e\x38\x32\x6c\x2d\x31\x31\x2e\ +\x39\x38\x20\x2d\x31\x31\x2e\x39\x38\x6d\x2d\x38\x2e\x39\x37\x20\ +\x2d\x38\x2e\x39\x37\x6c\x2d\x31\x31\x2e\x39\x37\x20\x2d\x31\x31\ +\x2e\x39\x37\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x6c\x69\x6e\x65\x20\ +\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x31\ +\x22\x20\x78\x31\x3d\x22\x31\x32\x39\x2e\x33\x34\x22\x20\x79\x31\ +\x3d\x22\x31\x32\x39\x2e\x33\x31\x22\x20\x78\x32\x3d\x22\x32\x32\ +\x38\x2e\x36\x33\x22\x20\x79\x32\x3d\x20\x22\x32\x32\x38\x2e\x36\ +\x33\x22\x20\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\ +\x6e\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\x22\x20\x63\x6c\x61\x73\ +\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\ +\x22\x32\x37\x30\x2e\x38\x34\x2c\x32\x32\x38\x2e\x35\x20\x32\x32\ +\x38\x2e\x35\x31\x2c\x32\x32\x38\x2e\x35\x20\x32\x32\x38\x2e\x35\ +\x31\x2c\x32\x37\x30\x2e\x38\x33\x20\x32\x37\x30\x2e\x38\x34\x2c\ +\x32\x37\x30\x2e\x38\x33\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\ +\x6f\x6c\x79\x67\x6f\x6e\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\x5f\ +\x30\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\ +\x70\x6f\x69\x6e\x74\x73\x3d\x22\x34\x32\x2e\x34\x32\x2c\x30\x2e\ +\x30\x38\x20\x30\x2e\x30\x39\x2c\x30\x2e\x30\x38\x20\x30\x2e\x30\ +\x39\x2c\x34\x32\x2e\x34\x31\x20\x34\x32\x2e\x34\x32\x2c\x34\x32\ +\x2e\x34\x31\x20\x22\x2f\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\ +\x3c\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x06\xde\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x34\x36\x38\x32\x43\x38\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x35\x2e\x38\x37\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3a\x72\x6f\x75\x6e\x64\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\ +\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\ +\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\x6e\ +\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\ +\x69\x6c\x6c\x3a\x72\x65\x64\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\x3e\ +\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\x3c\ +\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\x22\ +\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\x3e\ +\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\x69\x64\ +\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\x43\ +\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\x20\ +\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\ +\x6c\x30\x22\x20\x78\x3d\x22\x2d\x30\x2e\x30\x31\x22\x20\x79\x3d\ +\x22\x2d\x30\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\x30\x2e\ +\x39\x33\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\ +\x39\x33\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\ +\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\ +\x6f\x69\x6e\x74\x73\x3d\x22\x35\x2e\x34\x35\x2c\x39\x35\x2e\x32\ +\x34\x20\x33\x37\x2e\x31\x34\x2c\x36\x33\x2e\x35\x35\x20\x35\x2e\ +\x34\x34\x2c\x33\x31\x2e\x38\x35\x20\x33\x31\x2e\x38\x35\x2c\x35\ +\x2e\x34\x34\x20\x36\x33\x2e\x35\x35\x2c\x33\x37\x2e\x31\x34\x20\ +\x39\x35\x2e\x32\x35\x2c\x35\x2e\x34\x34\x20\x31\x32\x31\x2e\x36\ +\x36\x2c\x33\x31\x2e\x38\x35\x20\x38\x39\x2e\x39\x36\x2c\x36\x33\ +\x2e\x35\x35\x20\x31\x32\x31\x2e\x36\x35\x2c\x39\x35\x2e\x32\x33\ +\x20\x39\x35\x2e\x32\x34\x2c\x31\x32\x31\x2e\x36\x34\x20\x36\x33\ +\x2e\x35\x35\x2c\x38\x39\x2e\x39\x36\x20\x33\x31\x2e\x38\x36\x2c\ +\x31\x32\x31\x2e\x36\x35\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\ +\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\ +\x73\x74\x72\x30\x22\x20\x64\x3d\x22\x4d\x38\x30\x2e\x37\x39\x20\ +\x31\x35\x39\x2e\x31\x37\x6c\x2d\x32\x39\x2e\x33\x20\x32\x39\x2e\ +\x32\x38\x63\x2d\x31\x33\x2e\x32\x33\x2c\x31\x33\x2e\x32\x33\x20\ +\x2d\x31\x33\x2e\x32\x33\x2c\x33\x34\x2e\x36\x37\x20\x30\x2c\x34\ +\x37\x2e\x39\x20\x31\x33\x2e\x32\x32\x2c\x31\x33\x2e\x32\x33\x20\ +\x33\x34\x2e\x36\x34\x2c\x31\x33\x2e\x32\x33\x20\x34\x37\x2e\x38\ +\x38\x2c\x30\x2e\x30\x32\x6c\x32\x36\x2e\x39\x34\x20\x2d\x32\x36\ +\x2e\x39\x34\x6d\x2d\x31\x38\x2e\x30\x31\x20\x2d\x37\x37\x2e\x37\ +\x39\x6c\x32\x33\x2e\x39\x35\x20\x2d\x32\x33\x2e\x39\x35\x6d\x32\ +\x30\x2e\x39\x35\x20\x37\x34\x2e\x38\x35\x6c\x32\x39\x2e\x39\x34\ +\x20\x2d\x32\x39\x2e\x39\x34\x6d\x2d\x38\x36\x2e\x32\x36\x20\x31\ +\x35\x2e\x39\x63\x31\x2e\x36\x32\x2c\x35\x2e\x33\x20\x34\x2e\x35\ +\x31\x2c\x31\x30\x2e\x31\x32\x20\x38\x2e\x34\x33\x2c\x31\x34\x2e\ +\x30\x34\x20\x31\x33\x2e\x32\x32\x2c\x31\x33\x2e\x32\x33\x20\x33\ +\x34\x2e\x36\x38\x2c\x31\x33\x2e\x32\x33\x20\x34\x37\x2e\x39\x2c\ +\x30\x6d\x31\x35\x2e\x38\x39\x20\x2d\x38\x36\x2e\x32\x37\x63\x35\ +\x2e\x33\x2c\x31\x2e\x36\x31\x20\x31\x30\x2e\x31\x33\x2c\x34\x2e\ +\x35\x31\x20\x31\x34\x2e\x30\x34\x2c\x38\x2e\x34\x33\x20\x31\x33\ +\x2e\x32\x33\x2c\x31\x33\x2e\x32\x32\x20\x31\x33\x2e\x32\x33\x2c\ +\x33\x34\x2e\x36\x37\x20\x30\x2c\x34\x37\x2e\x39\x6d\x2d\x39\x2e\ +\x39\x34\x20\x2d\x31\x35\x2e\x34\x39\x63\x2d\x35\x2e\x33\x2c\x2d\ +\x31\x2e\x36\x31\x20\x2d\x31\x30\x2e\x31\x32\x2c\x2d\x34\x2e\x35\ +\x32\x20\x2d\x31\x34\x2e\x30\x33\x2c\x2d\x38\x2e\x34\x34\x20\x2d\ +\x31\x33\x2e\x32\x33\x2c\x2d\x31\x33\x2e\x32\x32\x20\x2d\x31\x33\ +\x2e\x32\x33\x2c\x2d\x33\x34\x2e\x36\x36\x20\x2d\x30\x2e\x30\x31\ +\x2c\x2d\x34\x37\x2e\x38\x39\x6d\x30\x2e\x30\x31\x20\x30\x2e\x30\ +\x31\x6c\x32\x39\x2e\x32\x38\x20\x2d\x32\x39\x2e\x33\x63\x31\x33\ +\x2e\x32\x33\x2c\x2d\x31\x33\x2e\x32\x33\x20\x33\x34\x2e\x36\x37\ +\x2c\x2d\x31\x33\x2e\x32\x33\x20\x34\x37\x2e\x39\x2c\x30\x20\x31\ +\x33\x2e\x32\x33\x2c\x31\x33\x2e\x32\x32\x20\x31\x33\x2e\x32\x33\ +\x2c\x33\x34\x2e\x36\x34\x20\x30\x2e\x30\x32\x2c\x34\x37\x2e\x38\ +\x38\x6c\x2d\x32\x36\x2e\x39\x34\x20\x32\x36\x2e\x39\x34\x6d\x2d\ +\x37\x32\x2e\x33\x32\x20\x34\x36\x2e\x38\x39\x63\x2d\x31\x2e\x36\ +\x31\x2c\x2d\x35\x2e\x33\x20\x2d\x34\x2e\x35\x32\x2c\x2d\x31\x30\ +\x2e\x31\x32\x20\x2d\x38\x2e\x34\x34\x2c\x2d\x31\x34\x2e\x30\x33\ +\x20\x2d\x31\x33\x2e\x32\x32\x2c\x2d\x31\x33\x2e\x32\x33\x20\x2d\ +\x33\x34\x2e\x36\x36\x2c\x2d\x31\x33\x2e\x32\x33\x20\x2d\x34\x37\ +\x2e\x38\x39\x2c\x2d\x30\x2e\x30\x31\x22\x2f\x3e\x0d\x0a\x20\x3c\ +\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x05\x2c\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x36\x32\x34\x36\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x36\x32\ +\x34\x36\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x36\x32\x2e\x34\x36\x20\x32\x36\ +\x32\x2e\x34\x36\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x34\x36\x38\x32\x43\x38\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x36\x2e\x39\x33\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\ +\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x73\x74\x72\x31\ +\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\x67\x72\x61\x79\x3b\x73\x74\ +\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x31\x34\x2e\x31\x31\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\ +\x3a\x72\x6f\x75\x6e\x64\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\ +\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\ +\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\ +\x6c\x6c\x3a\x6e\x6f\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\ +\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x67\x72\x61\x79\x7d\x0d\ +\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x32\x20\x7b\x66\x69\x6c\x6c\ +\x3a\x23\x46\x46\x39\x39\x33\x33\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\ +\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\ +\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\ +\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\ +\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\x69\ +\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\ +\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\ +\x20\x20\x3c\x63\x69\x72\x63\x6c\x65\x20\x63\x6c\x61\x73\x73\x3d\ +\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x30\x22\x20\x63\x78\x3d\x22\ +\x31\x33\x31\x2e\x32\x33\x22\x20\x63\x79\x3d\x22\x31\x33\x31\x2e\ +\x32\x33\x22\x20\x72\x3d\x22\x31\x32\x32\x2e\x37\x36\x22\x2f\x3e\ +\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\ +\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\ +\x3d\x22\x35\x30\x2e\x37\x36\x2c\x35\x30\x2e\x37\x35\x20\x37\x31\ +\x2e\x32\x2c\x35\x36\x2e\x32\x32\x20\x39\x31\x2e\x36\x34\x2c\x36\ +\x31\x2e\x37\x20\x37\x36\x2e\x36\x38\x2c\x37\x36\x2e\x36\x37\x20\ +\x36\x31\x2e\x37\x31\x2c\x39\x31\x2e\x36\x33\x20\x35\x36\x2e\x32\ +\x33\x2c\x37\x31\x2e\x31\x39\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\ +\x6c\x69\x6e\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\ +\x20\x73\x74\x72\x31\x22\x20\x78\x31\x3d\x22\x31\x32\x38\x2e\x36\ +\x36\x22\x20\x79\x31\x3d\x22\x31\x32\x38\x2e\x36\x35\x22\x20\x78\ +\x32\x3d\x22\x37\x31\x2e\x39\x31\x22\x20\x79\x32\x3d\x20\x22\x37\ +\x31\x2e\x39\x22\x20\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\ +\x67\x6f\x6e\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\x22\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\x20\x70\x6f\x69\x6e\x74\ +\x73\x3d\x22\x31\x35\x32\x2e\x34\x2c\x31\x31\x30\x2e\x30\x37\x20\ +\x31\x31\x30\x2e\x30\x37\x2c\x31\x31\x30\x2e\x30\x37\x20\x31\x31\ +\x30\x2e\x30\x37\x2c\x31\x35\x32\x2e\x34\x20\x31\x35\x32\x2e\x34\ +\x2c\x31\x35\x32\x2e\x34\x20\x22\x2f\x3e\x0d\x0a\x20\x3c\x2f\x67\ +\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x06\x10\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x36\x36\x36\x36\x36\x36\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x36\x2e\x39\x33\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6c\x69\x6e\x65\x63\x61\x70\x3a\x73\x71\x75\x61\x72\x65\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\ +\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\ +\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\x6e\ +\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x32\x20\x7b\x66\ +\x69\x6c\x6c\x3a\x23\x30\x30\x39\x39\x33\x33\x7d\x0d\x0a\x20\x20\ +\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x77\x68\ +\x69\x74\x65\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\ +\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\ +\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\ +\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\ +\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\ +\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\ +\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\x65\ +\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\ +\x78\x3d\x22\x30\x22\x20\x79\x3d\x22\x2d\x30\x22\x20\x77\x69\x64\ +\x74\x68\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x20\x68\x65\x69\x67\ +\x68\x74\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x2f\x3e\x0d\x0a\x20\ +\x20\x3c\x70\x6f\x6c\x79\x6c\x69\x6e\x65\x20\x63\x6c\x61\x73\x73\ +\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x30\x22\x20\x70\x6f\x69\ +\x6e\x74\x73\x3d\x22\x32\x35\x2e\x33\x39\x2c\x32\x31\x39\x2e\x31\ +\x36\x20\x35\x36\x2e\x31\x36\x2c\x38\x32\x2e\x35\x33\x20\x32\x31\ +\x34\x2e\x37\x35\x2c\x31\x38\x38\x2e\x33\x36\x20\x32\x34\x35\x2e\ +\x35\x32\x2c\x35\x31\x2e\x37\x36\x20\x22\x2f\x3e\x0d\x0a\x20\x20\ +\x3c\x67\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\ +\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\ +\x69\x6e\x74\x73\x3d\x22\x32\x37\x30\x2e\x39\x32\x2c\x35\x39\x2e\ +\x32\x36\x20\x32\x31\x31\x2e\x36\x35\x2c\x35\x39\x2e\x32\x36\x20\ +\x32\x31\x31\x2e\x36\x35\x2c\x2d\x30\x2e\x30\x31\x20\x32\x37\x30\ +\x2e\x39\x32\x2c\x2d\x30\x2e\x30\x31\x20\x22\x2f\x3e\x0d\x0a\x20\ +\x20\x20\x3c\x70\x61\x74\x68\x20\x69\x64\x3d\x22\x5f\x31\x22\x20\ +\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\x20\x64\x3d\x22\ +\x4d\x32\x37\x30\x2e\x39\x32\x20\x35\x39\x2e\x32\x36\x6c\x2d\x35\ +\x39\x2e\x32\x37\x20\x30\x20\x30\x20\x2d\x35\x39\x2e\x32\x37\x20\ +\x35\x39\x2e\x32\x37\x20\x30\x20\x30\x20\x35\x39\x2e\x32\x37\x7a\ +\x6d\x2d\x34\x36\x2e\x39\x32\x20\x2d\x31\x32\x2e\x33\x35\x6c\x33\ +\x34\x2e\x35\x37\x20\x30\x20\x30\x20\x2d\x33\x34\x2e\x35\x37\x20\ +\x2d\x33\x34\x2e\x35\x37\x20\x30\x20\x30\x20\x33\x34\x2e\x35\x37\ +\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\x20\x20\ +\x3c\x67\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\ +\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\ +\x69\x6e\x74\x73\x3d\x22\x35\x39\x2e\x32\x35\x2c\x32\x37\x30\x2e\ +\x39\x34\x20\x2d\x30\x2e\x30\x32\x2c\x32\x37\x30\x2e\x39\x34\x20\ +\x2d\x30\x2e\x30\x32\x2c\x32\x31\x31\x2e\x36\x37\x20\x35\x39\x2e\ +\x32\x35\x2c\x32\x31\x31\x2e\x36\x37\x20\x22\x2f\x3e\x0d\x0a\x20\ +\x20\x20\x3c\x70\x61\x74\x68\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\ +\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\x20\x64\ +\x3d\x22\x4d\x35\x39\x2e\x32\x35\x20\x32\x37\x30\x2e\x39\x34\x6c\ +\x2d\x35\x39\x2e\x32\x37\x20\x30\x20\x30\x20\x2d\x35\x39\x2e\x32\ +\x37\x20\x35\x39\x2e\x32\x37\x20\x30\x20\x30\x20\x35\x39\x2e\x32\ +\x37\x7a\x6d\x2d\x34\x36\x2e\x39\x32\x20\x2d\x31\x32\x2e\x33\x35\ +\x6c\x33\x34\x2e\x35\x37\x20\x30\x20\x30\x20\x2d\x33\x34\x2e\x35\ +\x37\x20\x2d\x33\x34\x2e\x35\x37\x20\x30\x20\x30\x20\x33\x34\x2e\ +\x35\x37\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\ +\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x05\x4f\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x34\x36\x38\x32\x43\x38\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x36\x2e\x39\x33\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\ +\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x73\x74\x72\x31\ +\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\x23\x42\x33\x42\x33\x42\x33\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x31\x36\ +\x2e\x39\x33\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\ +\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\ +\x20\x20\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\ +\x6e\x6f\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x31\ +\x20\x7b\x66\x69\x6c\x6c\x3a\x23\x46\x46\x39\x39\x33\x33\x7d\x0d\ +\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\ +\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\ +\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\ +\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\ +\x64\x61\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\ +\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\ +\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\x78\x3d\x22\x2d\x30\ +\x2e\x30\x31\x22\x20\x79\x3d\x22\x2d\x30\x22\x20\x77\x69\x64\x74\ +\x68\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x20\x68\x65\x69\x67\x68\ +\x74\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x2f\x3e\x0d\x0a\x20\x20\ +\x3c\x65\x6c\x6c\x69\x70\x73\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\ +\x66\x69\x6c\x30\x20\x73\x74\x72\x30\x22\x20\x63\x78\x3d\x22\x31\ +\x33\x35\x2e\x34\x36\x22\x20\x63\x79\x3d\x22\x31\x33\x35\x2e\x34\ +\x36\x22\x20\x72\x78\x3d\x22\x31\x32\x37\x22\x20\x72\x79\x3d\x22\ +\x39\x33\x2e\x31\x33\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x61\x74\ +\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\ +\x72\x31\x22\x20\x64\x3d\x22\x4d\x31\x33\x35\x2e\x34\x36\x20\x31\ +\x36\x30\x2e\x38\x35\x6c\x30\x20\x2d\x35\x30\x2e\x37\x39\x6d\x32\ +\x35\x2e\x34\x20\x32\x35\x2e\x33\x39\x6c\x2d\x35\x30\x2e\x37\x39\ +\x20\x30\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\ +\x6e\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\x22\x20\x63\x6c\x61\x73\ +\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\ +\x22\x30\x2e\x30\x38\x2c\x31\x31\x34\x2e\x33\x20\x34\x32\x2e\x34\ +\x31\x2c\x31\x31\x34\x2e\x33\x20\x34\x32\x2e\x34\x31\x2c\x31\x35\ +\x36\x2e\x36\x33\x20\x30\x2e\x30\x38\x2c\x31\x35\x36\x2e\x36\x33\ +\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\ +\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\x5f\x30\x22\x20\x63\x6c\x61\ +\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\ +\x3d\x22\x31\x31\x34\x2e\x32\x39\x2c\x32\x30\x37\x2e\x33\x36\x20\ +\x31\x35\x36\x2e\x36\x32\x2c\x32\x30\x37\x2e\x33\x36\x20\x31\x35\ +\x36\x2e\x36\x32\x2c\x32\x34\x39\x2e\x36\x39\x20\x31\x31\x34\x2e\ +\x32\x39\x2c\x32\x34\x39\x2e\x36\x39\x20\x22\x2f\x3e\x0d\x0a\x20\ +\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x05\xc8\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\ +\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x32\x20\x7b\ +\x66\x69\x6c\x6c\x3a\x23\x46\x46\x39\x39\x33\x33\x7d\x0d\x0a\x20\ +\x20\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x23\ +\x34\x36\x38\x32\x43\x38\x3b\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\ +\x3a\x6e\x6f\x6e\x7a\x65\x72\x6f\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\ +\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\ +\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\ +\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\ +\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\x69\ +\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\ +\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\ +\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\ +\x69\x6c\x30\x22\x20\x78\x3d\x22\x30\x22\x20\x79\x3d\x22\x30\x22\ +\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\x30\x2e\x39\x32\x22\x20\ +\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\x39\x32\x22\x2f\ +\x3e\x0d\x0a\x20\x20\x3c\x67\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x61\ +\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\ +\x64\x3d\x22\x4d\x31\x36\x2e\x39\x32\x20\x31\x39\x38\x2e\x36\x37\ +\x6c\x34\x32\x2e\x34\x20\x30\x20\x30\x20\x31\x37\x2e\x36\x34\x20\ +\x2d\x34\x32\x2e\x34\x20\x30\x20\x2d\x38\x2e\x38\x32\x20\x2d\x38\ +\x2e\x38\x32\x20\x38\x2e\x38\x32\x20\x2d\x38\x2e\x38\x32\x7a\x6d\ +\x30\x20\x31\x37\x2e\x36\x34\x6c\x2d\x38\x2e\x38\x32\x20\x30\x20\ +\x30\x20\x2d\x38\x2e\x38\x32\x20\x38\x2e\x38\x32\x20\x38\x2e\x38\ +\x32\x7a\x6d\x38\x2e\x38\x32\x20\x2d\x31\x37\x38\x2e\x31\x36\x6c\ +\x30\x20\x31\x36\x39\x2e\x33\x34\x20\x2d\x31\x37\x2e\x36\x34\x20\ +\x30\x20\x30\x20\x2d\x31\x36\x39\x2e\x33\x34\x20\x38\x2e\x38\x32\ +\x20\x2d\x38\x2e\x38\x32\x20\x38\x2e\x38\x32\x20\x38\x2e\x38\x32\ +\x7a\x6d\x2d\x31\x37\x2e\x36\x34\x20\x30\x6c\x30\x20\x2d\x38\x2e\ +\x38\x32\x20\x38\x2e\x38\x32\x20\x30\x20\x2d\x38\x2e\x38\x32\x20\ +\x38\x2e\x38\x32\x7a\x6d\x32\x34\x35\x2e\x39\x20\x38\x2e\x38\x32\ +\x6c\x2d\x32\x33\x37\x2e\x30\x38\x20\x30\x20\x30\x20\x2d\x31\x37\ +\x2e\x36\x34\x20\x32\x33\x37\x2e\x30\x38\x20\x30\x20\x38\x2e\x38\ +\x32\x20\x38\x2e\x38\x32\x20\x2d\x38\x2e\x38\x32\x20\x38\x2e\x38\ +\x32\x7a\x6d\x30\x20\x2d\x31\x37\x2e\x36\x34\x6c\x38\x2e\x38\x32\ +\x20\x30\x20\x30\x20\x38\x2e\x38\x32\x20\x2d\x38\x2e\x38\x32\x20\ +\x2d\x38\x2e\x38\x32\x7a\x6d\x2d\x38\x2e\x38\x32\x20\x31\x37\x38\ +\x2e\x31\x36\x6c\x30\x20\x2d\x31\x36\x39\x2e\x33\x34\x20\x31\x37\ +\x2e\x36\x34\x20\x30\x20\x30\x20\x31\x36\x39\x2e\x33\x34\x20\x2d\ +\x38\x2e\x38\x32\x20\x38\x2e\x38\x32\x20\x2d\x38\x2e\x38\x32\x20\ +\x2d\x38\x2e\x38\x32\x7a\x6d\x31\x37\x2e\x36\x34\x20\x30\x6c\x30\ +\x20\x38\x2e\x38\x32\x20\x2d\x38\x2e\x38\x32\x20\x30\x20\x38\x2e\ +\x38\x32\x20\x2d\x38\x2e\x38\x32\x7a\x6d\x2d\x31\x33\x35\x2e\x38\ +\x32\x20\x2d\x38\x2e\x38\x32\x6c\x31\x32\x37\x20\x30\x20\x30\x20\ +\x31\x37\x2e\x36\x34\x20\x2d\x31\x32\x37\x20\x30\x20\x30\x20\x2d\ +\x31\x37\x2e\x36\x34\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\ +\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x69\x64\ +\x3d\x22\x5f\x31\x5f\x30\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\ +\x69\x6c\x32\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x37\x31\x2e\ +\x39\x39\x2c\x31\x38\x36\x2e\x32\x31\x20\x31\x31\x34\x2e\x33\x32\ +\x2c\x31\x38\x36\x2e\x32\x31\x20\x31\x31\x34\x2e\x33\x32\x2c\x32\ +\x32\x38\x2e\x35\x34\x20\x37\x31\x2e\x39\x39\x2c\x32\x32\x38\x2e\ +\x35\x34\x20\x22\x2f\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\ +\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x06\x4d\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x39\x39\x39\x39\x39\x39\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x37\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\ +\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x73\x74\x72\x32\ +\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\x23\x34\x36\x38\x32\x43\x38\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x31\x37\ +\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\ +\x6f\x69\x6e\x3a\x72\x6f\x75\x6e\x64\x3b\x73\x74\x72\x6f\x6b\x65\ +\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\ +\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x73\x74\x72\x31\x20\ +\x7b\x73\x74\x72\x6f\x6b\x65\x3a\x23\x46\x46\x39\x39\x33\x33\x3b\ +\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x31\x37\x2e\ +\x36\x34\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\ +\x69\x6e\x3a\x72\x6f\x75\x6e\x64\x3b\x73\x74\x72\x6f\x6b\x65\x2d\ +\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\x32\ +\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\ +\x66\x69\x6c\x6c\x3a\x6e\x6f\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\ +\x2e\x66\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x23\x34\x36\x38\ +\x32\x43\x38\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\ +\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\ +\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\ +\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\ +\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\ +\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\ +\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\x65\ +\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\ +\x78\x3d\x22\x2d\x30\x2e\x30\x31\x22\x20\x79\x3d\x22\x2d\x30\x22\ +\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x20\ +\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x2f\ +\x3e\x0d\x0a\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\ +\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x30\x22\x20\x64\x3d\x22\ +\x4d\x31\x33\x30\x2e\x36\x32\x20\x31\x34\x30\x2e\x33\x6c\x31\x32\ +\x2e\x30\x33\x20\x2d\x31\x32\x2e\x30\x33\x6d\x2d\x33\x32\x2e\x39\ +\x37\x20\x33\x32\x2e\x39\x37\x6c\x31\x32\x2e\x30\x34\x20\x2d\x31\ +\x32\x2e\x30\x34\x6d\x35\x30\x2e\x37\x37\x20\x2d\x35\x30\x2e\x37\ +\x37\x6c\x31\x32\x2e\x30\x34\x20\x2d\x31\x32\x2e\x30\x34\x6d\x2d\ +\x33\x32\x2e\x39\x38\x20\x33\x32\x2e\x39\x38\x6c\x31\x32\x2e\x30\ +\x35\x20\x2d\x31\x32\x2e\x30\x35\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\ +\x6c\x69\x6e\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\ +\x20\x73\x74\x72\x31\x22\x20\x78\x31\x3d\x22\x32\x36\x31\x2e\x38\ +\x35\x22\x20\x79\x31\x3d\x22\x31\x33\x35\x2e\x35\x37\x22\x20\x78\ +\x32\x3d\x22\x31\x33\x34\x2e\x31\x31\x22\x20\x79\x32\x3d\x20\x22\ +\x37\x2e\x38\x31\x22\x20\x2f\x3e\x0d\x0a\x20\x20\x3c\x6c\x69\x6e\ +\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\ +\x72\x32\x22\x20\x78\x31\x3d\x22\x31\x32\x31\x2e\x36\x22\x20\x79\ +\x31\x3d\x22\x32\x34\x34\x2e\x35\x31\x22\x20\x78\x32\x3d\x22\x31\ +\x38\x39\x2e\x30\x34\x22\x20\x79\x32\x3d\x20\x22\x31\x37\x37\x2e\ +\x30\x38\x22\x20\x2f\x3e\x0d\x0a\x20\x20\x3c\x6c\x69\x6e\x65\x20\ +\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x32\ +\x22\x20\x78\x31\x3d\x22\x38\x2e\x34\x35\x22\x20\x79\x31\x3d\x22\ +\x32\x36\x32\x2e\x34\x37\x22\x20\x78\x32\x3d\x22\x31\x30\x30\x2e\ +\x37\x34\x22\x20\x79\x32\x3d\x20\x22\x31\x37\x30\x2e\x32\x32\x22\ +\x20\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\ +\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\ +\x6e\x74\x73\x3d\x22\x32\x31\x36\x2e\x34\x31\x2c\x31\x35\x30\x2e\ +\x31\x34\x20\x32\x30\x39\x2e\x32\x39\x2c\x31\x37\x36\x2e\x37\x32\ +\x20\x32\x30\x32\x2e\x31\x36\x2c\x32\x30\x33\x2e\x33\x20\x31\x38\ +\x32\x2e\x37\x31\x2c\x31\x38\x33\x2e\x38\x34\x20\x31\x36\x33\x2e\ +\x32\x35\x2c\x31\x36\x34\x2e\x33\x39\x20\x31\x38\x39\x2e\x38\x33\ +\x2c\x31\x35\x37\x2e\x32\x36\x20\x22\x2f\x3e\x0d\x0a\x20\x3c\x2f\ +\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x0a\xd7\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\ +\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x32\x20\x7b\ +\x66\x69\x6c\x6c\x3a\x67\x72\x61\x79\x7d\x0d\x0a\x20\x20\x20\x20\ +\x2e\x66\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x77\x68\x69\x74\ +\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x33\x20\x7b\x66\ +\x69\x6c\x6c\x3a\x23\x34\x36\x38\x32\x43\x38\x3b\x66\x69\x6c\x6c\ +\x2d\x72\x75\x6c\x65\x3a\x6e\x6f\x6e\x7a\x65\x72\x6f\x7d\x0d\x0a\ +\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\x6c\ +\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\x3c\ +\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\x30\ +\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\ +\x61\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\x72\ +\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\x72\ +\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\x61\ +\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\x78\x3d\x22\x2d\x30\x2e\ +\x30\x31\x22\x20\x79\x3d\x22\x2d\x30\x22\x20\x77\x69\x64\x74\x68\ +\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x20\x68\x65\x69\x67\x68\x74\ +\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\ +\x67\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\ +\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x64\x3d\x22\x4d\x31\x33\ +\x35\x2e\x34\x36\x20\x38\x2e\x34\x37\x63\x37\x30\x2e\x31\x33\x2c\ +\x30\x20\x31\x32\x36\x2e\x39\x39\x2c\x35\x36\x2e\x38\x35\x20\x31\ +\x32\x36\x2e\x39\x39\x2c\x31\x32\x36\x2e\x39\x39\x20\x30\x2c\x37\ +\x30\x2e\x31\x34\x20\x2d\x35\x36\x2e\x38\x36\x2c\x31\x32\x36\x2e\ +\x39\x39\x20\x2d\x31\x32\x36\x2e\x39\x39\x2c\x31\x32\x36\x2e\x39\ +\x39\x20\x2d\x37\x30\x2e\x31\x33\x2c\x30\x20\x2d\x31\x32\x36\x2e\ +\x39\x39\x2c\x2d\x35\x36\x2e\x38\x35\x20\x2d\x31\x32\x36\x2e\x39\ +\x39\x2c\x2d\x31\x32\x36\x2e\x39\x39\x20\x30\x2c\x2d\x37\x30\x2e\ +\x31\x34\x20\x35\x36\x2e\x38\x36\x2c\x2d\x31\x32\x36\x2e\x39\x39\ +\x20\x31\x32\x36\x2e\x39\x39\x2c\x2d\x31\x32\x36\x2e\x39\x39\x7a\ +\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x61\x74\x68\x20\x69\x64\ +\x3d\x22\x5f\x31\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\ +\x32\x22\x20\x64\x3d\x22\x4d\x31\x33\x35\x2e\x34\x36\x20\x38\x2e\ +\x34\x37\x63\x37\x30\x2e\x31\x33\x2c\x30\x20\x31\x32\x36\x2e\x39\ +\x39\x2c\x35\x36\x2e\x38\x35\x20\x31\x32\x36\x2e\x39\x39\x2c\x31\ +\x32\x36\x2e\x39\x39\x20\x30\x2c\x37\x30\x2e\x31\x34\x20\x2d\x35\ +\x36\x2e\x38\x36\x2c\x31\x32\x36\x2e\x39\x39\x20\x2d\x31\x32\x36\ +\x2e\x39\x39\x2c\x31\x32\x36\x2e\x39\x39\x20\x2d\x37\x30\x2e\x31\ +\x33\x2c\x30\x20\x2d\x31\x32\x36\x2e\x39\x39\x2c\x2d\x35\x36\x2e\ +\x38\x35\x20\x2d\x31\x32\x36\x2e\x39\x39\x2c\x2d\x31\x32\x36\x2e\ +\x39\x39\x20\x30\x2c\x2d\x37\x30\x2e\x31\x34\x20\x35\x36\x2e\x38\ +\x36\x2c\x2d\x31\x32\x36\x2e\x39\x39\x20\x31\x32\x36\x2e\x39\x39\ +\x2c\x2d\x31\x32\x36\x2e\x39\x39\x7a\x6d\x38\x31\x2e\x30\x39\x20\ +\x34\x35\x2e\x39\x63\x2d\x32\x30\x2e\x37\x34\x2c\x2d\x32\x30\x2e\ +\x37\x33\x20\x2d\x34\x39\x2e\x34\x31\x2c\x2d\x33\x33\x2e\x35\x35\ +\x20\x2d\x38\x31\x2e\x30\x39\x2c\x2d\x33\x33\x2e\x35\x35\x20\x2d\ +\x33\x31\x2e\x33\x37\x2c\x30\x20\x2d\x35\x39\x2e\x37\x39\x2c\x31\ +\x32\x2e\x35\x37\x20\x2d\x38\x30\x2e\x34\x39\x2c\x33\x32\x2e\x39\ +\x35\x20\x2d\x30\x2e\x31\x38\x2c\x30\x2e\x32\x31\x20\x2d\x30\x2e\ +\x33\x38\x2c\x30\x2e\x34\x32\x20\x2d\x30\x2e\x35\x38\x2c\x30\x2e\ +\x36\x32\x20\x2d\x32\x30\x2e\x37\x33\x2c\x32\x30\x2e\x37\x33\x20\ +\x2d\x33\x33\x2e\x35\x37\x2c\x34\x39\x2e\x34\x31\x20\x2d\x33\x33\ +\x2e\x35\x37\x2c\x38\x31\x2e\x30\x37\x20\x30\x2c\x33\x31\x2e\x36\ +\x36\x20\x31\x32\x2e\x38\x34\x2c\x36\x30\x2e\x33\x34\x20\x33\x33\ +\x2e\x35\x37\x2c\x38\x31\x2e\x30\x37\x6c\x2d\x30\x2e\x30\x32\x20\ +\x30\x2e\x30\x32\x63\x32\x30\x2e\x37\x34\x2c\x32\x30\x2e\x37\x33\ +\x20\x34\x39\x2e\x34\x31\x2c\x33\x33\x2e\x35\x35\x20\x38\x31\x2e\ +\x30\x39\x2c\x33\x33\x2e\x35\x35\x20\x33\x31\x2e\x33\x37\x2c\x30\ +\x20\x35\x39\x2e\x37\x39\x2c\x2d\x31\x32\x2e\x35\x37\x20\x38\x30\ +\x2e\x34\x39\x2c\x2d\x33\x32\x2e\x39\x35\x20\x30\x2e\x31\x38\x2c\ +\x2d\x30\x2e\x32\x31\x20\x30\x2e\x33\x38\x2c\x2d\x30\x2e\x34\x32\ +\x20\x30\x2e\x35\x38\x2c\x2d\x30\x2e\x36\x32\x20\x32\x30\x2e\x37\ +\x33\x2c\x2d\x32\x30\x2e\x37\x33\x20\x33\x33\x2e\x35\x37\x2c\x2d\ +\x34\x39\x2e\x34\x31\x20\x33\x33\x2e\x35\x37\x2c\x2d\x38\x31\x2e\ +\x30\x37\x20\x30\x2c\x2d\x33\x31\x2e\x36\x36\x20\x2d\x31\x32\x2e\ +\x38\x34\x2c\x2d\x36\x30\x2e\x33\x34\x20\x2d\x33\x33\x2e\x35\x37\ +\x2c\x2d\x38\x31\x2e\x30\x37\x6c\x30\x2e\x30\x32\x20\x2d\x30\x2e\ +\x30\x32\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\ +\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\ +\x69\x6c\x33\x22\x20\x64\x3d\x22\x4d\x31\x32\x32\x2e\x33\x20\x31\ +\x36\x30\x2e\x39\x39\x63\x30\x2c\x2d\x30\x2e\x37\x33\x20\x30\x2c\ +\x2d\x31\x2e\x34\x37\x20\x2d\x30\x2e\x30\x34\x2c\x2d\x32\x2e\x32\ +\x20\x2d\x30\x2e\x30\x34\x2c\x2d\x30\x2e\x37\x38\x20\x2d\x30\x2e\ +\x30\x38\x2c\x2d\x31\x2e\x35\x33\x20\x2d\x30\x2e\x30\x38\x2c\x2d\ +\x32\x2e\x32\x36\x20\x30\x2c\x2d\x37\x2e\x38\x33\x20\x31\x2e\x36\ +\x34\x2c\x2d\x31\x34\x2e\x32\x36\x20\x34\x2e\x39\x32\x2c\x2d\x31\ +\x39\x2e\x33\x20\x33\x2e\x32\x37\x2c\x2d\x35\x2e\x30\x35\x20\x38\ +\x2e\x32\x37\x2c\x2d\x39\x2e\x39\x31\x20\x31\x34\x2e\x39\x39\x2c\ +\x2d\x31\x34\x2e\x36\x20\x36\x2e\x36\x2c\x2d\x34\x2e\x36\x32\x20\ +\x31\x31\x2e\x34\x38\x2c\x2d\x38\x2e\x37\x37\x20\x31\x34\x2e\x36\ +\x38\x2c\x2d\x31\x32\x2e\x33\x37\x20\x33\x2e\x31\x39\x2c\x2d\x33\ +\x2e\x36\x20\x34\x2e\x37\x39\x2c\x2d\x38\x2e\x30\x37\x20\x34\x2e\ +\x37\x39\x2c\x2d\x31\x33\x2e\x34\x20\x30\x2c\x2d\x35\x2e\x37\x33\ +\x20\x2d\x32\x2e\x30\x39\x2c\x2d\x31\x30\x2e\x34\x31\x20\x2d\x36\ +\x2e\x32\x32\x2c\x2d\x31\x34\x2e\x31\x20\x2d\x34\x2e\x31\x39\x2c\ +\x2d\x33\x2e\x36\x34\x20\x2d\x31\x30\x2e\x35\x34\x2c\x2d\x35\x2e\ +\x34\x35\x20\x2d\x31\x39\x2e\x31\x2c\x2d\x35\x2e\x34\x35\x20\x2d\ +\x38\x2e\x39\x2c\x30\x20\x2d\x31\x35\x2e\x37\x39\x2c\x32\x2e\x30\ +\x31\x20\x2d\x32\x30\x2e\x36\x36\x2c\x36\x2e\x31\x20\x2d\x34\x2e\ +\x38\x38\x2c\x34\x2e\x30\x37\x20\x2d\x37\x2e\x33\x37\x2c\x39\x2e\ +\x38\x34\x20\x2d\x37\x2e\x34\x37\x2c\x31\x37\x2e\x32\x37\x6c\x2d\ +\x32\x36\x2e\x33\x38\x20\x30\x63\x30\x2c\x2d\x39\x2e\x34\x37\x20\ +\x32\x2e\x32\x39\x2c\x2d\x31\x37\x2e\x35\x31\x20\x36\x2e\x38\x34\ +\x2c\x2d\x32\x34\x2e\x31\x34\x20\x34\x2e\x35\x34\x2c\x2d\x36\x2e\ +\x36\x20\x31\x30\x2e\x39\x2c\x2d\x31\x31\x2e\x36\x39\x20\x31\x39\ +\x2e\x31\x2c\x2d\x31\x35\x2e\x31\x38\x20\x38\x2e\x31\x36\x2c\x2d\ +\x33\x2e\x34\x38\x20\x31\x37\x2e\x36\x36\x2c\x2d\x35\x2e\x32\x20\ +\x32\x38\x2e\x35\x37\x2c\x2d\x35\x2e\x32\x20\x31\x30\x2e\x36\x35\ +\x2c\x30\x20\x31\x39\x2e\x39\x31\x2c\x31\x2e\x35\x36\x20\x32\x37\ +\x2e\x38\x36\x2c\x34\x2e\x37\x32\x20\x37\x2e\x39\x31\x2c\x33\x2e\ +\x31\x31\x20\x31\x34\x2e\x30\x36\x2c\x37\x2e\x36\x36\x20\x31\x38\ +\x2e\x34\x39\x2c\x31\x33\x2e\x36\x20\x34\x2e\x33\x38\x2c\x35\x2e\ +\x39\x35\x20\x36\x2e\x36\x2c\x31\x33\x2e\x31\x32\x20\x36\x2e\x36\ +\x2c\x32\x31\x2e\x35\x32\x20\x30\x2c\x36\x2e\x31\x39\x20\x2d\x31\ +\x2e\x31\x32\x2c\x31\x31\x2e\x35\x35\x20\x2d\x33\x2e\x32\x34\x2c\ +\x31\x36\x2e\x30\x36\x20\x2d\x32\x2e\x31\x37\x2c\x34\x2e\x34\x37\ +\x20\x2d\x35\x2e\x32\x31\x2c\x38\x2e\x35\x33\x20\x2d\x39\x2e\x30\ +\x36\x2c\x31\x32\x2e\x31\x33\x20\x2d\x33\x2e\x39\x2c\x33\x2e\x36\ +\x31\x20\x2d\x38\x2e\x35\x37\x2c\x37\x2e\x31\x38\x20\x2d\x31\x34\ +\x2e\x30\x31\x2c\x31\x30\x2e\x37\x20\x2d\x34\x2e\x32\x33\x2c\x32\ +\x2e\x39\x35\x20\x2d\x37\x2e\x35\x31\x2c\x35\x2e\x37\x20\x2d\x39\ +\x2e\x38\x34\x2c\x38\x2e\x32\x34\x20\x2d\x32\x2e\x33\x34\x2c\x32\ +\x2e\x35\x34\x20\x2d\x33\x2e\x39\x38\x2c\x35\x2e\x32\x35\x20\x2d\ +\x34\x2e\x39\x32\x2c\x38\x2e\x30\x37\x20\x2d\x30\x2e\x39\x35\x2c\ +\x32\x2e\x38\x34\x20\x2d\x31\x2e\x36\x2c\x36\x2e\x31\x31\x20\x2d\ +\x31\x2e\x39\x33\x2c\x39\x2e\x37\x39\x6c\x2d\x32\x33\x2e\x38\x39\ +\x20\x30\x7a\x6d\x31\x32\x2e\x33\x38\x20\x35\x33\x2e\x37\x38\x63\ +\x2d\x35\x2e\x32\x31\x2c\x30\x20\x2d\x39\x2e\x34\x37\x2c\x2d\x31\ +\x2e\x36\x38\x20\x2d\x31\x32\x2e\x37\x39\x2c\x2d\x35\x2e\x30\x34\ +\x20\x2d\x33\x2e\x33\x32\x2c\x2d\x33\x2e\x33\x36\x20\x2d\x35\x2e\ +\x30\x31\x2c\x2d\x37\x2e\x35\x31\x20\x2d\x35\x2e\x30\x31\x2c\x2d\ +\x31\x32\x2e\x33\x37\x20\x30\x2c\x2d\x35\x2e\x30\x35\x20\x31\x2e\ +\x36\x39\x2c\x2d\x39\x2e\x31\x35\x20\x35\x2e\x30\x31\x2c\x2d\x31\ +\x32\x2e\x33\x39\x20\x33\x2e\x33\x32\x2c\x2d\x33\x2e\x31\x39\x20\ +\x37\x2e\x35\x38\x2c\x2d\x34\x2e\x37\x38\x20\x31\x32\x2e\x37\x39\ +\x2c\x2d\x34\x2e\x37\x38\x20\x35\x2e\x32\x31\x2c\x30\x20\x39\x2e\ +\x34\x36\x2c\x31\x2e\x35\x39\x20\x31\x32\x2e\x38\x33\x2c\x34\x2e\ +\x37\x38\x20\x33\x2e\x33\x35\x2c\x33\x2e\x32\x34\x20\x35\x2e\x30\ +\x34\x2c\x37\x2e\x33\x34\x20\x35\x2e\x30\x34\x2c\x31\x32\x2e\x33\ +\x39\x20\x30\x2c\x34\x2e\x38\x36\x20\x2d\x31\x2e\x36\x39\x2c\x39\ +\x2e\x30\x31\x20\x2d\x35\x2e\x30\x34\x2c\x31\x32\x2e\x33\x37\x20\ +\x2d\x33\x2e\x33\x37\x2c\x33\x2e\x33\x36\x20\x2d\x37\x2e\x36\x32\ +\x2c\x35\x2e\x30\x34\x20\x2d\x31\x32\x2e\x38\x33\x2c\x35\x2e\x30\ +\x34\x7a\x22\x2f\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\ +\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x07\x36\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x31\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x42\x33\x42\x33\x42\x33\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x36\x2e\x39\x33\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\ +\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x73\x74\x72\x30\ +\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\x67\x72\x61\x79\x3b\x73\x74\ +\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x31\x34\x2e\x31\x31\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\ +\x3a\x72\x6f\x75\x6e\x64\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\ +\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\ +\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x73\x74\x72\x32\x20\x7b\x73\x74\ +\x72\x6f\x6b\x65\x3a\x23\x34\x36\x38\x32\x43\x38\x3b\x73\x74\x72\ +\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x31\x36\x2e\x39\x33\x3b\ +\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x63\x61\x70\x3a\x73\ +\x71\x75\x61\x72\x65\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\ +\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\ +\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\ +\x6c\x3a\x6e\x6f\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\ +\x6c\x32\x20\x7b\x66\x69\x6c\x6c\x3a\x67\x72\x61\x79\x7d\x0d\x0a\ +\x20\x20\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\ +\x23\x43\x38\x41\x30\x30\x30\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\ +\x69\x6c\x33\x20\x7b\x66\x69\x6c\x6c\x3a\x23\x46\x46\x39\x39\x33\ +\x33\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\ +\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\x73\x3e\ +\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\x64\x61\ +\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\ +\x65\x74\x61\x64\x61\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\x72\x65\ +\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\x2d\x4c\ +\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\x65\x63\x74\ +\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\x78\x3d\ +\x22\x30\x22\x20\x79\x3d\x22\x30\x22\x20\x77\x69\x64\x74\x68\x3d\ +\x22\x32\x37\x30\x2e\x39\x32\x22\x20\x68\x65\x69\x67\x68\x74\x3d\ +\x22\x32\x37\x30\x2e\x39\x32\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x6c\ +\x69\x6e\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\ +\x73\x74\x72\x30\x22\x20\x78\x31\x3d\x22\x31\x37\x32\x2e\x38\x32\ +\x22\x20\x79\x31\x3d\x22\x39\x33\x2e\x38\x37\x22\x20\x78\x32\x3d\ +\x22\x36\x33\x2e\x35\x35\x22\x20\x79\x32\x3d\x20\x22\x32\x30\x37\ +\x2e\x33\x37\x22\x20\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x61\x74\x68\ +\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\ +\x31\x22\x20\x64\x3d\x22\x4d\x30\x2e\x30\x38\x20\x32\x34\x35\x2e\ +\x34\x34\x6c\x35\x30\x2e\x38\x20\x30\x6d\x2d\x32\x35\x2e\x34\x20\ +\x32\x35\x2e\x34\x6c\x30\x20\x2d\x35\x30\x2e\x38\x22\x2f\x3e\x0d\ +\x0a\x20\x20\x3c\x67\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x6f\x6c\x79\ +\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\ +\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x34\x32\x2e\x34\x2c\x32\x32\ +\x38\x2e\x35\x32\x20\x34\x37\x2e\x38\x37\x2c\x32\x30\x38\x2e\x30\ +\x38\x20\x35\x33\x2e\x33\x35\x2c\x31\x38\x37\x2e\x36\x34\x20\x36\ +\x38\x2e\x33\x32\x2c\x32\x30\x32\x2e\x36\x20\x38\x33\x2e\x32\x38\ +\x2c\x32\x31\x37\x2e\x35\x37\x20\x36\x32\x2e\x38\x34\x2c\x32\x32\ +\x33\x2e\x30\x35\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x6f\ +\x6c\x79\x67\x6f\x6e\x20\x69\x64\x3d\x22\x5f\x31\x22\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\x20\x70\x6f\x69\x6e\x74\ +\x73\x3d\x22\x34\x32\x2e\x34\x2c\x32\x32\x38\x2e\x35\x32\x20\x34\ +\x37\x2e\x38\x37\x2c\x32\x30\x38\x2e\x30\x38\x20\x35\x33\x2e\x33\ +\x35\x2c\x31\x38\x37\x2e\x36\x34\x20\x36\x38\x2e\x33\x32\x2c\x32\ +\x30\x32\x2e\x36\x20\x38\x33\x2e\x32\x38\x2c\x32\x31\x37\x2e\x35\ +\x37\x20\x36\x32\x2e\x38\x34\x2c\x32\x32\x33\x2e\x30\x35\x20\x22\ +\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\x20\x20\x3c\x70\ +\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\ +\x73\x74\x72\x32\x22\x20\x64\x3d\x22\x4d\x32\x34\x39\x2e\x36\x34\ +\x20\x32\x35\x38\x2e\x31\x32\x63\x2d\x34\x2e\x35\x2c\x2d\x31\x32\ +\x38\x2e\x38\x37\x20\x2d\x31\x30\x37\x2e\x39\x33\x2c\x2d\x32\x33\ +\x32\x2e\x33\x20\x2d\x32\x33\x36\x2e\x38\x2c\x2d\x32\x33\x36\x2e\ +\x38\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\ +\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\x22\x20\x63\x6c\x61\x73\x73\ +\x3d\x22\x66\x69\x6c\x33\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\ +\x34\x32\x2e\x33\x33\x2c\x2d\x30\x2e\x30\x31\x20\x34\x32\x2e\x33\ +\x33\x2c\x34\x32\x2e\x33\x32\x20\x2d\x30\x2c\x34\x32\x2e\x33\x32\ +\x20\x2d\x30\x2c\x2d\x30\x2e\x30\x31\x20\x22\x2f\x3e\x0d\x0a\x20\ +\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x69\x64\x3d\x22\x5f\x31\ +\x5f\x30\x5f\x30\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\ +\x33\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x32\x37\x30\x2e\x39\ +\x32\x2c\x32\x32\x38\x2e\x36\x20\x32\x37\x30\x2e\x39\x32\x2c\x32\ +\x37\x30\x2e\x39\x33\x20\x32\x32\x38\x2e\x35\x39\x2c\x32\x37\x30\ +\x2e\x39\x33\x20\x32\x32\x38\x2e\x35\x39\x2c\x32\x32\x38\x2e\x36\ +\x20\x22\x2f\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\ +\x76\x67\x3e\x0d\x0a\ +\x00\x00\x06\x13\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x34\x36\x38\x32\x43\x38\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x35\x2e\x38\x37\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3a\x72\x6f\x75\x6e\x64\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\ +\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\ +\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\x6e\ +\x65\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\ +\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\x73\x3e\ +\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\x64\x61\ +\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\ +\x65\x74\x61\x64\x61\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\x72\x65\ +\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\x2d\x4c\ +\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\x65\x63\x74\ +\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\x78\x3d\ +\x22\x2d\x30\x2e\x30\x31\x22\x20\x79\x3d\x22\x2d\x30\x22\x20\x77\ +\x69\x64\x74\x68\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x20\x68\x65\ +\x69\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x2f\x3e\x0d\ +\x0a\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\ +\x66\x69\x6c\x30\x20\x73\x74\x72\x30\x22\x20\x64\x3d\x22\x4d\x37\ +\x32\x2e\x33\x33\x20\x31\x35\x30\x2e\x37\x31\x6c\x2d\x32\x39\x2e\ +\x33\x20\x32\x39\x2e\x32\x38\x63\x2d\x31\x33\x2e\x32\x33\x2c\x31\ +\x33\x2e\x32\x33\x20\x2d\x31\x33\x2e\x32\x33\x2c\x33\x34\x2e\x36\ +\x37\x20\x30\x2c\x34\x37\x2e\x39\x20\x31\x33\x2e\x32\x32\x2c\x31\ +\x33\x2e\x32\x33\x20\x33\x34\x2e\x36\x34\x2c\x31\x33\x2e\x32\x33\ +\x20\x34\x37\x2e\x38\x38\x2c\x30\x2e\x30\x32\x6c\x32\x36\x2e\x39\ +\x34\x20\x2d\x32\x36\x2e\x39\x34\x6d\x2d\x31\x38\x2e\x30\x31\x20\ +\x2d\x37\x37\x2e\x37\x39\x6c\x32\x33\x2e\x39\x35\x20\x2d\x32\x33\ +\x2e\x39\x35\x6d\x32\x30\x2e\x39\x35\x20\x37\x34\x2e\x38\x35\x6c\ +\x32\x39\x2e\x39\x34\x20\x2d\x32\x39\x2e\x39\x34\x6d\x2d\x38\x36\ +\x2e\x32\x36\x20\x31\x35\x2e\x39\x63\x31\x2e\x36\x32\x2c\x35\x2e\ +\x33\x20\x34\x2e\x35\x31\x2c\x31\x30\x2e\x31\x32\x20\x38\x2e\x34\ +\x33\x2c\x31\x34\x2e\x30\x34\x20\x31\x33\x2e\x32\x32\x2c\x31\x33\ +\x2e\x32\x33\x20\x33\x34\x2e\x36\x38\x2c\x31\x33\x2e\x32\x33\x20\ +\x34\x37\x2e\x39\x2c\x30\x6d\x31\x35\x2e\x38\x39\x20\x2d\x38\x36\ +\x2e\x32\x37\x63\x35\x2e\x33\x2c\x31\x2e\x36\x31\x20\x31\x30\x2e\ +\x31\x33\x2c\x34\x2e\x35\x31\x20\x31\x34\x2e\x30\x34\x2c\x38\x2e\ +\x34\x33\x20\x31\x33\x2e\x32\x33\x2c\x31\x33\x2e\x32\x32\x20\x31\ +\x33\x2e\x32\x33\x2c\x33\x34\x2e\x36\x37\x20\x30\x2c\x34\x37\x2e\ +\x39\x6d\x2d\x39\x2e\x39\x34\x20\x2d\x31\x35\x2e\x34\x39\x63\x2d\ +\x35\x2e\x33\x2c\x2d\x31\x2e\x36\x31\x20\x2d\x31\x30\x2e\x31\x32\ +\x2c\x2d\x34\x2e\x35\x32\x20\x2d\x31\x34\x2e\x30\x33\x2c\x2d\x38\ +\x2e\x34\x34\x20\x2d\x31\x33\x2e\x32\x33\x2c\x2d\x31\x33\x2e\x32\ +\x32\x20\x2d\x31\x33\x2e\x32\x33\x2c\x2d\x33\x34\x2e\x36\x36\x20\ +\x2d\x30\x2e\x30\x31\x2c\x2d\x34\x37\x2e\x38\x39\x6d\x30\x2e\x30\ +\x31\x20\x30\x2e\x30\x31\x6c\x32\x39\x2e\x32\x38\x20\x2d\x32\x39\ +\x2e\x33\x63\x31\x33\x2e\x32\x33\x2c\x2d\x31\x33\x2e\x32\x33\x20\ +\x33\x34\x2e\x36\x37\x2c\x2d\x31\x33\x2e\x32\x33\x20\x34\x37\x2e\ +\x39\x2c\x30\x20\x31\x33\x2e\x32\x33\x2c\x31\x33\x2e\x32\x32\x20\ +\x31\x33\x2e\x32\x33\x2c\x33\x34\x2e\x36\x34\x20\x30\x2e\x30\x32\ +\x2c\x34\x37\x2e\x38\x38\x6c\x2d\x32\x36\x2e\x39\x34\x20\x32\x36\ +\x2e\x39\x34\x6d\x2d\x37\x32\x2e\x33\x32\x20\x34\x36\x2e\x38\x39\ +\x63\x2d\x31\x2e\x36\x31\x2c\x2d\x35\x2e\x33\x20\x2d\x34\x2e\x35\ +\x32\x2c\x2d\x31\x30\x2e\x31\x32\x20\x2d\x38\x2e\x34\x34\x2c\x2d\ +\x31\x34\x2e\x30\x33\x20\x2d\x31\x33\x2e\x32\x32\x2c\x2d\x31\x33\ +\x2e\x32\x33\x20\x2d\x33\x34\x2e\x36\x36\x2c\x2d\x31\x33\x2e\x32\ +\x33\x20\x2d\x34\x37\x2e\x38\x39\x2c\x2d\x30\x2e\x30\x31\x22\x2f\ +\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\ +\x0d\x0a\ +\x00\x00\x0a\xb8\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\ +\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\ +\x66\x69\x6c\x6c\x3a\x67\x72\x61\x79\x7d\x0d\x0a\x20\x20\x20\x20\ +\x2e\x66\x69\x6c\x33\x20\x7b\x66\x69\x6c\x6c\x3a\x75\x72\x6c\x28\ +\x23\x69\x64\x30\x29\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\ +\x32\x20\x7b\x66\x69\x6c\x6c\x3a\x75\x72\x6c\x28\x23\x69\x64\x31\ +\x29\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x34\x20\x7b\x66\ +\x69\x6c\x6c\x3a\x75\x72\x6c\x28\x23\x69\x64\x32\x29\x7d\x0d\x0a\ +\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\x6c\ +\x65\x3e\x0d\x0a\x20\x20\x3c\x6c\x69\x6e\x65\x61\x72\x47\x72\x61\ +\x64\x69\x65\x6e\x74\x20\x69\x64\x3d\x22\x69\x64\x30\x22\x20\x67\ +\x72\x61\x64\x69\x65\x6e\x74\x55\x6e\x69\x74\x73\x3d\x22\x75\x73\ +\x65\x72\x53\x70\x61\x63\x65\x4f\x6e\x55\x73\x65\x22\x20\x78\x31\ +\x3d\x22\x32\x31\x37\x2e\x37\x34\x22\x20\x79\x31\x3d\x22\x31\x37\ +\x2e\x36\x33\x22\x20\x78\x32\x3d\x22\x32\x35\x33\x2e\x32\x39\x22\ +\x20\x79\x32\x3d\x22\x35\x33\x2e\x31\x38\x22\x3e\x0d\x0a\x20\x20\ +\x20\x3c\x73\x74\x6f\x70\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\x30\ +\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\ +\x61\x63\x69\x74\x79\x3a\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\ +\x6c\x6f\x72\x3a\x23\x32\x34\x32\x39\x32\x45\x22\x2f\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x73\x74\x6f\x70\x20\x6f\x66\x66\x73\x65\x74\x3d\ +\x22\x30\x2e\x33\x32\x31\x35\x36\x39\x22\x20\x73\x74\x79\x6c\x65\ +\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\ +\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x42\x30\ +\x42\x46\x42\x46\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x73\x74\x6f\ +\x70\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\x31\x22\x20\x73\x74\x79\ +\x6c\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\ +\x3a\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\ +\x32\x34\x32\x39\x32\x45\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x6c\ +\x69\x6e\x65\x61\x72\x47\x72\x61\x64\x69\x65\x6e\x74\x3e\x0d\x0a\ +\x20\x20\x3c\x6c\x69\x6e\x65\x61\x72\x47\x72\x61\x64\x69\x65\x6e\ +\x74\x20\x69\x64\x3d\x22\x69\x64\x31\x22\x20\x67\x72\x61\x64\x69\ +\x65\x6e\x74\x55\x6e\x69\x74\x73\x3d\x22\x75\x73\x65\x72\x53\x70\ +\x61\x63\x65\x4f\x6e\x55\x73\x65\x22\x20\x78\x31\x3d\x22\x31\x37\ +\x39\x2e\x32\x31\x22\x20\x79\x31\x3d\x22\x35\x36\x2e\x31\x35\x22\ +\x20\x78\x32\x3d\x22\x32\x31\x34\x2e\x37\x37\x22\x20\x79\x32\x3d\ +\x22\x39\x31\x2e\x37\x31\x22\x3e\x0d\x0a\x20\x20\x20\x3c\x73\x74\ +\x6f\x70\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\x30\x22\x20\x73\x74\ +\x79\x6c\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\ +\x79\x3a\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\ +\x23\x46\x41\x39\x45\x30\x44\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\ +\x73\x74\x6f\x70\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\x30\x2e\x33\ +\x32\x31\x35\x36\x39\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x74\ +\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\x20\x73\x74\ +\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x46\x45\x46\x45\x46\x45\ +\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x73\x74\x6f\x70\x20\x6f\x66\ +\x66\x73\x65\x74\x3d\x22\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\ +\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\x20\ +\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x44\x39\x37\x33\ +\x30\x30\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x6c\x69\x6e\x65\x61\ +\x72\x47\x72\x61\x64\x69\x65\x6e\x74\x3e\x0d\x0a\x20\x20\x3c\x6c\ +\x69\x6e\x65\x61\x72\x47\x72\x61\x64\x69\x65\x6e\x74\x20\x69\x64\ +\x3d\x22\x69\x64\x32\x22\x20\x67\x72\x61\x64\x69\x65\x6e\x74\x55\ +\x6e\x69\x74\x73\x3d\x22\x75\x73\x65\x72\x53\x70\x61\x63\x65\x4f\ +\x6e\x55\x73\x65\x22\x20\x78\x31\x3d\x22\x31\x34\x32\x2e\x31\x37\ +\x22\x20\x79\x31\x3d\x22\x31\x30\x35\x2e\x30\x34\x22\x20\x78\x32\ +\x3d\x22\x31\x38\x33\x2e\x36\x36\x22\x20\x79\x32\x3d\x22\x31\x32\ +\x32\x2e\x38\x32\x22\x3e\x0d\x0a\x20\x20\x20\x3c\x73\x74\x6f\x70\ +\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\x30\x22\x20\x73\x74\x79\x6c\ +\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\ +\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x33\ +\x38\x32\x42\x32\x36\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x73\x74\ +\x6f\x70\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\x30\x2e\x33\x32\x31\ +\x35\x36\x39\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x74\x6f\x70\ +\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\x20\x73\x74\x6f\x70\ +\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x45\x33\x45\x33\x44\x45\x22\x2f\ +\x3e\x0d\x0a\x20\x20\x20\x3c\x73\x74\x6f\x70\x20\x6f\x66\x66\x73\ +\x65\x74\x3d\x22\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x74\ +\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\x20\x73\x74\ +\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x33\x38\x32\x42\x32\x36\ +\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x6c\x69\x6e\x65\x61\x72\x47\ +\x72\x61\x64\x69\x65\x6e\x74\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\ +\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\ +\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\ +\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\ +\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\ +\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\x65\ +\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\ +\x78\x3d\x22\x2d\x30\x2e\x30\x31\x22\x20\x79\x3d\x22\x2d\x30\x22\ +\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x20\ +\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x2f\ +\x3e\x0d\x0a\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\ +\x3d\x22\x66\x69\x6c\x31\x22\x20\x64\x3d\x22\x4d\x36\x32\x2e\x32\ +\x20\x34\x32\x2e\x33\x31\x63\x32\x39\x2e\x36\x37\x2c\x30\x20\x35\ +\x33\x2e\x37\x34\x2c\x32\x34\x2e\x30\x35\x20\x35\x33\x2e\x37\x33\ +\x2c\x35\x33\x2e\x37\x33\x20\x30\x2c\x33\x2e\x32\x34\x20\x2d\x30\ +\x2e\x32\x37\x2c\x36\x2e\x34\x31\x20\x2d\x30\x2e\x38\x32\x2c\x39\ +\x2e\x34\x39\x6c\x31\x30\x34\x2e\x35\x20\x31\x30\x34\x2e\x35\x31\ +\x20\x2d\x30\x2e\x30\x31\x20\x30\x63\x35\x2e\x35\x37\x2c\x35\x2e\ +\x35\x36\x20\x39\x2c\x31\x33\x2e\x32\x33\x20\x39\x2c\x32\x31\x2e\ +\x37\x31\x20\x30\x2c\x31\x36\x2e\x39\x35\x20\x2d\x31\x33\x2e\x37\ +\x35\x2c\x33\x30\x2e\x37\x20\x2d\x33\x30\x2e\x37\x2c\x33\x30\x2e\ +\x37\x31\x20\x2d\x38\x2e\x34\x38\x2c\x30\x20\x2d\x31\x36\x2e\x31\ +\x35\x2c\x2d\x33\x2e\x34\x35\x20\x2d\x32\x31\x2e\x37\x31\x2c\x2d\ +\x39\x2e\x30\x31\x6c\x2d\x30\x2e\x35\x31\x20\x2d\x30\x2e\x35\x32\ +\x20\x2d\x31\x30\x34\x20\x2d\x31\x30\x33\x2e\x39\x38\x63\x2d\x33\ +\x2e\x30\x38\x2c\x30\x2e\x35\x35\x20\x2d\x36\x2e\x32\x35\x2c\x30\ +\x2e\x38\x32\x20\x2d\x39\x2e\x34\x38\x2c\x30\x2e\x38\x32\x20\x2d\ +\x32\x39\x2e\x36\x37\x2c\x30\x20\x2d\x35\x33\x2e\x37\x33\x2c\x2d\ +\x32\x34\x2e\x30\x36\x20\x2d\x35\x33\x2e\x37\x33\x2c\x2d\x35\x33\ +\x2e\x37\x33\x20\x30\x2c\x2d\x36\x2e\x33\x32\x20\x31\x2e\x31\x2c\ +\x2d\x31\x32\x2e\x34\x20\x33\x2e\x31\x31\x2c\x2d\x31\x38\x2e\x30\ +\x34\x6c\x33\x33\x2e\x39\x34\x20\x33\x33\x2e\x39\x34\x20\x30\x2e\ +\x33\x39\x20\x30\x2e\x33\x39\x63\x34\x2e\x31\x36\x2c\x34\x2e\x31\ +\x36\x20\x39\x2e\x39\x32\x2c\x36\x2e\x37\x34\x20\x31\x36\x2e\x32\ +\x37\x2c\x36\x2e\x37\x33\x20\x31\x32\x2e\x37\x33\x2c\x30\x2e\x30\ +\x31\x20\x32\x33\x2e\x30\x35\x2c\x2d\x31\x30\x2e\x33\x20\x32\x33\ +\x2e\x30\x33\x2c\x2d\x32\x33\x2e\x30\x32\x20\x30\x2e\x30\x32\x2c\ +\x2d\x36\x2e\x33\x36\x20\x2d\x32\x2e\x35\x37\x2c\x2d\x31\x32\x2e\ +\x31\x31\x20\x2d\x36\x2e\x37\x33\x2c\x2d\x31\x36\x2e\x32\x37\x6c\ +\x2d\x33\x34\x2e\x33\x34\x20\x2d\x33\x34\x2e\x33\x35\x63\x35\x2e\ +\x36\x34\x2c\x2d\x32\x2e\x30\x31\x20\x31\x31\x2e\x37\x33\x2c\x2d\ +\x33\x2e\x31\x31\x20\x31\x38\x2e\x30\x36\x2c\x2d\x33\x2e\x31\x31\ +\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x67\x20\x69\x64\x3d\x22\x5f\ +\x32\x37\x37\x38\x30\x32\x38\x32\x38\x31\x32\x39\x36\x22\x3e\x0d\ +\x0a\x20\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\ +\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\x20\x70\x6f\x69\x6e\x74\x73\ +\x3d\x22\x32\x33\x39\x2e\x39\x36\x2c\x36\x36\x2e\x35\x32\x20\x31\ +\x38\x39\x2e\x35\x38\x2c\x31\x31\x36\x2e\x38\x39\x20\x31\x35\x34\ +\x2e\x30\x33\x2c\x38\x31\x2e\x33\x34\x20\x32\x30\x34\x2e\x34\x2c\ +\x33\x30\x2e\x39\x36\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x70\ +\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\ +\x6c\x33\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x32\x33\x31\x2e\ +\x30\x37\x2c\x34\x2e\x32\x39\x20\x32\x30\x34\x2e\x34\x2c\x33\x30\ +\x2e\x39\x36\x20\x32\x33\x39\x2e\x39\x36\x2c\x36\x36\x2e\x35\x32\ +\x20\x32\x36\x36\x2e\x36\x33\x2c\x33\x39\x2e\x38\x35\x20\x22\x2f\ +\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\ +\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x34\x22\x20\x70\x6f\x69\x6e\ +\x74\x73\x3d\x22\x31\x35\x34\x2e\x30\x32\x2c\x38\x31\x2e\x33\x35\ +\x20\x31\x38\x39\x2e\x35\x37\x2c\x31\x31\x36\x2e\x39\x20\x31\x34\ +\x38\x2e\x31\x2c\x31\x32\x32\x2e\x38\x32\x20\x22\x2f\x3e\x0d\x0a\ +\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\ +\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x05\x0c\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x34\x36\x38\x32\x43\x38\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x36\x2e\x39\x33\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\ +\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x30\ +\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\x6e\x65\x7d\x0d\x0a\x20\x20\ +\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x23\x46\ +\x46\x39\x39\x33\x33\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\ +\x20\x20\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\ +\x65\x66\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\ +\x6d\x61\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\ +\x20\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\x69\x64\x3d\x22\ +\x43\x6f\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\ +\x65\x6c\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\ +\x72\x65\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\ +\x22\x20\x78\x3d\x22\x2d\x30\x2e\x30\x31\x22\x20\x79\x3d\x22\x2d\ +\x30\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\x30\x2e\x39\x33\ +\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\x39\x33\ +\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x63\x69\x72\x63\x6c\x65\x20\x63\ +\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x30\x22\ +\x20\x63\x78\x3d\x22\x31\x33\x35\x2e\x34\x36\x22\x20\x63\x79\x3d\ +\x22\x31\x33\x35\x2e\x34\x36\x22\x20\x72\x3d\x22\x31\x32\x32\x2e\ +\x37\x36\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\ +\x6e\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\x22\x20\x63\x6c\x61\x73\ +\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\ +\x22\x32\x39\x2e\x36\x39\x2c\x31\x39\x38\x2e\x39\x20\x37\x32\x2e\ +\x30\x32\x2c\x31\x39\x38\x2e\x39\x20\x37\x32\x2e\x30\x32\x2c\x32\ +\x34\x31\x2e\x32\x33\x20\x32\x39\x2e\x36\x39\x2c\x32\x34\x31\x2e\ +\x32\x33\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\ +\x6f\x6e\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\x5f\x30\x22\x20\x63\ +\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\ +\x74\x73\x3d\x22\x31\x39\x38\x2e\x39\x38\x2c\x32\x39\x2e\x36\x20\ +\x32\x34\x31\x2e\x33\x31\x2c\x32\x39\x2e\x36\x20\x32\x34\x31\x2e\ +\x33\x31\x2c\x37\x31\x2e\x39\x33\x20\x31\x39\x38\x2e\x39\x38\x2c\ +\x37\x31\x2e\x39\x33\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\ +\x6c\x79\x67\x6f\x6e\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\x5f\x31\ +\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\ +\x6f\x69\x6e\x74\x73\x3d\x22\x32\x39\x2e\x35\x39\x2c\x32\x39\x2e\ +\x35\x38\x20\x37\x31\x2e\x39\x32\x2c\x32\x39\x2e\x35\x38\x20\x37\ +\x31\x2e\x39\x32\x2c\x37\x31\x2e\x39\x31\x20\x32\x39\x2e\x35\x39\ +\x2c\x37\x31\x2e\x39\x31\x20\x22\x2f\x3e\x0d\x0a\x20\x3c\x2f\x67\ +\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x0a\x29\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x31\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x39\x39\x39\x39\x39\x39\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x37\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\ +\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x73\x74\x72\x30\ +\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\x23\x34\x36\x38\x32\x43\x38\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x31\x37\ +\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\ +\x6f\x69\x6e\x3a\x72\x6f\x75\x6e\x64\x3b\x73\x74\x72\x6f\x6b\x65\ +\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\ +\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x30\x20\ +\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\x6e\x65\x7d\x0d\x0a\x20\x20\x20\ +\x20\x2e\x66\x69\x6c\x32\x20\x7b\x66\x69\x6c\x6c\x3a\x75\x72\x6c\ +\x28\x23\x69\x64\x30\x29\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\ +\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x75\x72\x6c\x28\x23\x69\x64\ +\x31\x29\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x33\x20\x7b\ +\x66\x69\x6c\x6c\x3a\x75\x72\x6c\x28\x23\x69\x64\x32\x29\x7d\x0d\ +\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\ +\x6c\x65\x3e\x0d\x0a\x20\x20\x3c\x6c\x69\x6e\x65\x61\x72\x47\x72\ +\x61\x64\x69\x65\x6e\x74\x20\x69\x64\x3d\x22\x69\x64\x30\x22\x20\ +\x67\x72\x61\x64\x69\x65\x6e\x74\x55\x6e\x69\x74\x73\x3d\x22\x75\ +\x73\x65\x72\x53\x70\x61\x63\x65\x4f\x6e\x55\x73\x65\x22\x20\x78\ +\x31\x3d\x22\x32\x31\x37\x2e\x37\x34\x22\x20\x79\x31\x3d\x22\x37\ +\x32\x2e\x36\x32\x22\x20\x78\x32\x3d\x22\x32\x35\x33\x2e\x32\x39\ +\x22\x20\x79\x32\x3d\x22\x31\x30\x38\x2e\x31\x37\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x73\x74\x6f\x70\x20\x6f\x66\x66\x73\x65\x74\x3d\ +\x22\x30\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x74\x6f\x70\x2d\ +\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\x20\x73\x74\x6f\x70\x2d\ +\x63\x6f\x6c\x6f\x72\x3a\x23\x32\x34\x32\x39\x32\x45\x22\x2f\x3e\ +\x0d\x0a\x20\x20\x20\x3c\x73\x74\x6f\x70\x20\x6f\x66\x66\x73\x65\ +\x74\x3d\x22\x30\x2e\x33\x32\x31\x35\x36\x39\x22\x20\x73\x74\x79\ +\x6c\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\ +\x3a\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\ +\x42\x30\x42\x46\x42\x46\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x73\ +\x74\x6f\x70\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\x31\x22\x20\x73\ +\x74\x79\x6c\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\ +\x74\x79\x3a\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\ +\x3a\x23\x32\x34\x32\x39\x32\x45\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\ +\x2f\x6c\x69\x6e\x65\x61\x72\x47\x72\x61\x64\x69\x65\x6e\x74\x3e\ +\x0d\x0a\x20\x20\x3c\x6c\x69\x6e\x65\x61\x72\x47\x72\x61\x64\x69\ +\x65\x6e\x74\x20\x69\x64\x3d\x22\x69\x64\x31\x22\x20\x67\x72\x61\ +\x64\x69\x65\x6e\x74\x55\x6e\x69\x74\x73\x3d\x22\x75\x73\x65\x72\ +\x53\x70\x61\x63\x65\x4f\x6e\x55\x73\x65\x22\x20\x78\x31\x3d\x22\ +\x31\x37\x39\x2e\x32\x31\x22\x20\x79\x31\x3d\x22\x31\x31\x31\x2e\ +\x31\x34\x22\x20\x78\x32\x3d\x22\x32\x31\x34\x2e\x37\x37\x22\x20\ +\x79\x32\x3d\x22\x31\x34\x36\x2e\x37\x22\x3e\x0d\x0a\x20\x20\x20\ +\x3c\x73\x74\x6f\x70\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\x30\x22\ +\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\ +\x63\x69\x74\x79\x3a\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\ +\x6f\x72\x3a\x23\x46\x41\x39\x45\x30\x44\x22\x2f\x3e\x0d\x0a\x20\ +\x20\x20\x3c\x73\x74\x6f\x70\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\ +\x30\x2e\x33\x32\x31\x35\x36\x39\x22\x20\x73\x74\x79\x6c\x65\x3d\ +\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\ +\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x46\x45\x46\ +\x45\x46\x45\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x73\x74\x6f\x70\ +\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\x31\x22\x20\x73\x74\x79\x6c\ +\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\ +\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x44\ +\x39\x37\x33\x30\x30\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x6c\x69\ +\x6e\x65\x61\x72\x47\x72\x61\x64\x69\x65\x6e\x74\x3e\x0d\x0a\x20\ +\x20\x3c\x6c\x69\x6e\x65\x61\x72\x47\x72\x61\x64\x69\x65\x6e\x74\ +\x20\x69\x64\x3d\x22\x69\x64\x32\x22\x20\x67\x72\x61\x64\x69\x65\ +\x6e\x74\x55\x6e\x69\x74\x73\x3d\x22\x75\x73\x65\x72\x53\x70\x61\ +\x63\x65\x4f\x6e\x55\x73\x65\x22\x20\x78\x31\x3d\x22\x31\x34\x32\ +\x2e\x31\x37\x22\x20\x79\x31\x3d\x22\x31\x36\x30\x2e\x30\x33\x22\ +\x20\x78\x32\x3d\x22\x31\x38\x33\x2e\x36\x36\x22\x20\x79\x32\x3d\ +\x22\x31\x37\x37\x2e\x38\x31\x22\x3e\x0d\x0a\x20\x20\x20\x3c\x73\ +\x74\x6f\x70\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\x30\x22\x20\x73\ +\x74\x79\x6c\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\ +\x74\x79\x3a\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\ +\x3a\x23\x33\x38\x32\x42\x32\x36\x22\x2f\x3e\x0d\x0a\x20\x20\x20\ +\x3c\x73\x74\x6f\x70\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\x30\x2e\ +\x33\x32\x31\x35\x36\x39\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\ +\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\x20\x73\ +\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x45\x33\x45\x33\x44\ +\x45\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x73\x74\x6f\x70\x20\x6f\ +\x66\x66\x73\x65\x74\x3d\x22\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\ +\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\ +\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x33\x38\x32\ +\x42\x32\x36\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x6c\x69\x6e\x65\ +\x61\x72\x47\x72\x61\x64\x69\x65\x6e\x74\x3e\x0d\x0a\x20\x3c\x2f\ +\x64\x65\x66\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\x22\x43\ +\x61\x6d\x61\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\x3e\x0d\ +\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\x69\x64\x3d\ +\x22\x43\x6f\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\x43\x6f\ +\x72\x65\x6c\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\x20\x20\ +\x3c\x72\x65\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\ +\x30\x22\x20\x78\x3d\x22\x2d\x30\x2e\x30\x31\x22\x20\x79\x3d\x22\ +\x2d\x30\x2e\x30\x33\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\ +\x30\x2e\x39\x35\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\ +\x30\x2e\x39\x35\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x61\x74\x68\ +\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\ +\x30\x22\x20\x64\x3d\x22\x4d\x31\x30\x35\x2e\x37\x31\x20\x36\x37\ +\x2e\x37\x33\x6c\x2d\x32\x35\x2e\x34\x20\x30\x63\x2d\x33\x37\x2e\ +\x33\x2c\x30\x20\x2d\x36\x37\x2e\x36\x2c\x33\x30\x2e\x33\x20\x2d\ +\x36\x37\x2e\x36\x2c\x36\x37\x2e\x36\x38\x20\x30\x2c\x33\x37\x2e\ +\x33\x38\x20\x33\x30\x2e\x33\x2c\x36\x37\x2e\x37\x39\x20\x36\x37\ +\x2e\x36\x2c\x36\x37\x2e\x37\x39\x6c\x39\x33\x2e\x31\x38\x20\x30\ +\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\ +\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x31\x22\x20\x64\ +\x3d\x22\x4d\x32\x33\x34\x2e\x39\x31\x20\x32\x30\x33\x2e\x31\x34\ +\x6c\x2d\x31\x37\x2e\x30\x32\x20\x30\x6d\x34\x36\x2e\x36\x33\x20\ +\x30\x6c\x2d\x31\x37\x2e\x30\x33\x20\x30\x6d\x2d\x34\x32\x2e\x31\ +\x38\x20\x30\x6c\x2d\x31\x37\x2e\x30\x34\x20\x30\x22\x2f\x3e\x0d\ +\x0a\x20\x20\x3c\x67\x20\x69\x64\x3d\x22\x5f\x32\x37\x34\x31\x30\ +\x38\x32\x37\x38\x37\x39\x30\x34\x22\x3e\x0d\x0a\x20\x20\x20\x3c\ +\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\ +\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x32\x33\x39\ +\x2e\x39\x36\x2c\x31\x32\x31\x2e\x35\x31\x20\x31\x38\x39\x2e\x35\ +\x38\x2c\x31\x37\x31\x2e\x38\x38\x20\x31\x35\x34\x2e\x30\x33\x2c\ +\x31\x33\x36\x2e\x33\x33\x20\x32\x30\x34\x2e\x34\x2c\x38\x35\x2e\ +\x39\x35\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x6f\x6c\x79\ +\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\ +\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x32\x33\x31\x2e\x30\x37\x2c\ +\x35\x39\x2e\x32\x38\x20\x32\x30\x34\x2e\x34\x2c\x38\x35\x2e\x39\ +\x35\x20\x32\x33\x39\x2e\x39\x36\x2c\x31\x32\x31\x2e\x35\x31\x20\ +\x32\x36\x36\x2e\x36\x33\x2c\x39\x34\x2e\x38\x34\x20\x22\x2f\x3e\ +\x0d\x0a\x20\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x33\x22\x20\x70\x6f\x69\x6e\x74\ +\x73\x3d\x22\x31\x35\x34\x2e\x30\x32\x2c\x31\x33\x36\x2e\x33\x34\ +\x20\x31\x38\x39\x2e\x35\x37\x2c\x31\x37\x31\x2e\x38\x39\x20\x31\ +\x34\x38\x2e\x31\x2c\x31\x37\x37\x2e\x38\x31\x20\x22\x2f\x3e\x0d\ +\x0a\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\ +\x3c\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x06\xa6\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x32\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x36\x36\x36\x36\x36\x36\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x37\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\ +\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x73\x74\x72\x31\ +\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\x23\x39\x39\x39\x39\x39\x39\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x31\x37\ +\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\ +\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\ +\x20\x20\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\ +\x65\x3a\x23\x36\x36\x36\x36\x36\x36\x3b\x73\x74\x72\x6f\x6b\x65\ +\x2d\x77\x69\x64\x74\x68\x3a\x31\x37\x2e\x36\x34\x3b\x73\x74\x72\ +\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3a\x72\x6f\x75\ +\x6e\x64\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\x6c\ +\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\x20\ +\x20\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\ +\x6f\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x32\x20\ +\x7b\x66\x69\x6c\x6c\x3a\x23\x30\x30\x39\x39\x33\x33\x7d\x0d\x0a\ +\x20\x20\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\ +\x77\x68\x69\x74\x65\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\ +\x20\x20\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\ +\x65\x66\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\ +\x6d\x61\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\ +\x20\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\x69\x64\x3d\x22\ +\x43\x6f\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\ +\x65\x6c\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\ +\x72\x65\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\ +\x22\x20\x78\x3d\x22\x2d\x30\x2e\x30\x31\x22\x20\x79\x3d\x22\x2d\ +\x30\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\x30\x2e\x39\x33\ +\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\x39\x33\ +\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x6c\x69\x6e\x65\x20\x63\x6c\x61\ +\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x30\x22\x20\x78\ +\x31\x3d\x22\x31\x37\x37\x2e\x38\x22\x20\x79\x31\x3d\x22\x36\x37\ +\x2e\x37\x31\x22\x20\x78\x32\x3d\x22\x34\x2e\x32\x32\x22\x20\x79\ +\x32\x3d\x20\x22\x36\x37\x2e\x37\x36\x22\x20\x2f\x3e\x0d\x0a\x20\ +\x20\x3c\x6c\x69\x6e\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\ +\x6c\x30\x20\x73\x74\x72\x30\x22\x20\x78\x31\x3d\x22\x32\x36\x36\ +\x2e\x36\x39\x22\x20\x79\x31\x3d\x22\x36\x37\x2e\x37\x31\x22\x20\ +\x78\x32\x3d\x22\x32\x32\x30\x2e\x31\x36\x22\x20\x79\x32\x3d\x20\ +\x22\x36\x37\x2e\x37\x36\x22\x20\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\ +\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\ +\x73\x74\x72\x31\x22\x20\x64\x3d\x22\x4d\x31\x31\x36\x2e\x37\x33\ +\x20\x31\x35\x34\x2e\x31\x36\x6c\x31\x31\x2e\x39\x37\x20\x2d\x31\ +\x31\x2e\x39\x37\x6d\x38\x2e\x39\x38\x20\x2d\x38\x2e\x39\x38\x6c\ +\x31\x31\x2e\x39\x37\x20\x2d\x31\x31\x2e\x39\x37\x6d\x38\x2e\x39\ +\x38\x20\x2d\x38\x2e\x39\x38\x6c\x31\x31\x2e\x39\x37\x20\x2d\x31\ +\x31\x2e\x39\x37\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x6c\x69\x6e\x65\ +\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\ +\x32\x22\x20\x78\x31\x3d\x22\x31\x30\x37\x2e\x37\x34\x22\x20\x79\ +\x31\x3d\x22\x31\x36\x33\x2e\x31\x35\x22\x20\x78\x32\x3d\x22\x38\ +\x2e\x34\x35\x22\x20\x79\x32\x3d\x20\x22\x32\x36\x32\x2e\x34\x37\ +\x22\x20\x2f\x3e\x0d\x0a\x20\x20\x3c\x67\x3e\x0d\x0a\x20\x20\x20\ +\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\ +\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x32\x33\ +\x32\x2e\x38\x35\x2c\x39\x37\x2e\x33\x33\x20\x31\x37\x33\x2e\x35\ +\x38\x2c\x39\x37\x2e\x33\x33\x20\x31\x37\x33\x2e\x35\x38\x2c\x33\ +\x38\x2e\x30\x36\x20\x32\x33\x32\x2e\x38\x35\x2c\x33\x38\x2e\x30\ +\x36\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x61\x74\x68\x20\ +\x69\x64\x3d\x22\x5f\x31\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\ +\x69\x6c\x32\x22\x20\x64\x3d\x22\x4d\x32\x33\x32\x2e\x38\x35\x20\ +\x39\x37\x2e\x33\x33\x6c\x2d\x35\x39\x2e\x32\x37\x20\x30\x20\x30\ +\x20\x2d\x35\x39\x2e\x32\x37\x20\x35\x39\x2e\x32\x37\x20\x30\x20\ +\x30\x20\x35\x39\x2e\x32\x37\x7a\x6d\x2d\x34\x36\x2e\x39\x32\x20\ +\x2d\x31\x32\x2e\x33\x35\x6c\x33\x34\x2e\x35\x37\x20\x30\x20\x30\ +\x20\x2d\x33\x34\x2e\x35\x37\x20\x2d\x33\x34\x2e\x35\x37\x20\x30\ +\x20\x30\x20\x33\x34\x2e\x35\x37\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\ +\x3c\x2f\x67\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\ +\x76\x67\x3e\x0d\x0a\ +\x00\x00\x06\x30\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\ +\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x32\x20\x7b\ +\x66\x69\x6c\x6c\x3a\x23\x46\x46\x39\x39\x33\x33\x7d\x0d\x0a\x20\ +\x20\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x23\ +\x34\x36\x38\x32\x43\x38\x3b\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\ +\x3a\x6e\x6f\x6e\x7a\x65\x72\x6f\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\ +\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\ +\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\ +\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\ +\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\x69\ +\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\ +\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\ +\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\ +\x69\x6c\x30\x22\x20\x78\x3d\x22\x30\x22\x20\x79\x3d\x22\x2d\x30\ +\x2e\x30\x31\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\x30\x2e\ +\x39\x32\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\ +\x39\x32\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x67\x3e\x0d\x0a\x20\x20\ +\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\ +\x6c\x31\x22\x20\x64\x3d\x22\x4d\x32\x35\x34\x20\x32\x31\x36\x2e\ +\x32\x38\x6c\x2d\x34\x32\x2e\x34\x20\x30\x20\x30\x20\x2d\x31\x37\ +\x2e\x36\x34\x20\x34\x32\x2e\x34\x20\x30\x20\x38\x2e\x38\x32\x20\ +\x38\x2e\x38\x32\x20\x2d\x38\x2e\x38\x32\x20\x38\x2e\x38\x32\x7a\ +\x6d\x38\x2e\x38\x32\x20\x2d\x38\x2e\x38\x32\x6c\x30\x20\x38\x2e\ +\x38\x32\x20\x2d\x38\x2e\x38\x32\x20\x30\x20\x38\x2e\x38\x32\x20\ +\x2d\x38\x2e\x38\x32\x7a\x6d\x30\x20\x2d\x31\x36\x39\x2e\x33\x34\ +\x6c\x30\x20\x31\x36\x39\x2e\x33\x34\x20\x2d\x31\x37\x2e\x36\x34\ +\x20\x30\x20\x30\x20\x2d\x31\x36\x39\x2e\x33\x34\x20\x38\x2e\x38\ +\x32\x20\x2d\x38\x2e\x38\x32\x20\x38\x2e\x38\x32\x20\x38\x2e\x38\ +\x32\x7a\x6d\x2d\x38\x2e\x38\x32\x20\x2d\x38\x2e\x38\x32\x6c\x38\ +\x2e\x38\x32\x20\x30\x20\x30\x20\x38\x2e\x38\x32\x20\x2d\x38\x2e\ +\x38\x32\x20\x2d\x38\x2e\x38\x32\x7a\x6d\x2d\x32\x33\x37\x2e\x30\ +\x38\x20\x30\x6c\x32\x33\x37\x2e\x30\x38\x20\x30\x20\x30\x20\x31\ +\x37\x2e\x36\x34\x20\x2d\x32\x33\x37\x2e\x30\x38\x20\x30\x20\x2d\ +\x38\x2e\x38\x32\x20\x2d\x38\x2e\x38\x32\x20\x38\x2e\x38\x32\x20\ +\x2d\x38\x2e\x38\x32\x7a\x6d\x2d\x38\x2e\x38\x32\x20\x38\x2e\x38\ +\x32\x6c\x30\x20\x2d\x38\x2e\x38\x32\x20\x38\x2e\x38\x32\x20\x30\ +\x20\x2d\x38\x2e\x38\x32\x20\x38\x2e\x38\x32\x7a\x6d\x30\x20\x31\ +\x36\x39\x2e\x33\x34\x6c\x30\x20\x2d\x31\x36\x39\x2e\x33\x34\x20\ +\x31\x37\x2e\x36\x34\x20\x30\x20\x30\x20\x31\x36\x39\x2e\x33\x34\ +\x20\x2d\x38\x2e\x38\x32\x20\x38\x2e\x38\x32\x20\x2d\x38\x2e\x38\ +\x32\x20\x2d\x38\x2e\x38\x32\x7a\x6d\x38\x2e\x38\x32\x20\x38\x2e\ +\x38\x32\x6c\x2d\x38\x2e\x38\x32\x20\x30\x20\x30\x20\x2d\x38\x2e\ +\x38\x32\x20\x38\x2e\x38\x32\x20\x38\x2e\x38\x32\x7a\x6d\x34\x32\ +\x2e\x34\x20\x30\x6c\x2d\x34\x32\x2e\x34\x20\x30\x20\x30\x20\x2d\ +\x31\x37\x2e\x36\x34\x20\x34\x32\x2e\x34\x20\x30\x20\x30\x20\x31\ +\x37\x2e\x36\x34\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\x3e\ +\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x69\x64\x3d\ +\x22\x5f\x31\x5f\x30\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\ +\x6c\x32\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x37\x31\x2e\x39\ +\x39\x2c\x31\x38\x36\x2e\x32\x31\x20\x31\x31\x34\x2e\x33\x32\x2c\ +\x31\x38\x36\x2e\x32\x31\x20\x31\x31\x34\x2e\x33\x32\x2c\x32\x32\ +\x38\x2e\x35\x34\x20\x37\x31\x2e\x39\x39\x2c\x32\x32\x38\x2e\x35\ +\x34\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\ +\x6e\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\x5f\x30\x22\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\x20\x70\x6f\x69\x6e\x74\ +\x73\x3d\x22\x31\x35\x36\x2e\x35\x39\x2c\x31\x38\x36\x2e\x32\x31\ +\x20\x31\x39\x38\x2e\x39\x32\x2c\x31\x38\x36\x2e\x32\x31\x20\x31\ +\x39\x38\x2e\x39\x32\x2c\x32\x32\x38\x2e\x35\x34\x20\x31\x35\x36\ +\x2e\x35\x39\x2c\x32\x32\x38\x2e\x35\x34\x20\x22\x2f\x3e\x0d\x0a\ +\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x07\x07\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x31\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x67\x72\x61\x79\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\ +\x68\x3a\x31\x34\x2e\x31\x31\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\ +\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\ +\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\ +\x74\x72\x6f\x6b\x65\x3a\x23\x34\x36\x38\x32\x43\x38\x3b\x73\x74\ +\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x31\x36\x2e\x39\x33\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x63\x61\x70\x3a\ +\x73\x71\x75\x61\x72\x65\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\ +\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\ +\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\ +\x6c\x6c\x3a\x6e\x6f\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\ +\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x67\x72\x61\x79\x7d\x0d\ +\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x32\x20\x7b\x66\x69\x6c\x6c\ +\x3a\x23\x46\x46\x39\x39\x33\x33\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\ +\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\ +\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\ +\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\ +\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\x69\ +\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\ +\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\ +\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\ +\x69\x6c\x30\x22\x20\x78\x3d\x22\x30\x22\x20\x79\x3d\x22\x30\x22\ +\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\x30\x2e\x39\x32\x22\x20\ +\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\x39\x32\x22\x2f\ +\x3e\x0d\x0a\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\ +\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x30\x22\x20\x64\x3d\x22\ +\x4d\x32\x34\x39\x2e\x36\x35\x20\x32\x35\x38\x2e\x31\x32\x63\x2d\ +\x34\x2e\x35\x2c\x2d\x31\x32\x38\x2e\x38\x37\x20\x2d\x31\x30\x37\ +\x2e\x39\x33\x2c\x2d\x32\x33\x32\x2e\x33\x20\x2d\x32\x33\x36\x2e\ +\x38\x2c\x2d\x32\x33\x36\x2e\x38\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\ +\x6c\x69\x6e\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\ +\x20\x73\x74\x72\x31\x22\x20\x78\x31\x3d\x22\x36\x31\x2e\x36\x37\ +\x22\x20\x79\x31\x3d\x22\x36\x31\x2e\x30\x39\x22\x20\x78\x32\x3d\ +\x22\x32\x32\x36\x2e\x31\x39\x22\x20\x79\x32\x3d\x20\x22\x32\x32\ +\x36\x2e\x37\x34\x22\x20\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\ +\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\ +\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x34\x36\x2e\x31\x32\x2c\ +\x34\x34\x2e\x39\x20\x35\x31\x2e\x35\x39\x2c\x36\x35\x2e\x33\x34\ +\x20\x35\x37\x2e\x30\x37\x2c\x38\x35\x2e\x37\x38\x20\x37\x32\x2e\ +\x30\x34\x2c\x37\x30\x2e\x38\x32\x20\x38\x37\x2c\x35\x35\x2e\x38\ +\x35\x20\x36\x36\x2e\x35\x36\x2c\x35\x30\x2e\x33\x37\x20\x22\x2f\ +\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x69\x64\ +\x3d\x22\x5f\x31\x5f\x30\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\ +\x69\x6c\x32\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x34\x32\x2e\ +\x33\x34\x2c\x2d\x30\x2e\x30\x31\x20\x34\x32\x2e\x33\x34\x2c\x34\ +\x32\x2e\x33\x32\x20\x30\x2e\x30\x31\x2c\x34\x32\x2e\x33\x32\x20\ +\x30\x2e\x30\x31\x2c\x2d\x30\x2e\x30\x31\x20\x22\x2f\x3e\x0d\x0a\ +\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x69\x64\x3d\x22\x5f\ +\x31\x5f\x30\x5f\x30\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\ +\x6c\x32\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x34\x32\x2e\x33\ +\x33\x2c\x32\x32\x38\x2e\x36\x20\x34\x32\x2e\x33\x33\x2c\x32\x37\ +\x30\x2e\x39\x33\x20\x2d\x30\x2c\x32\x37\x30\x2e\x39\x33\x20\x2d\ +\x30\x2c\x32\x32\x38\x2e\x36\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\ +\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\ +\x5f\x31\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\ +\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x32\x37\x30\x2e\x39\x33\x2c\ +\x32\x32\x38\x2e\x36\x20\x32\x37\x30\x2e\x39\x33\x2c\x32\x37\x30\ +\x2e\x39\x33\x20\x32\x32\x38\x2e\x36\x2c\x32\x37\x30\x2e\x39\x33\ +\x20\x32\x32\x38\x2e\x36\x2c\x32\x32\x38\x2e\x36\x20\x22\x2f\x3e\ +\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\ +\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\ +\x3d\x22\x32\x32\x30\x2e\x31\x34\x2c\x32\x34\x39\x2e\x37\x36\x20\ +\x32\x30\x31\x2e\x38\x31\x2c\x32\x33\x39\x2e\x31\x38\x20\x31\x38\ +\x33\x2e\x34\x38\x2c\x32\x32\x38\x2e\x36\x20\x31\x38\x33\x2e\x34\ +\x38\x2c\x32\x34\x39\x2e\x37\x36\x20\x31\x38\x33\x2e\x34\x38\x2c\ +\x32\x37\x30\x2e\x39\x33\x20\x32\x30\x31\x2e\x38\x31\x2c\x32\x36\ +\x30\x2e\x33\x35\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x6c\x69\x6e\ +\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\ +\x72\x31\x22\x20\x78\x31\x3d\x22\x32\x30\x30\x2e\x34\x22\x20\x79\ +\x31\x3d\x22\x32\x34\x39\x2e\x37\x36\x22\x20\x78\x32\x3d\x22\x35\ +\x30\x2e\x37\x39\x22\x20\x79\x32\x3d\x20\x22\x32\x34\x39\x2e\x37\ +\x36\x22\x20\x2f\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\ +\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x07\x58\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\ +\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x33\x20\x7b\ +\x66\x69\x6c\x6c\x3a\x23\x34\x36\x38\x32\x43\x38\x7d\x0d\x0a\x20\ +\x20\x20\x20\x2e\x66\x69\x6c\x32\x20\x7b\x66\x69\x6c\x6c\x3a\x23\ +\x46\x46\x39\x39\x33\x33\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\ +\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x77\x68\x69\x74\x65\x7d\x0d\ +\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\ +\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\ +\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\ +\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\ +\x64\x61\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\ +\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\ +\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\x78\x3d\x22\x2d\x30\ +\x2e\x30\x31\x22\x20\x79\x3d\x22\x2d\x30\x22\x20\x77\x69\x64\x74\ +\x68\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x20\x68\x65\x69\x67\x68\ +\x74\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x2f\x3e\x0d\x0a\x20\x20\ +\x3c\x67\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\ +\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\ +\x69\x6e\x74\x73\x3d\x22\x31\x36\x2e\x39\x33\x2c\x32\x35\x33\x2e\ +\x39\x38\x20\x31\x31\x30\x2e\x30\x36\x2c\x32\x35\x33\x2e\x39\x38\ +\x20\x31\x31\x30\x2e\x30\x36\x2c\x31\x36\x30\x2e\x38\x35\x20\x31\ +\x36\x2e\x39\x33\x2c\x31\x36\x30\x2e\x38\x35\x20\x22\x2f\x3e\x0d\ +\x0a\x20\x20\x20\x3c\x70\x61\x74\x68\x20\x69\x64\x3d\x22\x5f\x31\ +\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\x20\x64\ +\x3d\x22\x4d\x33\x34\x2e\x35\x37\x20\x32\x33\x36\x2e\x33\x34\x6c\ +\x35\x37\x2e\x38\x35\x20\x30\x20\x30\x20\x2d\x35\x37\x2e\x38\x35\ +\x20\x2d\x35\x37\x2e\x38\x35\x20\x30\x20\x30\x20\x35\x37\x2e\x38\ +\x35\x7a\x6d\x2d\x31\x37\x2e\x36\x34\x20\x31\x37\x2e\x36\x34\x6c\ +\x39\x33\x2e\x31\x33\x20\x30\x20\x30\x20\x2d\x39\x33\x2e\x31\x33\ +\x20\x2d\x39\x33\x2e\x31\x33\x20\x30\x20\x30\x20\x39\x33\x2e\x31\ +\x33\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\x20\ +\x20\x3c\x67\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\ +\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\ +\x6f\x69\x6e\x74\x73\x3d\x22\x31\x36\x30\x2e\x38\x35\x2c\x32\x35\ +\x33\x2e\x39\x38\x20\x32\x35\x33\x2e\x39\x38\x2c\x32\x35\x33\x2e\ +\x39\x38\x20\x32\x35\x33\x2e\x39\x38\x2c\x31\x36\x30\x2e\x38\x35\ +\x20\x31\x36\x30\x2e\x38\x35\x2c\x31\x36\x30\x2e\x38\x35\x20\x22\ +\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x61\x74\x68\x20\x69\x64\x3d\ +\x22\x5f\x31\x5f\x30\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\ +\x6c\x33\x22\x20\x64\x3d\x22\x4d\x31\x37\x38\x2e\x34\x39\x20\x32\ +\x33\x36\x2e\x33\x34\x6c\x35\x37\x2e\x38\x35\x20\x30\x20\x30\x20\ +\x2d\x35\x37\x2e\x38\x35\x20\x2d\x35\x37\x2e\x38\x35\x20\x30\x20\ +\x30\x20\x35\x37\x2e\x38\x35\x7a\x6d\x2d\x31\x37\x2e\x36\x34\x20\ +\x31\x37\x2e\x36\x34\x6c\x39\x33\x2e\x31\x33\x20\x30\x20\x30\x20\ +\x2d\x39\x33\x2e\x31\x33\x20\x2d\x39\x33\x2e\x31\x33\x20\x30\x20\ +\x30\x20\x39\x33\x2e\x31\x33\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\ +\x2f\x67\x3e\x0d\x0a\x20\x20\x3c\x67\x3e\x0d\x0a\x20\x20\x20\x3c\ +\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\ +\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x31\x36\x2e\ +\x39\x33\x2c\x31\x31\x30\x2e\x30\x36\x20\x31\x31\x30\x2e\x30\x36\ +\x2c\x31\x31\x30\x2e\x30\x36\x20\x31\x31\x30\x2e\x30\x36\x2c\x31\ +\x36\x2e\x39\x33\x20\x31\x36\x2e\x39\x33\x2c\x31\x36\x2e\x39\x33\ +\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x61\x74\x68\x20\x69\ +\x64\x3d\x22\x5f\x31\x5f\x31\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\ +\x66\x69\x6c\x33\x22\x20\x64\x3d\x22\x4d\x33\x34\x2e\x35\x37\x20\ +\x39\x32\x2e\x34\x32\x6c\x35\x37\x2e\x38\x35\x20\x30\x20\x30\x20\ +\x2d\x35\x37\x2e\x38\x35\x20\x2d\x35\x37\x2e\x38\x35\x20\x30\x20\ +\x30\x20\x35\x37\x2e\x38\x35\x7a\x6d\x2d\x31\x37\x2e\x36\x34\x20\ +\x31\x37\x2e\x36\x34\x6c\x39\x33\x2e\x31\x33\x20\x30\x20\x30\x20\ +\x2d\x39\x33\x2e\x31\x33\x20\x2d\x39\x33\x2e\x31\x33\x20\x30\x20\ +\x30\x20\x39\x33\x2e\x31\x33\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\ +\x2f\x67\x3e\x0d\x0a\x20\x20\x3c\x67\x3e\x0d\x0a\x20\x20\x20\x3c\ +\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\ +\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x31\x36\x30\ +\x2e\x38\x35\x2c\x31\x31\x30\x2e\x30\x36\x20\x32\x35\x33\x2e\x39\ +\x38\x2c\x31\x31\x30\x2e\x30\x36\x20\x32\x35\x33\x2e\x39\x38\x2c\ +\x31\x36\x2e\x39\x33\x20\x31\x36\x30\x2e\x38\x35\x2c\x31\x36\x2e\ +\x39\x33\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x61\x74\x68\ +\x20\x69\x64\x3d\x22\x5f\x31\x5f\x32\x22\x20\x63\x6c\x61\x73\x73\ +\x3d\x22\x66\x69\x6c\x33\x22\x20\x64\x3d\x22\x4d\x31\x37\x38\x2e\ +\x34\x39\x20\x39\x32\x2e\x34\x32\x6c\x35\x37\x2e\x38\x35\x20\x30\ +\x20\x30\x20\x2d\x35\x37\x2e\x38\x35\x20\x2d\x35\x37\x2e\x38\x35\ +\x20\x30\x20\x30\x20\x35\x37\x2e\x38\x35\x7a\x6d\x2d\x31\x37\x2e\ +\x36\x34\x20\x31\x37\x2e\x36\x34\x6c\x39\x33\x2e\x31\x33\x20\x30\ +\x20\x30\x20\x2d\x39\x33\x2e\x31\x33\x20\x2d\x39\x33\x2e\x31\x33\ +\x20\x30\x20\x30\x20\x39\x33\x2e\x31\x33\x7a\x22\x2f\x3e\x0d\x0a\ +\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\ +\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x05\x95\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x31\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x34\x36\x38\x32\x43\x38\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x36\x2e\x39\x33\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\ +\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x73\x74\x72\x30\ +\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\x23\x42\x33\x42\x33\x42\x33\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x31\x37\ +\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\ +\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\ +\x20\x20\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\ +\x6e\x6f\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x31\ +\x20\x7b\x66\x69\x6c\x6c\x3a\x23\x46\x46\x39\x39\x33\x33\x7d\x0d\ +\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\ +\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\ +\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\ +\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\ +\x64\x61\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\ +\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\ +\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\x78\x3d\x22\x2d\x30\ +\x22\x20\x79\x3d\x22\x2d\x30\x22\x20\x77\x69\x64\x74\x68\x3d\x22\ +\x32\x37\x30\x2e\x39\x33\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\ +\x32\x37\x30\x2e\x39\x33\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x61\ +\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\ +\x74\x72\x30\x22\x20\x64\x3d\x22\x4d\x31\x33\x35\x2e\x34\x36\x20\ +\x31\x36\x30\x2e\x38\x36\x6c\x30\x20\x2d\x35\x30\x2e\x37\x39\x6d\ +\x32\x35\x2e\x34\x20\x32\x35\x2e\x33\x39\x6c\x2d\x35\x30\x2e\x37\ +\x39\x20\x30\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x61\x74\x68\x20\ +\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x30\ +\x22\x20\x64\x3d\x22\x4d\x31\x33\x35\x2e\x34\x36\x20\x31\x36\x30\ +\x2e\x38\x36\x6c\x30\x20\x2d\x35\x30\x2e\x37\x39\x6d\x32\x35\x2e\ +\x34\x20\x32\x35\x2e\x33\x39\x6c\x2d\x35\x30\x2e\x37\x39\x20\x30\ +\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x65\x6c\x6c\x69\x70\x73\x65\x20\ +\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x31\ +\x22\x20\x63\x78\x3d\x22\x31\x33\x35\x2e\x34\x36\x22\x20\x63\x79\ +\x3d\x22\x31\x33\x35\x2e\x34\x36\x22\x20\x72\x78\x3d\x22\x31\x32\ +\x37\x22\x20\x72\x79\x3d\x22\x39\x33\x2e\x31\x33\x22\x2f\x3e\x0d\ +\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x69\x64\x3d\x22\ +\x5f\x31\x5f\x30\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\ +\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x32\x32\x30\x2e\x31\ +\x36\x2c\x38\x2e\x34\x33\x20\x32\x36\x32\x2e\x34\x39\x2c\x38\x2e\ +\x34\x33\x20\x32\x36\x32\x2e\x34\x39\x2c\x35\x30\x2e\x37\x36\x20\ +\x32\x32\x30\x2e\x31\x36\x2c\x35\x30\x2e\x37\x36\x20\x22\x2f\x3e\ +\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x69\x64\x3d\ +\x22\x5f\x31\x5f\x30\x5f\x30\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\ +\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x38\x2e\ +\x34\x33\x2c\x32\x32\x30\x2e\x31\x35\x20\x35\x30\x2e\x37\x36\x2c\ +\x32\x32\x30\x2e\x31\x35\x20\x35\x30\x2e\x37\x36\x2c\x32\x36\x32\ +\x2e\x34\x38\x20\x38\x2e\x34\x33\x2c\x32\x36\x32\x2e\x34\x38\x20\ +\x22\x2f\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\ +\x67\x3e\x0d\x0a\ +\x00\x00\x06\x55\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x31\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x67\x72\x61\x79\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\ +\x68\x3a\x31\x34\x2e\x31\x31\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\ +\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\ +\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\ +\x74\x72\x6f\x6b\x65\x3a\x23\x34\x36\x38\x32\x43\x38\x3b\x73\x74\ +\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x31\x36\x2e\x39\x33\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x63\x61\x70\x3a\ +\x73\x71\x75\x61\x72\x65\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\ +\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\ +\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\ +\x6c\x6c\x3a\x6e\x6f\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\ +\x69\x6c\x32\x20\x7b\x66\x69\x6c\x6c\x3a\x67\x72\x61\x79\x7d\x0d\ +\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\ +\x3a\x23\x46\x46\x39\x39\x33\x33\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\ +\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\ +\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\ +\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\ +\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\x69\ +\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\ +\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\ +\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\ +\x69\x6c\x30\x22\x20\x78\x3d\x22\x30\x22\x20\x79\x3d\x22\x30\x22\ +\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\x30\x2e\x39\x32\x22\x20\ +\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\x39\x32\x22\x2f\ +\x3e\x0d\x0a\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\ +\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x30\x22\x20\x64\x3d\x22\ +\x4d\x32\x34\x39\x2e\x36\x34\x20\x32\x35\x38\x2e\x31\x32\x63\x2d\ +\x34\x2e\x35\x2c\x2d\x31\x32\x38\x2e\x38\x37\x20\x2d\x31\x30\x37\ +\x2e\x39\x33\x2c\x2d\x32\x33\x32\x2e\x33\x20\x2d\x32\x33\x36\x2e\ +\x38\x2c\x2d\x32\x33\x36\x2e\x38\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\ +\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\ +\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\ +\x6f\x69\x6e\x74\x73\x3d\x22\x34\x32\x2e\x33\x33\x2c\x2d\x30\x2e\ +\x30\x31\x20\x34\x32\x2e\x33\x33\x2c\x34\x32\x2e\x33\x32\x20\x2d\ +\x30\x2c\x34\x32\x2e\x33\x32\x20\x2d\x30\x2c\x2d\x30\x2e\x30\x31\ +\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\ +\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\x5f\x30\x22\x20\x63\x6c\x61\ +\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\ +\x3d\x22\x32\x37\x30\x2e\x39\x32\x2c\x32\x32\x38\x2e\x36\x20\x32\ +\x37\x30\x2e\x39\x32\x2c\x32\x37\x30\x2e\x39\x33\x20\x32\x32\x38\ +\x2e\x35\x39\x2c\x32\x37\x30\x2e\x39\x33\x20\x32\x32\x38\x2e\x35\ +\x39\x2c\x32\x32\x38\x2e\x36\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\ +\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\ +\x5f\x31\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\ +\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x34\x32\x2e\x33\x32\x2c\x32\ +\x32\x38\x2e\x36\x20\x34\x32\x2e\x33\x32\x2c\x32\x37\x30\x2e\x39\ +\x33\x20\x2d\x30\x2e\x30\x31\x2c\x32\x37\x30\x2e\x39\x33\x20\x2d\ +\x30\x2e\x30\x31\x2c\x32\x32\x38\x2e\x36\x20\x22\x2f\x3e\x0d\x0a\ +\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\ +\x3d\x22\x66\x69\x6c\x32\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\ +\x32\x32\x30\x2e\x31\x34\x2c\x32\x34\x39\x2e\x37\x36\x20\x32\x30\ +\x31\x2e\x38\x31\x2c\x32\x33\x39\x2e\x31\x38\x20\x31\x38\x33\x2e\ +\x34\x38\x2c\x32\x32\x38\x2e\x36\x20\x31\x38\x33\x2e\x34\x38\x2c\ +\x32\x34\x39\x2e\x37\x36\x20\x31\x38\x33\x2e\x34\x38\x2c\x32\x37\ +\x30\x2e\x39\x33\x20\x32\x30\x31\x2e\x38\x31\x2c\x32\x36\x30\x2e\ +\x33\x35\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x6c\x69\x6e\x65\x20\ +\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x31\ +\x22\x20\x78\x31\x3d\x22\x32\x30\x30\x2e\x34\x22\x20\x79\x31\x3d\ +\x22\x32\x34\x39\x2e\x37\x36\x22\x20\x78\x32\x3d\x22\x35\x30\x2e\ +\x37\x39\x22\x20\x79\x32\x3d\x20\x22\x32\x34\x39\x2e\x37\x36\x22\ +\x20\x2f\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\ +\x67\x3e\x0d\x0a\ +\x00\x00\x07\xee\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x67\x72\x61\x79\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\ +\x68\x3a\x31\x34\x2e\x31\x31\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6c\ +\x69\x6e\x65\x6a\x6f\x69\x6e\x3a\x72\x6f\x75\x6e\x64\x3b\x73\x74\ +\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\ +\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x73\ +\x74\x72\x32\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\x23\x34\x36\x38\ +\x32\x43\x38\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\ +\x3a\x31\x37\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\ +\x6e\x65\x6a\x6f\x69\x6e\x3a\x72\x6f\x75\x6e\x64\x3b\x73\x74\x72\ +\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\ +\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x73\x74\ +\x72\x33\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\x23\x42\x33\x42\x33\ +\x42\x33\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\ +\x31\x37\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\ +\x65\x6a\x6f\x69\x6e\x3a\x72\x6f\x75\x6e\x64\x3b\x73\x74\x72\x6f\ +\x6b\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\ +\x2e\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x73\x74\x72\ +\x31\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\x23\x39\x39\x39\x39\x39\ +\x39\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x37\ +\x2e\x36\x32\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x63\ +\x61\x70\x3a\x72\x6f\x75\x6e\x64\x3b\x73\x74\x72\x6f\x6b\x65\x2d\ +\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3a\x72\x6f\x75\x6e\x64\x3b\x73\ +\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\ +\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\ +\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\x6e\x65\x7d\ +\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\x69\x6c\ +\x6c\x3a\x67\x72\x61\x79\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\ +\x6c\x32\x20\x7b\x66\x69\x6c\x6c\x3a\x23\x46\x46\x39\x39\x33\x33\ +\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\ +\x74\x79\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0d\ +\x0a\x20\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\x64\x61\x5f\ +\x78\x30\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\ +\x74\x61\x64\x61\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\x72\x65\x6c\ +\x43\x6f\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\x2d\x4c\x61\ +\x79\x65\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\x65\x63\x74\x20\ +\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\x78\x3d\x22\ +\x2d\x30\x2e\x30\x31\x22\x20\x79\x3d\x22\x2d\x30\x22\x20\x77\x69\ +\x64\x74\x68\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x20\x68\x65\x69\ +\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x2f\x3e\x0d\x0a\ +\x20\x20\x3c\x6c\x69\x6e\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\ +\x69\x6c\x30\x20\x73\x74\x72\x30\x22\x20\x78\x31\x3d\x22\x31\x34\ +\x34\x2e\x32\x22\x20\x79\x31\x3d\x22\x35\x30\x2e\x38\x33\x22\x20\ +\x78\x32\x3d\x22\x32\x32\x30\x2e\x33\x39\x22\x20\x79\x32\x3d\x20\ +\x22\x31\x32\x37\x2e\x30\x33\x22\x20\x2f\x3e\x0d\x0a\x20\x20\x3c\ +\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\ +\x69\x6c\x31\x20\x73\x74\x72\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\ +\x3d\x22\x31\x31\x38\x2e\x32\x2c\x32\x34\x2e\x33\x34\x20\x31\x32\ +\x33\x2e\x38\x37\x2c\x34\x35\x2e\x35\x31\x20\x31\x32\x39\x2e\x35\ +\x34\x2c\x36\x36\x2e\x36\x37\x20\x31\x34\x35\x2e\x30\x34\x2c\x35\ +\x31\x2e\x31\x38\x20\x31\x36\x30\x2e\x35\x33\x2c\x33\x35\x2e\x36\ +\x38\x20\x31\x33\x39\x2e\x33\x37\x2c\x33\x30\x2e\x30\x31\x20\x22\ +\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\ +\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x20\x73\x74\x72\x31\x22\ +\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x32\x34\x35\x2e\x37\x39\x2c\ +\x31\x35\x33\x2e\x35\x31\x20\x32\x34\x30\x2e\x31\x32\x2c\x31\x33\ +\x32\x2e\x33\x34\x20\x32\x33\x34\x2e\x34\x35\x2c\x31\x31\x31\x2e\ +\x31\x38\x20\x32\x31\x38\x2e\x39\x35\x2c\x31\x32\x36\x2e\x36\x37\ +\x20\x32\x30\x33\x2e\x34\x36\x2c\x31\x34\x32\x2e\x31\x37\x20\x32\ +\x32\x34\x2e\x36\x32\x2c\x31\x34\x37\x2e\x38\x34\x20\x22\x2f\x3e\ +\x0d\x0a\x20\x20\x3c\x6c\x69\x6e\x65\x20\x63\x6c\x61\x73\x73\x3d\ +\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x32\x22\x20\x78\x31\x3d\x22\ +\x32\x36\x32\x2e\x34\x36\x22\x20\x79\x31\x3d\x22\x32\x36\x32\x2e\ +\x34\x37\x22\x20\x78\x32\x3d\x22\x38\x2e\x34\x35\x22\x20\x79\x32\ +\x3d\x20\x22\x38\x2e\x34\x35\x22\x20\x2f\x3e\x0d\x0a\x20\x20\x3c\ +\x6c\x69\x6e\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\ +\x20\x73\x74\x72\x33\x22\x20\x78\x31\x3d\x22\x31\x32\x2e\x37\x35\ +\x22\x20\x79\x31\x3d\x22\x31\x31\x34\x2e\x33\x34\x22\x20\x78\x32\ +\x3d\x22\x31\x31\x38\x2e\x36\x33\x22\x20\x79\x32\x3d\x20\x22\x38\ +\x2e\x35\x33\x22\x20\x2f\x3e\x0d\x0a\x20\x20\x3c\x6c\x69\x6e\x65\ +\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\ +\x33\x22\x20\x78\x31\x3d\x22\x31\x35\x36\x2e\x35\x38\x22\x20\x79\ +\x31\x3d\x22\x32\x35\x38\x2e\x31\x37\x22\x20\x78\x32\x3d\x22\x32\ +\x36\x32\x2e\x33\x39\x22\x20\x79\x32\x3d\x20\x22\x31\x35\x32\x2e\ +\x32\x39\x22\x20\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\ +\x6f\x6e\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\x22\x20\x63\x6c\x61\ +\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\x20\x70\x6f\x69\x6e\x74\x73\ +\x3d\x22\x34\x32\x2e\x33\x38\x2c\x38\x34\x2e\x37\x32\x20\x38\x34\ +\x2e\x37\x31\x2c\x38\x34\x2e\x37\x32\x20\x38\x34\x2e\x37\x31\x2c\ +\x34\x32\x2e\x33\x39\x20\x34\x32\x2e\x33\x38\x2c\x34\x32\x2e\x33\ +\x39\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\ +\x6e\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\x5f\x30\x22\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\x20\x70\x6f\x69\x6e\x74\ +\x73\x3d\x22\x31\x38\x36\x2e\x32\x2c\x32\x32\x38\x2e\x35\x34\x20\ +\x32\x32\x38\x2e\x35\x33\x2c\x32\x32\x38\x2e\x35\x34\x20\x32\x32\ +\x38\x2e\x35\x33\x2c\x31\x38\x36\x2e\x32\x31\x20\x31\x38\x36\x2e\ +\x32\x2c\x31\x38\x36\x2e\x32\x31\x20\x22\x2f\x3e\x0d\x0a\x20\x3c\ +\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x05\x68\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x34\x36\x38\x32\x43\x38\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x37\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3a\x72\x6f\x75\x6e\x64\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\ +\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\ +\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\x6e\ +\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\ +\x69\x6c\x6c\x3a\x23\x46\x46\x39\x39\x33\x33\x7d\x0d\x0a\x20\x20\ +\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\x6c\x65\x3e\ +\x0d\x0a\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\x3c\x67\x20\ +\x69\x64\x3d\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\x30\x32\x30\ +\x5f\x31\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\ +\x61\x20\x69\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\x72\x70\x49\ +\x44\x5f\x30\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\x72\x22\x2f\ +\x3e\x0d\x0a\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\ +\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x30\x22\x20\x64\x3d\x22\ +\x4d\x31\x31\x30\x2e\x30\x36\x20\x36\x37\x2e\x37\x33\x6c\x2d\x32\ +\x35\x2e\x34\x20\x30\x63\x2d\x33\x37\x2e\x33\x2c\x30\x20\x2d\x36\ +\x37\x2e\x36\x2c\x33\x30\x2e\x33\x20\x2d\x36\x37\x2e\x36\x2c\x36\ +\x37\x2e\x36\x38\x20\x30\x2c\x33\x37\x2e\x33\x38\x20\x33\x30\x2e\ +\x33\x2c\x36\x37\x2e\x37\x39\x20\x36\x37\x2e\x36\x2c\x36\x37\x2e\ +\x37\x39\x6c\x31\x36\x30\x2e\x38\x36\x20\x30\x22\x2f\x3e\x0d\x0a\ +\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\ +\x69\x6c\x30\x22\x20\x78\x3d\x22\x2d\x30\x2e\x30\x31\x22\x20\x79\ +\x3d\x22\x2d\x30\x2e\x30\x33\x22\x20\x77\x69\x64\x74\x68\x3d\x22\ +\x32\x37\x30\x2e\x39\x35\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\ +\x32\x37\x30\x2e\x39\x35\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\ +\x6c\x79\x67\x6f\x6e\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\x22\x20\ +\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\ +\x6e\x74\x73\x3d\x22\x32\x32\x34\x2e\x32\x38\x2c\x31\x38\x31\x2e\ +\x39\x37\x20\x32\x36\x36\x2e\x36\x31\x2c\x31\x38\x31\x2e\x39\x37\ +\x20\x32\x36\x36\x2e\x36\x31\x2c\x32\x32\x34\x2e\x33\x20\x32\x32\ +\x34\x2e\x32\x38\x2c\x32\x32\x34\x2e\x33\x20\x22\x2f\x3e\x0d\x0a\ +\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x69\x64\x3d\x22\x5f\ +\x31\x5f\x30\x5f\x30\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\ +\x6c\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x38\x38\x2e\x39\ +\x32\x2c\x31\x38\x31\x2e\x39\x37\x20\x31\x33\x31\x2e\x32\x35\x2c\ +\x31\x38\x31\x2e\x39\x37\x20\x31\x33\x31\x2e\x32\x35\x2c\x32\x32\ +\x34\x2e\x33\x20\x38\x38\x2e\x39\x32\x2c\x32\x32\x34\x2e\x33\x20\ +\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\ +\x69\x64\x3d\x22\x5f\x31\x5f\x30\x5f\x31\x22\x20\x63\x6c\x61\x73\ +\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\ +\x22\x38\x38\x2e\x39\x32\x2c\x34\x36\x2e\x36\x31\x20\x31\x33\x31\ +\x2e\x32\x35\x2c\x34\x36\x2e\x36\x31\x20\x31\x33\x31\x2e\x32\x35\ +\x2c\x38\x38\x2e\x39\x34\x20\x38\x38\x2e\x39\x32\x2c\x38\x38\x2e\ +\x39\x34\x20\x22\x2f\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\ +\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x07\x3c\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\ +\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x34\x20\x7b\ +\x66\x69\x6c\x6c\x3a\x23\x30\x30\x39\x39\x33\x33\x7d\x0d\x0a\x20\ +\x20\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x23\ +\x36\x36\x36\x36\x36\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\ +\x6c\x33\x20\x7b\x66\x69\x6c\x6c\x3a\x77\x68\x69\x74\x65\x7d\x0d\ +\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x32\x20\x7b\x66\x69\x6c\x6c\ +\x3a\x23\x36\x36\x36\x36\x36\x36\x3b\x66\x69\x6c\x6c\x2d\x72\x75\ +\x6c\x65\x3a\x6e\x6f\x6e\x7a\x65\x72\x6f\x7d\x0d\x0a\x20\x20\x20\ +\x5d\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\ +\x0a\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\ +\x64\x3d\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\ +\x31\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\ +\x20\x69\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\ +\x5f\x30\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\ +\x0d\x0a\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\ +\x22\x66\x69\x6c\x30\x22\x20\x78\x3d\x22\x2d\x30\x2e\x30\x31\x22\ +\x20\x79\x3d\x22\x2d\x30\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\ +\x37\x30\x2e\x39\x33\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\ +\x37\x30\x2e\x39\x33\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x67\x3e\x0d\ +\x0a\x20\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\ +\x22\x66\x69\x6c\x31\x22\x20\x64\x3d\x22\x4d\x31\x30\x39\x2e\x38\ +\x20\x36\x37\x2e\x37\x35\x63\x2d\x32\x35\x2e\x37\x36\x2c\x30\x20\ +\x2d\x34\x39\x2e\x30\x36\x2c\x31\x30\x2e\x34\x31\x20\x2d\x36\x35\ +\x2e\x38\x35\x2c\x32\x37\x2e\x32\x31\x6c\x2d\x30\x2e\x30\x37\x20\ +\x30\x2e\x30\x37\x63\x2d\x31\x36\x2e\x38\x2c\x31\x36\x2e\x37\x39\ +\x20\x2d\x32\x37\x2e\x32\x31\x2c\x34\x30\x2e\x30\x39\x20\x2d\x32\ +\x37\x2e\x32\x31\x2c\x36\x35\x2e\x38\x35\x20\x30\x2c\x32\x35\x2e\ +\x37\x36\x20\x31\x30\x2e\x34\x31\x2c\x34\x39\x2e\x30\x36\x20\x32\ +\x37\x2e\x32\x31\x2c\x36\x35\x2e\x38\x35\x6c\x30\x2e\x30\x37\x20\ +\x30\x2e\x30\x37\x63\x31\x36\x2e\x37\x39\x2c\x31\x36\x2e\x38\x20\ +\x34\x30\x2e\x30\x39\x2c\x32\x37\x2e\x32\x31\x20\x36\x35\x2e\x38\ +\x35\x2c\x32\x37\x2e\x32\x31\x20\x32\x35\x2e\x37\x36\x2c\x30\x20\ +\x34\x39\x2e\x30\x36\x2c\x2d\x31\x30\x2e\x34\x31\x20\x36\x35\x2e\ +\x38\x35\x2c\x2d\x32\x37\x2e\x32\x31\x6c\x30\x2e\x30\x37\x20\x2d\ +\x30\x2e\x30\x37\x63\x31\x36\x2e\x38\x2c\x2d\x31\x36\x2e\x37\x39\ +\x20\x32\x37\x2e\x32\x31\x2c\x2d\x34\x30\x2e\x30\x39\x20\x32\x37\ +\x2e\x32\x31\x2c\x2d\x36\x35\x2e\x38\x35\x20\x30\x2c\x2d\x32\x35\ +\x2e\x37\x36\x20\x2d\x31\x30\x2e\x34\x31\x2c\x2d\x34\x39\x2e\x30\ +\x36\x20\x2d\x32\x37\x2e\x32\x31\x2c\x2d\x36\x35\x2e\x38\x35\x6c\ +\x2d\x30\x2e\x30\x37\x20\x2d\x30\x2e\x30\x37\x63\x2d\x31\x36\x2e\ +\x37\x39\x2c\x2d\x31\x36\x2e\x38\x20\x2d\x34\x30\x2e\x30\x39\x2c\ +\x2d\x32\x37\x2e\x32\x31\x20\x2d\x36\x35\x2e\x38\x35\x2c\x2d\x32\ +\x37\x2e\x32\x31\x7a\x6d\x30\x20\x2d\x31\x36\x2e\x39\x33\x63\x2d\ +\x36\x30\x2e\x37\x38\x2c\x30\x20\x2d\x31\x31\x30\x2e\x30\x36\x2c\ +\x34\x39\x2e\x32\x38\x20\x2d\x31\x31\x30\x2e\x30\x36\x2c\x31\x31\ +\x30\x2e\x30\x36\x20\x30\x2c\x36\x30\x2e\x37\x38\x20\x34\x39\x2e\ +\x32\x38\x2c\x31\x31\x30\x2e\x30\x36\x20\x31\x31\x30\x2e\x30\x36\ +\x2c\x31\x31\x30\x2e\x30\x36\x20\x36\x30\x2e\x37\x38\x2c\x30\x20\ +\x31\x31\x30\x2e\x30\x36\x2c\x2d\x34\x39\x2e\x32\x38\x20\x31\x31\ +\x30\x2e\x30\x36\x2c\x2d\x31\x31\x30\x2e\x30\x36\x20\x30\x2c\x2d\ +\x36\x30\x2e\x37\x38\x20\x2d\x34\x39\x2e\x32\x38\x2c\x2d\x31\x31\ +\x30\x2e\x30\x36\x20\x2d\x31\x31\x30\x2e\x30\x36\x2c\x2d\x31\x31\ +\x30\x2e\x30\x36\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\x3e\ +\x0d\x0a\x20\x20\x3c\x67\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x6f\x6c\ +\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\ +\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x31\x30\x39\x2e\x34\x2c\ +\x30\x2e\x34\x34\x20\x32\x37\x30\x2e\x32\x32\x2c\x31\x36\x31\x2e\ +\x32\x38\x20\x32\x35\x38\x2e\x32\x34\x2c\x31\x37\x33\x2e\x32\x36\ +\x20\x39\x37\x2e\x34\x32\x2c\x31\x32\x2e\x34\x32\x20\x22\x2f\x3e\ +\x0d\x0a\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\x20\x20\x3c\x67\x3e\x0d\ +\x0a\x20\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\ +\x73\x73\x3d\x22\x66\x69\x6c\x33\x22\x20\x70\x6f\x69\x6e\x74\x73\ +\x3d\x22\x31\x35\x34\x2e\x33\x31\x2c\x31\x31\x36\x2e\x36\x33\x20\ +\x32\x31\x33\x2e\x35\x38\x2c\x31\x31\x36\x2e\x36\x33\x20\x32\x31\ +\x33\x2e\x35\x38\x2c\x35\x37\x2e\x33\x36\x20\x31\x35\x34\x2e\x33\ +\x31\x2c\x35\x37\x2e\x33\x36\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x20\ +\x3c\x70\x61\x74\x68\x20\x69\x64\x3d\x22\x5f\x31\x22\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x34\x22\x20\x64\x3d\x22\x4d\x31\ +\x36\x36\x2e\x36\x36\x20\x31\x30\x34\x2e\x32\x38\x6c\x33\x34\x2e\ +\x35\x37\x20\x30\x20\x30\x20\x2d\x33\x34\x2e\x35\x37\x20\x2d\x33\ +\x34\x2e\x35\x37\x20\x30\x20\x30\x20\x33\x34\x2e\x35\x37\x7a\x6d\ +\x2d\x31\x32\x2e\x33\x35\x20\x31\x32\x2e\x33\x35\x6c\x35\x39\x2e\ +\x32\x37\x20\x30\x20\x30\x20\x2d\x35\x39\x2e\x32\x37\x20\x2d\x35\ +\x39\x2e\x32\x37\x20\x30\x20\x30\x20\x35\x39\x2e\x32\x37\x7a\x22\ +\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\x20\x3c\x2f\x67\ +\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x11\x62\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\ +\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x33\x20\x7b\ +\x66\x69\x6c\x6c\x3a\x23\x46\x46\x39\x39\x33\x33\x7d\x0d\x0a\x20\ +\x20\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x23\ +\x34\x36\x38\x32\x43\x38\x3b\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\ +\x3a\x6e\x6f\x6e\x7a\x65\x72\x6f\x7d\x0d\x0a\x20\x20\x20\x20\x2e\ +\x66\x69\x6c\x32\x20\x7b\x66\x69\x6c\x6c\x3a\x23\x39\x39\x39\x39\ +\x39\x39\x3b\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x6e\x6f\x6e\ +\x7a\x65\x72\x6f\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\ +\x20\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\ +\x61\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\ +\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\x69\x64\x3d\x22\x43\ +\x6f\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\ +\x6c\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\ +\x65\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\ +\x20\x78\x3d\x22\x2d\x30\x2e\x30\x31\x22\x20\x79\x3d\x22\x2d\x30\ +\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\ +\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\ +\x2f\x3e\x0d\x0a\x20\x20\x3c\x67\x3e\x0d\x0a\x20\x20\x20\x3c\x70\ +\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\ +\x20\x64\x3d\x22\x4d\x31\x30\x37\x2e\x39\x32\x20\x32\x32\x31\x2e\ +\x35\x32\x6c\x31\x35\x36\x2e\x36\x32\x20\x30\x2e\x30\x32\x20\x30\ +\x20\x31\x34\x2e\x31\x20\x2d\x31\x35\x36\x2e\x36\x32\x20\x2d\x30\ +\x2e\x30\x32\x20\x30\x20\x2d\x31\x34\x2e\x31\x7a\x6d\x2d\x31\x30\ +\x30\x2e\x31\x38\x20\x2d\x38\x36\x2e\x30\x36\x6c\x31\x34\x2e\x31\ +\x32\x20\x30\x20\x30\x20\x30\x20\x30\x2e\x31\x31\x20\x34\x2e\x34\ +\x33\x20\x30\x2e\x33\x33\x20\x34\x2e\x33\x38\x20\x30\x2e\x35\x34\ +\x20\x34\x2e\x33\x31\x20\x30\x2e\x37\x36\x20\x34\x2e\x32\x34\x20\ +\x30\x2e\x39\x36\x20\x34\x2e\x31\x35\x20\x31\x2e\x31\x37\x20\x34\ +\x2e\x30\x39\x20\x31\x2e\x33\x35\x20\x34\x20\x31\x2e\x35\x34\x20\ +\x33\x2e\x39\x20\x31\x2e\x37\x32\x20\x33\x2e\x38\x31\x20\x31\x2e\ +\x39\x20\x33\x2e\x37\x31\x20\x32\x2e\x30\x37\x20\x33\x2e\x36\x20\ +\x32\x2e\x32\x35\x20\x33\x2e\x35\x20\x32\x2e\x33\x39\x20\x33\x2e\ +\x33\x38\x20\x32\x2e\x35\x36\x20\x33\x2e\x32\x35\x20\x32\x2e\x37\ +\x20\x33\x2e\x31\x31\x20\x32\x2e\x38\x36\x20\x33\x20\x32\x2e\x39\ +\x39\x20\x32\x2e\x38\x35\x20\x33\x2e\x31\x32\x20\x32\x2e\x37\x31\ +\x20\x33\x2e\x32\x35\x20\x32\x2e\x35\x35\x20\x33\x2e\x33\x38\x20\ +\x32\x2e\x34\x20\x33\x2e\x34\x39\x20\x32\x2e\x32\x34\x20\x33\x2e\ +\x36\x31\x20\x32\x2e\x30\x37\x20\x33\x2e\x37\x20\x31\x2e\x39\x20\ +\x33\x2e\x38\x31\x20\x31\x2e\x37\x32\x20\x33\x2e\x39\x32\x20\x31\ +\x2e\x35\x34\x20\x33\x2e\x39\x39\x20\x31\x2e\x33\x35\x20\x34\x2e\ +\x30\x37\x20\x31\x2e\x31\x37\x20\x34\x2e\x31\x37\x20\x30\x2e\x39\ +\x35\x20\x34\x2e\x32\x33\x20\x30\x2e\x37\x36\x20\x34\x2e\x33\x31\ +\x20\x30\x2e\x35\x34\x20\x34\x2e\x33\x38\x20\x30\x2e\x33\x33\x20\ +\x34\x2e\x34\x33\x20\x30\x2e\x31\x32\x20\x30\x20\x31\x34\x2e\x31\ +\x20\x2d\x35\x2e\x31\x35\x20\x2d\x30\x2e\x31\x32\x20\x2d\x35\x2e\ +\x30\x38\x20\x2d\x30\x2e\x33\x39\x20\x2d\x35\x2e\x30\x31\x20\x2d\ +\x30\x2e\x36\x34\x20\x2d\x34\x2e\x39\x33\x20\x2d\x30\x2e\x38\x38\ +\x20\x2d\x34\x2e\x38\x35\x20\x2d\x31\x2e\x31\x31\x20\x2d\x34\x2e\ +\x37\x37\x20\x2d\x31\x2e\x33\x35\x20\x2d\x34\x2e\x36\x35\x20\x2d\ +\x31\x2e\x35\x37\x20\x2d\x34\x2e\x35\x34\x20\x2d\x31\x2e\x38\x20\ +\x2d\x34\x2e\x34\x35\x20\x2d\x32\x20\x2d\x34\x2e\x33\x32\x20\x2d\ +\x32\x2e\x32\x32\x20\x2d\x34\x2e\x31\x39\x20\x2d\x32\x2e\x34\x31\ +\x20\x2d\x34\x2e\x30\x37\x20\x2d\x32\x2e\x36\x20\x2d\x33\x2e\x39\ +\x32\x20\x2d\x32\x2e\x38\x20\x2d\x33\x2e\x37\x39\x20\x2d\x32\x2e\ +\x39\x37\x20\x2d\x33\x2e\x36\x34\x20\x2d\x33\x2e\x31\x35\x20\x2d\ +\x33\x2e\x34\x37\x20\x2d\x33\x2e\x33\x31\x20\x2d\x33\x2e\x33\x32\ +\x20\x2d\x33\x2e\x34\x38\x20\x2d\x33\x2e\x31\x34\x20\x2d\x33\x2e\ +\x36\x33\x20\x2d\x32\x2e\x39\x38\x20\x2d\x33\x2e\x37\x39\x20\x2d\ +\x32\x2e\x37\x39\x20\x2d\x33\x2e\x39\x32\x20\x2d\x32\x2e\x36\x31\ +\x20\x2d\x34\x2e\x30\x36\x20\x2d\x32\x2e\x34\x31\x20\x2d\x34\x2e\ +\x32\x20\x2d\x32\x2e\x32\x32\x20\x2d\x34\x2e\x33\x33\x20\x2d\x32\ +\x20\x2d\x34\x2e\x34\x33\x20\x2d\x31\x2e\x38\x20\x2d\x34\x2e\x35\ +\x36\x20\x2d\x31\x2e\x35\x37\x20\x2d\x34\x2e\x36\x36\x20\x2d\x31\ +\x2e\x33\x35\x20\x2d\x34\x2e\x37\x35\x20\x2d\x31\x2e\x31\x32\x20\ +\x2d\x34\x2e\x38\x35\x20\x2d\x30\x2e\x38\x38\x20\x2d\x34\x2e\x39\ +\x34\x20\x2d\x30\x2e\x36\x34\x20\x2d\x35\x2e\x30\x31\x20\x2d\x30\ +\x2e\x33\x39\x20\x2d\x35\x2e\x30\x38\x20\x2d\x30\x2e\x31\x33\x20\ +\x2d\x35\x2e\x31\x35\x20\x30\x20\x30\x7a\x6d\x31\x30\x30\x2e\x32\ +\x20\x2d\x31\x30\x30\x2e\x32\x6c\x30\x20\x31\x34\x2e\x31\x32\x20\ +\x30\x20\x30\x20\x2d\x34\x2e\x34\x33\x20\x30\x2e\x31\x31\x20\x2d\ +\x34\x2e\x33\x38\x20\x30\x2e\x33\x33\x20\x2d\x34\x2e\x33\x31\x20\ +\x30\x2e\x35\x34\x20\x2d\x34\x2e\x32\x34\x20\x30\x2e\x37\x36\x20\ +\x2d\x34\x2e\x31\x35\x20\x30\x2e\x39\x36\x20\x2d\x34\x2e\x30\x39\ +\x20\x31\x2e\x31\x37\x20\x2d\x34\x20\x31\x2e\x33\x35\x20\x2d\x33\ +\x2e\x39\x31\x20\x31\x2e\x35\x34\x20\x2d\x33\x2e\x38\x31\x20\x31\ +\x2e\x37\x32\x20\x2d\x33\x2e\x37\x20\x31\x2e\x39\x20\x2d\x33\x2e\ +\x36\x20\x32\x2e\x30\x37\x20\x2d\x33\x2e\x35\x20\x32\x2e\x32\x35\ +\x20\x2d\x33\x2e\x33\x38\x20\x32\x2e\x33\x39\x20\x2d\x33\x2e\x32\ +\x35\x20\x32\x2e\x35\x36\x20\x2d\x33\x2e\x31\x33\x20\x32\x2e\x37\ +\x31\x20\x2d\x33\x20\x32\x2e\x38\x35\x20\x2d\x32\x2e\x38\x34\x20\ +\x32\x2e\x39\x39\x20\x2d\x32\x2e\x37\x31\x20\x33\x2e\x31\x32\x20\ +\x2d\x32\x2e\x35\x35\x20\x33\x2e\x32\x35\x20\x2d\x32\x2e\x34\x20\ +\x33\x2e\x33\x38\x20\x2d\x32\x2e\x32\x34\x20\x33\x2e\x35\x20\x2d\ +\x32\x2e\x30\x38\x20\x33\x2e\x36\x20\x2d\x31\x2e\x38\x39\x20\x33\ +\x2e\x37\x31\x20\x2d\x31\x2e\x37\x33\x20\x33\x2e\x38\x32\x20\x2d\ +\x31\x2e\x35\x34\x20\x33\x2e\x39\x31\x20\x2d\x31\x2e\x33\x35\x20\ +\x33\x2e\x39\x39\x20\x2d\x31\x2e\x31\x37\x20\x34\x2e\x30\x38\x20\ +\x2d\x30\x2e\x39\x36\x20\x34\x2e\x31\x36\x20\x2d\x30\x2e\x37\x36\ +\x20\x34\x2e\x32\x34\x20\x2d\x30\x2e\x35\x34\x20\x34\x2e\x33\x31\ +\x20\x2d\x30\x2e\x33\x33\x20\x34\x2e\x33\x38\x20\x2d\x30\x2e\x31\ +\x31\x20\x34\x2e\x34\x33\x20\x2d\x31\x34\x2e\x31\x32\x20\x30\x20\ +\x30\x2e\x31\x33\x20\x2d\x35\x2e\x31\x35\x20\x30\x2e\x33\x39\x20\ +\x2d\x35\x2e\x30\x38\x20\x30\x2e\x36\x34\x20\x2d\x35\x2e\x30\x31\ +\x20\x30\x2e\x38\x38\x20\x2d\x34\x2e\x39\x34\x20\x31\x2e\x31\x32\ +\x20\x2d\x34\x2e\x38\x36\x20\x31\x2e\x33\x35\x20\x2d\x34\x2e\x37\ +\x36\x20\x31\x2e\x35\x37\x20\x2d\x34\x2e\x36\x35\x20\x31\x2e\x38\ +\x20\x2d\x34\x2e\x35\x35\x20\x32\x2e\x30\x31\x20\x2d\x34\x2e\x34\ +\x34\x20\x32\x2e\x32\x31\x20\x2d\x34\x2e\x33\x33\x20\x32\x2e\x34\ +\x32\x20\x2d\x34\x2e\x32\x20\x32\x2e\x36\x20\x2d\x34\x2e\x30\x36\ +\x20\x32\x2e\x38\x20\x2d\x33\x2e\x39\x32\x20\x32\x2e\x39\x37\x20\ +\x2d\x33\x2e\x37\x39\x20\x33\x2e\x31\x35\x20\x2d\x33\x2e\x36\x34\ +\x20\x33\x2e\x33\x32\x20\x2d\x33\x2e\x34\x37\x20\x33\x2e\x34\x38\ +\x20\x2d\x33\x2e\x33\x31\x20\x33\x2e\x36\x33\x20\x2d\x33\x2e\x31\ +\x35\x20\x33\x2e\x37\x39\x20\x2d\x32\x2e\x39\x38\x20\x33\x2e\x39\ +\x32\x20\x2d\x32\x2e\x37\x39\x20\x34\x2e\x30\x36\x20\x2d\x32\x2e\ +\x36\x31\x20\x34\x2e\x32\x20\x2d\x32\x2e\x34\x31\x20\x34\x2e\x33\ +\x32\x20\x2d\x32\x2e\x32\x32\x20\x34\x2e\x34\x35\x20\x2d\x32\x20\ +\x34\x2e\x35\x35\x20\x2d\x31\x2e\x38\x20\x34\x2e\x36\x36\x20\x2d\ +\x31\x2e\x35\x37\x20\x34\x2e\x37\x35\x20\x2d\x31\x2e\x33\x35\x20\ +\x34\x2e\x38\x35\x20\x2d\x31\x2e\x31\x32\x20\x34\x2e\x39\x34\x20\ +\x2d\x30\x2e\x38\x38\x20\x35\x2e\x30\x31\x20\x2d\x30\x2e\x36\x34\ +\x20\x35\x2e\x30\x38\x20\x2d\x30\x2e\x33\x39\x20\x35\x2e\x31\x35\ +\x20\x2d\x30\x2e\x31\x33\x20\x30\x20\x30\x7a\x6d\x38\x34\x2e\x36\ +\x35\x20\x31\x34\x2e\x31\x32\x6c\x2d\x38\x34\x2e\x36\x35\x20\x30\ +\x20\x30\x20\x2d\x31\x34\x2e\x31\x32\x20\x38\x34\x2e\x36\x35\x20\ +\x30\x20\x37\x2e\x30\x36\x20\x37\x2e\x30\x36\x20\x2d\x37\x2e\x30\ +\x36\x20\x37\x2e\x30\x36\x7a\x6d\x30\x20\x2d\x31\x34\x2e\x31\x32\ +\x6c\x37\x2e\x30\x36\x20\x30\x20\x30\x20\x37\x2e\x30\x36\x20\x2d\ +\x37\x2e\x30\x36\x20\x2d\x37\x2e\x30\x36\x7a\x6d\x2d\x37\x2e\x30\ +\x36\x20\x31\x30\x30\x2e\x31\x39\x6c\x30\x20\x2d\x39\x33\x2e\x31\ +\x33\x20\x31\x34\x2e\x31\x32\x20\x30\x20\x30\x20\x39\x33\x2e\x31\ +\x33\x20\x2d\x31\x34\x2e\x31\x32\x20\x30\x7a\x22\x2f\x3e\x0d\x0a\ +\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\x20\x20\x3c\x67\x3e\x0d\x0a\x20\ +\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\ +\x69\x6c\x32\x22\x20\x64\x3d\x22\x4d\x31\x30\x37\x2e\x39\x33\x20\ +\x31\x38\x34\x2e\x32\x33\x6c\x31\x35\x36\x2e\x36\x33\x20\x30\x2e\ +\x30\x35\x20\x30\x20\x31\x34\x2e\x31\x20\x2d\x31\x35\x36\x2e\x36\ +\x33\x20\x2d\x30\x2e\x30\x35\x20\x30\x20\x2d\x31\x34\x2e\x31\x31\ +\x20\x30\x20\x30\x2e\x30\x31\x7a\x6d\x2d\x36\x32\x2e\x39\x31\x20\ +\x2d\x34\x38\x2e\x38\x6c\x31\x34\x2e\x31\x32\x20\x30\x20\x30\x20\ +\x30\x20\x30\x2e\x30\x36\x20\x32\x2e\x35\x31\x20\x30\x2e\x31\x39\ +\x20\x32\x2e\x34\x39\x20\x30\x2e\x33\x31\x20\x32\x2e\x34\x35\x20\ +\x30\x2e\x34\x32\x20\x32\x2e\x33\x39\x20\x30\x2e\x35\x35\x20\x32\ +\x2e\x33\x36\x20\x30\x2e\x36\x36\x20\x32\x2e\x33\x32\x20\x30\x2e\ +\x37\x36\x20\x32\x2e\x32\x36\x20\x30\x2e\x38\x38\x20\x32\x2e\x32\ +\x31\x20\x30\x2e\x39\x38\x20\x32\x2e\x31\x37\x20\x31\x2e\x30\x37\ +\x20\x32\x2e\x31\x20\x31\x2e\x31\x37\x20\x32\x2e\x30\x34\x20\x31\ +\x2e\x32\x38\x20\x31\x2e\x39\x38\x20\x31\x2e\x33\x35\x20\x31\x2e\ +\x39\x32\x20\x31\x2e\x34\x34\x20\x31\x2e\x38\x33\x20\x31\x2e\x35\ +\x34\x20\x31\x2e\x37\x37\x20\x31\x2e\x36\x33\x20\x31\x2e\x37\x20\ +\x31\x2e\x37\x20\x31\x2e\x36\x33\x20\x31\x2e\x37\x37\x20\x31\x2e\ +\x35\x34\x20\x31\x2e\x38\x33\x20\x31\x2e\x34\x34\x20\x31\x2e\x39\ +\x32\x20\x31\x2e\x33\x35\x20\x31\x2e\x39\x38\x20\x31\x2e\x32\x38\ +\x20\x32\x2e\x30\x34\x20\x31\x2e\x31\x37\x20\x32\x2e\x31\x20\x31\ +\x2e\x30\x37\x20\x32\x2e\x31\x37\x20\x30\x2e\x39\x38\x20\x32\x2e\ +\x32\x31\x20\x30\x2e\x38\x38\x20\x32\x2e\x32\x36\x20\x30\x2e\x37\ +\x36\x20\x32\x2e\x33\x32\x20\x30\x2e\x36\x36\x20\x32\x2e\x33\x36\ +\x20\x30\x2e\x35\x35\x20\x32\x2e\x33\x39\x20\x30\x2e\x34\x32\x20\ +\x32\x2e\x34\x35\x20\x30\x2e\x33\x31\x20\x32\x2e\x34\x39\x20\x30\ +\x2e\x31\x39\x20\x32\x2e\x35\x31\x20\x30\x2e\x30\x36\x20\x30\x20\ +\x31\x34\x2e\x31\x32\x20\x2d\x33\x2e\x32\x33\x20\x2d\x30\x2e\x30\ +\x38\x20\x2d\x33\x2e\x31\x39\x20\x2d\x30\x2e\x32\x35\x20\x2d\x33\ +\x2e\x31\x35\x20\x2d\x30\x2e\x33\x39\x20\x2d\x33\x2e\x31\x31\x20\ +\x2d\x30\x2e\x35\x36\x20\x2d\x33\x2e\x30\x34\x20\x2d\x30\x2e\x37\ +\x31\x20\x2d\x32\x2e\x39\x38\x20\x2d\x30\x2e\x38\x34\x20\x2d\x32\ +\x2e\x39\x34\x20\x2d\x31\x20\x2d\x32\x2e\x38\x35\x20\x2d\x31\x2e\ +\x31\x32\x20\x2d\x32\x2e\x37\x39\x20\x2d\x31\x2e\x32\x36\x20\x2d\ +\x32\x2e\x37\x20\x2d\x31\x2e\x33\x39\x20\x2d\x32\x2e\x36\x34\x20\ +\x2d\x31\x2e\x35\x31\x20\x2d\x32\x2e\x35\x36\x20\x2d\x31\x2e\x36\ +\x34\x20\x2d\x32\x2e\x34\x36\x20\x2d\x31\x2e\x37\x35\x20\x2d\x32\ +\x2e\x33\x39\x20\x2d\x31\x2e\x38\x38\x20\x2d\x32\x2e\x32\x37\x20\ +\x2d\x31\x2e\x39\x38\x20\x2d\x32\x2e\x31\x38\x20\x2d\x32\x2e\x30\ +\x37\x20\x2d\x32\x2e\x30\x37\x20\x2d\x32\x2e\x31\x38\x20\x2d\x31\ +\x2e\x39\x38\x20\x2d\x32\x2e\x32\x37\x20\x2d\x31\x2e\x38\x38\x20\ +\x2d\x32\x2e\x33\x39\x20\x2d\x31\x2e\x37\x35\x20\x2d\x32\x2e\x34\ +\x36\x20\x2d\x31\x2e\x36\x34\x20\x2d\x32\x2e\x35\x36\x20\x2d\x31\ +\x2e\x35\x31\x20\x2d\x32\x2e\x36\x34\x20\x2d\x31\x2e\x33\x39\x20\ +\x2d\x32\x2e\x37\x20\x2d\x31\x2e\x32\x36\x20\x2d\x32\x2e\x37\x39\ +\x20\x2d\x31\x2e\x31\x32\x20\x2d\x32\x2e\x38\x35\x20\x2d\x31\x20\ +\x2d\x32\x2e\x39\x34\x20\x2d\x30\x2e\x38\x34\x20\x2d\x32\x2e\x39\ +\x38\x20\x2d\x30\x2e\x37\x31\x20\x2d\x33\x2e\x30\x34\x20\x2d\x30\ +\x2e\x35\x36\x20\x2d\x33\x2e\x31\x31\x20\x2d\x30\x2e\x33\x39\x20\ +\x2d\x33\x2e\x31\x35\x20\x2d\x30\x2e\x32\x35\x20\x2d\x33\x2e\x31\ +\x39\x20\x2d\x30\x2e\x30\x38\x20\x2d\x33\x2e\x32\x33\x20\x30\x20\ +\x30\x7a\x6d\x36\x32\x2e\x39\x31\x20\x2d\x36\x32\x2e\x39\x33\x6c\ +\x30\x20\x31\x34\x2e\x31\x32\x20\x30\x20\x30\x20\x2d\x32\x2e\x35\ +\x31\x20\x30\x2e\x30\x36\x20\x2d\x32\x2e\x34\x39\x20\x30\x2e\x31\ +\x39\x20\x2d\x32\x2e\x34\x35\x20\x30\x2e\x33\x31\x20\x2d\x32\x2e\ +\x33\x39\x20\x30\x2e\x34\x32\x20\x2d\x32\x2e\x33\x36\x20\x30\x2e\ +\x35\x35\x20\x2d\x32\x2e\x33\x32\x20\x30\x2e\x36\x36\x20\x2d\x32\ +\x2e\x32\x36\x20\x30\x2e\x37\x36\x20\x2d\x32\x2e\x32\x31\x20\x30\ +\x2e\x38\x38\x20\x2d\x32\x2e\x31\x37\x20\x30\x2e\x39\x38\x20\x2d\ +\x32\x2e\x30\x39\x20\x31\x2e\x30\x37\x20\x2d\x32\x2e\x30\x35\x20\ +\x31\x2e\x31\x38\x20\x2d\x31\x2e\x39\x38\x20\x31\x2e\x32\x37\x20\ +\x2d\x31\x2e\x39\x31\x20\x31\x2e\x33\x36\x20\x2d\x31\x2e\x38\x35\ +\x20\x31\x2e\x34\x35\x20\x2d\x31\x2e\x37\x36\x20\x31\x2e\x35\x33\ +\x20\x2d\x31\x2e\x37\x20\x31\x2e\x36\x32\x20\x2d\x31\x2e\x36\x32\ +\x20\x31\x2e\x37\x20\x2d\x31\x2e\x35\x34\x20\x31\x2e\x37\x37\x20\ +\x2d\x31\x2e\x34\x35\x20\x31\x2e\x38\x34\x20\x2d\x31\x2e\x33\x35\ +\x20\x31\x2e\x39\x32\x20\x2d\x31\x2e\x32\x38\x20\x31\x2e\x39\x38\ +\x20\x2d\x31\x2e\x31\x37\x20\x32\x2e\x30\x34\x20\x2d\x31\x2e\x30\ +\x37\x20\x32\x2e\x31\x31\x20\x2d\x30\x2e\x39\x38\x20\x32\x2e\x31\ +\x36\x20\x2d\x30\x2e\x38\x38\x20\x32\x2e\x32\x32\x20\x2d\x30\x2e\ +\x37\x36\x20\x32\x2e\x32\x35\x20\x2d\x30\x2e\x36\x36\x20\x32\x2e\ +\x33\x33\x20\x2d\x30\x2e\x35\x35\x20\x32\x2e\x33\x36\x20\x2d\x30\ +\x2e\x34\x32\x20\x32\x2e\x33\x39\x20\x2d\x30\x2e\x33\x31\x20\x32\ +\x2e\x34\x35\x20\x2d\x30\x2e\x31\x39\x20\x32\x2e\x34\x38\x20\x2d\ +\x30\x2e\x30\x36\x20\x32\x2e\x35\x32\x20\x2d\x31\x34\x2e\x31\x32\ +\x20\x30\x20\x30\x2e\x30\x38\x20\x2d\x33\x2e\x32\x34\x20\x30\x2e\ +\x32\x35\x20\x2d\x33\x2e\x31\x38\x20\x30\x2e\x33\x39\x20\x2d\x33\ +\x2e\x31\x35\x20\x30\x2e\x35\x36\x20\x2d\x33\x2e\x31\x31\x20\x30\ +\x2e\x37\x31\x20\x2d\x33\x2e\x30\x34\x20\x30\x2e\x38\x34\x20\x2d\ +\x32\x2e\x39\x39\x20\x31\x20\x2d\x32\x2e\x39\x33\x20\x31\x2e\x31\ +\x32\x20\x2d\x32\x2e\x38\x36\x20\x31\x2e\x32\x36\x20\x2d\x32\x2e\ +\x37\x38\x20\x31\x2e\x33\x39\x20\x2d\x32\x2e\x37\x31\x20\x31\x2e\ +\x35\x31\x20\x2d\x32\x2e\x36\x34\x20\x31\x2e\x36\x34\x20\x2d\x32\ +\x2e\x35\x36\x20\x31\x2e\x37\x35\x20\x2d\x32\x2e\x34\x36\x20\x31\ +\x2e\x38\x37\x20\x2d\x32\x2e\x33\x38\x20\x31\x2e\x39\x38\x20\x2d\ +\x32\x2e\x32\x39\x20\x32\x2e\x30\x38\x20\x2d\x32\x2e\x31\x38\x20\ +\x32\x2e\x31\x38\x20\x2d\x32\x2e\x30\x38\x20\x32\x2e\x32\x38\x20\ +\x2d\x31\x2e\x39\x37\x20\x32\x2e\x33\x37\x20\x2d\x31\x2e\x38\x37\ +\x20\x32\x2e\x34\x37\x20\x2d\x31\x2e\x37\x36\x20\x32\x2e\x35\x36\ +\x20\x2d\x31\x2e\x36\x33\x20\x32\x2e\x36\x33\x20\x2d\x31\x2e\x35\ +\x32\x20\x32\x2e\x37\x31\x20\x2d\x31\x2e\x33\x39\x20\x32\x2e\x37\ +\x39\x20\x2d\x31\x2e\x32\x36\x20\x32\x2e\x38\x35\x20\x2d\x31\x2e\ +\x31\x32\x20\x32\x2e\x39\x34\x20\x2d\x31\x20\x32\x2e\x39\x38\x20\ +\x2d\x30\x2e\x38\x34\x20\x33\x2e\x30\x34\x20\x2d\x30\x2e\x37\x31\ +\x20\x33\x2e\x31\x31\x20\x2d\x30\x2e\x35\x36\x20\x33\x2e\x31\x35\ +\x20\x2d\x30\x2e\x33\x39\x20\x33\x2e\x31\x39\x20\x2d\x30\x2e\x32\ +\x35\x20\x33\x2e\x32\x33\x20\x2d\x30\x2e\x30\x38\x20\x30\x20\x30\ +\x7a\x6d\x34\x37\x2e\x34\x33\x20\x31\x34\x2e\x31\x32\x6c\x2d\x34\ +\x37\x2e\x34\x33\x20\x30\x20\x30\x20\x2d\x31\x34\x2e\x31\x32\x20\ +\x34\x37\x2e\x34\x33\x20\x30\x20\x37\x2e\x30\x36\x20\x37\x2e\x30\ +\x36\x20\x2d\x37\x2e\x30\x36\x20\x37\x2e\x30\x36\x7a\x6d\x30\x20\ +\x2d\x31\x34\x2e\x31\x32\x6c\x37\x2e\x30\x36\x20\x30\x20\x30\x20\ +\x37\x2e\x30\x36\x20\x2d\x37\x2e\x30\x36\x20\x2d\x37\x2e\x30\x36\ +\x7a\x6d\x2d\x37\x2e\x30\x36\x20\x36\x32\x2e\x39\x35\x6c\x30\x20\ +\x2d\x35\x35\x2e\x38\x39\x20\x31\x34\x2e\x31\x32\x20\x30\x20\x30\ +\x20\x35\x35\x2e\x38\x39\x20\x2d\x31\x34\x2e\x31\x32\x20\x30\x7a\ +\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\x20\x20\x3c\ +\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\ +\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x33\x22\x20\x70\ +\x6f\x69\x6e\x74\x73\x3d\x22\x32\x31\x35\x2e\x38\x31\x2c\x36\x37\ +\x2e\x37\x36\x20\x32\x35\x38\x2e\x31\x34\x2c\x36\x37\x2e\x37\x36\ +\x20\x32\x35\x38\x2e\x31\x34\x2c\x31\x31\x30\x2e\x30\x39\x20\x32\ +\x31\x35\x2e\x38\x31\x2c\x31\x31\x30\x2e\x30\x39\x20\x22\x2f\x3e\ +\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\ +\x0a\ +\x00\x00\x05\xec\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x31\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x34\x36\x38\x32\x43\x38\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x36\x2e\x39\x33\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\ +\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x73\x74\x72\x30\ +\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\x67\x72\x61\x79\x3b\x73\x74\ +\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x31\x34\x2e\x31\x31\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\ +\x3a\x72\x6f\x75\x6e\x64\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\ +\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\ +\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\ +\x6c\x6c\x3a\x6e\x6f\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\ +\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x67\x72\x61\x79\x7d\x0d\ +\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x32\x20\x7b\x66\x69\x6c\x6c\ +\x3a\x23\x46\x46\x39\x39\x33\x33\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\ +\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\ +\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\ +\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\ +\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\x69\ +\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\ +\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\ +\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\ +\x69\x6c\x30\x22\x20\x78\x3d\x22\x2d\x30\x2e\x30\x31\x22\x20\x79\ +\x3d\x22\x2d\x30\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\x30\ +\x2e\x39\x33\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\x30\ +\x2e\x39\x33\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\ +\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\ +\x70\x6f\x69\x6e\x74\x73\x3d\x22\x32\x31\x35\x2e\x39\x2c\x32\x31\ +\x35\x2e\x39\x20\x31\x39\x35\x2e\x34\x36\x2c\x32\x31\x30\x2e\x34\ +\x33\x20\x31\x37\x35\x2e\x30\x32\x2c\x32\x30\x34\x2e\x39\x35\x20\ +\x31\x38\x39\x2e\x39\x38\x2c\x31\x38\x39\x2e\x39\x38\x20\x32\x30\ +\x34\x2e\x39\x35\x2c\x31\x37\x35\x2e\x30\x32\x20\x32\x31\x30\x2e\ +\x34\x33\x2c\x31\x39\x35\x2e\x34\x36\x20\x22\x2f\x3e\x0d\x0a\x20\ +\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\ +\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x35\ +\x35\x2e\x30\x32\x2c\x35\x35\x2e\x30\x33\x20\x37\x35\x2e\x34\x36\ +\x2c\x36\x30\x2e\x35\x20\x39\x35\x2e\x39\x2c\x36\x35\x2e\x39\x38\ +\x20\x38\x30\x2e\x39\x34\x2c\x38\x30\x2e\x39\x35\x20\x36\x35\x2e\ +\x39\x37\x2c\x39\x35\x2e\x39\x31\x20\x36\x30\x2e\x34\x39\x2c\x37\ +\x35\x2e\x34\x37\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x6c\x69\x6e\ +\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\ +\x72\x30\x22\x20\x78\x31\x3d\x22\x32\x30\x33\x2e\x31\x37\x22\x20\ +\x79\x31\x3d\x22\x32\x30\x33\x2e\x31\x38\x22\x20\x78\x32\x3d\x22\ +\x36\x37\x2e\x37\x33\x22\x20\x79\x32\x3d\x20\x22\x36\x37\x2e\x37\ +\x34\x22\x20\x2f\x3e\x0d\x0a\x20\x20\x3c\x63\x69\x72\x63\x6c\x65\ +\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\ +\x31\x22\x20\x63\x78\x3d\x22\x31\x33\x35\x2e\x34\x36\x22\x20\x63\ +\x79\x3d\x22\x31\x33\x35\x2e\x34\x36\x22\x20\x72\x3d\x22\x31\x32\ +\x32\x2e\x37\x36\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\ +\x67\x6f\x6e\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\x22\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\x20\x70\x6f\x69\x6e\x74\ +\x73\x3d\x22\x31\x31\x34\x2e\x33\x2c\x31\x35\x36\x2e\x36\x33\x20\ +\x31\x35\x36\x2e\x36\x33\x2c\x31\x35\x36\x2e\x36\x33\x20\x31\x35\ +\x36\x2e\x36\x33\x2c\x31\x31\x34\x2e\x33\x20\x31\x31\x34\x2e\x33\ +\x2c\x31\x31\x34\x2e\x33\x20\x22\x2f\x3e\x0d\x0a\x20\x3c\x2f\x67\ +\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x07\x08\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x67\x72\x61\x79\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\ +\x68\x3a\x31\x34\x2e\x31\x31\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\ +\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\ +\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x73\x74\x72\x31\x20\x7b\x73\ +\x74\x72\x6f\x6b\x65\x3a\x23\x34\x36\x38\x32\x43\x38\x3b\x73\x74\ +\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x31\x36\x2e\x39\x33\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x63\x61\x70\x3a\ +\x73\x71\x75\x61\x72\x65\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\ +\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\ +\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\ +\x6c\x6c\x3a\x6e\x6f\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\ +\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x67\x72\x61\x79\x7d\x0d\ +\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x32\x20\x7b\x66\x69\x6c\x6c\ +\x3a\x23\x46\x46\x39\x39\x33\x33\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\ +\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\ +\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\ +\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\ +\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\x69\ +\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\ +\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\ +\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\ +\x69\x6c\x30\x22\x20\x78\x3d\x22\x30\x22\x20\x79\x3d\x22\x30\x22\ +\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\x30\x2e\x39\x32\x22\x20\ +\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\x39\x32\x22\x2f\ +\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\x74\ +\x73\x3d\x22\x32\x31\x2e\x31\x34\x2c\x35\x30\x2e\x37\x39\x20\x33\ +\x31\x2e\x37\x32\x2c\x36\x39\x2e\x31\x32\x20\x34\x32\x2e\x33\x2c\ +\x38\x37\x2e\x34\x35\x20\x32\x31\x2e\x31\x34\x2c\x38\x37\x2e\x34\ +\x35\x20\x2d\x30\x2e\x30\x33\x2c\x38\x37\x2e\x34\x35\x20\x31\x30\ +\x2e\x35\x35\x2c\x36\x39\x2e\x31\x32\x20\x22\x2f\x3e\x0d\x0a\x20\ +\x20\x3c\x6c\x69\x6e\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\ +\x6c\x30\x20\x73\x74\x72\x30\x22\x20\x78\x31\x3d\x22\x32\x31\x2e\ +\x31\x34\x22\x20\x79\x31\x3d\x22\x37\x30\x2e\x35\x33\x22\x20\x78\ +\x32\x3d\x22\x32\x31\x2e\x31\x34\x22\x20\x79\x32\x3d\x20\x22\x32\ +\x32\x30\x2e\x31\x34\x22\x20\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x61\ +\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\ +\x74\x72\x31\x22\x20\x64\x3d\x22\x4d\x32\x34\x39\x2e\x36\x34\x20\ +\x32\x35\x38\x2e\x31\x32\x63\x2d\x34\x2e\x35\x2c\x2d\x31\x32\x38\ +\x2e\x38\x37\x20\x2d\x31\x30\x37\x2e\x39\x33\x2c\x2d\x32\x33\x32\ +\x2e\x33\x20\x2d\x32\x33\x36\x2e\x38\x2c\x2d\x32\x33\x36\x2e\x38\ +\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\ +\x69\x64\x3d\x22\x5f\x31\x5f\x30\x22\x20\x63\x6c\x61\x73\x73\x3d\ +\x22\x66\x69\x6c\x32\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x34\ +\x32\x2e\x33\x33\x2c\x2d\x30\x2e\x30\x31\x20\x34\x32\x2e\x33\x33\ +\x2c\x34\x32\x2e\x33\x32\x20\x2d\x30\x2c\x34\x32\x2e\x33\x32\x20\ +\x2d\x30\x2c\x2d\x30\x2e\x30\x31\x20\x22\x2f\x3e\x0d\x0a\x20\x20\ +\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x69\x64\x3d\x22\x5f\x31\x5f\ +\x30\x5f\x30\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\ +\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x34\x32\x2e\x33\x32\x2c\ +\x32\x32\x38\x2e\x36\x20\x34\x32\x2e\x33\x32\x2c\x32\x37\x30\x2e\ +\x39\x33\x20\x2d\x30\x2e\x30\x31\x2c\x32\x37\x30\x2e\x39\x33\x20\ +\x2d\x30\x2e\x30\x31\x2c\x32\x32\x38\x2e\x36\x20\x22\x2f\x3e\x0d\ +\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x69\x64\x3d\x22\ +\x5f\x31\x5f\x30\x5f\x31\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\ +\x69\x6c\x32\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x32\x37\x30\ +\x2e\x39\x32\x2c\x32\x32\x38\x2e\x36\x20\x32\x37\x30\x2e\x39\x32\ +\x2c\x32\x37\x30\x2e\x39\x33\x20\x32\x32\x38\x2e\x35\x39\x2c\x32\ +\x37\x30\x2e\x39\x33\x20\x32\x32\x38\x2e\x35\x39\x2c\x32\x32\x38\ +\x2e\x36\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\ +\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\ +\x70\x6f\x69\x6e\x74\x73\x3d\x22\x35\x30\x2e\x37\x38\x2c\x32\x34\ +\x39\x2e\x37\x38\x20\x36\x39\x2e\x31\x31\x2c\x32\x33\x39\x2e\x32\ +\x20\x38\x37\x2e\x34\x34\x2c\x32\x32\x38\x2e\x36\x32\x20\x38\x37\ +\x2e\x34\x34\x2c\x32\x34\x39\x2e\x37\x38\x20\x38\x37\x2e\x34\x34\ +\x2c\x32\x37\x30\x2e\x39\x35\x20\x36\x39\x2e\x31\x31\x2c\x32\x36\ +\x30\x2e\x33\x37\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x6c\x69\x6e\ +\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\ +\x72\x30\x22\x20\x78\x31\x3d\x22\x37\x30\x2e\x35\x32\x22\x20\x79\ +\x31\x3d\x22\x32\x34\x39\x2e\x37\x38\x22\x20\x78\x32\x3d\x22\x32\ +\x32\x30\x2e\x31\x33\x22\x20\x79\x32\x3d\x20\x22\x32\x34\x39\x2e\ +\x37\x38\x22\x20\x2f\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\ +\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x0b\xe4\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\ +\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x32\x20\x7b\ +\x66\x69\x6c\x6c\x3a\x23\x36\x36\x36\x36\x36\x36\x7d\x0d\x0a\x20\ +\x20\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x77\ +\x68\x69\x74\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x34\ +\x20\x7b\x66\x69\x6c\x6c\x3a\x75\x72\x6c\x28\x23\x69\x64\x30\x29\ +\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x33\x20\x7b\x66\x69\ +\x6c\x6c\x3a\x75\x72\x6c\x28\x23\x69\x64\x31\x29\x7d\x0d\x0a\x20\ +\x20\x20\x20\x2e\x66\x69\x6c\x35\x20\x7b\x66\x69\x6c\x6c\x3a\x75\ +\x72\x6c\x28\x23\x69\x64\x32\x29\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\ +\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\ +\x20\x3c\x6c\x69\x6e\x65\x61\x72\x47\x72\x61\x64\x69\x65\x6e\x74\ +\x20\x69\x64\x3d\x22\x69\x64\x30\x22\x20\x67\x72\x61\x64\x69\x65\ +\x6e\x74\x55\x6e\x69\x74\x73\x3d\x22\x75\x73\x65\x72\x53\x70\x61\ +\x63\x65\x4f\x6e\x55\x73\x65\x22\x20\x78\x31\x3d\x22\x32\x31\x37\ +\x2e\x37\x34\x22\x20\x79\x31\x3d\x22\x31\x37\x2e\x36\x33\x22\x20\ +\x78\x32\x3d\x22\x32\x35\x33\x2e\x32\x39\x22\x20\x79\x32\x3d\x22\ +\x35\x33\x2e\x31\x38\x22\x3e\x0d\x0a\x20\x20\x20\x3c\x73\x74\x6f\ +\x70\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\x30\x22\x20\x73\x74\x79\ +\x6c\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\ +\x3a\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\ +\x32\x34\x32\x39\x32\x45\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x73\ +\x74\x6f\x70\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\x30\x2e\x33\x32\ +\x31\x35\x36\x39\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x74\x6f\ +\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\x20\x73\x74\x6f\ +\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x42\x30\x42\x46\x42\x46\x22\ +\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x73\x74\x6f\x70\x20\x6f\x66\x66\ +\x73\x65\x74\x3d\x22\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\ +\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\x20\x73\ +\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x32\x34\x32\x39\x32\ +\x45\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x6c\x69\x6e\x65\x61\x72\ +\x47\x72\x61\x64\x69\x65\x6e\x74\x3e\x0d\x0a\x20\x20\x3c\x6c\x69\ +\x6e\x65\x61\x72\x47\x72\x61\x64\x69\x65\x6e\x74\x20\x69\x64\x3d\ +\x22\x69\x64\x31\x22\x20\x67\x72\x61\x64\x69\x65\x6e\x74\x55\x6e\ +\x69\x74\x73\x3d\x22\x75\x73\x65\x72\x53\x70\x61\x63\x65\x4f\x6e\ +\x55\x73\x65\x22\x20\x78\x31\x3d\x22\x31\x37\x39\x2e\x32\x31\x22\ +\x20\x79\x31\x3d\x22\x35\x36\x2e\x31\x35\x22\x20\x78\x32\x3d\x22\ +\x32\x31\x34\x2e\x37\x37\x22\x20\x79\x32\x3d\x22\x39\x31\x2e\x37\ +\x31\x22\x3e\x0d\x0a\x20\x20\x20\x3c\x73\x74\x6f\x70\x20\x6f\x66\ +\x66\x73\x65\x74\x3d\x22\x30\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\ +\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\x20\ +\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x46\x41\x39\x45\ +\x30\x44\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x73\x74\x6f\x70\x20\ +\x6f\x66\x66\x73\x65\x74\x3d\x22\x30\x2e\x33\x32\x31\x35\x36\x39\ +\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\ +\x61\x63\x69\x74\x79\x3a\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\ +\x6c\x6f\x72\x3a\x23\x46\x45\x46\x45\x46\x45\x22\x2f\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x73\x74\x6f\x70\x20\x6f\x66\x66\x73\x65\x74\x3d\ +\x22\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x74\x6f\x70\x2d\ +\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\x20\x73\x74\x6f\x70\x2d\ +\x63\x6f\x6c\x6f\x72\x3a\x23\x44\x39\x37\x33\x30\x30\x22\x2f\x3e\ +\x0d\x0a\x20\x20\x3c\x2f\x6c\x69\x6e\x65\x61\x72\x47\x72\x61\x64\ +\x69\x65\x6e\x74\x3e\x0d\x0a\x20\x20\x3c\x6c\x69\x6e\x65\x61\x72\ +\x47\x72\x61\x64\x69\x65\x6e\x74\x20\x69\x64\x3d\x22\x69\x64\x32\ +\x22\x20\x67\x72\x61\x64\x69\x65\x6e\x74\x55\x6e\x69\x74\x73\x3d\ +\x22\x75\x73\x65\x72\x53\x70\x61\x63\x65\x4f\x6e\x55\x73\x65\x22\ +\x20\x78\x31\x3d\x22\x31\x34\x32\x2e\x31\x37\x22\x20\x79\x31\x3d\ +\x22\x31\x30\x35\x2e\x30\x34\x22\x20\x78\x32\x3d\x22\x31\x38\x33\ +\x2e\x36\x36\x22\x20\x79\x32\x3d\x22\x31\x32\x32\x2e\x38\x32\x22\ +\x3e\x0d\x0a\x20\x20\x20\x3c\x73\x74\x6f\x70\x20\x6f\x66\x66\x73\ +\x65\x74\x3d\x22\x30\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x74\ +\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\x20\x73\x74\ +\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x33\x38\x32\x42\x32\x36\ +\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x73\x74\x6f\x70\x20\x6f\x66\ +\x66\x73\x65\x74\x3d\x22\x30\x2e\x33\x32\x31\x35\x36\x39\x22\x20\ +\x73\x74\x79\x6c\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\ +\x69\x74\x79\x3a\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\ +\x72\x3a\x23\x45\x33\x45\x33\x44\x45\x22\x2f\x3e\x0d\x0a\x20\x20\ +\x20\x3c\x73\x74\x6f\x70\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\x31\ +\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\ +\x61\x63\x69\x74\x79\x3a\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\ +\x6c\x6f\x72\x3a\x23\x33\x38\x32\x42\x32\x36\x22\x2f\x3e\x0d\x0a\ +\x20\x20\x3c\x2f\x6c\x69\x6e\x65\x61\x72\x47\x72\x61\x64\x69\x65\ +\x6e\x74\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\ +\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\ +\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\ +\x64\x61\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\ +\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\ +\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\x78\x3d\x22\x30\x2e\ +\x30\x31\x22\x20\x79\x3d\x22\x2d\x30\x2e\x30\x31\x22\x20\x77\x69\ +\x64\x74\x68\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x20\x68\x65\x69\ +\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x2f\x3e\x0d\x0a\ +\x20\x20\x3c\x67\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x6f\x6c\x79\x67\ +\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\ +\x70\x6f\x69\x6e\x74\x73\x3d\x22\x32\x35\x30\x2e\x30\x34\x2c\x32\ +\x36\x32\x2e\x36\x36\x20\x31\x31\x30\x2e\x33\x31\x2c\x32\x36\x32\ +\x2e\x36\x36\x20\x32\x30\x2e\x38\x35\x2c\x31\x35\x36\x2e\x33\x32\ +\x20\x31\x36\x30\x2e\x35\x38\x2c\x31\x35\x36\x2e\x33\x32\x20\x22\ +\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x61\x74\x68\x20\x69\x64\x3d\ +\x22\x5f\x31\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\ +\x22\x20\x64\x3d\x22\x4d\x32\x35\x30\x2e\x30\x34\x20\x32\x36\x32\ +\x2e\x36\x36\x6c\x2d\x31\x33\x39\x2e\x37\x33\x20\x30\x20\x2d\x38\ +\x39\x2e\x34\x36\x20\x2d\x31\x30\x36\x2e\x33\x34\x20\x31\x33\x39\ +\x2e\x37\x33\x20\x30\x20\x38\x39\x2e\x34\x36\x20\x31\x30\x36\x2e\ +\x33\x34\x7a\x6d\x2d\x31\x33\x34\x2e\x30\x36\x20\x2d\x31\x32\x2e\ +\x33\x35\x6c\x31\x30\x37\x2e\x36\x31\x20\x30\x20\x2d\x36\x38\x2e\ +\x36\x38\x20\x2d\x38\x31\x2e\x36\x34\x20\x2d\x31\x30\x37\x2e\x36\ +\x31\x20\x30\x20\x36\x38\x2e\x36\x38\x20\x38\x31\x2e\x36\x34\x7a\ +\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\x20\x20\x3c\ +\x67\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\ +\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\ +\x6e\x74\x73\x3d\x22\x32\x35\x30\x2e\x30\x34\x2c\x32\x31\x36\x2e\ +\x31\x33\x20\x31\x31\x30\x2e\x33\x31\x2c\x32\x31\x36\x2e\x31\x33\ +\x20\x32\x30\x2e\x38\x35\x2c\x31\x30\x39\x2e\x37\x39\x20\x31\x36\ +\x30\x2e\x35\x38\x2c\x31\x30\x39\x2e\x37\x39\x20\x22\x2f\x3e\x0d\ +\x0a\x20\x20\x20\x3c\x70\x61\x74\x68\x20\x69\x64\x3d\x22\x5f\x31\ +\x5f\x30\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\ +\x20\x64\x3d\x22\x4d\x32\x35\x30\x2e\x30\x34\x20\x32\x31\x36\x2e\ +\x31\x33\x6c\x2d\x31\x33\x39\x2e\x37\x33\x20\x30\x20\x2d\x38\x39\ +\x2e\x34\x36\x20\x2d\x31\x30\x36\x2e\x33\x34\x20\x31\x33\x39\x2e\ +\x37\x33\x20\x30\x20\x38\x39\x2e\x34\x36\x20\x31\x30\x36\x2e\x33\ +\x34\x7a\x6d\x2d\x31\x33\x34\x2e\x30\x36\x20\x2d\x31\x32\x2e\x33\ +\x35\x6c\x31\x30\x37\x2e\x36\x31\x20\x30\x20\x2d\x36\x38\x2e\x36\ +\x38\x20\x2d\x38\x31\x2e\x36\x34\x20\x2d\x31\x30\x37\x2e\x36\x31\ +\x20\x30\x20\x36\x38\x2e\x36\x38\x20\x38\x31\x2e\x36\x34\x7a\x22\ +\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\x20\x20\x3c\x67\ +\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\ +\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\ +\x74\x73\x3d\x22\x32\x35\x30\x2e\x30\x34\x2c\x31\x36\x39\x2e\x36\ +\x20\x31\x31\x30\x2e\x33\x31\x2c\x31\x36\x39\x2e\x36\x20\x32\x30\ +\x2e\x38\x35\x2c\x36\x33\x2e\x32\x36\x20\x31\x36\x30\x2e\x35\x38\ +\x2c\x36\x33\x2e\x32\x36\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\ +\x70\x61\x74\x68\x20\x69\x64\x3d\x22\x5f\x31\x5f\x31\x22\x20\x63\ +\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\x20\x64\x3d\x22\x4d\ +\x32\x35\x30\x2e\x30\x34\x20\x31\x36\x39\x2e\x36\x6c\x2d\x31\x33\ +\x39\x2e\x37\x33\x20\x30\x20\x2d\x38\x39\x2e\x34\x36\x20\x2d\x31\ +\x30\x36\x2e\x33\x34\x20\x31\x33\x39\x2e\x37\x33\x20\x30\x20\x38\ +\x39\x2e\x34\x36\x20\x31\x30\x36\x2e\x33\x34\x7a\x6d\x2d\x31\x33\ +\x34\x2e\x30\x36\x20\x2d\x31\x32\x2e\x33\x35\x6c\x31\x30\x37\x2e\ +\x36\x31\x20\x30\x20\x2d\x36\x38\x2e\x36\x38\x20\x2d\x38\x31\x2e\ +\x36\x34\x20\x2d\x31\x30\x37\x2e\x36\x31\x20\x30\x20\x36\x38\x2e\ +\x36\x38\x20\x38\x31\x2e\x36\x34\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\ +\x3c\x2f\x67\x3e\x0d\x0a\x20\x20\x3c\x67\x3e\x0d\x0a\x20\x20\x20\ +\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\ +\x66\x69\x6c\x33\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x32\x33\ +\x39\x2e\x39\x36\x2c\x36\x36\x2e\x35\x32\x20\x31\x38\x39\x2e\x35\ +\x38\x2c\x31\x31\x36\x2e\x38\x39\x20\x31\x35\x34\x2e\x30\x33\x2c\ +\x38\x31\x2e\x33\x34\x20\x32\x30\x34\x2e\x34\x2c\x33\x30\x2e\x39\ +\x36\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x6f\x6c\x79\x67\ +\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x34\x22\x20\ +\x70\x6f\x69\x6e\x74\x73\x3d\x22\x32\x33\x31\x2e\x30\x37\x2c\x34\ +\x2e\x32\x39\x20\x32\x30\x34\x2e\x34\x2c\x33\x30\x2e\x39\x36\x20\ +\x32\x33\x39\x2e\x39\x36\x2c\x36\x36\x2e\x35\x32\x20\x32\x36\x36\ +\x2e\x36\x33\x2c\x33\x39\x2e\x38\x35\x20\x22\x2f\x3e\x0d\x0a\x20\ +\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\ +\x3d\x22\x66\x69\x6c\x35\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\ +\x31\x35\x34\x2e\x30\x32\x2c\x38\x31\x2e\x33\x35\x20\x31\x38\x39\ +\x2e\x35\x37\x2c\x31\x31\x36\x2e\x39\x20\x31\x34\x38\x2e\x31\x2c\ +\x31\x32\x32\x2e\x38\x32\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\ +\x67\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\ +\x3e\x0d\x0a\ +\x00\x00\x05\x0b\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x31\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x34\x36\x38\x32\x43\x38\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x36\x2e\x39\x33\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\ +\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x73\x74\x72\x30\ +\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\x23\x42\x33\x42\x33\x42\x33\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x31\x37\ +\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\ +\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\ +\x20\x20\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\ +\x6e\x6f\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x31\ +\x20\x7b\x66\x69\x6c\x6c\x3a\x23\x46\x46\x39\x39\x33\x33\x7d\x0d\ +\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\ +\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\ +\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\ +\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\ +\x64\x61\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\ +\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\ +\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x30\x22\x20\ +\x64\x3d\x22\x4d\x31\x33\x35\x2e\x34\x36\x20\x31\x36\x30\x2e\x38\ +\x36\x6c\x30\x20\x2d\x35\x30\x2e\x37\x39\x6d\x32\x35\x2e\x34\x20\ +\x32\x35\x2e\x33\x39\x6c\x2d\x35\x30\x2e\x37\x39\x20\x30\x22\x2f\ +\x3e\x0d\x0a\x20\x20\x3c\x65\x6c\x6c\x69\x70\x73\x65\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x31\x22\x20\ +\x63\x78\x3d\x22\x31\x33\x35\x2e\x34\x36\x22\x20\x63\x79\x3d\x22\ +\x31\x33\x35\x2e\x34\x36\x22\x20\x72\x78\x3d\x22\x31\x32\x37\x22\ +\x20\x72\x79\x3d\x22\x39\x33\x2e\x31\x33\x22\x2f\x3e\x0d\x0a\x20\ +\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x69\x64\x3d\x22\x5f\x31\ +\x5f\x30\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\ +\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x31\x31\x34\x2e\x32\x39\x2c\ +\x32\x30\x37\x2e\x33\x36\x20\x31\x35\x36\x2e\x36\x32\x2c\x32\x30\ +\x37\x2e\x33\x36\x20\x31\x35\x36\x2e\x36\x32\x2c\x32\x34\x39\x2e\ +\x36\x39\x20\x31\x31\x34\x2e\x32\x39\x2c\x32\x34\x39\x2e\x36\x39\ +\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\ +\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\x5f\x30\x22\x20\x63\x6c\x61\ +\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\ +\x3d\x22\x32\x32\x38\x2e\x35\x2c\x31\x31\x34\x2e\x33\x20\x32\x37\ +\x30\x2e\x38\x33\x2c\x31\x31\x34\x2e\x33\x20\x32\x37\x30\x2e\x38\ +\x33\x2c\x31\x35\x36\x2e\x36\x33\x20\x32\x32\x38\x2e\x35\x2c\x31\ +\x35\x36\x2e\x36\x33\x20\x22\x2f\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\ +\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x19\x7c\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\ +\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x34\x20\x7b\ +\x66\x69\x6c\x6c\x3a\x23\x34\x36\x38\x32\x43\x38\x7d\x0d\x0a\x20\ +\x20\x20\x20\x2e\x66\x69\x6c\x33\x20\x7b\x66\x69\x6c\x6c\x3a\x23\ +\x46\x46\x39\x39\x33\x33\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\ +\x6c\x32\x20\x7b\x66\x69\x6c\x6c\x3a\x77\x68\x69\x74\x65\x7d\x0d\ +\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\ +\x3a\x67\x72\x61\x79\x3b\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\ +\x6e\x6f\x6e\x7a\x65\x72\x6f\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\x3e\ +\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\x3c\ +\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\x22\ +\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\x3e\ +\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\x69\x64\ +\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\x43\ +\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\x20\ +\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\ +\x6c\x30\x22\x20\x78\x3d\x22\x2d\x30\x2e\x30\x31\x22\x20\x79\x3d\ +\x22\x2d\x30\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\x30\x2e\ +\x39\x33\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\ +\x39\x33\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x67\x3e\x0d\x0a\x20\x20\ +\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\ +\x6c\x31\x22\x20\x64\x3d\x22\x4d\x37\x30\x2e\x35\x34\x20\x39\x36\ +\x2e\x33\x32\x6c\x30\x20\x2d\x31\x37\x2e\x36\x34\x20\x30\x20\x30\ +\x20\x31\x2e\x32\x34\x20\x30\x2e\x30\x31\x20\x31\x2e\x32\x33\x20\ +\x30\x2e\x30\x35\x20\x31\x2e\x32\x32\x20\x30\x2e\x30\x37\x20\x31\ +\x2e\x32\x33\x20\x30\x2e\x31\x31\x20\x31\x2e\x32\x33\x20\x30\x2e\ +\x31\x34\x20\x31\x2e\x32\x20\x30\x2e\x31\x37\x20\x31\x2e\x31\x38\ +\x20\x30\x2e\x31\x39\x20\x31\x2e\x32\x31\x20\x30\x2e\x32\x32\x20\ +\x31\x2e\x31\x39\x20\x30\x2e\x32\x35\x20\x31\x2e\x32\x31\x20\x30\ +\x2e\x32\x39\x20\x31\x2e\x31\x39\x20\x30\x2e\x33\x32\x20\x31\x2e\ +\x31\x33\x20\x30\x2e\x33\x33\x20\x31\x2e\x31\x37\x20\x30\x2e\x33\ +\x37\x20\x31\x2e\x31\x33\x20\x30\x2e\x33\x39\x20\x31\x2e\x31\x32\ +\x20\x30\x2e\x34\x31\x20\x31\x2e\x31\x38\x20\x30\x2e\x34\x37\x20\ +\x31\x2e\x31\x20\x30\x2e\x34\x38\x20\x31\x2e\x30\x37\x20\x30\x2e\ +\x34\x39\x20\x31\x2e\x31\x33\x20\x30\x2e\x35\x35\x20\x31\x2e\x30\ +\x36\x20\x30\x2e\x35\x35\x20\x31\x2e\x30\x35\x20\x30\x2e\x35\x38\ +\x20\x31\x2e\x30\x36\x20\x30\x2e\x36\x31\x20\x31\x2e\x30\x33\x20\ +\x30\x2e\x36\x34\x20\x31\x2e\x30\x34\x20\x30\x2e\x36\x37\x20\x31\ +\x2e\x30\x31\x20\x30\x2e\x37\x20\x30\x2e\x39\x35\x20\x30\x2e\x36\ +\x38\x20\x30\x2e\x39\x38\x20\x30\x2e\x37\x33\x20\x30\x2e\x39\x38\ +\x20\x30\x2e\x37\x39\x20\x30\x2e\x39\x34\x20\x30\x2e\x37\x39\x20\ +\x30\x2e\x39\x32\x20\x30\x2e\x38\x32\x20\x30\x2e\x39\x20\x30\x2e\ +\x38\x33\x20\x30\x2e\x38\x38\x20\x30\x2e\x38\x36\x20\x2d\x31\x32\ +\x2e\x34\x36\x20\x31\x32\x2e\x34\x38\x20\x2d\x30\x2e\x35\x38\x20\ +\x2d\x30\x2e\x35\x36\x20\x2d\x30\x2e\x35\x38\x20\x2d\x30\x2e\x35\ +\x35\x20\x2d\x30\x2e\x36\x20\x2d\x30\x2e\x35\x32\x20\x2d\x30\x2e\ +\x36\x20\x2d\x30\x2e\x35\x31\x20\x2d\x30\x2e\x36\x20\x2d\x30\x2e\ +\x34\x37\x20\x2d\x30\x2e\x36\x32\x20\x2d\x30\x2e\x34\x37\x20\x2d\ +\x30\x2e\x36\x39\x20\x2d\x30\x2e\x35\x20\x2d\x30\x2e\x36\x33\x20\ +\x2d\x30\x2e\x34\x34\x20\x2d\x30\x2e\x36\x34\x20\x2d\x30\x2e\x34\ +\x31\x20\x2d\x30\x2e\x36\x39\x20\x2d\x30\x2e\x34\x32\x20\x2d\x30\ +\x2e\x36\x36\x20\x2d\x30\x2e\x33\x39\x20\x2d\x30\x2e\x37\x31\x20\ +\x2d\x30\x2e\x33\x38\x20\x2d\x30\x2e\x37\x20\x2d\x30\x2e\x33\x37\ +\x20\x2d\x30\x2e\x36\x37\x20\x2d\x30\x2e\x33\x33\x20\x2d\x30\x2e\ +\x37\x35\x20\x2d\x30\x2e\x33\x33\x20\x2d\x30\x2e\x37\x32\x20\x2d\ +\x30\x2e\x33\x32\x20\x2d\x30\x2e\x36\x38\x20\x2d\x30\x2e\x32\x37\ +\x20\x2d\x30\x2e\x37\x34\x20\x2d\x30\x2e\x32\x37\x20\x2d\x30\x2e\ +\x37\x37\x20\x2d\x30\x2e\x32\x37\x20\x2d\x30\x2e\x37\x33\x20\x2d\ +\x30\x2e\x32\x33\x20\x2d\x30\x2e\x37\x39\x20\x2d\x30\x2e\x32\x33\ +\x20\x2d\x30\x2e\x37\x33\x20\x2d\x30\x2e\x32\x20\x2d\x30\x2e\x37\ +\x35\x20\x2d\x30\x2e\x31\x37\x20\x2d\x30\x2e\x37\x37\x20\x2d\x30\ +\x2e\x31\x37\x20\x2d\x30\x2e\x37\x35\x20\x2d\x30\x2e\x31\x34\x20\ +\x2d\x30\x2e\x38\x32\x20\x2d\x30\x2e\x31\x33\x20\x2d\x30\x2e\x37\ +\x38\x20\x2d\x30\x2e\x31\x31\x20\x2d\x30\x2e\x37\x37\x20\x2d\x30\ +\x2e\x30\x38\x20\x2d\x30\x2e\x37\x39\x20\x2d\x30\x2e\x30\x37\x20\ +\x2d\x30\x2e\x38\x20\x2d\x30\x2e\x30\x35\x20\x2d\x30\x2e\x37\x39\ +\x20\x2d\x30\x2e\x30\x33\x20\x2d\x30\x2e\x38\x20\x2d\x30\x2e\x30\ +\x31\x20\x30\x20\x30\x7a\x6d\x30\x20\x30\x63\x2d\x34\x2e\x38\x37\ +\x2c\x30\x20\x2d\x38\x2e\x38\x32\x2c\x2d\x33\x2e\x39\x35\x20\x2d\ +\x38\x2e\x38\x32\x2c\x2d\x38\x2e\x38\x32\x20\x30\x2c\x2d\x34\x2e\ +\x38\x37\x20\x33\x2e\x39\x35\x2c\x2d\x38\x2e\x38\x32\x20\x38\x2e\ +\x38\x32\x2c\x2d\x38\x2e\x38\x32\x6c\x30\x20\x31\x37\x2e\x36\x34\ +\x7a\x6d\x2d\x33\x32\x2e\x30\x39\x20\x33\x32\x2e\x30\x37\x6c\x2d\ +\x31\x37\x2e\x36\x34\x20\x30\x20\x30\x2e\x30\x36\x20\x2d\x32\x2e\ +\x35\x34\x20\x30\x2e\x32\x20\x2d\x32\x2e\x35\x33\x20\x30\x2e\x33\ +\x31\x20\x2d\x32\x2e\x34\x39\x20\x30\x2e\x34\x34\x20\x2d\x32\x2e\ +\x34\x36\x20\x30\x2e\x35\x36\x20\x2d\x32\x2e\x34\x20\x30\x2e\x36\ +\x37\x20\x2d\x32\x2e\x33\x35\x20\x30\x2e\x37\x38\x20\x2d\x32\x2e\ +\x33\x33\x20\x30\x2e\x38\x39\x20\x2d\x32\x2e\x32\x35\x20\x30\x2e\ +\x39\x39\x20\x2d\x32\x2e\x32\x31\x20\x31\x2e\x31\x31\x20\x2d\x32\ +\x2e\x31\x35\x20\x31\x2e\x31\x39\x20\x2d\x32\x2e\x30\x37\x20\x31\ +\x2e\x33\x31\x20\x2d\x32\x2e\x30\x33\x20\x31\x2e\x33\x37\x20\x2d\ +\x31\x2e\x39\x32\x20\x31\x2e\x34\x38\x20\x2d\x31\x2e\x38\x39\x20\ +\x31\x2e\x35\x37\x20\x2d\x31\x2e\x38\x31\x20\x31\x2e\x36\x32\x20\ +\x2d\x31\x2e\x37\x31\x20\x31\x2e\x37\x34\x20\x2d\x31\x2e\x36\x36\ +\x20\x31\x2e\x38\x31\x20\x2d\x31\x2e\x35\x35\x20\x31\x2e\x38\x36\ +\x20\x2d\x31\x2e\x34\x38\x20\x31\x2e\x39\x38\x20\x2d\x31\x2e\x34\ +\x20\x32\x2e\x30\x31\x20\x2d\x31\x2e\x32\x39\x20\x32\x2e\x30\x37\ +\x20\x2d\x31\x2e\x31\x38\x20\x32\x2e\x31\x34\x20\x2d\x31\x2e\x31\ +\x20\x32\x2e\x32\x32\x20\x2d\x31\x2e\x30\x31\x20\x32\x2e\x32\x37\ +\x20\x2d\x30\x2e\x38\x39\x20\x32\x2e\x33\x31\x20\x2d\x30\x2e\x37\ +\x37\x20\x32\x2e\x33\x35\x20\x2d\x30\x2e\x36\x37\x20\x32\x2e\x34\ +\x20\x2d\x30\x2e\x35\x36\x20\x32\x2e\x34\x36\x20\x2d\x30\x2e\x34\ +\x34\x20\x32\x2e\x34\x39\x20\x2d\x30\x2e\x33\x31\x20\x32\x2e\x35\ +\x33\x20\x2d\x30\x2e\x32\x20\x32\x2e\x35\x34\x20\x2d\x30\x2e\x30\ +\x36\x20\x30\x20\x31\x37\x2e\x36\x34\x20\x2d\x31\x2e\x36\x36\x20\ +\x30\x2e\x30\x34\x20\x2d\x31\x2e\x36\x33\x20\x30\x2e\x31\x32\x20\ +\x2d\x31\x2e\x36\x31\x20\x30\x2e\x32\x31\x20\x2d\x31\x2e\x35\x36\ +\x20\x30\x2e\x32\x38\x20\x2d\x31\x2e\x35\x36\x20\x30\x2e\x33\x36\ +\x20\x2d\x31\x2e\x35\x33\x20\x30\x2e\x34\x33\x20\x2d\x31\x2e\x34\ +\x39\x20\x30\x2e\x35\x31\x20\x2d\x31\x2e\x34\x35\x20\x30\x2e\x35\ +\x37\x20\x2d\x31\x2e\x34\x20\x30\x2e\x36\x33\x20\x2d\x31\x2e\x34\ +\x20\x30\x2e\x37\x32\x20\x2d\x31\x2e\x33\x35\x20\x30\x2e\x37\x38\ +\x20\x2d\x31\x2e\x33\x31\x20\x30\x2e\x38\x33\x20\x2d\x31\x2e\x32\ +\x34\x20\x30\x2e\x38\x38\x20\x2d\x31\x2e\x32\x32\x20\x30\x2e\x39\ +\x36\x20\x2d\x31\x2e\x31\x37\x20\x31\x2e\x30\x31\x20\x2d\x31\x2e\ +\x31\x20\x31\x2e\x30\x36\x20\x2d\x31\x2e\x30\x38\x20\x31\x2e\x31\ +\x33\x20\x2d\x31\x2e\x30\x31\x20\x31\x2e\x31\x35\x20\x2d\x30\x2e\ +\x39\x34\x20\x31\x2e\x32\x31\x20\x2d\x30\x2e\x39\x31\x20\x31\x2e\ +\x32\x38\x20\x2d\x30\x2e\x38\x33\x20\x31\x2e\x32\x39\x20\x2d\x30\ +\x2e\x37\x37\x20\x31\x2e\x33\x35\x20\x2d\x30\x2e\x37\x31\x20\x31\ +\x2e\x33\x37\x20\x2d\x30\x2e\x36\x33\x20\x31\x2e\x34\x31\x20\x2d\ +\x30\x2e\x35\x39\x20\x31\x2e\x34\x37\x20\x2d\x30\x2e\x35\x20\x31\ +\x2e\x34\x37\x20\x2d\x30\x2e\x34\x33\x20\x31\x2e\x35\x33\x20\x2d\ +\x30\x2e\x33\x36\x20\x31\x2e\x35\x36\x20\x2d\x30\x2e\x32\x38\x20\ +\x31\x2e\x35\x36\x20\x2d\x30\x2e\x32\x31\x20\x31\x2e\x36\x31\x20\ +\x2d\x30\x2e\x31\x32\x20\x31\x2e\x36\x33\x20\x2d\x30\x2e\x30\x34\ +\x20\x31\x2e\x36\x36\x7a\x6d\x30\x20\x30\x6c\x30\x20\x38\x36\x2e\ +\x31\x39\x20\x2d\x31\x37\x2e\x36\x34\x20\x30\x20\x30\x20\x2d\x38\ +\x36\x2e\x31\x39\x20\x31\x37\x2e\x36\x34\x20\x30\x7a\x6d\x31\x36\ +\x31\x2e\x39\x33\x20\x34\x36\x2e\x32\x31\x6c\x30\x20\x31\x37\x2e\ +\x36\x34\x20\x30\x20\x30\x20\x2d\x31\x2e\x32\x34\x20\x2d\x30\x2e\ +\x30\x31\x20\x2d\x31\x2e\x32\x33\x20\x2d\x30\x2e\x30\x35\x20\x2d\ +\x31\x2e\x32\x32\x20\x2d\x30\x2e\x30\x37\x20\x2d\x31\x2e\x32\x33\ +\x20\x2d\x30\x2e\x31\x31\x20\x2d\x31\x2e\x32\x32\x20\x2d\x30\x2e\ +\x31\x34\x20\x2d\x31\x2e\x32\x32\x20\x2d\x30\x2e\x31\x37\x20\x2d\ +\x31\x2e\x31\x37\x20\x2d\x30\x2e\x31\x39\x20\x2d\x31\x2e\x32\x31\ +\x20\x2d\x30\x2e\x32\x32\x20\x2d\x31\x2e\x32\x31\x20\x2d\x30\x2e\ +\x32\x36\x20\x2d\x31\x2e\x31\x39\x20\x2d\x30\x2e\x32\x38\x20\x2d\ +\x31\x2e\x31\x39\x20\x2d\x30\x2e\x33\x32\x20\x2d\x31\x2e\x31\x34\ +\x20\x2d\x30\x2e\x33\x33\x20\x2d\x31\x2e\x31\x38\x20\x2d\x30\x2e\ +\x33\x37\x20\x2d\x31\x2e\x31\x31\x20\x2d\x30\x2e\x33\x39\x20\x2d\ +\x31\x2e\x31\x31\x20\x2d\x30\x2e\x34\x20\x2d\x31\x2e\x32\x20\x2d\ +\x30\x2e\x34\x38\x20\x2d\x31\x2e\x31\x20\x2d\x30\x2e\x34\x38\x20\ +\x2d\x31\x2e\x30\x37\x20\x2d\x30\x2e\x34\x39\x20\x2d\x31\x2e\x31\ +\x33\x20\x2d\x30\x2e\x35\x35\x20\x2d\x31\x2e\x30\x36\x20\x2d\x30\ +\x2e\x35\x35\x20\x2d\x31\x2e\x30\x35\x20\x2d\x30\x2e\x35\x38\x20\ +\x2d\x31\x2e\x30\x36\x20\x2d\x30\x2e\x36\x31\x20\x2d\x31\x2e\x30\ +\x33\x20\x2d\x30\x2e\x36\x34\x20\x2d\x31\x2e\x30\x34\x20\x2d\x30\ +\x2e\x36\x37\x20\x2d\x31\x2e\x30\x31\x20\x2d\x30\x2e\x37\x20\x2d\ +\x30\x2e\x39\x35\x20\x2d\x30\x2e\x36\x38\x20\x2d\x30\x2e\x39\x38\ +\x20\x2d\x30\x2e\x37\x33\x20\x2d\x30\x2e\x39\x38\x20\x2d\x30\x2e\ +\x37\x39\x20\x2d\x30\x2e\x39\x34\x20\x2d\x30\x2e\x37\x39\x20\x2d\ +\x30\x2e\x39\x32\x20\x2d\x30\x2e\x38\x32\x20\x2d\x30\x2e\x39\x20\ +\x2d\x30\x2e\x38\x33\x20\x2d\x30\x2e\x38\x38\x20\x2d\x30\x2e\x38\ +\x36\x20\x31\x32\x2e\x34\x36\x20\x2d\x31\x32\x2e\x34\x38\x20\x30\ +\x2e\x35\x38\x20\x30\x2e\x35\x36\x20\x30\x2e\x35\x38\x20\x30\x2e\ +\x35\x35\x20\x30\x2e\x36\x20\x30\x2e\x35\x32\x20\x30\x2e\x36\x20\ +\x30\x2e\x35\x31\x20\x30\x2e\x36\x20\x30\x2e\x34\x37\x20\x30\x2e\ +\x36\x32\x20\x30\x2e\x34\x37\x20\x30\x2e\x36\x39\x20\x30\x2e\x35\ +\x20\x30\x2e\x36\x33\x20\x30\x2e\x34\x34\x20\x30\x2e\x36\x34\x20\ +\x30\x2e\x34\x31\x20\x30\x2e\x36\x39\x20\x30\x2e\x34\x32\x20\x30\ +\x2e\x36\x36\x20\x30\x2e\x33\x39\x20\x30\x2e\x37\x31\x20\x30\x2e\ +\x33\x38\x20\x30\x2e\x37\x20\x30\x2e\x33\x37\x20\x30\x2e\x36\x37\ +\x20\x30\x2e\x33\x33\x20\x30\x2e\x37\x35\x20\x30\x2e\x33\x33\x20\ +\x30\x2e\x37\x32\x20\x30\x2e\x33\x32\x20\x30\x2e\x36\x36\x20\x30\ +\x2e\x32\x36\x20\x30\x2e\x37\x37\x20\x30\x2e\x32\x38\x20\x30\x2e\ +\x37\x37\x20\x30\x2e\x32\x37\x20\x30\x2e\x37\x32\x20\x30\x2e\x32\ +\x33\x20\x30\x2e\x37\x38\x20\x30\x2e\x32\x33\x20\x30\x2e\x37\x35\ +\x20\x30\x2e\x32\x20\x30\x2e\x37\x35\x20\x30\x2e\x31\x38\x20\x30\ +\x2e\x37\x35\x20\x30\x2e\x31\x36\x20\x30\x2e\x37\x37\x20\x30\x2e\ +\x31\x34\x20\x30\x2e\x38\x31\x20\x30\x2e\x31\x33\x20\x30\x2e\x37\ +\x38\x20\x30\x2e\x31\x31\x20\x30\x2e\x37\x38\x20\x30\x2e\x30\x38\ +\x20\x30\x2e\x37\x39\x20\x30\x2e\x30\x37\x20\x30\x2e\x38\x20\x30\ +\x2e\x30\x35\x20\x30\x2e\x37\x39\x20\x30\x2e\x30\x33\x20\x30\x2e\ +\x38\x20\x30\x2e\x30\x31\x20\x30\x20\x30\x7a\x6d\x30\x20\x30\x63\ +\x34\x2e\x38\x37\x2c\x30\x20\x38\x2e\x38\x32\x2c\x33\x2e\x39\x35\ +\x20\x38\x2e\x38\x32\x2c\x38\x2e\x38\x32\x20\x30\x2c\x34\x2e\x38\ +\x37\x20\x2d\x33\x2e\x39\x35\x2c\x38\x2e\x38\x32\x20\x2d\x38\x2e\ +\x38\x32\x2c\x38\x2e\x38\x32\x6c\x30\x20\x2d\x31\x37\x2e\x36\x34\ +\x7a\x6d\x33\x32\x2e\x30\x39\x20\x2d\x33\x32\x2e\x30\x37\x6c\x31\ +\x37\x2e\x36\x34\x20\x30\x20\x2d\x30\x2e\x30\x36\x20\x32\x2e\x35\ +\x34\x20\x2d\x30\x2e\x32\x20\x32\x2e\x35\x33\x20\x2d\x30\x2e\x33\ +\x31\x20\x32\x2e\x34\x39\x20\x2d\x30\x2e\x34\x34\x20\x32\x2e\x34\ +\x36\x20\x2d\x30\x2e\x35\x36\x20\x32\x2e\x34\x20\x2d\x30\x2e\x36\ +\x37\x20\x32\x2e\x33\x35\x20\x2d\x30\x2e\x37\x38\x20\x32\x2e\x33\ +\x33\x20\x2d\x30\x2e\x38\x39\x20\x32\x2e\x32\x35\x20\x2d\x30\x2e\ +\x39\x39\x20\x32\x2e\x32\x31\x20\x2d\x31\x2e\x31\x31\x20\x32\x2e\ +\x31\x35\x20\x2d\x31\x2e\x31\x39\x20\x32\x2e\x30\x37\x20\x2d\x31\ +\x2e\x33\x31\x20\x32\x2e\x30\x33\x20\x2d\x31\x2e\x33\x37\x20\x31\ +\x2e\x39\x32\x20\x2d\x31\x2e\x34\x38\x20\x31\x2e\x38\x39\x20\x2d\ +\x31\x2e\x35\x37\x20\x31\x2e\x38\x31\x20\x2d\x31\x2e\x36\x32\x20\ +\x31\x2e\x37\x31\x20\x2d\x31\x2e\x37\x34\x20\x31\x2e\x36\x36\x20\ +\x2d\x31\x2e\x38\x31\x20\x31\x2e\x35\x35\x20\x2d\x31\x2e\x38\x36\ +\x20\x31\x2e\x34\x38\x20\x2d\x31\x2e\x39\x38\x20\x31\x2e\x34\x20\ +\x2d\x32\x2e\x30\x31\x20\x31\x2e\x32\x39\x20\x2d\x32\x2e\x30\x37\ +\x20\x31\x2e\x31\x38\x20\x2d\x32\x2e\x31\x34\x20\x31\x2e\x31\x20\ +\x2d\x32\x2e\x32\x32\x20\x31\x2e\x30\x31\x20\x2d\x32\x2e\x32\x37\ +\x20\x30\x2e\x38\x39\x20\x2d\x32\x2e\x33\x31\x20\x30\x2e\x37\x37\ +\x20\x2d\x32\x2e\x33\x35\x20\x30\x2e\x36\x37\x20\x2d\x32\x2e\x34\ +\x20\x30\x2e\x35\x36\x20\x2d\x32\x2e\x34\x36\x20\x30\x2e\x34\x34\ +\x20\x2d\x32\x2e\x34\x39\x20\x30\x2e\x33\x31\x20\x2d\x32\x2e\x35\ +\x33\x20\x30\x2e\x32\x20\x2d\x32\x2e\x35\x34\x20\x30\x2e\x30\x36\ +\x20\x30\x20\x2d\x31\x37\x2e\x36\x34\x20\x31\x2e\x36\x36\x20\x2d\ +\x30\x2e\x30\x34\x20\x31\x2e\x36\x33\x20\x2d\x30\x2e\x31\x32\x20\ +\x31\x2e\x36\x31\x20\x2d\x30\x2e\x32\x31\x20\x31\x2e\x35\x36\x20\ +\x2d\x30\x2e\x32\x38\x20\x31\x2e\x35\x36\x20\x2d\x30\x2e\x33\x36\ +\x20\x31\x2e\x35\x33\x20\x2d\x30\x2e\x34\x33\x20\x31\x2e\x34\x39\ +\x20\x2d\x30\x2e\x35\x31\x20\x31\x2e\x34\x35\x20\x2d\x30\x2e\x35\ +\x37\x20\x31\x2e\x34\x20\x2d\x30\x2e\x36\x33\x20\x31\x2e\x34\x20\ +\x2d\x30\x2e\x37\x32\x20\x31\x2e\x33\x35\x20\x2d\x30\x2e\x37\x38\ +\x20\x31\x2e\x33\x31\x20\x2d\x30\x2e\x38\x33\x20\x31\x2e\x32\x34\ +\x20\x2d\x30\x2e\x38\x38\x20\x31\x2e\x32\x32\x20\x2d\x30\x2e\x39\ +\x36\x20\x31\x2e\x31\x37\x20\x2d\x31\x2e\x30\x31\x20\x31\x2e\x31\ +\x20\x2d\x31\x2e\x30\x36\x20\x31\x2e\x30\x38\x20\x2d\x31\x2e\x31\ +\x33\x20\x31\x2e\x30\x31\x20\x2d\x31\x2e\x31\x35\x20\x30\x2e\x39\ +\x34\x20\x2d\x31\x2e\x32\x31\x20\x30\x2e\x39\x31\x20\x2d\x31\x2e\ +\x32\x38\x20\x30\x2e\x38\x33\x20\x2d\x31\x2e\x32\x39\x20\x30\x2e\ +\x37\x37\x20\x2d\x31\x2e\x33\x35\x20\x30\x2e\x37\x31\x20\x2d\x31\ +\x2e\x33\x37\x20\x30\x2e\x36\x33\x20\x2d\x31\x2e\x34\x31\x20\x30\ +\x2e\x35\x39\x20\x2d\x31\x2e\x34\x37\x20\x30\x2e\x35\x20\x2d\x31\ +\x2e\x34\x37\x20\x30\x2e\x34\x33\x20\x2d\x31\x2e\x35\x33\x20\x30\ +\x2e\x33\x36\x20\x2d\x31\x2e\x35\x36\x20\x30\x2e\x32\x38\x20\x2d\ +\x31\x2e\x35\x36\x20\x30\x2e\x32\x31\x20\x2d\x31\x2e\x36\x31\x20\ +\x30\x2e\x31\x32\x20\x2d\x31\x2e\x36\x33\x20\x30\x2e\x30\x34\x20\ +\x2d\x31\x2e\x36\x36\x7a\x6d\x30\x20\x30\x6c\x30\x20\x2d\x38\x36\ +\x2e\x31\x39\x20\x31\x37\x2e\x36\x34\x20\x30\x20\x30\x20\x38\x36\ +\x2e\x31\x39\x20\x2d\x31\x37\x2e\x36\x34\x20\x30\x7a\x6d\x2d\x31\ +\x32\x36\x2e\x37\x36\x20\x2d\x34\x39\x2e\x32\x39\x6c\x37\x31\x2e\ +\x39\x37\x20\x37\x31\x2e\x39\x37\x20\x2d\x31\x32\x2e\x34\x38\x20\ +\x31\x32\x2e\x34\x38\x20\x2d\x37\x31\x2e\x39\x37\x20\x2d\x37\x31\ +\x2e\x39\x37\x20\x31\x32\x2e\x34\x38\x20\x2d\x31\x32\x2e\x34\x38\ +\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\x20\x20\ +\x3c\x67\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\x20\x64\x3d\x22\x4d\x32\ +\x32\x2e\x39\x20\x32\x36\x33\x2e\x35\x31\x63\x2d\x32\x30\x2e\x32\ +\x34\x2c\x2d\x31\x31\x2e\x36\x39\x20\x2d\x32\x37\x2e\x31\x38\x2c\ +\x2d\x33\x37\x2e\x35\x37\x20\x2d\x31\x35\x2e\x34\x39\x2c\x2d\x35\ +\x37\x2e\x38\x31\x20\x31\x31\x2e\x36\x38\x2c\x2d\x32\x30\x2e\x32\ +\x35\x20\x33\x37\x2e\x35\x37\x2c\x2d\x32\x37\x2e\x31\x38\x20\x35\ +\x37\x2e\x38\x31\x2c\x2d\x31\x35\x2e\x35\x20\x32\x30\x2e\x32\x34\ +\x2c\x31\x31\x2e\x36\x39\x20\x32\x37\x2e\x31\x38\x2c\x33\x37\x2e\ +\x35\x38\x20\x31\x35\x2e\x34\x39\x2c\x35\x37\x2e\x38\x32\x20\x2d\ +\x31\x31\x2e\x36\x38\x2c\x32\x30\x2e\x32\x34\x20\x2d\x33\x37\x2e\ +\x35\x37\x2c\x32\x37\x2e\x31\x38\x20\x2d\x35\x37\x2e\x38\x31\x2c\ +\x31\x35\x2e\x34\x39\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x70\ +\x61\x74\x68\x20\x69\x64\x3d\x22\x5f\x31\x22\x20\x63\x6c\x61\x73\ +\x73\x3d\x22\x66\x69\x6c\x33\x22\x20\x64\x3d\x22\x4d\x32\x32\x2e\ +\x39\x20\x32\x36\x33\x2e\x35\x31\x63\x2d\x32\x30\x2e\x32\x34\x2c\ +\x2d\x31\x31\x2e\x36\x39\x20\x2d\x32\x37\x2e\x31\x38\x2c\x2d\x33\ +\x37\x2e\x35\x37\x20\x2d\x31\x35\x2e\x34\x39\x2c\x2d\x35\x37\x2e\ +\x38\x31\x20\x31\x31\x2e\x36\x38\x2c\x2d\x32\x30\x2e\x32\x35\x20\ +\x33\x37\x2e\x35\x37\x2c\x2d\x32\x37\x2e\x31\x38\x20\x35\x37\x2e\ +\x38\x31\x2c\x2d\x31\x35\x2e\x35\x20\x32\x30\x2e\x32\x34\x2c\x31\ +\x31\x2e\x36\x39\x20\x32\x37\x2e\x31\x38\x2c\x33\x37\x2e\x35\x38\ +\x20\x31\x35\x2e\x34\x39\x2c\x35\x37\x2e\x38\x32\x20\x2d\x31\x31\ +\x2e\x36\x38\x2c\x32\x30\x2e\x32\x34\x20\x2d\x33\x37\x2e\x35\x37\ +\x2c\x32\x37\x2e\x31\x38\x20\x2d\x35\x37\x2e\x38\x31\x2c\x31\x35\ +\x2e\x34\x39\x7a\x6d\x2d\x32\x2e\x37\x20\x2d\x33\x30\x2e\x32\x35\ +\x63\x31\x2e\x35\x36\x2c\x35\x2e\x38\x35\x20\x35\x2e\x32\x38\x2c\ +\x31\x31\x2e\x31\x33\x20\x31\x30\x2e\x37\x36\x2c\x31\x34\x2e\x35\ +\x36\x20\x30\x2e\x36\x32\x2c\x30\x2e\x33\x32\x20\x31\x2e\x32\x32\ +\x2c\x30\x2e\x36\x37\x20\x31\x2e\x38\x2c\x31\x2e\x30\x37\x20\x35\ +\x2e\x36\x32\x2c\x32\x2e\x38\x37\x20\x31\x31\x2e\x39\x34\x2c\x33\ +\x2e\x33\x37\x20\x31\x37\x2e\x37\x31\x2c\x31\x2e\x38\x33\x20\x35\ +\x2e\x38\x33\x2c\x2d\x31\x2e\x35\x37\x20\x31\x31\x2e\x31\x32\x2c\ +\x2d\x35\x2e\x32\x38\x20\x31\x34\x2e\x35\x34\x2c\x2d\x31\x30\x2e\ +\x37\x35\x20\x30\x2e\x33\x32\x2c\x2d\x30\x2e\x36\x32\x20\x30\x2e\ +\x36\x37\x2c\x2d\x31\x2e\x32\x32\x20\x31\x2e\x30\x37\x2c\x2d\x31\ +\x2e\x38\x20\x32\x2e\x38\x37\x2c\x2d\x35\x2e\x36\x32\x20\x33\x2e\ +\x33\x38\x2c\x2d\x31\x31\x2e\x39\x34\x20\x31\x2e\x38\x34\x2c\x2d\ +\x31\x37\x2e\x37\x31\x20\x2d\x31\x2e\x36\x33\x2c\x2d\x36\x2e\x31\ +\x32\x20\x2d\x35\x2e\x36\x32\x2c\x2d\x31\x31\x2e\x36\x32\x20\x2d\ +\x31\x31\x2e\x35\x32\x2c\x2d\x31\x35\x2e\x30\x33\x6c\x30\x2e\x30\ +\x31\x20\x2d\x30\x2e\x30\x33\x63\x2d\x35\x2e\x38\x36\x2c\x2d\x33\ +\x2e\x33\x36\x20\x2d\x31\x32\x2e\x36\x32\x2c\x2d\x34\x2e\x30\x35\ +\x20\x2d\x31\x38\x2e\x37\x36\x2c\x2d\x32\x2e\x34\x20\x2d\x36\x2e\ +\x30\x36\x2c\x31\x2e\x36\x32\x20\x2d\x31\x31\x2e\x35\x31\x2c\x35\ +\x2e\x35\x31\x20\x2d\x31\x34\x2e\x38\x39\x2c\x31\x31\x2e\x32\x34\ +\x20\x2d\x30\x2e\x30\x34\x2c\x30\x2e\x30\x37\x20\x2d\x30\x2e\x30\ +\x38\x2c\x30\x2e\x31\x34\x20\x2d\x30\x2e\x31\x32\x2c\x30\x2e\x32\ +\x31\x20\x2d\x33\x2e\x33\x39\x2c\x35\x2e\x38\x38\x20\x2d\x34\x2e\ +\x30\x38\x2c\x31\x32\x2e\x36\x36\x20\x2d\x32\x2e\x34\x34\x2c\x31\ +\x38\x2e\x38\x31\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\x3e\ +\x0d\x0a\x20\x20\x3c\x67\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x61\x74\ +\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\x20\x64\ +\x3d\x22\x4d\x31\x31\x34\x2e\x32\x39\x20\x31\x37\x32\x2e\x31\x31\ +\x63\x2d\x32\x30\x2e\x32\x34\x2c\x2d\x31\x31\x2e\x36\x39\x20\x2d\ +\x32\x37\x2e\x31\x38\x2c\x2d\x33\x37\x2e\x35\x38\x20\x2d\x31\x35\ +\x2e\x34\x39\x2c\x2d\x35\x37\x2e\x38\x32\x20\x31\x31\x2e\x36\x39\ +\x2c\x2d\x32\x30\x2e\x32\x34\x20\x33\x37\x2e\x35\x37\x2c\x2d\x32\ +\x37\x2e\x31\x38\x20\x35\x37\x2e\x38\x32\x2c\x2d\x31\x35\x2e\x34\ +\x39\x20\x32\x30\x2e\x32\x34\x2c\x31\x31\x2e\x36\x39\x20\x32\x37\ +\x2e\x31\x38\x2c\x33\x37\x2e\x35\x37\x20\x31\x35\x2e\x34\x39\x2c\ +\x35\x37\x2e\x38\x32\x20\x2d\x31\x31\x2e\x36\x39\x2c\x32\x30\x2e\ +\x32\x34\x20\x2d\x33\x37\x2e\x35\x38\x2c\x32\x37\x2e\x31\x37\x20\ +\x2d\x35\x37\x2e\x38\x32\x2c\x31\x35\x2e\x34\x39\x7a\x22\x2f\x3e\ +\x0d\x0a\x20\x20\x20\x3c\x70\x61\x74\x68\x20\x69\x64\x3d\x22\x5f\ +\x31\x5f\x30\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x34\ +\x22\x20\x64\x3d\x22\x4d\x31\x31\x34\x2e\x32\x39\x20\x31\x37\x32\ +\x2e\x31\x31\x63\x2d\x32\x30\x2e\x32\x34\x2c\x2d\x31\x31\x2e\x36\ +\x39\x20\x2d\x32\x37\x2e\x31\x38\x2c\x2d\x33\x37\x2e\x35\x38\x20\ +\x2d\x31\x35\x2e\x34\x39\x2c\x2d\x35\x37\x2e\x38\x32\x20\x31\x31\ +\x2e\x36\x39\x2c\x2d\x32\x30\x2e\x32\x34\x20\x33\x37\x2e\x35\x37\ +\x2c\x2d\x32\x37\x2e\x31\x38\x20\x35\x37\x2e\x38\x32\x2c\x2d\x31\ +\x35\x2e\x34\x39\x20\x32\x30\x2e\x32\x34\x2c\x31\x31\x2e\x36\x39\ +\x20\x32\x37\x2e\x31\x38\x2c\x33\x37\x2e\x35\x37\x20\x31\x35\x2e\ +\x34\x39\x2c\x35\x37\x2e\x38\x32\x20\x2d\x31\x31\x2e\x36\x39\x2c\ +\x32\x30\x2e\x32\x34\x20\x2d\x33\x37\x2e\x35\x38\x2c\x32\x37\x2e\ +\x31\x37\x20\x2d\x35\x37\x2e\x38\x32\x2c\x31\x35\x2e\x34\x39\x7a\ +\x6d\x2d\x32\x2e\x37\x20\x2d\x33\x30\x2e\x32\x36\x63\x31\x2e\x36\ +\x33\x2c\x36\x2e\x31\x32\x20\x35\x2e\x36\x32\x2c\x31\x31\x2e\x36\ +\x32\x20\x31\x31\x2e\x35\x32\x2c\x31\x35\x2e\x30\x33\x6c\x2d\x30\ +\x2e\x30\x32\x20\x30\x2e\x30\x33\x63\x35\x2e\x38\x37\x2c\x33\x2e\ +\x33\x36\x20\x31\x32\x2e\x36\x33\x2c\x34\x2e\x30\x35\x20\x31\x38\ +\x2e\x37\x37\x2c\x32\x2e\x34\x20\x36\x2e\x31\x34\x2c\x2d\x31\x2e\ +\x36\x34\x20\x31\x31\x2e\x36\x35\x2c\x2d\x35\x2e\x36\x20\x31\x35\ +\x2e\x30\x32\x2c\x2d\x31\x31\x2e\x34\x34\x20\x33\x2e\x34\x2c\x2d\ +\x35\x2e\x38\x39\x20\x34\x2e\x30\x38\x2c\x2d\x31\x32\x2e\x36\x38\ +\x20\x32\x2e\x34\x34\x2c\x2d\x31\x38\x2e\x38\x32\x20\x2d\x31\x2e\ +\x36\x33\x2c\x2d\x36\x2e\x31\x31\x20\x2d\x35\x2e\x36\x32\x2c\x2d\ +\x31\x31\x2e\x36\x31\x20\x2d\x31\x31\x2e\x35\x32\x2c\x2d\x31\x35\ +\x2e\x30\x32\x6c\x30\x2e\x30\x32\x20\x2d\x30\x2e\x30\x33\x63\x2d\ +\x35\x2e\x38\x38\x2c\x2d\x33\x2e\x33\x37\x20\x2d\x31\x32\x2e\x36\ +\x35\x2c\x2d\x34\x2e\x30\x35\x20\x2d\x31\x38\x2e\x37\x37\x2c\x2d\ +\x32\x2e\x34\x31\x20\x2d\x36\x2e\x31\x31\x2c\x31\x2e\x36\x33\x20\ +\x2d\x31\x31\x2e\x36\x31\x2c\x35\x2e\x36\x32\x20\x2d\x31\x35\x2e\ +\x30\x32\x2c\x31\x31\x2e\x35\x32\x6c\x2d\x30\x2e\x30\x33\x20\x2d\ +\x30\x2e\x30\x32\x63\x2d\x33\x2e\x33\x36\x2c\x35\x2e\x38\x37\x20\ +\x2d\x34\x2e\x30\x35\x2c\x31\x32\x2e\x36\x33\x20\x2d\x32\x2e\x34\ +\x31\x2c\x31\x38\x2e\x37\x36\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\ +\x2f\x67\x3e\x0d\x0a\x20\x20\x3c\x67\x3e\x0d\x0a\x20\x20\x20\x3c\ +\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\ +\x22\x20\x64\x3d\x22\x4d\x32\x30\x35\x2e\x36\x39\x20\x38\x30\x2e\ +\x37\x31\x63\x2d\x32\x30\x2e\x32\x34\x2c\x2d\x31\x31\x2e\x36\x39\ +\x20\x2d\x32\x37\x2e\x31\x37\x2c\x2d\x33\x37\x2e\x35\x38\x20\x2d\ +\x31\x35\x2e\x34\x39\x2c\x2d\x35\x37\x2e\x38\x32\x20\x31\x31\x2e\ +\x36\x39\x2c\x2d\x32\x30\x2e\x32\x34\x20\x33\x37\x2e\x35\x38\x2c\ +\x2d\x32\x37\x2e\x31\x38\x20\x35\x37\x2e\x38\x32\x2c\x2d\x31\x35\ +\x2e\x34\x39\x20\x32\x30\x2e\x32\x34\x2c\x31\x31\x2e\x36\x39\x20\ +\x32\x37\x2e\x31\x38\x2c\x33\x37\x2e\x35\x37\x20\x31\x35\x2e\x34\ +\x39\x2c\x35\x37\x2e\x38\x32\x20\x2d\x31\x31\x2e\x36\x39\x2c\x32\ +\x30\x2e\x32\x34\x20\x2d\x33\x37\x2e\x35\x37\x2c\x32\x37\x2e\x31\ +\x37\x20\x2d\x35\x37\x2e\x38\x32\x2c\x31\x35\x2e\x34\x39\x7a\x22\ +\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x61\x74\x68\x20\x69\x64\x3d\ +\x22\x5f\x31\x5f\x31\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\ +\x6c\x34\x22\x20\x64\x3d\x22\x4d\x32\x30\x35\x2e\x36\x39\x20\x38\ +\x30\x2e\x37\x31\x63\x2d\x32\x30\x2e\x32\x34\x2c\x2d\x31\x31\x2e\ +\x36\x39\x20\x2d\x32\x37\x2e\x31\x37\x2c\x2d\x33\x37\x2e\x35\x38\ +\x20\x2d\x31\x35\x2e\x34\x39\x2c\x2d\x35\x37\x2e\x38\x32\x20\x31\ +\x31\x2e\x36\x39\x2c\x2d\x32\x30\x2e\x32\x34\x20\x33\x37\x2e\x35\ +\x38\x2c\x2d\x32\x37\x2e\x31\x38\x20\x35\x37\x2e\x38\x32\x2c\x2d\ +\x31\x35\x2e\x34\x39\x20\x32\x30\x2e\x32\x34\x2c\x31\x31\x2e\x36\ +\x39\x20\x32\x37\x2e\x31\x38\x2c\x33\x37\x2e\x35\x37\x20\x31\x35\ +\x2e\x34\x39\x2c\x35\x37\x2e\x38\x32\x20\x2d\x31\x31\x2e\x36\x39\ +\x2c\x32\x30\x2e\x32\x34\x20\x2d\x33\x37\x2e\x35\x37\x2c\x32\x37\ +\x2e\x31\x37\x20\x2d\x35\x37\x2e\x38\x32\x2c\x31\x35\x2e\x34\x39\ +\x7a\x6d\x2d\x32\x2e\x36\x39\x20\x2d\x33\x30\x2e\x32\x35\x63\x31\ +\x2e\x36\x34\x2c\x36\x2e\x31\x34\x20\x35\x2e\x36\x2c\x31\x31\x2e\ +\x36\x35\x20\x31\x31\x2e\x34\x34\x2c\x31\x35\x2e\x30\x32\x20\x35\ +\x2e\x38\x39\x2c\x33\x2e\x33\x39\x20\x31\x32\x2e\x36\x36\x2c\x34\ +\x2e\x30\x38\x20\x31\x38\x2e\x38\x32\x2c\x32\x2e\x34\x33\x20\x36\ +\x2e\x31\x34\x2c\x2d\x31\x2e\x36\x34\x20\x31\x31\x2e\x36\x35\x2c\ +\x2d\x35\x2e\x36\x20\x31\x35\x2e\x30\x32\x2c\x2d\x31\x31\x2e\x34\ +\x34\x20\x33\x2e\x33\x39\x2c\x2d\x35\x2e\x38\x38\x20\x34\x2e\x30\ +\x38\x2c\x2d\x31\x32\x2e\x36\x37\x20\x32\x2e\x34\x34\x2c\x2d\x31\ +\x38\x2e\x38\x32\x20\x2d\x31\x2e\x36\x33\x2c\x2d\x36\x2e\x31\x31\ +\x20\x2d\x35\x2e\x36\x32\x2c\x2d\x31\x31\x2e\x36\x31\x20\x2d\x31\ +\x31\x2e\x35\x32\x2c\x2d\x31\x35\x2e\x30\x32\x6c\x30\x2e\x30\x32\ +\x20\x2d\x30\x2e\x30\x33\x63\x2d\x35\x2e\x38\x37\x2c\x2d\x33\x2e\ +\x33\x36\x20\x2d\x31\x32\x2e\x36\x33\x2c\x2d\x34\x2e\x30\x35\x20\ +\x2d\x31\x38\x2e\x37\x36\x2c\x2d\x32\x2e\x34\x31\x20\x2d\x36\x2e\ +\x31\x32\x2c\x31\x2e\x36\x33\x20\x2d\x31\x31\x2e\x36\x32\x2c\x35\ +\x2e\x36\x32\x20\x2d\x31\x35\x2e\x30\x33\x2c\x31\x31\x2e\x35\x32\ +\x6c\x2d\x30\x2e\x30\x33\x20\x2d\x30\x2e\x30\x32\x63\x2d\x33\x2e\ +\x33\x36\x2c\x35\x2e\x38\x37\x20\x2d\x34\x2e\x30\x35\x2c\x31\x32\ +\x2e\x36\x33\x20\x2d\x32\x2e\x34\x2c\x31\x38\x2e\x37\x37\x7a\x22\ +\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\x20\x3c\x2f\x67\ +\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x06\xfc\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x67\x72\x61\x79\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\ +\x68\x3a\x31\x34\x2e\x31\x31\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\ +\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\ +\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x73\x74\x72\x31\x20\x7b\x73\ +\x74\x72\x6f\x6b\x65\x3a\x23\x34\x36\x38\x32\x43\x38\x3b\x73\x74\ +\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x31\x36\x2e\x39\x33\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x63\x61\x70\x3a\ +\x73\x71\x75\x61\x72\x65\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\ +\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\ +\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\ +\x6c\x6c\x3a\x6e\x6f\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\ +\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x67\x72\x61\x79\x7d\x0d\ +\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x32\x20\x7b\x66\x69\x6c\x6c\ +\x3a\x23\x46\x46\x39\x39\x33\x33\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\ +\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\ +\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\ +\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\ +\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\x69\ +\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\ +\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\ +\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\ +\x69\x6c\x30\x22\x20\x78\x3d\x22\x30\x22\x20\x79\x3d\x22\x30\x22\ +\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\x30\x2e\x39\x32\x22\x20\ +\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\x39\x32\x22\x2f\ +\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\x74\ +\x73\x3d\x22\x34\x36\x2e\x35\x35\x2c\x32\x34\x39\x2e\x37\x36\x20\ +\x36\x34\x2e\x38\x38\x2c\x32\x36\x30\x2e\x33\x34\x20\x38\x33\x2e\ +\x32\x31\x2c\x32\x37\x30\x2e\x39\x32\x20\x38\x33\x2e\x32\x31\x2c\ +\x32\x34\x39\x2e\x37\x36\x20\x38\x33\x2e\x32\x31\x2c\x32\x32\x38\ +\x2e\x35\x39\x20\x36\x34\x2e\x38\x38\x2c\x32\x33\x39\x2e\x31\x37\ +\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x6c\x69\x6e\x65\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x30\x22\x20\ +\x78\x31\x3d\x22\x37\x30\x2e\x35\x32\x22\x20\x79\x31\x3d\x22\x32\ +\x34\x39\x2e\x37\x36\x22\x20\x78\x32\x3d\x22\x32\x32\x30\x2e\x31\ +\x33\x22\x20\x79\x32\x3d\x20\x22\x32\x34\x39\x2e\x37\x36\x22\x20\ +\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\ +\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x31\x22\x20\x64\x3d\ +\x22\x4d\x32\x34\x39\x2e\x36\x34\x20\x32\x35\x38\x2e\x31\x32\x63\ +\x2d\x34\x2e\x35\x2c\x2d\x31\x32\x38\x2e\x38\x37\x20\x2d\x31\x30\ +\x37\x2e\x39\x33\x2c\x2d\x32\x33\x32\x2e\x33\x20\x2d\x32\x33\x36\ +\x2e\x38\x2c\x2d\x32\x33\x36\x2e\x38\x22\x2f\x3e\x0d\x0a\x20\x20\ +\x3c\x6c\x69\x6e\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\ +\x30\x20\x73\x74\x72\x30\x22\x20\x78\x31\x3d\x22\x36\x31\x2e\x36\ +\x36\x22\x20\x79\x31\x3d\x22\x36\x31\x2e\x30\x39\x22\x20\x78\x32\ +\x3d\x22\x32\x31\x37\x2e\x37\x33\x22\x20\x79\x32\x3d\x20\x22\x32\ +\x31\x38\x2e\x32\x39\x22\x20\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\ +\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\ +\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x34\x36\x2e\x31\x31\ +\x2c\x34\x34\x2e\x39\x20\x35\x31\x2e\x35\x38\x2c\x36\x35\x2e\x33\ +\x34\x20\x35\x37\x2e\x30\x36\x2c\x38\x35\x2e\x37\x38\x20\x37\x32\ +\x2e\x30\x33\x2c\x37\x30\x2e\x38\x32\x20\x38\x36\x2e\x39\x39\x2c\ +\x35\x35\x2e\x38\x35\x20\x36\x36\x2e\x35\x35\x2c\x35\x30\x2e\x33\ +\x37\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\ +\x6e\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\x22\x20\x63\x6c\x61\x73\ +\x73\x3d\x22\x66\x69\x6c\x32\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\ +\x22\x34\x32\x2e\x33\x33\x2c\x32\x32\x38\x2e\x36\x20\x34\x32\x2e\ +\x33\x33\x2c\x32\x37\x30\x2e\x39\x33\x20\x2d\x30\x2c\x32\x37\x30\ +\x2e\x39\x33\x20\x2d\x30\x2c\x32\x32\x38\x2e\x36\x20\x22\x2f\x3e\ +\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x69\x64\x3d\ +\x22\x5f\x31\x5f\x30\x5f\x30\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\ +\x66\x69\x6c\x32\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x32\x37\ +\x30\x2e\x39\x32\x2c\x32\x32\x38\x2e\x36\x20\x32\x37\x30\x2e\x39\ +\x32\x2c\x32\x37\x30\x2e\x39\x33\x20\x32\x32\x38\x2e\x35\x39\x2c\ +\x32\x37\x30\x2e\x39\x33\x20\x32\x32\x38\x2e\x35\x39\x2c\x32\x32\ +\x38\x2e\x36\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\ +\x67\x6f\x6e\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\x5f\x31\x22\x20\ +\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\x20\x70\x6f\x69\ +\x6e\x74\x73\x3d\x22\x34\x32\x2e\x33\x33\x2c\x30\x20\x34\x32\x2e\ +\x33\x33\x2c\x34\x32\x2e\x33\x33\x20\x2d\x30\x2c\x34\x32\x2e\x33\ +\x33\x20\x2d\x30\x2c\x30\x20\x22\x2f\x3e\x0d\x0a\x20\x3c\x2f\x67\ +\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x04\xab\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x34\x36\x38\x32\x43\x38\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x36\x2e\x39\x33\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\ +\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x30\ +\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\x6e\x65\x7d\x0d\x0a\x20\x20\ +\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x23\x46\ +\x46\x39\x39\x33\x33\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\ +\x20\x20\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\ +\x65\x66\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\ +\x6d\x61\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\ +\x20\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\x69\x64\x3d\x22\ +\x43\x6f\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\ +\x65\x6c\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\ +\x72\x65\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\ +\x22\x20\x78\x3d\x22\x2d\x30\x2e\x30\x31\x22\x20\x79\x3d\x22\x2d\ +\x30\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\x30\x2e\x39\x33\ +\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\x39\x33\ +\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x63\x69\x72\x63\x6c\x65\x20\x63\ +\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x30\x22\ +\x20\x63\x78\x3d\x22\x31\x33\x35\x2e\x34\x36\x22\x20\x63\x79\x3d\ +\x22\x31\x33\x35\x2e\x34\x36\x22\x20\x72\x3d\x22\x31\x32\x32\x2e\ +\x37\x36\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\ +\x6e\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\x22\x20\x63\x6c\x61\x73\ +\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\ +\x22\x32\x39\x2e\x36\x39\x2c\x31\x39\x38\x2e\x39\x20\x37\x32\x2e\ +\x30\x32\x2c\x31\x39\x38\x2e\x39\x20\x37\x32\x2e\x30\x32\x2c\x32\ +\x34\x31\x2e\x32\x33\x20\x32\x39\x2e\x36\x39\x2c\x32\x34\x31\x2e\ +\x32\x33\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\ +\x6f\x6e\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\x5f\x30\x22\x20\x63\ +\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\ +\x74\x73\x3d\x22\x31\x39\x38\x2e\x39\x38\x2c\x32\x39\x2e\x36\x20\ +\x32\x34\x31\x2e\x33\x31\x2c\x32\x39\x2e\x36\x20\x32\x34\x31\x2e\ +\x33\x31\x2c\x37\x31\x2e\x39\x33\x20\x31\x39\x38\x2e\x39\x38\x2c\ +\x37\x31\x2e\x39\x33\x20\x22\x2f\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\ +\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x05\x8e\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x34\x36\x38\x32\x43\x38\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x36\x2e\x39\x33\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\ +\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x73\x74\x72\x31\ +\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\x23\x42\x33\x42\x33\x42\x33\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x31\x36\ +\x2e\x39\x33\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\ +\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\ +\x20\x20\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\ +\x6e\x6f\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x31\ +\x20\x7b\x66\x69\x6c\x6c\x3a\x23\x46\x46\x39\x39\x33\x33\x7d\x0d\ +\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\ +\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\ +\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\ +\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\ +\x64\x61\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\ +\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\ +\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\x78\x3d\x22\x2d\x30\ +\x22\x20\x79\x3d\x22\x2d\x30\x22\x20\x77\x69\x64\x74\x68\x3d\x22\ +\x32\x37\x30\x2e\x39\x33\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\ +\x32\x37\x30\x2e\x39\x33\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x61\ +\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\ +\x74\x72\x30\x22\x20\x64\x3d\x22\x4d\x31\x33\x36\x2e\x30\x36\x20\ +\x32\x33\x30\x2e\x33\x37\x63\x37\x30\x2e\x31\x33\x2c\x30\x20\x31\ +\x32\x37\x2c\x2d\x34\x31\x2e\x37\x20\x31\x32\x37\x2c\x2d\x39\x33\ +\x2e\x31\x33\x20\x30\x2c\x2d\x35\x31\x2e\x34\x33\x20\x2d\x35\x36\ +\x2e\x38\x37\x2c\x2d\x39\x33\x2e\x31\x33\x20\x2d\x31\x32\x37\x2c\ +\x2d\x39\x33\x2e\x31\x33\x20\x2d\x37\x30\x2e\x31\x33\x2c\x30\x20\ +\x2d\x31\x32\x37\x2c\x34\x31\x2e\x37\x20\x2d\x31\x32\x37\x2c\x39\ +\x33\x2e\x31\x33\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x61\x74\x68\ +\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\ +\x31\x22\x20\x64\x3d\x22\x4d\x31\x33\x35\x2e\x34\x37\x20\x31\x36\ +\x30\x2e\x38\x35\x6c\x30\x20\x2d\x35\x30\x2e\x37\x39\x6d\x2d\x32\ +\x35\x2e\x34\x20\x32\x35\x2e\x33\x39\x6c\x35\x30\x2e\x37\x39\x20\ +\x30\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\ +\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\x22\x20\x63\x6c\x61\x73\x73\ +\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\ +\x30\x2e\x30\x38\x2c\x31\x31\x34\x2e\x33\x20\x34\x32\x2e\x34\x31\ +\x2c\x31\x31\x34\x2e\x33\x20\x34\x32\x2e\x34\x31\x2c\x31\x35\x36\ +\x2e\x36\x33\x20\x30\x2e\x30\x38\x2c\x31\x35\x36\x2e\x36\x33\x20\ +\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\ +\x69\x64\x3d\x22\x5f\x31\x5f\x30\x5f\x30\x22\x20\x63\x6c\x61\x73\ +\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\ +\x22\x31\x31\x34\x2e\x32\x39\x2c\x32\x30\x37\x2e\x33\x36\x20\x31\ +\x35\x36\x2e\x36\x32\x2c\x32\x30\x37\x2e\x33\x36\x20\x31\x35\x36\ +\x2e\x36\x32\x2c\x32\x34\x39\x2e\x36\x39\x20\x31\x31\x34\x2e\x32\ +\x39\x2c\x32\x34\x39\x2e\x36\x39\x20\x22\x2f\x3e\x0d\x0a\x20\x3c\ +\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x09\xb8\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x31\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x39\x39\x39\x39\x39\x39\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x34\x2e\x31\x31\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3a\x72\x6f\x75\x6e\x64\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\ +\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\ +\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\x23\ +\x46\x46\x39\x39\x33\x33\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\ +\x64\x74\x68\x3a\x31\x37\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\x65\ +\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3a\x72\x6f\x75\x6e\x64\x3b\ +\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\ +\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\ +\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\x6e\x65\ +\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x32\x20\x7b\x66\x69\ +\x6c\x6c\x3a\x23\x34\x36\x38\x32\x43\x38\x7d\x0d\x0a\x20\x20\x20\ +\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x23\x34\x36\ +\x38\x32\x43\x38\x3b\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x6e\ +\x6f\x6e\x7a\x65\x72\x6f\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\ +\x0a\x20\x20\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\ +\x64\x65\x66\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\x22\x43\ +\x61\x6d\x61\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\x3e\x0d\ +\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\x69\x64\x3d\ +\x22\x43\x6f\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\x43\x6f\ +\x72\x65\x6c\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\x20\x20\ +\x3c\x72\x65\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\ +\x30\x22\x20\x78\x3d\x22\x2d\x30\x2e\x30\x31\x22\x20\x79\x3d\x22\ +\x2d\x30\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\x30\x2e\x39\ +\x33\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\x39\ +\x33\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x67\x3e\x0d\x0a\x20\x20\x20\ +\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\ +\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x34\x32\ +\x2e\x32\x39\x2c\x34\x36\x2e\x39\x31\x20\x34\x32\x2e\x32\x39\x2c\ +\x32\x39\x2e\x32\x37\x20\x35\x32\x2e\x35\x33\x2c\x32\x39\x2e\x35\ +\x33\x20\x36\x32\x2e\x36\x36\x2c\x33\x30\x2e\x32\x39\x20\x37\x32\ +\x2e\x36\x33\x2c\x33\x31\x2e\x35\x36\x20\x38\x32\x2e\x34\x35\x2c\ +\x33\x33\x2e\x33\x32\x20\x39\x32\x2e\x30\x39\x2c\x33\x35\x2e\x35\ +\x35\x20\x31\x30\x31\x2e\x35\x36\x2c\x33\x38\x2e\x32\x33\x20\x31\ +\x31\x30\x2e\x38\x32\x2c\x34\x31\x2e\x33\x37\x20\x31\x31\x39\x2e\ +\x38\x38\x2c\x34\x34\x2e\x39\x34\x20\x31\x32\x38\x2e\x37\x31\x2c\ +\x34\x38\x2e\x39\x33\x20\x31\x33\x37\x2e\x33\x2c\x35\x33\x2e\x33\ +\x33\x20\x31\x34\x35\x2e\x36\x35\x2c\x35\x38\x2e\x31\x33\x20\x31\ +\x35\x33\x2e\x37\x34\x2c\x36\x33\x2e\x33\x31\x20\x31\x36\x31\x2e\ +\x35\x35\x2c\x36\x38\x2e\x38\x38\x20\x31\x36\x39\x2e\x30\x38\x2c\ +\x37\x34\x2e\x37\x39\x20\x31\x37\x36\x2e\x33\x32\x2c\x38\x31\x2e\ +\x30\x36\x20\x31\x38\x33\x2e\x32\x33\x2c\x38\x37\x2e\x36\x35\x20\ +\x31\x38\x39\x2e\x38\x32\x2c\x39\x34\x2e\x35\x36\x20\x31\x39\x36\ +\x2e\x30\x39\x2c\x31\x30\x31\x2e\x38\x20\x32\x30\x32\x2c\x31\x30\ +\x39\x2e\x33\x33\x20\x32\x30\x37\x2e\x35\x37\x2c\x31\x31\x37\x2e\ +\x31\x34\x20\x32\x31\x32\x2e\x37\x35\x2c\x31\x32\x35\x2e\x32\x33\ +\x20\x32\x31\x37\x2e\x35\x35\x2c\x31\x33\x33\x2e\x35\x38\x20\x32\ +\x32\x31\x2e\x39\x35\x2c\x31\x34\x32\x2e\x31\x37\x20\x32\x32\x35\ +\x2e\x39\x34\x2c\x31\x35\x31\x20\x32\x32\x39\x2e\x35\x31\x2c\x31\ +\x36\x30\x2e\x30\x36\x20\x32\x33\x32\x2e\x36\x35\x2c\x31\x36\x39\ +\x2e\x33\x32\x20\x32\x33\x35\x2e\x33\x33\x2c\x31\x37\x38\x2e\x37\ +\x39\x20\x32\x33\x37\x2e\x35\x36\x2c\x31\x38\x38\x2e\x34\x33\x20\ +\x32\x33\x39\x2e\x33\x32\x2c\x31\x39\x38\x2e\x32\x35\x20\x32\x34\ +\x30\x2e\x35\x39\x2c\x32\x30\x38\x2e\x32\x32\x20\x32\x34\x31\x2e\ +\x33\x35\x2c\x32\x31\x38\x2e\x33\x35\x20\x32\x34\x31\x2e\x36\x31\ +\x2c\x32\x32\x38\x2e\x35\x39\x20\x32\x32\x33\x2e\x39\x37\x2c\x32\ +\x32\x38\x2e\x35\x39\x20\x32\x32\x33\x2e\x37\x33\x2c\x32\x31\x39\ +\x2e\x32\x33\x20\x32\x32\x33\x2e\x30\x33\x2c\x32\x31\x30\x20\x32\ +\x32\x31\x2e\x38\x38\x2c\x32\x30\x30\x2e\x39\x31\x20\x32\x32\x30\ +\x2e\x32\x38\x2c\x31\x39\x31\x2e\x39\x37\x20\x32\x31\x38\x2e\x32\ +\x35\x2c\x31\x38\x33\x2e\x31\x37\x20\x32\x31\x35\x2e\x38\x31\x2c\ +\x31\x37\x34\x2e\x35\x36\x20\x32\x31\x32\x2e\x39\x35\x2c\x31\x36\ +\x36\x2e\x31\x32\x20\x32\x30\x39\x2e\x37\x2c\x31\x35\x37\x2e\x38\ +\x38\x20\x32\x30\x36\x2e\x30\x35\x2c\x31\x34\x39\x2e\x38\x33\x20\ +\x32\x30\x32\x2e\x30\x35\x2c\x31\x34\x32\x20\x31\x39\x37\x2e\x36\ +\x37\x2c\x31\x33\x34\x2e\x33\x39\x20\x31\x39\x32\x2e\x39\x35\x2c\ +\x31\x32\x37\x2e\x30\x32\x20\x31\x38\x37\x2e\x38\x38\x2c\x31\x31\ +\x39\x2e\x38\x39\x20\x31\x38\x32\x2e\x34\x39\x2c\x31\x31\x33\x2e\ +\x30\x32\x20\x31\x37\x36\x2e\x37\x38\x2c\x31\x30\x36\x2e\x34\x34\ +\x20\x31\x37\x30\x2e\x37\x35\x2c\x31\x30\x30\x2e\x31\x33\x20\x31\ +\x36\x34\x2e\x34\x34\x2c\x39\x34\x2e\x31\x20\x31\x35\x37\x2e\x38\ +\x36\x2c\x38\x38\x2e\x33\x39\x20\x31\x35\x30\x2e\x39\x39\x2c\x38\ +\x33\x20\x31\x34\x33\x2e\x38\x36\x2c\x37\x37\x2e\x39\x33\x20\x31\ +\x33\x36\x2e\x34\x39\x2c\x37\x33\x2e\x32\x31\x20\x31\x32\x38\x2e\ +\x38\x38\x2c\x36\x38\x2e\x38\x33\x20\x31\x32\x31\x2e\x30\x35\x2c\ +\x36\x34\x2e\x38\x33\x20\x31\x31\x33\x2c\x36\x31\x2e\x31\x38\x20\ +\x31\x30\x34\x2e\x37\x36\x2c\x35\x37\x2e\x39\x33\x20\x39\x36\x2e\ +\x33\x32\x2c\x35\x35\x2e\x30\x37\x20\x38\x37\x2e\x37\x31\x2c\x35\ +\x32\x2e\x36\x33\x20\x37\x38\x2e\x39\x31\x2c\x35\x30\x2e\x36\x20\ +\x36\x39\x2e\x39\x37\x2c\x34\x39\x20\x36\x30\x2e\x38\x38\x2c\x34\ +\x37\x2e\x38\x35\x20\x35\x31\x2e\x36\x35\x2c\x34\x37\x2e\x31\x35\ +\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\x20\x20\ +\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\ +\x30\x20\x73\x74\x72\x30\x22\x20\x64\x3d\x22\x4d\x31\x37\x39\x2e\ +\x39\x34\x20\x32\x36\x30\x2e\x32\x38\x6c\x30\x20\x2d\x32\x37\x2e\ +\x39\x35\x63\x30\x2c\x2d\x37\x38\x2e\x30\x37\x20\x2d\x36\x33\x2e\ +\x33\x37\x2c\x2d\x31\x34\x31\x2e\x33\x37\x20\x2d\x31\x34\x31\x2e\ +\x35\x33\x2c\x2d\x31\x34\x31\x2e\x33\x37\x6c\x2d\x32\x37\x2e\x38\ +\x20\x2d\x30\x2e\x30\x31\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\ +\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\ +\x32\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x32\x33\x32\x2e\x38\ +\x36\x2c\x32\x35\x33\x2e\x39\x37\x20\x32\x31\x39\x2e\x31\x2c\x32\ +\x33\x30\x2e\x31\x34\x20\x32\x30\x35\x2e\x33\x35\x2c\x32\x30\x36\ +\x2e\x33\x31\x20\x32\x33\x32\x2e\x38\x36\x2c\x32\x30\x36\x2e\x33\ +\x31\x20\x32\x36\x30\x2e\x33\x38\x2c\x32\x30\x36\x2e\x33\x31\x20\ +\x32\x34\x36\x2e\x36\x32\x2c\x32\x33\x30\x2e\x31\x34\x20\x22\x2f\ +\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\x20\x70\x6f\x69\x6e\x74\ +\x73\x3d\x22\x31\x37\x2e\x38\x36\x2c\x33\x37\x2e\x37\x38\x20\x34\ +\x31\x2e\x36\x39\x2c\x32\x34\x2e\x30\x32\x20\x36\x35\x2e\x35\x32\ +\x2c\x31\x30\x2e\x32\x37\x20\x36\x35\x2e\x35\x32\x2c\x33\x37\x2e\ +\x37\x38\x20\x36\x35\x2e\x35\x32\x2c\x36\x35\x2e\x33\x20\x34\x31\ +\x2e\x36\x39\x2c\x35\x31\x2e\x35\x34\x20\x22\x2f\x3e\x0d\x0a\x20\ +\x20\x3c\x6c\x69\x6e\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\ +\x6c\x30\x20\x73\x74\x72\x31\x22\x20\x78\x31\x3d\x22\x31\x33\x35\ +\x2e\x34\x37\x22\x20\x79\x31\x3d\x22\x32\x36\x32\x2e\x34\x33\x22\ +\x20\x78\x32\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x20\x79\x32\x3d\ +\x20\x22\x32\x36\x32\x2e\x34\x33\x22\x20\x2f\x3e\x0d\x0a\x20\x20\ +\x3c\x6c\x69\x6e\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\ +\x30\x20\x73\x74\x72\x31\x22\x20\x78\x31\x3d\x22\x38\x2e\x34\x35\ +\x22\x20\x79\x31\x3d\x22\x31\x33\x35\x2e\x34\x32\x22\x20\x78\x32\ +\x3d\x22\x38\x2e\x34\x35\x22\x20\x79\x32\x3d\x20\x22\x2d\x30\x2e\ +\x30\x34\x22\x20\x2f\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\ +\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x05\x0e\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x36\x36\x36\x36\x36\x36\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x37\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3a\x72\x6f\x75\x6e\x64\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\ +\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\ +\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\x6e\ +\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x32\x20\x7b\x66\ +\x69\x6c\x6c\x3a\x23\x30\x30\x39\x39\x33\x33\x7d\x0d\x0a\x20\x20\ +\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x77\x68\ +\x69\x74\x65\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\ +\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\ +\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\ +\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\ +\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\ +\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\ +\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\x65\ +\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\ +\x78\x3d\x22\x2d\x30\x2e\x30\x31\x22\x20\x79\x3d\x22\x2d\x30\x22\ +\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x20\ +\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x2f\ +\x3e\x0d\x0a\x20\x20\x3c\x6c\x69\x6e\x65\x20\x63\x6c\x61\x73\x73\ +\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x30\x22\x20\x78\x31\x3d\ +\x22\x32\x36\x32\x2e\x34\x36\x22\x20\x79\x31\x3d\x22\x32\x36\x32\ +\x2e\x34\x36\x22\x20\x78\x32\x3d\x22\x38\x2e\x34\x35\x22\x20\x79\ +\x32\x3d\x20\x22\x38\x2e\x34\x36\x22\x20\x2f\x3e\x0d\x0a\x20\x20\ +\x3c\x67\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\ +\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\ +\x69\x6e\x74\x73\x3d\x22\x31\x30\x35\x2e\x38\x32\x2c\x31\x36\x35\ +\x2e\x31\x20\x31\x36\x35\x2e\x31\x2c\x31\x36\x35\x2e\x31\x20\x31\ +\x36\x35\x2e\x31\x2c\x31\x30\x35\x2e\x38\x32\x20\x31\x30\x35\x2e\ +\x38\x32\x2c\x31\x30\x35\x2e\x38\x32\x20\x22\x2f\x3e\x0d\x0a\x20\ +\x20\x20\x3c\x70\x61\x74\x68\x20\x69\x64\x3d\x22\x5f\x31\x22\x20\ +\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\x20\x64\x3d\x22\ +\x4d\x31\x31\x38\x2e\x31\x37\x20\x31\x35\x32\x2e\x37\x35\x6c\x33\ +\x34\x2e\x35\x38\x20\x30\x20\x30\x20\x2d\x33\x34\x2e\x35\x38\x20\ +\x2d\x33\x34\x2e\x35\x38\x20\x30\x20\x30\x20\x33\x34\x2e\x35\x38\ +\x7a\x6d\x2d\x31\x32\x2e\x33\x35\x20\x31\x32\x2e\x33\x35\x6c\x35\ +\x39\x2e\x32\x38\x20\x30\x20\x30\x20\x2d\x35\x39\x2e\x32\x38\x20\ +\x2d\x35\x39\x2e\x32\x38\x20\x30\x20\x30\x20\x35\x39\x2e\x32\x38\ +\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\x20\x3c\ +\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x04\xd1\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\ +\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\ +\x66\x69\x6c\x6c\x3a\x23\x34\x36\x38\x32\x43\x38\x7d\x0d\x0a\x20\ +\x20\x20\x20\x2e\x66\x69\x6c\x32\x20\x7b\x66\x69\x6c\x6c\x3a\x23\ +\x46\x46\x39\x39\x33\x33\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\ +\x0a\x20\x20\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\ +\x64\x65\x66\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\x22\x43\ +\x61\x6d\x61\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\x3e\x0d\ +\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\x69\x64\x3d\ +\x22\x43\x6f\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\x43\x6f\ +\x72\x65\x6c\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\x20\x20\ +\x3c\x72\x65\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\ +\x30\x22\x20\x78\x3d\x22\x2d\x30\x2e\x30\x31\x22\x20\x79\x3d\x22\ +\x2d\x30\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\x30\x2e\x39\ +\x33\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\x39\ +\x33\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x67\x3e\x0d\x0a\x20\x20\x20\ +\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\ +\x31\x22\x20\x64\x3d\x22\x4d\x33\x34\x2e\x35\x37\x20\x32\x31\x30\ +\x2e\x39\x38\x6c\x32\x30\x31\x2e\x37\x39\x20\x30\x20\x30\x20\x2d\ +\x31\x35\x31\x2e\x30\x34\x20\x2d\x32\x30\x31\x2e\x37\x39\x20\x30\ +\x20\x30\x20\x31\x35\x31\x2e\x30\x34\x7a\x6d\x2d\x31\x37\x2e\x36\ +\x34\x20\x31\x37\x2e\x36\x34\x6c\x32\x33\x37\x2e\x30\x37\x20\x30\ +\x20\x30\x20\x2d\x31\x38\x36\x2e\x33\x32\x20\x2d\x32\x33\x37\x2e\ +\x30\x37\x20\x30\x20\x30\x20\x31\x38\x36\x2e\x33\x32\x7a\x22\x2f\ +\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\ +\x6c\x79\x67\x6f\x6e\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\x22\x20\ +\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\x20\x70\x6f\x69\ +\x6e\x74\x73\x3d\x22\x34\x2e\x33\x32\x2c\x31\x39\x38\x2e\x38\x39\ +\x20\x34\x36\x2e\x36\x35\x2c\x31\x39\x38\x2e\x38\x39\x20\x34\x36\ +\x2e\x36\x35\x2c\x32\x34\x31\x2e\x32\x32\x20\x34\x2e\x33\x32\x2c\ +\x32\x34\x31\x2e\x32\x32\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\ +\x6f\x6c\x79\x67\x6f\x6e\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\x5f\ +\x30\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\x20\ +\x70\x6f\x69\x6e\x74\x73\x3d\x22\x32\x32\x34\x2e\x32\x38\x2c\x32\ +\x39\x2e\x36\x39\x20\x32\x36\x36\x2e\x36\x31\x2c\x32\x39\x2e\x36\ +\x39\x20\x32\x36\x36\x2e\x36\x31\x2c\x37\x32\x2e\x30\x32\x20\x32\ +\x32\x34\x2e\x32\x38\x2c\x37\x32\x2e\x30\x32\x20\x22\x2f\x3e\x0d\ +\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\x0a\ +\ +\x00\x00\x05\x58\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x36\x36\x36\x36\x36\x36\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x37\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3a\x72\x6f\x75\x6e\x64\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\ +\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\ +\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\x6e\ +\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x32\x20\x7b\x66\ +\x69\x6c\x6c\x3a\x23\x30\x30\x39\x39\x33\x33\x7d\x0d\x0a\x20\x20\ +\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x77\x68\ +\x69\x74\x65\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\ +\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\ +\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\ +\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\ +\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\ +\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\ +\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\x65\ +\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\ +\x78\x3d\x22\x2d\x30\x2e\x30\x31\x22\x20\x79\x3d\x22\x2d\x30\x22\ +\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x20\ +\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x2f\ +\x3e\x0d\x0a\x20\x20\x3c\x6c\x69\x6e\x65\x20\x63\x6c\x61\x73\x73\ +\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x30\x22\x20\x78\x31\x3d\ +\x22\x32\x36\x32\x2e\x34\x36\x22\x20\x79\x31\x3d\x22\x32\x36\x32\ +\x2e\x34\x36\x22\x20\x78\x32\x3d\x22\x38\x2e\x34\x35\x22\x20\x79\ +\x32\x3d\x20\x22\x38\x2e\x34\x36\x22\x20\x2f\x3e\x0d\x0a\x20\x20\ +\x3c\x6c\x69\x6e\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\ +\x30\x20\x73\x74\x72\x30\x22\x20\x78\x31\x3d\x22\x38\x2e\x34\x36\ +\x22\x20\x79\x31\x3d\x22\x32\x36\x32\x2e\x34\x36\x22\x20\x78\x32\ +\x3d\x22\x32\x36\x32\x2e\x34\x37\x22\x20\x79\x32\x3d\x20\x22\x38\ +\x2e\x34\x36\x22\x20\x2f\x3e\x0d\x0a\x20\x20\x3c\x67\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\ +\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\ +\x22\x31\x36\x35\x2e\x31\x2c\x31\x36\x35\x2e\x31\x20\x31\x30\x35\ +\x2e\x38\x32\x2c\x31\x36\x35\x2e\x31\x20\x31\x30\x35\x2e\x38\x32\ +\x2c\x31\x30\x35\x2e\x38\x32\x20\x31\x36\x35\x2e\x31\x2c\x31\x30\ +\x35\x2e\x38\x32\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x61\ +\x74\x68\x20\x69\x64\x3d\x22\x5f\x31\x22\x20\x63\x6c\x61\x73\x73\ +\x3d\x22\x66\x69\x6c\x32\x22\x20\x64\x3d\x22\x4d\x31\x36\x35\x2e\ +\x31\x20\x31\x36\x35\x2e\x31\x6c\x2d\x35\x39\x2e\x32\x38\x20\x30\ +\x20\x30\x20\x2d\x35\x39\x2e\x32\x38\x20\x35\x39\x2e\x32\x38\x20\ +\x30\x20\x30\x20\x35\x39\x2e\x32\x38\x7a\x6d\x2d\x34\x36\x2e\x39\ +\x33\x20\x2d\x31\x32\x2e\x33\x35\x6c\x33\x34\x2e\x35\x38\x20\x30\ +\x20\x30\x20\x2d\x33\x34\x2e\x35\x38\x20\x2d\x33\x34\x2e\x35\x38\ +\x20\x30\x20\x30\x20\x33\x34\x2e\x35\x38\x7a\x22\x2f\x3e\x0d\x0a\ +\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\ +\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x22\x1b\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x20\x73\x74\x61\x6e\x64\x61\x6c\x6f\x6e\x65\x3d\x22\ +\x6e\x6f\x22\x3f\x3e\x0a\x3c\x21\x2d\x2d\x20\x43\x72\x65\x61\x74\ +\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\x41\x57\x20\x32\x30\ +\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\x20\x2d\x2d\x3e\x0a\ +\x0a\x3c\x73\x76\x67\x0a\x20\x20\x20\x78\x6d\x6c\x3a\x73\x70\x61\ +\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\x65\x22\x0a\x20\x20\ +\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\x6d\x6d\ +\x22\x0a\x20\x20\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\ +\x30\x39\x32\x6d\x6d\x22\x0a\x20\x20\x20\x76\x65\x72\x73\x69\x6f\ +\x6e\x3d\x22\x31\x2e\x31\x22\x0a\x20\x20\x20\x73\x74\x79\x6c\x65\ +\x3d\x22\x73\x68\x61\x70\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x67\x65\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\ +\x73\x69\x6f\x6e\x3b\x20\x74\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\ +\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\ +\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\ +\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\ +\x65\x51\x75\x61\x6c\x69\x74\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\ +\x75\x6c\x65\x3a\x65\x76\x65\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\ +\x70\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\x6e\x6f\x64\x64\x22\x0a\ +\x20\x20\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\x30\x20\x30\x20\ +\x32\x37\x30\x2e\x39\x32\x20\x32\x37\x30\x2e\x39\x32\x22\x0a\x20\ +\x20\x20\x69\x64\x3d\x22\x73\x76\x67\x32\x34\x22\x0a\x20\x20\x20\ +\x73\x6f\x64\x69\x70\x6f\x64\x69\x3a\x64\x6f\x63\x6e\x61\x6d\x65\ +\x3d\x22\x71\x61\x64\x2e\x73\x76\x67\x22\x0a\x20\x20\x20\x69\x6e\ +\x6b\x73\x63\x61\x70\x65\x3a\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\ +\x31\x2e\x33\x20\x28\x30\x65\x31\x35\x30\x65\x64\x36\x63\x34\x2c\ +\x20\x32\x30\x32\x33\x2d\x30\x37\x2d\x32\x31\x29\x22\x0a\x20\x20\ +\x20\x78\x6d\x6c\x6e\x73\x3a\x69\x6e\x6b\x73\x63\x61\x70\x65\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x69\x6e\x6b\x73\ +\x63\x61\x70\x65\x2e\x6f\x72\x67\x2f\x6e\x61\x6d\x65\x73\x70\x61\ +\x63\x65\x73\x2f\x69\x6e\x6b\x73\x63\x61\x70\x65\x22\x0a\x20\x20\ +\x20\x78\x6d\x6c\x6e\x73\x3a\x73\x6f\x64\x69\x70\x6f\x64\x69\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x73\x6f\x64\x69\x70\x6f\x64\x69\ +\x2e\x73\x6f\x75\x72\x63\x65\x66\x6f\x72\x67\x65\x2e\x6e\x65\x74\ +\x2f\x44\x54\x44\x2f\x73\x6f\x64\x69\x70\x6f\x64\x69\x2d\x30\x2e\ +\x64\x74\x64\x22\x0a\x20\x20\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0a\x20\x20\x20\x78\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\ +\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\ +\x30\x30\x30\x2f\x73\x76\x67\x22\x0a\x20\x20\x20\x78\x6d\x6c\x6e\ +\x73\x3a\x73\x76\x67\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\ +\x67\x22\x3e\x3c\x73\x6f\x64\x69\x70\x6f\x64\x69\x3a\x6e\x61\x6d\ +\x65\x64\x76\x69\x65\x77\x0a\x20\x20\x20\x69\x64\x3d\x22\x6e\x61\ +\x6d\x65\x64\x76\x69\x65\x77\x32\x34\x22\x0a\x20\x20\x20\x70\x61\ +\x67\x65\x63\x6f\x6c\x6f\x72\x3d\x22\x23\x66\x66\x66\x66\x66\x66\ +\x22\x0a\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x63\x6f\x6c\x6f\x72\ +\x3d\x22\x23\x30\x30\x30\x30\x30\x30\x22\x0a\x20\x20\x20\x62\x6f\ +\x72\x64\x65\x72\x6f\x70\x61\x63\x69\x74\x79\x3d\x22\x30\x2e\x32\ +\x35\x22\x0a\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x73\ +\x68\x6f\x77\x70\x61\x67\x65\x73\x68\x61\x64\x6f\x77\x3d\x22\x32\ +\x22\x0a\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x70\x61\ +\x67\x65\x6f\x70\x61\x63\x69\x74\x79\x3d\x22\x30\x2e\x30\x22\x0a\ +\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x70\x61\x67\x65\ +\x63\x68\x65\x63\x6b\x65\x72\x62\x6f\x61\x72\x64\x3d\x22\x30\x22\ +\x0a\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x64\x65\x73\ +\x6b\x63\x6f\x6c\x6f\x72\x3d\x22\x23\x64\x31\x64\x31\x64\x31\x22\ +\x0a\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x64\x6f\x63\ +\x75\x6d\x65\x6e\x74\x2d\x75\x6e\x69\x74\x73\x3d\x22\x6d\x6d\x22\ +\x0a\x20\x20\x20\x73\x68\x6f\x77\x67\x72\x69\x64\x3d\x22\x66\x61\ +\x6c\x73\x65\x22\x0a\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\ +\x3a\x7a\x6f\x6f\x6d\x3d\x22\x31\x32\x33\x2e\x37\x34\x39\x37\x38\ +\x22\x0a\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x63\x78\ +\x3d\x22\x35\x2e\x35\x31\x31\x31\x32\x30\x39\x22\x0a\x20\x20\x20\ +\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x63\x79\x3d\x22\x34\x2e\x38\ +\x38\x30\x38\x31\x36\x37\x22\x0a\x20\x20\x20\x69\x6e\x6b\x73\x63\ +\x61\x70\x65\x3a\x77\x69\x6e\x64\x6f\x77\x2d\x77\x69\x64\x74\x68\ +\x3d\x22\x33\x38\x34\x30\x22\x0a\x20\x20\x20\x69\x6e\x6b\x73\x63\ +\x61\x70\x65\x3a\x77\x69\x6e\x64\x6f\x77\x2d\x68\x65\x69\x67\x68\ +\x74\x3d\x22\x32\x30\x35\x30\x22\x0a\x20\x20\x20\x69\x6e\x6b\x73\ +\x63\x61\x70\x65\x3a\x77\x69\x6e\x64\x6f\x77\x2d\x78\x3d\x22\x2d\ +\x31\x32\x22\x0a\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\ +\x77\x69\x6e\x64\x6f\x77\x2d\x79\x3d\x22\x2d\x31\x32\x22\x0a\x20\ +\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x77\x69\x6e\x64\x6f\ +\x77\x2d\x6d\x61\x78\x69\x6d\x69\x7a\x65\x64\x3d\x22\x31\x22\x0a\ +\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x63\x75\x72\x72\ +\x65\x6e\x74\x2d\x6c\x61\x79\x65\x72\x3d\x22\x43\x61\x6d\x61\x64\ +\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\x20\x2f\x3e\x26\x23\x31\ +\x30\x3b\x20\x3c\x64\x65\x66\x73\x0a\x20\x20\x20\x69\x64\x3d\x22\ +\x64\x65\x66\x73\x31\x39\x22\x3e\x3c\x72\x61\x64\x69\x61\x6c\x47\ +\x72\x61\x64\x69\x65\x6e\x74\x0a\x20\x20\x20\x69\x64\x3d\x22\x72\ +\x61\x64\x69\x61\x6c\x47\x72\x61\x64\x69\x65\x6e\x74\x32\x39\x22\ +\x0a\x20\x20\x20\x67\x72\x61\x64\x69\x65\x6e\x74\x55\x6e\x69\x74\ +\x73\x3d\x22\x75\x73\x65\x72\x53\x70\x61\x63\x65\x4f\x6e\x55\x73\ +\x65\x22\x0a\x20\x20\x20\x67\x72\x61\x64\x69\x65\x6e\x74\x54\x72\ +\x61\x6e\x73\x66\x6f\x72\x6d\x3d\x22\x6d\x61\x74\x72\x69\x78\x28\ +\x2d\x30\x2e\x30\x32\x32\x30\x36\x39\x35\x20\x31\x2e\x31\x37\x36\ +\x30\x37\x20\x2d\x31\x2e\x32\x35\x37\x39\x36\x20\x38\x2e\x31\x36\ +\x37\x36\x34\x45\x2d\x30\x36\x20\x32\x32\x39\x20\x2d\x39\x37\x29\ +\x22\x0a\x20\x20\x20\x63\x78\x3d\x22\x31\x34\x30\x2e\x34\x33\x22\ +\x0a\x20\x20\x20\x63\x79\x3d\x22\x36\x38\x2e\x33\x33\x22\x0a\x20\ +\x20\x20\x72\x3d\x22\x34\x38\x39\x37\x2e\x34\x31\x22\x0a\x20\x20\ +\x20\x66\x78\x3d\x22\x31\x34\x30\x2e\x34\x33\x22\x0a\x20\x20\x20\ +\x66\x79\x3d\x22\x36\x38\x2e\x33\x33\x22\x3e\x26\x23\x31\x30\x3b\ +\x20\x20\x20\x3c\x73\x74\x6f\x70\x0a\x20\x20\x20\x6f\x66\x66\x73\ +\x65\x74\x3d\x22\x30\x22\x0a\x20\x20\x20\x73\x74\x79\x6c\x65\x3d\ +\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\ +\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x77\x68\x69\x74\ +\x65\x22\x0a\x20\x20\x20\x69\x64\x3d\x22\x73\x74\x6f\x70\x32\x34\ +\x22\x20\x2f\x3e\x26\x23\x31\x30\x3b\x20\x20\x20\x3c\x73\x74\x6f\ +\x70\x0a\x20\x20\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\x30\x2e\x31\ +\x38\x36\x30\x36\x33\x35\x36\x22\x0a\x20\x20\x20\x73\x74\x79\x6c\ +\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\ +\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x77\x68\ +\x69\x74\x65\x22\x0a\x20\x20\x20\x69\x64\x3d\x22\x73\x74\x6f\x70\ +\x32\x35\x22\x20\x2f\x3e\x26\x23\x31\x30\x3b\x20\x20\x20\x3c\x73\ +\x74\x6f\x70\x0a\x20\x20\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\x30\ +\x2e\x35\x30\x31\x39\x36\x31\x22\x0a\x20\x20\x20\x73\x74\x79\x6c\ +\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\ +\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x43\ +\x43\x43\x43\x43\x43\x22\x0a\x20\x20\x20\x69\x64\x3d\x22\x73\x74\ +\x6f\x70\x32\x36\x22\x20\x2f\x3e\x26\x23\x31\x30\x3b\x20\x20\x20\ +\x3c\x73\x74\x6f\x70\x0a\x20\x20\x20\x6f\x66\x66\x73\x65\x74\x3d\ +\x22\x30\x2e\x37\x34\x39\x30\x32\x22\x0a\x20\x20\x20\x73\x74\x79\ +\x6c\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\ +\x3a\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\ +\x41\x36\x41\x36\x41\x36\x22\x0a\x20\x20\x20\x69\x64\x3d\x22\x73\ +\x74\x6f\x70\x32\x37\x22\x20\x2f\x3e\x26\x23\x31\x30\x3b\x20\x20\ +\x20\x3c\x73\x74\x6f\x70\x0a\x20\x20\x20\x6f\x66\x66\x73\x65\x74\ +\x3d\x22\x30\x2e\x38\x38\x36\x32\x37\x35\x22\x0a\x20\x20\x20\x73\ +\x74\x79\x6c\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\ +\x74\x79\x3a\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\ +\x3a\x23\x39\x33\x39\x33\x39\x33\x22\x0a\x20\x20\x20\x69\x64\x3d\ +\x22\x73\x74\x6f\x70\x32\x38\x22\x20\x2f\x3e\x26\x23\x31\x30\x3b\ +\x20\x20\x20\x3c\x73\x74\x6f\x70\x0a\x20\x20\x20\x6f\x66\x66\x73\ +\x65\x74\x3d\x22\x31\x22\x0a\x20\x20\x20\x73\x74\x79\x6c\x65\x3d\ +\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\ +\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x67\x72\x61\x79\ +\x22\x0a\x20\x20\x20\x69\x64\x3d\x22\x73\x74\x6f\x70\x32\x39\x22\ +\x20\x2f\x3e\x26\x23\x31\x30\x3b\x20\x20\x3c\x2f\x72\x61\x64\x69\ +\x61\x6c\x47\x72\x61\x64\x69\x65\x6e\x74\x3e\x26\x23\x31\x30\x3b\ +\x20\x20\x3c\x73\x74\x79\x6c\x65\x0a\x20\x20\x20\x74\x79\x70\x65\ +\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x0a\x20\x20\x20\x69\ +\x64\x3d\x22\x73\x74\x79\x6c\x65\x31\x22\x3e\x26\x23\x31\x30\x3b\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0a\x20\x20\x20\ +\x20\x2e\x66\x69\x6c\x33\x20\x7b\x66\x69\x6c\x6c\x3a\x75\x72\x6c\ +\x28\x23\x69\x64\x30\x29\x7d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\ +\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x75\x72\x6c\x28\x23\x69\x64\x31\ +\x29\x7d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\ +\x6c\x6c\x3a\x75\x72\x6c\x28\x23\x69\x64\x32\x29\x7d\x0a\x20\x20\ +\x20\x20\x2e\x66\x69\x6c\x32\x20\x7b\x66\x69\x6c\x6c\x3a\x75\x72\ +\x6c\x28\x23\x69\x64\x33\x29\x7d\x0a\x20\x20\x20\x20\x2e\x66\x69\ +\x6c\x34\x20\x7b\x66\x69\x6c\x6c\x3a\x75\x72\x6c\x28\x23\x69\x64\ +\x34\x29\x7d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x35\x20\x7b\x66\ +\x69\x6c\x6c\x3a\x75\x72\x6c\x28\x23\x69\x64\x35\x29\x7d\x0a\x20\ +\x20\x20\x5d\x5d\x3e\x26\x23\x31\x30\x3b\x20\x20\x3c\x2f\x73\x74\ +\x79\x6c\x65\x3e\x26\x23\x31\x30\x3b\x20\x20\x3c\x6c\x69\x6e\x65\ +\x61\x72\x47\x72\x61\x64\x69\x65\x6e\x74\x0a\x20\x20\x20\x69\x64\ +\x3d\x22\x69\x64\x30\x22\x0a\x20\x20\x20\x67\x72\x61\x64\x69\x65\ +\x6e\x74\x55\x6e\x69\x74\x73\x3d\x22\x75\x73\x65\x72\x53\x70\x61\ +\x63\x65\x4f\x6e\x55\x73\x65\x22\x0a\x20\x20\x20\x78\x31\x3d\x22\ +\x31\x37\x33\x2e\x32\x32\x22\x0a\x20\x20\x20\x79\x31\x3d\x22\x32\ +\x37\x30\x2e\x33\x35\x22\x0a\x20\x20\x20\x78\x32\x3d\x22\x31\x37\ +\x33\x2e\x32\x32\x22\x0a\x20\x20\x20\x79\x32\x3d\x22\x32\x34\x35\ +\x2e\x34\x32\x22\x3e\x26\x23\x31\x30\x3b\x20\x20\x20\x3c\x73\x74\ +\x6f\x70\x0a\x20\x20\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\x30\x22\ +\x0a\x20\x20\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x74\x6f\x70\x2d\ +\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\x20\x73\x74\x6f\x70\x2d\ +\x63\x6f\x6c\x6f\x72\x3a\x23\x32\x38\x34\x46\x30\x30\x22\x0a\x20\ +\x20\x20\x69\x64\x3d\x22\x73\x74\x6f\x70\x31\x22\x20\x2f\x3e\x26\ +\x23\x31\x30\x3b\x20\x20\x20\x3c\x73\x74\x6f\x70\x0a\x20\x20\x20\ +\x6f\x66\x66\x73\x65\x74\x3d\x22\x31\x22\x0a\x20\x20\x20\x73\x74\ +\x79\x6c\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\ +\x79\x3a\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\ +\x23\x34\x41\x39\x34\x30\x30\x22\x0a\x20\x20\x20\x69\x64\x3d\x22\ +\x73\x74\x6f\x70\x32\x22\x20\x2f\x3e\x26\x23\x31\x30\x3b\x20\x20\ +\x3c\x2f\x6c\x69\x6e\x65\x61\x72\x47\x72\x61\x64\x69\x65\x6e\x74\ +\x3e\x26\x23\x31\x30\x3b\x20\x20\x3c\x6c\x69\x6e\x65\x61\x72\x47\ +\x72\x61\x64\x69\x65\x6e\x74\x0a\x20\x20\x20\x69\x64\x3d\x22\x69\ +\x64\x31\x22\x0a\x20\x20\x20\x67\x72\x61\x64\x69\x65\x6e\x74\x55\ +\x6e\x69\x74\x73\x3d\x22\x75\x73\x65\x72\x53\x70\x61\x63\x65\x4f\ +\x6e\x55\x73\x65\x22\x0a\x20\x20\x20\x78\x31\x3d\x22\x31\x39\x31\ +\x2e\x31\x38\x22\x0a\x20\x20\x20\x79\x31\x3d\x22\x31\x39\x37\x2e\ +\x31\x31\x22\x0a\x20\x20\x20\x78\x32\x3d\x22\x31\x39\x30\x2e\x38\ +\x38\x22\x0a\x20\x20\x20\x79\x32\x3d\x22\x2d\x31\x2e\x39\x36\x22\ +\x3e\x26\x23\x31\x30\x3b\x20\x20\x20\x3c\x73\x74\x6f\x70\x0a\x20\ +\x20\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\x30\x22\x0a\x20\x20\x20\ +\x73\x74\x79\x6c\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\ +\x69\x74\x79\x3a\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\ +\x72\x3a\x23\x32\x38\x34\x46\x30\x30\x22\x0a\x20\x20\x20\x69\x64\ +\x3d\x22\x73\x74\x6f\x70\x33\x22\x20\x2f\x3e\x26\x23\x31\x30\x3b\ +\x20\x20\x20\x3c\x73\x74\x6f\x70\x0a\x20\x20\x20\x6f\x66\x66\x73\ +\x65\x74\x3d\x22\x31\x22\x0a\x20\x20\x20\x73\x74\x79\x6c\x65\x3d\ +\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\ +\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x37\x41\x43\ +\x46\x32\x35\x22\x0a\x20\x20\x20\x69\x64\x3d\x22\x73\x74\x6f\x70\ +\x34\x22\x20\x2f\x3e\x26\x23\x31\x30\x3b\x20\x20\x3c\x2f\x6c\x69\ +\x6e\x65\x61\x72\x47\x72\x61\x64\x69\x65\x6e\x74\x3e\x26\x23\x31\ +\x30\x3b\x20\x20\x3c\x6c\x69\x6e\x65\x61\x72\x47\x72\x61\x64\x69\ +\x65\x6e\x74\x0a\x20\x20\x20\x69\x64\x3d\x22\x69\x64\x32\x22\x0a\ +\x20\x20\x20\x67\x72\x61\x64\x69\x65\x6e\x74\x55\x6e\x69\x74\x73\ +\x3d\x22\x75\x73\x65\x72\x53\x70\x61\x63\x65\x4f\x6e\x55\x73\x65\ +\x22\x0a\x20\x20\x20\x78\x31\x3d\x22\x31\x36\x37\x2e\x37\x22\x0a\ +\x20\x20\x20\x79\x31\x3d\x22\x31\x34\x38\x2e\x39\x37\x22\x0a\x20\ +\x20\x20\x78\x32\x3d\x22\x31\x36\x37\x2e\x37\x22\x0a\x20\x20\x20\ +\x79\x32\x3d\x22\x31\x33\x39\x2e\x36\x34\x22\x3e\x26\x23\x31\x30\ +\x3b\x20\x20\x20\x3c\x73\x74\x6f\x70\x0a\x20\x20\x20\x6f\x66\x66\ +\x73\x65\x74\x3d\x22\x30\x22\x0a\x20\x20\x20\x73\x74\x79\x6c\x65\ +\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\ +\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x33\x36\ +\x36\x42\x30\x30\x22\x0a\x20\x20\x20\x69\x64\x3d\x22\x73\x74\x6f\ +\x70\x35\x22\x20\x2f\x3e\x26\x23\x31\x30\x3b\x20\x20\x20\x3c\x73\ +\x74\x6f\x70\x0a\x20\x20\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\x31\ +\x22\x0a\x20\x20\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x74\x6f\x70\ +\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\x20\x73\x74\x6f\x70\ +\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x38\x44\x45\x42\x32\x46\x22\x0a\ +\x20\x20\x20\x69\x64\x3d\x22\x73\x74\x6f\x70\x36\x22\x20\x2f\x3e\ +\x26\x23\x31\x30\x3b\x20\x20\x3c\x2f\x6c\x69\x6e\x65\x61\x72\x47\ +\x72\x61\x64\x69\x65\x6e\x74\x3e\x26\x23\x31\x30\x3b\x20\x20\x3c\ +\x6c\x69\x6e\x65\x61\x72\x47\x72\x61\x64\x69\x65\x6e\x74\x0a\x20\ +\x20\x20\x69\x64\x3d\x22\x69\x64\x33\x22\x0a\x20\x20\x20\x67\x72\ +\x61\x64\x69\x65\x6e\x74\x55\x6e\x69\x74\x73\x3d\x22\x75\x73\x65\ +\x72\x53\x70\x61\x63\x65\x4f\x6e\x55\x73\x65\x22\x0a\x20\x20\x20\ +\x78\x31\x3d\x22\x31\x38\x38\x2e\x31\x34\x22\x0a\x20\x20\x20\x79\ +\x31\x3d\x22\x31\x39\x37\x2e\x31\x31\x22\x0a\x20\x20\x20\x78\x32\ +\x3d\x22\x31\x38\x37\x2e\x36\x33\x22\x0a\x20\x20\x20\x79\x32\x3d\ +\x22\x32\x35\x39\x2e\x31\x37\x22\x3e\x26\x23\x31\x30\x3b\x20\x20\ +\x20\x3c\x73\x74\x6f\x70\x0a\x20\x20\x20\x6f\x66\x66\x73\x65\x74\ +\x3d\x22\x30\x22\x0a\x20\x20\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\ +\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\x20\x73\ +\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x33\x33\x37\x35\x30\ +\x30\x22\x0a\x20\x20\x20\x69\x64\x3d\x22\x73\x74\x6f\x70\x37\x22\ +\x20\x2f\x3e\x26\x23\x31\x30\x3b\x20\x20\x20\x3c\x73\x74\x6f\x70\ +\x0a\x20\x20\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\x30\x2e\x32\x35\ +\x38\x38\x32\x34\x22\x0a\x20\x20\x20\x73\x74\x79\x6c\x65\x3d\x22\ +\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\x20\ +\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x33\x33\x37\x35\ +\x30\x30\x22\x0a\x20\x20\x20\x69\x64\x3d\x22\x73\x74\x6f\x70\x38\ +\x22\x20\x2f\x3e\x26\x23\x31\x30\x3b\x20\x20\x20\x3c\x73\x74\x6f\ +\x70\x0a\x20\x20\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\x30\x2e\x35\ +\x30\x39\x38\x30\x34\x22\x0a\x20\x20\x20\x73\x74\x79\x6c\x65\x3d\ +\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\ +\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x37\x39\x43\ +\x39\x33\x43\x22\x0a\x20\x20\x20\x69\x64\x3d\x22\x73\x74\x6f\x70\ +\x39\x22\x20\x2f\x3e\x26\x23\x31\x30\x3b\x20\x20\x20\x3c\x73\x74\ +\x6f\x70\x0a\x20\x20\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\x30\x2e\ +\x36\x39\x30\x31\x39\x36\x22\x0a\x20\x20\x20\x73\x74\x79\x6c\x65\ +\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\ +\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x33\x33\ +\x37\x35\x30\x30\x22\x0a\x20\x20\x20\x69\x64\x3d\x22\x73\x74\x6f\ +\x70\x31\x30\x22\x20\x2f\x3e\x26\x23\x31\x30\x3b\x20\x20\x20\x3c\ +\x73\x74\x6f\x70\x0a\x20\x20\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\ +\x30\x2e\x38\x35\x38\x38\x32\x34\x22\x0a\x20\x20\x20\x73\x74\x79\ +\x6c\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\ +\x3a\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\ +\x33\x33\x37\x35\x30\x30\x22\x0a\x20\x20\x20\x69\x64\x3d\x22\x73\ +\x74\x6f\x70\x31\x31\x22\x20\x2f\x3e\x26\x23\x31\x30\x3b\x20\x20\ +\x20\x3c\x73\x74\x6f\x70\x0a\x20\x20\x20\x6f\x66\x66\x73\x65\x74\ +\x3d\x22\x30\x2e\x39\x32\x39\x34\x31\x32\x22\x0a\x20\x20\x20\x73\ +\x74\x79\x6c\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\ +\x74\x79\x3a\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\ +\x3a\x23\x39\x31\x45\x30\x35\x35\x22\x0a\x20\x20\x20\x69\x64\x3d\ +\x22\x73\x74\x6f\x70\x31\x32\x22\x20\x2f\x3e\x26\x23\x31\x30\x3b\ +\x20\x20\x20\x3c\x73\x74\x6f\x70\x0a\x20\x20\x20\x6f\x66\x66\x73\ +\x65\x74\x3d\x22\x31\x22\x0a\x20\x20\x20\x73\x74\x79\x6c\x65\x3d\ +\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\ +\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x32\x41\x36\ +\x31\x30\x30\x22\x0a\x20\x20\x20\x69\x64\x3d\x22\x73\x74\x6f\x70\ +\x31\x33\x22\x20\x2f\x3e\x26\x23\x31\x30\x3b\x20\x20\x3c\x2f\x6c\ +\x69\x6e\x65\x61\x72\x47\x72\x61\x64\x69\x65\x6e\x74\x3e\x26\x23\ +\x31\x30\x3b\x20\x20\x3c\x72\x61\x64\x69\x61\x6c\x47\x72\x61\x64\ +\x69\x65\x6e\x74\x0a\x20\x20\x20\x69\x64\x3d\x22\x69\x64\x34\x22\ +\x0a\x20\x20\x20\x67\x72\x61\x64\x69\x65\x6e\x74\x55\x6e\x69\x74\ +\x73\x3d\x22\x75\x73\x65\x72\x53\x70\x61\x63\x65\x4f\x6e\x55\x73\ +\x65\x22\x0a\x20\x20\x20\x67\x72\x61\x64\x69\x65\x6e\x74\x54\x72\ +\x61\x6e\x73\x66\x6f\x72\x6d\x3d\x22\x6d\x61\x74\x72\x69\x78\x28\ +\x2d\x30\x2e\x30\x32\x32\x30\x36\x39\x35\x20\x31\x2e\x31\x37\x36\ +\x30\x37\x20\x2d\x31\x2e\x32\x35\x37\x39\x36\x20\x38\x2e\x31\x36\ +\x37\x36\x34\x45\x2d\x30\x36\x20\x32\x32\x39\x20\x2d\x39\x37\x29\ +\x22\x0a\x20\x20\x20\x63\x78\x3d\x22\x31\x34\x30\x2e\x34\x33\x22\ +\x0a\x20\x20\x20\x63\x79\x3d\x22\x36\x38\x2e\x33\x33\x22\x0a\x20\ +\x20\x20\x72\x3d\x22\x34\x38\x39\x37\x2e\x34\x31\x22\x0a\x20\x20\ +\x20\x66\x78\x3d\x22\x31\x34\x30\x2e\x34\x33\x22\x0a\x20\x20\x20\ +\x66\x79\x3d\x22\x36\x38\x2e\x33\x33\x22\x3e\x26\x23\x31\x30\x3b\ +\x20\x20\x20\x3c\x73\x74\x6f\x70\x0a\x20\x20\x20\x6f\x66\x66\x73\ +\x65\x74\x3d\x22\x30\x22\x0a\x20\x20\x20\x73\x74\x79\x6c\x65\x3d\ +\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\ +\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x77\x68\x69\x74\ +\x65\x22\x0a\x20\x20\x20\x69\x64\x3d\x22\x73\x74\x6f\x70\x31\x34\ +\x22\x20\x2f\x3e\x26\x23\x31\x30\x3b\x20\x20\x20\x3c\x73\x74\x6f\ +\x70\x0a\x20\x20\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\x30\x2e\x31\ +\x36\x36\x38\x30\x30\x30\x35\x22\x0a\x20\x20\x20\x73\x74\x79\x6c\ +\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\ +\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x77\x68\ +\x69\x74\x65\x22\x0a\x20\x20\x20\x69\x64\x3d\x22\x73\x74\x6f\x70\ +\x31\x35\x22\x20\x2f\x3e\x26\x23\x31\x30\x3b\x20\x20\x20\x3c\x73\ +\x74\x6f\x70\x0a\x20\x20\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\x30\ +\x2e\x35\x30\x31\x39\x36\x31\x22\x0a\x20\x20\x20\x73\x74\x79\x6c\ +\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\ +\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x43\ +\x43\x43\x43\x43\x43\x22\x0a\x20\x20\x20\x69\x64\x3d\x22\x73\x74\ +\x6f\x70\x31\x36\x22\x20\x2f\x3e\x26\x23\x31\x30\x3b\x20\x20\x20\ +\x3c\x73\x74\x6f\x70\x0a\x20\x20\x20\x6f\x66\x66\x73\x65\x74\x3d\ +\x22\x30\x2e\x37\x34\x39\x30\x32\x22\x0a\x20\x20\x20\x73\x74\x79\ +\x6c\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\ +\x3a\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\ +\x41\x36\x41\x36\x41\x36\x22\x0a\x20\x20\x20\x69\x64\x3d\x22\x73\ +\x74\x6f\x70\x31\x37\x22\x20\x2f\x3e\x26\x23\x31\x30\x3b\x20\x20\ +\x20\x3c\x73\x74\x6f\x70\x0a\x20\x20\x20\x6f\x66\x66\x73\x65\x74\ +\x3d\x22\x30\x2e\x38\x38\x36\x32\x37\x35\x22\x0a\x20\x20\x20\x73\ +\x74\x79\x6c\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\ +\x74\x79\x3a\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\ +\x3a\x23\x39\x33\x39\x33\x39\x33\x22\x0a\x20\x20\x20\x69\x64\x3d\ +\x22\x73\x74\x6f\x70\x31\x38\x22\x20\x2f\x3e\x26\x23\x31\x30\x3b\ +\x20\x20\x20\x3c\x73\x74\x6f\x70\x0a\x20\x20\x20\x6f\x66\x66\x73\ +\x65\x74\x3d\x22\x31\x22\x0a\x20\x20\x20\x73\x74\x79\x6c\x65\x3d\ +\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\ +\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x67\x72\x61\x79\ +\x22\x0a\x20\x20\x20\x69\x64\x3d\x22\x73\x74\x6f\x70\x31\x39\x22\ +\x20\x2f\x3e\x26\x23\x31\x30\x3b\x20\x20\x3c\x2f\x72\x61\x64\x69\ +\x61\x6c\x47\x72\x61\x64\x69\x65\x6e\x74\x3e\x26\x23\x31\x30\x3b\ +\x20\x20\x3c\x72\x61\x64\x69\x61\x6c\x47\x72\x61\x64\x69\x65\x6e\ +\x74\x0a\x20\x20\x20\x69\x64\x3d\x22\x69\x64\x35\x22\x0a\x20\x20\ +\x20\x67\x72\x61\x64\x69\x65\x6e\x74\x55\x6e\x69\x74\x73\x3d\x22\ +\x75\x73\x65\x72\x53\x70\x61\x63\x65\x4f\x6e\x55\x73\x65\x22\x0a\ +\x20\x20\x20\x67\x72\x61\x64\x69\x65\x6e\x74\x54\x72\x61\x6e\x73\ +\x66\x6f\x72\x6d\x3d\x22\x6d\x61\x74\x72\x69\x78\x28\x2d\x38\x2e\ +\x38\x39\x35\x33\x30\x35\x31\x65\x2d\x35\x2c\x30\x2e\x30\x33\x35\ +\x32\x36\x31\x34\x34\x2c\x2d\x30\x2e\x30\x34\x31\x39\x38\x35\x39\ +\x32\x2c\x2d\x31\x2e\x30\x35\x39\x35\x34\x33\x65\x2d\x34\x2c\x31\ +\x31\x39\x2e\x37\x33\x36\x34\x31\x2c\x35\x35\x2e\x38\x39\x38\x39\ +\x36\x39\x29\x22\x0a\x20\x20\x20\x78\x6c\x69\x6e\x6b\x3a\x68\x72\ +\x65\x66\x3d\x22\x23\x72\x61\x64\x69\x61\x6c\x47\x72\x61\x64\x69\ +\x65\x6e\x74\x32\x39\x22\x0a\x20\x20\x20\x63\x78\x3d\x22\x31\x38\ +\x30\x2e\x37\x33\x35\x30\x39\x22\x0a\x20\x20\x20\x63\x79\x3d\x22\ +\x32\x32\x2e\x31\x39\x31\x31\x37\x35\x22\x0a\x20\x20\x20\x72\x3d\ +\x22\x34\x39\x32\x30\x2e\x37\x32\x22\x0a\x20\x20\x20\x66\x78\x3d\ +\x22\x31\x38\x30\x2e\x37\x33\x35\x30\x39\x22\x0a\x20\x20\x20\x66\ +\x79\x3d\x22\x32\x32\x2e\x31\x39\x31\x31\x37\x35\x22\x3e\x26\x23\ +\x31\x30\x3b\x20\x20\x3c\x2f\x72\x61\x64\x69\x61\x6c\x47\x72\x61\ +\x64\x69\x65\x6e\x74\x3e\x26\x23\x31\x30\x3b\x20\x3c\x72\x61\x64\ +\x69\x61\x6c\x47\x72\x61\x64\x69\x65\x6e\x74\x0a\x20\x20\x20\x69\ +\x6e\x6b\x73\x63\x61\x70\x65\x3a\x63\x6f\x6c\x6c\x65\x63\x74\x3d\ +\x22\x61\x6c\x77\x61\x79\x73\x22\x0a\x20\x20\x20\x78\x6c\x69\x6e\ +\x6b\x3a\x68\x72\x65\x66\x3d\x22\x23\x69\x64\x34\x22\x0a\x20\x20\ +\x20\x69\x64\x3d\x22\x72\x61\x64\x69\x61\x6c\x47\x72\x61\x64\x69\ +\x65\x6e\x74\x32\x34\x22\x0a\x20\x20\x20\x67\x72\x61\x64\x69\x65\ +\x6e\x74\x55\x6e\x69\x74\x73\x3d\x22\x75\x73\x65\x72\x53\x70\x61\ +\x63\x65\x4f\x6e\x55\x73\x65\x22\x0a\x20\x20\x20\x67\x72\x61\x64\ +\x69\x65\x6e\x74\x54\x72\x61\x6e\x73\x66\x6f\x72\x6d\x3d\x22\x6d\ +\x61\x74\x72\x69\x78\x28\x31\x2e\x37\x38\x32\x33\x37\x37\x37\x65\ +\x2d\x34\x2c\x30\x2e\x30\x33\x32\x30\x30\x30\x35\x38\x2c\x2d\x30\ +\x2e\x30\x35\x31\x32\x37\x31\x32\x36\x2c\x32\x2e\x38\x36\x36\x33\ +\x39\x38\x37\x65\x2d\x34\x2c\x31\x34\x33\x2e\x34\x32\x32\x37\x31\ +\x2c\x36\x33\x2e\x36\x34\x32\x36\x33\x33\x29\x22\x0a\x20\x20\x20\ +\x63\x78\x3d\x22\x2d\x31\x31\x36\x2e\x38\x35\x30\x30\x36\x22\x0a\ +\x20\x20\x20\x63\x79\x3d\x22\x34\x34\x36\x2e\x39\x31\x32\x36\x33\ +\x22\x0a\x20\x20\x20\x66\x78\x3d\x22\x2d\x31\x31\x36\x2e\x38\x35\ +\x30\x30\x36\x22\x0a\x20\x20\x20\x66\x79\x3d\x22\x34\x34\x36\x2e\ +\x39\x31\x32\x36\x33\x22\x0a\x20\x20\x20\x72\x3d\x22\x34\x38\x39\ +\x37\x2e\x34\x31\x22\x20\x2f\x3e\x3c\x2f\x64\x65\x66\x73\x3e\x26\ +\x23\x31\x30\x3b\x20\x3c\x67\x0a\x20\x20\x20\x69\x64\x3d\x22\x43\ +\x61\x6d\x61\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\x0a\x20\ +\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x6c\x61\x62\x65\x6c\ +\x3d\x22\x43\x61\x6d\x61\x64\x61\x20\x31\x22\x3e\x26\x23\x31\x30\ +\x3b\x20\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x0a\x20\x20\x20\ +\x69\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\ +\x30\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\x72\x22\x20\x2f\x3e\ +\x26\x23\x31\x30\x3b\x20\x20\x3c\x67\x0a\x20\x20\x20\x69\x64\x3d\ +\x22\x5f\x32\x32\x34\x38\x34\x34\x30\x32\x39\x33\x32\x34\x38\x22\ +\x3e\x26\x23\x31\x30\x3b\x20\x20\x20\x3c\x70\x61\x74\x68\x0a\x20\ +\x20\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\x0a\x20\ +\x20\x20\x64\x3d\x22\x4d\x31\x36\x39\x2e\x35\x34\x20\x31\x34\x32\ +\x2e\x31\x31\x63\x2d\x33\x2e\x30\x32\x2c\x2d\x33\x2e\x30\x38\x20\ +\x2d\x37\x2e\x39\x35\x2c\x2d\x33\x2e\x31\x35\x20\x2d\x31\x31\x2e\ +\x30\x35\x2c\x2d\x30\x2e\x31\x35\x20\x2d\x32\x2e\x34\x36\x2c\x32\ +\x2e\x33\x38\x20\x2d\x34\x2e\x39\x36\x2c\x34\x2e\x37\x32\x20\x2d\ +\x37\x2e\x35\x2c\x37\x2e\x30\x31\x6c\x32\x35\x2e\x31\x39\x20\x30\ +\x2e\x30\x31\x63\x2d\x32\x2e\x32\x2c\x2d\x32\x2e\x33\x31\x20\x2d\ +\x34\x2e\x34\x31\x2c\x2d\x34\x2e\x36\x20\x2d\x36\x2e\x36\x34\x2c\ +\x2d\x36\x2e\x38\x37\x7a\x22\x0a\x20\x20\x20\x69\x64\x3d\x22\x70\ +\x61\x74\x68\x31\x39\x22\x20\x2f\x3e\x26\x23\x31\x30\x3b\x20\x20\ +\x20\x3c\x70\x61\x74\x68\x0a\x20\x20\x20\x63\x6c\x61\x73\x73\x3d\ +\x22\x66\x69\x6c\x31\x22\x0a\x20\x20\x20\x64\x3d\x22\x4d\x32\x31\ +\x39\x2e\x31\x39\x20\x31\x33\x35\x2e\x34\x36\x63\x30\x2c\x2d\x31\ +\x38\x2e\x33\x37\x20\x2d\x31\x2e\x37\x34\x2c\x2d\x33\x36\x2e\x36\ +\x39\x20\x2d\x35\x2e\x31\x39\x2c\x2d\x35\x34\x2e\x37\x33\x20\x2d\ +\x32\x2e\x36\x39\x2c\x2d\x31\x34\x2e\x30\x33\x20\x2d\x37\x2e\x30\ +\x38\x2c\x2d\x32\x37\x2e\x36\x38\x20\x2d\x31\x33\x2e\x30\x38\x2c\ +\x2d\x34\x30\x2e\x36\x35\x20\x2d\x35\x2e\x35\x35\x2c\x2d\x31\x32\ +\x2e\x30\x31\x20\x2d\x31\x33\x2e\x34\x39\x2c\x2d\x32\x32\x2e\x37\ +\x35\x20\x2d\x32\x33\x2e\x33\x35\x2c\x2d\x33\x31\x2e\x35\x37\x20\ +\x2d\x35\x2e\x37\x31\x2c\x2d\x35\x2e\x31\x31\x20\x2d\x31\x33\x2e\ +\x31\x2c\x2d\x37\x2e\x39\x34\x20\x2d\x32\x30\x2e\x37\x36\x2c\x2d\ +\x37\x2e\x39\x34\x20\x2d\x37\x2e\x36\x36\x2c\x30\x20\x2d\x31\x35\ +\x2e\x30\x36\x2c\x32\x2e\x38\x33\x20\x2d\x32\x30\x2e\x37\x36\x2c\ +\x37\x2e\x39\x34\x20\x2d\x30\x2e\x32\x2c\x30\x2e\x31\x38\x20\x2d\ +\x30\x2e\x34\x2c\x30\x2e\x33\x35\x20\x2d\x30\x2e\x35\x39\x2c\x30\ +\x2e\x35\x33\x20\x39\x2e\x35\x38\x2c\x38\x2e\x37\x32\x20\x31\x37\ +\x2e\x33\x32\x2c\x31\x39\x2e\x32\x38\x20\x32\x32\x2e\x37\x36\x2c\ +\x33\x31\x2e\x30\x34\x20\x36\x2c\x31\x32\x2e\x39\x37\x20\x31\x30\ +\x2e\x33\x39\x2c\x32\x36\x2e\x36\x32\x20\x31\x33\x2e\x30\x38\x2c\ +\x34\x30\x2e\x36\x35\x20\x33\x2e\x34\x35\x2c\x31\x38\x2e\x30\x34\ +\x20\x35\x2e\x31\x39\x2c\x33\x36\x2e\x33\x36\x20\x35\x2e\x31\x39\ +\x2c\x35\x34\x2e\x37\x33\x20\x30\x2c\x34\x2e\x35\x31\x20\x2d\x30\ +\x2e\x31\x2c\x39\x2e\x30\x31\x20\x2d\x30\x2e\x33\x31\x2c\x31\x33\ +\x2e\x35\x32\x6c\x2d\x32\x35\x2e\x31\x39\x20\x2d\x30\x2e\x30\x31\ +\x63\x2d\x32\x2e\x30\x39\x2c\x31\x2e\x38\x38\x20\x2d\x34\x2e\x32\ +\x31\x2c\x33\x2e\x37\x33\x20\x2d\x36\x2e\x33\x35\x2c\x35\x2e\x35\ +\x34\x20\x2d\x31\x2e\x39\x31\x2c\x31\x2e\x36\x33\x20\x2d\x33\x2e\ +\x30\x37\x2c\x33\x2e\x39\x36\x20\x2d\x33\x2e\x32\x32\x2c\x36\x2e\ +\x34\x36\x20\x2d\x30\x2e\x31\x34\x2c\x32\x2e\x35\x31\x20\x30\x2e\ +\x37\x34\x2c\x34\x2e\x39\x35\x20\x32\x2e\x34\x35\x2c\x36\x2e\x37\ +\x38\x20\x38\x2e\x39\x33\x2c\x39\x2e\x35\x34\x20\x31\x37\x2e\x35\ +\x38\x2c\x31\x39\x2e\x33\x33\x20\x32\x35\x2e\x39\x36\x2c\x32\x39\ +\x2e\x33\x36\x6c\x34\x32\x2e\x37\x20\x30\x63\x30\x2e\x35\x33\x2c\ +\x2d\x32\x2e\x33\x20\x31\x2e\x30\x32\x2c\x2d\x34\x2e\x36\x20\x31\ +\x2e\x34\x37\x2c\x2d\x36\x2e\x39\x32\x20\x33\x2e\x34\x35\x2c\x2d\ +\x31\x38\x2e\x30\x34\x20\x35\x2e\x31\x39\x2c\x2d\x33\x36\x2e\x33\ +\x36\x20\x35\x2e\x31\x39\x2c\x2d\x35\x34\x2e\x37\x33\x7a\x22\x0a\ +\x20\x20\x20\x69\x64\x3d\x22\x70\x61\x74\x68\x32\x30\x22\x20\x2f\ +\x3e\x26\x23\x31\x30\x3b\x20\x20\x20\x3c\x70\x61\x74\x68\x0a\x20\ +\x20\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\x0a\x20\ +\x20\x20\x64\x3d\x22\x4d\x32\x32\x33\x2e\x34\x32\x20\x32\x32\x38\ +\x2e\x33\x34\x63\x2d\x31\x2e\x37\x32\x2c\x2d\x31\x30\x2e\x39\x38\ +\x20\x2d\x35\x2e\x34\x31\x2c\x2d\x32\x31\x2e\x35\x36\x20\x2d\x31\ +\x30\x2e\x38\x39\x2c\x2d\x33\x31\x2e\x32\x33\x6c\x2d\x34\x32\x2e\ +\x37\x20\x30\x63\x2d\x32\x2e\x37\x32\x2c\x31\x31\x2e\x36\x31\x20\ +\x2d\x36\x2e\x36\x2c\x32\x32\x2e\x39\x31\x20\x2d\x31\x31\x2e\x36\ +\x31\x2c\x33\x33\x2e\x37\x33\x20\x2d\x32\x2e\x34\x31\x2c\x35\x2e\ +\x32\x31\x20\x2d\x35\x2e\x32\x37\x2c\x31\x30\x2e\x31\x39\x20\x2d\ +\x38\x2e\x35\x36\x2c\x31\x34\x2e\x39\x6c\x34\x32\x2e\x37\x20\x30\ +\x63\x2d\x30\x2e\x32\x34\x2c\x32\x2e\x36\x32\x20\x2d\x30\x2e\x32\ +\x39\x2c\x35\x2e\x32\x36\x20\x2d\x30\x2e\x31\x35\x2c\x37\x2e\x39\ +\x20\x30\x2e\x30\x33\x2c\x30\x2e\x37\x35\x20\x30\x2e\x36\x37\x2c\ +\x31\x2e\x33\x33\x20\x31\x2e\x34\x32\x2c\x31\x2e\x33\x20\x31\x2e\ +\x32\x31\x2c\x2d\x30\x2e\x30\x35\x20\x32\x2e\x33\x38\x2c\x2d\x30\ +\x2e\x34\x32\x20\x33\x2e\x34\x2c\x2d\x31\x2e\x30\x36\x20\x36\x2e\ +\x39\x35\x2c\x2d\x34\x2e\x33\x35\x20\x31\x33\x2e\x35\x36\x2c\x2d\ +\x39\x2e\x32\x32\x20\x31\x39\x2e\x37\x38\x2c\x2d\x31\x34\x2e\x35\ +\x37\x20\x32\x2e\x34\x36\x2c\x2d\x32\x2e\x31\x20\x34\x2e\x35\x2c\ +\x2d\x34\x2e\x36\x34\x20\x36\x2e\x30\x32\x2c\x2d\x37\x2e\x34\x39\ +\x20\x30\x2e\x35\x36\x2c\x2d\x31\x2e\x30\x37\x20\x30\x2e\x37\x37\ +\x2c\x2d\x32\x2e\x32\x39\x20\x30\x2e\x35\x39\x2c\x2d\x33\x2e\x34\ +\x38\x7a\x22\x0a\x20\x20\x20\x69\x64\x3d\x22\x70\x61\x74\x68\x32\ +\x31\x22\x20\x2f\x3e\x26\x23\x31\x30\x3b\x20\x20\x20\x3c\x70\x61\ +\x74\x68\x0a\x20\x20\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\ +\x33\x22\x0a\x20\x20\x20\x64\x3d\x22\x4d\x31\x39\x32\x2e\x33\x36\ +\x20\x32\x34\x35\x2e\x37\x34\x6c\x2d\x34\x32\x2e\x37\x20\x30\x63\ +\x2d\x34\x2e\x31\x32\x2c\x35\x2e\x38\x39\x20\x2d\x38\x2e\x38\x38\ +\x2c\x31\x31\x2e\x33\x20\x2d\x31\x34\x2e\x32\x2c\x31\x36\x2e\x31\ +\x34\x20\x30\x2e\x31\x39\x2c\x30\x2e\x31\x38\x20\x30\x2e\x33\x39\ +\x2c\x30\x2e\x33\x35\x20\x30\x2e\x35\x39\x2c\x30\x2e\x35\x33\x20\ +\x35\x2e\x37\x31\x2c\x35\x2e\x31\x31\x20\x31\x33\x2e\x31\x2c\x37\ +\x2e\x39\x34\x20\x32\x30\x2e\x37\x36\x2c\x37\x2e\x39\x34\x20\x37\ +\x2e\x36\x36\x2c\x30\x20\x31\x35\x2e\x30\x36\x2c\x2d\x32\x2e\x38\ +\x33\x20\x32\x30\x2e\x37\x36\x2c\x2d\x37\x2e\x39\x34\x20\x35\x2e\ +\x35\x36\x2c\x2d\x34\x2e\x39\x37\x20\x31\x30\x2e\x35\x32\x2c\x2d\ +\x31\x30\x2e\x35\x37\x20\x31\x34\x2e\x37\x39\x2c\x2d\x31\x36\x2e\ +\x36\x37\x7a\x22\x0a\x20\x20\x20\x69\x64\x3d\x22\x70\x61\x74\x68\ +\x32\x32\x22\x20\x2f\x3e\x26\x23\x31\x30\x3b\x20\x20\x20\x3c\x70\ +\x61\x74\x68\x0a\x20\x20\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\ +\x6c\x34\x22\x0a\x20\x20\x20\x64\x3d\x22\x4d\x31\x36\x33\x2e\x30\ +\x38\x20\x31\x2e\x32\x31\x63\x35\x2e\x33\x39\x2c\x31\x2e\x31\x31\ +\x20\x31\x30\x2e\x33\x39\x2c\x33\x2e\x36\x33\x20\x31\x34\x2e\x34\ +\x39\x2c\x37\x2e\x33\x20\x39\x2e\x38\x35\x2c\x38\x2e\x38\x32\x20\ +\x31\x37\x2e\x38\x2c\x31\x39\x2e\x35\x37\x20\x32\x33\x2e\x33\x35\ +\x2c\x33\x31\x2e\x35\x37\x20\x36\x2c\x31\x32\x2e\x39\x37\x20\x31\ +\x30\x2e\x33\x39\x2c\x32\x36\x2e\x36\x32\x20\x31\x33\x2e\x30\x38\ +\x2c\x34\x30\x2e\x36\x35\x20\x33\x2e\x34\x35\x2c\x31\x38\x2e\x30\ +\x34\x20\x35\x2e\x31\x39\x2c\x33\x36\x2e\x33\x36\x20\x35\x2e\x31\ +\x39\x2c\x35\x34\x2e\x37\x33\x20\x30\x2c\x31\x38\x2e\x33\x37\x20\ +\x2d\x31\x2e\x37\x34\x2c\x33\x36\x2e\x36\x39\x20\x2d\x35\x2e\x31\ +\x39\x2c\x35\x34\x2e\x37\x33\x20\x2d\x30\x2e\x34\x35\x2c\x32\x2e\ +\x33\x32\x20\x2d\x30\x2e\x39\x34\x2c\x34\x2e\x36\x32\x20\x2d\x31\ +\x2e\x34\x37\x2c\x36\x2e\x39\x32\x20\x35\x2e\x34\x36\x2c\x39\x2e\ +\x36\x34\x20\x39\x2e\x31\x34\x2c\x32\x30\x2e\x31\x39\x20\x31\x30\ +\x2e\x38\x38\x2c\x33\x31\x2e\x31\x33\x20\x30\x2e\x32\x2c\x31\x2e\ +\x32\x32\x20\x30\x2c\x32\x2e\x34\x38\x20\x2d\x30\x2e\x35\x38\x2c\ +\x33\x2e\x35\x38\x20\x2d\x31\x2e\x35\x32\x2c\x32\x2e\x38\x35\x20\ +\x2d\x33\x2e\x35\x36\x2c\x35\x2e\x33\x39\x20\x2d\x36\x2e\x30\x32\ +\x2c\x37\x2e\x34\x39\x20\x2d\x36\x2e\x32\x32\x2c\x35\x2e\x33\x35\ +\x20\x2d\x31\x32\x2e\x38\x33\x2c\x31\x30\x2e\x32\x32\x20\x2d\x31\ +\x39\x2e\x37\x38\x2c\x31\x34\x2e\x35\x37\x20\x2d\x31\x2e\x30\x32\ +\x2c\x30\x2e\x36\x34\x20\x2d\x32\x2e\x31\x39\x2c\x31\x2e\x30\x31\ +\x20\x2d\x33\x2e\x34\x2c\x31\x2e\x30\x36\x20\x2d\x30\x2e\x37\x35\ +\x2c\x30\x2e\x30\x33\x20\x2d\x31\x2e\x33\x39\x2c\x2d\x30\x2e\x35\ +\x35\x20\x2d\x31\x2e\x34\x32\x2c\x2d\x31\x2e\x33\x20\x2d\x30\x2e\ +\x31\x34\x2c\x2d\x32\x2e\x36\x34\x20\x2d\x30\x2e\x30\x39\x2c\x2d\ +\x35\x2e\x32\x38\x20\x30\x2e\x31\x35\x2c\x2d\x37\x2e\x39\x20\x2d\ +\x34\x2e\x32\x37\x2c\x36\x2e\x31\x20\x2d\x39\x2e\x32\x33\x2c\x31\ +\x31\x2e\x37\x20\x2d\x31\x34\x2e\x37\x39\x2c\x31\x36\x2e\x36\x37\ +\x20\x2d\x34\x2e\x31\x2c\x33\x2e\x36\x37\x20\x2d\x39\x2e\x31\x2c\ +\x36\x2e\x31\x39\x20\x2d\x31\x34\x2e\x34\x39\x2c\x37\x2e\x33\x20\ +\x36\x33\x2e\x37\x32\x2c\x2d\x31\x34\x2e\x30\x37\x20\x31\x30\x38\ +\x2e\x37\x37\x2c\x2d\x37\x30\x2e\x39\x38\x20\x31\x30\x37\x2e\x38\ +\x33\x2c\x2d\x31\x33\x36\x2e\x32\x33\x20\x2d\x30\x2e\x39\x34\x2c\ +\x2d\x36\x35\x2e\x32\x34\x20\x2d\x34\x33\x2e\x37\x33\x2c\x2d\x31\ +\x32\x30\x2e\x30\x34\x20\x2d\x31\x30\x37\x2e\x38\x33\x2c\x2d\x31\ +\x33\x32\x2e\x32\x37\x7a\x22\x0a\x20\x20\x20\x69\x64\x3d\x22\x70\ +\x61\x74\x68\x32\x33\x22\x0a\x20\x20\x20\x73\x74\x79\x6c\x65\x3d\ +\x22\x66\x69\x6c\x6c\x3a\x75\x72\x6c\x28\x23\x72\x61\x64\x69\x61\ +\x6c\x47\x72\x61\x64\x69\x65\x6e\x74\x32\x34\x29\x22\x20\x2f\x3e\ +\x26\x23\x31\x30\x3b\x20\x20\x20\x3c\x70\x61\x74\x68\x0a\x20\x20\ +\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x35\x22\x0a\x20\x20\ +\x20\x64\x3d\x22\x4d\x31\x35\x38\x2e\x32\x32\x20\x34\x30\x2e\x30\ +\x38\x63\x2d\x35\x2e\x35\x35\x2c\x2d\x31\x32\x2e\x30\x31\x20\x2d\ +\x31\x33\x2e\x34\x39\x2c\x2d\x32\x32\x2e\x37\x35\x20\x2d\x32\x33\ +\x2e\x33\x35\x2c\x2d\x33\x31\x2e\x35\x37\x20\x2d\x35\x2e\x37\x31\ +\x2c\x2d\x35\x2e\x31\x31\x20\x2d\x31\x33\x2e\x31\x2c\x2d\x37\x2e\ +\x39\x34\x20\x2d\x32\x30\x2e\x37\x36\x2c\x2d\x37\x2e\x39\x34\x20\ +\x2d\x32\x2e\x31\x31\x2c\x30\x20\x2d\x34\x2e\x32\x31\x2c\x30\x2e\ +\x32\x31\x20\x2d\x36\x2e\x32\x37\x2c\x30\x2e\x36\x34\x20\x2d\x36\ +\x33\x2e\x37\x32\x2c\x31\x34\x2e\x30\x37\x20\x2d\x31\x30\x38\x2e\ +\x37\x37\x2c\x37\x30\x2e\x39\x38\x20\x2d\x31\x30\x37\x2e\x38\x33\ +\x2c\x31\x33\x36\x2e\x32\x33\x20\x30\x2e\x39\x34\x2c\x36\x35\x2e\ +\x32\x34\x20\x34\x33\x2e\x37\x33\x2c\x31\x32\x30\x2e\x30\x34\x20\ +\x31\x30\x37\x2e\x38\x33\x2c\x31\x33\x32\x2e\x32\x37\x20\x32\x2e\ +\x30\x36\x2c\x30\x2e\x34\x33\x20\x34\x2e\x31\x36\x2c\x30\x2e\x36\ +\x34\x20\x36\x2e\x32\x37\x2c\x30\x2e\x36\x34\x20\x37\x2e\x36\x36\ +\x2c\x30\x20\x31\x35\x2e\x30\x36\x2c\x2d\x32\x2e\x38\x33\x20\x32\ +\x30\x2e\x37\x36\x2c\x2d\x37\x2e\x39\x34\x20\x39\x2e\x38\x36\x2c\ +\x2d\x38\x2e\x38\x32\x20\x31\x37\x2e\x38\x2c\x2d\x31\x39\x2e\x35\ +\x36\x20\x32\x33\x2e\x33\x35\x2c\x2d\x33\x31\x2e\x35\x37\x20\x35\ +\x2e\x30\x31\x2c\x2d\x31\x30\x2e\x38\x32\x20\x38\x2e\x38\x39\x2c\ +\x2d\x32\x32\x2e\x31\x32\x20\x31\x31\x2e\x36\x31\x2c\x2d\x33\x33\ +\x2e\x37\x33\x20\x2d\x38\x2e\x33\x38\x2c\x2d\x31\x30\x2e\x30\x33\ +\x20\x2d\x31\x37\x2e\x30\x33\x2c\x2d\x31\x39\x2e\x38\x32\x20\x2d\ +\x32\x35\x2e\x39\x36\x2c\x2d\x32\x39\x2e\x33\x36\x20\x2d\x31\x2e\ +\x37\x31\x2c\x2d\x31\x2e\x38\x33\x20\x2d\x32\x2e\x36\x2c\x2d\x34\ +\x2e\x32\x38\x20\x2d\x32\x2e\x34\x35\x2c\x2d\x36\x2e\x37\x38\x20\ +\x30\x2e\x31\x34\x2c\x2d\x32\x2e\x35\x20\x31\x2e\x33\x31\x2c\x2d\ +\x34\x2e\x38\x34\x20\x33\x2e\x32\x32\x2c\x2d\x36\x2e\x34\x36\x20\ +\x32\x2e\x31\x34\x2c\x2d\x31\x2e\x38\x31\x20\x34\x2e\x32\x36\x2c\ +\x2d\x33\x2e\x36\x36\x20\x36\x2e\x33\x35\x2c\x2d\x35\x2e\x35\x34\ +\x20\x32\x2e\x35\x34\x2c\x2d\x32\x2e\x32\x39\x20\x35\x2e\x30\x34\ +\x2c\x2d\x34\x2e\x36\x33\x20\x37\x2e\x35\x2c\x2d\x37\x2e\x30\x31\ +\x20\x33\x2e\x31\x2c\x2d\x32\x2e\x39\x39\x20\x38\x2e\x30\x34\x2c\ +\x2d\x32\x2e\x39\x32\x20\x31\x31\x2e\x30\x35\x2c\x30\x2e\x31\x35\ +\x20\x32\x2e\x32\x33\x2c\x32\x2e\x32\x37\x20\x34\x2e\x34\x34\x2c\ +\x34\x2e\x35\x36\x20\x36\x2e\x36\x34\x2c\x36\x2e\x38\x37\x20\x30\ +\x2e\x32\x31\x2c\x2d\x34\x2e\x35\x31\x20\x30\x2e\x33\x31\x2c\x2d\ +\x39\x2e\x30\x31\x20\x30\x2e\x33\x31\x2c\x2d\x31\x33\x2e\x35\x32\ +\x20\x30\x2c\x2d\x31\x38\x2e\x33\x37\x20\x2d\x31\x2e\x37\x34\x2c\ +\x2d\x33\x36\x2e\x36\x39\x20\x2d\x35\x2e\x31\x39\x2c\x2d\x35\x34\ +\x2e\x37\x33\x20\x2d\x32\x2e\x36\x39\x2c\x2d\x31\x34\x2e\x30\x33\ +\x20\x2d\x37\x2e\x30\x38\x2c\x2d\x32\x37\x2e\x36\x38\x20\x2d\x31\ +\x33\x2e\x30\x38\x2c\x2d\x34\x30\x2e\x36\x35\x7a\x22\x0a\x20\x20\ +\x20\x69\x64\x3d\x22\x70\x61\x74\x68\x32\x34\x22\x0a\x20\x20\x20\ +\x73\x74\x79\x6c\x65\x3d\x22\x66\x69\x6c\x6c\x3a\x75\x72\x6c\x28\ +\x23\x69\x64\x35\x29\x22\x20\x2f\x3e\x26\x23\x31\x30\x3b\x20\x20\ +\x3c\x2f\x67\x3e\x26\x23\x31\x30\x3b\x20\x3c\x2f\x67\x3e\x26\x23\ +\x31\x30\x3b\x3c\x2f\x73\x76\x67\x3e\x0a\ +\x00\x00\x0d\x45\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x34\x36\x38\x32\x43\x38\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x37\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3a\x72\x6f\x75\x6e\x64\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\ +\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\ +\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\x6e\ +\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x32\x20\x7b\x66\ +\x69\x6c\x6c\x3a\x23\x34\x36\x38\x32\x43\x38\x7d\x0d\x0a\x20\x20\ +\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x23\x46\ +\x46\x39\x39\x33\x33\x3b\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\ +\x6e\x6f\x6e\x7a\x65\x72\x6f\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\x3e\ +\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\x3c\ +\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\x22\ +\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\x3e\ +\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\x69\x64\ +\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\x43\ +\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\x20\ +\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\ +\x6c\x30\x22\x20\x78\x3d\x22\x2d\x30\x2e\x30\x31\x22\x20\x79\x3d\ +\x22\x2d\x30\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\x30\x2e\ +\x39\x33\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\ +\x39\x33\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\x65\x63\x74\x20\x63\ +\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\x78\x3d\x22\x2d\ +\x30\x2e\x30\x31\x22\x20\x79\x3d\x22\x30\x2e\x30\x33\x22\x20\x77\ +\x69\x64\x74\x68\x3d\x22\x32\x37\x30\x2e\x39\x32\x22\x20\x68\x65\ +\x69\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\x39\x32\x22\x2f\x3e\x0d\ +\x0a\x20\x20\x3c\x67\x20\x69\x64\x3d\x22\x5f\x32\x35\x35\x37\x38\ +\x30\x36\x37\x34\x39\x37\x34\x34\x22\x3e\x0d\x0a\x20\x20\x20\x3c\ +\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\ +\x22\x20\x64\x3d\x22\x4d\x32\x36\x32\x2e\x34\x36\x20\x32\x35\x34\ +\x2e\x32\x38\x6c\x2d\x31\x36\x2e\x35\x20\x30\x20\x30\x20\x30\x20\ +\x30\x20\x30\x2e\x32\x35\x20\x30\x20\x30\x2e\x32\x34\x20\x30\x20\ +\x30\x2e\x32\x35\x20\x30\x20\x30\x2e\x32\x35\x20\x30\x20\x30\x2e\ +\x30\x39\x20\x2d\x30\x2e\x30\x31\x20\x30\x2e\x32\x34\x20\x30\x20\ +\x30\x2e\x34\x32\x20\x30\x20\x30\x2e\x32\x34\x20\x30\x20\x30\x2e\ +\x30\x39\x20\x2d\x30\x2e\x30\x31\x20\x30\x2e\x32\x35\x20\x30\x20\ +\x30\x2e\x34\x31\x20\x30\x20\x30\x2e\x30\x38\x20\x2d\x30\x2e\x30\ +\x31\x20\x30\x2e\x32\x35\x20\x30\x20\x30\x2e\x32\x35\x20\x2d\x30\ +\x2e\x30\x31\x20\x30\x2e\x32\x35\x20\x30\x20\x30\x2e\x32\x34\x20\ +\x2d\x30\x2e\x30\x31\x20\x30\x2e\x32\x36\x20\x30\x20\x30\x2e\x32\ +\x34\x20\x2d\x30\x2e\x30\x31\x20\x30\x2e\x32\x35\x20\x30\x20\x30\ +\x2e\x32\x34\x20\x30\x20\x30\x2e\x31\x31\x20\x2d\x30\x2e\x30\x31\ +\x20\x30\x2e\x33\x39\x20\x30\x20\x30\x2e\x32\x34\x20\x30\x20\x30\ +\x2e\x31\x31\x20\x2d\x30\x2e\x30\x32\x20\x30\x2e\x33\x39\x20\x30\ +\x20\x30\x2e\x32\x35\x20\x30\x20\x30\x2e\x30\x39\x20\x2d\x30\x2e\ +\x30\x31\x20\x30\x2e\x32\x36\x20\x2d\x30\x2e\x30\x32\x20\x30\x2e\ +\x33\x39\x20\x30\x20\x30\x2e\x32\x34\x20\x30\x20\x30\x2e\x31\x31\ +\x20\x2d\x30\x2e\x30\x32\x20\x30\x2e\x32\x39\x20\x31\x36\x2e\x35\ +\x20\x30\x2e\x35\x32\x20\x30\x20\x2d\x30\x2e\x32\x20\x30\x2e\x30\ +\x31\x20\x2d\x30\x2e\x34\x20\x30\x20\x2d\x30\x2e\x32\x34\x20\x30\ +\x20\x2d\x30\x2e\x31\x20\x30\x2e\x30\x31\x20\x2d\x30\x2e\x32\x35\ +\x20\x30\x2e\x30\x32\x20\x2d\x30\x2e\x33\x39\x20\x30\x20\x2d\x30\ +\x2e\x32\x35\x20\x30\x20\x2d\x30\x2e\x31\x20\x30\x2e\x30\x32\x20\ +\x2d\x30\x2e\x34\x20\x30\x20\x2d\x30\x2e\x32\x34\x20\x30\x20\x2d\ +\x30\x2e\x31\x20\x30\x2e\x30\x32\x20\x2d\x30\x2e\x34\x20\x30\x20\ +\x2d\x30\x2e\x32\x34\x20\x30\x2e\x30\x31\x20\x2d\x30\x2e\x32\x35\ +\x20\x30\x20\x2d\x30\x2e\x32\x35\x20\x30\x2e\x30\x31\x20\x2d\x30\ +\x2e\x32\x35\x20\x30\x20\x2d\x30\x2e\x32\x34\x20\x30\x2e\x30\x31\ +\x20\x2d\x30\x2e\x32\x35\x20\x30\x20\x2d\x30\x2e\x32\x35\x20\x30\ +\x2e\x30\x31\x20\x2d\x30\x2e\x32\x35\x20\x30\x20\x2d\x30\x2e\x34\ +\x20\x30\x20\x2d\x30\x2e\x31\x20\x30\x2e\x30\x31\x20\x2d\x30\x2e\ +\x32\x34\x20\x30\x20\x2d\x30\x2e\x34\x31\x20\x30\x20\x2d\x30\x2e\ +\x32\x34\x20\x30\x20\x2d\x30\x2e\x31\x20\x30\x2e\x30\x31\x20\x2d\ +\x30\x2e\x32\x34\x20\x30\x20\x2d\x30\x2e\x34\x31\x20\x30\x20\x2d\ +\x30\x2e\x32\x35\x20\x30\x20\x2d\x30\x2e\x32\x35\x20\x30\x20\x2d\ +\x30\x2e\x32\x34\x20\x30\x20\x2d\x30\x2e\x32\x35\x20\x30\x20\x30\ +\x7a\x6d\x30\x20\x30\x63\x30\x2c\x2d\x34\x2e\x35\x36\x20\x2d\x33\ +\x2e\x37\x2c\x2d\x38\x2e\x32\x35\x20\x2d\x38\x2e\x32\x35\x2c\x2d\ +\x38\x2e\x32\x35\x20\x2d\x34\x2e\x35\x36\x2c\x30\x20\x2d\x38\x2e\ +\x32\x35\x2c\x33\x2e\x36\x39\x20\x2d\x38\x2e\x32\x35\x2c\x38\x2e\ +\x32\x35\x6c\x31\x36\x2e\x35\x20\x30\x7a\x6d\x2d\x32\x34\x35\x2e\ +\x37\x36\x20\x2d\x32\x34\x35\x2e\x38\x32\x6c\x30\x20\x31\x36\x2e\ +\x35\x20\x30\x20\x30\x20\x31\x31\x2e\x38\x31\x20\x30\x2e\x33\x20\ +\x31\x31\x2e\x36\x34\x20\x30\x2e\x38\x38\x20\x31\x31\x2e\x34\x38\ +\x20\x31\x2e\x34\x37\x20\x31\x31\x2e\x32\x38\x20\x32\x2e\x30\x31\ +\x20\x31\x31\x2e\x30\x39\x20\x32\x2e\x35\x36\x20\x31\x30\x2e\x38\ +\x38\x20\x33\x2e\x30\x39\x20\x31\x30\x2e\x36\x35\x20\x33\x2e\x36\ +\x20\x31\x30\x2e\x34\x31\x20\x34\x2e\x31\x20\x31\x30\x2e\x31\x35\ +\x20\x34\x2e\x36\x20\x39\x2e\x38\x38\x20\x35\x2e\x30\x36\x20\x39\ +\x2e\x36\x31\x20\x35\x2e\x35\x32\x20\x39\x2e\x32\x39\x20\x35\x2e\ +\x39\x37\x20\x39\x20\x36\x2e\x33\x39\x20\x38\x2e\x36\x36\x20\x36\ +\x2e\x38\x31\x20\x38\x2e\x33\x32\x20\x37\x2e\x32\x31\x20\x37\x2e\ +\x39\x36\x20\x37\x2e\x35\x39\x20\x37\x2e\x36\x20\x37\x2e\x39\x37\ +\x20\x37\x2e\x32\x31\x20\x38\x2e\x33\x32\x20\x36\x2e\x38\x20\x38\ +\x2e\x36\x37\x20\x36\x2e\x33\x39\x20\x38\x2e\x39\x39\x20\x35\x2e\ +\x39\x37\x20\x39\x2e\x33\x20\x35\x2e\x35\x31\x20\x39\x2e\x36\x31\ +\x20\x35\x2e\x30\x36\x20\x39\x2e\x38\x38\x20\x34\x2e\x36\x20\x31\ +\x30\x2e\x31\x36\x20\x34\x2e\x31\x31\x20\x31\x30\x2e\x34\x31\x20\ +\x33\x2e\x36\x20\x31\x30\x2e\x36\x35\x20\x33\x2e\x30\x38\x20\x31\ +\x30\x2e\x38\x38\x20\x32\x2e\x35\x36\x20\x31\x31\x2e\x30\x39\x20\ +\x32\x2e\x30\x31\x20\x31\x31\x2e\x32\x39\x20\x31\x2e\x34\x37\x20\ +\x31\x31\x2e\x34\x38\x20\x30\x2e\x38\x38\x20\x31\x31\x2e\x36\x34\ +\x20\x30\x2e\x33\x20\x31\x31\x2e\x38\x32\x20\x31\x36\x2e\x35\x20\ +\x30\x20\x2d\x30\x2e\x33\x32\x20\x2d\x31\x32\x2e\x36\x34\x20\x2d\ +\x30\x2e\x39\x36\x20\x2d\x31\x32\x2e\x34\x39\x20\x2d\x31\x2e\x35\ +\x36\x20\x2d\x31\x32\x2e\x33\x20\x2d\x32\x2e\x31\x36\x20\x2d\x31\ +\x32\x2e\x31\x31\x20\x2d\x32\x2e\x37\x35\x20\x2d\x31\x31\x2e\x38\ +\x39\x20\x2d\x33\x2e\x33\x31\x20\x2d\x31\x31\x2e\x36\x37\x20\x2d\ +\x33\x2e\x38\x36\x20\x2d\x31\x31\x2e\x34\x32\x20\x2d\x34\x2e\x34\ +\x20\x2d\x31\x31\x2e\x31\x37\x20\x2d\x34\x2e\x39\x32\x20\x2d\x31\ +\x30\x2e\x38\x39\x20\x2d\x35\x2e\x34\x33\x20\x2d\x31\x30\x2e\x36\ +\x20\x2d\x35\x2e\x39\x31\x20\x2d\x31\x30\x2e\x33\x20\x2d\x36\x2e\ +\x34\x20\x2d\x39\x2e\x39\x37\x20\x2d\x36\x2e\x38\x35\x20\x2d\x39\ +\x2e\x36\x33\x20\x2d\x37\x2e\x33\x20\x2d\x39\x2e\x32\x39\x20\x2d\ +\x37\x2e\x37\x31\x20\x2d\x38\x2e\x39\x31\x20\x2d\x38\x2e\x31\x34\ +\x20\x2d\x38\x2e\x35\x35\x20\x2d\x38\x2e\x35\x34\x20\x2d\x38\x2e\ +\x31\x34\x20\x2d\x38\x2e\x39\x31\x20\x2d\x37\x2e\x37\x31\x20\x2d\ +\x39\x2e\x32\x39\x20\x2d\x37\x2e\x33\x20\x2d\x39\x2e\x36\x33\x20\ +\x2d\x36\x2e\x38\x35\x20\x2d\x39\x2e\x39\x37\x20\x2d\x36\x2e\x34\ +\x20\x2d\x31\x30\x2e\x32\x39\x20\x2d\x35\x2e\x39\x31\x20\x2d\x31\ +\x30\x2e\x36\x20\x2d\x35\x2e\x34\x34\x20\x2d\x31\x30\x2e\x38\x38\ +\x20\x2d\x34\x2e\x39\x32\x20\x2d\x31\x31\x2e\x31\x38\x20\x2d\x34\ +\x2e\x34\x20\x2d\x31\x31\x2e\x34\x31\x20\x2d\x33\x2e\x38\x36\x20\ +\x2d\x31\x31\x2e\x36\x36\x20\x2d\x33\x2e\x33\x32\x20\x2d\x31\x31\ +\x2e\x39\x20\x2d\x32\x2e\x37\x35\x20\x2d\x31\x32\x2e\x31\x31\x20\ +\x2d\x32\x2e\x31\x36\x20\x2d\x31\x32\x2e\x32\x39\x20\x2d\x31\x2e\ +\x35\x36\x20\x2d\x31\x32\x2e\x34\x38\x20\x2d\x30\x2e\x39\x35\x20\ +\x2d\x31\x32\x2e\x36\x34\x20\x2d\x30\x2e\x33\x32\x20\x30\x20\x30\ +\x7a\x6d\x30\x20\x30\x63\x2d\x34\x2e\x35\x35\x2c\x30\x20\x2d\x38\ +\x2e\x32\x34\x2c\x33\x2e\x36\x39\x20\x2d\x38\x2e\x32\x34\x2c\x38\ +\x2e\x32\x35\x20\x30\x2c\x34\x2e\x35\x35\x20\x33\x2e\x36\x39\x2c\ +\x38\x2e\x32\x35\x20\x38\x2e\x32\x34\x2c\x38\x2e\x32\x35\x6c\x30\ +\x20\x2d\x31\x36\x2e\x35\x7a\x6d\x2d\x38\x2e\x31\x37\x20\x30\x2e\ +\x31\x34\x6c\x30\x2e\x35\x32\x20\x31\x36\x2e\x35\x20\x30\x2e\x32\ +\x39\x20\x2d\x30\x2e\x30\x32\x20\x30\x2e\x31\x31\x20\x30\x20\x30\ +\x2e\x32\x34\x20\x30\x20\x30\x2e\x33\x39\x20\x2d\x30\x2e\x30\x32\ +\x20\x30\x2e\x32\x35\x20\x2d\x30\x2e\x30\x31\x20\x30\x2e\x31\x20\ +\x30\x20\x30\x2e\x32\x35\x20\x30\x20\x30\x2e\x34\x20\x2d\x30\x2e\ +\x30\x32\x20\x30\x2e\x30\x39\x20\x30\x20\x30\x2e\x32\x35\x20\x30\ +\x20\x30\x2e\x33\x39\x20\x2d\x30\x2e\x30\x32\x20\x30\x2e\x31\x31\ +\x20\x30\x20\x30\x2e\x32\x34\x20\x30\x20\x30\x2e\x32\x34\x20\x30\ +\x20\x30\x2e\x32\x35\x20\x30\x20\x30\x2e\x32\x35\x20\x2d\x30\x2e\ +\x30\x31\x20\x30\x2e\x32\x35\x20\x30\x20\x30\x2e\x32\x34\x20\x2d\ +\x30\x2e\x30\x31\x20\x30\x2e\x32\x35\x20\x30\x20\x30\x2e\x32\x35\ +\x20\x2d\x30\x2e\x30\x31\x20\x30\x2e\x30\x39\x20\x30\x20\x30\x2e\ +\x34\x20\x30\x20\x30\x2e\x32\x35\x20\x2d\x30\x2e\x30\x31\x20\x30\ +\x2e\x30\x39\x20\x30\x20\x30\x2e\x32\x34\x20\x30\x20\x30\x2e\x34\ +\x31\x20\x30\x20\x30\x2e\x32\x35\x20\x2d\x30\x2e\x30\x31\x20\x30\ +\x2e\x30\x39\x20\x30\x20\x30\x2e\x32\x34\x20\x30\x20\x30\x2e\x32\ +\x36\x20\x30\x20\x30\x2e\x32\x34\x20\x30\x20\x30\x2e\x32\x35\x20\ +\x30\x20\x30\x20\x2d\x31\x36\x2e\x35\x20\x2d\x30\x2e\x32\x35\x20\ +\x30\x20\x2d\x30\x2e\x32\x34\x20\x30\x20\x2d\x30\x2e\x32\x36\x20\ +\x30\x20\x2d\x30\x2e\x32\x34\x20\x30\x20\x2d\x30\x2e\x34\x31\x20\ +\x30\x20\x2d\x30\x2e\x32\x34\x20\x30\x2e\x30\x31\x20\x2d\x30\x2e\ +\x31\x20\x30\x20\x2d\x30\x2e\x32\x34\x20\x30\x20\x2d\x30\x2e\x34\ +\x31\x20\x30\x20\x2d\x30\x2e\x32\x34\x20\x30\x2e\x30\x31\x20\x2d\ +\x30\x2e\x30\x39\x20\x30\x20\x2d\x30\x2e\x34\x31\x20\x30\x20\x2d\ +\x30\x2e\x32\x34\x20\x30\x2e\x30\x31\x20\x2d\x30\x2e\x32\x36\x20\ +\x30\x20\x2d\x30\x2e\x32\x34\x20\x30\x2e\x30\x31\x20\x2d\x30\x2e\ +\x32\x35\x20\x30\x20\x2d\x30\x2e\x32\x34\x20\x30\x2e\x30\x31\x20\ +\x2d\x30\x2e\x32\x36\x20\x30\x20\x2d\x30\x2e\x32\x34\x20\x30\x2e\ +\x30\x31\x20\x2d\x30\x2e\x32\x34\x20\x30\x20\x2d\x30\x2e\x34\x20\ +\x30\x2e\x30\x31\x20\x2d\x30\x2e\x31\x20\x30\x20\x2d\x30\x2e\x32\ +\x35\x20\x30\x20\x2d\x30\x2e\x34\x20\x30\x2e\x30\x32\x20\x2d\x30\ +\x2e\x30\x39\x20\x30\x20\x2d\x30\x2e\x32\x35\x20\x30\x20\x2d\x30\ +\x2e\x33\x39\x20\x30\x2e\x30\x32\x20\x2d\x30\x2e\x32\x35\x20\x30\ +\x2e\x30\x31\x20\x2d\x30\x2e\x31\x20\x30\x20\x2d\x30\x2e\x32\x34\ +\x20\x30\x20\x2d\x30\x2e\x34\x20\x30\x2e\x30\x32\x20\x2d\x30\x2e\ +\x32\x20\x30\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\x3e\x0d\ +\x0a\x20\x20\x3c\x6c\x69\x6e\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\ +\x66\x69\x6c\x30\x20\x73\x74\x72\x30\x22\x20\x78\x31\x3d\x22\x31\ +\x35\x35\x2e\x39\x22\x20\x79\x31\x3d\x22\x31\x31\x35\x2e\x30\x33\ +\x22\x20\x78\x32\x3d\x22\x31\x32\x2e\x36\x39\x22\x20\x79\x32\x3d\ +\x20\x22\x32\x35\x38\x2e\x32\x33\x22\x20\x2f\x3e\x0d\x0a\x20\x20\ +\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\ +\x66\x69\x6c\x32\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x31\x37\ +\x33\x2e\x37\x37\x2c\x39\x37\x2e\x31\x36\x20\x31\x34\x37\x2e\x31\ +\x39\x2c\x31\x30\x34\x2e\x32\x38\x20\x31\x32\x30\x2e\x36\x31\x2c\ +\x31\x31\x31\x2e\x34\x20\x31\x34\x30\x2e\x30\x37\x2c\x31\x33\x30\ +\x2e\x38\x36\x20\x31\x35\x39\x2e\x35\x32\x2c\x31\x35\x30\x2e\x33\ +\x32\x20\x31\x36\x36\x2e\x36\x35\x2c\x31\x32\x33\x2e\x37\x34\x20\ +\x22\x2f\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\ +\x67\x3e\x0d\x0a\ +\x00\x00\x05\x63\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x36\x36\x36\x36\x36\x36\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x37\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3a\x72\x6f\x75\x6e\x64\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\ +\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\ +\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\x6e\ +\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x32\x20\x7b\x66\ +\x69\x6c\x6c\x3a\x23\x30\x30\x39\x39\x33\x33\x7d\x0d\x0a\x20\x20\ +\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x77\x68\ +\x69\x74\x65\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\ +\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\ +\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\ +\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\ +\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\ +\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\ +\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\x65\ +\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\ +\x78\x3d\x22\x2d\x30\x2e\x30\x31\x22\x20\x79\x3d\x22\x2d\x30\x22\ +\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x20\ +\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x2f\ +\x3e\x0d\x0a\x20\x20\x3c\x6c\x69\x6e\x65\x20\x63\x6c\x61\x73\x73\ +\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x30\x22\x20\x78\x31\x3d\ +\x22\x34\x2e\x32\x32\x22\x20\x79\x31\x3d\x22\x32\x31\x31\x2e\x36\ +\x39\x22\x20\x78\x32\x3d\x22\x32\x36\x36\x2e\x36\x39\x22\x20\x79\ +\x32\x3d\x20\x22\x32\x31\x31\x2e\x36\x39\x22\x20\x2f\x3e\x0d\x0a\ +\x20\x20\x3c\x6c\x69\x6e\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\ +\x69\x6c\x30\x20\x73\x74\x72\x30\x22\x20\x78\x31\x3d\x22\x31\x33\ +\x35\x2e\x34\x36\x22\x20\x79\x31\x3d\x22\x32\x31\x31\x2e\x36\x33\ +\x22\x20\x78\x32\x3d\x22\x31\x33\x35\x2e\x34\x36\x22\x20\x79\x32\ +\x3d\x20\x22\x33\x33\x2e\x38\x37\x22\x20\x2f\x3e\x0d\x0a\x20\x20\ +\x3c\x67\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\ +\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\ +\x69\x6e\x74\x73\x3d\x22\x31\x36\x35\x2e\x31\x31\x2c\x32\x34\x31\ +\x2e\x33\x33\x20\x31\x30\x35\x2e\x38\x33\x2c\x32\x34\x31\x2e\x33\ +\x33\x20\x31\x30\x35\x2e\x38\x33\x2c\x31\x38\x32\x2e\x30\x36\x20\ +\x31\x36\x35\x2e\x31\x31\x2c\x31\x38\x32\x2e\x30\x36\x20\x22\x2f\ +\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x61\x74\x68\x20\x69\x64\x3d\x22\ +\x5f\x31\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\ +\x20\x64\x3d\x22\x4d\x31\x36\x35\x2e\x31\x31\x20\x32\x34\x31\x2e\ +\x33\x33\x6c\x2d\x35\x39\x2e\x32\x38\x20\x30\x20\x30\x20\x2d\x35\ +\x39\x2e\x32\x37\x20\x35\x39\x2e\x32\x38\x20\x30\x20\x30\x20\x35\ +\x39\x2e\x32\x37\x7a\x6d\x2d\x34\x36\x2e\x39\x33\x20\x2d\x31\x32\ +\x2e\x33\x35\x6c\x33\x34\x2e\x35\x38\x20\x30\x20\x30\x20\x2d\x33\ +\x34\x2e\x35\x37\x20\x2d\x33\x34\x2e\x35\x38\x20\x30\x20\x30\x20\ +\x33\x34\x2e\x35\x37\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\ +\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\ +\x0d\x0a\ +\x00\x00\x07\x45\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x32\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x34\x44\x34\x44\x34\x44\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x36\x2e\x39\x33\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\ +\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x73\x74\x72\x31\ +\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\x67\x72\x61\x79\x3b\x73\x74\ +\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x31\x34\x2e\x31\x31\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\ +\x3a\x72\x6f\x75\x6e\x64\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\ +\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\ +\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\ +\x72\x6f\x6b\x65\x3a\x23\x34\x36\x38\x32\x43\x38\x3b\x73\x74\x72\ +\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x31\x36\x2e\x39\x33\x3b\ +\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x63\x61\x70\x3a\x73\ +\x71\x75\x61\x72\x65\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\ +\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\ +\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\ +\x6c\x3a\x6e\x6f\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\ +\x6c\x32\x20\x7b\x66\x69\x6c\x6c\x3a\x67\x72\x61\x79\x7d\x0d\x0a\ +\x20\x20\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\ +\x23\x43\x38\x41\x30\x30\x30\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\ +\x69\x6c\x33\x20\x7b\x66\x69\x6c\x6c\x3a\x23\x46\x46\x39\x39\x33\ +\x33\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\ +\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\x73\x3e\ +\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\x64\x61\ +\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\ +\x65\x74\x61\x64\x61\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\x72\x65\ +\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\x2d\x4c\ +\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x61\x74\x68\ +\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\ +\x30\x22\x20\x64\x3d\x22\x4d\x32\x34\x39\x2e\x36\x34\x20\x32\x35\ +\x38\x2e\x31\x32\x63\x2d\x34\x2e\x35\x2c\x2d\x31\x32\x38\x2e\x38\ +\x37\x20\x2d\x31\x30\x37\x2e\x39\x33\x2c\x2d\x32\x33\x32\x2e\x33\ +\x20\x2d\x32\x33\x36\x2e\x38\x2c\x2d\x32\x33\x36\x2e\x38\x22\x2f\ +\x3e\x0d\x0a\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\x61\x73\x73\ +\x3d\x22\x66\x69\x6c\x30\x22\x20\x78\x3d\x22\x2d\x30\x2e\x30\x31\ +\x22\x20\x79\x3d\x22\x2d\x30\x22\x20\x77\x69\x64\x74\x68\x3d\x22\ +\x32\x37\x30\x2e\x39\x33\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\ +\x32\x37\x30\x2e\x39\x33\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x6c\x69\ +\x6e\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\ +\x74\x72\x31\x22\x20\x78\x31\x3d\x22\x32\x35\x2e\x34\x38\x22\x20\ +\x79\x31\x3d\x22\x32\x34\x35\x2e\x34\x34\x22\x20\x78\x32\x3d\x22\ +\x31\x35\x35\x2e\x39\x22\x20\x79\x32\x3d\x20\x22\x31\x31\x35\x2e\ +\x30\x32\x22\x20\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x61\x74\x68\x20\ +\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x32\ +\x22\x20\x64\x3d\x22\x4d\x30\x2e\x30\x38\x20\x32\x34\x35\x2e\x34\ +\x34\x6c\x35\x30\x2e\x38\x20\x30\x6d\x2d\x32\x35\x2e\x34\x20\x32\ +\x35\x2e\x34\x6c\x30\x20\x2d\x35\x30\x2e\x38\x22\x2f\x3e\x0d\x0a\ +\x20\x20\x3c\x67\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x6f\x6c\x79\x67\ +\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\ +\x70\x6f\x69\x6e\x74\x73\x3d\x22\x31\x37\x32\x2e\x38\x32\x2c\x39\ +\x38\x2e\x31\x20\x31\x36\x37\x2e\x33\x35\x2c\x31\x31\x38\x2e\x35\ +\x34\x20\x31\x36\x31\x2e\x38\x37\x2c\x31\x33\x38\x2e\x39\x38\x20\ +\x31\x34\x36\x2e\x39\x2c\x31\x32\x34\x2e\x30\x32\x20\x31\x33\x31\ +\x2e\x39\x34\x2c\x31\x30\x39\x2e\x30\x35\x20\x31\x35\x32\x2e\x33\ +\x38\x2c\x31\x30\x33\x2e\x35\x37\x20\x22\x2f\x3e\x0d\x0a\x20\x20\ +\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x69\x64\x3d\x22\x5f\x31\ +\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\x20\x70\ +\x6f\x69\x6e\x74\x73\x3d\x22\x31\x37\x32\x2e\x38\x32\x2c\x39\x38\ +\x2e\x31\x20\x31\x36\x37\x2e\x33\x35\x2c\x31\x31\x38\x2e\x35\x34\ +\x20\x31\x36\x31\x2e\x38\x37\x2c\x31\x33\x38\x2e\x39\x38\x20\x31\ +\x34\x36\x2e\x39\x2c\x31\x32\x34\x2e\x30\x32\x20\x31\x33\x31\x2e\ +\x39\x34\x2c\x31\x30\x39\x2e\x30\x35\x20\x31\x35\x32\x2e\x33\x38\ +\x2c\x31\x30\x33\x2e\x35\x37\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\ +\x2f\x67\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\ +\x69\x64\x3d\x22\x5f\x31\x5f\x30\x22\x20\x63\x6c\x61\x73\x73\x3d\ +\x22\x66\x69\x6c\x33\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x32\ +\x37\x30\x2e\x39\x33\x2c\x32\x32\x38\x2e\x35\x39\x20\x32\x37\x30\ +\x2e\x39\x33\x2c\x32\x37\x30\x2e\x39\x32\x20\x32\x32\x38\x2e\x36\ +\x2c\x32\x37\x30\x2e\x39\x32\x20\x32\x32\x38\x2e\x36\x2c\x32\x32\ +\x38\x2e\x35\x39\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\ +\x79\x67\x6f\x6e\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\x5f\x30\x22\ +\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x33\x22\x20\x70\x6f\ +\x69\x6e\x74\x73\x3d\x22\x34\x32\x2e\x33\x33\x2c\x2d\x30\x2e\x30\ +\x31\x20\x34\x32\x2e\x33\x33\x2c\x34\x32\x2e\x33\x32\x20\x2d\x30\ +\x2c\x34\x32\x2e\x33\x32\x20\x2d\x30\x2c\x2d\x30\x2e\x30\x31\x20\ +\x22\x2f\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\ +\x67\x3e\x0d\x0a\ +\x00\x00\x04\xf2\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x36\x32\x33\x31\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x36\x32\ +\x33\x31\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x36\x32\x2e\x33\x32\x20\x32\x36\ +\x32\x2e\x33\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x34\x36\x38\x32\x43\x38\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x37\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3a\x72\x6f\x75\x6e\x64\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\ +\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\ +\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\x6e\ +\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\ +\x69\x6c\x6c\x3a\x23\x34\x36\x38\x32\x43\x38\x7d\x0d\x0a\x20\x20\ +\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\x6c\x65\x3e\ +\x0d\x0a\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\x3c\x67\x20\ +\x69\x64\x3d\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\x30\x32\x30\ +\x5f\x31\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\ +\x61\x20\x69\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\x72\x70\x49\ +\x44\x5f\x30\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\x72\x22\x2f\ +\x3e\x0d\x0a\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\ +\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x30\x22\x20\x64\x3d\x22\ +\x4d\x31\x33\x31\x2e\x31\x35\x20\x32\x31\x32\x2e\x36\x38\x6c\x30\ +\x20\x2d\x31\x36\x33\x2e\x30\x33\x6d\x2d\x38\x31\x2e\x35\x31\x20\ +\x38\x31\x2e\x35\x31\x6c\x31\x36\x33\x2e\x30\x33\x20\x30\x22\x2f\ +\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\x74\ +\x73\x3d\x22\x32\x31\x31\x2e\x35\x31\x2c\x31\x35\x38\x2e\x36\x36\ +\x20\x32\x36\x32\x2e\x33\x32\x2c\x31\x33\x31\x2e\x31\x37\x20\x32\ +\x31\x31\x2e\x35\x31\x2c\x31\x30\x33\x2e\x36\x37\x20\x22\x2f\x3e\ +\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\ +\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\ +\x3d\x22\x35\x30\x2e\x38\x31\x2c\x31\x35\x38\x2e\x36\x36\x20\x2d\ +\x30\x2c\x31\x33\x31\x2e\x31\x37\x20\x35\x30\x2e\x38\x31\x2c\x31\ +\x30\x33\x2e\x36\x37\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\ +\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\ +\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x31\x35\x38\x2e\x36\ +\x35\x2c\x35\x30\x2e\x38\x31\x20\x31\x33\x31\x2e\x31\x35\x2c\x30\ +\x20\x31\x30\x33\x2e\x36\x36\x2c\x35\x30\x2e\x38\x31\x20\x22\x2f\ +\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\x74\ +\x73\x3d\x22\x31\x35\x38\x2e\x36\x35\x2c\x32\x31\x31\x2e\x35\x31\ +\x20\x31\x33\x31\x2e\x31\x35\x2c\x32\x36\x32\x2e\x33\x32\x20\x31\ +\x30\x33\x2e\x36\x36\x2c\x32\x31\x31\x2e\x35\x31\x20\x22\x2f\x3e\ +\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\ +\x0a\ +\x00\x00\x05\x76\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x34\x36\x38\x32\x43\x38\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x36\x2e\x39\x33\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\ +\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x73\x74\x72\x31\ +\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\x67\x72\x61\x79\x3b\x73\x74\ +\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x31\x34\x2e\x31\x31\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\ +\x3a\x72\x6f\x75\x6e\x64\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\ +\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\ +\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\ +\x6c\x6c\x3a\x6e\x6f\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\ +\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x67\x72\x61\x79\x7d\x0d\ +\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x32\x20\x7b\x66\x69\x6c\x6c\ +\x3a\x23\x46\x46\x39\x39\x33\x33\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\ +\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\ +\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\ +\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\ +\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\x69\ +\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\ +\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\ +\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\ +\x69\x6c\x30\x22\x20\x78\x3d\x22\x2d\x30\x2e\x30\x31\x22\x20\x79\ +\x3d\x22\x2d\x30\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\x30\ +\x2e\x39\x33\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\x30\ +\x2e\x39\x33\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x63\x69\x72\x63\x6c\ +\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\ +\x72\x30\x22\x20\x63\x78\x3d\x22\x31\x33\x35\x2e\x34\x36\x22\x20\ +\x63\x79\x3d\x22\x31\x33\x35\x2e\x34\x36\x22\x20\x72\x3d\x22\x31\ +\x32\x32\x2e\x37\x36\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\ +\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\ +\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x35\x34\x2e\x39\x39\x2c\ +\x35\x34\x2e\x39\x38\x20\x37\x35\x2e\x34\x33\x2c\x36\x30\x2e\x34\ +\x35\x20\x39\x35\x2e\x38\x37\x2c\x36\x35\x2e\x39\x33\x20\x38\x30\ +\x2e\x39\x31\x2c\x38\x30\x2e\x39\x20\x36\x35\x2e\x39\x34\x2c\x39\ +\x35\x2e\x38\x36\x20\x36\x30\x2e\x34\x36\x2c\x37\x35\x2e\x34\x32\ +\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x6c\x69\x6e\x65\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x31\x22\x20\ +\x78\x31\x3d\x22\x31\x33\x32\x2e\x38\x39\x22\x20\x79\x31\x3d\x22\ +\x31\x33\x32\x2e\x38\x38\x22\x20\x78\x32\x3d\x22\x37\x36\x2e\x31\ +\x34\x22\x20\x79\x32\x3d\x20\x22\x37\x36\x2e\x31\x33\x22\x20\x2f\ +\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x69\x64\ +\x3d\x22\x5f\x31\x5f\x30\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\ +\x69\x6c\x32\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x31\x31\x34\ +\x2e\x33\x2c\x31\x35\x36\x2e\x36\x33\x20\x31\x35\x36\x2e\x36\x33\ +\x2c\x31\x35\x36\x2e\x36\x33\x20\x31\x35\x36\x2e\x36\x33\x2c\x31\ +\x31\x34\x2e\x33\x20\x31\x31\x34\x2e\x33\x2c\x31\x31\x34\x2e\x33\ +\x20\x22\x2f\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\ +\x76\x67\x3e\x0d\x0a\ +\x00\x00\x05\xd4\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x31\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x34\x36\x38\x32\x43\x38\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x36\x2e\x39\x33\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\ +\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x73\x74\x72\x30\ +\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\x23\x42\x33\x42\x33\x42\x33\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x31\x37\ +\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\ +\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\ +\x20\x20\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\ +\x6e\x6f\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x31\ +\x20\x7b\x66\x69\x6c\x6c\x3a\x23\x46\x46\x39\x39\x33\x33\x7d\x0d\ +\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\ +\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\ +\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\ +\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\ +\x64\x61\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\ +\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\ +\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x30\x22\x20\ +\x64\x3d\x22\x4d\x31\x33\x35\x2e\x34\x37\x20\x31\x36\x30\x2e\x38\ +\x35\x6c\x30\x20\x2d\x35\x30\x2e\x37\x39\x6d\x32\x35\x2e\x33\x39\ +\x20\x32\x35\x2e\x34\x6c\x2d\x35\x30\x2e\x37\x39\x20\x30\x22\x2f\ +\x3e\x0d\x0a\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\ +\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x30\x22\x20\x64\x3d\x22\ +\x4d\x35\x39\x2e\x32\x36\x20\x31\x33\x35\x2e\x34\x36\x6c\x31\x37\ +\x2e\x30\x33\x20\x30\x6d\x38\x2e\x33\x35\x20\x30\x2e\x30\x31\x6c\ +\x31\x37\x2e\x30\x33\x20\x30\x6d\x36\x37\x2e\x35\x37\x20\x30\x6c\ +\x31\x37\x2e\x30\x33\x20\x30\x6d\x38\x2e\x33\x35\x20\x2d\x30\x2e\ +\x30\x31\x6c\x31\x37\x2e\x30\x33\x20\x30\x22\x2f\x3e\x0d\x0a\x20\ +\x20\x3c\x65\x6c\x6c\x69\x70\x73\x65\x20\x63\x6c\x61\x73\x73\x3d\ +\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x31\x22\x20\x63\x78\x3d\x22\ +\x31\x33\x35\x2e\x34\x36\x22\x20\x63\x79\x3d\x22\x31\x33\x35\x2e\ +\x34\x36\x22\x20\x72\x78\x3d\x22\x31\x32\x37\x22\x20\x72\x79\x3d\ +\x22\x39\x33\x2e\x31\x33\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\ +\x6c\x79\x67\x6f\x6e\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\x22\x20\ +\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\ +\x6e\x74\x73\x3d\x22\x30\x2e\x30\x38\x2c\x31\x31\x34\x2e\x33\x20\ +\x34\x32\x2e\x34\x31\x2c\x31\x31\x34\x2e\x33\x20\x34\x32\x2e\x34\ +\x31\x2c\x31\x35\x36\x2e\x36\x33\x20\x30\x2e\x30\x38\x2c\x31\x35\ +\x36\x2e\x36\x33\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\ +\x79\x67\x6f\x6e\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\x5f\x30\x22\ +\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\ +\x69\x6e\x74\x73\x3d\x22\x31\x31\x34\x2e\x32\x39\x2c\x32\x31\x2e\ +\x32\x34\x20\x31\x35\x36\x2e\x36\x32\x2c\x32\x31\x2e\x32\x34\x20\ +\x31\x35\x36\x2e\x36\x32\x2c\x36\x33\x2e\x35\x37\x20\x31\x31\x34\ +\x2e\x32\x39\x2c\x36\x33\x2e\x35\x37\x20\x22\x2f\x3e\x0d\x0a\x20\ +\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x69\x64\x3d\x22\x5f\x31\ +\x5f\x30\x5f\x31\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\ +\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x32\x32\x38\x2e\x35\ +\x2c\x31\x31\x34\x2e\x33\x20\x32\x37\x30\x2e\x38\x33\x2c\x31\x31\ +\x34\x2e\x33\x20\x32\x37\x30\x2e\x38\x33\x2c\x31\x35\x36\x2e\x36\ +\x33\x20\x32\x32\x38\x2e\x35\x2c\x31\x35\x36\x2e\x36\x33\x20\x22\ +\x2f\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\ +\x3e\x0d\x0a\ +\x00\x00\x05\xb5\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x31\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x34\x36\x38\x32\x43\x38\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x36\x2e\x39\x33\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\ +\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x73\x74\x72\x30\ +\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\x23\x42\x33\x42\x33\x42\x33\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x31\x37\ +\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\ +\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\ +\x20\x20\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\ +\x6e\x6f\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x31\ +\x20\x7b\x66\x69\x6c\x6c\x3a\x23\x46\x46\x39\x39\x33\x33\x7d\x0d\ +\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\ +\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\ +\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\ +\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\ +\x64\x61\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\ +\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\ +\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x30\x22\x20\ +\x64\x3d\x22\x4d\x31\x33\x35\x2e\x34\x36\x20\x31\x36\x30\x2e\x38\ +\x36\x6c\x30\x20\x2d\x35\x30\x2e\x37\x39\x6d\x32\x35\x2e\x34\x20\ +\x32\x35\x2e\x33\x39\x6c\x2d\x35\x30\x2e\x37\x39\x20\x30\x22\x2f\ +\x3e\x0d\x0a\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\ +\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x30\x22\x20\x64\x3d\x22\ +\x4d\x31\x33\x35\x2e\x34\x36\x20\x31\x36\x30\x2e\x38\x36\x6c\x30\ +\x20\x2d\x35\x30\x2e\x37\x39\x6d\x32\x35\x2e\x34\x20\x32\x35\x2e\ +\x33\x39\x6c\x2d\x35\x30\x2e\x37\x39\x20\x30\x22\x2f\x3e\x0d\x0a\ +\x20\x20\x3c\x65\x6c\x6c\x69\x70\x73\x65\x20\x63\x6c\x61\x73\x73\ +\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x31\x22\x20\x63\x78\x3d\ +\x22\x31\x33\x35\x2e\x34\x36\x22\x20\x63\x79\x3d\x22\x31\x33\x35\ +\x2e\x34\x36\x22\x20\x72\x78\x3d\x22\x31\x32\x37\x22\x20\x72\x79\ +\x3d\x22\x39\x33\x2e\x31\x33\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\ +\x6f\x6c\x79\x67\x6f\x6e\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\x22\ +\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\ +\x69\x6e\x74\x73\x3d\x22\x33\x38\x2e\x31\x35\x2c\x33\x38\x2e\x31\ +\x36\x20\x38\x30\x2e\x34\x38\x2c\x33\x38\x2e\x31\x36\x20\x38\x30\ +\x2e\x34\x38\x2c\x38\x30\x2e\x34\x39\x20\x33\x38\x2e\x31\x35\x2c\ +\x38\x30\x2e\x34\x39\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\ +\x6c\x79\x67\x6f\x6e\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\x5f\x30\ +\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\ +\x6f\x69\x6e\x74\x73\x3d\x22\x32\x32\x38\x2e\x35\x2c\x31\x31\x34\ +\x2e\x33\x20\x32\x37\x30\x2e\x38\x33\x2c\x31\x31\x34\x2e\x33\x20\ +\x32\x37\x30\x2e\x38\x33\x2c\x31\x35\x36\x2e\x36\x33\x20\x32\x32\ +\x38\x2e\x35\x2c\x31\x35\x36\x2e\x36\x33\x20\x22\x2f\x3e\x0d\x0a\ +\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x69\x64\x3d\x22\x5f\ +\x31\x5f\x30\x5f\x31\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\ +\x6c\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x33\x38\x2e\x31\ +\x35\x2c\x31\x39\x30\x2e\x34\x34\x20\x38\x30\x2e\x34\x38\x2c\x31\ +\x39\x30\x2e\x34\x34\x20\x38\x30\x2e\x34\x38\x2c\x32\x33\x32\x2e\ +\x37\x37\x20\x33\x38\x2e\x31\x35\x2c\x32\x33\x32\x2e\x37\x37\x20\ +\x22\x2f\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\ +\x67\x3e\x0d\x0a\ +\x00\x00\x09\x9e\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x34\x36\x38\x32\x43\x38\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x36\x2e\x39\x33\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6c\x69\x6e\x65\x63\x61\x70\x3a\x73\x71\x75\x61\x72\x65\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\ +\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\ +\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\x6e\ +\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x32\x20\x7b\x66\ +\x69\x6c\x6c\x3a\x67\x72\x61\x79\x7d\x0d\x0a\x20\x20\x20\x20\x2e\ +\x66\x69\x6c\x33\x20\x7b\x66\x69\x6c\x6c\x3a\x23\x46\x46\x39\x39\ +\x33\x33\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\ +\x66\x69\x6c\x6c\x3a\x67\x72\x61\x79\x3b\x66\x69\x6c\x6c\x2d\x72\ +\x75\x6c\x65\x3a\x6e\x6f\x6e\x7a\x65\x72\x6f\x7d\x0d\x0a\x20\x20\ +\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\x6c\x65\x3e\ +\x0d\x0a\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\x3c\x67\x20\ +\x69\x64\x3d\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\x30\x32\x30\ +\x5f\x31\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\ +\x61\x20\x69\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\x72\x70\x49\ +\x44\x5f\x30\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\x72\x22\x2f\ +\x3e\x0d\x0a\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\x61\x73\x73\ +\x3d\x22\x66\x69\x6c\x30\x22\x20\x78\x3d\x22\x30\x22\x20\x79\x3d\ +\x22\x30\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\x30\x2e\x39\ +\x32\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\x39\ +\x32\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x67\x3e\x0d\x0a\x20\x20\x20\ +\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\ +\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x32\x39\ +\x2e\x36\x37\x2c\x31\x33\x38\x2e\x32\x34\x20\x32\x39\x2e\x36\x37\ +\x2c\x31\x32\x34\x2e\x31\x32\x20\x33\x35\x2e\x32\x35\x2c\x31\x32\ +\x34\x2e\x32\x36\x20\x34\x30\x2e\x37\x37\x2c\x31\x32\x34\x2e\x36\ +\x38\x20\x34\x36\x2e\x32\x2c\x31\x32\x35\x2e\x33\x38\x20\x35\x31\ +\x2e\x35\x36\x2c\x31\x32\x36\x2e\x33\x33\x20\x35\x36\x2e\x38\x32\ +\x2c\x31\x32\x37\x2e\x35\x35\x20\x36\x31\x2e\x39\x38\x2c\x31\x32\ +\x39\x2e\x30\x31\x20\x36\x37\x2e\x30\x32\x2c\x31\x33\x30\x2e\x37\ +\x32\x20\x37\x31\x2e\x39\x36\x2c\x31\x33\x32\x2e\x36\x36\x20\x37\ +\x36\x2e\x37\x38\x2c\x31\x33\x34\x2e\x38\x34\x20\x38\x31\x2e\x34\ +\x37\x2c\x31\x33\x37\x2e\x32\x34\x20\x38\x36\x2e\x30\x32\x2c\x31\ +\x33\x39\x2e\x38\x36\x20\x39\x30\x2e\x34\x32\x2c\x31\x34\x32\x2e\ +\x36\x38\x20\x39\x34\x2e\x36\x38\x2c\x31\x34\x35\x2e\x37\x32\x20\ +\x39\x38\x2e\x37\x39\x2c\x31\x34\x38\x2e\x39\x34\x20\x31\x30\x32\ +\x2e\x37\x33\x2c\x31\x35\x32\x2e\x33\x36\x20\x31\x30\x36\x2e\x35\ +\x2c\x31\x35\x35\x2e\x39\x35\x20\x31\x31\x30\x2e\x31\x2c\x31\x35\ +\x39\x2e\x37\x33\x20\x31\x31\x33\x2e\x35\x31\x2c\x31\x36\x33\x2e\ +\x36\x37\x20\x31\x31\x36\x2e\x37\x34\x2c\x31\x36\x37\x2e\x37\x36\ +\x20\x31\x31\x39\x2e\x37\x37\x2c\x31\x37\x32\x2e\x30\x34\x20\x31\ +\x32\x32\x2e\x35\x39\x2c\x31\x37\x36\x2e\x34\x34\x20\x31\x32\x35\ +\x2e\x32\x31\x2c\x31\x38\x30\x2e\x39\x39\x20\x31\x32\x37\x2e\x36\ +\x31\x2c\x31\x38\x35\x2e\x36\x38\x20\x31\x32\x39\x2e\x37\x39\x2c\ +\x31\x39\x30\x2e\x34\x39\x20\x31\x33\x31\x2e\x37\x33\x2c\x31\x39\ +\x35\x2e\x34\x33\x20\x31\x33\x33\x2e\x34\x34\x2c\x32\x30\x30\x2e\ +\x34\x37\x20\x31\x33\x34\x2e\x39\x2c\x32\x30\x35\x2e\x36\x34\x20\ +\x31\x33\x36\x2e\x31\x32\x2c\x32\x31\x30\x2e\x39\x20\x31\x33\x37\ +\x2e\x30\x37\x2c\x32\x31\x36\x2e\x32\x36\x20\x31\x33\x37\x2e\x37\ +\x37\x2c\x32\x32\x31\x2e\x36\x39\x20\x31\x33\x38\x2e\x31\x39\x2c\ +\x32\x32\x37\x2e\x32\x31\x20\x31\x33\x38\x2e\x33\x33\x2c\x32\x33\ +\x32\x2e\x37\x39\x20\x31\x32\x34\x2e\x32\x31\x2c\x32\x33\x32\x2e\ +\x37\x39\x20\x31\x32\x34\x2e\x30\x39\x2c\x32\x32\x37\x2e\x39\x31\ +\x20\x31\x32\x33\x2e\x37\x33\x2c\x32\x32\x33\x2e\x31\x31\x20\x31\ +\x32\x33\x2e\x31\x33\x2c\x32\x31\x38\x2e\x33\x38\x20\x31\x32\x32\ +\x2e\x33\x2c\x32\x31\x33\x2e\x37\x32\x20\x31\x32\x31\x2e\x32\x34\ +\x2c\x32\x30\x39\x2e\x31\x36\x20\x31\x31\x39\x2e\x39\x36\x2c\x32\ +\x30\x34\x2e\x36\x37\x20\x31\x31\x38\x2e\x34\x37\x2c\x32\x30\x30\ +\x2e\x32\x37\x20\x31\x31\x36\x2e\x37\x39\x2c\x31\x39\x35\x2e\x39\ +\x39\x20\x31\x31\x34\x2e\x38\x39\x2c\x31\x39\x31\x2e\x38\x20\x31\ +\x31\x32\x2e\x38\x31\x2c\x31\x38\x37\x2e\x37\x33\x20\x31\x31\x30\ +\x2e\x35\x33\x2c\x31\x38\x33\x2e\x37\x36\x20\x31\x30\x38\x2e\x30\ +\x37\x2c\x31\x37\x39\x2e\x39\x32\x20\x31\x30\x35\x2e\x34\x34\x2c\ +\x31\x37\x36\x2e\x32\x32\x20\x31\x30\x32\x2e\x36\x33\x2c\x31\x37\ +\x32\x2e\x36\x35\x20\x39\x39\x2e\x36\x36\x2c\x31\x36\x39\x2e\x32\ +\x31\x20\x39\x36\x2e\x35\x32\x2c\x31\x36\x35\x2e\x39\x33\x20\x39\ +\x33\x2e\x32\x33\x2c\x31\x36\x32\x2e\x38\x20\x38\x39\x2e\x38\x31\ +\x2c\x31\x35\x39\x2e\x38\x32\x20\x38\x36\x2e\x32\x34\x2c\x31\x35\ +\x37\x2e\x30\x32\x20\x38\x32\x2e\x35\x32\x2c\x31\x35\x34\x2e\x33\ +\x38\x20\x37\x38\x2e\x36\x38\x2c\x31\x35\x31\x2e\x39\x32\x20\x37\ +\x34\x2e\x37\x33\x2c\x31\x34\x39\x2e\x36\x34\x20\x37\x30\x2e\x36\ +\x36\x2c\x31\x34\x37\x2e\x35\x36\x20\x36\x36\x2e\x34\x38\x2c\x31\ +\x34\x35\x2e\x36\x36\x20\x36\x32\x2e\x31\x38\x2c\x31\x34\x33\x2e\ +\x39\x38\x20\x35\x37\x2e\x37\x38\x2c\x31\x34\x32\x2e\x34\x39\x20\ +\x35\x33\x2e\x33\x2c\x31\x34\x31\x2e\x32\x31\x20\x34\x38\x2e\x37\ +\x34\x2c\x31\x34\x30\x2e\x31\x35\x20\x34\x34\x2e\x30\x38\x2c\x31\ +\x33\x39\x2e\x33\x32\x20\x33\x39\x2e\x33\x35\x2c\x31\x33\x38\x2e\ +\x37\x32\x20\x33\x34\x2e\x35\x35\x2c\x31\x33\x38\x2e\x33\x36\x20\ +\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\x20\x20\x3c\ +\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\ +\x69\x6c\x32\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x31\x33\x31\ +\x2e\x32\x33\x2c\x32\x36\x33\x2e\x37\x37\x20\x31\x32\x30\x2e\x36\ +\x35\x2c\x32\x34\x35\x2e\x34\x34\x20\x31\x31\x30\x2e\x30\x37\x2c\ +\x32\x32\x37\x2e\x31\x31\x20\x31\x33\x31\x2e\x32\x33\x2c\x32\x32\ +\x37\x2e\x31\x31\x20\x31\x35\x32\x2e\x34\x2c\x32\x32\x37\x2e\x31\ +\x31\x20\x31\x34\x31\x2e\x38\x32\x2c\x32\x34\x35\x2e\x34\x34\x20\ +\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\ +\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\x20\x70\x6f\x69\ +\x6e\x74\x73\x3d\x22\x37\x2e\x31\x35\x2c\x31\x33\x31\x2e\x32\x32\ +\x20\x32\x35\x2e\x34\x38\x2c\x31\x32\x30\x2e\x36\x33\x20\x34\x33\ +\x2e\x38\x2c\x31\x31\x30\x2e\x30\x36\x20\x34\x33\x2e\x38\x2c\x31\ +\x33\x31\x2e\x32\x32\x20\x34\x33\x2e\x38\x2c\x31\x35\x32\x2e\x33\ +\x38\x20\x32\x35\x2e\x34\x38\x2c\x31\x34\x31\x2e\x38\x31\x20\x22\ +\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\ +\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x30\x22\x20\x64\x3d\ +\x22\x4d\x32\x34\x39\x2e\x36\x34\x20\x32\x35\x38\x2e\x31\x32\x63\ +\x2d\x34\x2e\x35\x2c\x2d\x31\x32\x38\x2e\x38\x37\x20\x2d\x31\x30\ +\x37\x2e\x39\x33\x2c\x2d\x32\x33\x32\x2e\x33\x20\x2d\x32\x33\x36\ +\x2e\x38\x2c\x2d\x32\x33\x36\x2e\x38\x22\x2f\x3e\x0d\x0a\x20\x20\ +\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x69\x64\x3d\x22\x5f\x31\x5f\ +\x30\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x33\x22\x20\ +\x70\x6f\x69\x6e\x74\x73\x3d\x22\x34\x32\x2e\x33\x33\x2c\x2d\x30\ +\x2e\x30\x31\x20\x34\x32\x2e\x33\x33\x2c\x34\x32\x2e\x33\x32\x20\ +\x2d\x30\x2c\x34\x32\x2e\x33\x32\x20\x2d\x30\x2c\x2d\x30\x2e\x30\ +\x31\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\ +\x6e\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\x5f\x30\x22\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x33\x22\x20\x70\x6f\x69\x6e\x74\ +\x73\x3d\x22\x32\x37\x30\x2e\x39\x32\x2c\x32\x32\x38\x2e\x36\x20\ +\x32\x37\x30\x2e\x39\x32\x2c\x32\x37\x30\x2e\x39\x33\x20\x32\x32\ +\x38\x2e\x35\x39\x2c\x32\x37\x30\x2e\x39\x33\x20\x32\x32\x38\x2e\ +\x35\x39\x2c\x32\x32\x38\x2e\x36\x20\x22\x2f\x3e\x0d\x0a\x20\x3c\ +\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x09\x8e\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x34\x36\x38\x32\x43\x38\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x37\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6c\x69\x6e\x65\x63\x61\x70\x3a\x72\x6f\x75\x6e\x64\x3b\ +\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\ +\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\ +\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\x6e\x65\ +\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\x69\ +\x6c\x6c\x3a\x75\x72\x6c\x28\x23\x69\x64\x30\x29\x7d\x0d\x0a\x20\ +\x20\x20\x20\x2e\x66\x69\x6c\x32\x20\x7b\x66\x69\x6c\x6c\x3a\x75\ +\x72\x6c\x28\x23\x69\x64\x31\x29\x7d\x0d\x0a\x20\x20\x20\x20\x2e\ +\x66\x69\x6c\x33\x20\x7b\x66\x69\x6c\x6c\x3a\x75\x72\x6c\x28\x23\ +\x69\x64\x32\x29\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\ +\x20\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\x20\x3c\x6c\x69\ +\x6e\x65\x61\x72\x47\x72\x61\x64\x69\x65\x6e\x74\x20\x69\x64\x3d\ +\x22\x69\x64\x30\x22\x20\x67\x72\x61\x64\x69\x65\x6e\x74\x55\x6e\ +\x69\x74\x73\x3d\x22\x75\x73\x65\x72\x53\x70\x61\x63\x65\x4f\x6e\ +\x55\x73\x65\x22\x20\x78\x31\x3d\x22\x31\x37\x39\x2e\x32\x32\x22\ +\x20\x79\x31\x3d\x22\x35\x36\x2e\x31\x34\x22\x20\x78\x32\x3d\x22\ +\x32\x31\x34\x2e\x37\x38\x22\x20\x79\x32\x3d\x22\x39\x31\x2e\x37\ +\x22\x3e\x0d\x0a\x20\x20\x20\x3c\x73\x74\x6f\x70\x20\x6f\x66\x66\ +\x73\x65\x74\x3d\x22\x30\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\ +\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\x20\x73\ +\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x46\x41\x39\x45\x30\ +\x44\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x73\x74\x6f\x70\x20\x6f\ +\x66\x66\x73\x65\x74\x3d\x22\x30\x2e\x33\x32\x31\x35\x36\x39\x22\ +\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\ +\x63\x69\x74\x79\x3a\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\ +\x6f\x72\x3a\x23\x46\x45\x46\x45\x46\x45\x22\x2f\x3e\x0d\x0a\x20\ +\x20\x20\x3c\x73\x74\x6f\x70\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\ +\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\ +\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\ +\x6f\x6c\x6f\x72\x3a\x23\x44\x39\x37\x33\x30\x30\x22\x2f\x3e\x0d\ +\x0a\x20\x20\x3c\x2f\x6c\x69\x6e\x65\x61\x72\x47\x72\x61\x64\x69\ +\x65\x6e\x74\x3e\x0d\x0a\x20\x20\x3c\x6c\x69\x6e\x65\x61\x72\x47\ +\x72\x61\x64\x69\x65\x6e\x74\x20\x69\x64\x3d\x22\x69\x64\x31\x22\ +\x20\x67\x72\x61\x64\x69\x65\x6e\x74\x55\x6e\x69\x74\x73\x3d\x22\ +\x75\x73\x65\x72\x53\x70\x61\x63\x65\x4f\x6e\x55\x73\x65\x22\x20\ +\x78\x31\x3d\x22\x32\x31\x37\x2e\x37\x35\x22\x20\x79\x31\x3d\x22\ +\x31\x37\x2e\x36\x32\x22\x20\x78\x32\x3d\x22\x32\x35\x33\x2e\x33\ +\x22\x20\x79\x32\x3d\x22\x35\x33\x2e\x31\x37\x22\x3e\x0d\x0a\x20\ +\x20\x20\x3c\x73\x74\x6f\x70\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\ +\x30\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\ +\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\ +\x6f\x6c\x6f\x72\x3a\x23\x32\x34\x32\x39\x32\x45\x22\x2f\x3e\x0d\ +\x0a\x20\x20\x20\x3c\x73\x74\x6f\x70\x20\x6f\x66\x66\x73\x65\x74\ +\x3d\x22\x30\x2e\x33\x32\x31\x35\x36\x39\x22\x20\x73\x74\x79\x6c\ +\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\ +\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x42\ +\x30\x42\x46\x42\x46\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x73\x74\ +\x6f\x70\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\x31\x22\x20\x73\x74\ +\x79\x6c\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\ +\x79\x3a\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\ +\x23\x32\x34\x32\x39\x32\x45\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\ +\x6c\x69\x6e\x65\x61\x72\x47\x72\x61\x64\x69\x65\x6e\x74\x3e\x0d\ +\x0a\x20\x20\x3c\x6c\x69\x6e\x65\x61\x72\x47\x72\x61\x64\x69\x65\ +\x6e\x74\x20\x69\x64\x3d\x22\x69\x64\x32\x22\x20\x67\x72\x61\x64\ +\x69\x65\x6e\x74\x55\x6e\x69\x74\x73\x3d\x22\x75\x73\x65\x72\x53\ +\x70\x61\x63\x65\x4f\x6e\x55\x73\x65\x22\x20\x78\x31\x3d\x22\x31\ +\x34\x32\x2e\x31\x38\x22\x20\x79\x31\x3d\x22\x31\x30\x35\x2e\x30\ +\x33\x22\x20\x78\x32\x3d\x22\x31\x38\x33\x2e\x36\x37\x22\x20\x79\ +\x32\x3d\x22\x31\x32\x32\x2e\x38\x31\x22\x3e\x0d\x0a\x20\x20\x20\ +\x3c\x73\x74\x6f\x70\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\x30\x22\ +\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\ +\x63\x69\x74\x79\x3a\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\ +\x6f\x72\x3a\x23\x33\x38\x32\x42\x32\x36\x22\x2f\x3e\x0d\x0a\x20\ +\x20\x20\x3c\x73\x74\x6f\x70\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\ +\x30\x2e\x33\x32\x31\x35\x36\x39\x22\x20\x73\x74\x79\x6c\x65\x3d\ +\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\ +\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x45\x33\x45\ +\x33\x44\x45\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x73\x74\x6f\x70\ +\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\x31\x22\x20\x73\x74\x79\x6c\ +\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\ +\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x33\ +\x38\x32\x42\x32\x36\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x6c\x69\ +\x6e\x65\x61\x72\x47\x72\x61\x64\x69\x65\x6e\x74\x3e\x0d\x0a\x20\ +\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\ +\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\ +\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\x69\ +\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\ +\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\ +\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\ +\x69\x6c\x30\x22\x20\x78\x3d\x22\x2d\x30\x2e\x30\x31\x22\x20\x79\ +\x3d\x22\x2d\x30\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\x30\ +\x2e\x39\x33\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\x30\ +\x2e\x39\x33\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\ +\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\ +\x74\x72\x30\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x32\x35\x2e\ +\x33\x39\x2c\x31\x31\x38\x2e\x35\x33\x20\x32\x35\x2e\x33\x39\x2c\ +\x32\x34\x35\x2e\x35\x33\x20\x31\x35\x32\x2e\x33\x39\x2c\x32\x34\ +\x35\x2e\x35\x33\x20\x32\x33\x37\x2e\x30\x36\x2c\x31\x36\x30\x2e\ +\x38\x36\x20\x31\x31\x30\x2e\x30\x36\x2c\x33\x33\x2e\x38\x36\x20\ +\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x67\x20\x69\x64\x3d\x22\x5f\x32\ +\x37\x37\x38\x35\x32\x35\x39\x31\x31\x33\x34\x34\x22\x3e\x0d\x0a\ +\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\x20\x20\x3c\x67\x20\x69\x64\x3d\ +\x22\x5f\x32\x37\x37\x38\x35\x32\x35\x39\x31\x30\x38\x31\x36\x22\ +\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\ +\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\ +\x74\x73\x3d\x22\x32\x33\x39\x2e\x39\x37\x2c\x36\x36\x2e\x35\x31\ +\x20\x31\x38\x39\x2e\x35\x39\x2c\x31\x31\x36\x2e\x38\x38\x20\x31\ +\x35\x34\x2e\x30\x34\x2c\x38\x31\x2e\x33\x33\x20\x32\x30\x34\x2e\ +\x34\x31\x2c\x33\x30\x2e\x39\x35\x20\x22\x2f\x3e\x0d\x0a\x20\x20\ +\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\ +\x22\x66\x69\x6c\x32\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x32\ +\x33\x31\x2e\x30\x38\x2c\x34\x2e\x32\x38\x20\x32\x30\x34\x2e\x34\ +\x31\x2c\x33\x30\x2e\x39\x35\x20\x32\x33\x39\x2e\x39\x37\x2c\x36\ +\x36\x2e\x35\x31\x20\x32\x36\x36\x2e\x36\x34\x2c\x33\x39\x2e\x38\ +\x34\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x6f\x6c\x79\x67\ +\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x33\x22\x20\ +\x70\x6f\x69\x6e\x74\x73\x3d\x22\x31\x35\x34\x2e\x30\x33\x2c\x38\ +\x31\x2e\x33\x34\x20\x31\x38\x39\x2e\x35\x38\x2c\x31\x31\x36\x2e\ +\x38\x39\x20\x31\x34\x38\x2e\x31\x31\x2c\x31\x32\x32\x2e\x38\x31\ +\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\x20\x3c\ +\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x05\xb4\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x39\x39\x39\x39\x39\x39\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x37\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\ +\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x73\x74\x72\x32\ +\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\x23\x34\x36\x38\x32\x43\x38\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x31\x37\ +\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x63\ +\x61\x70\x3a\x72\x6f\x75\x6e\x64\x3b\x73\x74\x72\x6f\x6b\x65\x2d\ +\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\x32\ +\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x73\x74\x72\x31\x20\x7b\ +\x73\x74\x72\x6f\x6b\x65\x3a\x23\x46\x46\x39\x39\x33\x33\x3b\x73\ +\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x31\x37\x2e\x36\ +\x34\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x63\x61\x70\ +\x3a\x72\x6f\x75\x6e\x64\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\ +\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\ +\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\ +\x6c\x6c\x3a\x6e\x6f\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\ +\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x77\x68\x69\x74\x65\x7d\ +\x0d\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\ +\x79\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\ +\x20\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\ +\x30\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\ +\x61\x64\x61\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\ +\x6f\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\ +\x65\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\x65\x63\x74\x20\x63\ +\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\x78\x3d\x22\x2d\ +\x30\x2e\x30\x31\x22\x20\x79\x3d\x22\x2d\x30\x22\x20\x77\x69\x64\ +\x74\x68\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x20\x68\x65\x69\x67\ +\x68\x74\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x2f\x3e\x0d\x0a\x20\ +\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\ +\x6c\x30\x20\x73\x74\x72\x30\x22\x20\x64\x3d\x22\x4d\x31\x33\x35\ +\x2e\x34\x36\x20\x32\x31\x37\x2e\x39\x39\x6c\x30\x20\x2d\x31\x37\ +\x2e\x30\x32\x6d\x30\x20\x2d\x31\x30\x31\x2e\x34\x32\x6c\x30\x20\ +\x2d\x31\x37\x2e\x30\x32\x6d\x30\x20\x34\x36\x2e\x36\x33\x6c\x30\ +\x20\x2d\x31\x37\x2e\x30\x33\x6d\x30\x20\x2d\x37\x31\x2e\x37\x38\ +\x6c\x30\x20\x2d\x31\x37\x2e\x30\x34\x6d\x30\x20\x34\x36\x2e\x36\ +\x34\x6c\x30\x20\x2d\x31\x37\x2e\x30\x33\x6d\x30\x20\x31\x39\x34\ +\x2e\x36\x38\x6c\x30\x20\x2d\x31\x37\x2e\x30\x33\x6d\x30\x20\x2d\ +\x37\x31\x2e\x37\x38\x6c\x30\x20\x2d\x31\x37\x2e\x30\x34\x6d\x30\ +\x20\x34\x36\x2e\x36\x34\x6c\x30\x20\x2d\x31\x37\x2e\x30\x33\x22\ +\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\ +\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x20\x73\x74\x72\x31\x22\ +\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x32\x39\x2e\x36\x38\x2c\x36\ +\x37\x2e\x37\x33\x20\x39\x37\x2e\x34\x32\x2c\x31\x33\x35\x2e\x34\ +\x36\x20\x32\x39\x2e\x36\x38\x2c\x32\x30\x33\x2e\x32\x20\x22\x2f\ +\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x20\x73\x74\x72\x32\x22\x20\ +\x70\x6f\x69\x6e\x74\x73\x3d\x22\x32\x34\x31\x2e\x32\x34\x2c\x36\ +\x37\x2e\x37\x33\x20\x31\x37\x33\x2e\x35\x2c\x31\x33\x35\x2e\x34\ +\x36\x20\x32\x34\x31\x2e\x32\x34\x2c\x32\x30\x33\x2e\x32\x20\x22\ +\x2f\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\ +\x3e\x0d\x0a\ +\x00\x00\x05\xd5\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\ +\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x33\x20\x7b\ +\x66\x69\x6c\x6c\x3a\x23\x34\x36\x38\x32\x43\x38\x7d\x0d\x0a\x20\ +\x20\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x23\ +\x39\x39\x39\x39\x39\x39\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\ +\x6c\x32\x20\x7b\x66\x69\x6c\x6c\x3a\x23\x45\x36\x45\x36\x45\x36\ +\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\ +\x74\x79\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0d\ +\x0a\x20\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\x64\x61\x5f\ +\x78\x30\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\ +\x74\x61\x64\x61\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\x72\x65\x6c\ +\x43\x6f\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\x2d\x4c\x61\ +\x79\x65\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\x65\x63\x74\x20\ +\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\x78\x3d\x22\ +\x2d\x30\x2e\x30\x31\x22\x20\x79\x3d\x22\x2d\x30\x22\x20\x77\x69\ +\x64\x74\x68\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x20\x68\x65\x69\ +\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x2f\x3e\x0d\x0a\ +\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\ +\x69\x6c\x31\x22\x20\x64\x3d\x22\x4d\x34\x34\x2e\x32\x37\x20\x31\ +\x32\x2e\x37\x6c\x31\x38\x32\x2e\x33\x37\x20\x30\x63\x31\x35\x2e\ +\x30\x35\x2c\x30\x20\x32\x37\x2e\x33\x35\x2c\x31\x32\x2e\x33\x34\ +\x20\x32\x37\x2e\x33\x35\x2c\x32\x37\x2e\x33\x35\x6c\x30\x20\x31\ +\x39\x30\x2e\x38\x32\x63\x30\x2c\x31\x35\x2e\x30\x31\x20\x2d\x31\ +\x32\x2e\x33\x34\x2c\x32\x37\x2e\x33\x35\x20\x2d\x32\x37\x2e\x33\ +\x35\x2c\x32\x37\x2e\x33\x35\x6c\x2d\x31\x38\x32\x2e\x33\x37\x20\ +\x30\x63\x2d\x31\x35\x2e\x30\x31\x2c\x30\x20\x2d\x32\x37\x2e\x33\ +\x35\x2c\x2d\x31\x32\x2e\x33\x20\x2d\x32\x37\x2e\x33\x35\x2c\x2d\ +\x32\x37\x2e\x33\x35\x6c\x30\x20\x2d\x31\x39\x30\x2e\x38\x32\x63\ +\x30\x2c\x2d\x31\x35\x2e\x30\x35\x20\x31\x32\x2e\x33\x2c\x2d\x32\ +\x37\x2e\x33\x35\x20\x32\x37\x2e\x33\x35\x2c\x2d\x32\x37\x2e\x33\ +\x35\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x61\x74\x68\x20\x63\ +\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\x20\x64\x3d\x22\x4d\ +\x35\x33\x2e\x30\x36\x20\x33\x33\x2e\x38\x36\x6c\x31\x36\x34\x2e\ +\x37\x39\x20\x30\x63\x38\x2e\x32\x35\x2c\x30\x20\x31\x34\x2e\x39\ +\x38\x2c\x36\x2e\x37\x35\x20\x31\x34\x2e\x39\x38\x2c\x31\x34\x2e\ +\x39\x38\x6c\x30\x20\x31\x35\x36\x2e\x33\x32\x63\x30\x2c\x38\x2e\ +\x32\x32\x20\x2d\x36\x2e\x37\x35\x2c\x31\x34\x2e\x39\x38\x20\x2d\ +\x31\x34\x2e\x39\x38\x2c\x31\x34\x2e\x39\x38\x6c\x2d\x31\x36\x34\ +\x2e\x37\x39\x20\x30\x63\x2d\x38\x2e\x32\x33\x2c\x30\x20\x2d\x31\ +\x34\x2e\x39\x37\x2c\x2d\x36\x2e\x37\x34\x20\x2d\x31\x34\x2e\x39\ +\x37\x2c\x2d\x31\x34\x2e\x39\x38\x6c\x30\x20\x2d\x31\x35\x36\x2e\ +\x33\x32\x63\x30\x2c\x2d\x38\x2e\x32\x35\x20\x36\x2e\x37\x32\x2c\ +\x2d\x31\x34\x2e\x39\x38\x20\x31\x34\x2e\x39\x37\x2c\x2d\x31\x34\ +\x2e\x39\x38\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\ +\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x33\x22\ +\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x31\x32\x30\x2e\x36\x32\x2c\ +\x35\x34\x2e\x38\x36\x20\x31\x35\x30\x2e\x32\x35\x2c\x35\x34\x2e\ +\x38\x31\x20\x31\x35\x30\x2e\x34\x2c\x31\x35\x30\x2e\x38\x20\x31\ +\x39\x38\x2e\x33\x32\x2c\x31\x30\x32\x2e\x38\x38\x20\x32\x31\x39\ +\x2e\x32\x37\x2c\x31\x32\x33\x2e\x38\x33\x20\x31\x33\x35\x2e\x34\ +\x36\x2c\x32\x30\x37\x2e\x36\x35\x20\x35\x31\x2e\x36\x35\x2c\x31\ +\x32\x33\x2e\x38\x33\x20\x37\x32\x2e\x36\x2c\x31\x30\x32\x2e\x38\ +\x38\x20\x31\x32\x30\x2e\x37\x37\x2c\x31\x35\x31\x2e\x30\x35\x20\ +\x22\x2f\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\ +\x67\x3e\x0d\x0a\ +\x00\x00\x07\xa5\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x34\x36\x38\x32\x43\x38\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x37\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3a\x72\x6f\x75\x6e\x64\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\ +\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\ +\x20\x2e\x73\x74\x72\x31\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\x23\ +\x42\x33\x42\x33\x42\x33\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\ +\x64\x74\x68\x3a\x31\x37\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\x65\ +\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3a\x72\x6f\x75\x6e\x64\x3b\ +\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\ +\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\ +\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\x6e\x65\ +\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x32\x20\x7b\x66\x69\ +\x6c\x6c\x3a\x23\x46\x46\x39\x39\x33\x33\x7d\x0d\x0a\x20\x20\x20\ +\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x67\x72\x61\ +\x79\x3b\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x6e\x6f\x6e\x7a\ +\x65\x72\x6f\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\ +\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\ +\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\ +\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\ +\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\ +\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\ +\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\x65\ +\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\ +\x78\x3d\x22\x2d\x30\x2e\x30\x31\x22\x20\x79\x3d\x22\x2d\x30\x22\ +\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x20\ +\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x2f\ +\x3e\x0d\x0a\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\ +\x3d\x22\x66\x69\x6c\x31\x22\x20\x64\x3d\x22\x4d\x32\x31\x38\x2e\ +\x36\x37\x20\x31\x31\x32\x2e\x34\x32\x6c\x30\x20\x2d\x34\x35\x2e\ +\x37\x36\x63\x30\x2c\x2d\x37\x2e\x33\x20\x2d\x31\x2e\x31\x31\x2c\ +\x2d\x31\x32\x2e\x33\x37\x20\x2d\x33\x2e\x33\x37\x2c\x2d\x31\x35\ +\x2e\x31\x37\x20\x2d\x32\x2e\x32\x35\x2c\x2d\x32\x2e\x37\x39\x20\ +\x2d\x36\x2e\x30\x38\x2c\x2d\x34\x2e\x31\x39\x20\x2d\x31\x31\x2e\ +\x35\x33\x2c\x2d\x34\x2e\x31\x39\x20\x2d\x37\x2c\x30\x20\x2d\x31\ +\x32\x2e\x34\x33\x2c\x31\x2e\x39\x36\x20\x2d\x31\x36\x2e\x32\x38\ +\x2c\x35\x2e\x39\x32\x20\x2d\x33\x2e\x38\x36\x2c\x33\x2e\x39\x36\ +\x20\x2d\x35\x2e\x37\x39\x2c\x39\x2e\x35\x37\x20\x2d\x35\x2e\x37\ +\x39\x2c\x31\x36\x2e\x38\x35\x6c\x30\x20\x34\x32\x2e\x33\x35\x20\ +\x2d\x31\x32\x2e\x35\x31\x20\x30\x20\x30\x20\x2d\x37\x34\x2e\x33\ +\x36\x20\x31\x31\x2e\x39\x37\x20\x30\x20\x30\x20\x31\x30\x2e\x38\ +\x33\x63\x32\x2e\x38\x37\x2c\x2d\x34\x2e\x32\x39\x20\x36\x2e\x32\ +\x35\x2c\x2d\x37\x2e\x34\x39\x20\x31\x30\x2e\x31\x38\x2c\x2d\x39\ +\x2e\x35\x35\x20\x33\x2e\x39\x34\x2c\x2d\x32\x2e\x30\x39\x20\x38\ +\x2e\x34\x39\x2c\x2d\x33\x2e\x31\x32\x20\x31\x33\x2e\x36\x38\x2c\ +\x2d\x33\x2e\x31\x32\x20\x35\x2e\x35\x39\x2c\x30\x20\x31\x30\x2e\ +\x33\x2c\x30\x2e\x38\x34\x20\x31\x34\x2e\x31\x34\x2c\x32\x2e\x35\ +\x32\x20\x33\x2e\x38\x37\x2c\x31\x2e\x36\x39\x20\x36\x2e\x38\x33\ +\x2c\x34\x2e\x32\x33\x20\x38\x2e\x38\x39\x2c\x37\x2e\x35\x39\x20\ +\x31\x2e\x31\x36\x2c\x31\x2e\x38\x32\x20\x31\x2e\x39\x39\x2c\x33\ +\x2e\x39\x33\x20\x32\x2e\x35\x31\x2c\x36\x2e\x33\x35\x20\x30\x2e\ +\x35\x2c\x32\x2e\x34\x34\x20\x30\x2e\x37\x36\x2c\x36\x2e\x32\x38\ +\x20\x30\x2e\x37\x36\x2c\x31\x31\x2e\x35\x36\x6c\x30\x20\x33\x2e\ +\x35\x33\x20\x30\x20\x34\x34\x2e\x36\x35\x20\x2d\x31\x32\x2e\x36\ +\x35\x20\x30\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x6c\x69\x6e\x65\ +\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\ +\x30\x22\x20\x78\x31\x3d\x22\x32\x36\x32\x2e\x34\x36\x22\x20\x79\ +\x31\x3d\x22\x32\x36\x32\x2e\x34\x37\x22\x20\x78\x32\x3d\x22\x38\ +\x2e\x34\x35\x22\x20\x79\x32\x3d\x20\x22\x38\x2e\x34\x35\x22\x20\ +\x2f\x3e\x0d\x0a\x20\x20\x3c\x6c\x69\x6e\x65\x20\x63\x6c\x61\x73\ +\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x31\x22\x20\x78\x31\ +\x3d\x22\x31\x32\x2e\x37\x35\x22\x20\x79\x31\x3d\x22\x31\x31\x34\ +\x2e\x33\x34\x22\x20\x78\x32\x3d\x22\x31\x31\x38\x2e\x36\x33\x22\ +\x20\x79\x32\x3d\x20\x22\x38\x2e\x35\x33\x22\x20\x2f\x3e\x0d\x0a\ +\x20\x20\x3c\x6c\x69\x6e\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\ +\x69\x6c\x30\x20\x73\x74\x72\x31\x22\x20\x78\x31\x3d\x22\x31\x35\ +\x36\x2e\x35\x38\x22\x20\x79\x31\x3d\x22\x32\x35\x38\x2e\x31\x37\ +\x22\x20\x78\x32\x3d\x22\x32\x36\x32\x2e\x33\x39\x22\x20\x79\x32\ +\x3d\x20\x22\x31\x35\x32\x2e\x32\x39\x22\x20\x2f\x3e\x0d\x0a\x20\ +\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x69\x64\x3d\x22\x5f\x31\ +\x5f\x30\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\ +\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x34\x32\x2e\x33\x38\x2c\x38\ +\x34\x2e\x37\x32\x20\x38\x34\x2e\x37\x31\x2c\x38\x34\x2e\x37\x32\ +\x20\x38\x34\x2e\x37\x31\x2c\x34\x32\x2e\x33\x39\x20\x34\x32\x2e\ +\x33\x38\x2c\x34\x32\x2e\x33\x39\x20\x22\x2f\x3e\x0d\x0a\x20\x20\ +\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x69\x64\x3d\x22\x5f\x31\x5f\ +\x30\x5f\x30\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\ +\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x31\x38\x36\x2e\x32\x2c\ +\x32\x32\x38\x2e\x35\x34\x20\x32\x32\x38\x2e\x35\x33\x2c\x32\x32\ +\x38\x2e\x35\x34\x20\x32\x32\x38\x2e\x35\x33\x2c\x31\x38\x36\x2e\ +\x32\x31\x20\x31\x38\x36\x2e\x32\x2c\x31\x38\x36\x2e\x32\x31\x20\ +\x22\x2f\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\ +\x67\x3e\x0d\x0a\ +\x00\x00\x05\xca\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x31\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x39\x39\x39\x39\x39\x39\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x37\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\ +\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x73\x74\x72\x32\ +\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\x23\x34\x36\x38\x32\x43\x38\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x31\x37\ +\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\ +\x6f\x69\x6e\x3a\x72\x6f\x75\x6e\x64\x3b\x73\x74\x72\x6f\x6b\x65\ +\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\ +\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x73\x74\x72\x30\x20\ +\x7b\x73\x74\x72\x6f\x6b\x65\x3a\x23\x46\x46\x39\x39\x33\x33\x3b\ +\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x31\x37\x2e\ +\x36\x34\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\ +\x69\x6e\x3a\x72\x6f\x75\x6e\x64\x3b\x73\x74\x72\x6f\x6b\x65\x2d\ +\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\x32\ +\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\ +\x66\x69\x6c\x6c\x3a\x6e\x6f\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x5d\ +\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\ +\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\ +\x3d\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\ +\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\ +\x69\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\ +\x30\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\ +\x0a\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\ +\x66\x69\x6c\x30\x22\x20\x78\x3d\x22\x2d\x30\x2e\x30\x31\x22\x20\ +\x79\x3d\x22\x2d\x30\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\ +\x30\x2e\x39\x33\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\ +\x30\x2e\x39\x33\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x61\x74\x68\ +\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\ +\x30\x22\x20\x64\x3d\x22\x4d\x32\x34\x35\x2e\x35\x37\x20\x31\x36\ +\x35\x2e\x30\x39\x6c\x2d\x30\x2e\x30\x34\x20\x31\x30\x35\x2e\x38\ +\x34\x6d\x2d\x32\x34\x35\x2e\x35\x35\x20\x2d\x32\x34\x35\x2e\x35\ +\x35\x6c\x31\x30\x35\x2e\x38\x31\x20\x30\x22\x2f\x3e\x0d\x0a\x20\ +\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\ +\x6c\x30\x20\x73\x74\x72\x31\x22\x20\x64\x3d\x22\x4d\x31\x39\x34\ +\x2e\x38\x34\x20\x32\x35\x2e\x33\x39\x6c\x2d\x31\x37\x2e\x30\x34\ +\x20\x30\x6d\x36\x37\x2e\x37\x32\x20\x35\x30\x2e\x36\x39\x6c\x30\ +\x2e\x30\x31\x20\x31\x37\x2e\x30\x34\x6d\x30\x20\x34\x32\x2e\x31\ +\x38\x6c\x30\x20\x31\x37\x2e\x30\x34\x6d\x30\x20\x2d\x34\x36\x2e\ +\x36\x35\x6c\x30\x20\x31\x37\x2e\x30\x34\x6d\x30\x20\x2d\x37\x36\ +\x2e\x32\x35\x6c\x30\x20\x31\x37\x2e\x30\x32\x6d\x30\x20\x2d\x34\ +\x36\x2e\x36\x33\x6c\x30\x20\x31\x37\x2e\x30\x33\x6d\x2d\x31\x30\ +\x39\x2e\x39\x31\x20\x2d\x38\x2e\x35\x31\x6c\x2d\x31\x37\x2e\x30\ +\x34\x20\x30\x6d\x34\x36\x2e\x36\x35\x20\x30\x6c\x2d\x31\x37\x2e\ +\x30\x34\x20\x30\x6d\x37\x36\x2e\x32\x35\x20\x30\x6c\x2d\x31\x37\ +\x2e\x30\x32\x20\x30\x6d\x34\x36\x2e\x36\x33\x20\x30\x6c\x2d\x31\ +\x37\x2e\x30\x33\x20\x30\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x61\ +\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\ +\x74\x72\x32\x22\x20\x64\x3d\x22\x4d\x31\x31\x38\x2e\x34\x39\x20\ +\x32\x35\x2e\x34\x33\x63\x37\x30\x2e\x31\x35\x2c\x30\x20\x31\x32\ +\x37\x2e\x30\x31\x2c\x35\x36\x2e\x38\x36\x20\x31\x32\x37\x2e\x30\ +\x31\x2c\x31\x32\x37\x22\x2f\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\ +\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x07\x05\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x36\x36\x36\x36\x36\x36\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x36\x2e\x39\x33\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6c\x69\x6e\x65\x63\x61\x70\x3a\x73\x71\x75\x61\x72\x65\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\ +\x3a\x72\x6f\x75\x6e\x64\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\ +\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\ +\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\ +\x6c\x6c\x3a\x6e\x6f\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\ +\x69\x6c\x32\x20\x7b\x66\x69\x6c\x6c\x3a\x23\x30\x30\x39\x39\x33\ +\x33\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\ +\x69\x6c\x6c\x3a\x77\x68\x69\x74\x65\x7d\x0d\x0a\x20\x20\x20\x5d\ +\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\ +\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\ +\x3d\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\ +\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\ +\x69\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\ +\x30\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\ +\x0a\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\ +\x66\x69\x6c\x30\x22\x20\x78\x3d\x22\x2d\x30\x2e\x30\x31\x22\x20\ +\x79\x3d\x22\x2d\x30\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\ +\x30\x2e\x39\x33\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\ +\x30\x2e\x39\x33\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x6c\x69\x6e\x65\ +\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\ +\x30\x22\x20\x78\x31\x3d\x22\x32\x32\x30\x2e\x31\x31\x22\x20\x79\ +\x31\x3d\x22\x32\x32\x30\x2e\x31\x33\x22\x20\x78\x32\x3d\x22\x35\ +\x30\x2e\x37\x38\x22\x20\x79\x32\x3d\x20\x22\x35\x30\x2e\x37\x39\ +\x22\x20\x2f\x3e\x0d\x0a\x20\x20\x3c\x67\x3e\x0d\x0a\x20\x20\x20\ +\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\ +\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x2d\x30\ +\x2e\x30\x32\x2c\x35\x39\x2e\x32\x36\x20\x35\x39\x2e\x32\x35\x2c\ +\x35\x39\x2e\x32\x36\x20\x35\x39\x2e\x32\x35\x2c\x2d\x30\x2e\x30\ +\x31\x20\x2d\x30\x2e\x30\x32\x2c\x2d\x30\x2e\x30\x31\x20\x22\x2f\ +\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x61\x74\x68\x20\x69\x64\x3d\x22\ +\x5f\x31\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\ +\x20\x64\x3d\x22\x4d\x31\x32\x2e\x33\x33\x20\x34\x36\x2e\x39\x31\ +\x6c\x33\x34\x2e\x35\x37\x20\x30\x20\x30\x20\x2d\x33\x34\x2e\x35\ +\x37\x20\x2d\x33\x34\x2e\x35\x37\x20\x30\x20\x30\x20\x33\x34\x2e\ +\x35\x37\x7a\x6d\x2d\x31\x32\x2e\x33\x35\x20\x31\x32\x2e\x33\x35\ +\x6c\x35\x39\x2e\x32\x37\x20\x30\x20\x30\x20\x2d\x35\x39\x2e\x32\ +\x37\x20\x2d\x35\x39\x2e\x32\x37\x20\x30\x20\x30\x20\x35\x39\x2e\ +\x32\x37\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\ +\x20\x20\x3c\x67\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x6f\x6c\x79\x67\ +\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\ +\x70\x6f\x69\x6e\x74\x73\x3d\x22\x32\x31\x31\x2e\x36\x35\x2c\x32\ +\x37\x30\x2e\x39\x34\x20\x32\x37\x30\x2e\x39\x32\x2c\x32\x37\x30\ +\x2e\x39\x34\x20\x32\x37\x30\x2e\x39\x32\x2c\x32\x31\x31\x2e\x36\ +\x37\x20\x32\x31\x31\x2e\x36\x35\x2c\x32\x31\x31\x2e\x36\x37\x20\ +\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x61\x74\x68\x20\x69\x64\ +\x3d\x22\x5f\x31\x5f\x30\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\ +\x69\x6c\x32\x22\x20\x64\x3d\x22\x4d\x32\x32\x34\x20\x32\x35\x38\ +\x2e\x35\x39\x6c\x33\x34\x2e\x35\x37\x20\x30\x20\x30\x20\x2d\x33\ +\x34\x2e\x35\x37\x20\x2d\x33\x34\x2e\x35\x37\x20\x30\x20\x30\x20\ +\x33\x34\x2e\x35\x37\x7a\x6d\x2d\x31\x32\x2e\x33\x35\x20\x31\x32\ +\x2e\x33\x35\x6c\x35\x39\x2e\x32\x37\x20\x30\x20\x30\x20\x2d\x35\ +\x39\x2e\x32\x37\x20\x2d\x35\x39\x2e\x32\x37\x20\x30\x20\x30\x20\ +\x35\x39\x2e\x32\x37\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\ +\x3e\x0d\x0a\x20\x20\x3c\x67\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x6f\ +\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\ +\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x31\x30\x35\x2e\x38\ +\x2c\x31\x36\x35\x2e\x31\x20\x31\x36\x35\x2e\x30\x38\x2c\x31\x36\ +\x35\x2e\x31\x20\x31\x36\x35\x2e\x30\x38\x2c\x31\x30\x35\x2e\x38\ +\x32\x20\x31\x30\x35\x2e\x38\x2c\x31\x30\x35\x2e\x38\x32\x20\x22\ +\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x61\x74\x68\x20\x69\x64\x3d\ +\x22\x5f\x31\x5f\x31\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\ +\x6c\x32\x22\x20\x64\x3d\x22\x4d\x31\x31\x38\x2e\x31\x35\x20\x31\ +\x35\x32\x2e\x37\x35\x6c\x33\x34\x2e\x35\x38\x20\x30\x20\x30\x20\ +\x2d\x33\x34\x2e\x35\x38\x20\x2d\x33\x34\x2e\x35\x38\x20\x30\x20\ +\x30\x20\x33\x34\x2e\x35\x38\x7a\x6d\x2d\x31\x32\x2e\x33\x35\x20\ +\x31\x32\x2e\x33\x35\x6c\x35\x39\x2e\x32\x38\x20\x30\x20\x30\x20\ +\x2d\x35\x39\x2e\x32\x38\x20\x2d\x35\x39\x2e\x32\x38\x20\x30\x20\ +\x30\x20\x35\x39\x2e\x32\x38\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\ +\x2f\x67\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\ +\x67\x3e\x0d\x0a\ +\x00\x00\x06\x03\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x31\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x67\x72\x61\x79\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\ +\x68\x3a\x31\x34\x2e\x31\x31\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6c\ +\x69\x6e\x65\x6a\x6f\x69\x6e\x3a\x72\x6f\x75\x6e\x64\x3b\x73\x74\ +\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\ +\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x73\ +\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\x23\x34\x36\x38\ +\x32\x43\x38\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\ +\x3a\x31\x36\x2e\x39\x33\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\ +\x6e\x65\x63\x61\x70\x3a\x73\x71\x75\x61\x72\x65\x3b\x73\x74\x72\ +\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\ +\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\ +\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\x6e\x65\x7d\x0d\x0a\ +\x20\x20\x20\x20\x2e\x66\x69\x6c\x32\x20\x7b\x66\x69\x6c\x6c\x3a\ +\x67\x72\x61\x79\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x31\ +\x20\x7b\x66\x69\x6c\x6c\x3a\x23\x46\x46\x39\x39\x33\x33\x7d\x0d\ +\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\ +\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\ +\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\ +\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\ +\x64\x61\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\ +\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\ +\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\x78\x3d\x22\x30\x22\ +\x20\x79\x3d\x22\x30\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\ +\x30\x2e\x39\x32\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\ +\x30\x2e\x39\x32\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x61\x74\x68\ +\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\ +\x30\x22\x20\x64\x3d\x22\x4d\x32\x34\x39\x2e\x36\x34\x20\x32\x35\ +\x38\x2e\x31\x32\x63\x2d\x34\x2e\x35\x2c\x2d\x31\x32\x38\x2e\x38\ +\x37\x20\x2d\x31\x30\x37\x2e\x39\x33\x2c\x2d\x32\x33\x32\x2e\x33\ +\x20\x2d\x32\x33\x36\x2e\x38\x2c\x2d\x32\x33\x36\x2e\x38\x22\x2f\ +\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x69\x64\ +\x3d\x22\x5f\x31\x5f\x30\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\ +\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x32\x37\x30\ +\x2e\x39\x33\x2c\x32\x32\x38\x2e\x35\x39\x20\x32\x37\x30\x2e\x39\ +\x33\x2c\x32\x37\x30\x2e\x39\x32\x20\x32\x32\x38\x2e\x36\x2c\x32\ +\x37\x30\x2e\x39\x32\x20\x32\x32\x38\x2e\x36\x2c\x32\x32\x38\x2e\ +\x35\x39\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\ +\x6f\x6e\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\x5f\x30\x22\x20\x63\ +\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\ +\x74\x73\x3d\x22\x34\x32\x2e\x33\x33\x2c\x2d\x30\x2e\x30\x31\x20\ +\x34\x32\x2e\x33\x33\x2c\x34\x32\x2e\x33\x32\x20\x2d\x30\x2c\x34\ +\x32\x2e\x33\x32\x20\x2d\x30\x2c\x2d\x30\x2e\x30\x31\x20\x22\x2f\ +\x3e\x0d\x0a\x20\x20\x3c\x6c\x69\x6e\x65\x20\x63\x6c\x61\x73\x73\ +\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x31\x22\x20\x78\x31\x3d\ +\x22\x32\x34\x39\x2e\x37\x39\x22\x20\x79\x31\x3d\x22\x32\x31\x36\ +\x2e\x38\x33\x22\x20\x78\x32\x3d\x22\x32\x34\x39\x2e\x37\x38\x22\ +\x20\x79\x32\x3d\x20\x22\x33\x32\x2e\x33\x39\x22\x20\x2f\x3e\x0d\ +\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\ +\x73\x3d\x22\x66\x69\x6c\x32\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\ +\x22\x32\x34\x39\x2e\x37\x38\x2c\x38\x2e\x34\x36\x20\x32\x36\x30\ +\x2e\x33\x37\x2c\x32\x36\x2e\x37\x39\x20\x32\x37\x30\x2e\x39\x34\ +\x2c\x34\x35\x2e\x31\x32\x20\x32\x34\x39\x2e\x37\x38\x2c\x34\x35\ +\x2e\x31\x32\x20\x32\x32\x38\x2e\x36\x32\x2c\x34\x35\x2e\x31\x32\ +\x20\x32\x33\x39\x2e\x31\x39\x2c\x32\x36\x2e\x37\x39\x20\x22\x2f\ +\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\ +\x0d\x0a\ +\x00\x00\x04\xc9\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x34\x36\x38\x32\x43\x38\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x37\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3a\x72\x6f\x75\x6e\x64\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\ +\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\ +\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\x6e\ +\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\ +\x69\x6c\x6c\x3a\x23\x46\x46\x39\x39\x33\x33\x7d\x0d\x0a\x20\x20\ +\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\x6c\x65\x3e\ +\x0d\x0a\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\x3c\x67\x20\ +\x69\x64\x3d\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\x30\x32\x30\ +\x5f\x31\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\ +\x61\x20\x69\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\x72\x70\x49\ +\x44\x5f\x30\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\x72\x22\x2f\ +\x3e\x0d\x0a\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\x61\x73\x73\ +\x3d\x22\x66\x69\x6c\x30\x22\x20\x78\x3d\x22\x2d\x30\x22\x20\x79\ +\x3d\x22\x2d\x30\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\x30\ +\x2e\x39\x33\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\x30\ +\x2e\x39\x33\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x6c\x69\x6e\x65\x20\ +\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x30\ +\x22\x20\x78\x31\x3d\x22\x32\x39\x2e\x36\x32\x22\x20\x79\x31\x3d\ +\x22\x32\x39\x2e\x36\x32\x22\x20\x78\x32\x3d\x22\x32\x34\x31\x2e\ +\x32\x39\x22\x20\x79\x32\x3d\x20\x22\x32\x34\x31\x2e\x33\x31\x22\ +\x20\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\ +\x69\x64\x3d\x22\x5f\x31\x5f\x30\x22\x20\x63\x6c\x61\x73\x73\x3d\ +\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x32\ +\x36\x32\x2e\x33\x38\x2c\x32\x32\x30\x2e\x30\x34\x20\x32\x32\x30\ +\x2e\x30\x35\x2c\x32\x32\x30\x2e\x30\x34\x20\x32\x32\x30\x2e\x30\ +\x35\x2c\x32\x36\x32\x2e\x33\x37\x20\x32\x36\x32\x2e\x33\x38\x2c\ +\x32\x36\x32\x2e\x33\x37\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\ +\x6f\x6c\x79\x67\x6f\x6e\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\x5f\ +\x30\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\ +\x70\x6f\x69\x6e\x74\x73\x3d\x22\x35\x30\x2e\x38\x38\x2c\x38\x2e\ +\x35\x34\x20\x38\x2e\x35\x35\x2c\x38\x2e\x35\x34\x20\x38\x2e\x35\ +\x35\x2c\x35\x30\x2e\x38\x37\x20\x35\x30\x2e\x38\x38\x2c\x35\x30\ +\x2e\x38\x37\x20\x22\x2f\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\ +\x3c\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x05\xca\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x31\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x36\x36\x36\x36\x36\x36\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x37\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\ +\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x73\x74\x72\x30\ +\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\x23\x39\x39\x39\x39\x39\x39\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x31\x37\ +\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\ +\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\ +\x20\x20\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\ +\x6e\x6f\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x32\ +\x20\x7b\x66\x69\x6c\x6c\x3a\x23\x30\x30\x39\x39\x33\x33\x7d\x0d\ +\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\ +\x3a\x77\x68\x69\x74\x65\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\ +\x0a\x20\x20\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\ +\x64\x65\x66\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\x22\x43\ +\x61\x6d\x61\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\x3e\x0d\ +\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\x69\x64\x3d\ +\x22\x43\x6f\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\x43\x6f\ +\x72\x65\x6c\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\x20\x20\ +\x3c\x72\x65\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\ +\x30\x22\x20\x78\x3d\x22\x2d\x30\x2e\x30\x31\x22\x20\x79\x3d\x22\ +\x2d\x30\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\x30\x2e\x39\ +\x33\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\x39\ +\x33\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x30\x22\x20\ +\x64\x3d\x22\x4d\x31\x33\x37\x2e\x32\x34\x20\x31\x33\x37\x2e\x32\ +\x32\x6c\x2d\x31\x31\x2e\x39\x38\x20\x2d\x31\x31\x2e\x39\x38\x6d\ +\x2d\x38\x2e\x39\x38\x20\x2d\x38\x2e\x39\x38\x6c\x2d\x31\x31\x2e\ +\x39\x37\x20\x2d\x31\x31\x2e\x39\x37\x6d\x2d\x38\x2e\x39\x38\x20\ +\x2d\x38\x2e\x39\x38\x6c\x2d\x31\x31\x2e\x39\x37\x20\x2d\x31\x31\ +\x2e\x39\x37\x6d\x2d\x38\x2e\x39\x38\x20\x2d\x38\x2e\x39\x38\x6c\ +\x2d\x31\x31\x2e\x39\x37\x20\x2d\x31\x31\x2e\x39\x37\x22\x2f\x3e\ +\x0d\x0a\x20\x20\x3c\x6c\x69\x6e\x65\x20\x63\x6c\x61\x73\x73\x3d\ +\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x31\x22\x20\x78\x31\x3d\x22\ +\x31\x34\x36\x2e\x32\x32\x22\x20\x79\x31\x3d\x22\x31\x34\x36\x2e\ +\x32\x22\x20\x78\x32\x3d\x22\x32\x36\x32\x2e\x34\x36\x22\x20\x79\ +\x32\x3d\x20\x22\x32\x36\x32\x2e\x34\x37\x22\x20\x2f\x3e\x0d\x0a\ +\x20\x20\x3c\x67\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x6f\x6c\x79\x67\ +\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\ +\x70\x6f\x69\x6e\x74\x73\x3d\x22\x2d\x30\x2e\x30\x31\x2c\x35\x39\ +\x2e\x32\x36\x20\x35\x39\x2e\x32\x36\x2c\x35\x39\x2e\x32\x36\x20\ +\x35\x39\x2e\x32\x36\x2c\x2d\x30\x2e\x30\x31\x20\x2d\x30\x2e\x30\ +\x31\x2c\x2d\x30\x2e\x30\x31\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x20\ +\x3c\x70\x61\x74\x68\x20\x69\x64\x3d\x22\x5f\x31\x22\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\x20\x64\x3d\x22\x4d\x31\ +\x32\x2e\x33\x34\x20\x34\x36\x2e\x39\x31\x6c\x33\x34\x2e\x35\x37\ +\x20\x30\x20\x30\x20\x2d\x33\x34\x2e\x35\x37\x20\x2d\x33\x34\x2e\ +\x35\x37\x20\x30\x20\x30\x20\x33\x34\x2e\x35\x37\x7a\x6d\x2d\x31\ +\x32\x2e\x33\x35\x20\x31\x32\x2e\x33\x35\x6c\x35\x39\x2e\x32\x37\ +\x20\x30\x20\x30\x20\x2d\x35\x39\x2e\x32\x37\x20\x2d\x35\x39\x2e\ +\x32\x37\x20\x30\x20\x30\x20\x35\x39\x2e\x32\x37\x7a\x22\x2f\x3e\ +\x0d\x0a\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\ +\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x07\x6e\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x39\x39\x39\x39\x39\x39\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x37\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6c\x69\x6e\x65\x63\x61\x70\x3a\x72\x6f\x75\x6e\x64\x3b\ +\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3a\ +\x72\x6f\x75\x6e\x64\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\ +\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\ +\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\ +\x6c\x3a\x6e\x6f\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\ +\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x23\x34\x36\x38\x32\x43\x38\ +\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\ +\x74\x79\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0d\ +\x0a\x20\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\x64\x61\x5f\ +\x78\x30\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\ +\x74\x61\x64\x61\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\x72\x65\x6c\ +\x43\x6f\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\x2d\x4c\x61\ +\x79\x65\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\x65\x63\x74\x20\ +\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\x78\x3d\x22\ +\x2d\x30\x2e\x30\x31\x22\x20\x79\x3d\x22\x2d\x30\x22\x20\x77\x69\ +\x64\x74\x68\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x20\x68\x65\x69\ +\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x2f\x3e\x0d\x0a\ +\x20\x20\x3c\x67\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x61\x74\x68\x20\ +\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x64\x3d\x22\ +\x4d\x32\x31\x31\x2e\x36\x37\x20\x31\x36\x31\x2e\x35\x36\x6c\x2d\ +\x31\x36\x2e\x32\x31\x20\x30\x20\x2d\x31\x37\x2e\x36\x34\x20\x30\ +\x20\x30\x20\x2d\x31\x37\x2e\x36\x34\x20\x30\x20\x2d\x37\x36\x2e\ +\x31\x39\x63\x30\x2c\x2d\x31\x31\x2e\x35\x20\x2d\x34\x2e\x36\x36\ +\x2c\x2d\x32\x31\x2e\x39\x31\x20\x2d\x31\x32\x2e\x31\x39\x2c\x2d\ +\x32\x39\x2e\x34\x34\x20\x2d\x37\x2e\x35\x33\x2c\x2d\x37\x2e\x35\ +\x33\x20\x2d\x31\x37\x2e\x39\x34\x2c\x2d\x31\x32\x2e\x31\x39\x20\ +\x2d\x32\x39\x2e\x34\x34\x2c\x2d\x31\x32\x2e\x31\x39\x20\x2d\x31\ +\x31\x2e\x35\x2c\x30\x20\x2d\x32\x31\x2e\x39\x31\x2c\x34\x2e\x36\ +\x36\x20\x2d\x32\x39\x2e\x34\x34\x2c\x31\x32\x2e\x31\x39\x20\x2d\ +\x37\x2e\x35\x33\x2c\x37\x2e\x35\x33\x20\x2d\x31\x32\x2e\x31\x39\ +\x2c\x31\x37\x2e\x39\x34\x20\x2d\x31\x32\x2e\x31\x39\x2c\x32\x39\ +\x2e\x34\x34\x6c\x30\x20\x37\x36\x2e\x31\x39\x20\x30\x20\x31\x37\ +\x2e\x36\x34\x20\x2d\x31\x37\x2e\x36\x34\x20\x30\x20\x2d\x31\x37\ +\x2e\x36\x37\x20\x30\x63\x2d\x31\x31\x2e\x35\x32\x2c\x30\x20\x2d\ +\x32\x31\x2e\x39\x35\x2c\x34\x2e\x36\x36\x20\x2d\x32\x39\x2e\x34\ +\x37\x2c\x31\x32\x2e\x31\x36\x6c\x30\x2e\x30\x33\x20\x30\x2e\x30\ +\x33\x63\x2d\x37\x2e\x35\x33\x2c\x37\x2e\x35\x33\x20\x2d\x31\x32\ +\x2e\x31\x39\x2c\x31\x37\x2e\x39\x35\x20\x2d\x31\x32\x2e\x31\x39\ +\x2c\x32\x39\x2e\x34\x34\x20\x30\x2c\x31\x31\x2e\x35\x20\x34\x2e\ +\x36\x36\x2c\x32\x31\x2e\x39\x31\x20\x31\x32\x2e\x31\x39\x2c\x32\ +\x39\x2e\x34\x34\x20\x37\x2e\x35\x33\x2c\x37\x2e\x35\x33\x20\x31\ +\x37\x2e\x39\x34\x2c\x31\x32\x2e\x31\x39\x20\x32\x39\x2e\x34\x34\ +\x2c\x31\x32\x2e\x31\x39\x6c\x31\x35\x32\x2e\x34\x32\x20\x30\x63\ +\x31\x31\x2e\x35\x2c\x30\x20\x32\x31\x2e\x39\x31\x2c\x2d\x34\x2e\ +\x36\x36\x20\x32\x39\x2e\x34\x34\x2c\x2d\x31\x32\x2e\x31\x39\x20\ +\x37\x2e\x35\x33\x2c\x2d\x37\x2e\x35\x33\x20\x31\x32\x2e\x31\x39\ +\x2c\x2d\x31\x37\x2e\x39\x34\x20\x31\x32\x2e\x31\x39\x2c\x2d\x32\ +\x39\x2e\x34\x34\x20\x30\x2c\x2d\x31\x31\x2e\x34\x39\x20\x2d\x34\ +\x2e\x36\x36\x2c\x2d\x32\x31\x2e\x39\x31\x20\x2d\x31\x32\x2e\x31\ +\x39\x2c\x2d\x32\x39\x2e\x34\x34\x6c\x30\x2e\x30\x33\x20\x2d\x30\ +\x2e\x30\x33\x63\x2d\x37\x2e\x35\x32\x2c\x2d\x37\x2e\x35\x20\x2d\ +\x31\x37\x2e\x39\x35\x2c\x2d\x31\x32\x2e\x31\x36\x20\x2d\x32\x39\ +\x2e\x34\x37\x2c\x2d\x31\x32\x2e\x31\x36\x7a\x6d\x2d\x31\x36\x2e\ +\x32\x31\x20\x2d\x31\x37\x2e\x36\x34\x6c\x31\x36\x2e\x32\x31\x20\ +\x30\x63\x33\x32\x2e\x37\x33\x2c\x30\x20\x35\x39\x2e\x32\x37\x2c\ +\x32\x36\x2e\x35\x34\x20\x35\x39\x2e\x32\x37\x2c\x35\x39\x2e\x32\ +\x37\x20\x30\x2c\x33\x32\x2e\x37\x33\x20\x2d\x32\x36\x2e\x35\x34\ +\x2c\x35\x39\x2e\x32\x37\x20\x2d\x35\x39\x2e\x32\x37\x2c\x35\x39\ +\x2e\x32\x37\x6c\x2d\x31\x35\x32\x2e\x34\x32\x20\x30\x63\x2d\x33\ +\x32\x2e\x37\x33\x2c\x30\x20\x2d\x35\x39\x2e\x32\x37\x2c\x2d\x32\ +\x36\x2e\x35\x34\x20\x2d\x35\x39\x2e\x32\x37\x2c\x2d\x35\x39\x2e\ +\x32\x37\x20\x30\x2c\x2d\x33\x32\x2e\x37\x33\x20\x32\x36\x2e\x35\ +\x34\x2c\x2d\x35\x39\x2e\x32\x37\x20\x35\x39\x2e\x32\x37\x2c\x2d\ +\x35\x39\x2e\x32\x37\x6c\x31\x37\x2e\x36\x37\x20\x30\x20\x30\x20\ +\x2d\x37\x36\x2e\x31\x39\x63\x30\x2c\x2d\x33\x32\x2e\x37\x33\x20\ +\x32\x36\x2e\x35\x34\x2c\x2d\x35\x39\x2e\x32\x37\x20\x35\x39\x2e\ +\x32\x37\x2c\x2d\x35\x39\x2e\x32\x37\x20\x33\x32\x2e\x37\x33\x2c\ +\x30\x20\x35\x39\x2e\x32\x37\x2c\x32\x36\x2e\x35\x34\x20\x35\x39\ +\x2e\x32\x37\x2c\x35\x39\x2e\x32\x37\x6c\x30\x20\x37\x36\x2e\x31\ +\x39\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\x20\ +\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\ +\x6c\x30\x20\x73\x74\x72\x30\x22\x20\x64\x3d\x22\x4d\x35\x35\x2e\ +\x30\x32\x20\x32\x30\x33\x2e\x32\x6c\x31\x36\x30\x2e\x38\x38\x20\ +\x30\x6d\x2d\x38\x30\x2e\x34\x34\x20\x2d\x31\x33\x35\x2e\x34\x37\ +\x6c\x30\x20\x31\x33\x35\x2e\x34\x37\x22\x2f\x3e\x0d\x0a\x20\x3c\ +\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x06\x87\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\ +\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x33\x20\x7b\ +\x66\x69\x6c\x6c\x3a\x23\x30\x30\x39\x39\x33\x33\x7d\x0d\x0a\x20\ +\x20\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x23\ +\x36\x36\x36\x36\x36\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\ +\x6c\x32\x20\x7b\x66\x69\x6c\x6c\x3a\x77\x68\x69\x74\x65\x7d\x0d\ +\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\ +\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\ +\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\ +\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\ +\x64\x61\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\ +\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\ +\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\x78\x3d\x22\x2d\x30\ +\x2e\x30\x31\x22\x20\x79\x3d\x22\x2d\x30\x22\x20\x77\x69\x64\x74\ +\x68\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x20\x68\x65\x69\x67\x68\ +\x74\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x2f\x3e\x0d\x0a\x20\x20\ +\x3c\x67\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x64\x3d\x22\x4d\x32\ +\x36\x32\x2e\x34\x36\x20\x31\x33\x35\x2e\x34\x36\x63\x30\x2c\x37\ +\x30\x2e\x31\x34\x20\x2d\x35\x36\x2e\x38\x36\x2c\x31\x32\x37\x20\ +\x2d\x31\x32\x37\x2c\x31\x32\x37\x20\x2d\x37\x30\x2e\x31\x34\x2c\ +\x30\x20\x2d\x31\x32\x37\x2c\x2d\x35\x36\x2e\x38\x36\x20\x2d\x31\ +\x32\x37\x2c\x2d\x31\x32\x37\x20\x30\x2c\x2d\x37\x30\x2e\x31\x34\ +\x20\x35\x36\x2e\x38\x36\x2c\x2d\x31\x32\x37\x20\x31\x32\x37\x2c\ +\x2d\x31\x32\x37\x20\x37\x30\x2e\x31\x34\x2c\x30\x20\x31\x32\x37\ +\x2c\x35\x36\x2e\x38\x36\x20\x31\x32\x37\x2c\x31\x32\x37\x7a\x6d\ +\x2d\x34\x39\x2e\x31\x37\x20\x2d\x37\x37\x2e\x39\x63\x2d\x31\x39\ +\x2e\x38\x36\x2c\x2d\x31\x39\x2e\x38\x37\x20\x2d\x34\x37\x2e\x33\ +\x39\x2c\x2d\x33\x32\x2e\x31\x37\x20\x2d\x37\x37\x2e\x38\x33\x2c\ +\x2d\x33\x32\x2e\x31\x37\x20\x2d\x33\x30\x2e\x34\x34\x2c\x30\x20\ +\x2d\x35\x37\x2e\x39\x37\x2c\x31\x32\x2e\x33\x20\x2d\x37\x37\x2e\ +\x38\x33\x2c\x33\x32\x2e\x31\x37\x6c\x2d\x30\x2e\x30\x37\x20\x30\ +\x2e\x30\x37\x63\x2d\x31\x39\x2e\x38\x37\x2c\x31\x39\x2e\x38\x36\ +\x20\x2d\x33\x32\x2e\x31\x37\x2c\x34\x37\x2e\x33\x39\x20\x2d\x33\ +\x32\x2e\x31\x37\x2c\x37\x37\x2e\x38\x33\x20\x30\x2c\x33\x30\x2e\ +\x34\x34\x20\x31\x32\x2e\x33\x2c\x35\x37\x2e\x39\x37\x20\x33\x32\ +\x2e\x31\x37\x2c\x37\x37\x2e\x38\x33\x6c\x30\x2e\x30\x37\x20\x30\ +\x2e\x30\x37\x63\x31\x39\x2e\x38\x36\x2c\x31\x39\x2e\x38\x37\x20\ +\x34\x37\x2e\x33\x39\x2c\x33\x32\x2e\x31\x37\x20\x37\x37\x2e\x38\ +\x33\x2c\x33\x32\x2e\x31\x37\x20\x33\x30\x2e\x34\x34\x2c\x30\x20\ +\x35\x37\x2e\x39\x37\x2c\x2d\x31\x32\x2e\x33\x20\x37\x37\x2e\x38\ +\x33\x2c\x2d\x33\x32\x2e\x31\x37\x6c\x30\x2e\x30\x37\x20\x2d\x30\ +\x2e\x30\x37\x63\x31\x39\x2e\x38\x37\x2c\x2d\x31\x39\x2e\x38\x36\ +\x20\x33\x32\x2e\x31\x37\x2c\x2d\x34\x37\x2e\x33\x39\x20\x33\x32\ +\x2e\x31\x37\x2c\x2d\x37\x37\x2e\x38\x33\x20\x30\x2c\x2d\x33\x30\ +\x2e\x34\x34\x20\x2d\x31\x32\x2e\x33\x2c\x2d\x35\x37\x2e\x39\x37\ +\x20\x2d\x33\x32\x2e\x31\x37\x2c\x2d\x37\x37\x2e\x38\x33\x6c\x2d\ +\x30\x2e\x30\x37\x20\x2d\x30\x2e\x30\x37\x7a\x22\x2f\x3e\x0d\x0a\ +\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\x20\x20\x3c\x67\x3e\x0d\x0a\x20\ +\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\ +\x3d\x22\x66\x69\x6c\x32\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\ +\x31\x36\x35\x2e\x31\x2c\x31\x36\x35\x2e\x31\x20\x31\x30\x35\x2e\ +\x38\x32\x2c\x31\x36\x35\x2e\x31\x20\x31\x30\x35\x2e\x38\x32\x2c\ +\x31\x30\x35\x2e\x38\x32\x20\x31\x36\x35\x2e\x31\x2c\x31\x30\x35\ +\x2e\x38\x32\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x61\x74\ +\x68\x20\x69\x64\x3d\x22\x5f\x31\x22\x20\x63\x6c\x61\x73\x73\x3d\ +\x22\x66\x69\x6c\x33\x22\x20\x64\x3d\x22\x4d\x31\x36\x35\x2e\x31\ +\x20\x31\x36\x35\x2e\x31\x6c\x2d\x35\x39\x2e\x32\x38\x20\x30\x20\ +\x30\x20\x2d\x35\x39\x2e\x32\x38\x20\x35\x39\x2e\x32\x38\x20\x30\ +\x20\x30\x20\x35\x39\x2e\x32\x38\x7a\x6d\x2d\x34\x36\x2e\x39\x33\ +\x20\x2d\x31\x32\x2e\x33\x35\x6c\x33\x34\x2e\x35\x38\x20\x30\x20\ +\x30\x20\x2d\x33\x34\x2e\x35\x38\x20\x2d\x33\x34\x2e\x35\x38\x20\ +\x30\x20\x30\x20\x33\x34\x2e\x35\x38\x7a\x22\x2f\x3e\x0d\x0a\x20\ +\x20\x3c\x2f\x67\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\ +\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x05\x7a\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x34\x36\x38\x32\x43\x38\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x36\x2e\x39\x33\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6c\x69\x6e\x65\x63\x61\x70\x3a\x73\x71\x75\x61\x72\x65\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\ +\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\ +\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\x6e\ +\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\ +\x69\x6c\x6c\x3a\x23\x46\x46\x39\x39\x33\x33\x7d\x0d\x0a\x20\x20\ +\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\x6c\x65\x3e\ +\x0d\x0a\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\x3c\x67\x20\ +\x69\x64\x3d\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\x30\x32\x30\ +\x5f\x31\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\ +\x61\x20\x69\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\x72\x70\x49\ +\x44\x5f\x30\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\x72\x22\x2f\ +\x3e\x0d\x0a\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\x61\x73\x73\ +\x3d\x22\x66\x69\x6c\x30\x22\x20\x78\x3d\x22\x30\x22\x20\x79\x3d\ +\x22\x30\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\x30\x2e\x39\ +\x32\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\x39\ +\x32\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x30\x22\x20\ +\x64\x3d\x22\x4d\x32\x34\x39\x2e\x36\x34\x20\x32\x35\x38\x2e\x31\ +\x32\x63\x2d\x34\x2e\x35\x2c\x2d\x31\x32\x38\x2e\x38\x37\x20\x2d\ +\x31\x30\x37\x2e\x39\x33\x2c\x2d\x32\x33\x32\x2e\x33\x20\x2d\x32\ +\x33\x36\x2e\x38\x2c\x2d\x32\x33\x36\x2e\x38\x22\x2f\x3e\x0d\x0a\ +\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\ +\x69\x6c\x30\x22\x20\x78\x3d\x22\x2d\x30\x22\x20\x79\x3d\x22\x2d\ +\x30\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\x30\x2e\x39\x33\ +\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\x39\x33\ +\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\ +\x69\x64\x3d\x22\x5f\x31\x5f\x30\x22\x20\x63\x6c\x61\x73\x73\x3d\ +\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x34\ +\x32\x2e\x33\x33\x2c\x2d\x30\x2e\x30\x31\x20\x34\x32\x2e\x33\x33\ +\x2c\x34\x32\x2e\x33\x32\x20\x2d\x30\x2c\x34\x32\x2e\x33\x32\x20\ +\x2d\x30\x2c\x2d\x30\x2e\x30\x31\x20\x22\x2f\x3e\x0d\x0a\x20\x20\ +\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x69\x64\x3d\x22\x5f\x31\x5f\ +\x30\x5f\x30\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\ +\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x32\x37\x30\x2e\x39\x33\ +\x2c\x32\x32\x38\x2e\x35\x39\x20\x32\x37\x30\x2e\x39\x33\x2c\x32\ +\x37\x30\x2e\x39\x32\x20\x32\x32\x38\x2e\x36\x2c\x32\x37\x30\x2e\ +\x39\x32\x20\x32\x32\x38\x2e\x36\x2c\x32\x32\x38\x2e\x35\x39\x20\ +\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\ +\x69\x64\x3d\x22\x5f\x31\x5f\x30\x5f\x31\x22\x20\x63\x6c\x61\x73\ +\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\ +\x22\x31\x39\x34\x2e\x37\x2c\x37\x36\x2e\x32\x32\x20\x31\x39\x34\ +\x2e\x37\x2c\x31\x31\x38\x2e\x35\x35\x20\x31\x35\x32\x2e\x33\x37\ +\x2c\x31\x31\x38\x2e\x35\x35\x20\x31\x35\x32\x2e\x33\x37\x2c\x37\ +\x36\x2e\x32\x32\x20\x22\x2f\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\ +\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x05\x77\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\ +\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\ +\x66\x69\x6c\x6c\x3a\x23\x34\x36\x38\x32\x43\x38\x7d\x0d\x0a\x20\ +\x20\x20\x20\x2e\x66\x69\x6c\x32\x20\x7b\x66\x69\x6c\x6c\x3a\x23\ +\x46\x46\x39\x39\x33\x33\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\ +\x0a\x20\x20\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\ +\x64\x65\x66\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\x22\x43\ +\x61\x6d\x61\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\x3e\x0d\ +\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\x69\x64\x3d\ +\x22\x43\x6f\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\x43\x6f\ +\x72\x65\x6c\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\x20\x20\ +\x3c\x72\x65\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\ +\x30\x22\x20\x78\x3d\x22\x2d\x30\x2e\x30\x31\x22\x20\x79\x3d\x22\ +\x2d\x30\x2e\x30\x31\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\ +\x30\x2e\x39\x35\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\ +\x30\x2e\x39\x35\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x67\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\ +\x66\x69\x6c\x31\x22\x20\x64\x3d\x22\x4d\x31\x33\x35\x2e\x34\x36\ +\x20\x31\x31\x2e\x32\x38\x6c\x36\x33\x2e\x30\x37\x20\x34\x35\x2e\ +\x38\x33\x20\x36\x33\x2e\x30\x38\x20\x34\x35\x2e\x38\x32\x20\x2d\ +\x32\x34\x2e\x30\x39\x20\x37\x34\x2e\x31\x35\x20\x2d\x32\x34\x2e\ +\x31\x20\x37\x34\x2e\x31\x35\x20\x2d\x37\x37\x2e\x39\x36\x20\x30\ +\x20\x2d\x37\x37\x2e\x39\x36\x20\x30\x20\x2d\x32\x34\x2e\x31\x20\ +\x2d\x37\x34\x2e\x31\x35\x20\x2d\x32\x34\x2e\x30\x39\x20\x2d\x37\ +\x34\x2e\x31\x35\x20\x36\x33\x2e\x30\x38\x20\x2d\x34\x35\x2e\x38\ +\x32\x20\x36\x33\x2e\x30\x37\x20\x2d\x34\x35\x2e\x38\x33\x7a\x6d\ +\x35\x33\x2e\x31\x35\x20\x35\x39\x2e\x35\x32\x6c\x2d\x35\x33\x2e\ +\x31\x35\x20\x2d\x33\x38\x2e\x36\x32\x20\x2d\x35\x33\x2e\x31\x35\ +\x20\x33\x38\x2e\x36\x32\x20\x2d\x35\x33\x2e\x31\x33\x20\x33\x38\ +\x2e\x36\x20\x32\x30\x2e\x32\x39\x20\x36\x32\x2e\x34\x36\x20\x32\ +\x30\x2e\x32\x39\x20\x36\x32\x2e\x34\x34\x20\x36\x35\x2e\x37\x20\ +\x30\x20\x36\x35\x2e\x37\x20\x30\x20\x32\x30\x2e\x32\x39\x20\x2d\ +\x36\x32\x2e\x34\x34\x20\x32\x30\x2e\x32\x39\x20\x2d\x36\x32\x2e\ +\x34\x36\x20\x2d\x35\x33\x2e\x31\x33\x20\x2d\x33\x38\x2e\x36\x7a\ +\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\x20\x20\x3c\ +\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\ +\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\x20\x70\ +\x6f\x69\x6e\x74\x73\x3d\x22\x31\x31\x34\x2e\x33\x2c\x31\x32\x32\ +\x2e\x37\x35\x20\x31\x35\x36\x2e\x36\x33\x2c\x31\x32\x32\x2e\x37\ +\x35\x20\x31\x35\x36\x2e\x36\x33\x2c\x31\x36\x35\x2e\x30\x38\x20\ +\x31\x31\x34\x2e\x33\x2c\x31\x36\x35\x2e\x30\x38\x20\x22\x2f\x3e\ +\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x69\x64\x3d\ +\x22\x5f\x31\x5f\x30\x5f\x30\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\ +\x66\x69\x6c\x32\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x34\x32\ +\x2e\x33\x39\x2c\x32\x32\x30\x2e\x30\x34\x20\x38\x34\x2e\x37\x32\ +\x2c\x32\x32\x30\x2e\x30\x34\x20\x38\x34\x2e\x37\x32\x2c\x32\x36\ +\x32\x2e\x33\x37\x20\x34\x32\x2e\x33\x39\x2c\x32\x36\x32\x2e\x33\ +\x37\x20\x22\x2f\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\ +\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x05\xb5\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x31\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x34\x36\x38\x32\x43\x38\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x36\x2e\x39\x33\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\ +\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x73\x74\x72\x30\ +\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\x23\x42\x33\x42\x33\x42\x33\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x31\x37\ +\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\ +\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\ +\x20\x20\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\ +\x6e\x6f\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x31\ +\x20\x7b\x66\x69\x6c\x6c\x3a\x23\x46\x46\x39\x39\x33\x33\x7d\x0d\ +\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\ +\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\ +\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\ +\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\ +\x64\x61\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\ +\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\ +\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\x78\x3d\x22\x2d\x30\ +\x22\x20\x79\x3d\x22\x2d\x30\x22\x20\x77\x69\x64\x74\x68\x3d\x22\ +\x32\x37\x30\x2e\x39\x33\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\ +\x32\x37\x30\x2e\x39\x33\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x61\ +\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\ +\x74\x72\x30\x22\x20\x64\x3d\x22\x4d\x31\x33\x35\x2e\x34\x36\x20\ +\x31\x36\x30\x2e\x38\x36\x6c\x30\x20\x2d\x35\x30\x2e\x37\x39\x6d\ +\x32\x35\x2e\x34\x20\x32\x35\x2e\x33\x39\x6c\x2d\x35\x30\x2e\x37\ +\x39\x20\x30\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x65\x6c\x6c\x69\x70\ +\x73\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\ +\x74\x72\x31\x22\x20\x63\x78\x3d\x22\x31\x33\x35\x2e\x34\x36\x22\ +\x20\x63\x79\x3d\x22\x31\x33\x35\x2e\x34\x36\x22\x20\x72\x78\x3d\ +\x22\x31\x32\x37\x22\x20\x72\x79\x3d\x22\x39\x33\x2e\x31\x33\x22\ +\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x69\ +\x64\x3d\x22\x5f\x31\x5f\x30\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\ +\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x31\x31\ +\x34\x2e\x32\x39\x2c\x32\x31\x2e\x32\x33\x20\x31\x35\x36\x2e\x36\ +\x32\x2c\x32\x31\x2e\x32\x33\x20\x31\x35\x36\x2e\x36\x32\x2c\x36\ +\x33\x2e\x35\x36\x20\x31\x31\x34\x2e\x32\x39\x2c\x36\x33\x2e\x35\ +\x36\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\ +\x6e\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\x5f\x30\x22\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\x74\ +\x73\x3d\x22\x31\x39\x38\x2e\x38\x39\x2c\x31\x31\x34\x2e\x32\x39\ +\x20\x32\x34\x31\x2e\x32\x32\x2c\x31\x31\x34\x2e\x32\x39\x20\x32\ +\x34\x31\x2e\x32\x32\x2c\x31\x35\x36\x2e\x36\x32\x20\x31\x39\x38\ +\x2e\x38\x39\x2c\x31\x35\x36\x2e\x36\x32\x20\x22\x2f\x3e\x0d\x0a\ +\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x69\x64\x3d\x22\x5f\ +\x31\x5f\x30\x5f\x31\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\ +\x6c\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x32\x35\x2e\x34\ +\x36\x2c\x31\x31\x34\x2e\x32\x39\x20\x36\x37\x2e\x37\x39\x2c\x31\ +\x31\x34\x2e\x32\x39\x20\x36\x37\x2e\x37\x39\x2c\x31\x35\x36\x2e\ +\x36\x32\x20\x32\x35\x2e\x34\x36\x2c\x31\x35\x36\x2e\x36\x32\x20\ +\x22\x2f\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\ +\x67\x3e\x0d\x0a\ +\x00\x00\x05\x5c\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x36\x36\x36\x36\x36\x36\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x37\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3a\x72\x6f\x75\x6e\x64\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\ +\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\ +\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\x6e\ +\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x32\x20\x7b\x66\ +\x69\x6c\x6c\x3a\x23\x30\x30\x39\x39\x33\x33\x7d\x0d\x0a\x20\x20\ +\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x77\x68\ +\x69\x74\x65\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\ +\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\ +\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\ +\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\ +\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\ +\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\ +\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\x65\ +\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\ +\x78\x3d\x22\x2d\x30\x2e\x30\x31\x22\x20\x79\x3d\x22\x2d\x30\x22\ +\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x20\ +\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x2f\ +\x3e\x0d\x0a\x20\x20\x3c\x6c\x69\x6e\x65\x20\x63\x6c\x61\x73\x73\ +\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x30\x22\x20\x78\x31\x3d\ +\x22\x32\x31\x31\x2e\x36\x37\x22\x20\x79\x31\x3d\x22\x32\x36\x32\ +\x2e\x34\x35\x22\x20\x78\x32\x3d\x22\x38\x2e\x34\x36\x22\x20\x79\ +\x32\x3d\x20\x22\x35\x39\x2e\x32\x35\x22\x20\x2f\x3e\x0d\x0a\x20\ +\x20\x3c\x6c\x69\x6e\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\ +\x6c\x30\x20\x73\x74\x72\x30\x22\x20\x78\x31\x3d\x22\x32\x36\x32\ +\x2e\x34\x36\x22\x20\x79\x31\x3d\x22\x32\x31\x31\x2e\x36\x36\x22\ +\x20\x78\x32\x3d\x22\x35\x39\x2e\x32\x35\x22\x20\x79\x32\x3d\x20\ +\x22\x38\x2e\x34\x36\x22\x20\x2f\x3e\x0d\x0a\x20\x20\x3c\x67\x3e\ +\x0d\x0a\x20\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\x74\ +\x73\x3d\x22\x31\x36\x30\x2e\x38\x35\x2c\x31\x36\x39\x2e\x33\x20\ +\x32\x32\x30\x2e\x31\x32\x2c\x31\x36\x39\x2e\x33\x20\x32\x32\x30\ +\x2e\x31\x32\x2c\x31\x31\x30\x2e\x30\x32\x20\x31\x36\x30\x2e\x38\ +\x35\x2c\x31\x31\x30\x2e\x30\x32\x20\x22\x2f\x3e\x0d\x0a\x20\x20\ +\x20\x3c\x70\x61\x74\x68\x20\x69\x64\x3d\x22\x5f\x31\x22\x20\x63\ +\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\x20\x64\x3d\x22\x4d\ +\x31\x37\x33\x2e\x32\x20\x31\x35\x36\x2e\x39\x35\x6c\x33\x34\x2e\ +\x35\x37\x20\x30\x20\x30\x20\x2d\x33\x34\x2e\x35\x38\x20\x2d\x33\ +\x34\x2e\x35\x37\x20\x30\x20\x30\x20\x33\x34\x2e\x35\x38\x7a\x6d\ +\x2d\x31\x32\x2e\x33\x35\x20\x31\x32\x2e\x33\x35\x6c\x35\x39\x2e\ +\x32\x37\x20\x30\x20\x30\x20\x2d\x35\x39\x2e\x32\x38\x20\x2d\x35\ +\x39\x2e\x32\x37\x20\x30\x20\x30\x20\x35\x39\x2e\x32\x38\x7a\x22\ +\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\x20\x3c\x2f\x67\ +\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x09\xe7\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x36\x36\x36\x36\x36\x36\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x37\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6c\x69\x6e\x65\x63\x61\x70\x3a\x72\x6f\x75\x6e\x64\x3b\ +\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\ +\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\ +\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\x6e\x65\ +\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x32\x20\x7b\x66\x69\ +\x6c\x6c\x3a\x23\x42\x33\x42\x33\x42\x33\x7d\x0d\x0a\x20\x20\x20\ +\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x77\x68\x69\ +\x74\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x34\x20\x7b\ +\x66\x69\x6c\x6c\x3a\x75\x72\x6c\x28\x23\x69\x64\x30\x29\x7d\x0d\ +\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x33\x20\x7b\x66\x69\x6c\x6c\ +\x3a\x75\x72\x6c\x28\x23\x69\x64\x31\x29\x7d\x0d\x0a\x20\x20\x20\ +\x20\x2e\x66\x69\x6c\x35\x20\x7b\x66\x69\x6c\x6c\x3a\x75\x72\x6c\ +\x28\x23\x69\x64\x32\x29\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\ +\x0a\x20\x20\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\x20\x3c\ +\x6c\x69\x6e\x65\x61\x72\x47\x72\x61\x64\x69\x65\x6e\x74\x20\x69\ +\x64\x3d\x22\x69\x64\x30\x22\x20\x67\x72\x61\x64\x69\x65\x6e\x74\ +\x55\x6e\x69\x74\x73\x3d\x22\x75\x73\x65\x72\x53\x70\x61\x63\x65\ +\x4f\x6e\x55\x73\x65\x22\x20\x78\x31\x3d\x22\x32\x31\x37\x2e\x37\ +\x34\x22\x20\x79\x31\x3d\x22\x31\x37\x2e\x36\x33\x22\x20\x78\x32\ +\x3d\x22\x32\x35\x33\x2e\x32\x39\x22\x20\x79\x32\x3d\x22\x35\x33\ +\x2e\x31\x38\x22\x3e\x0d\x0a\x20\x20\x20\x3c\x73\x74\x6f\x70\x20\ +\x6f\x66\x66\x73\x65\x74\x3d\x22\x30\x22\x20\x73\x74\x79\x6c\x65\ +\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\ +\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x32\x34\ +\x32\x39\x32\x45\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x73\x74\x6f\ +\x70\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\x30\x2e\x33\x32\x31\x35\ +\x36\x39\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x74\x6f\x70\x2d\ +\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\x20\x73\x74\x6f\x70\x2d\ +\x63\x6f\x6c\x6f\x72\x3a\x23\x42\x30\x42\x46\x42\x46\x22\x2f\x3e\ +\x0d\x0a\x20\x20\x20\x3c\x73\x74\x6f\x70\x20\x6f\x66\x66\x73\x65\ +\x74\x3d\x22\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x74\x6f\ +\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\x20\x73\x74\x6f\ +\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x32\x34\x32\x39\x32\x45\x22\ +\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x6c\x69\x6e\x65\x61\x72\x47\x72\ +\x61\x64\x69\x65\x6e\x74\x3e\x0d\x0a\x20\x20\x3c\x6c\x69\x6e\x65\ +\x61\x72\x47\x72\x61\x64\x69\x65\x6e\x74\x20\x69\x64\x3d\x22\x69\ +\x64\x31\x22\x20\x67\x72\x61\x64\x69\x65\x6e\x74\x55\x6e\x69\x74\ +\x73\x3d\x22\x75\x73\x65\x72\x53\x70\x61\x63\x65\x4f\x6e\x55\x73\ +\x65\x22\x20\x78\x31\x3d\x22\x31\x37\x39\x2e\x32\x31\x22\x20\x79\ +\x31\x3d\x22\x35\x36\x2e\x31\x35\x22\x20\x78\x32\x3d\x22\x32\x31\ +\x34\x2e\x37\x37\x22\x20\x79\x32\x3d\x22\x39\x31\x2e\x37\x31\x22\ +\x3e\x0d\x0a\x20\x20\x20\x3c\x73\x74\x6f\x70\x20\x6f\x66\x66\x73\ +\x65\x74\x3d\x22\x30\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x74\ +\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\x20\x73\x74\ +\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x46\x41\x39\x45\x30\x44\ +\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x73\x74\x6f\x70\x20\x6f\x66\ +\x66\x73\x65\x74\x3d\x22\x30\x2e\x33\x32\x31\x35\x36\x39\x22\x20\ +\x73\x74\x79\x6c\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\ +\x69\x74\x79\x3a\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\ +\x72\x3a\x23\x46\x45\x46\x45\x46\x45\x22\x2f\x3e\x0d\x0a\x20\x20\ +\x20\x3c\x73\x74\x6f\x70\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\x31\ +\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\ +\x61\x63\x69\x74\x79\x3a\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\ +\x6c\x6f\x72\x3a\x23\x44\x39\x37\x33\x30\x30\x22\x2f\x3e\x0d\x0a\ +\x20\x20\x3c\x2f\x6c\x69\x6e\x65\x61\x72\x47\x72\x61\x64\x69\x65\ +\x6e\x74\x3e\x0d\x0a\x20\x20\x3c\x6c\x69\x6e\x65\x61\x72\x47\x72\ +\x61\x64\x69\x65\x6e\x74\x20\x69\x64\x3d\x22\x69\x64\x32\x22\x20\ +\x67\x72\x61\x64\x69\x65\x6e\x74\x55\x6e\x69\x74\x73\x3d\x22\x75\ +\x73\x65\x72\x53\x70\x61\x63\x65\x4f\x6e\x55\x73\x65\x22\x20\x78\ +\x31\x3d\x22\x31\x34\x32\x2e\x31\x37\x22\x20\x79\x31\x3d\x22\x31\ +\x30\x35\x2e\x30\x34\x22\x20\x78\x32\x3d\x22\x31\x38\x33\x2e\x36\ +\x36\x22\x20\x79\x32\x3d\x22\x31\x32\x32\x2e\x38\x32\x22\x3e\x0d\ +\x0a\x20\x20\x20\x3c\x73\x74\x6f\x70\x20\x6f\x66\x66\x73\x65\x74\ +\x3d\x22\x30\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x74\x6f\x70\ +\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\x20\x73\x74\x6f\x70\ +\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x33\x38\x32\x42\x32\x36\x22\x2f\ +\x3e\x0d\x0a\x20\x20\x20\x3c\x73\x74\x6f\x70\x20\x6f\x66\x66\x73\ +\x65\x74\x3d\x22\x30\x2e\x33\x32\x31\x35\x36\x39\x22\x20\x73\x74\ +\x79\x6c\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\ +\x79\x3a\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\ +\x23\x45\x33\x45\x33\x44\x45\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\ +\x73\x74\x6f\x70\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\x31\x22\x20\ +\x73\x74\x79\x6c\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\ +\x69\x74\x79\x3a\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\ +\x72\x3a\x23\x33\x38\x32\x42\x32\x36\x22\x2f\x3e\x0d\x0a\x20\x20\ +\x3c\x2f\x6c\x69\x6e\x65\x61\x72\x47\x72\x61\x64\x69\x65\x6e\x74\ +\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\x3c\x67\ +\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\x30\x32\ +\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\x61\ +\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\x72\x70\ +\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\x72\x22\ +\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\x61\x73\ +\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\x78\x3d\x22\x2d\x30\x2e\x30\ +\x31\x22\x20\x79\x3d\x22\x2d\x30\x22\x20\x77\x69\x64\x74\x68\x3d\ +\x22\x32\x37\x30\x2e\x39\x33\x22\x20\x68\x65\x69\x67\x68\x74\x3d\ +\x22\x32\x37\x30\x2e\x39\x33\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\ +\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\ +\x6c\x31\x20\x73\x74\x72\x30\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\ +\x22\x31\x34\x2e\x37\x36\x2c\x31\x32\x37\x2e\x35\x34\x20\x31\x34\ +\x32\x2e\x38\x39\x2c\x32\x35\x35\x2e\x36\x36\x20\x32\x33\x38\x2e\ +\x36\x38\x2c\x31\x35\x39\x2e\x38\x37\x20\x31\x31\x30\x2e\x35\x35\ +\x2c\x33\x31\x2e\x37\x35\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\ +\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\ +\x6c\x32\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x31\x31\x30\x2e\ +\x35\x36\x2c\x37\x39\x2e\x36\x31\x20\x31\x39\x30\x2e\x38\x32\x2c\ +\x31\x35\x39\x2e\x38\x38\x20\x31\x34\x32\x2e\x39\x2c\x32\x30\x37\ +\x2e\x38\x20\x36\x32\x2e\x36\x33\x2c\x31\x32\x37\x2e\x35\x34\x20\ +\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x67\x20\x69\x64\x3d\x22\x5f\x32\ +\x37\x38\x32\x36\x38\x33\x32\x33\x33\x37\x37\x36\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\ +\x73\x3d\x22\x66\x69\x6c\x33\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\ +\x22\x32\x33\x39\x2e\x39\x36\x2c\x36\x36\x2e\x35\x32\x20\x31\x38\ +\x39\x2e\x35\x38\x2c\x31\x31\x36\x2e\x38\x39\x20\x31\x35\x34\x2e\ +\x30\x33\x2c\x38\x31\x2e\x33\x34\x20\x32\x30\x34\x2e\x34\x2c\x33\ +\x30\x2e\x39\x36\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x6f\ +\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\ +\x34\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x32\x33\x31\x2e\x30\ +\x37\x2c\x34\x2e\x32\x39\x20\x32\x30\x34\x2e\x34\x2c\x33\x30\x2e\ +\x39\x36\x20\x32\x33\x39\x2e\x39\x36\x2c\x36\x36\x2e\x35\x32\x20\ +\x32\x36\x36\x2e\x36\x33\x2c\x33\x39\x2e\x38\x35\x20\x22\x2f\x3e\ +\x0d\x0a\x20\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x35\x22\x20\x70\x6f\x69\x6e\x74\ +\x73\x3d\x22\x31\x35\x34\x2e\x30\x32\x2c\x38\x31\x2e\x33\x35\x20\ +\x31\x38\x39\x2e\x35\x37\x2c\x31\x31\x36\x2e\x39\x20\x31\x34\x38\ +\x2e\x31\x2c\x31\x32\x32\x2e\x38\x32\x20\x22\x2f\x3e\x0d\x0a\x20\ +\x20\x3c\x2f\x67\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\ +\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x09\x28\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\ +\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x33\x20\x7b\ +\x66\x69\x6c\x6c\x3a\x23\x30\x30\x39\x39\x33\x33\x7d\x0d\x0a\x20\ +\x20\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x23\ +\x36\x36\x36\x36\x36\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\ +\x6c\x32\x20\x7b\x66\x69\x6c\x6c\x3a\x77\x68\x69\x74\x65\x7d\x0d\ +\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\ +\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\ +\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\ +\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\ +\x64\x61\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\ +\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\ +\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\x78\x3d\x22\x2d\x30\ +\x2e\x30\x31\x22\x20\x79\x3d\x22\x2d\x30\x22\x20\x77\x69\x64\x74\ +\x68\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x20\x68\x65\x69\x67\x68\ +\x74\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x2f\x3e\x0d\x0a\x20\x20\ +\x3c\x67\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x64\x3d\x22\x4d\x31\ +\x33\x35\x2e\x34\x36\x20\x31\x32\x2e\x36\x39\x63\x36\x37\x2e\x38\ +\x2c\x30\x20\x31\x32\x32\x2e\x37\x37\x2c\x35\x34\x2e\x39\x37\x20\ +\x31\x32\x32\x2e\x37\x37\x2c\x31\x32\x32\x2e\x37\x37\x20\x30\x2c\ +\x36\x37\x2e\x38\x20\x2d\x35\x34\x2e\x39\x37\x2c\x31\x32\x32\x2e\ +\x37\x37\x20\x2d\x31\x32\x32\x2e\x37\x37\x2c\x31\x32\x32\x2e\x37\ +\x37\x20\x2d\x36\x37\x2e\x38\x2c\x30\x20\x2d\x31\x32\x32\x2e\x37\ +\x37\x2c\x2d\x35\x34\x2e\x39\x37\x20\x2d\x31\x32\x32\x2e\x37\x37\ +\x2c\x2d\x31\x32\x32\x2e\x37\x37\x20\x30\x2c\x2d\x36\x37\x2e\x38\ +\x20\x35\x34\x2e\x39\x37\x2c\x2d\x31\x32\x32\x2e\x37\x37\x20\x31\ +\x32\x32\x2e\x37\x37\x2c\x2d\x31\x32\x32\x2e\x37\x37\x7a\x6d\x37\ +\x34\x2e\x38\x34\x20\x34\x37\x2e\x39\x33\x63\x2d\x31\x39\x2e\x31\ +\x34\x2c\x2d\x31\x39\x2e\x31\x34\x20\x2d\x34\x35\x2e\x36\x32\x2c\ +\x2d\x33\x31\x20\x2d\x37\x34\x2e\x38\x34\x2c\x2d\x33\x31\x20\x2d\ +\x32\x39\x2e\x32\x32\x2c\x30\x20\x2d\x35\x35\x2e\x37\x2c\x31\x31\ +\x2e\x38\x36\x20\x2d\x37\x34\x2e\x38\x34\x2c\x33\x31\x20\x2d\x31\ +\x39\x2e\x31\x34\x2c\x31\x39\x2e\x31\x34\x20\x2d\x33\x31\x2c\x34\ +\x35\x2e\x36\x32\x20\x2d\x33\x31\x2c\x37\x34\x2e\x38\x34\x20\x30\ +\x2c\x32\x39\x2e\x32\x32\x20\x31\x31\x2e\x38\x36\x2c\x35\x35\x2e\ +\x37\x20\x33\x31\x2c\x37\x34\x2e\x38\x34\x20\x31\x39\x2e\x31\x34\ +\x2c\x31\x39\x2e\x31\x34\x20\x34\x35\x2e\x36\x32\x2c\x33\x31\x20\ +\x37\x34\x2e\x38\x34\x2c\x33\x31\x20\x32\x39\x2e\x32\x32\x2c\x30\ +\x20\x35\x35\x2e\x37\x2c\x2d\x31\x31\x2e\x38\x36\x20\x37\x34\x2e\ +\x38\x34\x2c\x2d\x33\x31\x20\x31\x39\x2e\x31\x34\x2c\x2d\x31\x39\ +\x2e\x31\x34\x20\x33\x31\x2c\x2d\x34\x35\x2e\x36\x32\x20\x33\x31\ +\x2c\x2d\x37\x34\x2e\x38\x34\x20\x30\x2c\x2d\x32\x39\x2e\x32\x32\ +\x20\x2d\x31\x31\x2e\x38\x36\x2c\x2d\x35\x35\x2e\x37\x20\x2d\x33\ +\x31\x2c\x2d\x37\x34\x2e\x38\x34\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\ +\x3c\x2f\x67\x3e\x0d\x0a\x20\x20\x3c\x67\x3e\x0d\x0a\x20\x20\x20\ +\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\ +\x66\x69\x6c\x32\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x31\x36\ +\x35\x2e\x31\x2c\x32\x37\x30\x2e\x38\x35\x20\x31\x30\x35\x2e\x38\ +\x32\x2c\x32\x37\x30\x2e\x38\x35\x20\x31\x30\x35\x2e\x38\x32\x2c\ +\x32\x31\x31\x2e\x35\x38\x20\x31\x36\x35\x2e\x31\x2c\x32\x31\x31\ +\x2e\x35\x38\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x61\x74\ +\x68\x20\x69\x64\x3d\x22\x5f\x31\x22\x20\x63\x6c\x61\x73\x73\x3d\ +\x22\x66\x69\x6c\x33\x22\x20\x64\x3d\x22\x4d\x31\x36\x35\x2e\x31\ +\x20\x32\x37\x30\x2e\x38\x35\x6c\x2d\x35\x39\x2e\x32\x38\x20\x30\ +\x20\x30\x20\x2d\x35\x39\x2e\x32\x37\x20\x35\x39\x2e\x32\x38\x20\ +\x30\x20\x30\x20\x35\x39\x2e\x32\x37\x7a\x6d\x2d\x34\x36\x2e\x39\ +\x33\x20\x2d\x31\x32\x2e\x33\x35\x6c\x33\x34\x2e\x35\x38\x20\x30\ +\x20\x30\x20\x2d\x33\x34\x2e\x35\x37\x20\x2d\x33\x34\x2e\x35\x38\ +\x20\x30\x20\x30\x20\x33\x34\x2e\x35\x37\x7a\x22\x2f\x3e\x0d\x0a\ +\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\x20\x20\x3c\x67\x3e\x0d\x0a\x20\ +\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\ +\x3d\x22\x66\x69\x6c\x32\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\ +\x32\x37\x30\x2e\x38\x35\x2c\x31\x36\x35\x2e\x31\x20\x32\x31\x31\ +\x2e\x35\x38\x2c\x31\x36\x35\x2e\x31\x20\x32\x31\x31\x2e\x35\x38\ +\x2c\x31\x30\x35\x2e\x38\x32\x20\x32\x37\x30\x2e\x38\x35\x2c\x31\ +\x30\x35\x2e\x38\x32\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x70\ +\x61\x74\x68\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\x22\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x33\x22\x20\x64\x3d\x22\x4d\x32\ +\x37\x30\x2e\x38\x35\x20\x31\x36\x35\x2e\x31\x6c\x2d\x35\x39\x2e\ +\x32\x37\x20\x30\x20\x30\x20\x2d\x35\x39\x2e\x32\x38\x20\x35\x39\ +\x2e\x32\x37\x20\x30\x20\x30\x20\x35\x39\x2e\x32\x38\x7a\x6d\x2d\ +\x34\x36\x2e\x39\x32\x20\x2d\x31\x32\x2e\x33\x35\x6c\x33\x34\x2e\ +\x35\x37\x20\x30\x20\x30\x20\x2d\x33\x34\x2e\x35\x38\x20\x2d\x33\ +\x34\x2e\x35\x37\x20\x30\x20\x30\x20\x33\x34\x2e\x35\x38\x7a\x22\ +\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\x20\x20\x3c\x67\ +\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\ +\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\x20\x70\x6f\x69\x6e\ +\x74\x73\x3d\x22\x31\x36\x35\x2e\x31\x2c\x35\x35\x2e\x31\x31\x20\ +\x31\x30\x35\x2e\x38\x32\x2c\x35\x35\x2e\x31\x31\x20\x31\x30\x35\ +\x2e\x38\x32\x2c\x2d\x34\x2e\x31\x36\x20\x31\x36\x35\x2e\x31\x2c\ +\x2d\x34\x2e\x31\x36\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x70\ +\x61\x74\x68\x20\x69\x64\x3d\x22\x5f\x31\x5f\x31\x22\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x33\x22\x20\x64\x3d\x22\x4d\x31\ +\x36\x35\x2e\x31\x20\x35\x35\x2e\x31\x31\x6c\x2d\x35\x39\x2e\x32\ +\x38\x20\x30\x20\x30\x20\x2d\x35\x39\x2e\x32\x37\x20\x35\x39\x2e\ +\x32\x38\x20\x30\x20\x30\x20\x35\x39\x2e\x32\x37\x7a\x6d\x2d\x34\ +\x36\x2e\x39\x33\x20\x2d\x31\x32\x2e\x33\x35\x6c\x33\x34\x2e\x35\ +\x38\x20\x30\x20\x30\x20\x2d\x33\x34\x2e\x35\x37\x20\x2d\x33\x34\ +\x2e\x35\x38\x20\x30\x20\x30\x20\x33\x34\x2e\x35\x37\x7a\x22\x2f\ +\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\x20\x20\x3c\x67\x3e\ +\x0d\x0a\x20\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\x20\x70\x6f\x69\x6e\x74\ +\x73\x3d\x22\x35\x39\x2e\x33\x34\x2c\x31\x36\x35\x2e\x31\x20\x30\ +\x2e\x30\x37\x2c\x31\x36\x35\x2e\x31\x20\x30\x2e\x30\x37\x2c\x31\ +\x30\x35\x2e\x38\x32\x20\x35\x39\x2e\x33\x34\x2c\x31\x30\x35\x2e\ +\x38\x32\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x61\x74\x68\ +\x20\x69\x64\x3d\x22\x5f\x31\x5f\x32\x22\x20\x63\x6c\x61\x73\x73\ +\x3d\x22\x66\x69\x6c\x33\x22\x20\x64\x3d\x22\x4d\x35\x39\x2e\x33\ +\x34\x20\x31\x36\x35\x2e\x31\x6c\x2d\x35\x39\x2e\x32\x37\x20\x30\ +\x20\x30\x20\x2d\x35\x39\x2e\x32\x38\x20\x35\x39\x2e\x32\x37\x20\ +\x30\x20\x30\x20\x35\x39\x2e\x32\x38\x7a\x6d\x2d\x34\x36\x2e\x39\ +\x32\x20\x2d\x31\x32\x2e\x33\x35\x6c\x33\x34\x2e\x35\x37\x20\x30\ +\x20\x30\x20\x2d\x33\x34\x2e\x35\x38\x20\x2d\x33\x34\x2e\x35\x37\ +\x20\x30\x20\x30\x20\x33\x34\x2e\x35\x38\x7a\x22\x2f\x3e\x0d\x0a\ +\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\ +\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x05\x11\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x34\x36\x38\x32\x43\x38\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x37\x2e\x30\x36\x3b\x73\x74\x72\x6f\x6b\x65\ +\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\ +\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x30\x20\ +\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\x6e\x65\x7d\x0d\x0a\x20\x20\x20\ +\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x23\x43\x43\ +\x43\x43\x43\x43\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\ +\x20\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\ +\x61\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\ +\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\x69\x64\x3d\x22\x43\ +\x6f\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\ +\x6c\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\ +\x65\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\ +\x20\x78\x3d\x22\x2d\x30\x2e\x30\x32\x22\x20\x79\x3d\x22\x2d\x30\ +\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\ +\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\ +\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\x61\x73\ +\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\x78\x3d\x22\x36\x37\x2e\x37\ +\x32\x22\x20\x79\x3d\x22\x36\x37\x2e\x37\x33\x22\x20\x77\x69\x64\ +\x74\x68\x3d\x22\x31\x33\x35\x2e\x34\x37\x22\x20\x68\x65\x69\x67\ +\x68\x74\x3d\x22\x31\x33\x35\x2e\x34\x37\x22\x2f\x3e\x0d\x0a\x20\ +\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\ +\x6c\x31\x20\x73\x74\x72\x30\x22\x20\x64\x3d\x22\x4d\x31\x34\x33\ +\x2e\x39\x33\x20\x39\x37\x2e\x33\x36\x63\x32\x38\x2e\x30\x36\x2c\ +\x30\x20\x35\x30\x2e\x38\x2c\x32\x32\x2e\x37\x34\x20\x35\x30\x2e\ +\x38\x2c\x35\x30\x2e\x38\x20\x30\x2c\x32\x38\x2e\x30\x36\x20\x2d\ +\x32\x32\x2e\x37\x34\x2c\x35\x30\x2e\x38\x20\x2d\x35\x30\x2e\x38\ +\x2c\x35\x30\x2e\x38\x6c\x2d\x36\x37\x2e\x37\x32\x20\x30\x20\x30\ +\x20\x34\x32\x2e\x33\x33\x20\x36\x37\x2e\x37\x32\x20\x30\x63\x35\ +\x31\x2e\x34\x33\x2c\x30\x20\x39\x33\x2e\x31\x33\x2c\x2d\x34\x31\ +\x2e\x37\x20\x39\x33\x2e\x31\x33\x2c\x2d\x39\x33\x2e\x31\x33\x20\ +\x30\x2c\x2d\x35\x31\x2e\x34\x33\x20\x2d\x34\x31\x2e\x37\x2c\x2d\ +\x39\x33\x2e\x31\x33\x20\x2d\x39\x33\x2e\x31\x33\x2c\x2d\x39\x33\ +\x2e\x31\x33\x20\x2d\x30\x2e\x30\x34\x2c\x30\x20\x2d\x30\x2e\x30\ +\x38\x2c\x30\x20\x2d\x30\x2e\x30\x33\x2c\x30\x6c\x2d\x32\x35\x2e\ +\x33\x37\x20\x30\x20\x38\x2e\x34\x36\x20\x2d\x32\x35\x2e\x34\x20\ +\x2d\x39\x33\x2e\x31\x33\x20\x34\x36\x2e\x35\x36\x20\x39\x33\x2e\ +\x31\x33\x20\x34\x36\x2e\x35\x37\x20\x2d\x38\x2e\x34\x36\x20\x2d\ +\x32\x35\x2e\x34\x20\x32\x35\x2e\x34\x20\x30\x7a\x22\x2f\x3e\x0d\ +\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\x0a\ +\ +\x00\x00\x08\x50\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x34\x36\x38\x32\x43\x38\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x38\x2e\x38\x32\x3b\x73\x74\x72\x6f\x6b\x65\ +\x2d\x6c\x69\x6e\x65\x63\x61\x70\x3a\x72\x6f\x75\x6e\x64\x3b\x73\ +\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\ +\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\ +\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\x6e\x65\x7d\ +\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\x69\x6c\ +\x6c\x3a\x77\x68\x69\x74\x65\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\x3e\ +\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\x3c\ +\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\x22\ +\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\x3e\ +\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\x69\x64\ +\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\x43\ +\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\x20\ +\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\ +\x6c\x30\x22\x20\x78\x3d\x22\x2d\x30\x2e\x30\x31\x22\x20\x79\x3d\ +\x22\x2d\x30\x2e\x30\x32\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\ +\x37\x30\x2e\x39\x33\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\ +\x37\x30\x2e\x39\x33\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x61\x74\ +\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x20\x73\x74\ +\x72\x30\x22\x20\x64\x3d\x22\x4d\x32\x33\x31\x2e\x35\x35\x20\x31\ +\x35\x36\x2e\x35\x35\x6c\x2d\x32\x31\x20\x30\x63\x2d\x31\x2e\x37\ +\x2c\x36\x2e\x30\x32\x20\x2d\x34\x2e\x30\x38\x2c\x31\x31\x2e\x37\ +\x34\x20\x2d\x37\x2e\x30\x38\x2c\x31\x37\x2e\x30\x39\x6c\x31\x34\ +\x2e\x38\x35\x20\x31\x34\x2e\x38\x35\x63\x33\x2e\x37\x36\x2c\x33\ +\x2e\x37\x36\x20\x33\x2e\x37\x39\x2c\x39\x2e\x39\x38\x20\x30\x2c\ +\x31\x33\x2e\x37\x38\x6c\x2d\x31\x36\x2e\x30\x35\x20\x31\x36\x2e\ +\x30\x35\x63\x2d\x33\x2e\x38\x2c\x33\x2e\x37\x39\x20\x2d\x39\x2e\ +\x39\x38\x2c\x33\x2e\x37\x39\x20\x2d\x31\x33\x2e\x37\x38\x2c\x30\ +\x6c\x2d\x31\x34\x2e\x38\x35\x20\x2d\x31\x34\x2e\x38\x35\x63\x2d\ +\x35\x2e\x33\x33\x2c\x33\x20\x2d\x31\x31\x2e\x30\x37\x2c\x35\x2e\ +\x33\x38\x20\x2d\x31\x37\x2e\x30\x39\x2c\x37\x2e\x30\x38\x6c\x30\ +\x20\x32\x31\x63\x30\x2c\x35\x2e\x33\x32\x20\x2d\x34\x2e\x33\x37\ +\x2c\x39\x2e\x37\x34\x20\x2d\x39\x2e\x37\x34\x2c\x39\x2e\x37\x34\ +\x6c\x2d\x32\x32\x2e\x37\x20\x30\x63\x2d\x35\x2e\x33\x37\x2c\x30\ +\x20\x2d\x39\x2e\x37\x34\x2c\x2d\x34\x2e\x33\x38\x20\x2d\x39\x2e\ +\x37\x34\x2c\x2d\x39\x2e\x37\x34\x6c\x30\x20\x2d\x32\x31\x63\x2d\ +\x36\x2e\x30\x32\x2c\x2d\x31\x2e\x36\x38\x20\x2d\x31\x31\x2e\x37\ +\x34\x2c\x2d\x34\x2e\x30\x38\x20\x2d\x31\x37\x2e\x30\x39\x2c\x2d\ +\x37\x2e\x30\x38\x6c\x2d\x31\x34\x2e\x38\x35\x20\x31\x34\x2e\x38\ +\x35\x63\x2d\x33\x2e\x37\x36\x2c\x33\x2e\x37\x36\x20\x2d\x39\x2e\ +\x39\x38\x2c\x33\x2e\x37\x39\x20\x2d\x31\x33\x2e\x37\x38\x2c\x30\ +\x6c\x2d\x31\x36\x2e\x30\x35\x20\x2d\x31\x36\x2e\x30\x35\x63\x2d\ +\x33\x2e\x37\x39\x2c\x2d\x33\x2e\x38\x20\x2d\x33\x2e\x37\x39\x2c\ +\x2d\x39\x2e\x39\x38\x20\x30\x2c\x2d\x31\x33\x2e\x37\x38\x6c\x31\ +\x34\x2e\x38\x35\x20\x2d\x31\x34\x2e\x38\x35\x63\x2d\x33\x2c\x2d\ +\x35\x2e\x33\x35\x20\x2d\x35\x2e\x33\x38\x2c\x2d\x31\x31\x2e\x30\ +\x37\x20\x2d\x37\x2e\x30\x38\x2c\x2d\x31\x37\x2e\x30\x39\x6c\x2d\ +\x32\x31\x20\x30\x63\x2d\x35\x2e\x33\x32\x2c\x30\x20\x2d\x39\x2e\ +\x37\x34\x2c\x2d\x34\x2e\x33\x37\x20\x2d\x39\x2e\x37\x34\x2c\x2d\ +\x39\x2e\x37\x34\x6c\x30\x20\x2d\x32\x32\x2e\x37\x63\x30\x2c\x2d\ +\x35\x2e\x33\x37\x20\x34\x2e\x33\x38\x2c\x2d\x39\x2e\x37\x34\x20\ +\x39\x2e\x37\x34\x2c\x2d\x39\x2e\x37\x34\x6c\x32\x31\x20\x30\x63\ +\x31\x2e\x37\x2c\x2d\x36\x2e\x30\x32\x20\x34\x2e\x30\x38\x2c\x2d\ +\x31\x31\x2e\x37\x34\x20\x37\x2e\x30\x38\x2c\x2d\x31\x37\x2e\x30\ +\x39\x6c\x2d\x31\x34\x2e\x38\x35\x20\x2d\x31\x34\x2e\x38\x35\x63\ +\x2d\x33\x2e\x37\x36\x2c\x2d\x33\x2e\x37\x36\x20\x2d\x33\x2e\x37\ +\x39\x2c\x2d\x39\x2e\x39\x38\x20\x30\x2c\x2d\x31\x33\x2e\x37\x38\ +\x6c\x31\x36\x2e\x30\x35\x20\x2d\x31\x36\x2e\x30\x35\x63\x33\x2e\ +\x38\x2c\x2d\x33\x2e\x37\x39\x20\x39\x2e\x39\x38\x2c\x2d\x33\x2e\ +\x37\x39\x20\x31\x33\x2e\x37\x38\x2c\x30\x6c\x31\x34\x2e\x38\x35\ +\x20\x31\x34\x2e\x38\x35\x63\x35\x2e\x33\x33\x2c\x2d\x33\x20\x31\ +\x31\x2e\x30\x37\x2c\x2d\x35\x2e\x33\x38\x20\x31\x37\x2e\x30\x39\ +\x2c\x2d\x37\x2e\x30\x38\x6c\x30\x20\x2d\x32\x31\x63\x30\x2c\x2d\ +\x35\x2e\x33\x32\x20\x34\x2e\x33\x37\x2c\x2d\x39\x2e\x37\x34\x20\ +\x39\x2e\x37\x34\x2c\x2d\x39\x2e\x37\x34\x6c\x32\x32\x2e\x37\x20\ +\x30\x63\x35\x2e\x33\x37\x2c\x30\x20\x39\x2e\x37\x34\x2c\x34\x2e\ +\x33\x38\x20\x39\x2e\x37\x34\x2c\x39\x2e\x37\x34\x6c\x30\x20\x32\ +\x31\x63\x36\x2e\x30\x32\x2c\x31\x2e\x36\x38\x20\x31\x31\x2e\x37\ +\x34\x2c\x34\x2e\x30\x38\x20\x31\x37\x2e\x30\x39\x2c\x37\x2e\x30\ +\x38\x6c\x31\x34\x2e\x38\x35\x20\x2d\x31\x34\x2e\x38\x35\x63\x33\ +\x2e\x37\x36\x2c\x2d\x33\x2e\x37\x36\x20\x39\x2e\x39\x38\x2c\x2d\ +\x33\x2e\x37\x39\x20\x31\x33\x2e\x37\x38\x2c\x30\x6c\x31\x36\x2e\ +\x30\x35\x20\x31\x36\x2e\x30\x35\x63\x33\x2e\x37\x39\x2c\x33\x2e\ +\x38\x20\x33\x2e\x37\x39\x2c\x39\x2e\x39\x38\x20\x30\x2c\x31\x33\ +\x2e\x37\x38\x6c\x2d\x31\x34\x2e\x38\x35\x20\x31\x34\x2e\x38\x35\ +\x63\x33\x2c\x35\x2e\x33\x35\x20\x35\x2e\x33\x38\x2c\x31\x31\x2e\ +\x30\x37\x20\x37\x2e\x30\x38\x2c\x31\x37\x2e\x30\x39\x6c\x32\x31\ +\x20\x30\x63\x35\x2e\x33\x32\x2c\x30\x20\x39\x2e\x37\x34\x2c\x34\ +\x2e\x33\x37\x20\x39\x2e\x37\x34\x2c\x39\x2e\x37\x34\x6c\x30\x20\ +\x32\x32\x2e\x37\x63\x30\x2c\x35\x2e\x33\x37\x20\x2d\x34\x2e\x33\ +\x38\x2c\x39\x2e\x37\x34\x20\x2d\x39\x2e\x37\x34\x2c\x39\x2e\x37\ +\x34\x7a\x6d\x2d\x39\x36\x2e\x30\x39\x20\x2d\x36\x33\x2e\x34\x31\ +\x63\x32\x33\x2e\x33\x39\x2c\x30\x20\x34\x32\x2e\x33\x32\x2c\x31\ +\x38\x2e\x39\x33\x20\x34\x32\x2e\x33\x32\x2c\x34\x32\x2e\x33\x32\ +\x20\x30\x2c\x32\x33\x2e\x33\x39\x20\x2d\x31\x38\x2e\x39\x33\x2c\ +\x34\x32\x2e\x33\x32\x20\x2d\x34\x32\x2e\x33\x32\x2c\x34\x32\x2e\ +\x33\x32\x20\x2d\x32\x33\x2e\x33\x39\x2c\x30\x20\x2d\x34\x32\x2e\ +\x33\x32\x2c\x2d\x31\x38\x2e\x39\x33\x20\x2d\x34\x32\x2e\x33\x32\ +\x2c\x2d\x34\x32\x2e\x33\x32\x20\x30\x2c\x2d\x32\x33\x2e\x33\x39\ +\x20\x31\x38\x2e\x39\x33\x2c\x2d\x34\x32\x2e\x33\x32\x20\x34\x32\ +\x2e\x33\x32\x2c\x2d\x34\x32\x2e\x33\x32\x7a\x22\x2f\x3e\x0d\x0a\ +\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x06\x09\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x34\x36\x38\x32\x43\x38\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x36\x2e\x39\x33\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\ +\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x73\x74\x72\x31\ +\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\x23\x42\x33\x42\x33\x42\x33\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x31\x36\ +\x2e\x39\x33\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\ +\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\ +\x20\x20\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\ +\x6e\x6f\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x31\ +\x20\x7b\x66\x69\x6c\x6c\x3a\x23\x46\x46\x39\x39\x33\x33\x7d\x0d\ +\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\ +\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\ +\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\ +\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\ +\x64\x61\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\ +\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\ +\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\x78\x3d\x22\x2d\x30\ +\x2e\x30\x31\x22\x20\x79\x3d\x22\x2d\x30\x22\x20\x77\x69\x64\x74\ +\x68\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x20\x68\x65\x69\x67\x68\ +\x74\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x2f\x3e\x0d\x0a\x20\x20\ +\x3c\x65\x6c\x6c\x69\x70\x73\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\ +\x66\x69\x6c\x30\x20\x73\x74\x72\x30\x22\x20\x63\x78\x3d\x22\x31\ +\x33\x35\x2e\x34\x36\x22\x20\x63\x79\x3d\x22\x31\x33\x35\x2e\x34\ +\x36\x22\x20\x72\x78\x3d\x22\x31\x32\x37\x22\x20\x72\x79\x3d\x22\ +\x39\x33\x2e\x31\x33\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x61\x74\ +\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\ +\x72\x31\x22\x20\x64\x3d\x22\x4d\x31\x33\x35\x2e\x34\x37\x20\x31\ +\x36\x30\x2e\x38\x35\x6c\x30\x20\x2d\x35\x30\x2e\x37\x39\x6d\x2d\ +\x32\x35\x2e\x34\x20\x32\x35\x2e\x33\x39\x6c\x35\x30\x2e\x37\x39\ +\x20\x30\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\ +\x6e\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\x22\x20\x63\x6c\x61\x73\ +\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\ +\x22\x35\x35\x2e\x30\x37\x2c\x32\x39\x2e\x37\x20\x39\x37\x2e\x34\ +\x2c\x32\x39\x2e\x37\x20\x39\x37\x2e\x34\x2c\x37\x32\x2e\x30\x33\ +\x20\x35\x35\x2e\x30\x37\x2c\x37\x32\x2e\x30\x33\x20\x22\x2f\x3e\ +\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x69\x64\x3d\ +\x22\x5f\x31\x5f\x30\x5f\x30\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\ +\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x35\x35\ +\x2e\x30\x37\x2c\x31\x39\x34\x2e\x36\x37\x20\x39\x37\x2e\x34\x2c\ +\x31\x39\x34\x2e\x36\x37\x20\x39\x37\x2e\x34\x2c\x32\x33\x37\x20\ +\x35\x35\x2e\x30\x37\x2c\x32\x33\x37\x20\x22\x2f\x3e\x0d\x0a\x20\ +\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x69\x64\x3d\x22\x5f\x31\ +\x5f\x30\x5f\x31\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\ +\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x31\x37\x33\x2e\x35\ +\x31\x2c\x32\x39\x2e\x37\x20\x32\x31\x35\x2e\x38\x34\x2c\x32\x39\ +\x2e\x37\x20\x32\x31\x35\x2e\x38\x34\x2c\x37\x32\x2e\x30\x33\x20\ +\x31\x37\x33\x2e\x35\x31\x2c\x37\x32\x2e\x30\x33\x20\x22\x2f\x3e\ +\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x69\x64\x3d\ +\x22\x5f\x31\x5f\x30\x5f\x32\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\ +\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x32\x32\ +\x34\x2e\x32\x37\x2c\x31\x36\x30\x2e\x38\x33\x20\x32\x36\x36\x2e\ +\x36\x2c\x31\x36\x30\x2e\x38\x33\x20\x32\x36\x36\x2e\x36\x2c\x32\ +\x30\x33\x2e\x31\x36\x20\x32\x32\x34\x2e\x32\x37\x2c\x32\x30\x33\ +\x2e\x31\x36\x20\x22\x2f\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\ +\x3c\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x07\xd0\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x31\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x46\x46\x39\x39\x33\x33\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x37\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\ +\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x73\x74\x72\x30\ +\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\x23\x34\x36\x38\x32\x43\x38\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x31\x35\ +\x2e\x38\x37\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\ +\x6f\x69\x6e\x3a\x72\x6f\x75\x6e\x64\x3b\x73\x74\x72\x6f\x6b\x65\ +\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\ +\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x73\x74\x72\x32\x20\ +\x7b\x73\x74\x72\x6f\x6b\x65\x3a\x67\x72\x61\x79\x3b\x73\x74\x72\ +\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x31\x37\x2e\x36\x34\x3b\ +\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3a\ +\x72\x6f\x75\x6e\x64\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\ +\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\ +\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\ +\x6c\x3a\x6e\x6f\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\ +\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x23\x34\x36\x38\x32\x43\x38\ +\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x32\x20\x7b\x66\x69\ +\x6c\x6c\x3a\x77\x68\x69\x74\x65\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\ +\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\ +\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\ +\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\ +\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\x69\ +\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\ +\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\ +\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\ +\x69\x6c\x30\x22\x20\x78\x3d\x22\x2d\x30\x2e\x30\x31\x22\x20\x79\ +\x3d\x22\x2d\x30\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\x30\ +\x2e\x39\x33\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\x30\ +\x2e\x39\x33\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x6c\x69\x6e\x65\x20\ +\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x30\ +\x22\x20\x78\x31\x3d\x22\x39\x39\x2e\x30\x33\x22\x20\x79\x31\x3d\ +\x22\x39\x38\x2e\x36\x22\x20\x78\x32\x3d\x22\x34\x38\x2e\x35\x31\ +\x22\x20\x79\x32\x3d\x20\x22\x34\x38\x2e\x30\x38\x22\x20\x2f\x3e\ +\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\ +\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\ +\x3d\x22\x32\x31\x2e\x31\x34\x2c\x32\x31\x2e\x31\x34\x20\x32\x38\ +\x2e\x32\x36\x2c\x34\x37\x2e\x37\x32\x20\x33\x35\x2e\x33\x39\x2c\ +\x37\x34\x2e\x33\x20\x35\x34\x2e\x38\x34\x2c\x35\x34\x2e\x38\x34\ +\x20\x37\x34\x2e\x33\x2c\x33\x35\x2e\x33\x39\x20\x34\x37\x2e\x37\ +\x32\x2c\x32\x38\x2e\x32\x36\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\ +\x67\x20\x69\x64\x3d\x22\x5f\x32\x35\x35\x38\x32\x37\x38\x32\x34\ +\x34\x36\x34\x30\x22\x3e\x0d\x0a\x20\x20\x20\x3c\x72\x65\x63\x74\ +\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\x20\x74\x72\ +\x61\x6e\x73\x66\x6f\x72\x6d\x3d\x22\x6d\x61\x74\x72\x69\x78\x28\ +\x2d\x30\x2e\x37\x38\x38\x31\x31\x34\x20\x2d\x30\x2e\x37\x38\x38\ +\x31\x31\x34\x20\x30\x2e\x38\x34\x38\x35\x39\x35\x20\x2d\x30\x2e\ +\x38\x34\x38\x35\x39\x35\x20\x31\x38\x37\x2e\x35\x31\x37\x20\x32\ +\x35\x39\x2e\x33\x35\x39\x29\x22\x20\x77\x69\x64\x74\x68\x3d\x22\ +\x31\x34\x38\x2e\x31\x32\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\ +\x38\x34\x2e\x36\x36\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x6f\ +\x6c\x79\x6c\x69\x6e\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\ +\x6c\x30\x20\x73\x74\x72\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\ +\x22\x31\x33\x36\x2e\x36\x31\x2c\x31\x39\x36\x2e\x34\x37\x20\x31\ +\x38\x37\x2e\x35\x31\x2c\x32\x34\x37\x2e\x33\x37\x20\x32\x34\x37\ +\x2e\x33\x37\x2c\x31\x38\x37\x2e\x35\x31\x20\x31\x39\x36\x2e\x34\ +\x37\x2c\x31\x33\x36\x2e\x36\x31\x20\x22\x2f\x3e\x0d\x0a\x20\x20\ +\x20\x3c\x6c\x69\x6e\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\ +\x6c\x30\x20\x73\x74\x72\x32\x22\x20\x78\x31\x3d\x22\x31\x38\x39\ +\x22\x20\x79\x31\x3d\x22\x31\x32\x39\x2e\x31\x38\x22\x20\x78\x32\ +\x3d\x22\x31\x37\x37\x2e\x30\x33\x22\x20\x79\x32\x3d\x20\x22\x31\ +\x31\x37\x2e\x32\x31\x22\x20\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x70\ +\x6f\x6c\x79\x6c\x69\x6e\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\ +\x69\x6c\x30\x20\x73\x74\x72\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\ +\x3d\x22\x31\x30\x39\x2e\x37\x34\x2c\x31\x36\x39\x2e\x36\x20\x38\ +\x32\x2e\x37\x37\x2c\x31\x34\x32\x2e\x36\x33\x20\x31\x34\x32\x2e\ +\x36\x33\x2c\x38\x32\x2e\x37\x37\x20\x31\x36\x39\x2e\x36\x2c\x31\ +\x30\x39\x2e\x37\x34\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x6c\ +\x69\x6e\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\ +\x73\x74\x72\x32\x22\x20\x78\x31\x3d\x22\x31\x36\x38\x2e\x30\x36\ +\x22\x20\x79\x31\x3d\x22\x31\x35\x30\x2e\x31\x32\x22\x20\x78\x32\ +\x3d\x22\x31\x35\x36\x2e\x30\x39\x22\x20\x79\x32\x3d\x20\x22\x31\ +\x33\x38\x2e\x31\x35\x22\x20\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x6c\ +\x69\x6e\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\ +\x73\x74\x72\x32\x22\x20\x78\x31\x3d\x22\x31\x34\x37\x2e\x31\x33\ +\x22\x20\x79\x31\x3d\x22\x31\x37\x31\x2e\x30\x36\x22\x20\x78\x32\ +\x3d\x22\x31\x33\x35\x2e\x31\x36\x22\x20\x79\x32\x3d\x20\x22\x31\ +\x35\x39\x2e\x30\x38\x22\x20\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x6c\ +\x69\x6e\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\ +\x73\x74\x72\x32\x22\x20\x78\x31\x3d\x22\x31\x32\x39\x2e\x31\x38\ +\x22\x20\x79\x31\x3d\x22\x31\x38\x39\x22\x20\x78\x32\x3d\x22\x31\ +\x31\x37\x2e\x32\x31\x22\x20\x79\x32\x3d\x20\x22\x31\x37\x37\x2e\ +\x30\x33\x22\x20\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\ +\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x05\x00\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x36\x36\x36\x36\x36\x36\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x36\x2e\x39\x33\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\ +\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x30\ +\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\x6e\x65\x7d\x0d\x0a\x20\x20\ +\x20\x20\x2e\x66\x69\x6c\x32\x20\x7b\x66\x69\x6c\x6c\x3a\x23\x30\ +\x30\x39\x39\x33\x33\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\ +\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x77\x68\x69\x74\x65\x7d\x0d\x0a\ +\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\x6c\ +\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\x3c\ +\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\x30\ +\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\ +\x61\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\x72\ +\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\x72\ +\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\x61\ +\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\x78\x3d\x22\x2d\x30\x2e\ +\x30\x31\x22\x20\x79\x3d\x22\x2d\x30\x22\x20\x77\x69\x64\x74\x68\ +\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x20\x68\x65\x69\x67\x68\x74\ +\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\ +\x70\x6f\x6c\x79\x6c\x69\x6e\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\ +\x66\x69\x6c\x30\x20\x73\x74\x72\x30\x22\x20\x70\x6f\x69\x6e\x74\ +\x73\x3d\x22\x36\x2e\x32\x37\x2c\x32\x31\x33\x2e\x38\x35\x20\x31\ +\x37\x37\x2e\x37\x39\x2c\x34\x32\x2e\x33\x33\x20\x32\x36\x33\x2e\ +\x33\x37\x2c\x31\x32\x37\x2e\x38\x39\x20\x22\x2f\x3e\x0d\x0a\x20\ +\x20\x3c\x67\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\ +\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\ +\x6f\x69\x6e\x74\x73\x3d\x22\x31\x30\x35\x2e\x38\x37\x2c\x31\x37\ +\x33\x2e\x35\x36\x20\x34\x36\x2e\x36\x2c\x31\x37\x33\x2e\x35\x36\ +\x20\x34\x36\x2e\x36\x2c\x31\x31\x34\x2e\x32\x38\x20\x31\x30\x35\ +\x2e\x38\x37\x2c\x31\x31\x34\x2e\x32\x38\x20\x22\x2f\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x70\x61\x74\x68\x20\x69\x64\x3d\x22\x5f\x31\x22\ +\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\x20\x64\x3d\ +\x22\x4d\x31\x30\x35\x2e\x38\x37\x20\x31\x37\x33\x2e\x35\x36\x6c\ +\x2d\x35\x39\x2e\x32\x37\x20\x30\x20\x30\x20\x2d\x35\x39\x2e\x32\ +\x38\x20\x35\x39\x2e\x32\x37\x20\x30\x20\x30\x20\x35\x39\x2e\x32\ +\x38\x7a\x6d\x2d\x34\x36\x2e\x39\x32\x20\x2d\x31\x32\x2e\x33\x35\ +\x6c\x33\x34\x2e\x35\x37\x20\x30\x20\x30\x20\x2d\x33\x34\x2e\x35\ +\x38\x20\x2d\x33\x34\x2e\x35\x37\x20\x30\x20\x30\x20\x33\x34\x2e\ +\x35\x38\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\ +\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x06\xe7\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\ +\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x33\x20\x7b\ +\x66\x69\x6c\x6c\x3a\x23\x30\x30\x39\x39\x33\x33\x7d\x0d\x0a\x20\ +\x20\x20\x20\x2e\x66\x69\x6c\x32\x20\x7b\x66\x69\x6c\x6c\x3a\x67\ +\x72\x61\x79\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x31\x20\ +\x7b\x66\x69\x6c\x6c\x3a\x77\x68\x69\x74\x65\x7d\x0d\x0a\x20\x20\ +\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\x6c\x65\x3e\ +\x0d\x0a\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\x3c\x67\x20\ +\x69\x64\x3d\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\x30\x32\x30\ +\x5f\x31\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\ +\x61\x20\x69\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\x72\x70\x49\ +\x44\x5f\x30\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\x72\x22\x2f\ +\x3e\x0d\x0a\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\x61\x73\x73\ +\x3d\x22\x66\x69\x6c\x30\x22\x20\x78\x3d\x22\x2d\x30\x2e\x30\x31\ +\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\ +\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\x39\x32\x22\ +\x2f\x3e\x0d\x0a\x20\x20\x3c\x67\x3e\x0d\x0a\x20\x20\x20\x3c\x70\ +\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\ +\x20\x64\x3d\x22\x4d\x34\x36\x2e\x39\x35\x20\x32\x39\x2e\x36\x32\ +\x6c\x31\x33\x34\x2e\x37\x31\x20\x30\x63\x32\x31\x2e\x31\x37\x2c\ +\x30\x20\x33\x38\x2e\x34\x38\x2c\x31\x37\x2e\x33\x32\x20\x33\x38\ +\x2e\x34\x38\x2c\x33\x38\x2e\x34\x38\x6c\x30\x20\x31\x33\x34\x2e\ +\x37\x31\x63\x30\x2c\x32\x31\x2e\x31\x37\x20\x2d\x31\x37\x2e\x33\ +\x31\x2c\x33\x38\x2e\x34\x39\x20\x2d\x33\x38\x2e\x34\x38\x2c\x33\ +\x38\x2e\x34\x39\x6c\x2d\x31\x33\x34\x2e\x37\x31\x20\x30\x63\x2d\ +\x32\x31\x2e\x31\x37\x2c\x30\x20\x2d\x33\x38\x2e\x34\x38\x2c\x2d\ +\x31\x37\x2e\x33\x32\x20\x2d\x33\x38\x2e\x34\x38\x2c\x2d\x33\x38\ +\x2e\x34\x39\x6c\x30\x20\x2d\x31\x33\x34\x2e\x37\x31\x63\x30\x2c\ +\x2d\x32\x31\x2e\x31\x36\x20\x31\x37\x2e\x33\x31\x2c\x2d\x33\x38\ +\x2e\x34\x38\x20\x33\x38\x2e\x34\x38\x2c\x2d\x33\x38\x2e\x34\x38\ +\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x61\x74\x68\x20\x69\ +\x64\x3d\x22\x5f\x31\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\ +\x6c\x32\x22\x20\x64\x3d\x22\x4d\x34\x36\x2e\x39\x35\x20\x32\x39\ +\x2e\x36\x32\x6c\x31\x33\x34\x2e\x37\x31\x20\x30\x63\x32\x31\x2e\ +\x31\x37\x2c\x30\x20\x33\x38\x2e\x34\x38\x2c\x31\x37\x2e\x33\x32\ +\x20\x33\x38\x2e\x34\x38\x2c\x33\x38\x2e\x34\x38\x6c\x30\x20\x31\ +\x33\x34\x2e\x37\x31\x63\x30\x2c\x32\x31\x2e\x31\x37\x20\x2d\x31\ +\x37\x2e\x33\x31\x2c\x33\x38\x2e\x34\x39\x20\x2d\x33\x38\x2e\x34\ +\x38\x2c\x33\x38\x2e\x34\x39\x6c\x2d\x31\x33\x34\x2e\x37\x31\x20\ +\x30\x63\x2d\x32\x31\x2e\x31\x37\x2c\x30\x20\x2d\x33\x38\x2e\x34\ +\x38\x2c\x2d\x31\x37\x2e\x33\x32\x20\x2d\x33\x38\x2e\x34\x38\x2c\ +\x2d\x33\x38\x2e\x34\x39\x6c\x30\x20\x2d\x31\x33\x34\x2e\x37\x31\ +\x63\x30\x2c\x2d\x32\x31\x2e\x31\x36\x20\x31\x37\x2e\x33\x31\x2c\ +\x2d\x33\x38\x2e\x34\x38\x20\x33\x38\x2e\x34\x38\x2c\x2d\x33\x38\ +\x2e\x34\x38\x7a\x6d\x31\x33\x34\x2e\x37\x31\x20\x31\x37\x2e\x36\ +\x34\x6c\x2d\x31\x33\x34\x2e\x37\x31\x20\x30\x63\x2d\x35\x2e\x37\ +\x34\x2c\x30\x20\x2d\x31\x30\x2e\x39\x37\x2c\x32\x2e\x33\x34\x20\ +\x2d\x31\x34\x2e\x37\x34\x2c\x36\x2e\x31\x20\x2d\x33\x2e\x37\x36\ +\x2c\x33\x2e\x37\x37\x20\x2d\x36\x2e\x31\x2c\x39\x20\x2d\x36\x2e\ +\x31\x2c\x31\x34\x2e\x37\x34\x6c\x30\x20\x31\x33\x34\x2e\x37\x31\ +\x63\x30\x2c\x35\x2e\x37\x35\x20\x32\x2e\x33\x34\x2c\x31\x30\x2e\ +\x39\x38\x20\x36\x2e\x31\x2c\x31\x34\x2e\x37\x35\x20\x33\x2e\x37\ +\x37\x2c\x33\x2e\x37\x36\x20\x39\x2c\x36\x2e\x31\x20\x31\x34\x2e\ +\x37\x34\x2c\x36\x2e\x31\x6c\x31\x33\x34\x2e\x37\x31\x20\x30\x63\ +\x35\x2e\x37\x34\x2c\x30\x20\x31\x30\x2e\x39\x37\x2c\x2d\x32\x2e\ +\x33\x34\x20\x31\x34\x2e\x37\x34\x2c\x2d\x36\x2e\x31\x20\x33\x2e\ +\x37\x36\x2c\x2d\x33\x2e\x37\x37\x20\x36\x2e\x31\x2c\x2d\x39\x20\ +\x36\x2e\x31\x2c\x2d\x31\x34\x2e\x37\x35\x6c\x30\x20\x2d\x31\x33\ +\x34\x2e\x37\x31\x63\x30\x2c\x2d\x35\x2e\x37\x34\x20\x2d\x32\x2e\ +\x33\x34\x2c\x2d\x31\x30\x2e\x39\x37\x20\x2d\x36\x2e\x31\x2c\x2d\ +\x31\x34\x2e\x37\x34\x20\x2d\x33\x2e\x37\x37\x2c\x2d\x33\x2e\x37\ +\x36\x20\x2d\x39\x2c\x2d\x36\x2e\x31\x20\x2d\x31\x34\x2e\x37\x34\ +\x2c\x2d\x36\x2e\x31\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\ +\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x33\x22\x20\x70\x6f\x69\x6e\x74\ +\x73\x3d\x22\x37\x32\x2e\x33\x31\x2c\x31\x33\x34\x2e\x37\x34\x20\ +\x39\x39\x2e\x32\x34\x2c\x31\x30\x37\x2e\x38\x20\x31\x33\x35\x2e\ +\x31\x35\x2c\x31\x34\x33\x2e\x37\x31\x20\x32\x33\x30\x2e\x39\x31\ +\x2c\x34\x37\x2e\x39\x36\x20\x32\x35\x37\x2e\x38\x35\x2c\x37\x34\ +\x2e\x38\x39\x20\x31\x33\x35\x2e\x31\x35\x2c\x31\x39\x37\x2e\x35\ +\x39\x20\x22\x2f\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\ +\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x06\x19\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x34\x36\x38\x32\x43\x38\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x37\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3a\x72\x6f\x75\x6e\x64\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\ +\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\ +\x20\x2e\x73\x74\x72\x31\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\x67\ +\x72\x61\x79\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\ +\x3a\x31\x37\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\ +\x6e\x65\x6a\x6f\x69\x6e\x3a\x72\x6f\x75\x6e\x64\x3b\x73\x74\x72\ +\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\ +\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\ +\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\x6e\x65\x7d\x0d\x0a\ +\x20\x20\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\ +\x23\x34\x36\x38\x32\x43\x38\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\x3e\ +\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\x3c\ +\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\x22\ +\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\x3e\ +\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\x69\x64\ +\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\x43\ +\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\x20\ +\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\ +\x6c\x30\x22\x20\x78\x3d\x22\x2d\x30\x22\x20\x79\x3d\x22\x2d\x30\ +\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\ +\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\ +\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\ +\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\ +\x74\x73\x3d\x22\x31\x33\x35\x2e\x34\x36\x2c\x32\x36\x38\x2e\x37\ +\x38\x20\x31\x38\x36\x2e\x32\x37\x2c\x32\x34\x31\x2e\x32\x39\x20\ +\x31\x33\x35\x2e\x34\x36\x2c\x32\x31\x33\x2e\x38\x20\x22\x2f\x3e\ +\x0d\x0a\x20\x20\x3c\x67\x20\x69\x64\x3d\x22\x5f\x32\x37\x37\x39\ +\x35\x34\x33\x36\x33\x34\x35\x32\x38\x22\x3e\x0d\x0a\x20\x20\x20\ +\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\ +\x30\x20\x73\x74\x72\x30\x22\x20\x64\x3d\x22\x4d\x31\x33\x35\x2e\ +\x34\x36\x20\x32\x34\x31\x2e\x32\x39\x63\x2d\x35\x38\x2e\x34\x34\ +\x2c\x30\x20\x2d\x31\x30\x35\x2e\x38\x33\x2c\x2d\x34\x37\x2e\x33\ +\x38\x20\x2d\x31\x30\x35\x2e\x38\x33\x2c\x2d\x31\x30\x35\x2e\x38\ +\x33\x20\x30\x2c\x2d\x35\x38\x2e\x34\x35\x20\x34\x37\x2e\x33\x39\ +\x2c\x2d\x31\x30\x35\x2e\x38\x33\x20\x31\x30\x35\x2e\x38\x33\x2c\ +\x2d\x31\x30\x35\x2e\x38\x33\x20\x32\x33\x2e\x38\x2c\x30\x20\x34\ +\x36\x2e\x39\x2c\x38\x2e\x30\x32\x20\x36\x35\x2e\x35\x38\x2c\x32\ +\x32\x2e\x37\x36\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x61\x74\ +\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\ +\x72\x31\x22\x20\x64\x3d\x22\x4d\x32\x31\x30\x2e\x32\x38\x20\x36\ +\x30\x2e\x36\x33\x63\x33\x2e\x32\x38\x2c\x33\x2e\x32\x37\x20\x36\ +\x2e\x33\x33\x2c\x36\x2e\x37\x34\x20\x39\x2e\x31\x34\x2c\x31\x30\ +\x2e\x34\x31\x6d\x31\x38\x2e\x32\x36\x20\x39\x31\x2e\x38\x63\x2d\ +\x31\x2e\x32\x2c\x34\x2e\x34\x37\x20\x2d\x32\x2e\x36\x38\x2c\x38\ +\x2e\x38\x35\x20\x2d\x34\x2e\x34\x34\x2c\x31\x33\x2e\x31\x32\x6d\ +\x38\x2e\x30\x34\x20\x2d\x34\x30\x2e\x35\x31\x63\x30\x2e\x30\x31\ +\x2c\x34\x2e\x36\x33\x20\x2d\x30\x2e\x33\x2c\x39\x2e\x32\x34\x20\ +\x2d\x30\x2e\x39\x2c\x31\x33\x2e\x38\x32\x6d\x2d\x32\x2e\x37\x31\ +\x20\x2d\x34\x31\x2e\x32\x31\x63\x31\x2e\x32\x2c\x34\x2e\x34\x37\ +\x20\x32\x2e\x31\x31\x2c\x39\x20\x32\x2e\x37\x31\x2c\x31\x33\x2e\ +\x35\x39\x6d\x2d\x31\x33\x2e\x32\x38\x20\x2d\x33\x39\x2e\x31\x31\ +\x63\x32\x2e\x33\x32\x2c\x34\x20\x34\x2e\x33\x37\x2c\x38\x2e\x31\ +\x35\x20\x36\x2e\x31\x34\x2c\x31\x32\x2e\x34\x32\x22\x2f\x3e\x0d\ +\x0a\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\ +\x3c\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x0b\x2d\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x67\x72\x61\x79\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\ +\x68\x3a\x31\x34\x2e\x31\x31\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\ +\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\ +\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x73\x74\x72\x31\x20\x7b\x73\ +\x74\x72\x6f\x6b\x65\x3a\x23\x34\x36\x38\x32\x43\x38\x3b\x73\x74\ +\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x31\x36\x2e\x39\x33\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x63\x61\x70\x3a\ +\x73\x71\x75\x61\x72\x65\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\ +\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\ +\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\ +\x6c\x6c\x3a\x6e\x6f\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\ +\x69\x6c\x32\x20\x7b\x66\x69\x6c\x6c\x3a\x67\x72\x61\x79\x7d\x0d\ +\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x33\x20\x7b\x66\x69\x6c\x6c\ +\x3a\x23\x46\x46\x39\x39\x33\x33\x7d\x0d\x0a\x20\x20\x20\x20\x2e\ +\x66\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x67\x72\x61\x79\x3b\ +\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x6e\x6f\x6e\x7a\x65\x72\ +\x6f\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\ +\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\x73\x3e\ +\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\x64\x61\ +\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\ +\x65\x74\x61\x64\x61\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\x72\x65\ +\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\x2d\x4c\ +\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\x65\x63\x74\ +\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\x78\x3d\ +\x22\x30\x22\x20\x79\x3d\x22\x30\x22\x20\x77\x69\x64\x74\x68\x3d\ +\x22\x32\x37\x30\x2e\x39\x32\x22\x20\x68\x65\x69\x67\x68\x74\x3d\ +\x22\x32\x37\x30\x2e\x39\x32\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x67\ +\x20\x69\x64\x3d\x22\x5f\x31\x32\x32\x30\x36\x32\x35\x31\x33\x37\ +\x33\x31\x32\x22\x3e\x0d\x0a\x20\x20\x20\x3c\x67\x3e\x0d\x0a\x20\ +\x20\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\ +\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\ +\x22\x36\x32\x2e\x30\x39\x2c\x31\x30\x37\x2e\x39\x34\x20\x36\x32\ +\x2e\x30\x39\x2c\x39\x33\x2e\x38\x32\x20\x36\x37\x2e\x36\x37\x2c\ +\x39\x33\x2e\x39\x36\x20\x37\x33\x2e\x31\x39\x2c\x39\x34\x2e\x33\ +\x38\x20\x37\x38\x2e\x36\x32\x2c\x39\x35\x2e\x30\x38\x20\x38\x33\ +\x2e\x39\x38\x2c\x39\x36\x2e\x30\x33\x20\x38\x39\x2e\x32\x34\x2c\ +\x39\x37\x2e\x32\x35\x20\x39\x34\x2e\x34\x2c\x39\x38\x2e\x37\x31\ +\x20\x39\x39\x2e\x34\x34\x2c\x31\x30\x30\x2e\x34\x32\x20\x31\x30\ +\x34\x2e\x33\x38\x2c\x31\x30\x32\x2e\x33\x36\x20\x31\x30\x39\x2e\ +\x32\x2c\x31\x30\x34\x2e\x35\x34\x20\x31\x31\x33\x2e\x38\x39\x2c\ +\x31\x30\x36\x2e\x39\x34\x20\x31\x31\x38\x2e\x34\x34\x2c\x31\x30\ +\x39\x2e\x35\x36\x20\x31\x32\x32\x2e\x38\x34\x2c\x31\x31\x32\x2e\ +\x33\x38\x20\x31\x32\x37\x2e\x31\x2c\x31\x31\x35\x2e\x34\x32\x20\ +\x31\x33\x31\x2e\x32\x31\x2c\x31\x31\x38\x2e\x36\x34\x20\x31\x33\ +\x35\x2e\x31\x35\x2c\x31\x32\x32\x2e\x30\x36\x20\x31\x33\x38\x2e\ +\x39\x32\x2c\x31\x32\x35\x2e\x36\x35\x20\x31\x34\x32\x2e\x35\x32\ +\x2c\x31\x32\x39\x2e\x34\x33\x20\x31\x34\x35\x2e\x39\x33\x2c\x31\ +\x33\x33\x2e\x33\x37\x20\x31\x34\x39\x2e\x31\x36\x2c\x31\x33\x37\ +\x2e\x34\x36\x20\x31\x35\x32\x2e\x31\x39\x2c\x31\x34\x31\x2e\x37\ +\x34\x20\x31\x35\x35\x2e\x30\x31\x2c\x31\x34\x36\x2e\x31\x34\x20\ +\x31\x35\x37\x2e\x36\x33\x2c\x31\x35\x30\x2e\x36\x39\x20\x31\x36\ +\x30\x2e\x30\x33\x2c\x31\x35\x35\x2e\x33\x38\x20\x31\x36\x32\x2e\ +\x32\x31\x2c\x31\x36\x30\x2e\x31\x39\x20\x31\x36\x34\x2e\x31\x35\ +\x2c\x31\x36\x35\x2e\x31\x33\x20\x31\x36\x35\x2e\x38\x36\x2c\x31\ +\x37\x30\x2e\x31\x37\x20\x31\x36\x37\x2e\x33\x32\x2c\x31\x37\x35\ +\x2e\x33\x34\x20\x31\x36\x38\x2e\x35\x34\x2c\x31\x38\x30\x2e\x36\ +\x20\x31\x36\x39\x2e\x34\x39\x2c\x31\x38\x35\x2e\x39\x36\x20\x31\ +\x37\x30\x2e\x31\x39\x2c\x31\x39\x31\x2e\x33\x39\x20\x31\x37\x30\ +\x2e\x36\x31\x2c\x31\x39\x36\x2e\x39\x31\x20\x31\x37\x30\x2e\x37\ +\x35\x2c\x32\x30\x32\x2e\x34\x39\x20\x31\x35\x36\x2e\x36\x33\x2c\ +\x32\x30\x32\x2e\x34\x39\x20\x31\x35\x36\x2e\x35\x31\x2c\x31\x39\ +\x37\x2e\x36\x31\x20\x31\x35\x36\x2e\x31\x35\x2c\x31\x39\x32\x2e\ +\x38\x31\x20\x31\x35\x35\x2e\x35\x35\x2c\x31\x38\x38\x2e\x30\x38\ +\x20\x31\x35\x34\x2e\x37\x32\x2c\x31\x38\x33\x2e\x34\x32\x20\x31\ +\x35\x33\x2e\x36\x36\x2c\x31\x37\x38\x2e\x38\x36\x20\x31\x35\x32\ +\x2e\x33\x38\x2c\x31\x37\x34\x2e\x33\x37\x20\x31\x35\x30\x2e\x38\ +\x39\x2c\x31\x36\x39\x2e\x39\x37\x20\x31\x34\x39\x2e\x32\x31\x2c\ +\x31\x36\x35\x2e\x36\x39\x20\x31\x34\x37\x2e\x33\x31\x2c\x31\x36\ +\x31\x2e\x35\x20\x31\x34\x35\x2e\x32\x33\x2c\x31\x35\x37\x2e\x34\ +\x33\x20\x31\x34\x32\x2e\x39\x35\x2c\x31\x35\x33\x2e\x34\x36\x20\ +\x31\x34\x30\x2e\x34\x39\x2c\x31\x34\x39\x2e\x36\x32\x20\x31\x33\ +\x37\x2e\x38\x36\x2c\x31\x34\x35\x2e\x39\x32\x20\x31\x33\x35\x2e\ +\x30\x35\x2c\x31\x34\x32\x2e\x33\x35\x20\x31\x33\x32\x2e\x30\x38\ +\x2c\x31\x33\x38\x2e\x39\x31\x20\x31\x32\x38\x2e\x39\x34\x2c\x31\ +\x33\x35\x2e\x36\x33\x20\x31\x32\x35\x2e\x36\x35\x2c\x31\x33\x32\ +\x2e\x35\x20\x31\x32\x32\x2e\x32\x33\x2c\x31\x32\x39\x2e\x35\x32\ +\x20\x31\x31\x38\x2e\x36\x36\x2c\x31\x32\x36\x2e\x37\x32\x20\x31\ +\x31\x34\x2e\x39\x34\x2c\x31\x32\x34\x2e\x30\x38\x20\x31\x31\x31\ +\x2e\x31\x2c\x31\x32\x31\x2e\x36\x32\x20\x31\x30\x37\x2e\x31\x35\ +\x2c\x31\x31\x39\x2e\x33\x34\x20\x31\x30\x33\x2e\x30\x38\x2c\x31\ +\x31\x37\x2e\x32\x36\x20\x39\x38\x2e\x39\x2c\x31\x31\x35\x2e\x33\ +\x36\x20\x39\x34\x2e\x36\x2c\x31\x31\x33\x2e\x36\x38\x20\x39\x30\ +\x2e\x32\x2c\x31\x31\x32\x2e\x31\x39\x20\x38\x35\x2e\x37\x32\x2c\ +\x31\x31\x30\x2e\x39\x31\x20\x38\x31\x2e\x31\x36\x2c\x31\x30\x39\ +\x2e\x38\x35\x20\x37\x36\x2e\x35\x2c\x31\x30\x39\x2e\x30\x32\x20\ +\x37\x31\x2e\x37\x37\x2c\x31\x30\x38\x2e\x34\x32\x20\x36\x36\x2e\ +\x39\x37\x2c\x31\x30\x38\x2e\x30\x36\x20\x22\x2f\x3e\x0d\x0a\x20\ +\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x6f\x6c\x79\ +\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\ +\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x31\x36\x33\x2e\x36\x35\x2c\ +\x32\x33\x33\x2e\x34\x37\x20\x31\x35\x33\x2e\x30\x37\x2c\x32\x31\ +\x35\x2e\x31\x34\x20\x31\x34\x32\x2e\x34\x39\x2c\x31\x39\x36\x2e\ +\x38\x31\x20\x31\x36\x33\x2e\x36\x35\x2c\x31\x39\x36\x2e\x38\x31\ +\x20\x31\x38\x34\x2e\x38\x32\x2c\x31\x39\x36\x2e\x38\x31\x20\x31\ +\x37\x34\x2e\x32\x34\x2c\x32\x31\x35\x2e\x31\x34\x20\x22\x2f\x3e\ +\x0d\x0a\x20\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\x20\x70\x6f\x69\x6e\x74\ +\x73\x3d\x22\x34\x33\x2e\x38\x2c\x31\x30\x30\x2e\x39\x32\x20\x36\ +\x32\x2e\x31\x33\x2c\x39\x30\x2e\x33\x33\x20\x38\x30\x2e\x34\x35\ +\x2c\x37\x39\x2e\x37\x36\x20\x38\x30\x2e\x34\x35\x2c\x31\x30\x30\ +\x2e\x39\x32\x20\x38\x30\x2e\x34\x35\x2c\x31\x32\x32\x2e\x30\x38\ +\x20\x36\x32\x2e\x31\x33\x2c\x31\x31\x31\x2e\x35\x31\x20\x22\x2f\ +\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\ +\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\ +\x32\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x32\x32\x30\x2e\x31\ +\x34\x2c\x32\x34\x39\x2e\x37\x36\x20\x32\x30\x31\x2e\x38\x31\x2c\ +\x32\x33\x39\x2e\x31\x38\x20\x31\x38\x33\x2e\x34\x38\x2c\x32\x32\ +\x38\x2e\x36\x20\x31\x38\x33\x2e\x34\x38\x2c\x32\x34\x39\x2e\x37\ +\x36\x20\x31\x38\x33\x2e\x34\x38\x2c\x32\x37\x30\x2e\x39\x33\x20\ +\x32\x30\x31\x2e\x38\x31\x2c\x32\x36\x30\x2e\x33\x35\x20\x22\x2f\ +\x3e\x0d\x0a\x20\x20\x3c\x6c\x69\x6e\x65\x20\x63\x6c\x61\x73\x73\ +\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x30\x22\x20\x78\x31\x3d\ +\x22\x32\x30\x30\x2e\x34\x22\x20\x79\x31\x3d\x22\x32\x34\x39\x2e\ +\x37\x36\x22\x20\x78\x32\x3d\x22\x35\x30\x2e\x37\x39\x22\x20\x79\ +\x32\x3d\x20\x22\x32\x34\x39\x2e\x37\x36\x22\x20\x2f\x3e\x0d\x0a\ +\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\ +\x69\x6c\x30\x20\x73\x74\x72\x31\x22\x20\x64\x3d\x22\x4d\x32\x34\ +\x39\x2e\x36\x35\x20\x32\x35\x38\x2e\x31\x32\x63\x2d\x34\x2e\x35\ +\x2c\x2d\x31\x32\x38\x2e\x38\x37\x20\x2d\x31\x30\x37\x2e\x39\x33\ +\x2c\x2d\x32\x33\x32\x2e\x33\x20\x2d\x32\x33\x36\x2e\x38\x2c\x2d\ +\x32\x33\x36\x2e\x38\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\ +\x79\x67\x6f\x6e\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\x22\x20\x63\ +\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x33\x22\x20\x70\x6f\x69\x6e\ +\x74\x73\x3d\x22\x32\x37\x30\x2e\x39\x33\x2c\x32\x32\x38\x2e\x36\ +\x20\x32\x37\x30\x2e\x39\x33\x2c\x32\x37\x30\x2e\x39\x33\x20\x32\ +\x32\x38\x2e\x36\x2c\x32\x37\x30\x2e\x39\x33\x20\x32\x32\x38\x2e\ +\x36\x2c\x32\x32\x38\x2e\x36\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\ +\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\ +\x5f\x30\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x33\x22\ +\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x34\x32\x2e\x33\x33\x2c\x32\ +\x32\x38\x2e\x36\x20\x34\x32\x2e\x33\x33\x2c\x32\x37\x30\x2e\x39\ +\x33\x20\x2d\x30\x2c\x32\x37\x30\x2e\x39\x33\x20\x2d\x30\x2c\x32\ +\x32\x38\x2e\x36\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\ +\x79\x67\x6f\x6e\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\x5f\x31\x22\ +\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x33\x22\x20\x70\x6f\ +\x69\x6e\x74\x73\x3d\x22\x34\x32\x2e\x33\x33\x2c\x30\x20\x34\x32\ +\x2e\x33\x33\x2c\x34\x32\x2e\x33\x33\x20\x2d\x30\x2c\x34\x32\x2e\ +\x33\x33\x20\x2d\x30\x2c\x30\x20\x22\x2f\x3e\x0d\x0a\x20\x3c\x2f\ +\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x06\x7e\ +\x00\ +\x00\x16\x87\x78\x9c\xd5\x58\x6d\x6f\x13\x47\x10\xfe\x5e\x89\xff\ +\xb0\x5c\xbf\xb4\xd2\xed\xde\xed\xee\xbd\xec\x05\xbb\x08\x1c\x40\ +\x48\xb4\xa5\x6d\x28\xaa\x10\x8a\x2c\xfb\xb0\x4f\xf5\x9b\xec\x23\ +\x4e\xa8\xfa\xdf\x3b\xf3\x8c\x2d\x8e\xe0\x10\x22\x41\x45\xbe\x38\ +\x67\xef\xdc\xcc\xce\xec\xf3\xcc\x33\x9b\xde\xfd\xf3\xf9\x4c\x9d\ +\xd5\xeb\x4d\xb3\x5c\xf4\x23\x6b\xd2\x48\xd5\x8b\xd1\x72\xdc\x2c\ +\x26\xfd\xe8\xc5\xc9\x63\x1d\xa2\xfb\x3f\xdd\xf9\xae\x77\xf7\xf8\ +\xd7\xc1\xc9\x5f\xcf\x1f\xa9\xcd\xd9\x44\x3d\x7f\xf1\xf0\xd9\xd3\ +\x81\x8a\x74\x92\xbc\xf4\x83\x24\x39\x3e\x39\x56\x7f\xfc\xf9\x44\ +\x59\x63\x93\xe4\xd1\x2f\x91\x8a\xa6\x6d\xbb\x3a\x4a\x92\xed\x76\ +\x6b\xb6\xde\x2c\xd7\x93\xe4\xc9\x7a\xb8\x9a\x36\xa3\x4d\x42\x86\ +\x09\x1b\xd2\x4b\x09\x39\xb3\xd6\x8c\xdb\x71\x84\x18\x5a\xab\xc1\ +\xba\x1e\xb6\xcb\xf5\x91\x1a\x2c\xd7\xf5\xec\xf8\xf7\x07\x2f\x95\ +\x4b\x9d\x55\x3f\x14\x99\x7a\xd8\xb4\x3f\x2a\xad\xd9\x94\x77\x41\ +\x3b\x5f\x6c\xfa\x07\x42\xb9\x34\x4d\xd9\x75\xc4\x26\x47\x9b\xd5\ +\x70\x54\xf7\xa3\xd5\xba\xde\xd4\xeb\xb3\x3a\x52\xdb\x66\xdc\x4e\ +\xfb\x91\x33\x65\x5a\xb9\xf9\x3c\x52\xd3\xba\x99\x4c\xdb\xee\x2f\ +\x9d\x82\xd8\x48\x6d\xda\x8b\x19\x79\xd8\x4c\x87\xab\x5a\xaf\xeb\ +\xc5\xb8\x5e\x53\x79\x8e\x26\xf5\x72\x5e\xb7\xeb\x66\xf4\x7c\x5d\ +\x8f\x1a\xb6\xbf\xa7\xda\xfa\xbc\xbd\xc6\xa4\x99\x0f\x27\x5d\x37\ +\xcb\x55\xdb\xcc\x9b\x77\xf5\x6f\x6f\x87\xb3\xa6\xbd\xb8\xa7\xde\ +\x34\xb3\x99\x5e\xbf\x9d\xd5\x47\xf5\x59\xbd\x58\x8e\xc7\xf7\xd4\ +\x68\xd6\xac\x3e\xf8\x29\xba\xf3\xdd\x59\x53\x6f\x1f\x2e\xcf\xfb\ +\x51\xaa\x52\xe5\xca\xd4\x54\x6e\xf7\x87\x16\xa5\x38\x47\xe7\xb3\ +\x66\xf1\xf7\xa1\x12\xd9\xaa\xaa\x12\xac\x76\x8c\x97\xe3\xf9\x07\ +\xb6\x23\x3e\x02\xfa\x9c\x27\x78\x1a\xaf\x87\xdb\x84\x6c\xb8\xbe\ +\x9e\x0f\x4c\xf5\xc6\xf5\x9b\x0d\x3f\xa8\x1e\x6a\xa4\xda\x8b\x15\ +\x15\x8a\x8b\x90\x8c\x36\x1b\xd8\xd0\xda\xdd\x57\x83\xe3\x07\x27\ +\x0f\x5e\xe1\x9b\x32\x94\x5f\xaa\xfe\xe1\x2c\x8f\x16\xcb\x45\xfd\ +\xef\xfb\x9f\xfd\xee\xe7\xef\xb3\x22\xb8\x41\xe8\xac\xb8\xfd\xca\ +\xe3\xc7\x55\xe5\x7d\x67\xc5\xee\x56\xb6\xd3\xa6\xdd\xf9\x7a\xfd\ +\x5a\xf6\x94\x60\x53\xd8\x68\xb2\xdf\x69\x6f\xa2\x9a\x71\x3f\x1a\ +\x0c\xe7\xc3\xf1\xf0\xf4\x3c\x4d\x5d\x7a\x6a\x65\xa3\x3d\x3a\x2a\ +\xfa\xb1\x1d\x8a\x05\xa7\x4c\x1f\xab\xa7\xc7\xa7\x29\xbe\xe8\x67\ +\xc3\x8b\x7a\x1d\x25\x62\x4c\x07\xda\xd2\xb9\x0c\x37\x04\x41\xce\ +\x88\xc0\xd6\x8f\x74\x6a\x52\x02\xcc\x05\x3f\xbd\x07\x1a\x1f\x8a\ +\xef\xc0\x4c\xbe\xef\xfc\x4c\x76\x45\x5a\x0d\xdb\x69\xc7\x1f\x79\ +\xa1\x4d\xfc\xec\xac\x71\x99\xb2\xa5\x33\xd6\x8e\xb4\x4b\xe9\x5b\ +\xac\x89\x32\x45\xa5\xb4\x2b\x8d\x0d\xb1\xf6\xa5\xc9\x4b\xa5\x6d\ +\x6e\xb2\x2a\xd6\x79\x69\x82\x55\x6c\x41\x4b\xb0\x57\x30\x88\xc5\ +\x5c\x61\x3d\x16\x6b\x25\xfe\x60\xac\xc4\x9b\x38\x13\x5f\xe2\x4a\ +\xa2\xc5\xe2\x4a\x82\xc5\x6c\x4b\x21\xc5\x17\x8c\xdf\xed\xd2\xd9\ +\xe5\xc1\x05\xa4\xb2\x76\xf2\x71\xb7\x2c\x9f\xb9\xa6\x6e\x40\xeb\ +\x64\x96\x8f\xe8\x0d\x1f\x17\xb4\x65\x95\x9b\xc2\x21\x00\xf6\x94\ +\x3b\xb6\x4e\xdd\x8c\xcf\xdd\x29\xfa\xf0\xa3\xdc\x84\x32\xf6\xc6\ +\x17\xca\x3a\x7e\x2d\x33\x69\xae\x6c\x30\x65\x11\x3b\x93\x91\x83\ +\x92\x12\xa1\x57\xa9\x0a\x04\x03\xce\xd0\x58\x7a\xce\x0c\x17\x22\ +\x35\x19\xbb\xf1\x79\x4c\x1e\x4b\x4f\x8f\xf4\x1a\x59\x67\x64\x61\ +\x9c\xa7\xa4\x8d\xf5\xca\x71\x08\xcd\x5b\x51\x14\xa8\x44\x01\x2b\ +\xb6\x08\x64\x61\x4b\x53\x72\x96\x26\x2f\xd8\x26\x78\xca\xcb\x38\ +\x31\xb2\xbc\x40\x3e\xc9\x3d\x05\xa4\x2d\x50\x14\xda\x22\x7d\x7a\ +\xcf\xaf\xf0\x61\xa4\x38\x8b\x9d\x2f\x93\x56\xfc\x7a\x61\x39\x72\ +\x28\x50\xbc\x8a\x16\x90\x1f\x87\xa2\x4a\xb2\xad\x53\x08\x15\x73\ +\x54\x25\x91\x62\x8e\xaa\x24\x4e\x8c\xa0\x1c\xcd\xbb\x38\xe5\x7d\ +\x73\x98\x92\xcc\x9d\xe3\x58\x29\x3f\x06\xa5\x91\x18\xf2\xe2\x08\ +\x21\x96\xbc\x38\x00\xb9\xe0\xbc\xf6\x30\xeb\x25\x93\xcf\x61\x4f\ +\x49\xef\x57\x2a\xd0\x19\x0b\xd6\xbc\x60\x2d\x08\xd6\xca\xab\xb1\ +\x56\x1d\xc0\x5a\x79\x15\xd6\xaa\x4f\x63\x2d\x7c\x8c\xb5\x70\x3d\ +\x77\x4e\xd3\x2e\x7b\xfc\xad\xca\x87\xb9\xc3\x20\xda\x93\x87\x00\ +\xc0\xb0\x57\x8c\xf4\x18\xa8\x57\x40\x7a\xcc\xa8\x67\x8c\xfb\x98\ +\x31\xaf\x18\xe7\x31\x43\x5e\x31\xcc\x19\x1d\x5e\x08\xc7\xb8\x50\ +\x00\x03\x33\x8b\x1e\x19\x0b\x0c\x19\x5e\x0f\x19\xf8\x04\x03\x82\ +\x1d\xd0\xae\x04\x76\x02\x76\x05\xd8\x69\xe0\x0e\xb0\xd3\xc0\x1d\ +\x60\xc7\xd8\xba\xc4\xa7\xd0\xe1\x53\xd6\xe1\x13\x13\x05\x3d\x00\ +\x96\x52\x79\x29\x49\xee\x50\x44\xea\x03\x68\x03\x1a\x7d\x40\xa3\ +\x11\xec\x99\x82\x56\xa0\xd1\x0b\xb4\x34\x03\xcd\xdd\x80\x1d\x52\ +\x49\x4c\x91\x49\x6d\x73\x46\xbf\x12\x67\x7c\x16\x59\x06\x26\x50\ +\xfd\x4d\xa0\x22\x67\x60\x0a\xf9\x2a\x98\x2b\x19\x95\x28\x50\xd5\ +\x6f\x4a\x0a\x1a\x13\xf2\x82\x92\x12\x0c\xe5\x57\xf7\xdf\xfc\x00\ +\x84\x72\x40\x28\x74\xdb\xaf\xfb\x12\xed\xf7\x73\x28\x61\x0f\x51\ +\xe2\x36\x64\xd3\x11\x93\x8c\xc5\x24\x63\x31\x01\xb6\xe1\x3f\xe3\ +\x2d\x65\x39\xb4\x84\x25\x26\x04\x86\x79\x05\x01\x29\x58\x40\x82\ +\xc2\x41\xb3\x80\xb0\x82\x84\x1c\x88\x2f\x80\x78\x0f\xc4\x07\x20\ +\xbe\x10\xc4\x17\x37\x44\xfc\x95\x0a\x02\x9b\x90\x5d\xa3\x20\xb9\ +\xe5\x50\x68\xf6\xb4\x7f\x7e\x86\x08\x18\x88\x58\x60\x5d\x35\x20\ +\x42\x9a\x81\x08\x36\xe7\x2f\xa0\x4e\x60\x98\x53\x6d\x52\x61\x02\ +\x13\xc1\x4b\x4d\xed\x25\x22\xe4\x16\x1a\xeb\x41\x2e\x3a\x6b\xa6\ +\x15\x93\xa2\x00\x29\x72\x90\xc2\x83\x14\xcc\x89\xb2\xbc\x21\x27\ +\x6c\x41\x81\x4a\x41\x92\xfb\x3a\x73\x89\xef\x40\xa9\xfc\xb2\x73\ +\xd6\xa9\x3b\x40\x8c\xdb\x93\xd2\xd7\x1a\xb5\xd0\x5b\xf5\x9e\x5f\ +\x45\x0e\xc0\x2b\x81\x94\x96\xe6\x8a\xde\xaa\xd1\x5c\x33\x21\x07\ +\x9a\x2b\x7a\xab\x06\xe7\x00\xe4\x8c\x6d\x4a\x26\xb5\xb8\x84\x84\ +\xc9\xb4\x06\x1e\x80\x06\xa5\x0c\x52\xdc\xe0\x59\xc7\x34\x88\x88\ +\x81\x0d\xf4\xf3\x7b\xdd\x80\x98\x09\xe1\x20\x0f\xa5\x12\xc6\x81\ +\x99\xb0\xe2\x69\x47\x0e\xe6\x5b\x19\xa4\x5c\x5a\xa2\xcc\x1f\xcf\ +\xed\xe1\x5a\x30\x7d\x03\xb3\x94\x3f\xc0\x0f\xc7\xf6\xb4\x43\xe2\ +\x47\x66\x47\x98\x83\x31\x06\x63\x0a\xc6\x10\x6c\x65\xa2\xa5\x11\ +\x18\x13\x30\x06\x60\x99\x7f\x05\x73\x98\x7e\x31\xfc\xe6\xbb\x69\ +\x79\xd7\x92\x6f\xdb\x10\x82\x3b\x09\xae\x24\x02\xed\x18\x57\x12\ +\x00\x3b\x96\x2b\x09\x80\x1d\xe3\x4a\x02\x5c\xc7\x32\x9a\xe0\x4e\ +\x82\x2b\xc9\x25\xc4\x95\x1d\xc4\x79\x41\x9c\xc2\x8d\x24\x97\x99\ +\xcd\xc1\x80\xd2\x10\x2d\x41\xa1\xa8\x0f\x04\x10\x98\xb6\x73\x1b\ +\x20\x76\x13\x69\x29\x30\xb4\x7f\x09\xe6\x1c\x68\xc3\xf9\xe1\x21\ +\x25\xff\xb8\x0b\xe7\x92\x55\xe8\x66\xe5\x3e\x45\x9c\xec\x90\xb0\ +\x94\x9e\x4f\x8e\xee\x22\x81\x79\x43\xea\x0f\xf1\x87\xf6\x43\xfa\ +\x59\xf9\x21\xfc\xac\xfb\x90\x7d\x51\x7d\x88\xbe\x68\x3e\x24\x9f\ +\x15\x1f\x90\x56\xa2\xf8\x97\xda\x33\x49\xfe\xbe\xd5\xdb\x91\x4c\ +\x29\x8c\x66\x6e\xd4\x85\x34\x6a\xc7\x8d\xda\xa2\x51\x97\x98\x3e\ +\x98\x11\x05\x57\x9b\x2c\x79\x2a\x81\x17\x14\x00\x45\xcf\x4c\xa8\ +\x40\x8c\x92\x89\x98\xa2\x6b\x5b\x74\x6a\x9a\xb0\x78\x84\xc1\xf0\ +\x62\xe5\x32\x1b\x40\x9b\x4a\x68\x53\x80\x36\x41\x89\x26\x80\x36\ +\xe0\x0d\x4d\x62\xef\xaf\xbc\x3e\xc6\x20\xa6\x65\x12\x93\x41\xec\ +\x86\x9d\xfa\x0a\xde\xb0\x45\xc8\xfe\x1f\xde\x7c\x6d\x80\xed\x68\ +\x23\x7f\x7a\xfc\x6f\x5a\xfa\xfb\x1f\xdf\xe9\xd1\x3e\ +\x00\x00\x07\x77\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x3f\x3e\x0a\x3c\x73\x76\x67\x20\x77\x69\x64\x74\x68\x3d\ +\x22\x32\x37\x30\x2e\x39\x32\x22\x20\x68\x65\x69\x67\x68\x74\x3d\ +\x22\x32\x37\x30\x2e\x39\x32\x22\x20\x78\x6d\x6c\x6e\x73\x3d\x22\ +\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\ +\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\x6c\x6e\ +\x73\x3a\x73\x76\x67\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\ +\x67\x22\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3d\x22\x65\x76\ +\x65\x6e\x6f\x64\x64\x22\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\ +\x3d\x22\x65\x76\x65\x6e\x6f\x64\x64\x22\x20\x76\x65\x72\x73\x69\ +\x6f\x6e\x3d\x22\x31\x2e\x31\x22\x20\x78\x6d\x6c\x3a\x73\x70\x61\ +\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\x65\x22\x3e\x0a\x20\ +\x3c\x64\x65\x66\x73\x3e\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\ +\x74\x79\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\ +\x0a\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\ +\x20\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\ +\x3a\x23\x36\x36\x36\x36\x36\x36\x3b\x73\x74\x72\x6f\x6b\x65\x2d\ +\x77\x69\x64\x74\x68\x3a\x31\x36\x2e\x39\x33\x3b\x73\x74\x72\x6f\ +\x6b\x65\x2d\x6c\x69\x6e\x65\x63\x61\x70\x3a\x73\x71\x75\x61\x72\ +\x65\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\ +\x6d\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\ +\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\ +\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x32\x20\x7b\ +\x66\x69\x6c\x6c\x3a\x23\x30\x30\x39\x39\x33\x33\x7d\x0d\x0a\x20\ +\x20\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x77\ +\x68\x69\x74\x65\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\x3e\x0a\x20\x20\ +\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0a\x20\x3c\x2f\x64\x65\x66\x73\ +\x3e\x0a\x20\x3c\x67\x20\x63\x6c\x61\x73\x73\x3d\x22\x6c\x61\x79\ +\x65\x72\x22\x3e\x0a\x20\x20\x3c\x74\x69\x74\x6c\x65\x3e\x4c\x61\ +\x79\x65\x72\x20\x31\x3c\x2f\x74\x69\x74\x6c\x65\x3e\x0a\x20\x20\ +\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\ +\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\ +\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0a\x20\x20\x3c\x72\x65\x63\ +\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\x68\ +\x65\x69\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x20\x69\ +\x64\x3d\x22\x73\x76\x67\x5f\x31\x22\x20\x77\x69\x64\x74\x68\x3d\ +\x22\x32\x37\x30\x2e\x39\x33\x22\x2f\x3e\x0a\x20\x20\x3c\x70\x6f\ +\x6c\x79\x6c\x69\x6e\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\ +\x6c\x30\x20\x73\x74\x72\x30\x22\x20\x69\x64\x3d\x22\x73\x76\x67\ +\x5f\x32\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x32\x35\x2e\x33\ +\x39\x2c\x32\x31\x39\x2e\x31\x36\x20\x35\x36\x2e\x31\x36\x2c\x38\ +\x32\x2e\x35\x33\x20\x32\x31\x34\x2e\x37\x35\x2c\x31\x38\x38\x2e\ +\x33\x36\x20\x32\x34\x35\x2e\x35\x32\x2c\x35\x31\x2e\x37\x36\x20\ +\x22\x2f\x3e\x0a\x20\x20\x3c\x67\x20\x69\x64\x3d\x22\x73\x76\x67\ +\x5f\x33\x22\x3e\x0a\x20\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\ +\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x69\x64\ +\x3d\x22\x73\x76\x67\x5f\x34\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\ +\x22\x32\x37\x30\x2e\x39\x32\x2c\x35\x39\x2e\x32\x36\x20\x32\x31\ +\x31\x2e\x36\x35\x2c\x35\x39\x2e\x32\x36\x20\x32\x31\x31\x2e\x36\ +\x35\x2c\x2d\x30\x2e\x30\x31\x20\x32\x37\x30\x2e\x39\x32\x2c\x2d\ +\x30\x2e\x30\x31\x20\x22\x2f\x3e\x0a\x20\x20\x20\x3c\x70\x61\x74\ +\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\x20\x64\ +\x3d\x22\x6d\x32\x37\x30\x2e\x39\x32\x2c\x35\x39\x2e\x32\x36\x6c\ +\x2d\x35\x39\x2e\x32\x37\x2c\x30\x6c\x30\x2c\x2d\x35\x39\x2e\x32\ +\x37\x6c\x35\x39\x2e\x32\x37\x2c\x30\x6c\x30\x2c\x35\x39\x2e\x32\ +\x37\x7a\x6d\x2d\x34\x36\x2e\x39\x32\x2c\x2d\x31\x32\x2e\x33\x35\ +\x6c\x33\x34\x2e\x35\x37\x2c\x30\x6c\x30\x2c\x2d\x33\x34\x2e\x35\ +\x37\x6c\x2d\x33\x34\x2e\x35\x37\x2c\x30\x6c\x30\x2c\x33\x34\x2e\ +\x35\x37\x7a\x22\x20\x69\x64\x3d\x22\x5f\x31\x22\x2f\x3e\x0a\x20\ +\x20\x3c\x2f\x67\x3e\x0a\x20\x20\x3c\x67\x20\x69\x64\x3d\x22\x73\ +\x76\x67\x5f\x35\x22\x3e\x0a\x20\x20\x20\x3c\x70\x6f\x6c\x79\x67\ +\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\ +\x69\x64\x3d\x22\x73\x76\x67\x5f\x36\x22\x20\x70\x6f\x69\x6e\x74\ +\x73\x3d\x22\x35\x39\x2e\x32\x35\x2c\x32\x37\x30\x2e\x39\x34\x20\ +\x2d\x30\x2e\x30\x32\x2c\x32\x37\x30\x2e\x39\x34\x20\x2d\x30\x2e\ +\x30\x32\x2c\x32\x31\x31\x2e\x36\x37\x20\x35\x39\x2e\x32\x35\x2c\ +\x32\x31\x31\x2e\x36\x37\x20\x22\x2f\x3e\x0a\x20\x20\x20\x3c\x70\ +\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\ +\x20\x64\x3d\x22\x6d\x35\x39\x2e\x32\x35\x2c\x32\x37\x30\x2e\x39\ +\x34\x6c\x2d\x35\x39\x2e\x32\x37\x2c\x30\x6c\x30\x2c\x2d\x35\x39\ +\x2e\x32\x37\x6c\x35\x39\x2e\x32\x37\x2c\x30\x6c\x30\x2c\x35\x39\ +\x2e\x32\x37\x7a\x6d\x2d\x34\x36\x2e\x39\x32\x2c\x2d\x31\x32\x2e\ +\x33\x35\x6c\x33\x34\x2e\x35\x37\x2c\x30\x6c\x30\x2c\x2d\x33\x34\ +\x2e\x35\x37\x6c\x2d\x33\x34\x2e\x35\x37\x2c\x30\x6c\x30\x2c\x33\ +\x34\x2e\x35\x37\x7a\x22\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\x22\ +\x2f\x3e\x0a\x20\x20\x3c\x2f\x67\x3e\x0a\x20\x20\x3c\x67\x20\x69\ +\x64\x3d\x22\x73\x76\x67\x5f\x37\x22\x3e\x0a\x20\x20\x20\x3c\x70\ +\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\ +\x6c\x31\x22\x20\x69\x64\x3d\x22\x73\x76\x67\x5f\x38\x22\x20\x70\ +\x6f\x69\x6e\x74\x73\x3d\x22\x38\x37\x2e\x32\x35\x2c\x31\x31\x32\ +\x2e\x39\x34\x30\x30\x30\x32\x34\x34\x31\x34\x30\x36\x32\x35\x20\ +\x32\x37\x2e\x39\x37\x39\x39\x39\x39\x35\x34\x32\x32\x33\x36\x33\ +\x32\x38\x2c\x31\x31\x32\x2e\x39\x34\x30\x30\x30\x32\x34\x34\x31\ +\x34\x30\x36\x32\x35\x20\x32\x37\x2e\x39\x37\x39\x39\x39\x39\x35\ +\x34\x32\x32\x33\x36\x33\x32\x38\x2c\x35\x33\x2e\x36\x36\x39\x39\ +\x39\x38\x31\x36\x38\x39\x34\x35\x33\x31\x20\x38\x37\x2e\x32\x35\ +\x2c\x35\x33\x2e\x36\x36\x39\x39\x39\x38\x31\x36\x38\x39\x34\x35\ +\x33\x31\x20\x22\x2f\x3e\x0a\x20\x20\x20\x3c\x70\x61\x74\x68\x20\ +\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\x20\x64\x3d\x22\ +\x6d\x38\x37\x2e\x32\x35\x2c\x31\x31\x32\x2e\x39\x34\x6c\x2d\x35\ +\x39\x2e\x32\x37\x2c\x30\x6c\x30\x2c\x2d\x35\x39\x2e\x32\x37\x6c\ +\x35\x39\x2e\x32\x37\x2c\x30\x6c\x30\x2c\x35\x39\x2e\x32\x37\x7a\ +\x6d\x2d\x34\x36\x2e\x39\x32\x2c\x2d\x31\x32\x2e\x33\x35\x6c\x33\ +\x34\x2e\x35\x37\x2c\x30\x6c\x30\x2c\x2d\x33\x34\x2e\x35\x37\x6c\ +\x2d\x33\x34\x2e\x35\x37\x2c\x30\x6c\x30\x2c\x33\x34\x2e\x35\x37\ +\x7a\x22\x20\x69\x64\x3d\x22\x73\x76\x67\x5f\x39\x22\x2f\x3e\x0a\ +\x20\x20\x3c\x2f\x67\x3e\x0a\x20\x20\x3c\x67\x20\x69\x64\x3d\x22\ +\x73\x76\x67\x5f\x31\x30\x22\x3e\x0a\x20\x20\x20\x3c\x70\x6f\x6c\ +\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\ +\x22\x20\x69\x64\x3d\x22\x73\x76\x67\x5f\x31\x31\x22\x20\x70\x6f\ +\x69\x6e\x74\x73\x3d\x22\x32\x33\x38\x2e\x32\x35\x2c\x32\x30\x39\ +\x2e\x39\x34\x30\x30\x30\x32\x34\x34\x31\x34\x30\x36\x32\x35\x20\ +\x31\x37\x38\x2e\x39\x37\x39\x39\x39\x39\x35\x34\x32\x32\x33\x36\ +\x33\x33\x2c\x32\x30\x39\x2e\x39\x34\x30\x30\x30\x32\x34\x34\x31\ +\x34\x30\x36\x32\x35\x20\x31\x37\x38\x2e\x39\x37\x39\x39\x39\x39\ +\x35\x34\x32\x32\x33\x36\x33\x33\x2c\x31\x35\x30\x2e\x36\x36\x39\ +\x39\x39\x38\x31\x36\x38\x39\x34\x35\x33\x20\x32\x33\x38\x2e\x32\ +\x35\x2c\x31\x35\x30\x2e\x36\x36\x39\x39\x39\x38\x31\x36\x38\x39\ +\x34\x35\x33\x20\x22\x2f\x3e\x0a\x20\x20\x20\x3c\x70\x61\x74\x68\ +\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\x20\x64\x3d\ +\x22\x6d\x32\x33\x38\x2e\x32\x35\x2c\x32\x30\x39\x2e\x39\x34\x6c\ +\x2d\x35\x39\x2e\x32\x37\x2c\x30\x6c\x30\x2c\x2d\x35\x39\x2e\x32\ +\x37\x6c\x35\x39\x2e\x32\x37\x2c\x30\x6c\x30\x2c\x35\x39\x2e\x32\ +\x37\x7a\x6d\x2d\x34\x36\x2e\x39\x32\x2c\x2d\x31\x32\x2e\x33\x35\ +\x6c\x33\x34\x2e\x35\x37\x2c\x30\x6c\x30\x2c\x2d\x33\x34\x2e\x35\ +\x37\x6c\x2d\x33\x34\x2e\x35\x37\x2c\x30\x6c\x30\x2c\x33\x34\x2e\ +\x35\x37\x7a\x22\x20\x69\x64\x3d\x22\x73\x76\x67\x5f\x31\x32\x22\ +\x2f\x3e\x0a\x20\x20\x3c\x2f\x67\x3e\x0a\x20\x3c\x2f\x67\x3e\x0a\ +\x3c\x2f\x73\x76\x67\x3e\ +\x00\x00\x07\xaa\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x67\x72\x61\x79\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\ +\x68\x3a\x31\x37\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6c\ +\x69\x6e\x65\x63\x61\x70\x3a\x72\x6f\x75\x6e\x64\x3b\x73\x74\x72\ +\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3a\x72\x6f\x75\ +\x6e\x64\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\x6c\ +\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\x20\ +\x20\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\ +\x6f\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x32\x20\ +\x7b\x66\x69\x6c\x6c\x3a\x23\x43\x38\x38\x43\x34\x36\x7d\x0d\x0a\ +\x20\x20\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\ +\x72\x65\x64\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x33\x20\ +\x7b\x66\x69\x6c\x6c\x3a\x23\x39\x39\x46\x46\x46\x46\x3b\x66\x69\ +\x6c\x6c\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x30\x2e\x35\x30\x31\ +\x39\x36\x31\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\ +\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\ +\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\ +\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\ +\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\ +\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\ +\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\x65\ +\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\ +\x78\x3d\x22\x2d\x30\x2e\x30\x31\x22\x20\x79\x3d\x22\x2d\x30\x22\ +\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x20\ +\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x2f\ +\x3e\x0d\x0a\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\ +\x3d\x22\x66\x69\x6c\x31\x22\x20\x64\x3d\x22\x4d\x37\x36\x2e\x31\ +\x37\x20\x32\x33\x37\x2e\x30\x38\x6c\x2d\x33\x36\x2e\x36\x20\x2d\ +\x36\x33\x2e\x34\x63\x2d\x33\x2e\x38\x34\x2c\x2d\x36\x2e\x34\x37\ +\x20\x2d\x35\x2e\x38\x35\x2c\x2d\x31\x33\x2e\x38\x36\x20\x2d\x35\ +\x2e\x38\x35\x2c\x2d\x32\x31\x2e\x33\x38\x20\x30\x2c\x2d\x32\x33\ +\x2e\x33\x38\x20\x31\x38\x2e\x39\x36\x2c\x2d\x34\x32\x2e\x33\x33\ +\x20\x34\x32\x2e\x33\x34\x2c\x2d\x34\x32\x2e\x33\x33\x20\x32\x33\ +\x2e\x33\x38\x2c\x30\x20\x34\x32\x2e\x33\x33\x2c\x31\x38\x2e\x39\ +\x35\x20\x34\x32\x2e\x33\x33\x2c\x34\x32\x2e\x33\x33\x20\x30\x2c\ +\x37\x2e\x34\x32\x20\x2d\x31\x2e\x39\x35\x2c\x31\x34\x2e\x37\x31\ +\x20\x2d\x35\x2e\x36\x35\x2c\x32\x31\x2e\x31\x34\x6c\x2d\x33\x36\ +\x2e\x35\x37\x20\x36\x33\x2e\x36\x34\x7a\x6d\x2d\x30\x2e\x31\x31\ +\x20\x2d\x36\x37\x2e\x38\x35\x63\x39\x2e\x33\x35\x2c\x30\x20\x31\ +\x36\x2e\x39\x33\x2c\x2d\x37\x2e\x35\x38\x20\x31\x36\x2e\x39\x33\ +\x2c\x2d\x31\x36\x2e\x39\x33\x20\x30\x2c\x2d\x39\x2e\x33\x35\x20\ +\x2d\x37\x2e\x35\x38\x2c\x2d\x31\x36\x2e\x39\x33\x20\x2d\x31\x36\ +\x2e\x39\x33\x2c\x2d\x31\x36\x2e\x39\x33\x20\x2d\x39\x2e\x33\x35\ +\x2c\x30\x20\x2d\x31\x36\x2e\x39\x33\x2c\x37\x2e\x35\x38\x20\x2d\ +\x31\x36\x2e\x39\x33\x2c\x31\x36\x2e\x39\x33\x20\x30\x2c\x39\x2e\ +\x33\x35\x20\x37\x2e\x35\x38\x2c\x31\x36\x2e\x39\x33\x20\x31\x36\ +\x2e\x39\x33\x2c\x31\x36\x2e\x39\x33\x7a\x22\x2f\x3e\x0d\x0a\x20\ +\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\ +\x6c\x32\x22\x20\x64\x3d\x22\x4d\x32\x33\x36\x2e\x33\x34\x20\x32\ +\x32\x32\x2e\x32\x33\x6c\x2d\x33\x34\x2e\x33\x31\x20\x2d\x33\x34\ +\x2e\x33\x32\x63\x2d\x36\x2e\x36\x2c\x2d\x36\x2e\x35\x38\x20\x2d\ +\x36\x2e\x36\x2c\x2d\x31\x37\x2e\x33\x36\x20\x30\x2c\x2d\x32\x33\ +\x2e\x39\x34\x6c\x30\x2e\x30\x31\x20\x2d\x30\x2e\x30\x33\x63\x34\ +\x2e\x33\x39\x2c\x2d\x34\x2e\x33\x39\x20\x34\x2e\x33\x32\x2c\x2d\ +\x31\x31\x2e\x36\x34\x20\x30\x2e\x30\x32\x2c\x2d\x31\x35\x2e\x39\ +\x34\x6c\x2d\x31\x31\x2e\x31\x39\x20\x2d\x31\x31\x2e\x31\x38\x20\ +\x2d\x31\x35\x2e\x39\x37\x20\x31\x35\x2e\x39\x36\x20\x31\x31\x2e\ +\x31\x39\x20\x31\x31\x2e\x31\x39\x63\x34\x2e\x33\x39\x2c\x34\x2e\ +\x33\x39\x20\x31\x31\x2e\x35\x33\x2c\x34\x2e\x33\x39\x20\x31\x35\ +\x2e\x39\x34\x2c\x30\x20\x36\x2e\x35\x38\x2c\x2d\x36\x2e\x36\x20\ +\x31\x37\x2e\x33\x39\x2c\x2d\x36\x2e\x35\x36\x20\x32\x33\x2e\x39\ +\x34\x2c\x30\x6c\x33\x34\x2e\x33\x32\x20\x33\x34\x2e\x33\x31\x63\ +\x36\x2e\x35\x35\x2c\x36\x2e\x35\x36\x20\x36\x2e\x35\x39\x2c\x31\ +\x37\x2e\x33\x36\x20\x30\x2c\x32\x33\x2e\x39\x35\x20\x2d\x36\x2e\ +\x35\x39\x2c\x36\x2e\x35\x39\x20\x2d\x31\x37\x2e\x33\x38\x2c\x36\ +\x2e\x35\x37\x20\x2d\x32\x33\x2e\x39\x35\x2c\x30\x7a\x22\x2f\x3e\ +\x0d\x0a\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\ +\x22\x66\x69\x6c\x33\x22\x20\x64\x3d\x22\x4d\x39\x30\x2e\x35\x36\ +\x20\x31\x33\x38\x2e\x30\x36\x63\x32\x34\x2e\x38\x2c\x32\x34\x2e\ +\x38\x20\x36\x35\x2c\x32\x34\x2e\x38\x20\x38\x39\x2e\x38\x2c\x30\ +\x20\x32\x34\x2e\x38\x2c\x2d\x32\x34\x2e\x38\x20\x32\x34\x2e\x38\ +\x2c\x2d\x36\x35\x20\x30\x2c\x2d\x38\x39\x2e\x38\x20\x2d\x32\x34\ +\x2e\x38\x2c\x2d\x32\x34\x2e\x38\x20\x2d\x36\x35\x2c\x2d\x32\x34\ +\x2e\x38\x20\x2d\x38\x39\x2e\x38\x2c\x30\x20\x2d\x32\x34\x2e\x38\ +\x2c\x32\x34\x2e\x38\x20\x2d\x32\x34\x2e\x38\x2c\x36\x35\x20\x30\ +\x2c\x38\x39\x2e\x38\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x63\x69\ +\x72\x63\x6c\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\ +\x20\x73\x74\x72\x30\x22\x20\x74\x72\x61\x6e\x73\x66\x6f\x72\x6d\ +\x3d\x22\x6d\x61\x74\x72\x69\x78\x28\x30\x2e\x35\x33\x30\x33\x37\ +\x32\x20\x30\x2e\x35\x33\x30\x33\x37\x32\x20\x2d\x30\x2e\x35\x33\ +\x30\x33\x37\x32\x20\x30\x2e\x35\x33\x30\x33\x37\x32\x20\x31\x33\ +\x35\x2e\x34\x36\x20\x39\x33\x2e\x31\x36\x29\x22\x20\x72\x3d\x22\ +\x38\x34\x2e\x36\x36\x22\x2f\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\ +\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x04\x55\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x34\x36\x38\x32\x43\x38\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x36\x2e\x39\x33\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\ +\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x30\ +\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\x6e\x65\x7d\x0d\x0a\x20\x20\ +\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x23\x46\ +\x46\x39\x39\x33\x33\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\ +\x20\x20\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\ +\x65\x66\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\ +\x6d\x61\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\ +\x20\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\x69\x64\x3d\x22\ +\x43\x6f\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\ +\x65\x6c\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\ +\x72\x65\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\ +\x22\x20\x78\x3d\x22\x2d\x30\x22\x20\x79\x3d\x22\x2d\x30\x22\x20\ +\x77\x69\x64\x74\x68\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x20\x68\ +\x65\x69\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x2f\x3e\ +\x0d\x0a\x20\x20\x3c\x65\x6c\x6c\x69\x70\x73\x65\x20\x63\x6c\x61\ +\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\x72\x30\x22\x20\x63\ +\x78\x3d\x22\x31\x33\x35\x2e\x34\x36\x22\x20\x63\x79\x3d\x22\x31\ +\x33\x35\x2e\x34\x36\x22\x20\x72\x78\x3d\x22\x31\x32\x37\x22\x20\ +\x72\x79\x3d\x22\x39\x33\x2e\x31\x33\x22\x2f\x3e\x0d\x0a\x20\x20\ +\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x69\x64\x3d\x22\x5f\x31\x5f\ +\x30\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\ +\x70\x6f\x69\x6e\x74\x73\x3d\x22\x31\x31\x34\x2e\x32\x39\x2c\x31\ +\x31\x34\x2e\x32\x39\x20\x31\x35\x36\x2e\x36\x32\x2c\x31\x31\x34\ +\x2e\x32\x39\x20\x31\x35\x36\x2e\x36\x32\x2c\x31\x35\x36\x2e\x36\ +\x32\x20\x31\x31\x34\x2e\x32\x39\x2c\x31\x35\x36\x2e\x36\x32\x20\ +\x22\x2f\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\ +\x67\x3e\x0d\x0a\ +\x00\x00\x09\xd2\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\ +\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x36\x20\x7b\ +\x66\x69\x6c\x6c\x3a\x23\x30\x30\x39\x39\x33\x33\x7d\x0d\x0a\x20\ +\x20\x20\x20\x2e\x66\x69\x6c\x33\x20\x7b\x66\x69\x6c\x6c\x3a\x23\ +\x33\x33\x33\x33\x33\x33\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\ +\x6c\x32\x20\x7b\x66\x69\x6c\x6c\x3a\x23\x39\x39\x39\x39\x39\x39\ +\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\x69\ +\x6c\x6c\x3a\x23\x45\x36\x45\x36\x45\x36\x7d\x0d\x0a\x20\x20\x20\ +\x20\x2e\x66\x69\x6c\x34\x20\x7b\x66\x69\x6c\x6c\x3a\x72\x65\x64\ +\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x35\x20\x7b\x66\x69\ +\x6c\x6c\x3a\x77\x68\x69\x74\x65\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\ +\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\ +\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\ +\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\ +\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\x69\ +\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\ +\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\ +\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\ +\x69\x6c\x30\x22\x20\x78\x3d\x22\x2d\x30\x2e\x30\x31\x22\x20\x79\ +\x3d\x22\x2d\x30\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\x30\ +\x2e\x39\x33\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\x30\ +\x2e\x39\x33\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x67\x20\x69\x64\x3d\ +\x22\x5f\x32\x31\x30\x33\x36\x33\x37\x34\x33\x36\x31\x32\x38\x22\ +\x3e\x0d\x0a\x20\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\x61\x73\ +\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x78\x3d\x22\x38\x2e\x34\x35\ +\x22\x20\x79\x3d\x22\x35\x38\x2e\x33\x35\x22\x20\x77\x69\x64\x74\ +\x68\x3d\x22\x35\x30\x2e\x38\x22\x20\x68\x65\x69\x67\x68\x74\x3d\ +\x22\x35\x30\x2e\x38\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x72\x65\ +\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\ +\x78\x3d\x22\x31\x32\x37\x2e\x30\x36\x22\x20\x79\x3d\x22\x35\x38\ +\x2e\x33\x35\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x35\x30\x2e\x38\ +\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x35\x30\x2e\x38\x22\x2f\ +\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\ +\x73\x3d\x22\x66\x69\x6c\x32\x22\x20\x64\x3d\x22\x4d\x31\x37\x37\ +\x2e\x38\x36\x20\x31\x30\x39\x2e\x31\x36\x6c\x2d\x30\x2e\x30\x32\ +\x20\x36\x38\x2e\x36\x32\x63\x2d\x30\x2e\x30\x32\x2c\x34\x36\x2e\ +\x37\x38\x20\x2d\x33\x37\x2e\x39\x32\x2c\x38\x34\x2e\x37\x20\x2d\ +\x38\x34\x2e\x36\x38\x2c\x38\x34\x2e\x37\x20\x2d\x34\x36\x2e\x37\ +\x36\x2c\x30\x20\x2d\x38\x34\x2e\x36\x37\x2c\x2d\x33\x37\x2e\x39\ +\x32\x20\x2d\x38\x34\x2e\x36\x39\x2c\x2d\x38\x34\x2e\x37\x6c\x2d\ +\x30\x2e\x30\x32\x20\x2d\x36\x38\x2e\x36\x32\x20\x35\x30\x2e\x37\ +\x39\x20\x2d\x30\x2e\x30\x32\x20\x30\x2e\x30\x31\x20\x36\x30\x2e\ +\x31\x37\x63\x30\x2c\x31\x38\x2e\x37\x33\x20\x31\x35\x2e\x31\x36\ +\x2c\x33\x33\x2e\x39\x33\x20\x33\x33\x2e\x39\x31\x2c\x33\x33\x2e\ +\x39\x33\x20\x31\x38\x2e\x37\x35\x2c\x30\x20\x33\x33\x2e\x38\x39\ +\x2c\x2d\x31\x35\x2e\x33\x31\x20\x33\x33\x2e\x39\x2c\x2d\x33\x33\ +\x2e\x39\x33\x6c\x30\x2e\x30\x32\x20\x2d\x36\x30\x2e\x31\x37\x20\ +\x35\x30\x2e\x37\x38\x20\x30\x2e\x30\x32\x7a\x22\x2f\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x67\x3e\x0d\x0a\x20\x20\x20\x20\x3c\x70\x61\x74\ +\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x33\x22\x20\x64\ +\x3d\x22\x4d\x31\x37\x37\x2e\x38\x36\x20\x35\x38\x2e\x33\x36\x6c\ +\x2d\x30\x2e\x30\x33\x20\x31\x31\x39\x2e\x34\x63\x2d\x30\x2e\x30\ +\x32\x2c\x34\x36\x2e\x37\x38\x20\x2d\x33\x37\x2e\x39\x32\x2c\x38\ +\x34\x2e\x37\x20\x2d\x38\x34\x2e\x36\x38\x2c\x38\x34\x2e\x37\x20\ +\x2d\x34\x36\x2e\x37\x36\x2c\x30\x20\x2d\x38\x34\x2e\x36\x37\x2c\ +\x2d\x33\x37\x2e\x39\x32\x20\x2d\x38\x34\x2e\x36\x39\x2c\x2d\x38\ +\x34\x2e\x37\x6c\x2d\x30\x2e\x30\x31\x20\x2d\x31\x31\x39\x2e\x34\ +\x20\x35\x30\x2e\x37\x39\x20\x2d\x30\x2e\x30\x32\x20\x30\x20\x31\ +\x31\x30\x2e\x39\x35\x63\x30\x2c\x31\x38\x2e\x37\x33\x20\x31\x35\ +\x2e\x31\x36\x2c\x33\x33\x2e\x39\x33\x20\x33\x33\x2e\x39\x31\x2c\ +\x33\x33\x2e\x39\x33\x20\x31\x38\x2e\x37\x35\x2c\x30\x20\x33\x33\ +\x2e\x38\x39\x2c\x2d\x31\x35\x2e\x33\x32\x20\x33\x33\x2e\x39\x2c\ +\x2d\x33\x33\x2e\x39\x33\x6c\x30\x2e\x30\x33\x20\x2d\x31\x31\x30\ +\x2e\x39\x35\x20\x35\x30\x2e\x37\x38\x20\x30\x2e\x30\x32\x7a\x6d\ +\x2d\x38\x2e\x38\x32\x20\x31\x31\x39\x2e\x34\x6c\x30\x2e\x30\x33\ +\x20\x2d\x31\x31\x30\x2e\x36\x31\x20\x2d\x33\x33\x2e\x32\x20\x2d\ +\x30\x2e\x30\x32\x20\x2d\x30\x2e\x30\x33\x20\x31\x30\x32\x2e\x31\ +\x36\x63\x2d\x30\x2e\x30\x31\x2c\x31\x31\x2e\x37\x37\x20\x2d\x34\ +\x2e\x37\x39\x2c\x32\x32\x2e\x35\x20\x2d\x31\x32\x2e\x35\x31\x2c\ +\x33\x30\x2e\x32\x32\x6c\x2d\x30\x2e\x30\x32\x20\x2d\x30\x2e\x30\ +\x32\x63\x2d\x37\x2e\x37\x33\x2c\x37\x2e\x37\x33\x20\x2d\x31\x38\ +\x2e\x34\x2c\x31\x32\x2e\x35\x35\x20\x2d\x33\x30\x2e\x31\x36\x2c\ +\x31\x32\x2e\x35\x35\x20\x2d\x31\x31\x2e\x38\x31\x2c\x30\x20\x2d\ +\x32\x32\x2e\x35\x2c\x2d\x34\x2e\x37\x39\x20\x2d\x33\x30\x2e\x32\ +\x32\x2c\x2d\x31\x32\x2e\x35\x31\x6c\x30\x20\x2d\x30\x2e\x30\x34\ +\x63\x2d\x37\x2e\x37\x33\x2c\x2d\x37\x2e\x37\x34\x20\x2d\x31\x32\ +\x2e\x35\x31\x2c\x2d\x31\x38\x2e\x34\x33\x20\x2d\x31\x32\x2e\x35\ +\x31\x2c\x2d\x33\x30\x2e\x32\x6c\x30\x20\x2d\x31\x30\x32\x2e\x31\ +\x36\x20\x2d\x33\x33\x2e\x31\x38\x20\x30\x2e\x30\x32\x20\x30\x2e\ +\x30\x31\x20\x31\x31\x30\x2e\x36\x31\x63\x30\x2c\x32\x30\x2e\x39\ +\x34\x20\x38\x2e\x35\x31\x2c\x33\x39\x2e\x39\x33\x20\x32\x32\x2e\ +\x32\x33\x2c\x35\x33\x2e\x36\x35\x6c\x30\x2e\x30\x34\x20\x30\x2e\ +\x30\x34\x63\x31\x33\x2e\x36\x39\x2c\x31\x33\x2e\x37\x20\x33\x32\ +\x2e\x36\x37\x2c\x32\x32\x2e\x31\x39\x20\x35\x33\x2e\x36\x33\x2c\ +\x32\x32\x2e\x31\x39\x20\x32\x30\x2e\x39\x33\x2c\x30\x20\x33\x39\ +\x2e\x39\x2c\x2d\x38\x2e\x35\x20\x35\x33\x2e\x36\x32\x2c\x2d\x32\ +\x32\x2e\x32\x32\x20\x31\x33\x2e\x37\x35\x2c\x2d\x31\x33\x2e\x37\ +\x35\x20\x32\x32\x2e\x32\x37\x2c\x2d\x33\x32\x2e\x37\x34\x20\x32\ +\x32\x2e\x32\x37\x2c\x2d\x35\x33\x2e\x36\x36\x7a\x22\x2f\x3e\x0d\ +\x0a\x20\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\x3e\ +\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\ +\x73\x73\x3d\x22\x66\x69\x6c\x34\x22\x20\x70\x6f\x69\x6e\x74\x73\ +\x3d\x22\x31\x35\x36\x2e\x34\x39\x2c\x38\x38\x2e\x30\x33\x20\x31\ +\x38\x35\x2e\x31\x39\x2c\x35\x39\x2e\x33\x33\x20\x31\x35\x36\x2e\ +\x34\x38\x2c\x33\x30\x2e\x36\x31\x20\x31\x38\x32\x2e\x38\x39\x2c\ +\x34\x2e\x32\x20\x32\x31\x31\x2e\x36\x2c\x33\x32\x2e\x39\x32\x20\ +\x32\x34\x30\x2e\x33\x2c\x34\x2e\x32\x32\x20\x32\x36\x36\x2e\x37\ +\x31\x2c\x33\x30\x2e\x36\x33\x20\x32\x33\x38\x2e\x30\x31\x2c\x35\ +\x39\x2e\x33\x33\x20\x32\x36\x36\x2e\x37\x31\x2c\x38\x38\x2e\x30\ +\x33\x20\x32\x34\x30\x2e\x33\x2c\x31\x31\x34\x2e\x34\x34\x20\x32\ +\x31\x31\x2e\x36\x2c\x38\x35\x2e\x37\x34\x20\x31\x38\x32\x2e\x39\ +\x2c\x31\x31\x34\x2e\x34\x34\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\ +\x67\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\ +\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x35\x22\x20\x70\x6f\x69\ +\x6e\x74\x73\x3d\x22\x32\x36\x32\x2e\x34\x35\x2c\x32\x36\x32\x2e\ +\x35\x35\x20\x32\x30\x33\x2e\x31\x38\x2c\x32\x36\x32\x2e\x35\x35\ +\x20\x32\x30\x33\x2e\x31\x38\x2c\x32\x30\x33\x2e\x32\x38\x20\x32\ +\x36\x32\x2e\x34\x35\x2c\x32\x30\x33\x2e\x32\x38\x20\x22\x2f\x3e\ +\x0d\x0a\x20\x20\x20\x3c\x70\x61\x74\x68\x20\x69\x64\x3d\x22\x5f\ +\x31\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x36\x22\x20\ +\x64\x3d\x22\x4d\x32\x36\x32\x2e\x34\x35\x20\x32\x36\x32\x2e\x35\ +\x35\x6c\x2d\x35\x39\x2e\x32\x37\x20\x30\x20\x30\x20\x2d\x35\x39\ +\x2e\x32\x37\x20\x35\x39\x2e\x32\x37\x20\x30\x20\x30\x20\x35\x39\ +\x2e\x32\x37\x7a\x6d\x2d\x34\x36\x2e\x39\x32\x20\x2d\x31\x32\x2e\ +\x33\x35\x6c\x33\x34\x2e\x35\x37\x20\x30\x20\x30\x20\x2d\x33\x34\ +\x2e\x35\x37\x20\x2d\x33\x34\x2e\x35\x37\x20\x30\x20\x30\x20\x33\ +\x34\x2e\x35\x37\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\x3e\ +\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\ +\x0a\ +\x00\x00\x06\x16\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x36\x36\x36\x36\x36\x36\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x37\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3a\x72\x6f\x75\x6e\x64\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\ +\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\ +\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\x6e\ +\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x32\x20\x7b\x66\ +\x69\x6c\x6c\x3a\x23\x30\x30\x39\x39\x33\x33\x7d\x0d\x0a\x20\x20\ +\x20\x20\x2e\x66\x69\x6c\x33\x20\x7b\x66\x69\x6c\x6c\x3a\x23\x39\ +\x39\x39\x39\x39\x39\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\ +\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x77\x68\x69\x74\x65\x7d\x0d\x0a\ +\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\x6c\ +\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\x3c\ +\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\x30\ +\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\ +\x61\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\x72\ +\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\x72\ +\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\x61\ +\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\x78\x3d\x22\x2d\x30\x2e\ +\x30\x31\x22\x20\x79\x3d\x22\x2d\x30\x22\x20\x77\x69\x64\x74\x68\ +\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x20\x68\x65\x69\x67\x68\x74\ +\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\ +\x6c\x69\x6e\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\ +\x20\x73\x74\x72\x30\x22\x20\x78\x31\x3d\x22\x32\x36\x32\x2e\x34\ +\x36\x22\x20\x79\x31\x3d\x22\x32\x36\x32\x2e\x34\x36\x22\x20\x78\ +\x32\x3d\x22\x38\x2e\x34\x35\x22\x20\x79\x32\x3d\x20\x22\x38\x2e\ +\x34\x36\x22\x20\x2f\x3e\x0d\x0a\x20\x20\x3c\x67\x3e\x0d\x0a\x20\ +\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\ +\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\ +\x38\x30\x2e\x33\x37\x2c\x31\x33\x39\x2e\x36\x34\x20\x31\x33\x39\ +\x2e\x36\x35\x2c\x31\x33\x39\x2e\x36\x34\x20\x31\x33\x39\x2e\x36\ +\x35\x2c\x38\x30\x2e\x33\x36\x20\x38\x30\x2e\x33\x37\x2c\x38\x30\ +\x2e\x33\x36\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x61\x74\ +\x68\x20\x69\x64\x3d\x22\x5f\x31\x22\x20\x63\x6c\x61\x73\x73\x3d\ +\x22\x66\x69\x6c\x32\x22\x20\x64\x3d\x22\x4d\x39\x32\x2e\x37\x32\ +\x20\x31\x32\x37\x2e\x32\x39\x6c\x33\x34\x2e\x35\x38\x20\x30\x20\ +\x30\x20\x2d\x33\x34\x2e\x35\x38\x20\x2d\x33\x34\x2e\x35\x38\x20\ +\x30\x20\x30\x20\x33\x34\x2e\x35\x38\x7a\x6d\x2d\x31\x32\x2e\x33\ +\x35\x20\x31\x32\x2e\x33\x35\x6c\x35\x39\x2e\x32\x38\x20\x30\x20\ +\x30\x20\x2d\x35\x39\x2e\x32\x38\x20\x2d\x35\x39\x2e\x32\x38\x20\ +\x30\x20\x30\x20\x35\x39\x2e\x32\x38\x7a\x22\x2f\x3e\x0d\x0a\x20\ +\x20\x3c\x2f\x67\x3e\x0d\x0a\x20\x20\x3c\x67\x3e\x0d\x0a\x20\x20\ +\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\ +\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x38\ +\x2e\x34\x36\x2c\x32\x30\x33\x2e\x30\x39\x20\x36\x37\x2e\x37\x33\ +\x2c\x32\x30\x33\x2e\x30\x39\x20\x36\x37\x2e\x37\x33\x2c\x31\x34\ +\x33\x2e\x38\x32\x20\x38\x2e\x34\x36\x2c\x31\x34\x33\x2e\x38\x32\ +\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x61\x74\x68\x20\x69\ +\x64\x3d\x22\x5f\x31\x5f\x30\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\ +\x66\x69\x6c\x33\x22\x20\x64\x3d\x22\x4d\x32\x30\x2e\x38\x31\x20\ +\x31\x39\x30\x2e\x37\x34\x6c\x33\x34\x2e\x35\x37\x20\x30\x20\x30\ +\x20\x2d\x33\x34\x2e\x35\x37\x20\x2d\x33\x34\x2e\x35\x37\x20\x30\ +\x20\x30\x20\x33\x34\x2e\x35\x37\x7a\x6d\x2d\x31\x32\x2e\x33\x35\ +\x20\x31\x32\x2e\x33\x35\x6c\x35\x39\x2e\x32\x37\x20\x30\x20\x30\ +\x20\x2d\x35\x39\x2e\x32\x37\x20\x2d\x35\x39\x2e\x32\x37\x20\x30\ +\x20\x30\x20\x35\x39\x2e\x32\x37\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\ +\x3c\x2f\x67\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\ +\x76\x67\x3e\x0d\x0a\ +\x00\x00\x04\xc6\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x34\x36\x38\x32\x43\x38\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x37\x2e\x30\x36\x3b\x73\x74\x72\x6f\x6b\x65\ +\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\ +\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x30\x20\ +\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\x6e\x65\x7d\x0d\x0a\x20\x20\x20\ +\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x23\x43\x43\ +\x43\x43\x43\x43\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\ +\x20\x3c\x2f\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\ +\x61\x64\x61\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\ +\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\x61\x20\x69\x64\x3d\x22\x43\ +\x6f\x72\x65\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\ +\x6c\x2d\x4c\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\ +\x65\x63\x74\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\ +\x20\x78\x3d\x22\x2d\x30\x2e\x30\x32\x22\x20\x79\x3d\x22\x2d\x30\ +\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\ +\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\ +\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\ +\x73\x3d\x22\x66\x69\x6c\x31\x20\x73\x74\x72\x30\x22\x20\x64\x3d\ +\x22\x4d\x31\x34\x33\x2e\x39\x33\x20\x39\x37\x2e\x33\x36\x63\x32\ +\x38\x2e\x30\x36\x2c\x30\x20\x35\x30\x2e\x38\x2c\x32\x32\x2e\x37\ +\x34\x20\x35\x30\x2e\x38\x2c\x35\x30\x2e\x38\x20\x30\x2c\x32\x38\ +\x2e\x30\x36\x20\x2d\x32\x32\x2e\x37\x34\x2c\x35\x30\x2e\x38\x20\ +\x2d\x35\x30\x2e\x38\x2c\x35\x30\x2e\x38\x6c\x2d\x36\x37\x2e\x37\ +\x32\x20\x30\x20\x30\x20\x34\x32\x2e\x33\x33\x20\x36\x37\x2e\x37\ +\x32\x20\x30\x63\x35\x31\x2e\x34\x33\x2c\x30\x20\x39\x33\x2e\x31\ +\x33\x2c\x2d\x34\x31\x2e\x37\x20\x39\x33\x2e\x31\x33\x2c\x2d\x39\ +\x33\x2e\x31\x33\x20\x30\x2c\x2d\x35\x31\x2e\x34\x33\x20\x2d\x34\ +\x31\x2e\x37\x2c\x2d\x39\x33\x2e\x31\x33\x20\x2d\x39\x33\x2e\x31\ +\x33\x2c\x2d\x39\x33\x2e\x31\x33\x20\x2d\x30\x2e\x30\x34\x2c\x30\ +\x20\x2d\x30\x2e\x30\x38\x2c\x30\x20\x2d\x30\x2e\x30\x33\x2c\x30\ +\x6c\x2d\x32\x35\x2e\x33\x37\x20\x30\x20\x38\x2e\x34\x36\x20\x2d\ +\x32\x35\x2e\x34\x20\x2d\x39\x33\x2e\x31\x33\x20\x34\x36\x2e\x35\ +\x36\x20\x39\x33\x2e\x31\x33\x20\x34\x36\x2e\x35\x37\x20\x2d\x38\ +\x2e\x34\x36\x20\x2d\x32\x35\x2e\x34\x20\x32\x35\x2e\x34\x20\x30\ +\x7a\x22\x2f\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\ +\x76\x67\x3e\x0d\x0a\ +\x00\x00\x0b\x98\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x34\x36\x38\x32\x43\x38\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x37\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3a\x72\x6f\x75\x6e\x64\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\ +\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\ +\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\x6e\ +\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x35\x20\x7b\x66\ +\x69\x6c\x6c\x3a\x23\x34\x36\x38\x32\x43\x38\x7d\x0d\x0a\x20\x20\ +\x20\x20\x2e\x66\x69\x6c\x34\x20\x7b\x66\x69\x6c\x6c\x3a\x23\x39\ +\x39\x39\x39\x39\x39\x3b\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\ +\x6e\x6f\x6e\x7a\x65\x72\x6f\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\ +\x69\x6c\x32\x20\x7b\x66\x69\x6c\x6c\x3a\x75\x72\x6c\x28\x23\x69\ +\x64\x30\x29\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x31\x20\ +\x7b\x66\x69\x6c\x6c\x3a\x75\x72\x6c\x28\x23\x69\x64\x31\x29\x7d\ +\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x33\x20\x7b\x66\x69\x6c\ +\x6c\x3a\x75\x72\x6c\x28\x23\x69\x64\x32\x29\x7d\x0d\x0a\x20\x20\ +\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\x6c\x65\x3e\ +\x0d\x0a\x20\x20\x3c\x6c\x69\x6e\x65\x61\x72\x47\x72\x61\x64\x69\ +\x65\x6e\x74\x20\x69\x64\x3d\x22\x69\x64\x30\x22\x20\x67\x72\x61\ +\x64\x69\x65\x6e\x74\x55\x6e\x69\x74\x73\x3d\x22\x75\x73\x65\x72\ +\x53\x70\x61\x63\x65\x4f\x6e\x55\x73\x65\x22\x20\x78\x31\x3d\x22\ +\x32\x33\x39\x2e\x30\x34\x22\x20\x79\x31\x3d\x22\x31\x2e\x30\x35\ +\x22\x20\x78\x32\x3d\x22\x32\x36\x38\x2e\x39\x37\x22\x20\x79\x32\ +\x3d\x22\x33\x30\x2e\x39\x37\x22\x3e\x0d\x0a\x20\x20\x20\x3c\x73\ +\x74\x6f\x70\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\x30\x22\x20\x73\ +\x74\x79\x6c\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\ +\x74\x79\x3a\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\ +\x3a\x23\x32\x34\x32\x39\x32\x45\x22\x2f\x3e\x0d\x0a\x20\x20\x20\ +\x3c\x73\x74\x6f\x70\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\x30\x2e\ +\x33\x32\x31\x35\x36\x39\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\ +\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\x20\x73\ +\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x42\x30\x42\x46\x42\ +\x46\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x73\x74\x6f\x70\x20\x6f\ +\x66\x66\x73\x65\x74\x3d\x22\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\ +\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\ +\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x32\x34\x32\ +\x39\x32\x45\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x6c\x69\x6e\x65\ +\x61\x72\x47\x72\x61\x64\x69\x65\x6e\x74\x3e\x0d\x0a\x20\x20\x3c\ +\x6c\x69\x6e\x65\x61\x72\x47\x72\x61\x64\x69\x65\x6e\x74\x20\x69\ +\x64\x3d\x22\x69\x64\x31\x22\x20\x67\x72\x61\x64\x69\x65\x6e\x74\ +\x55\x6e\x69\x74\x73\x3d\x22\x75\x73\x65\x72\x53\x70\x61\x63\x65\ +\x4f\x6e\x55\x73\x65\x22\x20\x78\x31\x3d\x22\x31\x37\x32\x2e\x36\ +\x38\x22\x20\x79\x31\x3d\x22\x31\x33\x34\x2e\x34\x37\x22\x20\x78\ +\x32\x3d\x22\x31\x33\x36\x2e\x34\x34\x22\x20\x79\x32\x3d\x22\x38\ +\x35\x2e\x39\x31\x22\x3e\x0d\x0a\x20\x20\x20\x3c\x73\x74\x6f\x70\ +\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\x30\x22\x20\x73\x74\x79\x6c\ +\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\ +\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x46\ +\x41\x39\x45\x30\x44\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x73\x74\ +\x6f\x70\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\x30\x2e\x33\x32\x31\ +\x35\x36\x39\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x74\x6f\x70\ +\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\x20\x73\x74\x6f\x70\ +\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x46\x46\x44\x37\x39\x36\x22\x2f\ +\x3e\x0d\x0a\x20\x20\x20\x3c\x73\x74\x6f\x70\x20\x6f\x66\x66\x73\ +\x65\x74\x3d\x22\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x74\ +\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\x20\x73\x74\ +\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x44\x39\x37\x33\x30\x30\ +\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x6c\x69\x6e\x65\x61\x72\x47\ +\x72\x61\x64\x69\x65\x6e\x74\x3e\x0d\x0a\x20\x20\x3c\x6c\x69\x6e\ +\x65\x61\x72\x47\x72\x61\x64\x69\x65\x6e\x74\x20\x69\x64\x3d\x22\ +\x69\x64\x32\x22\x20\x67\x72\x61\x64\x69\x65\x6e\x74\x55\x6e\x69\ +\x74\x73\x3d\x22\x75\x73\x65\x72\x53\x70\x61\x63\x65\x4f\x6e\x55\ +\x73\x65\x22\x20\x78\x31\x3d\x22\x31\x37\x38\x2e\x32\x31\x22\x20\ +\x79\x31\x3d\x22\x36\x30\x2e\x38\x36\x22\x20\x78\x32\x3d\x22\x32\ +\x30\x38\x2e\x32\x31\x22\x20\x79\x32\x3d\x22\x38\x39\x2e\x38\x36\ +\x22\x3e\x0d\x0a\x20\x20\x20\x3c\x73\x74\x6f\x70\x20\x6f\x66\x66\ +\x73\x65\x74\x3d\x22\x30\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\ +\x74\x6f\x70\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\x20\x73\ +\x74\x6f\x70\x2d\x63\x6f\x6c\x6f\x72\x3a\x23\x33\x38\x32\x42\x32\ +\x36\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x73\x74\x6f\x70\x20\x6f\ +\x66\x66\x73\x65\x74\x3d\x22\x30\x2e\x33\x32\x31\x35\x36\x39\x22\ +\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\x70\x61\ +\x63\x69\x74\x79\x3a\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\x6f\x6c\ +\x6f\x72\x3a\x23\x45\x33\x45\x33\x44\x45\x22\x2f\x3e\x0d\x0a\x20\ +\x20\x20\x3c\x73\x74\x6f\x70\x20\x6f\x66\x66\x73\x65\x74\x3d\x22\ +\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x74\x6f\x70\x2d\x6f\ +\x70\x61\x63\x69\x74\x79\x3a\x31\x3b\x20\x73\x74\x6f\x70\x2d\x63\ +\x6f\x6c\x6f\x72\x3a\x23\x33\x38\x32\x42\x32\x36\x22\x2f\x3e\x0d\ +\x0a\x20\x20\x3c\x2f\x6c\x69\x6e\x65\x61\x72\x47\x72\x61\x64\x69\ +\x65\x6e\x74\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\ +\x20\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\ +\x30\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\ +\x61\x64\x61\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\ +\x6f\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\ +\x65\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\x65\x63\x74\x20\x63\ +\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\x78\x3d\x22\x2d\ +\x30\x2e\x30\x31\x22\x20\x79\x3d\x22\x2d\x30\x22\x20\x77\x69\x64\ +\x74\x68\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x20\x68\x65\x69\x67\ +\x68\x74\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x2f\x3e\x0d\x0a\x20\ +\x20\x3c\x67\x20\x69\x64\x3d\x22\x5f\x32\x37\x37\x38\x35\x32\x35\ +\x38\x36\x31\x38\x30\x38\x22\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x61\ +\x74\x68\x20\x69\x64\x3d\x22\x70\x61\x74\x68\x39\x22\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x64\x3d\x22\x4d\x38\ +\x37\x2e\x33\x36\x20\x31\x34\x30\x2e\x36\x6c\x30\x2e\x30\x33\x20\ +\x2d\x35\x2e\x33\x39\x63\x32\x36\x2e\x34\x39\x2c\x2d\x32\x31\x2e\ +\x37\x31\x20\x33\x33\x2e\x32\x35\x2c\x2d\x32\x33\x2e\x38\x38\x20\ +\x35\x35\x2e\x32\x32\x2c\x2d\x34\x38\x2e\x37\x20\x31\x34\x2e\x36\ +\x37\x2c\x2d\x31\x36\x2e\x35\x35\x20\x33\x35\x2e\x39\x36\x2c\x2d\ +\x31\x33\x2e\x38\x37\x20\x34\x35\x2e\x37\x38\x2c\x2d\x33\x2e\x33\ +\x32\x20\x39\x2e\x34\x2c\x31\x30\x2e\x30\x38\x20\x38\x2e\x39\x36\ +\x2c\x32\x37\x2e\x34\x37\x20\x2d\x30\x2e\x34\x2c\x33\x38\x2e\x36\ +\x32\x20\x2d\x39\x2e\x35\x2c\x31\x31\x2e\x32\x38\x20\x2d\x32\x32\ +\x2e\x33\x31\x2c\x31\x35\x2e\x36\x32\x20\x2d\x33\x37\x2e\x30\x35\ +\x2c\x31\x35\x2e\x37\x32\x6c\x2d\x36\x33\x2e\x35\x38\x20\x33\x2e\ +\x30\x37\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x6f\x6c\x79\ +\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\ +\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x32\x33\x39\x2e\x30\x34\x2c\ +\x31\x2e\x30\x35\x20\x31\x37\x39\x2e\x31\x37\x2c\x36\x30\x2e\x39\ +\x31\x20\x32\x30\x39\x2e\x31\x2c\x39\x30\x2e\x38\x33\x20\x32\x36\ +\x38\x2e\x39\x37\x2c\x33\x30\x2e\x39\x38\x20\x22\x2f\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\ +\x73\x3d\x22\x66\x69\x6c\x33\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\ +\x22\x31\x38\x33\x2e\x34\x2c\x36\x35\x2e\x31\x33\x20\x31\x37\x34\ +\x2e\x32\x39\x2c\x37\x34\x2e\x32\x34\x20\x31\x39\x35\x2e\x37\x36\ +\x2c\x39\x35\x2e\x37\x31\x20\x32\x30\x34\x2e\x38\x37\x2c\x38\x36\ +\x2e\x36\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\ +\x20\x20\x3c\x67\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x61\x74\x68\x20\ +\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x34\x22\x20\x64\x3d\x22\ +\x4d\x31\x39\x2e\x37\x37\x20\x31\x32\x36\x2e\x39\x35\x6c\x30\x20\ +\x31\x33\x35\x2e\x34\x35\x20\x2d\x31\x34\x2e\x31\x32\x20\x30\x20\ +\x30\x20\x2d\x31\x33\x35\x2e\x34\x35\x20\x31\x34\x2e\x31\x32\x20\ +\x30\x7a\x6d\x32\x34\x35\x2e\x35\x31\x20\x30\x2e\x30\x31\x6c\x30\ +\x20\x31\x33\x35\x2e\x34\x36\x20\x2d\x31\x34\x2e\x31\x32\x20\x30\ +\x20\x30\x20\x2d\x31\x33\x35\x2e\x34\x36\x20\x31\x34\x2e\x31\x32\ +\x20\x30\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\x3e\x0d\x0a\ +\x20\x20\x3c\x6c\x69\x6e\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\ +\x69\x6c\x30\x20\x73\x74\x72\x30\x22\x20\x78\x31\x3d\x22\x35\x35\ +\x2e\x38\x31\x22\x20\x79\x31\x3d\x22\x31\x36\x35\x2e\x30\x37\x22\ +\x20\x78\x32\x3d\x22\x32\x31\x35\x2e\x31\x32\x22\x20\x79\x32\x3d\ +\x20\x22\x31\x36\x35\x2e\x30\x37\x22\x20\x2f\x3e\x0d\x0a\x20\x20\ +\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\ +\x66\x69\x6c\x35\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x31\x38\ +\x2e\x35\x37\x2c\x31\x36\x35\x2e\x30\x37\x20\x34\x32\x2e\x34\x2c\ +\x31\x35\x31\x2e\x33\x31\x20\x36\x36\x2e\x32\x33\x2c\x31\x33\x37\ +\x2e\x35\x35\x20\x36\x36\x2e\x32\x33\x2c\x31\x36\x35\x2e\x30\x37\ +\x20\x36\x36\x2e\x32\x33\x2c\x31\x39\x32\x2e\x35\x38\x20\x34\x32\ +\x2e\x34\x2c\x31\x37\x38\x2e\x38\x33\x20\x22\x2f\x3e\x0d\x0a\x20\ +\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\ +\x22\x66\x69\x6c\x35\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x32\ +\x35\x32\x2e\x33\x35\x2c\x31\x36\x35\x2e\x30\x37\x20\x32\x32\x38\ +\x2e\x35\x32\x2c\x31\x37\x38\x2e\x38\x33\x20\x32\x30\x34\x2e\x36\ +\x39\x2c\x31\x39\x32\x2e\x35\x39\x20\x32\x30\x34\x2e\x36\x39\x2c\ +\x31\x36\x35\x2e\x30\x37\x20\x32\x30\x34\x2e\x36\x39\x2c\x31\x33\ +\x37\x2e\x35\x36\x20\x32\x32\x38\x2e\x35\x32\x2c\x31\x35\x31\x2e\ +\x33\x31\x20\x22\x2f\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\ +\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x1a\x10\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x23\x42\x33\x42\x33\x42\x33\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\ +\x69\x64\x74\x68\x3a\x31\x37\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\ +\x65\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\ +\x39\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x73\x74\x72\x31\ +\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\x23\x34\x36\x38\x32\x43\x38\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x31\x37\ +\x2e\x36\x34\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\ +\x6f\x69\x6e\x3a\x72\x6f\x75\x6e\x64\x3b\x73\x74\x72\x6f\x6b\x65\ +\x2d\x6d\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\ +\x32\x35\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x30\x20\ +\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\x6e\x65\x7d\x0d\x0a\x20\x20\x20\ +\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x67\x72\x61\ +\x79\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\ +\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\x73\x3e\ +\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\x64\x61\ +\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\ +\x65\x74\x61\x64\x61\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\x72\x65\ +\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\x2d\x4c\ +\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\x65\x63\x74\ +\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\x78\x3d\ +\x22\x2d\x30\x2e\x30\x31\x22\x20\x79\x3d\x22\x2d\x30\x22\x20\x77\ +\x69\x64\x74\x68\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x20\x68\x65\ +\x69\x67\x68\x74\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x2f\x3e\x0d\ +\x0a\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\ +\x66\x69\x6c\x30\x20\x73\x74\x72\x30\x22\x20\x64\x3d\x22\x4d\x32\ +\x34\x35\x2e\x35\x32\x20\x38\x33\x2e\x38\x6c\x30\x20\x2d\x31\x37\ +\x2e\x30\x32\x6d\x30\x20\x34\x36\x2e\x36\x33\x6c\x30\x20\x2d\x31\ +\x37\x2e\x30\x33\x6d\x30\x20\x2d\x37\x31\x2e\x37\x38\x6c\x30\x20\ +\x2d\x31\x37\x2e\x30\x34\x6d\x30\x20\x34\x36\x2e\x36\x34\x6c\x30\ +\x20\x2d\x31\x37\x2e\x30\x33\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x6c\ +\x69\x6e\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\ +\x73\x74\x72\x31\x22\x20\x78\x31\x3d\x22\x32\x34\x35\x2e\x34\x38\ +\x22\x20\x79\x31\x3d\x22\x31\x32\x36\x2e\x31\x32\x22\x20\x78\x32\ +\x3d\x22\x32\x34\x35\x2e\x35\x34\x22\x20\x79\x32\x3d\x20\x22\x32\ +\x36\x32\x2e\x34\x37\x22\x20\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x61\ +\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\ +\x64\x3d\x22\x4d\x31\x32\x38\x2e\x30\x32\x20\x31\x32\x31\x2e\x30\ +\x32\x63\x2d\x31\x2e\x39\x34\x2c\x32\x2e\x34\x36\x20\x2d\x35\x2e\ +\x32\x31\x2c\x33\x2e\x34\x37\x20\x2d\x38\x2c\x31\x2e\x33\x33\x20\ +\x2d\x32\x2e\x33\x38\x2c\x2d\x31\x2e\x38\x31\x20\x2d\x33\x2e\x33\ +\x32\x2c\x2d\x35\x2e\x30\x36\x20\x2d\x31\x2e\x32\x37\x2c\x2d\x38\ +\x2e\x30\x36\x20\x32\x2e\x35\x32\x2c\x2d\x33\x2e\x36\x38\x20\x39\ +\x2e\x33\x32\x2c\x2d\x32\x2e\x38\x31\x20\x31\x30\x2e\x31\x38\x2c\ +\x32\x2e\x33\x32\x20\x30\x2e\x32\x32\x2c\x31\x2e\x33\x34\x20\x30\ +\x2e\x31\x39\x2c\x33\x2e\x30\x32\x20\x2d\x30\x2e\x39\x31\x2c\x34\ +\x2e\x34\x31\x7a\x6d\x2d\x33\x37\x2e\x38\x38\x20\x2d\x35\x38\x2e\ +\x33\x34\x63\x2d\x32\x2e\x37\x34\x2c\x2d\x31\x2e\x34\x31\x20\x2d\ +\x33\x2e\x37\x33\x2c\x2d\x32\x2e\x32\x38\x20\x2d\x35\x2e\x38\x31\ +\x2c\x2d\x34\x2e\x32\x39\x20\x2d\x30\x2e\x36\x32\x2c\x2d\x30\x2e\ +\x36\x20\x2d\x30\x2e\x38\x37\x2c\x2d\x30\x2e\x39\x31\x20\x2d\x31\ +\x2e\x33\x39\x2c\x2d\x31\x2e\x34\x39\x20\x2d\x32\x2e\x38\x33\x2c\ +\x2d\x33\x2e\x32\x33\x20\x2d\x34\x2e\x35\x37\x2c\x2d\x37\x2e\x35\ +\x34\x20\x2d\x35\x2e\x30\x39\x2c\x2d\x31\x31\x2e\x37\x31\x6c\x2d\ +\x30\x2e\x31\x36\x20\x2d\x33\x2e\x36\x39\x63\x30\x2e\x33\x31\x2c\ +\x2d\x34\x2e\x32\x34\x20\x32\x2e\x34\x31\x2c\x2d\x39\x2e\x31\x34\ +\x20\x35\x2e\x39\x39\x2c\x2d\x31\x31\x2e\x35\x39\x20\x32\x2e\x33\ +\x37\x2c\x2d\x31\x2e\x36\x31\x20\x32\x2e\x36\x34\x2c\x2d\x31\x2e\ +\x33\x34\x20\x34\x2e\x37\x35\x2c\x2d\x32\x2e\x32\x35\x20\x30\x2e\ +\x37\x35\x2c\x2d\x30\x2e\x33\x32\x20\x30\x2e\x33\x36\x2c\x2d\x30\ +\x2e\x32\x20\x31\x2e\x32\x2c\x2d\x30\x2e\x32\x37\x20\x31\x2e\x34\ +\x33\x2c\x2d\x30\x2e\x31\x34\x20\x31\x2e\x36\x37\x2c\x2d\x30\x2e\ +\x34\x37\x20\x34\x2e\x30\x35\x2c\x2d\x30\x2e\x31\x34\x20\x33\x2e\ +\x35\x39\x2c\x30\x2e\x34\x38\x20\x33\x2e\x39\x38\x2c\x30\x2e\x35\ +\x33\x20\x37\x2e\x32\x33\x2c\x32\x2e\x32\x20\x32\x2e\x30\x36\x2c\ +\x31\x2e\x30\x37\x20\x33\x2e\x36\x31\x2c\x32\x2e\x35\x36\x20\x35\ +\x2e\x31\x36\x2c\x34\x2e\x30\x34\x20\x30\x2e\x36\x2c\x30\x2e\x35\ +\x37\x20\x30\x2e\x37\x32\x2c\x30\x2e\x38\x39\x20\x31\x2e\x32\x33\ +\x2c\x31\x2e\x35\x20\x32\x2e\x36\x32\x2c\x33\x2e\x31\x34\x20\x34\ +\x2c\x36\x2e\x33\x32\x20\x34\x2e\x37\x33\x2c\x31\x30\x2e\x31\x31\ +\x20\x30\x2e\x37\x39\x2c\x34\x2e\x30\x37\x20\x30\x2e\x34\x38\x2c\ +\x38\x2e\x30\x37\x20\x2d\x31\x2e\x36\x32\x2c\x31\x32\x2e\x31\x36\ +\x20\x2d\x30\x2e\x34\x34\x2c\x30\x2e\x38\x35\x20\x2d\x31\x2e\x33\ +\x31\x2c\x31\x2e\x38\x37\x20\x2d\x31\x2e\x38\x38\x2c\x32\x2e\x36\ +\x34\x20\x2d\x30\x2e\x38\x2c\x31\x2e\x30\x38\x20\x2d\x31\x2e\x32\ +\x33\x2c\x31\x2e\x33\x34\x20\x2d\x32\x2e\x31\x35\x2c\x32\x2e\x30\ +\x34\x20\x2d\x31\x2e\x31\x31\x2c\x30\x2e\x38\x34\x20\x2d\x33\x2e\ +\x33\x36\x2c\x31\x2e\x37\x39\x20\x2d\x34\x2e\x37\x36\x2c\x32\x2e\ +\x31\x39\x20\x2d\x33\x2e\x35\x36\x2c\x31\x2e\x30\x33\x20\x2d\x37\ +\x2e\x39\x37\x2c\x30\x2e\x33\x36\x20\x2d\x31\x31\x2e\x34\x38\x2c\ +\x2d\x31\x2e\x34\x35\x7a\x6d\x2d\x35\x31\x2e\x35\x34\x20\x38\x38\ +\x2e\x30\x37\x6c\x2d\x31\x2e\x35\x34\x20\x2d\x30\x2e\x37\x39\x63\ +\x2d\x34\x2e\x38\x37\x2c\x2d\x32\x2e\x35\x20\x2d\x38\x2e\x33\x35\ +\x2c\x2d\x38\x2e\x34\x34\x20\x2d\x38\x2e\x32\x36\x2c\x2d\x31\x33\ +\x2e\x34\x33\x20\x30\x2e\x30\x36\x2c\x2d\x33\x2e\x33\x20\x31\x2e\ +\x37\x38\x2c\x2d\x37\x2e\x38\x36\x20\x34\x2e\x34\x33\x2c\x2d\x31\ +\x30\x2e\x31\x38\x20\x31\x2e\x31\x2c\x2d\x30\x2e\x39\x35\x20\x31\ +\x2e\x36\x38\x2c\x2d\x31\x2e\x37\x34\x20\x32\x2e\x39\x36\x2c\x2d\ +\x32\x2e\x35\x31\x20\x30\x2e\x35\x35\x2c\x2d\x30\x2e\x33\x34\x20\ +\x30\x2e\x39\x38\x2c\x2d\x30\x2e\x36\x33\x20\x31\x2e\x35\x32\x2c\ +\x2d\x30\x2e\x39\x35\x20\x31\x2e\x31\x2c\x2d\x30\x2e\x36\x36\x20\ +\x33\x2e\x38\x31\x2c\x2d\x31\x2e\x38\x32\x20\x35\x2e\x31\x35\x2c\ +\x2d\x32\x2e\x30\x36\x20\x33\x2e\x35\x33\x2c\x2d\x30\x2e\x36\x32\ +\x20\x32\x2e\x39\x39\x2c\x2d\x30\x2e\x39\x33\x20\x37\x2e\x33\x38\ +\x2c\x2d\x30\x2e\x37\x34\x20\x32\x2e\x38\x35\x2c\x30\x2e\x31\x32\ +\x20\x36\x2e\x30\x38\x2c\x31\x2e\x30\x32\x20\x39\x2e\x30\x33\x2c\ +\x32\x2e\x35\x34\x20\x31\x2e\x31\x36\x2c\x30\x2e\x36\x20\x31\x2e\ +\x38\x33\x2c\x31\x2e\x31\x39\x20\x32\x2e\x39\x33\x2c\x31\x2e\x39\ +\x31\x20\x31\x2e\x30\x32\x2c\x30\x2e\x36\x37\x20\x31\x2e\x34\x34\ +\x2c\x31\x2e\x33\x38\x20\x32\x2e\x31\x36\x2c\x32\x2e\x30\x39\x20\ +\x32\x2e\x32\x38\x2c\x32\x2e\x33\x32\x20\x33\x2e\x39\x39\x2c\x36\ +\x2e\x37\x34\x20\x33\x2e\x37\x33\x2c\x39\x2e\x39\x38\x20\x2d\x30\ +\x2e\x31\x39\x2c\x32\x2e\x34\x34\x20\x2d\x30\x2e\x36\x32\x2c\x34\ +\x2e\x30\x35\x20\x2d\x31\x2e\x38\x31\x2c\x36\x2e\x33\x31\x20\x2d\ +\x32\x2e\x35\x2c\x34\x2e\x38\x31\x20\x2d\x37\x2e\x32\x31\x2c\x37\ +\x2e\x37\x20\x2d\x31\x32\x2e\x31\x38\x2c\x39\x2e\x30\x33\x20\x2d\ +\x31\x2e\x30\x38\x2c\x30\x2e\x32\x39\x20\x2d\x32\x2e\x33\x33\x2c\ +\x30\x2e\x36\x34\x20\x2d\x33\x2e\x35\x31\x2c\x30\x2e\x37\x34\x20\ +\x2d\x30\x2e\x35\x39\x2c\x30\x2e\x30\x35\x20\x2d\x31\x2e\x33\x31\ +\x2c\x30\x2e\x30\x36\x20\x2d\x31\x2e\x38\x31\x2c\x30\x2e\x30\x37\ +\x20\x2d\x32\x2e\x30\x38\x2c\x30\x2e\x30\x33\x20\x2d\x33\x2e\x38\ +\x37\x2c\x2d\x30\x2e\x30\x38\x20\x2d\x35\x2e\x38\x37\x2c\x2d\x30\ +\x2e\x35\x35\x20\x2d\x30\x2e\x37\x39\x2c\x2d\x30\x2e\x31\x38\x20\ +\x2d\x31\x2e\x34\x33\x2c\x2d\x30\x2e\x35\x20\x2d\x32\x2e\x32\x31\ +\x2c\x2d\x30\x2e\x37\x31\x20\x2d\x30\x2e\x36\x34\x2c\x2d\x30\x2e\ +\x31\x38\x20\x2d\x30\x2e\x31\x33\x2c\x30\x2e\x32\x37\x20\x2d\x32\ +\x2e\x31\x2c\x2d\x30\x2e\x37\x35\x7a\x6d\x36\x37\x2e\x35\x38\x20\ +\x2d\x37\x31\x2e\x35\x38\x63\x30\x2e\x35\x32\x2c\x31\x2e\x33\x35\ +\x20\x37\x2e\x32\x35\x2c\x31\x30\x2e\x34\x33\x20\x38\x2e\x33\x36\ +\x2c\x31\x31\x2e\x38\x37\x6c\x36\x2e\x32\x34\x20\x38\x2e\x38\x32\ +\x63\x32\x2e\x39\x36\x2c\x33\x2e\x33\x39\x20\x31\x2e\x32\x39\x2c\ +\x33\x2e\x32\x37\x20\x34\x2e\x35\x37\x2c\x33\x2e\x32\x33\x6c\x33\ +\x30\x2e\x39\x33\x20\x2d\x31\x2e\x34\x33\x63\x31\x2e\x33\x36\x2c\ +\x2d\x30\x2e\x30\x39\x20\x32\x2e\x35\x37\x2c\x2d\x30\x2e\x31\x20\ +\x33\x2e\x38\x33\x2c\x2d\x30\x2e\x31\x35\x20\x37\x2e\x31\x38\x2c\ +\x2d\x30\x2e\x32\x38\x20\x31\x32\x2e\x34\x33\x2c\x2d\x31\x2e\x30\ +\x31\x20\x31\x39\x2e\x37\x34\x2c\x30\x2e\x36\x37\x20\x31\x2e\x33\ +\x34\x2c\x30\x2e\x33\x31\x20\x33\x2e\x31\x32\x2c\x30\x2e\x39\x39\ +\x20\x34\x2e\x32\x38\x2c\x31\x2e\x32\x31\x20\x31\x2e\x33\x36\x2c\ +\x30\x2e\x32\x36\x20\x33\x2e\x30\x39\x2c\x31\x2e\x31\x38\x20\x34\ +\x2e\x35\x33\x2c\x31\x2e\x37\x20\x35\x2e\x38\x37\x2c\x32\x2e\x31\ +\x33\x20\x31\x32\x2e\x34\x36\x2c\x36\x2e\x35\x35\x20\x31\x37\x2e\ +\x33\x32\x2c\x39\x2e\x38\x32\x6c\x37\x2e\x33\x20\x35\x2e\x35\x32\ +\x63\x31\x2e\x30\x38\x2c\x30\x2e\x39\x32\x20\x32\x2e\x33\x34\x2c\ +\x31\x2e\x38\x31\x20\x33\x2e\x34\x33\x2c\x32\x2e\x37\x36\x20\x30\ +\x2e\x35\x35\x2c\x30\x2e\x34\x38\x20\x31\x2e\x30\x35\x2c\x30\x2e\ +\x39\x35\x20\x31\x2e\x37\x2c\x31\x2e\x34\x20\x30\x2e\x34\x2c\x30\ +\x2e\x32\x38\x20\x30\x2e\x36\x2c\x30\x2e\x34\x32\x20\x30\x2e\x39\ +\x34\x2c\x30\x2e\x36\x39\x20\x30\x2e\x34\x2c\x30\x2e\x33\x20\x30\ +\x2e\x37\x2c\x30\x2e\x34\x32\x20\x30\x2e\x37\x35\x2c\x30\x2e\x38\ +\x34\x20\x2d\x30\x2e\x35\x36\x2c\x30\x2e\x35\x31\x20\x2d\x34\x2e\ +\x32\x34\x2c\x30\x2e\x32\x31\x20\x2d\x34\x2e\x39\x31\x2c\x30\x2e\ +\x31\x38\x20\x2d\x30\x2e\x39\x38\x2c\x2d\x30\x2e\x30\x34\x20\x2d\ +\x31\x2e\x35\x39\x2c\x30\x2e\x30\x33\x20\x2d\x32\x2e\x34\x34\x2c\ +\x30\x2e\x30\x36\x20\x2d\x30\x2e\x39\x33\x2c\x30\x2e\x30\x35\x20\ +\x2d\x31\x2e\x36\x31\x2c\x30\x20\x2d\x32\x2e\x36\x31\x2c\x2d\x30\ +\x2e\x30\x36\x6c\x2d\x35\x2e\x30\x35\x20\x2d\x30\x2e\x30\x31\x63\ +\x2d\x30\x2e\x38\x34\x2c\x30\x2e\x30\x31\x20\x2d\x31\x2e\x35\x38\ +\x2c\x30\x2e\x30\x31\x20\x2d\x32\x2e\x34\x36\x2c\x30\x2e\x30\x32\ +\x20\x2d\x30\x2e\x38\x34\x2c\x30\x2e\x30\x31\x20\x2d\x31\x2e\x37\ +\x2c\x30\x2e\x30\x36\x20\x2d\x32\x2e\x35\x35\x2c\x30\x2e\x30\x34\ +\x20\x2d\x30\x2e\x38\x37\x2c\x2d\x30\x2e\x30\x31\x20\x2d\x31\x2e\ +\x37\x35\x2c\x2d\x30\x2e\x31\x37\x20\x2d\x32\x2e\x35\x36\x2c\x2d\ +\x30\x2e\x31\x37\x20\x2d\x32\x2e\x34\x36\x2c\x2d\x30\x2e\x30\x31\ +\x20\x2d\x34\x2e\x39\x31\x2c\x30\x2e\x32\x34\x20\x2d\x37\x2e\x34\ +\x38\x2c\x30\x2e\x30\x39\x20\x2d\x33\x2e\x37\x34\x2c\x2d\x30\x2e\ +\x32\x31\x20\x2d\x31\x31\x2e\x30\x37\x2c\x2d\x30\x2e\x30\x35\x20\ +\x2d\x31\x35\x2e\x30\x33\x2c\x2d\x30\x2e\x30\x31\x20\x2d\x31\x2e\ +\x36\x39\x2c\x30\x2e\x30\x32\x20\x2d\x33\x2e\x34\x37\x2c\x2d\x30\ +\x2e\x30\x39\x20\x2d\x35\x2e\x31\x31\x2c\x2d\x30\x2e\x31\x6c\x2d\ +\x31\x38\x2e\x38\x20\x2d\x30\x2e\x30\x36\x63\x2d\x30\x2e\x34\x33\ +\x2c\x30\x20\x2d\x30\x2e\x38\x39\x2c\x2d\x30\x2e\x30\x36\x20\x2d\ +\x31\x2e\x33\x32\x2c\x2d\x30\x2e\x30\x36\x20\x2d\x30\x2e\x33\x38\ +\x2c\x30\x2e\x30\x31\x20\x2d\x30\x2e\x36\x38\x2c\x30\x2e\x31\x33\ +\x20\x2d\x31\x2e\x31\x34\x2c\x30\x2e\x31\x31\x6c\x2d\x34\x2e\x31\ +\x33\x20\x2d\x30\x2e\x31\x33\x63\x2d\x30\x2e\x30\x33\x2c\x30\x20\ +\x2d\x30\x2e\x30\x38\x2c\x30\x2e\x30\x31\x20\x2d\x30\x2e\x31\x2c\ +\x30\x2e\x30\x31\x20\x2d\x30\x2e\x30\x34\x2c\x30\x20\x2d\x30\x2e\ +\x31\x37\x2c\x30\x2e\x30\x32\x20\x2d\x30\x2e\x32\x31\x2c\x30\x2e\ +\x30\x32\x6c\x2d\x31\x2e\x39\x35\x20\x30\x63\x2d\x30\x2e\x35\x2c\ +\x2d\x30\x2e\x30\x35\x20\x2d\x30\x2e\x38\x2c\x2d\x30\x2e\x30\x31\ +\x20\x2d\x31\x2e\x32\x35\x2c\x30\x2e\x30\x32\x20\x2d\x30\x2e\x35\ +\x31\x2c\x30\x2e\x30\x34\x20\x2d\x30\x2e\x38\x37\x2c\x2d\x30\x2e\ +\x31\x33\x20\x2d\x31\x2e\x31\x39\x2c\x30\x2e\x32\x20\x30\x2e\x32\ +\x32\x2c\x30\x2e\x35\x39\x20\x33\x2e\x36\x2c\x35\x2e\x30\x39\x20\ +\x34\x2e\x33\x31\x2c\x36\x20\x30\x2e\x36\x31\x2c\x30\x2e\x37\x37\ +\x20\x30\x2e\x38\x39\x2c\x31\x2e\x34\x32\x20\x31\x2e\x33\x37\x2c\ +\x32\x2e\x31\x32\x6c\x34\x2e\x35\x32\x20\x36\x2e\x30\x39\x63\x30\ +\x2e\x35\x33\x2c\x30\x2e\x36\x38\x20\x30\x2e\x39\x31\x2c\x31\x2e\ +\x35\x33\x20\x31\x2e\x34\x34\x2c\x32\x2e\x31\x31\x20\x30\x2e\x38\ +\x36\x2c\x30\x2e\x39\x34\x20\x30\x2e\x38\x36\x2c\x31\x2e\x31\x31\ +\x20\x31\x2e\x35\x2c\x32\x6c\x32\x2e\x39\x20\x34\x2e\x31\x31\x63\ +\x30\x2e\x35\x33\x2c\x30\x2e\x37\x39\x20\x31\x2e\x30\x34\x2c\x31\ +\x2e\x33\x20\x31\x2e\x36\x32\x2c\x32\x2e\x30\x38\x6c\x36\x2e\x35\ +\x31\x20\x39\x2e\x31\x38\x63\x30\x2e\x33\x2c\x30\x2e\x34\x34\x20\ +\x30\x2e\x33\x33\x2c\x30\x2e\x36\x32\x20\x30\x2e\x36\x34\x2c\x31\ +\x2e\x30\x37\x6c\x33\x20\x34\x63\x31\x2e\x36\x2c\x32\x2e\x30\x31\ +\x20\x32\x2e\x39\x2c\x34\x2e\x31\x36\x20\x34\x2e\x34\x34\x2c\x36\ +\x2e\x32\x20\x31\x2e\x30\x39\x2c\x31\x2e\x34\x34\x20\x31\x2e\x39\ +\x35\x2c\x32\x2e\x36\x37\x20\x32\x2e\x39\x32\x2c\x34\x2e\x31\x31\ +\x20\x30\x2e\x34\x38\x2c\x30\x2e\x37\x31\x20\x31\x2e\x31\x2c\x31\ +\x2e\x33\x31\x20\x31\x2e\x35\x34\x2c\x32\x6c\x32\x2e\x39\x31\x20\ +\x34\x2e\x31\x63\x30\x2e\x33\x32\x2c\x30\x2e\x34\x33\x20\x30\x2e\ +\x34\x31\x2c\x30\x2e\x36\x33\x20\x30\x2e\x37\x32\x2c\x31\x2e\x30\ +\x36\x6c\x30\x2e\x38\x39\x20\x31\x2e\x31\x32\x63\x30\x2e\x30\x32\ +\x2c\x30\x2e\x30\x33\x20\x30\x2e\x30\x34\x2c\x30\x2e\x30\x36\x20\ +\x30\x2e\x30\x36\x2c\x30\x2e\x30\x39\x6c\x30\x2e\x35\x38\x20\x30\ +\x2e\x37\x39\x63\x30\x2e\x33\x39\x2c\x30\x2e\x35\x31\x20\x30\x2e\ +\x33\x39\x2c\x30\x2e\x35\x34\x20\x30\x2e\x37\x31\x2c\x31\x2e\x30\ +\x34\x20\x30\x2e\x34\x37\x2c\x30\x2e\x37\x35\x20\x30\x2e\x39\x37\ +\x2c\x31\x2e\x34\x33\x20\x31\x2e\x35\x2c\x32\x2e\x31\x32\x6c\x32\ +\x2e\x31\x34\x20\x33\x2e\x30\x36\x63\x30\x2e\x33\x36\x2c\x30\x2e\ +\x35\x33\x20\x30\x2e\x37\x2c\x30\x2e\x35\x35\x20\x30\x2e\x34\x36\ +\x2c\x31\x2e\x30\x39\x20\x2d\x30\x2e\x37\x38\x2c\x2d\x30\x2e\x30\ +\x38\x20\x2d\x31\x2e\x38\x2c\x2d\x30\x2e\x36\x39\x20\x2d\x32\x2e\ +\x36\x39\x2c\x2d\x30\x2e\x38\x31\x6c\x2d\x38\x2e\x34\x31\x20\x2d\ +\x32\x2e\x32\x35\x63\x2d\x30\x2e\x39\x37\x2c\x2d\x30\x2e\x33\x31\ +\x20\x2d\x31\x2e\x39\x38\x2c\x2d\x30\x2e\x37\x35\x20\x2d\x32\x2e\ +\x39\x36\x2c\x2d\x31\x2e\x30\x35\x6c\x2d\x32\x2e\x32\x33\x20\x2d\ +\x30\x2e\x36\x35\x63\x2d\x30\x2e\x30\x33\x2c\x2d\x30\x2e\x30\x31\ +\x20\x2d\x30\x2e\x30\x37\x2c\x30\x20\x2d\x30\x2e\x31\x31\x2c\x2d\ +\x30\x2e\x30\x31\x20\x2d\x30\x2e\x36\x38\x2c\x2d\x30\x2e\x31\x32\ +\x20\x2d\x31\x2e\x32\x36\x2c\x2d\x30\x2e\x35\x36\x20\x2d\x31\x2e\ +\x39\x35\x2c\x2d\x30\x2e\x37\x20\x2d\x31\x2e\x30\x32\x2c\x2d\x30\ +\x2e\x32\x32\x20\x2d\x33\x2e\x32\x37\x2c\x2d\x31\x2e\x32\x37\x20\ +\x2d\x34\x2e\x33\x39\x2c\x2d\x31\x2e\x36\x38\x20\x2d\x32\x2e\x39\ +\x34\x2c\x2d\x31\x2e\x30\x39\x20\x2d\x39\x2e\x31\x2c\x2d\x33\x2e\ +\x38\x33\x20\x2d\x31\x31\x2e\x35\x31\x2c\x2d\x35\x2e\x36\x37\x20\ +\x2d\x30\x2e\x37\x36\x2c\x2d\x30\x2e\x36\x20\x2d\x31\x2e\x38\x36\ +\x2c\x2d\x31\x2e\x31\x31\x20\x2d\x32\x2e\x37\x34\x2c\x2d\x31\x2e\ +\x37\x31\x20\x2d\x31\x2e\x37\x34\x2c\x2d\x31\x2e\x31\x39\x20\x2d\ +\x33\x2e\x32\x39\x2c\x2d\x32\x2e\x32\x36\x20\x2d\x34\x2e\x39\x2c\ +\x2d\x33\x2e\x36\x31\x20\x2d\x30\x2e\x36\x35\x2c\x2d\x30\x2e\x35\ +\x34\x20\x2d\x31\x2e\x37\x31\x2c\x2d\x31\x2e\x34\x20\x2d\x32\x2e\ +\x32\x31\x2c\x2d\x31\x2e\x38\x39\x6c\x2d\x33\x2e\x35\x38\x20\x2d\ +\x33\x2e\x38\x38\x63\x2d\x32\x2e\x35\x39\x2c\x2d\x33\x2e\x31\x34\ +\x20\x2d\x34\x2e\x39\x38\x2c\x2d\x37\x2e\x31\x36\x20\x2d\x37\x2e\ +\x32\x2c\x2d\x31\x30\x2e\x35\x39\x20\x2d\x30\x2e\x32\x38\x2c\x2d\ +\x30\x2e\x34\x32\x20\x2d\x30\x2e\x33\x35\x2c\x2d\x30\x2e\x36\x34\ +\x20\x2d\x30\x2e\x36\x38\x2c\x2d\x31\x2e\x30\x37\x6c\x2d\x33\x2e\ +\x33\x36\x20\x2d\x35\x2e\x33\x36\x63\x2d\x31\x2e\x31\x35\x2c\x2d\ +\x32\x2e\x30\x31\x20\x2d\x34\x2e\x31\x36\x2c\x2d\x36\x2e\x32\x39\ +\x20\x2d\x35\x2e\x35\x36\x2c\x2d\x38\x2e\x35\x34\x6c\x2d\x35\x2e\ +\x34\x34\x20\x2d\x38\x2e\x34\x35\x63\x2d\x30\x2e\x35\x32\x2c\x2d\ +\x30\x2e\x37\x36\x20\x2d\x30\x2e\x38\x34\x2c\x2d\x31\x2e\x34\x20\ +\x2d\x31\x2e\x33\x35\x2c\x2d\x32\x2e\x31\x34\x6c\x2d\x34\x20\x2d\ +\x36\x2e\x31\x34\x63\x2d\x37\x2e\x31\x31\x2c\x30\x2e\x31\x33\x20\ +\x2d\x32\x32\x2e\x38\x32\x2c\x30\x2e\x34\x31\x20\x2d\x32\x37\x2e\ +\x39\x32\x2c\x30\x2e\x33\x37\x20\x2d\x30\x2e\x36\x39\x2c\x2d\x30\ +\x2e\x30\x39\x20\x2d\x30\x2e\x36\x33\x2c\x2d\x30\x2e\x32\x34\x20\ +\x2d\x31\x2e\x32\x31\x2c\x30\x2e\x31\x38\x20\x2d\x30\x2e\x30\x37\ +\x2c\x30\x2e\x34\x38\x20\x31\x2e\x30\x34\x2c\x34\x20\x31\x2e\x32\ +\x35\x2c\x34\x2e\x38\x33\x20\x30\x2e\x35\x32\x2c\x32\x2e\x30\x34\ +\x20\x30\x2e\x36\x33\x2c\x36\x2e\x32\x31\x20\x30\x2e\x32\x37\x2c\ +\x38\x2e\x34\x38\x20\x2d\x30\x2e\x36\x33\x2c\x33\x2e\x39\x34\x20\ +\x2d\x32\x2e\x32\x33\x2c\x37\x2e\x39\x33\x20\x2d\x34\x2e\x35\x36\ +\x2c\x31\x31\x2e\x32\x31\x20\x2d\x30\x2e\x34\x38\x2c\x30\x2e\x37\ +\x20\x2d\x30\x2e\x36\x38\x2c\x31\x2e\x31\x31\x20\x2d\x31\x2e\x31\ +\x39\x2c\x31\x2e\x37\x6c\x2d\x31\x2e\x33\x31\x20\x31\x2e\x34\x37\ +\x63\x2d\x32\x2e\x35\x39\x2c\x33\x2e\x31\x32\x20\x2d\x34\x2e\x38\ +\x39\x2c\x34\x2e\x36\x38\x20\x2d\x38\x2e\x32\x31\x2c\x36\x2e\x36\ +\x38\x20\x2d\x30\x2e\x39\x31\x2c\x30\x2e\x35\x34\x20\x2d\x33\x2e\ +\x35\x31\x2c\x31\x2e\x39\x35\x20\x2d\x34\x2e\x34\x33\x2c\x32\x2e\ +\x32\x36\x6c\x2d\x33\x2e\x30\x36\x20\x31\x2e\x31\x37\x63\x2d\x30\ +\x2e\x35\x31\x2c\x30\x2e\x31\x36\x20\x2d\x31\x2e\x31\x2c\x30\x2e\ +\x33\x36\x20\x2d\x31\x2e\x35\x37\x2c\x30\x2e\x34\x36\x20\x2d\x30\ +\x2e\x36\x33\x2c\x30\x2e\x31\x33\x20\x2d\x30\x2e\x38\x39\x2c\x30\ +\x2e\x31\x38\x20\x2d\x31\x2e\x36\x2c\x30\x2e\x33\x39\x20\x2d\x30\ +\x2e\x36\x32\x2c\x30\x2e\x31\x38\x20\x2d\x31\x2e\x30\x34\x2c\x30\ +\x2e\x32\x32\x20\x2d\x31\x2e\x36\x31\x2c\x30\x2e\x33\x35\x20\x2d\ +\x32\x2e\x35\x2c\x30\x2e\x36\x20\x2d\x37\x2e\x37\x31\x2c\x30\x2e\ +\x38\x34\x20\x2d\x31\x30\x2e\x32\x2c\x30\x2e\x37\x31\x20\x2d\x31\ +\x2e\x32\x38\x2c\x2d\x30\x2e\x30\x36\x20\x2d\x32\x2e\x31\x37\x2c\ +\x2d\x30\x2e\x31\x37\x20\x2d\x33\x2e\x35\x34\x2c\x2d\x30\x2e\x33\ +\x32\x20\x2d\x30\x2e\x37\x33\x2c\x2d\x30\x2e\x30\x38\x20\x2d\x31\ +\x2e\x30\x36\x2c\x2d\x30\x2e\x30\x36\x20\x2d\x31\x2e\x37\x37\x2c\ +\x2d\x30\x2e\x32\x33\x20\x2d\x30\x2e\x37\x31\x2c\x2d\x30\x2e\x31\ +\x37\x20\x2d\x31\x2e\x31\x38\x2c\x2d\x30\x2e\x32\x33\x20\x2d\x31\ +\x2e\x38\x33\x2c\x2d\x30\x2e\x33\x31\x20\x2d\x34\x2e\x38\x33\x2c\ +\x2d\x30\x2e\x36\x31\x20\x2d\x31\x31\x2e\x33\x37\x2c\x2d\x34\x2e\ +\x34\x32\x20\x2d\x31\x35\x2e\x31\x39\x2c\x2d\x37\x2e\x32\x36\x20\ +\x2d\x30\x2e\x38\x34\x2c\x2d\x30\x2e\x36\x33\x20\x2d\x30\x2e\x37\ +\x39\x2c\x2d\x30\x2e\x37\x34\x20\x2d\x31\x2e\x33\x35\x2c\x2d\x31\ +\x2e\x32\x35\x20\x2d\x30\x2e\x35\x32\x2c\x2d\x30\x2e\x34\x39\x20\ +\x2d\x30\x2e\x39\x37\x2c\x2d\x30\x2e\x37\x38\x20\x2d\x31\x2e\x34\ +\x34\x2c\x2d\x31\x2e\x33\x20\x2d\x32\x2e\x32\x31\x2c\x2d\x32\x2e\ +\x34\x32\x20\x2d\x31\x2e\x36\x35\x2c\x2d\x31\x2e\x37\x37\x20\x2d\ +\x33\x2e\x33\x33\x2c\x2d\x34\x2e\x30\x39\x20\x2d\x32\x2e\x31\x38\ +\x2c\x2d\x33\x2e\x30\x33\x20\x2d\x33\x2e\x36\x2c\x2d\x37\x2e\x32\ +\x32\x20\x2d\x34\x2e\x31\x33\x2c\x2d\x31\x30\x2e\x38\x31\x20\x2d\ +\x30\x2e\x31\x36\x2c\x2d\x31\x2e\x31\x36\x20\x2d\x30\x2e\x31\x32\ +\x2c\x2d\x32\x2e\x31\x37\x20\x2d\x30\x2e\x31\x38\x2c\x2d\x33\x2e\ +\x33\x38\x20\x2d\x30\x2e\x31\x31\x2c\x2d\x32\x2e\x31\x36\x20\x30\ +\x2e\x35\x35\x2c\x2d\x35\x2e\x32\x20\x31\x2e\x30\x33\x2c\x2d\x37\ +\x2e\x33\x6c\x31\x2e\x34\x35\x20\x2d\x33\x2e\x38\x35\x63\x31\x2e\ +\x36\x32\x2c\x2d\x33\x2e\x31\x31\x20\x32\x2e\x36\x34\x2c\x2d\x34\ +\x2e\x35\x32\x20\x34\x2e\x36\x36\x2c\x2d\x36\x2e\x37\x38\x20\x30\ +\x2e\x32\x37\x2c\x2d\x30\x2e\x33\x20\x30\x2e\x33\x39\x2c\x2d\x30\ +\x2e\x35\x39\x20\x30\x2e\x36\x32\x2c\x2d\x30\x2e\x38\x38\x20\x30\ +\x2e\x32\x39\x2c\x2d\x30\x2e\x33\x34\x20\x30\x2e\x32\x38\x2c\x2d\ +\x30\x2e\x31\x36\x20\x30\x2e\x36\x35\x2c\x2d\x30\x2e\x34\x36\x6c\ +\x31\x2e\x33\x33\x20\x2d\x31\x2e\x32\x37\x63\x30\x2e\x35\x32\x2c\ +\x2d\x30\x2e\x34\x34\x20\x30\x2e\x38\x39\x2c\x2d\x30\x2e\x37\x31\ +\x20\x31\x2e\x33\x36\x2c\x2d\x31\x2e\x31\x33\x20\x30\x2e\x36\x36\ +\x2c\x2d\x30\x2e\x35\x39\x20\x30\x2e\x37\x38\x2c\x2d\x30\x2e\x35\ +\x33\x20\x31\x2e\x33\x39\x2c\x2d\x30\x2e\x39\x36\x20\x31\x2e\x39\ +\x35\x2c\x2d\x31\x2e\x33\x36\x20\x33\x2e\x36\x32\x2c\x2d\x32\x2e\ +\x35\x31\x20\x35\x2e\x38\x36\x2c\x2d\x33\x2e\x35\x33\x20\x30\x2e\ +\x39\x37\x2c\x2d\x30\x2e\x34\x34\x20\x32\x2c\x2d\x30\x2e\x39\x33\ +\x20\x33\x2e\x30\x32\x2c\x2d\x31\x2e\x33\x31\x20\x33\x2e\x32\x35\ +\x2c\x2d\x31\x2e\x32\x33\x20\x37\x2e\x37\x33\x2c\x2d\x32\x2e\x32\ +\x33\x20\x31\x31\x2e\x32\x32\x2c\x2d\x32\x2e\x35\x31\x6c\x39\x2e\ +\x33\x20\x2d\x30\x2e\x34\x37\x63\x30\x2e\x32\x2c\x2d\x30\x2e\x30\ +\x32\x20\x30\x2e\x31\x38\x2c\x2d\x30\x2e\x30\x31\x20\x30\x2e\x34\ +\x31\x2c\x2d\x30\x2e\x30\x33\x6c\x32\x2e\x32\x39\x20\x30\x2e\x30\ +\x31\x63\x30\x2e\x34\x32\x2c\x30\x2e\x30\x31\x20\x31\x2e\x32\x31\ +\x2c\x2d\x30\x2e\x30\x39\x20\x31\x2e\x36\x37\x2c\x2d\x30\x2e\x31\ +\x33\x20\x32\x2e\x34\x33\x2c\x2d\x30\x2e\x32\x20\x35\x2e\x36\x38\ +\x2c\x2d\x30\x2e\x31\x33\x20\x38\x2e\x34\x35\x2c\x2d\x30\x2e\x34\ +\x32\x20\x30\x2e\x33\x31\x2c\x2d\x30\x2e\x30\x33\x20\x30\x2e\x35\ +\x35\x2c\x2d\x30\x2e\x30\x33\x20\x30\x2e\x38\x35\x2c\x2d\x30\x2e\ +\x30\x34\x20\x30\x2e\x34\x34\x2c\x2d\x30\x2e\x30\x33\x20\x30\x2e\ +\x35\x2c\x2d\x30\x2e\x30\x39\x20\x30\x2e\x38\x31\x2c\x2d\x30\x2e\ +\x31\x31\x6c\x31\x2e\x37\x33\x20\x30\x63\x31\x2e\x31\x32\x2c\x2d\ +\x30\x2e\x30\x39\x20\x32\x2e\x32\x39\x2c\x2d\x30\x2e\x31\x31\x20\ +\x33\x2e\x33\x37\x2c\x2d\x30\x2e\x32\x31\x20\x31\x2e\x31\x32\x2c\ +\x2d\x30\x2e\x30\x39\x20\x32\x2e\x33\x32\x2c\x30\x2e\x30\x34\x20\ +\x33\x2e\x34\x31\x2c\x2d\x30\x2e\x31\x32\x6c\x31\x39\x2e\x37\x34\ +\x20\x2d\x30\x2e\x37\x32\x20\x2d\x31\x34\x2e\x30\x36\x20\x2d\x32\ +\x31\x2e\x39\x34\x63\x2d\x30\x2e\x34\x34\x2c\x2d\x30\x2e\x38\x34\ +\x20\x2d\x31\x2c\x2d\x31\x2e\x33\x39\x20\x2d\x31\x2e\x34\x38\x2c\ +\x2d\x32\x2e\x31\x33\x6c\x2d\x31\x2e\x32\x37\x20\x2d\x32\x2e\x31\ +\x35\x63\x2d\x30\x2e\x36\x2c\x2d\x31\x2e\x31\x38\x20\x2d\x31\x2e\ +\x35\x32\x2c\x2d\x32\x2e\x30\x37\x20\x2d\x32\x2e\x30\x38\x2c\x2d\ +\x33\x2e\x32\x31\x20\x2d\x30\x2e\x35\x35\x2c\x2d\x31\x2e\x31\x35\ +\x20\x2d\x32\x2e\x36\x33\x2c\x2d\x34\x2e\x32\x35\x20\x2d\x33\x2e\ +\x33\x39\x2c\x2d\x35\x2e\x33\x35\x6c\x2d\x34\x2e\x31\x31\x20\x2d\ +\x36\x2e\x33\x32\x63\x2d\x31\x2e\x33\x32\x2c\x2d\x31\x2e\x39\x38\ +\x20\x2d\x32\x2e\x37\x34\x2c\x2d\x34\x2e\x33\x38\x20\x2d\x33\x2e\ +\x37\x38\x2c\x2d\x36\x2e\x35\x33\x20\x2d\x31\x2e\x39\x32\x2c\x2d\ +\x33\x2e\x39\x38\x20\x2d\x33\x2e\x35\x38\x2c\x2d\x39\x2e\x35\x34\ +\x20\x2d\x33\x2e\x37\x36\x2c\x2d\x31\x34\x2e\x31\x34\x20\x2d\x30\ +\x2e\x31\x31\x2c\x2d\x32\x2e\x36\x32\x20\x2d\x30\x2e\x32\x35\x2c\ +\x2d\x35\x2e\x32\x20\x30\x2e\x32\x39\x2c\x2d\x37\x2e\x38\x32\x20\ +\x30\x2e\x36\x2c\x2d\x32\x2e\x38\x39\x20\x31\x2e\x32\x32\x2c\x2d\ +\x35\x2e\x37\x34\x20\x32\x2e\x36\x35\x2c\x2d\x38\x2e\x34\x38\x20\ +\x31\x2e\x32\x36\x2c\x2d\x32\x2e\x34\x33\x20\x32\x2e\x36\x35\x2c\ +\x2d\x34\x2e\x36\x37\x20\x34\x2e\x33\x36\x2c\x2d\x36\x2e\x36\x31\ +\x20\x30\x2e\x32\x39\x2c\x2d\x30\x2e\x33\x32\x20\x30\x2e\x36\x34\ +\x2c\x2d\x30\x2e\x35\x34\x20\x30\x2e\x39\x38\x2c\x2d\x30\x2e\x39\ +\x20\x30\x2e\x36\x2c\x2d\x30\x2e\x36\x34\x20\x31\x2e\x33\x2c\x2d\ +\x31\x2e\x32\x31\x20\x32\x2e\x30\x31\x2c\x2d\x31\x2e\x37\x35\x20\ +\x33\x2e\x33\x31\x2c\x2d\x32\x2e\x35\x31\x20\x37\x2e\x32\x34\x2c\ +\x2d\x34\x2e\x31\x36\x20\x31\x31\x2e\x33\x2c\x2d\x34\x2e\x39\x32\ +\x20\x32\x2e\x35\x35\x2c\x2d\x30\x2e\x34\x38\x20\x35\x2e\x31\x33\ +\x2c\x2d\x30\x2e\x33\x37\x20\x37\x2e\x36\x39\x2c\x2d\x30\x2e\x31\ +\x36\x20\x30\x2e\x39\x39\x2c\x30\x2e\x30\x38\x20\x31\x2e\x37\x2c\ +\x30\x2e\x32\x35\x20\x32\x2e\x37\x2c\x30\x2e\x33\x39\x20\x30\x2e\ +\x39\x2c\x30\x2e\x31\x32\x20\x31\x2e\x39\x37\x2c\x30\x2e\x35\x37\ +\x20\x32\x2e\x38\x34\x2c\x30\x2e\x37\x38\x20\x33\x2e\x33\x31\x2c\ +\x30\x2e\x37\x39\x20\x38\x2e\x35\x33\x2c\x33\x2e\x34\x34\x20\x31\ +\x31\x2e\x32\x38\x2c\x35\x2e\x36\x38\x20\x30\x2e\x37\x38\x2c\x30\ +\x2e\x36\x34\x20\x31\x2e\x35\x39\x2c\x31\x2e\x31\x39\x20\x32\x2e\ +\x33\x36\x2c\x31\x2e\x38\x33\x20\x31\x2e\x37\x35\x2c\x31\x2e\x34\ +\x33\x20\x33\x2e\x32\x2c\x33\x2e\x33\x35\x20\x34\x2e\x37\x32\x2c\ +\x34\x2e\x39\x32\x20\x30\x2e\x39\x31\x2c\x30\x2e\x39\x34\x20\x31\ +\x2e\x35\x34\x2c\x32\x2e\x30\x31\x20\x32\x2e\x32\x2c\x33\x2e\x30\ +\x34\x20\x30\x2e\x34\x36\x2c\x30\x2e\x37\x32\x20\x31\x2e\x30\x37\ +\x2c\x31\x2e\x34\x31\x20\x31\x2e\x34\x39\x2c\x32\x2e\x31\x33\x20\ +\x30\x2e\x38\x33\x2c\x31\x2e\x34\x33\x20\x31\x2e\x35\x35\x2c\x32\ +\x2e\x39\x31\x20\x32\x2e\x33\x31\x2c\x34\x2e\x34\x32\x20\x31\x2e\ +\x31\x32\x2c\x32\x2e\x32\x32\x20\x32\x2e\x34\x38\x2c\x37\x2e\x30\ +\x31\x20\x32\x2e\x38\x33\x2c\x39\x2e\x34\x20\x30\x2e\x33\x2c\x32\ +\x2e\x30\x37\x20\x30\x2e\x36\x35\x2c\x33\x2e\x38\x37\x20\x30\x2e\ +\x37\x31\x2c\x36\x2e\x31\x20\x30\x2e\x30\x31\x2c\x30\x2e\x34\x32\ +\x20\x2d\x30\x2e\x30\x37\x2c\x30\x2e\x38\x36\x20\x2d\x30\x2e\x30\ +\x36\x2c\x31\x2e\x33\x31\x20\x30\x2e\x31\x35\x2c\x36\x2e\x32\x31\ +\x20\x2d\x32\x2e\x39\x34\x2c\x31\x34\x2e\x35\x34\x20\x2d\x37\x2e\ +\x32\x37\x2c\x31\x39\x2e\x30\x35\x20\x2d\x30\x2e\x39\x38\x2c\x31\ +\x2e\x30\x33\x20\x2d\x31\x2e\x38\x37\x2c\x32\x2e\x31\x32\x20\x2d\ +\x32\x2e\x39\x36\x2c\x32\x2e\x38\x37\x20\x2d\x30\x2e\x34\x31\x2c\ +\x30\x2e\x32\x38\x20\x2d\x30\x2e\x36\x37\x2c\x30\x2e\x33\x33\x20\ +\x2d\x31\x2e\x30\x36\x2c\x30\x2e\x36\x34\x20\x2d\x32\x2e\x31\x37\ +\x2c\x31\x2e\x37\x35\x20\x2d\x34\x2e\x31\x35\x2c\x32\x2e\x36\x36\ +\x20\x2d\x36\x2e\x36\x34\x2c\x33\x2e\x35\x20\x2d\x30\x2e\x35\x39\ +\x2c\x30\x2e\x32\x20\x2d\x31\x2e\x37\x38\x2c\x30\x2e\x33\x36\x20\ +\x2d\x32\x2e\x33\x35\x2c\x30\x2e\x38\x32\x7a\x22\x2f\x3e\x0d\x0a\ +\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x0b\x37\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x73\x74\x72\x31\x20\x7b\x73\x74\x72\x6f\x6b\x65\x3a\ +\x67\x72\x61\x79\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\ +\x68\x3a\x31\x34\x2e\x31\x31\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\ +\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\ +\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x73\x74\x72\x30\x20\x7b\x73\ +\x74\x72\x6f\x6b\x65\x3a\x23\x34\x36\x38\x32\x43\x38\x3b\x73\x74\ +\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\x3a\x31\x36\x2e\x39\x33\ +\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x63\x61\x70\x3a\ +\x73\x71\x75\x61\x72\x65\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\ +\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x32\x32\x2e\x39\x32\x35\x36\ +\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\ +\x6c\x6c\x3a\x6e\x6f\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\ +\x69\x6c\x32\x20\x7b\x66\x69\x6c\x6c\x3a\x67\x72\x61\x79\x7d\x0d\ +\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\ +\x3a\x23\x46\x46\x39\x39\x33\x33\x7d\x0d\x0a\x20\x20\x20\x20\x2e\ +\x66\x69\x6c\x33\x20\x7b\x66\x69\x6c\x6c\x3a\x67\x72\x61\x79\x3b\ +\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x6e\x6f\x6e\x7a\x65\x72\ +\x6f\x7d\x0d\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\ +\x73\x74\x79\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\x73\x3e\ +\x0d\x0a\x20\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\x64\x61\ +\x5f\x78\x30\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\ +\x65\x74\x61\x64\x61\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\x72\x65\ +\x6c\x43\x6f\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\x2d\x4c\ +\x61\x79\x65\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\x65\x63\x74\ +\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\x78\x3d\ +\x22\x30\x22\x20\x79\x3d\x22\x30\x22\x20\x77\x69\x64\x74\x68\x3d\ +\x22\x32\x37\x30\x2e\x39\x32\x22\x20\x68\x65\x69\x67\x68\x74\x3d\ +\x22\x32\x37\x30\x2e\x39\x32\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\ +\x61\x74\x68\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\ +\x73\x74\x72\x30\x22\x20\x64\x3d\x22\x4d\x32\x34\x39\x2e\x36\x34\ +\x20\x32\x35\x38\x2e\x31\x32\x63\x2d\x34\x2e\x35\x2c\x2d\x31\x32\ +\x38\x2e\x38\x37\x20\x2d\x31\x30\x37\x2e\x39\x33\x2c\x2d\x32\x33\ +\x32\x2e\x33\x20\x2d\x32\x33\x36\x2e\x38\x2c\x2d\x32\x33\x36\x2e\ +\x38\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\ +\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\x22\x20\x63\x6c\x61\x73\x73\ +\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\ +\x34\x32\x2e\x33\x33\x2c\x2d\x30\x2e\x30\x31\x20\x34\x32\x2e\x33\ +\x33\x2c\x34\x32\x2e\x33\x32\x20\x2d\x30\x2c\x34\x32\x2e\x33\x32\ +\x20\x2d\x30\x2c\x2d\x30\x2e\x30\x31\x20\x22\x2f\x3e\x0d\x0a\x20\ +\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x69\x64\x3d\x22\x5f\x31\ +\x5f\x30\x5f\x30\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\ +\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x32\x37\x30\x2e\x39\ +\x33\x2c\x32\x32\x38\x2e\x35\x39\x20\x32\x37\x30\x2e\x39\x33\x2c\ +\x32\x37\x30\x2e\x39\x32\x20\x32\x32\x38\x2e\x36\x2c\x32\x37\x30\ +\x2e\x39\x32\x20\x32\x32\x38\x2e\x36\x2c\x32\x32\x38\x2e\x35\x39\ +\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\ +\x20\x69\x64\x3d\x22\x5f\x31\x5f\x30\x5f\x31\x22\x20\x63\x6c\x61\ +\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x70\x6f\x69\x6e\x74\x73\ +\x3d\x22\x34\x32\x2e\x34\x32\x2c\x32\x32\x38\x2e\x35\x20\x34\x32\ +\x2e\x34\x32\x2c\x32\x37\x30\x2e\x38\x33\x20\x30\x2e\x30\x39\x2c\ +\x32\x37\x30\x2e\x38\x33\x20\x30\x2e\x30\x39\x2c\x32\x32\x38\x2e\ +\x35\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\ +\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\x20\x70\ +\x6f\x69\x6e\x74\x73\x3d\x22\x35\x30\x2e\x37\x39\x2c\x32\x34\x39\ +\x2e\x37\x35\x20\x36\x39\x2e\x31\x32\x2c\x32\x33\x39\x2e\x31\x37\ +\x20\x38\x37\x2e\x34\x35\x2c\x32\x32\x38\x2e\x35\x39\x20\x38\x37\ +\x2e\x34\x35\x2c\x32\x34\x39\x2e\x37\x35\x20\x38\x37\x2e\x34\x35\ +\x2c\x32\x37\x30\x2e\x39\x32\x20\x36\x39\x2e\x31\x32\x2c\x32\x36\ +\x30\x2e\x33\x34\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x6c\x69\x6e\ +\x65\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x20\x73\x74\ +\x72\x31\x22\x20\x78\x31\x3d\x22\x37\x30\x2e\x35\x33\x22\x20\x79\ +\x31\x3d\x22\x32\x34\x39\x2e\x37\x35\x22\x20\x78\x32\x3d\x22\x32\ +\x32\x30\x2e\x31\x34\x22\x20\x79\x32\x3d\x20\x22\x32\x34\x39\x2e\ +\x37\x35\x22\x20\x2f\x3e\x0d\x0a\x20\x20\x3c\x67\x20\x69\x64\x3d\ +\x22\x5f\x31\x32\x35\x37\x38\x33\x31\x36\x34\x37\x32\x34\x38\x22\ +\x3e\x0d\x0a\x20\x20\x20\x3c\x67\x3e\x0d\x0a\x20\x20\x20\x20\x3c\ +\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\ +\x69\x6c\x33\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x36\x32\x2e\ +\x30\x39\x2c\x31\x30\x37\x2e\x39\x34\x20\x36\x32\x2e\x30\x39\x2c\ +\x39\x33\x2e\x38\x32\x20\x36\x37\x2e\x36\x37\x2c\x39\x33\x2e\x39\ +\x36\x20\x37\x33\x2e\x31\x39\x2c\x39\x34\x2e\x33\x38\x20\x37\x38\ +\x2e\x36\x32\x2c\x39\x35\x2e\x30\x38\x20\x38\x33\x2e\x39\x38\x2c\ +\x39\x36\x2e\x30\x33\x20\x38\x39\x2e\x32\x34\x2c\x39\x37\x2e\x32\ +\x35\x20\x39\x34\x2e\x34\x2c\x39\x38\x2e\x37\x31\x20\x39\x39\x2e\ +\x34\x34\x2c\x31\x30\x30\x2e\x34\x32\x20\x31\x30\x34\x2e\x33\x38\ +\x2c\x31\x30\x32\x2e\x33\x36\x20\x31\x30\x39\x2e\x32\x2c\x31\x30\ +\x34\x2e\x35\x34\x20\x31\x31\x33\x2e\x38\x39\x2c\x31\x30\x36\x2e\ +\x39\x34\x20\x31\x31\x38\x2e\x34\x34\x2c\x31\x30\x39\x2e\x35\x36\ +\x20\x31\x32\x32\x2e\x38\x34\x2c\x31\x31\x32\x2e\x33\x38\x20\x31\ +\x32\x37\x2e\x31\x2c\x31\x31\x35\x2e\x34\x32\x20\x31\x33\x31\x2e\ +\x32\x31\x2c\x31\x31\x38\x2e\x36\x34\x20\x31\x33\x35\x2e\x31\x35\ +\x2c\x31\x32\x32\x2e\x30\x36\x20\x31\x33\x38\x2e\x39\x32\x2c\x31\ +\x32\x35\x2e\x36\x35\x20\x31\x34\x32\x2e\x35\x32\x2c\x31\x32\x39\ +\x2e\x34\x33\x20\x31\x34\x35\x2e\x39\x33\x2c\x31\x33\x33\x2e\x33\ +\x37\x20\x31\x34\x39\x2e\x31\x36\x2c\x31\x33\x37\x2e\x34\x36\x20\ +\x31\x35\x32\x2e\x31\x39\x2c\x31\x34\x31\x2e\x37\x34\x20\x31\x35\ +\x35\x2e\x30\x31\x2c\x31\x34\x36\x2e\x31\x34\x20\x31\x35\x37\x2e\ +\x36\x33\x2c\x31\x35\x30\x2e\x36\x39\x20\x31\x36\x30\x2e\x30\x33\ +\x2c\x31\x35\x35\x2e\x33\x38\x20\x31\x36\x32\x2e\x32\x31\x2c\x31\ +\x36\x30\x2e\x31\x39\x20\x31\x36\x34\x2e\x31\x35\x2c\x31\x36\x35\ +\x2e\x31\x33\x20\x31\x36\x35\x2e\x38\x36\x2c\x31\x37\x30\x2e\x31\ +\x37\x20\x31\x36\x37\x2e\x33\x32\x2c\x31\x37\x35\x2e\x33\x34\x20\ +\x31\x36\x38\x2e\x35\x34\x2c\x31\x38\x30\x2e\x36\x20\x31\x36\x39\ +\x2e\x34\x39\x2c\x31\x38\x35\x2e\x39\x36\x20\x31\x37\x30\x2e\x31\ +\x39\x2c\x31\x39\x31\x2e\x33\x39\x20\x31\x37\x30\x2e\x36\x31\x2c\ +\x31\x39\x36\x2e\x39\x31\x20\x31\x37\x30\x2e\x37\x35\x2c\x32\x30\ +\x32\x2e\x34\x39\x20\x31\x35\x36\x2e\x36\x33\x2c\x32\x30\x32\x2e\ +\x34\x39\x20\x31\x35\x36\x2e\x35\x31\x2c\x31\x39\x37\x2e\x36\x31\ +\x20\x31\x35\x36\x2e\x31\x35\x2c\x31\x39\x32\x2e\x38\x31\x20\x31\ +\x35\x35\x2e\x35\x35\x2c\x31\x38\x38\x2e\x30\x38\x20\x31\x35\x34\ +\x2e\x37\x32\x2c\x31\x38\x33\x2e\x34\x32\x20\x31\x35\x33\x2e\x36\ +\x36\x2c\x31\x37\x38\x2e\x38\x36\x20\x31\x35\x32\x2e\x33\x38\x2c\ +\x31\x37\x34\x2e\x33\x37\x20\x31\x35\x30\x2e\x38\x39\x2c\x31\x36\ +\x39\x2e\x39\x37\x20\x31\x34\x39\x2e\x32\x31\x2c\x31\x36\x35\x2e\ +\x36\x39\x20\x31\x34\x37\x2e\x33\x31\x2c\x31\x36\x31\x2e\x35\x20\ +\x31\x34\x35\x2e\x32\x33\x2c\x31\x35\x37\x2e\x34\x33\x20\x31\x34\ +\x32\x2e\x39\x35\x2c\x31\x35\x33\x2e\x34\x36\x20\x31\x34\x30\x2e\ +\x34\x39\x2c\x31\x34\x39\x2e\x36\x32\x20\x31\x33\x37\x2e\x38\x36\ +\x2c\x31\x34\x35\x2e\x39\x32\x20\x31\x33\x35\x2e\x30\x35\x2c\x31\ +\x34\x32\x2e\x33\x35\x20\x31\x33\x32\x2e\x30\x38\x2c\x31\x33\x38\ +\x2e\x39\x31\x20\x31\x32\x38\x2e\x39\x34\x2c\x31\x33\x35\x2e\x36\ +\x33\x20\x31\x32\x35\x2e\x36\x35\x2c\x31\x33\x32\x2e\x35\x20\x31\ +\x32\x32\x2e\x32\x33\x2c\x31\x32\x39\x2e\x35\x32\x20\x31\x31\x38\ +\x2e\x36\x36\x2c\x31\x32\x36\x2e\x37\x32\x20\x31\x31\x34\x2e\x39\ +\x34\x2c\x31\x32\x34\x2e\x30\x38\x20\x31\x31\x31\x2e\x31\x2c\x31\ +\x32\x31\x2e\x36\x32\x20\x31\x30\x37\x2e\x31\x35\x2c\x31\x31\x39\ +\x2e\x33\x34\x20\x31\x30\x33\x2e\x30\x38\x2c\x31\x31\x37\x2e\x32\ +\x36\x20\x39\x38\x2e\x39\x2c\x31\x31\x35\x2e\x33\x36\x20\x39\x34\ +\x2e\x36\x2c\x31\x31\x33\x2e\x36\x38\x20\x39\x30\x2e\x32\x2c\x31\ +\x31\x32\x2e\x31\x39\x20\x38\x35\x2e\x37\x32\x2c\x31\x31\x30\x2e\ +\x39\x31\x20\x38\x31\x2e\x31\x36\x2c\x31\x30\x39\x2e\x38\x35\x20\ +\x37\x36\x2e\x35\x2c\x31\x30\x39\x2e\x30\x32\x20\x37\x31\x2e\x37\ +\x37\x2c\x31\x30\x38\x2e\x34\x32\x20\x36\x36\x2e\x39\x37\x2c\x31\ +\x30\x38\x2e\x30\x36\x20\x22\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x2f\ +\x67\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\ +\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x32\x22\x20\x70\x6f\x69\ +\x6e\x74\x73\x3d\x22\x31\x36\x33\x2e\x36\x35\x2c\x32\x33\x33\x2e\ +\x34\x37\x20\x31\x35\x33\x2e\x30\x37\x2c\x32\x31\x35\x2e\x31\x34\ +\x20\x31\x34\x32\x2e\x34\x39\x2c\x31\x39\x36\x2e\x38\x31\x20\x31\ +\x36\x33\x2e\x36\x35\x2c\x31\x39\x36\x2e\x38\x31\x20\x31\x38\x34\ +\x2e\x38\x32\x2c\x31\x39\x36\x2e\x38\x31\x20\x31\x37\x34\x2e\x32\ +\x34\x2c\x32\x31\x35\x2e\x31\x34\x20\x22\x2f\x3e\x0d\x0a\x20\x20\ +\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\ +\x22\x66\x69\x6c\x32\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x34\ +\x33\x2e\x38\x2c\x31\x30\x30\x2e\x39\x32\x20\x36\x32\x2e\x31\x33\ +\x2c\x39\x30\x2e\x33\x33\x20\x38\x30\x2e\x34\x35\x2c\x37\x39\x2e\ +\x37\x36\x20\x38\x30\x2e\x34\x35\x2c\x31\x30\x30\x2e\x39\x32\x20\ +\x38\x30\x2e\x34\x35\x2c\x31\x32\x32\x2e\x30\x38\x20\x36\x32\x2e\ +\x31\x33\x2c\x31\x31\x31\x2e\x35\x31\x20\x22\x2f\x3e\x0d\x0a\x20\ +\x20\x3c\x2f\x67\x3e\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\ +\x73\x76\x67\x3e\x0d\x0a\ +\x00\x00\x05\x32\ +\x3c\ +\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\ +\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\ +\x2d\x38\x22\x3f\x3e\x0d\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\ +\x20\x73\x76\x67\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\ +\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x53\x56\x47\x20\x31\x2e\x31\ +\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\ +\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x47\x72\x61\x70\x68\x69\x63\ +\x73\x2f\x53\x56\x47\x2f\x31\x2e\x31\x2f\x44\x54\x44\x2f\x73\x76\ +\x67\x31\x31\x2e\x64\x74\x64\x22\x3e\x0d\x0a\x3c\x21\x2d\x2d\x20\ +\x43\x72\x65\x61\x74\x6f\x72\x3a\x20\x43\x6f\x72\x65\x6c\x44\x52\ +\x41\x57\x20\x32\x30\x32\x31\x20\x28\x36\x34\x20\x42\x69\x74\x29\ +\x20\x2d\x2d\x3e\x0d\x0a\x3c\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\ +\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\ +\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x78\x6d\ +\x6c\x3a\x73\x70\x61\x63\x65\x3d\x22\x70\x72\x65\x73\x65\x72\x76\ +\x65\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x2e\x37\x30\x39\x32\ +\x6d\x6d\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x2e\x37\x30\ +\x39\x32\x6d\x6d\x22\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\ +\x2e\x31\x22\x20\x73\x74\x79\x6c\x65\x3d\x22\x73\x68\x61\x70\x65\ +\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\x6f\x6d\x65\ +\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\x3b\x20\x74\ +\x65\x78\x74\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\x67\x3a\x67\x65\ +\x6f\x6d\x65\x74\x72\x69\x63\x50\x72\x65\x63\x69\x73\x69\x6f\x6e\ +\x3b\x20\x69\x6d\x61\x67\x65\x2d\x72\x65\x6e\x64\x65\x72\x69\x6e\ +\x67\x3a\x6f\x70\x74\x69\x6d\x69\x7a\x65\x51\x75\x61\x6c\x69\x74\ +\x79\x3b\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3a\x65\x76\x65\ +\x6e\x6f\x64\x64\x3b\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3a\ +\x65\x76\x65\x6e\x6f\x64\x64\x22\x0d\x0a\x76\x69\x65\x77\x42\x6f\ +\x78\x3d\x22\x30\x20\x30\x20\x32\x37\x30\x2e\x39\x32\x20\x32\x37\ +\x30\x2e\x39\x32\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6c\ +\x69\x6e\x6b\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\ +\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x78\x6c\x69\x6e\ +\x6b\x22\x0d\x0a\x20\x78\x6d\x6c\x6e\x73\x3a\x78\x6f\x64\x6d\x3d\ +\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x63\x6f\x72\x65\ +\x6c\x2e\x63\x6f\x6d\x2f\x63\x6f\x72\x65\x6c\x64\x72\x61\x77\x2f\ +\x6f\x64\x6d\x2f\x32\x30\x30\x33\x22\x3e\x0d\x0a\x20\x3c\x64\x65\ +\x66\x73\x3e\x0d\x0a\x20\x20\x3c\x73\x74\x79\x6c\x65\x20\x74\x79\ +\x70\x65\x3d\x22\x74\x65\x78\x74\x2f\x63\x73\x73\x22\x3e\x0d\x0a\ +\x20\x20\x20\x3c\x21\x5b\x43\x44\x41\x54\x41\x5b\x0d\x0a\x20\x20\ +\x20\x20\x2e\x66\x69\x6c\x30\x20\x7b\x66\x69\x6c\x6c\x3a\x6e\x6f\ +\x6e\x65\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\x6c\x33\x20\x7b\ +\x66\x69\x6c\x6c\x3a\x23\x30\x30\x39\x39\x33\x33\x7d\x0d\x0a\x20\ +\x20\x20\x20\x2e\x66\x69\x6c\x31\x20\x7b\x66\x69\x6c\x6c\x3a\x23\ +\x36\x36\x36\x36\x36\x36\x7d\x0d\x0a\x20\x20\x20\x20\x2e\x66\x69\ +\x6c\x32\x20\x7b\x66\x69\x6c\x6c\x3a\x77\x68\x69\x74\x65\x7d\x0d\ +\x0a\x20\x20\x20\x5d\x5d\x3e\x0d\x0a\x20\x20\x3c\x2f\x73\x74\x79\ +\x6c\x65\x3e\x0d\x0a\x20\x3c\x2f\x64\x65\x66\x73\x3e\x0d\x0a\x20\ +\x3c\x67\x20\x69\x64\x3d\x22\x43\x61\x6d\x61\x64\x61\x5f\x78\x30\ +\x30\x32\x30\x5f\x31\x22\x3e\x0d\x0a\x20\x20\x3c\x6d\x65\x74\x61\ +\x64\x61\x74\x61\x20\x69\x64\x3d\x22\x43\x6f\x72\x65\x6c\x43\x6f\ +\x72\x70\x49\x44\x5f\x30\x43\x6f\x72\x65\x6c\x2d\x4c\x61\x79\x65\ +\x72\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x72\x65\x63\x74\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x30\x22\x20\x78\x3d\x22\x2d\x30\ +\x2e\x30\x31\x22\x20\x79\x3d\x22\x2d\x30\x22\x20\x77\x69\x64\x74\ +\x68\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x20\x68\x65\x69\x67\x68\ +\x74\x3d\x22\x32\x37\x30\x2e\x39\x33\x22\x2f\x3e\x0d\x0a\x20\x20\ +\x3c\x67\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x61\x74\x68\x20\x63\x6c\ +\x61\x73\x73\x3d\x22\x66\x69\x6c\x31\x22\x20\x64\x3d\x22\x4d\x31\ +\x33\x35\x2e\x34\x35\x20\x32\x32\x39\x2e\x30\x35\x6c\x39\x33\x2e\ +\x35\x39\x20\x2d\x39\x33\x2e\x35\x39\x20\x2d\x39\x33\x2e\x35\x39\ +\x20\x2d\x39\x33\x2e\x35\x39\x20\x2d\x39\x33\x2e\x35\x39\x20\x39\ +\x33\x2e\x35\x39\x20\x39\x33\x2e\x35\x39\x20\x39\x33\x2e\x35\x39\ +\x7a\x6d\x30\x20\x32\x34\x2e\x39\x34\x6c\x31\x31\x38\x2e\x35\x33\ +\x20\x2d\x31\x31\x38\x2e\x35\x33\x20\x2d\x31\x31\x38\x2e\x35\x33\ +\x20\x2d\x31\x31\x38\x2e\x35\x33\x20\x2d\x31\x31\x38\x2e\x35\x33\ +\x20\x31\x31\x38\x2e\x35\x33\x20\x31\x31\x38\x2e\x35\x33\x20\x31\ +\x31\x38\x2e\x35\x33\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\ +\x3e\x0d\x0a\x20\x20\x3c\x67\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x6f\ +\x6c\x79\x67\x6f\x6e\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\ +\x32\x22\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x31\x36\x35\x2e\x31\ +\x2c\x31\x36\x35\x2e\x31\x20\x31\x30\x35\x2e\x38\x32\x2c\x31\x36\ +\x35\x2e\x31\x20\x31\x30\x35\x2e\x38\x32\x2c\x31\x30\x35\x2e\x38\ +\x32\x20\x31\x36\x35\x2e\x31\x2c\x31\x30\x35\x2e\x38\x32\x20\x22\ +\x2f\x3e\x0d\x0a\x20\x20\x20\x3c\x70\x61\x74\x68\x20\x69\x64\x3d\ +\x22\x5f\x31\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x69\x6c\x33\ +\x22\x20\x64\x3d\x22\x4d\x31\x36\x35\x2e\x31\x20\x31\x36\x35\x2e\ +\x31\x6c\x2d\x35\x39\x2e\x32\x38\x20\x30\x20\x30\x20\x2d\x35\x39\ +\x2e\x32\x38\x20\x35\x39\x2e\x32\x38\x20\x30\x20\x30\x20\x35\x39\ +\x2e\x32\x38\x7a\x6d\x2d\x34\x36\x2e\x39\x33\x20\x2d\x31\x32\x2e\ +\x33\x35\x6c\x33\x34\x2e\x35\x38\x20\x30\x20\x30\x20\x2d\x33\x34\ +\x2e\x35\x38\x20\x2d\x33\x34\x2e\x35\x38\x20\x30\x20\x30\x20\x33\ +\x34\x2e\x35\x38\x7a\x22\x2f\x3e\x0d\x0a\x20\x20\x3c\x2f\x67\x3e\ +\x0d\x0a\x20\x3c\x2f\x67\x3e\x0d\x0a\x3c\x2f\x73\x76\x67\x3e\x0d\ +\x0a\ +" + +qt_resource_name = b"\ +\x00\x07\ +\x07\x3b\xe0\xb3\ +\x00\x70\ +\x00\x6c\x00\x75\x00\x67\x00\x69\x00\x6e\x00\x73\ +\x00\x03\ +\x00\x00\x77\x74\ +\x00\x71\ +\x00\x61\x00\x64\ +\x00\x05\ +\x00\x6f\xa6\x53\ +\x00\x69\ +\x00\x63\x00\x6f\x00\x6e\x00\x73\ +\x00\x09\ +\x08\x28\xa9\xe7\ +\x00\x73\ +\x00\x63\x00\x61\x00\x6c\x00\x65\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x08\ +\x06\x7c\x57\x87\ +\x00\x63\ +\x00\x6f\x00\x70\x00\x79\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x0e\ +\x04\x7c\x97\x27\ +\x00\x73\ +\x00\x75\x00\x70\x00\x70\x00\x6f\x00\x72\x00\x74\x00\x65\x00\x72\x00\x73\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x0b\ +\x0c\x83\x95\x67\ +\x00\x63\ +\x00\x6f\x00\x70\x00\x79\x00\x45\x00\x6e\x00\x74\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x17\ +\x09\x44\x0e\x27\ +\x00\x63\ +\x00\x69\x00\x72\x00\x63\x00\x6c\x00\x65\x00\x42\x00\x79\x00\x32\x00\x54\x00\x61\x00\x6e\x00\x73\x00\x52\x00\x61\x00\x64\x00\x69\ +\x00\x75\x00\x73\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x09\ +\x08\x98\x83\xc7\ +\x00\x65\ +\x00\x72\x00\x61\x00\x73\x00\x65\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x17\ +\x00\xd2\x51\x47\ +\x00\x73\ +\x00\x65\x00\x74\x00\x63\x00\x75\x00\x72\x00\x72\x00\x6c\x00\x61\x00\x79\x00\x65\x00\x72\x00\x62\x00\x79\x00\x67\x00\x72\x00\x61\ +\x00\x70\x00\x68\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x0a\ +\x01\xb9\x31\x87\ +\x00\x6c\ +\x00\x6f\x00\x63\x00\x6b\x00\x65\x00\x64\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x0d\ +\x0d\x81\x3b\xe7\ +\x00\x64\ +\x00\x69\x00\x6d\x00\x4c\x00\x69\x00\x6e\x00\x65\x00\x61\x00\x72\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x08\ +\x0c\xf7\x55\x87\ +\x00\x74\ +\x00\x65\x00\x78\x00\x74\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x0e\ +\x04\x87\x1f\x47\ +\x00\x64\ +\x00\x69\x00\x6d\x00\x41\x00\x6c\x00\x69\x00\x67\x00\x6e\x00\x65\x00\x64\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x11\ +\x04\xd1\xe9\x07\ +\x00\x63\ +\x00\x69\x00\x72\x00\x63\x00\x6c\x00\x65\x00\x42\x00\x79\x00\x33\x00\x54\x00\x61\x00\x6e\x00\x73\x00\x2e\x00\x73\x00\x76\x00\x67\ +\ +\x00\x0a\ +\x0c\x99\x51\xe7\ +\x00\x69\ +\x00\x6e\x00\x73\x00\x65\x00\x72\x00\x74\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x08\ +\x0b\xb2\x55\xc7\ +\x00\x72\ +\x00\x65\x00\x64\x00\x6f\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x0c\ +\x01\x19\xc7\x87\ +\x00\x6c\ +\x00\x65\x00\x6e\x00\x67\x00\x74\x00\x68\x00\x65\x00\x6e\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x0b\ +\x05\x57\x4e\xe7\ +\x00\x64\ +\x00\x69\x00\x73\x00\x6a\x00\x6f\x00\x69\x00\x6e\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x0a\ +\x0a\x2d\x1b\xc7\ +\x00\x63\ +\x00\x69\x00\x72\x00\x63\x00\x6c\x00\x65\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x11\ +\x05\x36\x5a\x87\ +\x00\x6f\ +\x00\x73\x00\x6e\x00\x61\x00\x70\x00\x5f\x00\x65\x00\x6e\x00\x64\x00\x4c\x00\x69\x00\x6e\x00\x65\x00\x2e\x00\x73\x00\x76\x00\x67\ +\ +\x00\x0b\ +\x07\x50\x3c\xc7\ +\x00\x65\ +\x00\x6c\x00\x6c\x00\x69\x00\x70\x00\x73\x00\x65\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x11\ +\x02\xc9\xb9\x87\ +\x00\x62\ +\x00\x72\x00\x65\x00\x61\x00\x6b\x00\x42\x00\x79\x00\x31\x00\x50\x00\x6f\x00\x69\x00\x6e\x00\x74\x00\x2e\x00\x73\x00\x76\x00\x67\ +\ +\x00\x0a\ +\x0c\x4a\xc5\x87\ +\x00\x65\ +\x00\x78\x00\x74\x00\x65\x00\x6e\x00\x64\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x08\ +\x0c\x33\x57\x07\ +\x00\x68\ +\x00\x65\x00\x6c\x00\x70\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x17\ +\x01\xbb\x4f\x47\ +\x00\x61\ +\x00\x72\x00\x63\x00\x42\x00\x79\x00\x53\x00\x74\x00\x61\x00\x72\x00\x74\x00\x45\x00\x6e\x00\x64\x00\x52\x00\x61\x00\x64\x00\x69\ +\x00\x75\x00\x73\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x08\ +\x06\x01\x54\xe7\ +\x00\x6a\ +\x00\x6f\x00\x69\x00\x6e\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x0d\ +\x03\xdf\x2e\x67\ +\x00\x64\ +\x00\x73\x00\x65\x00\x74\x00\x74\x00\x69\x00\x6e\x00\x67\x00\x73\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x13\ +\x0e\x08\x39\xe7\ +\x00\x63\ +\x00\x69\x00\x72\x00\x63\x00\x6c\x00\x65\x00\x42\x00\x79\x00\x33\x00\x50\x00\x6f\x00\x69\x00\x6e\x00\x74\x00\x73\x00\x2e\x00\x73\ +\x00\x76\x00\x67\ +\x00\x09\ +\x0b\x07\xb7\xa7\ +\x00\x70\ +\x00\x65\x00\x64\x00\x69\x00\x74\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x10\ +\x00\x81\x6c\x87\ +\x00\x6f\ +\x00\x73\x00\x6e\x00\x61\x00\x70\x00\x5f\x00\x65\x00\x78\x00\x74\x00\x49\x00\x6e\x00\x74\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x09\ +\x0b\x7e\x89\xc7\ +\x00\x62\ +\x00\x72\x00\x65\x00\x61\x00\x6b\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x1a\ +\x0a\xe6\x46\x47\ +\x00\x61\ +\x00\x72\x00\x63\x00\x42\x00\x79\x00\x43\x00\x65\x00\x6e\x00\x74\x00\x65\x00\x72\x00\x53\x00\x74\x00\x61\x00\x72\x00\x74\x00\x4c\ +\x00\x65\x00\x6e\x00\x67\x00\x74\x00\x68\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x0d\ +\x0a\x96\x46\x67\ +\x00\x61\ +\x00\x72\x00\x72\x00\x61\x00\x79\x00\x52\x00\x65\x00\x63\x00\x74\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x13\ +\x0b\xa0\xd8\x87\ +\x00\x65\ +\x00\x6c\x00\x6c\x00\x69\x00\x70\x00\x73\x00\x65\x00\x42\x00\x79\x00\x45\x00\x78\x00\x74\x00\x65\x00\x6e\x00\x74\x00\x2e\x00\x73\ +\x00\x76\x00\x67\ +\x00\x17\ +\x02\x27\x1e\x27\ +\x00\x61\ +\x00\x72\x00\x63\x00\x42\x00\x79\x00\x43\x00\x65\x00\x6e\x00\x74\x00\x65\x00\x72\x00\x53\x00\x74\x00\x61\x00\x72\x00\x74\x00\x45\ +\x00\x6e\x00\x64\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x0b\ +\x0c\x63\x2b\x87\ +\x00\x6d\ +\x00\x65\x00\x61\x00\x73\x00\x75\x00\x72\x00\x65\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x09\ +\x00\x48\xb4\xa7\ +\x00\x70\ +\x00\x6c\x00\x69\x00\x6e\x00\x65\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x0d\ +\x07\x48\x57\xc7\ +\x00\x6f\ +\x00\x73\x00\x6e\x00\x61\x00\x70\x00\x5f\x00\x74\x00\x61\x00\x6e\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x0a\ +\x09\xc9\xe3\x67\ +\x00\x6f\ +\x00\x66\x00\x66\x00\x73\x00\x65\x00\x74\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x1a\ +\x05\xdb\x90\xa7\ +\x00\x63\ +\x00\x69\x00\x72\x00\x63\x00\x6c\x00\x65\x00\x42\x00\x79\x00\x43\x00\x65\x00\x6e\x00\x74\x00\x65\x00\x72\x00\x44\x00\x69\x00\x61\ +\x00\x6d\x00\x65\x00\x74\x00\x65\x00\x72\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x1d\ +\x00\xee\x2a\xc7\ +\x00\x61\ +\x00\x72\x00\x63\x00\x42\x00\x79\x00\x53\x00\x74\x00\x61\x00\x72\x00\x74\x00\x43\x00\x65\x00\x6e\x00\x74\x00\x65\x00\x72\x00\x45\ +\x00\x6e\x00\x64\x00\x50\x00\x6f\x00\x69\x00\x6e\x00\x74\x00\x73\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x21\ +\x0a\x4b\xfa\xc7\ +\x00\x73\ +\x00\x65\x00\x74\x00\x63\x00\x75\x00\x72\x00\x72\x00\x75\x00\x70\x00\x64\x00\x61\x00\x74\x00\x65\x00\x61\x00\x62\x00\x6c\x00\x65\ +\x00\x6c\x00\x61\x00\x79\x00\x65\x00\x72\x00\x62\x00\x79\x00\x67\x00\x72\x00\x61\x00\x70\x00\x68\x00\x2e\x00\x73\x00\x76\x00\x67\ +\ +\x00\x1a\ +\x04\x01\x07\x07\ +\x00\x65\ +\x00\x6c\x00\x6c\x00\x69\x00\x70\x00\x73\x00\x65\x00\x42\x00\x79\x00\x43\x00\x65\x00\x6e\x00\x74\x00\x65\x00\x72\x00\x32\x00\x50\ +\x00\x6f\x00\x69\x00\x6e\x00\x74\x00\x73\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x0d\ +\x09\x9a\x47\xa7\ +\x00\x61\ +\x00\x72\x00\x72\x00\x61\x00\x79\x00\x50\x00\x61\x00\x74\x00\x68\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x1a\ +\x0a\x88\x56\xe7\ +\x00\x61\ +\x00\x72\x00\x63\x00\x42\x00\x79\x00\x53\x00\x74\x00\x61\x00\x72\x00\x74\x00\x43\x00\x65\x00\x6e\x00\x74\x00\x65\x00\x72\x00\x4c\ +\x00\x65\x00\x6e\x00\x67\x00\x74\x00\x68\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x13\ +\x0e\x1a\x39\xe7\ +\x00\x63\ +\x00\x69\x00\x72\x00\x63\x00\x6c\x00\x65\x00\x42\x00\x79\x00\x32\x00\x50\x00\x6f\x00\x69\x00\x6e\x00\x74\x00\x73\x00\x2e\x00\x73\ +\x00\x76\x00\x67\ +\x00\x0e\ +\x0e\xe6\xb2\xe7\ +\x00\x65\ +\x00\x6c\x00\x6c\x00\x69\x00\x70\x00\x73\x00\x65\x00\x41\x00\x72\x00\x63\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x0a\ +\x08\x8b\x3a\xe7\ +\x00\x64\ +\x00\x69\x00\x6d\x00\x41\x00\x72\x00\x63\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x0d\ +\x0e\xbe\x57\x47\ +\x00\x6f\ +\x00\x73\x00\x6e\x00\x61\x00\x70\x00\x5f\x00\x6d\x00\x69\x00\x64\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x0d\ +\x0f\x55\x0b\xa7\ +\x00\x72\ +\x00\x65\x00\x63\x00\x74\x00\x61\x00\x6e\x00\x67\x00\x6c\x00\x65\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x0d\ +\x05\x1e\x57\xa7\ +\x00\x6f\ +\x00\x73\x00\x6e\x00\x61\x00\x70\x00\x5f\x00\x69\x00\x6e\x00\x74\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x07\ +\x07\x77\x5a\x27\ +\x00\x71\ +\x00\x61\x00\x64\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x0d\ +\x06\xc3\x54\x07\ +\x00\x64\ +\x00\x69\x00\x6d\x00\x52\x00\x61\x00\x64\x00\x69\x00\x75\x00\x73\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x0d\ +\x0b\x8c\x57\xc7\ +\x00\x6f\ +\x00\x73\x00\x6e\x00\x61\x00\x70\x00\x5f\x00\x70\x00\x65\x00\x72\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x07\ +\x08\x86\x5a\x07\ +\x00\x61\ +\x00\x72\x00\x63\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x08\ +\x06\xc8\x54\x47\ +\x00\x6d\ +\x00\x6f\x00\x76\x00\x65\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x18\ +\x06\x3b\x15\xe7\ +\x00\x63\ +\x00\x69\x00\x72\x00\x63\x00\x6c\x00\x65\x00\x42\x00\x79\x00\x43\x00\x65\x00\x6e\x00\x74\x00\x65\x00\x72\x00\x52\x00\x61\x00\x64\ +\x00\x69\x00\x75\x00\x73\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x17\ +\x0f\x5c\x7a\x67\ +\x00\x65\ +\x00\x6c\x00\x6c\x00\x69\x00\x70\x00\x73\x00\x65\x00\x42\x00\x79\x00\x41\x00\x78\x00\x69\x00\x73\x00\x31\x00\x50\x00\x6f\x00\x69\ +\x00\x6e\x00\x74\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x1a\ +\x03\xe3\x07\x07\ +\x00\x65\ +\x00\x6c\x00\x6c\x00\x69\x00\x70\x00\x73\x00\x65\x00\x42\x00\x79\x00\x43\x00\x65\x00\x6e\x00\x74\x00\x65\x00\x72\x00\x33\x00\x50\ +\x00\x6f\x00\x69\x00\x6e\x00\x74\x00\x73\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x16\ +\x0d\x33\xab\xe7\ +\x00\x61\ +\x00\x72\x00\x63\x00\x42\x00\x79\x00\x53\x00\x74\x00\x61\x00\x72\x00\x74\x00\x45\x00\x6e\x00\x64\x00\x41\x00\x6e\x00\x67\x00\x6c\ +\x00\x65\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x0c\ +\x04\xef\x9d\x27\ +\x00\x6d\ +\x00\x70\x00\x6f\x00\x6c\x00\x79\x00\x67\x00\x6f\x00\x6e\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x0a\ +\x09\x6b\xdb\xe7\ +\x00\x6d\ +\x00\x69\x00\x72\x00\x72\x00\x6f\x00\x72\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x0c\ +\x0c\xa9\x92\x07\ +\x00\x64\ +\x00\x6f\x00\x77\x00\x6e\x00\x5f\x00\x6b\x00\x65\x00\x79\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x0a\ +\x0f\xad\x3b\x47\ +\x00\x64\ +\x00\x69\x00\x76\x00\x69\x00\x64\x00\x65\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x0a\ +\x02\xca\xfa\xa7\ +\x00\x66\ +\x00\x69\x00\x6c\x00\x6c\x00\x65\x00\x74\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x0f\ +\x0e\x9e\xc7\xa7\ +\x00\x6f\ +\x00\x73\x00\x6e\x00\x61\x00\x70\x00\x5f\x00\x6d\x00\x69\x00\x64\x00\x32\x00\x70\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x14\ +\x0a\xd6\x4d\x07\ +\x00\x61\ +\x00\x72\x00\x63\x00\x42\x00\x79\x00\x53\x00\x74\x00\x61\x00\x72\x00\x74\x00\x45\x00\x6e\x00\x64\x00\x54\x00\x61\x00\x6e\x00\x2e\ +\x00\x73\x00\x76\x00\x67\ +\x00\x08\ +\x00\x48\x54\xa7\ +\x00\x6c\ +\x00\x69\x00\x6e\x00\x65\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x0d\ +\x01\xbe\x57\xa7\ +\x00\x6f\ +\x00\x73\x00\x6e\x00\x61\x00\x70\x00\x5f\x00\x65\x00\x78\x00\x74\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x0b\ +\x0c\x22\x6d\x47\ +\x00\x6d\ +\x00\x62\x00\x75\x00\x66\x00\x66\x00\x65\x00\x72\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x0d\ +\x05\x08\x57\xa7\ +\x00\x6f\ +\x00\x73\x00\x6e\x00\x61\x00\x70\x00\x5f\x00\x63\x00\x65\x00\x6e\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x10\ +\x06\x40\x90\xa7\ +\x00\x61\ +\x00\x72\x00\x63\x00\x42\x00\x79\x00\x33\x00\x50\x00\x6f\x00\x69\x00\x6e\x00\x74\x00\x73\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x0b\ +\x0e\xcf\x9d\x27\ +\x00\x70\ +\x00\x6f\x00\x6c\x00\x79\x00\x67\x00\x6f\x00\x6e\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x16\ +\x07\x34\xea\x87\ +\x00\x65\ +\x00\x6c\x00\x6c\x00\x69\x00\x70\x00\x73\x00\x65\x00\x42\x00\x79\x00\x46\x00\x6f\x00\x63\x00\x69\x00\x50\x00\x6f\x00\x69\x00\x6e\ +\x00\x74\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x0d\ +\x0b\xcc\x57\xc7\ +\x00\x6f\ +\x00\x73\x00\x6e\x00\x61\x00\x70\x00\x5f\x00\x70\x00\x61\x00\x72\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x0d\ +\x0c\x09\xd6\x67\ +\x00\x6d\ +\x00\x61\x00\x70\x00\x6d\x00\x70\x00\x65\x00\x64\x00\x69\x00\x74\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x0d\ +\x05\xfd\x57\xc7\ +\x00\x6f\ +\x00\x73\x00\x6e\x00\x61\x00\x70\x00\x5f\x00\x71\x00\x75\x00\x61\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x05\ +\x00\x78\x5a\xc7\ +\x00\x75\ +\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x0c\ +\x06\xb9\x44\x27\ +\x00\x76\ +\x00\x61\x00\x72\x00\x69\x00\x61\x00\x62\x00\x6c\x00\x65\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x14\ +\x0a\xe9\x61\x27\ +\x00\x65\ +\x00\x6c\x00\x6c\x00\x69\x00\x70\x00\x73\x00\x65\x00\x42\x00\x79\x00\x34\x00\x50\x00\x6f\x00\x69\x00\x6e\x00\x74\x00\x73\x00\x2e\ +\x00\x73\x00\x76\x00\x67\ +\x00\x0b\ +\x0a\x6e\x2b\x47\ +\x00\x73\ +\x00\x74\x00\x72\x00\x65\x00\x74\x00\x63\x00\x68\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x0c\ +\x08\xa1\xca\x07\ +\x00\x6f\ +\x00\x73\x00\x6e\x00\x61\x00\x70\x00\x5f\x00\x70\x00\x72\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x0b\ +\x06\xba\xac\xc7\ +\x00\x6f\ +\x00\x70\x00\x74\x00\x69\x00\x6f\x00\x6e\x00\x73\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x0a\ +\x08\xab\x77\x87\ +\x00\x72\ +\x00\x6f\x00\x74\x00\x61\x00\x74\x00\x65\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x19\ +\x0e\x64\xa6\x87\ +\x00\x61\ +\x00\x72\x00\x63\x00\x42\x00\x79\x00\x43\x00\x65\x00\x6e\x00\x74\x00\x65\x00\x72\x00\x53\x00\x74\x00\x61\x00\x72\x00\x74\x00\x41\ +\x00\x6e\x00\x67\x00\x6c\x00\x65\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x0e\ +\x01\x94\x8e\x27\ +\x00\x61\ +\x00\x72\x00\x72\x00\x61\x00\x79\x00\x50\x00\x6f\x00\x6c\x00\x61\x00\x72\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x0d\ +\x09\x0e\x57\xa7\ +\x00\x6f\ +\x00\x73\x00\x6e\x00\x61\x00\x70\x00\x5f\x00\x65\x00\x6e\x00\x64\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x06\ +\x06\xf7\x5a\xc7\ +\x00\x69\ +\x00\x64\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x15\ +\x04\x1b\x6c\x87\ +\x00\x65\ +\x00\x6c\x00\x6c\x00\x69\x00\x70\x00\x73\x00\x65\x00\x46\x00\x72\x00\x6f\x00\x6d\x00\x43\x00\x65\x00\x6e\x00\x74\x00\x65\x00\x72\ +\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x11\ +\x04\x4c\xda\x27\ +\x00\x6f\ +\x00\x73\x00\x6e\x00\x61\x00\x70\x00\x5f\x00\x64\x00\x69\x00\x73\x00\x61\x00\x62\x00\x6c\x00\x65\x00\x2e\x00\x73\x00\x76\x00\x67\ +\ +\x00\x0d\ +\x09\xfd\x57\x47\ +\x00\x6f\ +\x00\x73\x00\x6e\x00\x61\x00\x70\x00\x5f\x00\x6e\x00\x65\x00\x61\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x08\ +\x04\xb2\x55\x47\ +\x00\x75\ +\x00\x6e\x00\x64\x00\x6f\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x0c\ +\x05\x48\x0f\xa7\ +\x00\x64\ +\x00\x69\x00\x6d\x00\x53\x00\x74\x00\x79\x00\x6c\x00\x65\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x08\ +\x09\x00\x55\xa7\ +\x00\x74\ +\x00\x72\x00\x69\x00\x6d\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x19\ +\x0b\x66\x47\x87\ +\x00\x61\ +\x00\x72\x00\x63\x00\x42\x00\x79\x00\x53\x00\x74\x00\x61\x00\x72\x00\x74\x00\x43\x00\x65\x00\x6e\x00\x74\x00\x65\x00\x72\x00\x41\ +\x00\x6e\x00\x67\x00\x6c\x00\x65\x00\x2e\x00\x73\x00\x76\x00\x67\ +\x00\x0d\ +\x08\x1e\x57\x47\ +\x00\x6f\ +\x00\x73\x00\x6e\x00\x61\x00\x70\x00\x5f\x00\x6e\x00\x6f\x00\x64\x00\x2e\x00\x73\x00\x76\x00\x67\ +" + +qt_resource_struct_v1 = b"\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ +\x00\x00\x00\x14\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\ +\x00\x00\x00\x20\x00\x02\x00\x00\x00\x5e\x00\x00\x00\x04\ +\x00\x00\x09\x38\x00\x00\x00\x00\x00\x01\x00\x01\xff\x6a\ +\x00\x00\x04\x98\x00\x00\x00\x00\x00\x01\x00\x00\xf4\x40\ +\x00\x00\x0a\x7e\x00\x00\x00\x00\x00\x01\x00\x02\x41\x2b\ +\x00\x00\x03\x84\x00\x00\x00\x00\x00\x01\x00\x00\xc5\x17\ +\x00\x00\x00\xe8\x00\x00\x00\x00\x00\x01\x00\x00\x27\x55\ +\x00\x00\x05\x24\x00\x00\x00\x00\x00\x01\x00\x01\x18\x42\ +\x00\x00\x01\xe6\x00\x00\x00\x00\x00\x01\x00\x00\x69\xcf\ +\x00\x00\x0b\x82\x00\x01\x00\x00\x00\x01\x00\x02\x79\xb2\ +\x00\x00\x01\x1c\x00\x00\x00\x00\x00\x01\x00\x00\x2e\x82\ +\x00\x00\x02\xd6\x00\x00\x00\x00\x00\x01\x00\x00\x9d\xcd\ +\x00\x00\x09\x4e\x00\x00\x00\x00\x00\x01\x00\x02\x04\x37\ +\x00\x00\x04\x48\x00\x00\x00\x00\x00\x01\x00\x00\xe5\xf5\ +\x00\x00\x02\x7e\x00\x00\x00\x00\x00\x01\x00\x00\x86\xd5\ +\x00\x00\x08\xcc\x00\x00\x00\x00\x00\x01\x00\x01\xec\x8c\ +\x00\x00\x03\x20\x00\x00\x00\x00\x00\x01\x00\x00\xab\x1e\ +\x00\x00\x07\xf0\x00\x00\x00\x00\x00\x01\x00\x01\xc0\x65\ +\x00\x00\x05\xac\x00\x00\x00\x00\x00\x01\x00\x01\x2b\x36\ +\x00\x00\x0b\xd6\x00\x00\x00\x00\x00\x01\x00\x02\x8f\x5d\ +\x00\x00\x0c\x06\x00\x00\x00\x00\x00\x01\x00\x02\x93\xb6\ +\x00\x00\x00\x5e\x00\x00\x00\x00\x00\x01\x00\x00\x0f\x51\ +\x00\x00\x01\x6c\x00\x00\x00\x00\x00\x01\x00\x00\x53\x57\ +\x00\x00\x0c\x4e\x00\x00\x00\x00\x00\x01\x00\x02\xa3\xa6\ +\x00\x00\x01\x8e\x00\x00\x00\x00\x00\x01\x00\x00\x59\xa9\ +\x00\x00\x08\x5c\x00\x00\x00\x00\x00\x01\x00\x01\xcf\xc0\ +\x00\x00\x09\x8a\x00\x00\x00\x00\x00\x01\x00\x02\x11\x77\ +\x00\x00\x06\xe8\x00\x00\x00\x00\x00\x01\x00\x01\x6e\xa9\ +\x00\x00\x02\x3a\x00\x00\x00\x00\x00\x01\x00\x00\x7b\x6e\ +\x00\x00\x0c\x64\x00\x00\x00\x00\x00\x01\x00\x02\xa8\x70\ +\x00\x00\x02\x04\x00\x00\x00\x00\x00\x01\x00\x00\x6f\x5c\ +\x00\x00\x04\xea\x00\x00\x00\x00\x00\x01\x00\x01\x12\x52\ +\x00\x00\x0a\x5e\x00\x00\x00\x00\x00\x01\x00\x02\x37\xff\ +\x00\x00\x03\x0a\x00\x00\x00\x00\x00\x01\x00\x00\xa5\x07\ +\x00\x00\x07\x86\x00\x00\x00\x00\x00\x01\x00\x01\xb5\x13\ +\x00\x00\x09\xaa\x00\x00\x00\x00\x00\x01\x00\x02\x18\x02\ +\x00\x00\x00\x48\x00\x00\x00\x00\x00\x01\x00\x00\x07\x49\ +\x00\x00\x0a\x8e\x00\x00\x00\x00\x00\x01\x00\x02\x46\x40\ +\x00\x00\x0b\x14\x00\x00\x00\x00\x00\x01\x00\x02\x61\x79\ +\x00\x00\x07\x1c\x00\x00\x00\x00\x00\x01\x00\x01\x96\x24\ +\x00\x00\x07\x70\x00\x00\x00\x00\x00\x01\x00\x01\xb0\x1d\ +\x00\x00\x0b\xc4\x00\x00\x00\x00\x00\x01\x00\x02\x87\xaf\ +\x00\x00\x09\xec\x00\x00\x00\x00\x00\x01\x00\x02\x22\xfb\ +\x00\x00\x04\xb0\x00\x00\x00\x00\x00\x01\x00\x00\xf9\xac\ +\x00\x00\x02\x62\x00\x00\x00\x00\x00\x01\x00\x00\x81\x82\ +\x00\x00\x07\x08\x00\x00\x00\x00\x00\x01\x00\x01\x74\x05\ +\x00\x00\x0c\xd0\x00\x00\x00\x00\x00\x01\x00\x02\xd9\x5b\ +\x00\x00\x00\x30\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ +\x00\x00\x07\x5c\x00\x00\x00\x00\x00\x01\x00\x01\xa8\xd4\ +\x00\x00\x06\x8e\x00\x00\x00\x00\x00\x01\x00\x01\x5b\x06\ +\x00\x00\x00\xd0\x00\x00\x00\x00\x00\x01\x00\x00\x21\x16\ +\x00\x00\x0a\xf6\x00\x00\x00\x00\x00\x01\x00\x02\x5c\x75\ +\x00\x00\x0b\x30\x00\x00\x00\x00\x00\x01\x00\x02\x68\x64\ +\x00\x00\x0c\x82\x00\x00\x00\x00\x00\x01\x00\x02\xb4\x0c\ +\x00\x00\x0b\xa4\x00\x00\x00\x00\x00\x01\x00\x02\x80\x34\ +\x00\x00\x00\x9c\x00\x00\x00\x00\x00\x01\x00\x00\x1a\xa1\ +\x00\x00\x08\x7a\x00\x00\x00\x00\x00\x01\x00\x01\xd9\x52\ +\x00\x00\x05\xe6\x00\x00\x00\x00\x00\x01\x00\x01\x30\x45\ +\x00\x00\x04\xd0\x00\x00\x00\x00\x00\x01\x00\x01\x00\xec\ +\x00\x00\x0c\x2e\x00\x00\x00\x00\x00\x01\x00\x02\x9d\x8c\ +\x00\x00\x02\x20\x00\x00\x00\x00\x00\x01\x00\x00\x76\x3e\ +\x00\x00\x05\x64\x00\x00\x00\x00\x00\x01\x00\x01\x1f\x4e\ +\x00\x00\x0a\xda\x00\x00\x00\x00\x00\x01\x00\x02\x54\xa1\ +\x00\x00\x06\x06\x00\x00\x00\x00\x00\x01\x00\x01\x49\xc5\ +\x00\x00\x03\xfc\x00\x00\x00\x00\x00\x01\x00\x00\xd9\x00\ +\x00\x00\x09\x0a\x00\x00\x00\x00\x00\x01\x00\x01\xf9\x63\ +\x00\x00\x03\xc2\x00\x00\x00\x00\x00\x01\x00\x00\xd1\xf5\ +\x00\x00\x0a\xac\x00\x00\x00\x00\x00\x01\x00\x02\x4e\x94\ +\x00\x00\x03\x6c\x00\x00\x00\x00\x00\x01\x00\x00\xba\xea\ +\x00\x00\x0c\x98\x00\x00\x00\x00\x00\x01\x00\x02\xce\x20\ +\x00\x00\x03\xaa\x00\x00\x00\x00\x00\x01\x00\x00\xcb\xc1\ +\x00\x00\x07\x3c\x00\x00\x00\x00\x00\x01\x00\x01\xa3\x6d\ +\x00\x00\x04\x1c\x00\x00\x00\x00\x00\x01\x00\x00\xe0\x5c\ +\x00\x00\x01\xd0\x00\x00\x00\x00\x00\x01\x00\x00\x64\xbb\ +\x00\x00\x0a\x1e\x00\x00\x00\x00\x00\x01\x00\x02\x28\xb4\ +\x00\x00\x0a\x3e\x00\x00\x00\x00\x00\x01\x00\x02\x2e\x14\ +\x00\x00\x09\x6e\x00\x00\x00\x00\x00\x01\x00\x02\x0a\x05\ +\x00\x00\x02\xc0\x00\x00\x00\x00\x00\x01\x00\x00\x92\xf2\ +\x00\x00\x02\xa6\x00\x00\x00\x00\x00\x01\x00\x00\x8c\xa1\ +\x00\x00\x04\x7c\x00\x00\x00\x00\x00\x01\x00\x00\xec\x4e\ +\x00\x00\x00\x80\x00\x00\x00\x00\x00\x01\x00\x00\x14\x88\ +\x00\x00\x01\xb6\x00\x00\x00\x00\x00\x01\x00\x00\x5e\xbd\ +\x00\x00\x08\x94\x00\x00\x00\x00\x00\x01\x00\x01\xdf\x0a\ +\x00\x00\x01\x56\x00\x00\x00\x00\x00\x01\x00\x00\x45\x77\ +\x00\x00\x08\x2a\x00\x00\x00\x00\x00\x01\x00\x01\xc6\x1e\ +\x00\x00\x01\x36\x00\x00\x00\x00\x00\x01\x00\x00\x3f\x2a\ +\x00\x00\x03\x40\x00\x00\x00\x00\x00\x01\x00\x00\xb5\xda\ +\x00\x00\x06\x40\x00\x00\x00\x00\x00\x01\x00\x01\x50\xc5\ +\x00\x00\x0b\x4a\x00\x00\x00\x00\x00\x01\x00\x02\x6e\x81\ +\x00\x00\x08\xe6\x00\x00\x00\x00\x00\x01\x00\x01\xf2\x5a\ +\x00\x00\x06\xa8\x00\x00\x00\x00\x00\x01\x00\x01\x64\xc2\ +\x00\x00\x09\xd0\x00\x00\x00\x00\x00\x01\x00\x02\x1d\x80\ +\x00\x00\x06\x6c\x00\x00\x00\x00\x00\x01\x00\x01\x55\x74\ +\x00\x00\x06\xc8\x00\x00\x00\x00\x00\x01\x00\x01\x69\xd4\ +\x00\x00\x07\xbc\x00\x00\x00\x00\x00\x01\x00\x01\xba\x8d\ +\x00\x00\x08\xb2\x00\x00\x00\x00\x00\x01\x00\x01\xe4\xe3\ +" + +qt_resource_struct_v2 = b"\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x14\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x20\x00\x02\x00\x00\x00\x5e\x00\x00\x00\x04\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x09\x38\x00\x00\x00\x00\x00\x01\x00\x01\xff\x6a\ +\x00\x00\x01\x92\x09\x38\x25\x2f\ +\x00\x00\x04\x98\x00\x00\x00\x00\x00\x01\x00\x00\xf4\x40\ +\x00\x00\x01\x92\x09\x38\x30\xb8\ +\x00\x00\x0a\x7e\x00\x00\x00\x00\x00\x01\x00\x02\x41\x2b\ +\x00\x00\x01\x92\x09\x38\x36\x27\ +\x00\x00\x03\x84\x00\x00\x00\x00\x00\x01\x00\x00\xc5\x17\ +\x00\x00\x01\x92\x09\x38\x2b\xc3\ +\x00\x00\x00\xe8\x00\x00\x00\x00\x00\x01\x00\x00\x27\x55\ +\x00\x00\x01\x92\x09\x38\x33\x84\ +\x00\x00\x05\x24\x00\x00\x00\x00\x00\x01\x00\x01\x18\x42\ +\x00\x00\x01\x92\x09\x38\x11\xe8\ +\x00\x00\x01\xe6\x00\x00\x00\x00\x00\x01\x00\x00\x69\xcf\ +\x00\x00\x01\x92\x09\x38\x24\xcb\ +\x00\x00\x0b\x82\x00\x01\x00\x00\x00\x01\x00\x02\x79\xb2\ +\x00\x00\x01\x92\x09\x38\x14\x6d\ +\x00\x00\x01\x1c\x00\x00\x00\x00\x00\x01\x00\x00\x2e\x82\ +\x00\x00\x01\x92\x09\x38\x25\x93\ +\x00\x00\x02\xd6\x00\x00\x00\x00\x00\x01\x00\x00\x9d\xcd\ +\x00\x00\x01\x92\x09\x38\x13\x26\ +\x00\x00\x09\x4e\x00\x00\x00\x00\x00\x01\x00\x02\x04\x37\ +\x00\x00\x01\x92\x09\x38\x2b\x5f\ +\x00\x00\x04\x48\x00\x00\x00\x00\x00\x01\x00\x00\xe5\xf5\ +\x00\x00\x01\x92\x09\x38\x10\xa7\ +\x00\x00\x02\x7e\x00\x00\x00\x00\x00\x01\x00\x00\x86\xd5\ +\x00\x00\x01\x92\x09\x38\x15\xaa\ +\x00\x00\x08\xcc\x00\x00\x00\x00\x00\x01\x00\x01\xec\x8c\ +\x00\x00\x01\x92\x09\x38\x22\x44\ +\x00\x00\x03\x20\x00\x00\x00\x00\x00\x01\x00\x00\xab\x1e\ +\x00\x00\x01\x92\x09\x38\x1d\x3f\ +\x00\x00\x07\xf0\x00\x00\x00\x00\x00\x01\x00\x01\xc0\x65\ +\x00\x00\x01\x92\x09\x38\x1f\xc1\ +\x00\x00\x05\xac\x00\x00\x00\x00\x00\x01\x00\x01\x2b\x36\ +\x00\x00\x01\x92\x09\x38\x1f\x5d\ +\x00\x00\x0b\xd6\x00\x00\x00\x00\x00\x01\x00\x02\x8f\x5d\ +\x00\x00\x01\x92\x09\x38\x21\x0e\ +\x00\x00\x0c\x06\x00\x00\x00\x00\x00\x01\x00\x02\x93\xb6\ +\x00\x00\x01\x92\x09\x38\x2a\x24\ +\x00\x00\x00\x5e\x00\x00\x00\x00\x00\x01\x00\x00\x0f\x51\ +\x00\x00\x01\x92\x09\x38\x34\xeb\ +\x00\x00\x01\x6c\x00\x00\x00\x00\x00\x01\x00\x00\x53\x57\ +\x00\x00\x01\x92\x09\x38\x19\xe4\ +\x00\x00\x0c\x4e\x00\x00\x00\x00\x00\x01\x00\x02\xa3\xa6\ +\x00\x00\x01\x92\x09\x38\x36\x8b\ +\x00\x00\x01\x8e\x00\x00\x00\x00\x00\x01\x00\x00\x59\xa9\ +\x00\x00\x01\x92\x09\x38\x17\xb7\ +\x00\x00\x08\x5c\x00\x00\x00\x00\x00\x01\x00\x01\xcf\xc0\ +\x00\x00\x01\x92\x09\x38\x28\x07\ +\x00\x00\x09\x8a\x00\x00\x00\x00\x00\x01\x00\x02\x11\x77\ +\x00\x00\x01\x92\x09\x38\x29\xb0\ +\x00\x00\x06\xe8\x00\x00\x00\x00\x00\x01\x00\x01\x6e\xa9\ +\x00\x00\x01\x92\x09\x38\x2c\x28\ +\x00\x00\x02\x3a\x00\x00\x00\x00\x00\x01\x00\x00\x7b\x6e\ +\x00\x00\x01\x92\x09\x38\x2a\xec\ +\x00\x00\x0c\x64\x00\x00\x00\x00\x00\x01\x00\x02\xa8\x70\ +\x00\x00\x01\x92\x09\x38\x1b\x95\ +\x00\x00\x02\x04\x00\x00\x00\x00\x00\x01\x00\x00\x6f\x5c\ +\x00\x00\x01\x92\x09\x38\x1c\x03\ +\x00\x00\x04\xea\x00\x00\x00\x00\x00\x01\x00\x01\x12\x52\ +\x00\x00\x01\x92\x09\x38\x18\x2b\ +\x00\x00\x0a\x5e\x00\x00\x00\x00\x00\x01\x00\x02\x37\xff\ +\x00\x00\x01\x92\x09\x38\x2f\x82\ +\x00\x00\x03\x0a\x00\x00\x00\x00\x00\x01\x00\x00\xa5\x07\ +\x00\x00\x01\x92\x09\x38\x23\xf4\ +\x00\x00\x07\x86\x00\x00\x00\x00\x00\x01\x00\x01\xb5\x13\ +\x00\x00\x01\x92\x09\x38\x18\x9f\ +\x00\x00\x09\xaa\x00\x00\x00\x00\x00\x01\x00\x02\x18\x02\ +\x00\x00\x01\x92\x09\x38\x0f\xe2\ +\x00\x00\x00\x48\x00\x00\x00\x00\x00\x01\x00\x00\x07\x49\ +\x00\x00\x01\x92\x09\x38\x19\x12\ +\x00\x00\x0a\x8e\x00\x00\x00\x00\x00\x01\x00\x02\x46\x40\ +\x00\x00\x01\x92\x09\x38\x36\xff\ +\x00\x00\x0b\x14\x00\x00\x00\x00\x00\x01\x00\x02\x61\x79\ +\x00\x00\x01\x92\x09\x38\x28\xe8\ +\x00\x00\x07\x1c\x00\x00\x00\x00\x00\x01\x00\x01\x96\x24\ +\x00\x00\x01\x92\x09\x38\x1b\x21\ +\x00\x00\x07\x70\x00\x00\x00\x00\x00\x01\x00\x01\xb0\x1d\ +\x00\x00\x01\x92\x09\x38\x27\xa3\ +\x00\x00\x0b\xc4\x00\x00\x00\x00\x00\x01\x00\x02\x87\xaf\ +\x00\x00\x01\x92\x09\x38\x23\x1c\ +\x00\x00\x09\xec\x00\x00\x00\x00\x00\x01\x00\x02\x22\xfb\ +\x00\x00\x01\x92\x09\x38\x20\x9a\ +\x00\x00\x04\xb0\x00\x00\x00\x00\x00\x01\x00\x00\xf9\xac\ +\x00\x00\x01\x92\x09\x38\x2f\xe6\ +\x00\x00\x02\x62\x00\x00\x00\x00\x00\x01\x00\x00\x81\x82\ +\x00\x00\x01\x92\x09\x38\x1d\xb3\ +\x00\x00\x07\x08\x00\x00\x00\x00\x00\x01\x00\x01\x74\x05\ +\x00\x00\x01\x92\x09\x38\x31\x80\ +\x00\x00\x0c\xd0\x00\x00\x00\x00\x00\x01\x00\x02\xd9\x5b\ +\x00\x00\x01\x92\x09\x38\x2d\xe3\ +\x00\x00\x00\x30\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ +\x00\x00\x01\x92\x09\x38\x33\x10\ +\x00\x00\x07\x5c\x00\x00\x00\x00\x00\x01\x00\x01\xa8\xd4\ +\x00\x00\x01\x92\x09\x38\x0f\x74\ +\x00\x00\x06\x8e\x00\x00\x00\x00\x00\x01\x00\x01\x5b\x06\ +\x00\x00\x01\x92\x09\x38\x1a\x48\ +\x00\x00\x00\xd0\x00\x00\x00\x00\x00\x01\x00\x00\x21\x16\ +\x00\x00\x01\x92\x09\x38\x21\x72\ +\x00\x00\x0a\xf6\x00\x00\x00\x00\x00\x01\x00\x02\x5c\x75\ +\x00\x00\x01\x92\x09\x38\x2f\x0f\ +\x00\x00\x0b\x30\x00\x00\x00\x00\x00\x01\x00\x02\x68\x64\ +\x00\x00\x01\x92\x09\x38\x32\xac\ +\x00\x00\x0c\x82\x00\x00\x00\x00\x00\x01\x00\x02\xb4\x0c\ +\x00\x00\x01\x92\x09\x38\x35\xc3\ +\x00\x00\x0b\xa4\x00\x00\x00\x00\x00\x01\x00\x02\x80\x34\ +\x00\x00\x01\x92\x09\x82\xc1\x32\ +\x00\x00\x00\x9c\x00\x00\x00\x00\x00\x01\x00\x00\x1a\xa1\ +\x00\x00\x01\x92\x09\x38\x16\xe5\ +\x00\x00\x08\x7a\x00\x00\x00\x00\x00\x01\x00\x01\xd9\x52\ +\x00\x00\x01\x92\x09\x38\x27\x3f\ +\x00\x00\x05\xe6\x00\x00\x00\x00\x00\x01\x00\x01\x30\x45\ +\x00\x00\x01\x92\x09\x38\x14\x08\ +\x00\x00\x04\xd0\x00\x00\x00\x00\x00\x01\x00\x01\x00\xec\ +\x00\x00\x01\x92\x09\x38\x28\x7b\ +\x00\x00\x0c\x2e\x00\x00\x00\x00\x00\x01\x00\x02\x9d\x8c\ +\x00\x00\x01\x92\x09\x38\x2d\x6d\ +\x00\x00\x02\x20\x00\x00\x00\x00\x00\x01\x00\x00\x76\x3e\ +\x00\x00\x01\x92\x09\x38\x16\x0e\ +\x00\x00\x05\x64\x00\x00\x00\x00\x00\x01\x00\x01\x1f\x4e\ +\x00\x00\x01\x92\x09\x38\x33\xfe\ +\x00\x00\x0a\xda\x00\x00\x00\x00\x00\x01\x00\x02\x54\xa1\ +\x00\x00\x01\x92\x09\x38\x34\x72\ +\x00\x00\x06\x06\x00\x00\x00\x00\x00\x01\x00\x01\x49\xc5\ +\x00\x00\x01\x92\x09\x38\x12\x4f\ +\x00\x00\x03\xfc\x00\x00\x00\x00\x00\x01\x00\x00\xd9\x00\ +\x00\x00\x01\x92\x09\x38\x14\xd1\ +\x00\x00\x09\x0a\x00\x00\x00\x00\x00\x01\x00\x01\xf9\x63\ +\x00\x00\x01\x92\x09\x38\x13\x9a\ +\x00\x00\x03\xc2\x00\x00\x00\x00\x00\x01\x00\x00\xd1\xf5\ +\x00\x00\x01\x92\x09\x38\x11\x14\ +\x00\x00\x0a\xac\x00\x00\x00\x00\x00\x01\x00\x02\x4e\x94\ +\x00\x00\x01\x92\x09\x38\x1e\x8a\ +\x00\x00\x03\x6c\x00\x00\x00\x00\x00\x01\x00\x00\xba\xea\ +\x00\x00\x01\x92\x09\x38\x30\x55\ +\x00\x00\x0c\x98\x00\x00\x00\x00\x00\x01\x00\x02\xce\x20\ +\x00\x00\x01\x92\x09\x38\x11\x7b\ +\x00\x00\x03\xaa\x00\x00\x00\x00\x00\x01\x00\x00\xcb\xc1\ +\x00\x00\x01\x92\x09\x38\x15\x35\ +\x00\x00\x07\x3c\x00\x00\x00\x00\x00\x01\x00\x01\xa3\x6d\ +\x00\x00\x01\x92\x09\x38\x2e\xab\ +\x00\x00\x04\x1c\x00\x00\x00\x00\x00\x01\x00\x00\xe0\x5c\ +\x00\x00\x01\x92\x09\x38\x20\x36\ +\x00\x00\x01\xd0\x00\x00\x00\x00\x00\x01\x00\x00\x64\xbb\ +\x00\x00\x01\x92\x09\x38\x32\x49\ +\x00\x00\x0a\x1e\x00\x00\x00\x00\x00\x01\x00\x02\x28\xb4\ +\x00\x00\x01\x92\x09\x38\x2e\x47\ +\x00\x00\x0a\x3e\x00\x00\x00\x00\x00\x01\x00\x02\x2e\x14\ +\x00\x00\x01\x92\x09\x38\x26\x01\ +\x00\x00\x09\x6e\x00\x00\x00\x00\x00\x01\x00\x02\x0a\x05\ +\x00\x00\x01\x92\x09\x38\x26\x65\ +\x00\x00\x02\xc0\x00\x00\x00\x00\x00\x01\x00\x00\x92\xf2\ +\x00\x00\x01\x92\x09\x38\x22\xb8\ +\x00\x00\x02\xa6\x00\x00\x00\x00\x00\x01\x00\x00\x8c\xa1\ +\x00\x00\x01\x92\x09\x38\x21\xe0\ +\x00\x00\x04\x7c\x00\x00\x00\x00\x00\x01\x00\x00\xec\x4e\ +\x00\x00\x01\x92\x09\x38\x26\xc9\ +\x00\x00\x00\x80\x00\x00\x00\x00\x00\x01\x00\x00\x14\x88\ +\x00\x00\x01\x92\x09\x38\x19\x76\ +\x00\x00\x01\xb6\x00\x00\x00\x00\x00\x01\x00\x00\x5e\xbd\ +\x00\x00\x01\x92\x09\x38\x23\x90\ +\x00\x00\x08\x94\x00\x00\x00\x00\x00\x01\x00\x01\xdf\x0a\ +\x00\x00\x01\x92\x09\x38\x1c\xda\ +\x00\x00\x01\x56\x00\x00\x00\x00\x00\x01\x00\x00\x45\x77\ +\x00\x00\x01\x92\x09\x38\x35\x4f\ +\x00\x00\x08\x2a\x00\x00\x00\x00\x00\x01\x00\x01\xc6\x1e\ +\x00\x00\x01\x92\x09\x38\x12\xc2\ +\x00\x00\x01\x36\x00\x00\x00\x00\x00\x01\x00\x00\x3f\x2a\ +\x00\x00\x01\x92\x09\x38\x1a\xbd\ +\x00\x00\x03\x40\x00\x00\x00\x00\x00\x01\x00\x00\xb5\xda\ +\x00\x00\x01\x92\x09\x38\x17\x53\ +\x00\x00\x06\x40\x00\x00\x00\x00\x00\x01\x00\x01\x50\xc5\ +\x00\x00\x01\x92\x09\x38\x16\x72\ +\x00\x00\x0b\x4a\x00\x00\x00\x00\x00\x01\x00\x02\x6e\x81\ +\x00\x00\x01\x92\x09\x38\x10\x4c\ +\x00\x00\x08\xe6\x00\x00\x00\x00\x00\x01\x00\x01\xf2\x5a\ +\x00\x00\x01\x92\x09\x38\x2d\x09\ +\x00\x00\x06\xa8\x00\x00\x00\x00\x00\x01\x00\x01\x64\xc2\ +\x00\x00\x01\x92\x09\x38\x2c\xa5\ +\x00\x00\x09\xd0\x00\x00\x00\x00\x00\x01\x00\x02\x1d\x80\ +\x00\x00\x01\x92\x09\x38\x31\x1c\ +\x00\x00\x06\x6c\x00\x00\x00\x00\x00\x01\x00\x01\x55\x74\ +\x00\x00\x01\x92\x09\x38\x1e\x17\ +\x00\x00\x06\xc8\x00\x00\x00\x00\x00\x01\x00\x01\x69\xd4\ +\x00\x00\x01\x92\x09\x38\x31\xda\ +\x00\x00\x07\xbc\x00\x00\x00\x00\x00\x01\x00\x01\xba\x8d\ +\x00\x00\x01\x92\x09\x38\x1e\xee\ +\x00\x00\x08\xb2\x00\x00\x00\x00\x00\x01\x00\x01\xe4\xe3\ +\x00\x00\x01\x92\x09\x38\x1c\x67\ +" + +qt_version = [int(v) for v in QtCore.qVersion().split('.')] +if qt_version < [5, 8, 0]: + rcc_version = 1 + qt_resource_struct = qt_resource_struct_v1 +else: + rcc_version = 2 + qt_resource_struct = qt_resource_struct_v2 + +def qInitResources(): + QtCore.qRegisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data) + +def qCleanupResources(): + QtCore.qUnregisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data) + +qInitResources() diff --git a/qad_rectangle_maptool.py b/qad_rectangle_maptool.py deleted file mode 100644 index aaf5c6dc..00000000 --- a/qad_rectangle_maptool.py +++ /dev/null @@ -1,122 +0,0 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - classe per gestire il map tool in ambito del comando rectangle - - ------------------- - begin : 2013-12-3 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -from qgis.gui import * -import math - - -import qad_utils -from qad_snapper import * -from qad_snappointsdisplaymanager import * -from qad_variables import * -from qad_getpoint import * -from qad_rubberband import QadRubberBand - - -#=============================================================================== -# Qad_rectangle_maptool_ModeEnum class. -#=============================================================================== -class Qad_rectangle_maptool_ModeEnum(): - # noto niente si richiede il primo angolo - NONE_KNOWN_ASK_FOR_FIRST_CORNER = 1 - # noto il primo angolo si richiede l'angolo opposto - FIRST_CORNER_KNOWN_ASK_FOR_SECOND_CORNER = 2 - -#=============================================================================== -# Qad_rotate_maptool class -#=============================================================================== -class Qad_rectangle_maptool(QadGetPoint): - - def __init__(self, plugIn): - QadGetPoint.__init__(self, plugIn) - - self.firstCorner = None - self.secondCorner = None - self.basePt = None - self.gapType = 0 # 0 = Angoli retti; 1 = Raccorda i segmenti; 2 = Cima i segmenti - self.gapValue1 = 0 # se gapType = 1 -> raggio di curvatura; se gapType = 2 -> prima distanza di cimatura - self.gapValue2 = 0 # se gapType = 2 -> seconda distanza di cimatura - self.rot = 0 - self.vertices = [] - - self.__rubberBand = QadRubberBand(self.canvas, True) - self.geomType = QGis.Polygon - - def hidePointMapToolMarkers(self): - QadGetPoint.hidePointMapToolMarkers(self) - self.__rubberBand.hide() - - def showPointMapToolMarkers(self): - QadGetPoint.showPointMapToolMarkers(self) - self.__rubberBand.show() - - def clear(self): - QadGetPoint.clear(self) - self.__rubberBand.reset() - self.mode = None - - def canvasMoveEvent(self, event): - QadGetPoint.canvasMoveEvent(self, event) - - self.__rubberBand.reset() - - result = False - del self.vertices[:] # svuoto la lista - - # noto il primo angolo si richiede l'angolo opposto - if self.mode == Qad_rectangle_maptool_ModeEnum.FIRST_CORNER_KNOWN_ASK_FOR_SECOND_CORNER: - self.vertices.extend(qad_utils.getRectByCorners(self.firstCorner, self.tmpPoint, self.rot, \ - self.gapType, self.gapValue1, self.gapValue2)) - result = True - - if result == True: - if self.vertices is not None: - if self.geomType == QGis.Polygon: - self.__rubberBand.setPolygon(self.vertices) - else: - self.__rubberBand.setLine(self.vertices) - - def activate(self): - QadGetPoint.activate(self) - self.__rubberBand.show() - - def deactivate(self): - try: # necessario perché se si chiude QGIS parte questo evento nonostante non ci sia più l'oggetto maptool ! - QadGetPoint.deactivate(self) - self.__rubberBand.hide() - except: - pass - - def setMode(self, mode): - self.mode = mode - # noto niente si richiede il primo angolo - if self.mode == Qad_rectangle_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_FIRST_CORNER: - self.setDrawMode(QadGetPointDrawModeEnum.NONE) - # noto il primo angolo si richiede l'angolo opposto - elif self.mode == Qad_rectangle_maptool_ModeEnum.FIRST_CORNER_KNOWN_ASK_FOR_SECOND_CORNER: - self.setDrawMode(QadGetPointDrawModeEnum.NONE) diff --git a/qad_rightclick.ui b/qad_rightclick.ui new file mode 100644 index 00000000..52ea2af2 --- /dev/null +++ b/qad_rightclick.ui @@ -0,0 +1,415 @@ + + + RightClick_Dialog + + + + 0 + 0 + 424 + 424 + + + + + 424 + 424 + + + + + 424 + 424 + + + + Right click customization + + + + + + + + 10 + 10 + 361 + 17 + + + + Controls right-click behavior. A quick click is the same as pressing Enter. A longer click displays a shortcut menu. You can set the duration of the longer click in milliseconds. + + + Turn on Time-Sensitive Right-Click + + + + + + 40 + 30 + 331 + 21 + + + + Quick click for ENTER + + + + + + 40 + 50 + 331 + 21 + + + + Longer click to display Shortcut Menu + + + + + + 10 + 110 + 401 + 71 + + + + Default Mode + + + + + 10 + 30 + 341 + 17 + + + + Disables the Default shortcut menu. As a result, right-clicking in the drawing area when no objects are selected and no commands are in progress is the same as pressing Enter, which repeats the last issued command. + + + Repeat last command + + + + + + 10 + 50 + 341 + 17 + + + + Enables the Default shortcut menu. + + + Shortcut Menu + + + + + + 10 + 10 + 381 + 20 + + + + If no objects are selected, right click means + + + + + + + 10 + 190 + 401 + 71 + + + + Edit Mode + + + + + 10 + 30 + 341 + 17 + + + + Disables the Edit shortcut menu. As a result, right-clicking in the drawing area when one or more objects are selected and no commands are in progress is the same as pressing Enter, which repeats the last issued command. + + + Repeat last command + + + + + + 10 + 50 + 341 + 17 + + + + Enables the Edit shortcut menu. + + + Shortcut Menu + + + + + + 10 + 10 + 381 + 20 + + + + If one or more objects are selected, right click means + + + + + + + 10 + 270 + 401 + 111 + + + + Command Mode + + + + + 10 + 30 + 341 + 17 + + + + Disables the Command shortcut menu. As a result, right-clicking in the drawing area when a command is in progress is the same as pressing Enter. + + + ENTER + + + + + + 10 + 50 + 341 + 17 + + + + Enables the Command shortcut menu. + + + Shortcut Menu: always enabled + + + + + + 10 + 10 + 381 + 20 + + + + If a command is in progress, right click means + + + + + + 10 + 70 + 341 + 31 + + + + Enables the Command shortcut menu only when command options are available at the Command prompt. If no options are available, a right-click is the same as pressing Enter. + + + Shortcut Menu: enabled when command options are present + + + + + + + 160 + 80 + 41 + 20 + + + + Controls right-click behavior. A quick click is the same as pressing Enter. A longer click displays a shortcut menu. You can set the duration of the longer click in milliseconds. + + + + + + 210 + 80 + 121 + 21 + + + + milliseconds + + + + + + 20 + 80 + 131 + 16 + + + + Longer click duration: + + + + + + 260 + 390 + 71 + 23 + + + + Cancel + + + + + + 130 + 390 + 121 + 23 + + + + Apply && Close + + + + + + 340 + 390 + 75 + 23 + + + + Help + + + + + + + Button_ApplyClose + clicked() + RightClick_Dialog + applyClose_clicked() + + + 140 + 407 + + + 9 + 404 + + + + + Button_Cancel + clicked() + RightClick_Dialog + cancel_clicked() + + + 269 + 399 + + + 260 + 383 + + + + + Button_Help + clicked() + RightClick_Dialog + help_clicked() + + + 344 + 395 + + + 342 + 381 + + + + + checkBox_timeSensitive + clicked() + RightClick_Dialog + timeSensitive_clicked() + + + 83 + 20 + + + 364 + 80 + + + + + + applyClose_clicked() + cancel_clicked() + help_clicked() + timeSensitive_clicked() + + diff --git a/qad_rightclick_dlg.py b/qad_rightclick_dlg.py new file mode 100644 index 00000000..90c24538 --- /dev/null +++ b/qad_rightclick_dlg.py @@ -0,0 +1,207 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + Gestione dei click destro del mouse di QAD + + ------------------- + begin : 2016-17-02 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.PyQt.QtWidgets import QDialog +from qgis.PyQt.QtCore import * +from qgis.PyQt.QtGui import * +from qgis.core import * +from qgis.utils import * +from qgis.gui import * + + +from .qad_rightclick_ui import Ui_RightClick_Dialog + + +from .qad_variables import QadVariables, QadVariable +from .qad_msg import QadMsg, qadShowPluginPDFHelp +from . import qad_utils + + +####################################################################################### +# Classe che gestisce l'interfaccia grafica per il click destro del mouse +class QadRightClickDialog(QDialog, QObject, Ui_RightClick_Dialog): + def __init__(self, plugIn, parent): + self.plugIn = plugIn + self.iface = self.plugIn.iface.mainWindow() + + QDialog.__init__(self, parent) + + self.setupUi(self) + self.setWindowTitle(QadMsg.getQADTitle() + " - " + self.windowTitle()) + + # Inizializzazione dei valori + self.init_values() + + + def eventFilter(self, obj, event): + if event is not None: + if event.type() == QEvent.FocusOut: + if obj == self.lineEdit_duration: + return not self.lineEdit_SHORTCUTMENUDURATION_Validation() + + # standard event processing + return QObject.eventFilter(self, obj, event); + + + # ============================================================================ + # timeSensitive_clicked + # ============================================================================ + def timeSensitive_clicked(self): + if self.checkBox_timeSensitive.checkState() == Qt.Checked: + self.lineEdit_duration.setEnabled(True) + self.groupBox_default.setEnabled(False) + self.groupBox_command.setEnabled(False) + else: + self.lineEdit_duration.setEnabled(False) + self.groupBox_default.setEnabled(True) + self.groupBox_command.setEnabled(True) + + + def lineEdit_SHORTCUTMENUDURATION_Validation(self): + varName = QadMsg.translate("Environment variables", "SHORTCUTMENUDURATION") + var = QadVariables.getVariable(QadMsg.translate("Environment variables", varName)) + return qad_utils.intLineEditWidgetValidation(self.lineEdit_duration, \ + var, \ + QadMsg.translate("RightClick_Dialog", "Invalid duration time")) + + + # ============================================================================ + # init_values + # ============================================================================ + def init_values(self): + # Inizializzazione dei valori + shortCutMenu = QadVariables.get(QadMsg.translate("Environment variables", "SHORTCUTMENU")) + if shortCutMenu == 0: + shortCutMenu = 11 # inizializzo questo default + shortCutMenuDuration = QadVariables.get(QadMsg.translate("Environment variables", "SHORTCUTMENUDURATION")) + + self.lineEdit_duration.setText(str(shortCutMenuDuration)) + self.lineEdit_duration.setValidator(QIntValidator(self.lineEdit_duration)) + self.lineEdit_duration.installEventFilter(self) + + # 1 = Enables Default mode shortcut menus + if shortCutMenu & 1: + self.radioButton_default_shortcut.setChecked(True) + else: + self.radioButton_default_last_cmd.setChecked(True) + + # 2 = Enables Edit mode shortcut menus + if shortCutMenu & 2: + self.radioButton_edit_shortcut.setChecked(True) + else: + self.radioButton_edit_last_cmd.setChecked(True) + + # 4 = Enables Command mode shortcut menus whenever a command is active. + if shortCutMenu & 4: + self.radioButton_cmd_shortcut.setChecked(True) + else: + # 8 = Enables Command mode shortcut menus only when command options are currently available at the Command prompt. + if shortCutMenu & 8: + self.radioButton_cmd_shortcut_with_options.setChecked(True) + else: + self.radioButton_cmd_enter.setChecked(True) + + # 16 = Enables the display of a shortcut menu when the right button on the pointing device is held down long enough + if shortCutMenu & 16: + self.checkBox_timeSensitive.setChecked(True) + + self.timeSensitive_clicked() + + + # ============================================================================ + # getShortCutMenuValue + # ============================================================================ + def getShortCutMenuValue(self): + # ritorna la composizione bit a bit della variabile SHORTCUTMENU leggendo i valori dei vari widget della dialog + shortCutMenu = 0 + # 1 = Enables Default mode shortcut menus + if self.radioButton_default_shortcut.isChecked(): + shortCutMenu = shortCutMenu | 1 + + # 2 = Enables Edit mode shortcut menus + if self.radioButton_edit_shortcut.isChecked(): + shortCutMenu = shortCutMenu | 2 + + # 4 = Enables Command mode shortcut menus whenever a command is active. + if self.radioButton_cmd_shortcut.isChecked(): + shortCutMenu = shortCutMenu | 4 + + # 8 = Enables Command mode shortcut menus only when command options are currently available at the Command prompt. + if self.radioButton_cmd_shortcut_with_options.isChecked(): + shortCutMenu = shortCutMenu | 8 + + # 16 = Enables the display of a shortcut menu when the right button on the pointing device is held down long enough + if self.checkBox_timeSensitive.checkState() == Qt.Checked: + shortCutMenu = shortCutMenu | 16 + + return shortCutMenu + + + # ============================================================================ + # getSysVariableList + # ============================================================================ + def getSysVariableList(self): + # ritorna una lista di variabili gestite da questa finestra + variables = [] + + variable = QadVariables.getVariable(QadMsg.translate("Environment variables", "SHORTCUTMENUDURATION")) + varValue = qad_utils.str2int(self.lineEdit_duration.text()) + variables.append(QadVariable(variable.name, varValue, variable.typeValue)) + + variable = QadVariables.getVariable(QadMsg.translate("Environment variables", "SHORTCUTMENU")) + varValue = self.getShortCutMenuValue() + variables.append(QadVariable(variable.name, varValue, variable.typeValue)) + + return variables + + + # ============================================================================ + # applyClose_clicked + # ============================================================================ + def applyClose_clicked(self): + # Memorizzo il valore di SHORTCUTMENUDURATION + value = self.lineEdit_duration.text() + shortCutMenuDuration = qad_utils.str2int(value) + QadVariables.set(QadMsg.translate("Environment variables", "SHORTCUTMENUDURATION"), shortCutMenuDuration) + + shortCutMenu = self.getShortCutMenuValue() + QadVariables.set(QadMsg.translate("Environment variables", "SHORTCUTMENU"), shortCutMenu) + + QDialog.accept(self) + + + # ============================================================================ + # cancel_clicked + # ============================================================================ + def cancel_clicked(self): + QDialog.reject(self) + + + # ============================================================================ + # help_clicked + # ============================================================================ + def help_clicked(self): + qadShowPluginPDFHelp(QadMsg.translate("Help", "")) diff --git a/qad_rightclick_ui.py b/qad_rightclick_ui.py new file mode 100644 index 00000000..086ee044 --- /dev/null +++ b/qad_rightclick_ui.py @@ -0,0 +1,127 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file '.\qad_rightclick.ui' +# +# Created by: PyQt5 UI code generator 5.15.4 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_RightClick_Dialog(object): + def setupUi(self, RightClick_Dialog): + RightClick_Dialog.setObjectName("RightClick_Dialog") + RightClick_Dialog.resize(424, 424) + RightClick_Dialog.setMinimumSize(QtCore.QSize(424, 424)) + RightClick_Dialog.setMaximumSize(QtCore.QSize(424, 424)) + RightClick_Dialog.setToolTip("") + self.checkBox_timeSensitive = QtWidgets.QCheckBox(RightClick_Dialog) + self.checkBox_timeSensitive.setGeometry(QtCore.QRect(10, 10, 361, 17)) + self.checkBox_timeSensitive.setObjectName("checkBox_timeSensitive") + self.label = QtWidgets.QLabel(RightClick_Dialog) + self.label.setGeometry(QtCore.QRect(40, 30, 331, 21)) + self.label.setObjectName("label") + self.label_2 = QtWidgets.QLabel(RightClick_Dialog) + self.label_2.setGeometry(QtCore.QRect(40, 50, 331, 21)) + self.label_2.setObjectName("label_2") + self.groupBox_default = QtWidgets.QGroupBox(RightClick_Dialog) + self.groupBox_default.setGeometry(QtCore.QRect(10, 110, 401, 71)) + self.groupBox_default.setObjectName("groupBox_default") + self.radioButton_default_last_cmd = QtWidgets.QRadioButton(self.groupBox_default) + self.radioButton_default_last_cmd.setGeometry(QtCore.QRect(10, 30, 341, 17)) + self.radioButton_default_last_cmd.setObjectName("radioButton_default_last_cmd") + self.radioButton_default_shortcut = QtWidgets.QRadioButton(self.groupBox_default) + self.radioButton_default_shortcut.setGeometry(QtCore.QRect(10, 50, 341, 17)) + self.radioButton_default_shortcut.setObjectName("radioButton_default_shortcut") + self.label_3 = QtWidgets.QLabel(self.groupBox_default) + self.label_3.setGeometry(QtCore.QRect(10, 10, 381, 20)) + self.label_3.setObjectName("label_3") + self.groupBox_edit = QtWidgets.QGroupBox(RightClick_Dialog) + self.groupBox_edit.setGeometry(QtCore.QRect(10, 190, 401, 71)) + self.groupBox_edit.setObjectName("groupBox_edit") + self.radioButton_edit_last_cmd = QtWidgets.QRadioButton(self.groupBox_edit) + self.radioButton_edit_last_cmd.setGeometry(QtCore.QRect(10, 30, 341, 17)) + self.radioButton_edit_last_cmd.setObjectName("radioButton_edit_last_cmd") + self.radioButton_edit_shortcut = QtWidgets.QRadioButton(self.groupBox_edit) + self.radioButton_edit_shortcut.setGeometry(QtCore.QRect(10, 50, 341, 17)) + self.radioButton_edit_shortcut.setObjectName("radioButton_edit_shortcut") + self.label_5 = QtWidgets.QLabel(self.groupBox_edit) + self.label_5.setGeometry(QtCore.QRect(10, 10, 381, 20)) + self.label_5.setObjectName("label_5") + self.groupBox_command = QtWidgets.QGroupBox(RightClick_Dialog) + self.groupBox_command.setGeometry(QtCore.QRect(10, 270, 401, 111)) + self.groupBox_command.setObjectName("groupBox_command") + self.radioButton_cmd_enter = QtWidgets.QRadioButton(self.groupBox_command) + self.radioButton_cmd_enter.setGeometry(QtCore.QRect(10, 30, 341, 17)) + self.radioButton_cmd_enter.setObjectName("radioButton_cmd_enter") + self.radioButton_cmd_shortcut = QtWidgets.QRadioButton(self.groupBox_command) + self.radioButton_cmd_shortcut.setGeometry(QtCore.QRect(10, 50, 341, 17)) + self.radioButton_cmd_shortcut.setObjectName("radioButton_cmd_shortcut") + self.label_6 = QtWidgets.QLabel(self.groupBox_command) + self.label_6.setGeometry(QtCore.QRect(10, 10, 381, 20)) + self.label_6.setObjectName("label_6") + self.radioButton_cmd_shortcut_with_options = QtWidgets.QRadioButton(self.groupBox_command) + self.radioButton_cmd_shortcut_with_options.setGeometry(QtCore.QRect(10, 70, 341, 31)) + self.radioButton_cmd_shortcut_with_options.setObjectName("radioButton_cmd_shortcut_with_options") + self.lineEdit_duration = QtWidgets.QLineEdit(RightClick_Dialog) + self.lineEdit_duration.setGeometry(QtCore.QRect(160, 80, 41, 20)) + self.lineEdit_duration.setObjectName("lineEdit_duration") + self.label_7 = QtWidgets.QLabel(RightClick_Dialog) + self.label_7.setGeometry(QtCore.QRect(210, 80, 121, 21)) + self.label_7.setObjectName("label_7") + self.label_8 = QtWidgets.QLabel(RightClick_Dialog) + self.label_8.setGeometry(QtCore.QRect(20, 80, 131, 16)) + self.label_8.setObjectName("label_8") + self.Button_Cancel = QtWidgets.QPushButton(RightClick_Dialog) + self.Button_Cancel.setGeometry(QtCore.QRect(260, 390, 71, 23)) + self.Button_Cancel.setObjectName("Button_Cancel") + self.Button_ApplyClose = QtWidgets.QPushButton(RightClick_Dialog) + self.Button_ApplyClose.setGeometry(QtCore.QRect(130, 390, 121, 23)) + self.Button_ApplyClose.setObjectName("Button_ApplyClose") + self.Button_Help = QtWidgets.QPushButton(RightClick_Dialog) + self.Button_Help.setGeometry(QtCore.QRect(340, 390, 75, 23)) + self.Button_Help.setObjectName("Button_Help") + + self.retranslateUi(RightClick_Dialog) + self.Button_ApplyClose.clicked.connect(RightClick_Dialog.applyClose_clicked) + self.Button_Cancel.clicked.connect(RightClick_Dialog.cancel_clicked) + self.Button_Help.clicked.connect(RightClick_Dialog.help_clicked) + self.checkBox_timeSensitive.clicked.connect(RightClick_Dialog.timeSensitive_clicked) + QtCore.QMetaObject.connectSlotsByName(RightClick_Dialog) + + def retranslateUi(self, RightClick_Dialog): + _translate = QtCore.QCoreApplication.translate + RightClick_Dialog.setWindowTitle(_translate("RightClick_Dialog", "Right click customization")) + self.checkBox_timeSensitive.setToolTip(_translate("RightClick_Dialog", "Controls right-click behavior. A quick click is the same as pressing Enter. A longer click displays a shortcut menu. You can set the duration of the longer click in milliseconds.")) + self.checkBox_timeSensitive.setText(_translate("RightClick_Dialog", "Turn on Time-Sensitive Right-Click")) + self.label.setText(_translate("RightClick_Dialog", "Quick click for ENTER")) + self.label_2.setText(_translate("RightClick_Dialog", "Longer click to display Shortcut Menu")) + self.groupBox_default.setTitle(_translate("RightClick_Dialog", "Default Mode")) + self.radioButton_default_last_cmd.setToolTip(_translate("RightClick_Dialog", "Disables the Default shortcut menu. As a result, right-clicking in the drawing area when no objects are selected and no commands are in progress is the same as pressing Enter, which repeats the last issued command.")) + self.radioButton_default_last_cmd.setText(_translate("RightClick_Dialog", "Repeat last command")) + self.radioButton_default_shortcut.setToolTip(_translate("RightClick_Dialog", "Enables the Default shortcut menu.")) + self.radioButton_default_shortcut.setText(_translate("RightClick_Dialog", "Shortcut Menu")) + self.label_3.setText(_translate("RightClick_Dialog", "If no objects are selected, right click means")) + self.groupBox_edit.setTitle(_translate("RightClick_Dialog", "Edit Mode")) + self.radioButton_edit_last_cmd.setToolTip(_translate("RightClick_Dialog", "Disables the Edit shortcut menu. As a result, right-clicking in the drawing area when one or more objects are selected and no commands are in progress is the same as pressing Enter, which repeats the last issued command.")) + self.radioButton_edit_last_cmd.setText(_translate("RightClick_Dialog", "Repeat last command")) + self.radioButton_edit_shortcut.setToolTip(_translate("RightClick_Dialog", "Enables the Edit shortcut menu.")) + self.radioButton_edit_shortcut.setText(_translate("RightClick_Dialog", "Shortcut Menu")) + self.label_5.setText(_translate("RightClick_Dialog", "If one or more objects are selected, right click means")) + self.groupBox_command.setTitle(_translate("RightClick_Dialog", "Command Mode")) + self.radioButton_cmd_enter.setToolTip(_translate("RightClick_Dialog", "Disables the Command shortcut menu. As a result, right-clicking in the drawing area when a command is in progress is the same as pressing Enter.")) + self.radioButton_cmd_enter.setText(_translate("RightClick_Dialog", "ENTER")) + self.radioButton_cmd_shortcut.setToolTip(_translate("RightClick_Dialog", "Enables the Command shortcut menu.")) + self.radioButton_cmd_shortcut.setText(_translate("RightClick_Dialog", "Shortcut Menu: always enabled")) + self.label_6.setText(_translate("RightClick_Dialog", "If a command is in progress, right click means")) + self.radioButton_cmd_shortcut_with_options.setToolTip(_translate("RightClick_Dialog", "Enables the Command shortcut menu only when command options are available at the Command prompt. If no options are available, a right-click is the same as pressing Enter.")) + self.radioButton_cmd_shortcut_with_options.setText(_translate("RightClick_Dialog", "Shortcut Menu: enabled when command options are present")) + self.lineEdit_duration.setToolTip(_translate("RightClick_Dialog", "Controls right-click behavior. A quick click is the same as pressing Enter. A longer click displays a shortcut menu. You can set the duration of the longer click in milliseconds.")) + self.label_7.setText(_translate("RightClick_Dialog", "milliseconds")) + self.label_8.setText(_translate("RightClick_Dialog", "Longer click duration:")) + self.Button_Cancel.setText(_translate("RightClick_Dialog", "Cancel")) + self.Button_ApplyClose.setText(_translate("RightClick_Dialog", "Apply && Close")) + self.Button_Help.setText(_translate("RightClick_Dialog", "Help")) diff --git a/qad_rotate_maptool.py b/qad_rotate_maptool.py deleted file mode 100644 index de94efe9..00000000 --- a/qad_rotate_maptool.py +++ /dev/null @@ -1,197 +0,0 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - classe per gestire il map tool in ambito del comando rotate - - ------------------- - begin : 2013-09-27 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -from qgis.gui import * -import math - - -import qad_utils -from qad_snapper import * -from qad_snappointsdisplaymanager import * -from qad_variables import * -from qad_getpoint import * -from qad_dim import * -from qad_highlight import QadHighlight - - -#=============================================================================== -# Qad_rotate_maptool_ModeEnum class. -#=============================================================================== -class Qad_rotate_maptool_ModeEnum(): - # noto niente si richiede il punto base - NONE_KNOWN_ASK_FOR_BASE_PT = 1 - # noto il punto base si richiede il secondo punto per l'angolo di rotazione - BASE_PT_KNOWN_ASK_FOR_ROTATION_PT = 2 - # si richiede il primo punto per l'angolo di riferimento - ASK_FOR_FIRST_PT_REFERENCE_ANG = 3 - # noto il primo punto si richiede il secondo punto per l'angolo di riferimento - FIRST_PT_KNOWN_ASK_FOR_SECOND_PT_REFERENCE_ANG = 4 - # noto il punto base si richiede il secondo punto per il nuovo angolo - BASE_PT_KNOWN_ASK_FOR_NEW_ROTATION_PT = 5 - # si richiede il primo punto per il nuovo angolo - ASK_FOR_FIRST_NEW_ROTATION_PT = 6 - # noto il primo punto si richiede il secondo punto per il nuovo angolo - FIRST_PT_KNOWN_ASK_FOR_SECOND_NEW_ROTATION_PT = 7 - -#=============================================================================== -# Qad_rotate_maptool class -#=============================================================================== -class Qad_rotate_maptool(QadGetPoint): - - def __init__(self, plugIn): - QadGetPoint.__init__(self, plugIn) - - self.basePt = None - self.Pt1ReferenceAng = None - self.ReferenceAng = 0 - self.Pt1NewAng = None - self.entitySet = QadEntitySet() - self.__highlight = QadHighlight(self.canvas) - - def hidePointMapToolMarkers(self): - QadGetPoint.hidePointMapToolMarkers(self) - self.__highlight.hide() - - def showPointMapToolMarkers(self): - QadGetPoint.showPointMapToolMarkers(self) - self.__highlight.show() - - def clear(self): - QadGetPoint.clear(self) - self.__highlight.reset() - self.mode = None - - - #============================================================================ - # rotate - #============================================================================ - def rotate(self, entity, basePt, angle): - # verifico se l'entità appartiene ad uno stile di quotatura - if entity.whatIs() == "ENTITY": - # ruoto l'entità - rotatedGeom = qad_utils.rotateQgsGeometry(entity.getGeometry(), basePt, angle) - if rotatedGeom is not None: - self.__highlight.addGeometry(rotatedGeom, entity.layer) - else: - # ruoto la quota - entity.rotate(self.plugIn, basePt, angle) - self.__highlight.addGeometry(entity.textualFeature.geometry(), entity.getTextualLayer()) - self.__highlight.addGeometries(entity.getLinearGeometryCollection(), entity.getLinearLayer()) - self.__highlight.addGeometries(entity.getSymbolGeometryCollection(), entity.getSymbolLayer()) - - - #============================================================================ - # addRotatedGeometries - #============================================================================ - def addRotatedGeometries(self, angle): - self.__highlight.reset() - - dimElaboratedList = [] # lista delle quotature già elaborate - entity = QadEntity() - - for layerEntitySet in self.entitySet.layerEntitySetList: - layer = layerEntitySet.layer - - transformedBasePt = self.canvas.mapRenderer().mapToLayerCoordinates(layer, self.basePt) - - for featureId in layerEntitySet.featureIds: - # verifico se l'entità appartiene ad uno stile di quotatura - dimEntity = QadDimStyles.getDimEntity(layer, featureId) - if dimEntity is None: - entity.set(layer, featureId) - self.rotate(entity, transformedBasePt, angle) - else: - found = False - for dimElaborated in dimElaboratedList: - if dimElaborated == dimEntity: - found = True - if found == False: # quota non ancora elaborata - dimElaboratedList.append(dimEntity) - self.rotate(dimEntity, transformedBasePt, angle) - - - def canvasMoveEvent(self, event): - QadGetPoint.canvasMoveEvent(self, event) - - # noto il punto base si richiede il secondo punto per l'angolo di rotazione - if self.mode == Qad_rotate_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_ROTATION_PT: - angle = qad_utils.getAngleBy2Pts(self.basePt, self.tmpPoint) - self.addRotatedGeometries(angle) - # noto il punto base si richiede il secondo punto per il nuovo angolo - elif self.mode == Qad_rotate_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_NEW_ROTATION_PT: - angle = qad_utils.getAngleBy2Pts(self.basePt, self.tmpPoint) - diffAngle = angle - self.ReferenceAng - self.addRotatedGeometries(diffAngle) - # noto il primo punto si richiede il secondo punto per il nuovo angolo - elif self.mode == Qad_rotate_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_NEW_ROTATION_PT: - angle = qad_utils.getAngleBy2Pts(self.Pt1NewAng, self.tmpPoint) - diffAngle = angle - self.ReferenceAng - self.addRotatedGeometries(diffAngle) - - - def activate(self): - QadGetPoint.activate(self) - self.__highlight.show() - - def deactivate(self): - try: # necessario perché se si chiude QGIS parte questo evento nonostante non ci sia più l'oggetto maptool ! - QadGetPoint.deactivate(self) - self.__highlight.hide() - except: - pass - - def setMode(self, mode): - self.mode = mode - # noto niente si richiede il punto base - if self.mode == Qad_rotate_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_BASE_PT: - self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) - self.setDrawMode(QadGetPointDrawModeEnum.NONE) - self.__highlight.reset() - # noto il punto base si richiede il secondo punto per l'angolo di rotazione - elif self.mode == Qad_rotate_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_ROTATION_PT: - self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.setStartPoint(self.basePt) - # si richiede il primo punto per l'angolo di riferimento - elif self.mode == Qad_rotate_maptool_ModeEnum.ASK_FOR_FIRST_PT_REFERENCE_ANG: - self.setDrawMode(QadGetPointDrawModeEnum.NONE) - self.__highlight.reset() - # noto il primo punto si richiede il secondo punto per l'angolo di riferimento - elif self.mode == Qad_rotate_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT_REFERENCE_ANG: - self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.setStartPoint(self.Pt1ReferenceAng) - # noto il punto base si richiede il secondo punto per il nuovo angolo - elif self.mode == Qad_rotate_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_NEW_ROTATION_PT: - self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.setStartPoint(self.basePt) - # si richiede il primo punto per il nuovo angolo - elif self.mode == Qad_rotate_maptool_ModeEnum.ASK_FOR_FIRST_NEW_ROTATION_PT: - self.setDrawMode(QadGetPointDrawModeEnum.NONE) - # noto il primo punto si richiede il secondo punto per il nuovo angolo - elif self.mode == Qad_rotate_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_NEW_ROTATION_PT: - self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.setStartPoint(self.Pt1NewAng) diff --git a/qad_rubberband.py b/qad_rubberband.py index 3b22948d..085d9c90 100644 --- a/qad_rubberband.py +++ b/qad_rubberband.py @@ -1,375 +1,443 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - Classe per gestire rubber band di oggetti geometricamente non omogenei - - ------------------- - begin : 2013-12-12 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -from qgis.gui import * - - -from qad_entity import * -from qad_variables import * - - -#=============================================================================== -# QadCursorTypeEnum class. -#=============================================================================== -class QadCursorTypeEnum(): - BOX = 1 # un quadratino usato per selezionare entità - CROSS = 2 # una croce usata per selezionare un punto - - -#=============================================================================== -# createCursorRubberBand -#=============================================================================== -# Classe che gestisce rubber band per disegnare il cursore a croce e il quadratino di pickbox -class QadCursorRubberBand(): - def __init__(self, mapCanvas, cursorType): - self.mapCanvas = mapCanvas - self.cursorType = cursorType - - if cursorType & QadCursorTypeEnum.BOX: - self.__boxRubberBand = QgsRubberBand(mapCanvas, QGis.Line) - self.__boxRubberBand.setColor(QColor(QadVariables.get(QadMsg.translate("Environment variables", "PICKBOXCOLOR")))) - self.__pickSize = QadVariables.get(QadMsg.translate("Environment variables", "PICKBOX")) - else: - self.__boxRubberBand = None - - if cursorType & QadCursorTypeEnum.CROSS: - csrColor = QColor(QadVariables.get(QadMsg.translate("Environment variables", "CURSORCOLOR"))) - csrSize = QadVariables.get(QadMsg.translate("Environment variables", "CURSORSIZE")) - self.__crosshairRubberBandSx = QgsRubberBand(mapCanvas, QGis.Line) - self.__crosshairRubberBandSx.setColor(csrColor) - self.__crosshairRubberBandDx = QgsRubberBand(mapCanvas, QGis.Line) - self.__crosshairRubberBandDx.setColor(csrColor) - self.__crosshairRubberBandDw = QgsRubberBand(mapCanvas, QGis.Line) - self.__crosshairRubberBandDw.setColor(csrColor) - self.__crosshairRubberBandUp = QgsRubberBand(mapCanvas, QGis.Line) - self.__crosshairRubberBandUp.setColor(csrColor) - screenRect = QApplication.desktop().screenGeometry(mapCanvas) - self.__halfScreenSize = max(screenRect.height(), screenRect.width()) - if csrSize < 100: - self.__halfScreenSize = self.__halfScreenSize / 2 - self.__halfScreenSize = self.__halfScreenSize * QadVariables.get(QadMsg.translate("Environment variables", "CURSORSIZE")) / 100 - else: - self.__crosshairRubberBandSx = None - self.__crosshairRubberBandDx = None - self.__crosshairRubberBandDw = None - self.__crosshairRubberBandUp = None - - def __del__(self): - self.removeItems() - - def removeItems(self): - if self.__boxRubberBand is not None: - self.mapCanvas.scene().removeItem(self.__boxRubberBand) - del self.__boxRubberBand - self.__boxRubberBand = None - - if self.__crosshairRubberBandSx is not None: - self.mapCanvas.scene().removeItem(self.__crosshairRubberBandSx) - self.mapCanvas.scene().removeItem(self.__crosshairRubberBandDx) - self.mapCanvas.scene().removeItem(self.__crosshairRubberBandDw) - self.mapCanvas.scene().removeItem(self.__crosshairRubberBandUp) - del self.__crosshairRubberBandSx - self.__crosshairRubberBandSx = None - del self.__crosshairRubberBandDx - self.__crosshairRubberBandDx = None - del self.__crosshairRubberBandDw - self.__crosshairRubberBandDw = None - del self.__crosshairRubberBandUp - self.__crosshairRubberBandUp = None - - def moveEvent(self, point): - # point è risultato di toMapCoordinates - if self.cursorType & QadCursorTypeEnum.BOX: - pickSize = self.__pickSize * self.mapCanvas.mapUnitsPerPixel() - - self.__boxRubberBand.reset(QGis.Line) - - point1 = QgsPoint(point.x() - pickSize / 2, point.y() - pickSize / 2) - self.__boxRubberBand.addPoint(point1, False) - point1.setX(point1.x() + pickSize) - self.__boxRubberBand.addPoint(point1, False) - point1.setY(point1.y() + pickSize) - self.__boxRubberBand.addPoint(point1, False) - point1.setX(point1.x() - pickSize) - self.__boxRubberBand.addPoint(point1, False) - point1.setY(point1.y() - pickSize) - self.__boxRubberBand.addPoint(point1, True) - - if self.cursorType & QadCursorTypeEnum.CROSS: - halfScreenSize = self.__halfScreenSize * self.mapCanvas.mapUnitsPerPixel() - - self.__crosshairRubberBandSx.reset(QGis.Line) - self.__crosshairRubberBandDx.reset(QGis.Line) - self.__crosshairRubberBandDw.reset(QGis.Line) - self.__crosshairRubberBandUp.reset(QGis.Line) - - if self.cursorType & QadCursorTypeEnum.BOX: - halfPickSize = pickSize / 2 - point1 = QgsPoint(point.x() - halfScreenSize, point.y()) - point2 = QgsPoint(point.x() - halfPickSize, point.y()) - self.__crosshairRubberBandSx.addPoint(point1, False) - self.__crosshairRubberBandSx.addPoint(point2, True) - - point1.setX(point.x() + halfScreenSize) - point2.setX(point.x() + halfPickSize) - self.__crosshairRubberBandDx.addPoint(point1, False) - self.__crosshairRubberBandDx.addPoint(point2, True) - - point1.set(point.x(), point.y() - halfScreenSize) - point2.set(point.x(), point.y() - halfPickSize) - self.__crosshairRubberBandDw.addPoint(point1, False) - self.__crosshairRubberBandDw.addPoint(point2, True) - - point1.setY(point.y() + halfScreenSize) - point2.setY(point.y() + halfPickSize) - self.__crosshairRubberBandUp.addPoint(point1, False) - self.__crosshairRubberBandUp.addPoint(point2, True) - else: - point1 = QgsPoint(point.x() - halfScreenSize, point.y()) - self.__crosshairRubberBandSx.addPoint(point, False) - self.__crosshairRubberBandSx.addPoint(point1, True) - - point1.setX(point.x() + halfScreenSize) - self.__crosshairRubberBandDx.addPoint(point, False) - self.__crosshairRubberBandDx.addPoint(point1, True) - - point1.set(point.x(), point.y() - halfScreenSize) - self.__crosshairRubberBandDw.addPoint(point, False) - self.__crosshairRubberBandDw.addPoint(point1, True) - - point1.setY(point.y() + halfScreenSize) - self.__crosshairRubberBandUp.addPoint(point, False) - self.__crosshairRubberBandUp.addPoint(point1, True) - - def hide(self): - if self.__boxRubberBand is not None: - self.__boxRubberBand.hide() - - if self.__crosshairRubberBandSx is not None: - self.__crosshairRubberBandSx.hide() - self.__crosshairRubberBandDx.hide() - self.__crosshairRubberBandDw.hide() - self.__crosshairRubberBandUp.hide() - - def show(self): - if self.__boxRubberBand is not None: - self.__boxRubberBand.show() - - if self.__crosshairRubberBandSx is not None: - self.__crosshairRubberBandSx.show() - self.__crosshairRubberBandDx.show() - self.__crosshairRubberBandDw.show() - self.__crosshairRubberBandUp.show() - - -#=============================================================================== -# getQGISColorForRubberBand -#=============================================================================== -def getQGISColorForRubberBand(geometryType = QGis.Line, alternativeBand = False): - """ - La funzione legge il colore impostato da QGIS per il rubber band di tipo . - Se = True, il rubber band sarà impostato con più trasparenza - """ - settings = QSettings() - color = QColor(int(settings.value( "/qgis/digitizing/line_color_red", 1)), \ - int(settings.value( "/qgis/digitizing/line_color_green", 1)), \ - int(settings.value( "/qgis/digitizing/line_color_blue", 1))) - alpha = float(int(settings.value( "/qgis/digitizing/line_color_alpha", 200)) / 255.0) - - if alternativeBand: - alpha = alpha * float(settings.value( "/qgis/digitizing/line_color_alpha_scale", 0.75)) - - if geometryType == QGis.Polygon: - color.setAlphaF(alpha) - - color.setAlphaF(alpha) - return color - - -#=============================================================================== -# getColorForWindowSelectionArea -#=============================================================================== -def getColorForWindowSelectionArea(): - """ - La funzione legge il colore (RGB) dell'area di selezione degli oggetti nel modo finestra. - """ - if QadVariables.get(QadMsg.translate("Environment variables", "SELECTIONAREA")) == 0: - color = QColor() - color.setAlphaF(0) # trasparente - else: - color = QColor(QadVariables.get(QadMsg.translate("Environment variables", "WINDOWAREACOLOR"))) - opacity = QadVariables.get(QadMsg.translate("Environment variables", "SELECTIONAREAOPACITY")) # 0 = trasparente [0-100] - color.setAlphaF(opacity / 100.0) # trasformo da 0-100 a 0-1 - - return color - - -#=============================================================================== -# getColorForCrossingSelectionArea -#=============================================================================== -def getColorForCrossingSelectionArea(): - """ - La funzione legge il colore (RGB) dell'area di selezione degli oggetti nel modo intersezione. - """ - if QadVariables.get(QadMsg.translate("Environment variables", "SELECTIONAREA")) == 0: - color = QColor() - color.setAlphaF(0) # trasparente - else: - color = QColor(QadVariables.get(QadMsg.translate("Environment variables", "CROSSINGAREACOLOR"))) - opacity = QadVariables.get(QadMsg.translate("Environment variables", "SELECTIONAREAOPACITY")) # 0 = trasparente [0-100] - color.setAlphaF(opacity / 100.0) # trasformo da 0-100 a 0-1 - - return color - - -#=============================================================================== -# createRubberBand -#=============================================================================== -def createRubberBand(mapCanvas, geometryType = QGis.Line, alternativeBand = False, borderColor = None, fillColor = None): - """ - la funzione crea un rubber band di tipo con le impostazioni di QGIS. - Se = True, il rubber band sarà impostato con più trasparenza e tipolinea punteggiato - """ - settings = QSettings() - width = int(settings.value( "/qgis/digitizing/line_width", 1)) - - rb = QgsRubberBand(mapCanvas, geometryType) - - if alternativeBand: - rb.setLineStyle(Qt.DotLine) - - if borderColor is None: - borderColor = getQGISColorForRubberBand(geometryType, alternativeBand) - rb.setBorderColor(borderColor) - - if fillColor is None: - rb.setFillColor(borderColor) - else: - rb.setFillColor(fillColor) - - rb.setWidth(width) - - return rb - - -# Classe che gestisce rubber band di oggetti geometricamente non omogenei -class QadRubberBand(): - def __init__(self, mapCanvas, alternativeBand = False, borderColor = None, fillColor = None): - """ - Se = True, il rubber band sarà impostato con più trasparenza e tipolinea punteggiato - """ - self.mapCanvas = mapCanvas - self.__rubberBandPoint = createRubberBand(self.mapCanvas, QGis.Point, alternativeBand, borderColor, fillColor) - self.__rubberBandLine = createRubberBand(self.mapCanvas, QGis.Line, alternativeBand, borderColor, fillColor) - self.__rubberBandPolygon = createRubberBand(self.mapCanvas, QGis.Polygon, alternativeBand, borderColor, fillColor) - - def __del__(self): - self.hide() - - self.mapCanvas.scene().removeItem(self.__rubberBandPoint) - del self.__rubberBandPoint - - self.mapCanvas.scene().removeItem(self.__rubberBandLine) - del self.__rubberBandLine - - self.mapCanvas.scene().removeItem(self.__rubberBandPolygon) - del self.__rubberBandPolygon - - def hide(self): - self.__rubberBandPoint.hide() - self.__rubberBandLine.hide() - self.__rubberBandPolygon.hide() - - def show(self): - self.__rubberBandPoint.show() - self.__rubberBandLine.show() - self.__rubberBandPolygon.show() - - def addGeometry(self, geom, layer): - # uso la geometria del layer per risolvere il caso ambiguo in cui - # si vuole inserire una linea chiusa in un layer poligono - geomType = layer.geometryType() - #geomType = geom.type() - if geomType == QGis.Point: - self.__rubberBandPoint.addGeometry(geom, layer) - elif geomType == QGis.Line: - self.__rubberBandLine.addGeometry(geom, layer) - elif geomType == QGis.Polygon: - self.__rubberBandPolygon.addGeometry(geom, layer) - - def addGeometries(self, geoms, layer): - for g in geoms: - self.addGeometry(g, layer) - - - def setLine(self, points): - self.__rubberBandLine.reset(QGis.Line) - tot = len(points) - 1 - i = 0 - while i <= tot: - if i < tot: - self.__rubberBandLine.addPoint(points[i], False) - else: # ultimo punto - self.__rubberBandLine.addPoint(points[i], True) - i = i + 1 - - def setPolygon(self, points): - self.__rubberBandPolygon.reset(QGis.Polygon) - tot = len(points) - 1 - i = 0 - while i <= tot: - if i < tot: - self.__rubberBandPolygon.addPoint(points[i], False) - else: # ultimo punto - self.__rubberBandPolygon.addPoint(points[i], True) - i = i + 1 - - def addLinePoint(self, point, doUpdate = True, geometryIndex = 0): - self.__rubberBandLine.addPoint(point, doUpdate, geometryIndex) - - def addPolygonPoint(self, point, doUpdate = True, geometryIndex = 0): - self.__rubberBandPolygon.addPoint(point, doUpdate, geometryIndex) - - def reset(self): - self.__rubberBandPoint.reset(QGis.Point) - self.__rubberBandLine.reset(QGis.Line) - self.__rubberBandPolygon.reset(QGis.Polygon) - - def setLineStyle(self, penStyle): - self.__rubberBandLine.setLineStyle(penStyle) - self.__rubberBandPolygon.setLineStyle(penStyle) - - def setBorderColor(self, color): - self.__rubberBandPoint.setBorderColor(color) - self.__rubberBandLine.setBorderColor(color) - self.__rubberBandPolygon.setBorderColor(color) - - def setFillColor(self, color): - self.__rubberBandPolygon.setFillColor(color) - +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + Classe per gestire rubber band di oggetti geometricamente non omogenei + + ------------------- + begin : 2013-12-12 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.PyQt.QtCore import * +from qgis.PyQt.QtGui import * +from qgis.PyQt.QtWidgets import QApplication +from qgis.core import * +from qgis.gui import * + + +from .qad_variables import QadVariables +from .qad_msg import QadMsg + + +# =============================================================================== +# QadCursorTypeEnum class. +# =============================================================================== +class QadCursorTypeEnum(): + NONE = 0 # nessun cursore + BOX = 1 # un quadratino usato per selezionare entità + CROSS = 2 # una croce usata per selezionare un punto + APERTURE = 4 # un quadratino usato per selezionare i punti di snap + + +# =============================================================================== +# createCursorRubberBand +# =============================================================================== +# Classe che gestisce rubber band per disegnare il cursore a croce e il quadratino di pickbox +class QadCursorRubberBand(): + def __init__(self, mapCanvas, cursorType): + self.mapCanvas = mapCanvas + self.cursorType = cursorType + + if cursorType & QadCursorTypeEnum.BOX: + self.__boxRubberBand = QgsRubberBand(mapCanvas, QgsWkbTypes.LineGeometry) + self.__boxRubberBand.setColor(QColor(QadVariables.get(QadMsg.translate("Environment variables", "PICKBOXCOLOR")))) + self.__pickSize = QadVariables.get(QadMsg.translate("Environment variables", "PICKBOX")) + else: + self.__boxRubberBand = None + + if cursorType & QadCursorTypeEnum.CROSS: + csrColor = QColor(QadVariables.get(QadMsg.translate("Environment variables", "CURSORCOLOR"))) + csrSize = QadVariables.get(QadMsg.translate("Environment variables", "CURSORSIZE")) + self.__crosshairRubberBandSx = QgsRubberBand(mapCanvas, QgsWkbTypes.LineGeometry) + self.__crosshairRubberBandSx.setColor(csrColor) + self.__crosshairRubberBandDx = QgsRubberBand(mapCanvas, QgsWkbTypes.LineGeometry) + self.__crosshairRubberBandDx.setColor(csrColor) + self.__crosshairRubberBandDw = QgsRubberBand(mapCanvas, QgsWkbTypes.LineGeometry) + self.__crosshairRubberBandDw.setColor(csrColor) + self.__crosshairRubberBandUp = QgsRubberBand(mapCanvas, QgsWkbTypes.LineGeometry) + self.__crosshairRubberBandUp.setColor(csrColor) + screenRect = QApplication.desktop().screenGeometry(mapCanvas) + self.__halfScreenSize = max(screenRect.height(), screenRect.width()) + if csrSize < 100: + self.__halfScreenSize = self.__halfScreenSize / 2 + self.__halfScreenSize = self.__halfScreenSize * QadVariables.get(QadMsg.translate("Environment variables", "CURSORSIZE")) / 100 + else: + self.__crosshairRubberBandSx = None + self.__crosshairRubberBandDx = None + self.__crosshairRubberBandDw = None + self.__crosshairRubberBandUp = None + + if cursorType & QadCursorTypeEnum.APERTURE: + self.__apertureRubberBand = QgsRubberBand(mapCanvas, QgsWkbTypes.LineGeometry) + self.__apertureRubberBand.setColor(QColor(QadVariables.get(QadMsg.translate("Environment variables", "PICKBOXCOLOR")))) + self.__apertureRubberBand.setLineStyle(Qt.DotLine) + self.__apertureSize = QadVariables.get(QadMsg.translate("Environment variables", "APERTURE")) + else: + self.__apertureRubberBand = None + + def __del__(self): + self.removeItems() + + def removeItems(self): + if self.__boxRubberBand is not None: + self.mapCanvas.scene().removeItem(self.__boxRubberBand) + del self.__boxRubberBand + self.__boxRubberBand = None + + if self.__crosshairRubberBandSx is not None: + self.mapCanvas.scene().removeItem(self.__crosshairRubberBandSx) + self.mapCanvas.scene().removeItem(self.__crosshairRubberBandDx) + self.mapCanvas.scene().removeItem(self.__crosshairRubberBandDw) + self.mapCanvas.scene().removeItem(self.__crosshairRubberBandUp) + del self.__crosshairRubberBandSx + self.__crosshairRubberBandSx = None + del self.__crosshairRubberBandDx + self.__crosshairRubberBandDx = None + del self.__crosshairRubberBandDw + self.__crosshairRubberBandDw = None + del self.__crosshairRubberBandUp + self.__crosshairRubberBandUp = None + + if self.__apertureRubberBand is not None: + self.mapCanvas.scene().removeItem(self.__apertureRubberBand) + del self.__apertureRubberBand + self.__apertureRubberBand = None + + + def moveEvent(self, point): + # point è risultato di toMapCoordinates + if self.cursorType & QadCursorTypeEnum.BOX: + pickSize = self.__pickSize * self.mapCanvas.mapUnitsPerPixel() + + self.__boxRubberBand.reset(QgsWkbTypes.LineGeometry) + + point1 = QgsPointXY(point.x() - pickSize, point.y() - pickSize) + dblPickSize = pickSize * 2 + self.__boxRubberBand.addPoint(point1, False) + point1.setX(point1.x() + dblPickSize) + self.__boxRubberBand.addPoint(point1, False) + point1.setY(point1.y() + dblPickSize) + self.__boxRubberBand.addPoint(point1, False) + point1.setX(point1.x() - dblPickSize) + self.__boxRubberBand.addPoint(point1, False) + point1.setY(point1.y() - dblPickSize) + self.__boxRubberBand.addPoint(point1, True) + + if self.cursorType & QadCursorTypeEnum.CROSS: + halfScreenSize = self.__halfScreenSize * self.mapCanvas.mapUnitsPerPixel() + + self.__crosshairRubberBandSx.reset(QgsWkbTypes.LineGeometry) + self.__crosshairRubberBandDx.reset(QgsWkbTypes.LineGeometry) + self.__crosshairRubberBandDw.reset(QgsWkbTypes.LineGeometry) + self.__crosshairRubberBandUp.reset(QgsWkbTypes.LineGeometry) + + if self.cursorType & QadCursorTypeEnum.BOX: + point1 = QgsPointXY(point.x() - halfScreenSize, point.y()) + point2 = QgsPointXY(point.x() - pickSize, point.y()) + self.__crosshairRubberBandSx.addPoint(point1, False) + self.__crosshairRubberBandSx.addPoint(point2, True) + + point1.setX(point.x() + halfScreenSize) + point2.setX(point.x() + pickSize) + self.__crosshairRubberBandDx.addPoint(point1, False) + self.__crosshairRubberBandDx.addPoint(point2, True) + + point1.set(point.x(), point.y() - halfScreenSize) + point2.set(point.x(), point.y() - pickSize) + self.__crosshairRubberBandDw.addPoint(point1, False) + self.__crosshairRubberBandDw.addPoint(point2, True) + + point1.setY(point.y() + halfScreenSize) + point2.setY(point.y() + pickSize) + self.__crosshairRubberBandUp.addPoint(point1, False) + self.__crosshairRubberBandUp.addPoint(point2, True) + else: + point1 = QgsPointXY(point.x() - halfScreenSize, point.y()) + self.__crosshairRubberBandSx.addPoint(point, False) + self.__crosshairRubberBandSx.addPoint(point1, True) + + point1.setX(point.x() + halfScreenSize) + self.__crosshairRubberBandDx.addPoint(point, False) + self.__crosshairRubberBandDx.addPoint(point1, True) + + point1.set(point.x(), point.y() - halfScreenSize) + self.__crosshairRubberBandDw.addPoint(point, False) + self.__crosshairRubberBandDw.addPoint(point1, True) + + point1.setY(point.y() + halfScreenSize) + self.__crosshairRubberBandUp.addPoint(point, False) + self.__crosshairRubberBandUp.addPoint(point1, True) + + if self.cursorType & QadCursorTypeEnum.APERTURE: + apertureSize = self.__apertureSize * self.mapCanvas.mapUnitsPerPixel() + + self.__apertureRubberBand.reset(QgsWkbTypes.LineGeometry) + + point1 = QgsPointXY(point.x() - apertureSize, point.y() - apertureSize) + dblApertureSize = apertureSize * 2 + self.__apertureRubberBand.addPoint(point1, False) + point1.setX(point1.x() + dblApertureSize) + self.__apertureRubberBand.addPoint(point1, False) + point1.setY(point1.y() + dblApertureSize) + self.__apertureRubberBand.addPoint(point1, False) + point1.setX(point1.x() - dblApertureSize) + self.__apertureRubberBand.addPoint(point1, False) + point1.setY(point1.y() - dblApertureSize) + self.__apertureRubberBand.addPoint(point1, True) + + + def hide(self): + if self.__boxRubberBand is not None: + self.__boxRubberBand.hide() + + if self.__crosshairRubberBandSx is not None: + self.__crosshairRubberBandSx.hide() + self.__crosshairRubberBandDx.hide() + self.__crosshairRubberBandDw.hide() + self.__crosshairRubberBandUp.hide() + + if self.__apertureRubberBand is not None: + self.__apertureRubberBand.hide() + + + def show(self): + if self.__boxRubberBand is not None: + self.__boxRubberBand.show() + + if self.__crosshairRubberBandSx is not None: + self.__crosshairRubberBandSx.show() + self.__crosshairRubberBandDx.show() + self.__crosshairRubberBandDw.show() + self.__crosshairRubberBandUp.show() + + if self.__apertureRubberBand is not None: + self.__apertureRubberBand.show() + + +# =============================================================================== +# getQGISColorForRubberBand +# =============================================================================== +def getQGISColorForRubberBand(geometryType = QgsWkbTypes.LineGeometry, alternativeBand = False): + """ + La funzione legge il colore impostato da QGIS per il rubber band di tipo . + Se = True, il rubber band sarà impostato con più trasparenza + """ + settings = QSettings() + color = QColor(int(settings.value( "/qgis/digitizing/line_color_red", 1)), \ + int(settings.value( "/qgis/digitizing/line_color_green", 1)), \ + int(settings.value( "/qgis/digitizing/line_color_blue", 1))) + alpha = float(int(settings.value( "/qgis/digitizing/line_color_alpha", 200)) / 255.0) + + if alternativeBand: + alpha = alpha * float(settings.value( "/qgis/digitizing/line_color_alpha_scale", 0.75)) + + if geometryType == QgsWkbTypes.PolygonGeometry: + color.setAlphaF(alpha) + + color.setAlphaF(alpha) + return color + + +# =============================================================================== +# getColorForWindowSelectionArea +# =============================================================================== +def getColorForWindowSelectionArea(): + """ + La funzione legge il colore (RGB) dell'area di selezione degli oggetti nel modo finestra. + """ + if QadVariables.get(QadMsg.translate("Environment variables", "SELECTIONAREA")) == 0: + color = QColor() + color.setAlphaF(0) # trasparente + else: + color = QColor(QadVariables.get(QadMsg.translate("Environment variables", "WINDOWAREACOLOR"))) + opacity = QadVariables.get(QadMsg.translate("Environment variables", "SELECTIONAREAOPACITY")) # 0 = trasparente [0-100] + color.setAlphaF(opacity / 100.0) # trasformo da 0-100 a 0-1 + + return color + + +# =============================================================================== +# getColorForCrossingSelectionArea +# =============================================================================== +def getColorForCrossingSelectionArea(): + """ + La funzione legge il colore (RGB) dell'area di selezione degli oggetti nel modo intersezione. + """ + if QadVariables.get(QadMsg.translate("Environment variables", "SELECTIONAREA")) == 0: + color = QColor() + color.setAlphaF(0) # trasparente + else: + color = QColor(QadVariables.get(QadMsg.translate("Environment variables", "CROSSINGAREACOLOR"))) + opacity = QadVariables.get(QadMsg.translate("Environment variables", "SELECTIONAREAOPACITY")) # 0 = trasparente [0-100] + color.setAlphaF(opacity / 100.0) # trasformo da 0-100 a 0-1 + + return color + + +# =============================================================================== +# createRubberBand +# =============================================================================== +def createRubberBand(mapCanvas, geometryType = QgsWkbTypes.LineGeometry, alternativeBand = False, borderColor = None, fillColor = None): + """ + la funzione crea un rubber band di tipo con le impostazioni di QGIS. + Se = True, il rubber band sarà impostato con più trasparenza e tipolinea punteggiato + """ + settings = QSettings() + width = int(settings.value( "/qgis/digitizing/line_width", 1)) + + rb = QgsRubberBand(mapCanvas, geometryType) + + if alternativeBand: + rb.setLineStyle(Qt.DotLine) + + if borderColor is None: + borderColor = getQGISColorForRubberBand(geometryType, alternativeBand) + rb.setStrokeColor(borderColor) + + if fillColor is None: + rb.setFillColor(borderColor) + else: + rb.setFillColor(fillColor) + + rb.setWidth(width) + + return rb + + +# Classe che gestisce rubber band di oggetti geometricamente non omogenei +class QadRubberBand(): + def __init__(self, mapCanvas, alternativeBand = False, borderColor = None, fillColor = None): + """ + Se = True, il rubber band sarà impostato con più trasparenza e tipolinea punteggiato + """ + self.mapCanvas = mapCanvas + self.__rubberBandPoint = createRubberBand(self.mapCanvas, QgsWkbTypes.PointGeometry, alternativeBand, borderColor, fillColor) + self.__rubberBandLine = createRubberBand(self.mapCanvas, QgsWkbTypes.LineGeometry, alternativeBand, borderColor, fillColor) + self.__rubberBandPolygon = createRubberBand(self.mapCanvas, QgsWkbTypes.PolygonGeometry, alternativeBand, borderColor, fillColor) + + def __del__(self): + self.hide() + + self.mapCanvas.scene().removeItem(self.__rubberBandPoint) + del self.__rubberBandPoint + + self.mapCanvas.scene().removeItem(self.__rubberBandLine) + del self.__rubberBandLine + + self.mapCanvas.scene().removeItem(self.__rubberBandPolygon) + del self.__rubberBandPolygon + + def hide(self): + self.__rubberBandPoint.hide() + self.__rubberBandLine.hide() + self.__rubberBandPolygon.hide() + + def show(self): + self.__rubberBandPoint.show() + self.__rubberBandLine.show() + self.__rubberBandPolygon.show() + + def addGeometry(self, geom, layer = None, doUpdate = True): + if layer is not None: + # uso la geometria del layer per risolvere il caso ambiguo in cui + # si vuole inserire una linea chiusa in un layer poligono + geomType = layer.geometryType() + #geomType = geom.type() + if geomType == QgsWkbTypes.PointGeometry: + self.__rubberBandPoint.addGeometry(geom, layer, doUpdate) + elif geomType == QgsWkbTypes.LineGeometry: + self.__rubberBandLine.addGeometry(geom, layer, doUpdate) + elif geomType == QgsWkbTypes.PolygonGeometry: + self.__rubberBandPolygon.addGeometry(geom, layer, doUpdate) + else: + geomType = QgsWkbTypes.geometryType(geom.wkbType()) + if geomType == QgsWkbTypes.PointGeometry: + self.__rubberBandPoint.addGeometry(geom, QgsCoordinateReferenceSystem(), doUpdate) + elif geomType == QgsWkbTypes.LineGeometry: + self.__rubberBandLine.addGeometry(geom, QgsCoordinateReferenceSystem(), doUpdate) + elif geomType == QgsWkbTypes.PolygonGeometry: + self.__rubberBandPolygon.addGeometry(geom, QgsCoordinateReferenceSystem(), doUpdate) + + + def addGeometries(self, geoms, layer = None): + for g in geoms: + self.addGeometry(g, layer, False) + + # After adding the final geometry updatePosition() should be called. + self.__rubberBandPoint.updatePosition() + self.__rubberBandLine.updatePosition() + self.__rubberBandPolygon.updatePosition() + + + def setLine(self, points): + self.__rubberBandLine.reset(QgsWkbTypes.LineGeometry) + tot = len(points) - 1 + i = 0 + while i <= tot: + if i < tot: + self.__rubberBandLine.addPoint(points[i], False) + else: # ultimo punto + self.__rubberBandLine.addPoint(points[i], True) + i = i + 1 + + def setPolygon(self, points): + self.__rubberBandPolygon.reset(QgsWkbTypes.PolygonGeometry) + tot = len(points) - 1 + i = 0 + while i <= tot: + if i < tot: + self.__rubberBandPolygon.addPoint(points[i], False) + else: # ultimo punto + self.__rubberBandPolygon.addPoint(points[i], True) + i = i + 1 + + + def setGeometry(self, geom): + self.reset() + self.addGeometry(geom) + + + def addLinePoint(self, point, doUpdate = True, geometryIndex = 0): + self.__rubberBandLine.addPoint(point, doUpdate, geometryIndex) + + def addPolygonPoint(self, point, doUpdate = True, geometryIndex = 0): + self.__rubberBandPolygon.addPoint(point, doUpdate, geometryIndex) + + def reset(self): + self.__rubberBandPoint.reset(QgsWkbTypes.PointGeometry) + self.__rubberBandLine.reset(QgsWkbTypes.LineGeometry) + self.__rubberBandPolygon.reset(QgsWkbTypes.PolygonGeometry) + + def setLineStyle(self, penStyle): + self.__rubberBandLine.setLineStyle(penStyle) + self.__rubberBandPolygon.setLineStyle(penStyle) + + def setBorderColor(self, color): + self.__rubberBandPoint.setSecondaryStrokeColor(color) + self.__rubberBandLine.setSecondaryStrokeColor(color) + self.__rubberBandPolygon.setSecondaryStrokeColor(color) + + def setFillColor(self, color): + self.__rubberBandPolygon.setFillColor(color) + + def setWidth(self, width): + self.__rubberBandPoint.setWidth(width) + self.__rubberBandLine.setWidth(width) + self.__rubberBandPolygon.setWidth(width) + \ No newline at end of file diff --git a/qad_scale_maptool.py b/qad_scale_maptool.py deleted file mode 100644 index a0c2bd14..00000000 --- a/qad_scale_maptool.py +++ /dev/null @@ -1,195 +0,0 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - classe per gestire il map tool in ambito del comando scale - - ------------------- - begin : 2013-09-27 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -from qgis.gui import * -import math - - -import qad_utils -from qad_snapper import * -from qad_snappointsdisplaymanager import * -from qad_variables import * -from qad_getpoint import * -from qad_dim import * -from qad_highlight import QadHighlight - - -#=============================================================================== -# Qad_scale_maptool_ModeEnum class. -#=============================================================================== -class Qad_scale_maptool_ModeEnum(): - # noto niente si richiede il punto base - NONE_KNOWN_ASK_FOR_BASE_PT = 1 - # noto il punto base si richiede il secondo punto per la scala - BASE_PT_KNOWN_ASK_FOR_SCALE_PT = 2 - # si richiede il primo punto per la lunghezza di riferimento - ASK_FOR_FIRST_PT_REFERENCE_LEN = 3 - # noto il primo punto si richiede il secondo punto per la lunghezza di riferimento - FIRST_PT_KNOWN_ASK_FOR_SECOND_PT_REFERENCE_LEN = 4 - # noto il punto base si richiede il secondo punto per la nuova lunghezza - BASE_PT_KNOWN_ASK_FOR_NEW_LEN_PT = 5 - # si richiede il primo punto per la nuova lunghezza - ASK_FOR_FIRST_NEW_LEN_PT = 6 - # noto il primo punto si richiede il secondo punto per la nuova lunghezza - FIRST_PT_KNOWN_ASK_FOR_SECOND_NEW_LEN_PT = 7 - -#=============================================================================== -# Qad_scale_maptool class -#=============================================================================== -class Qad_scale_maptool(QadGetPoint): - - def __init__(self, plugIn): - QadGetPoint.__init__(self, plugIn) - - self.basePt = None - self.Pt1ReferenceLen = None - self.ReferenceLen = 0 - self.Pt1NewLen = None - self.entitySet = QadEntitySet() - self.__highlight = QadHighlight(self.canvas) - - def hidePointMapToolMarkers(self): - QadGetPoint.hidePointMapToolMarkers(self) - self.__highlight.hide() - - def showPointMapToolMarkers(self): - QadGetPoint.showPointMapToolMarkers(self) - self.__highlight.show() - - def clear(self): - QadGetPoint.clear(self) - self.__highlight.reset() - self.mode = None - - - #============================================================================ - # scale - #============================================================================ - def scale(self, f, basePt, scale, layerEntitySet, entitySet): - # verifico se l'entità appartiene ad uno stile di quotatura - dimEntity = QadDimStyles.getDimEntity(layerEntitySet.layer, f.id()) - - if dimEntity is None: - # scalo la feature e la rimuovo da entitySet (é la prima) - f.setGeometry(qad_utils.scaleQgsGeometry(f.geometry(), basePt, scale)) - self.__highlight.addGeometry(f.geometry(), layerEntitySet.layer) - del layerEntitySet.featureIds[0] - else: - # scalo la quota e la rimuovo da entitySet - dimEntitySet = dimEntity.getEntitySet() - dimEntity.scale(self.plugIn, basePt, scale) - self.__highlight.addGeometry(dimEntity.textualFeature.geometry(), dimEntity.getTextualLayer()) - self.__highlight.addGeometries(dimEntity.getLinearGeometryCollection(), dimEntity.getLinearLayer()) - self.__highlight.addGeometries(dimEntity.getSymbolGeometryCollection(), dimEntity.getSymbolLayer()) - entitySet.subtract(dimEntitySet) - - - #============================================================================ - # addScaledGeometries - #============================================================================ - def addScaledGeometries(self, scale): - self.__highlight.reset() - - # copio entitySet - entitySet = QadEntitySet(self.entitySet) - - if scale <= 0: - return - - for layerEntitySet in entitySet.layerEntitySetList: - layer = layerEntitySet.layer - - transformedBasePt = self.canvas.mapRenderer().mapToLayerCoordinates(layer, self.basePt) - - while len(layerEntitySet.featureIds) > 0: - featureId = layerEntitySet.featureIds[0] - f = layerEntitySet.getFeature(featureId) - self.scale(f, transformedBasePt, scale, layerEntitySet, entitySet) - - - def canvasMoveEvent(self, event): - QadGetPoint.canvasMoveEvent(self, event) - - # noto il punto base si richiede il secondo punto per la scala - if self.mode == Qad_scale_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_SCALE_PT: - scale = qad_utils.getDistance(self.basePt, self.tmpPoint) - self.addScaledGeometries(scale) - # noto il primo punto si richiede il secondo punto per la lunghezza di riferimento - elif self.mode == Qad_scale_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_NEW_LEN_PT: - len = qad_utils.getDistance(self.basePt, self.tmpPoint) - scale = len / self.ReferenceLen - self.addScaledGeometries(scale) - # noto il primo punto si richiede il secondo punto per la nuova lunghezza - elif self.mode == Qad_scale_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_NEW_LEN_PT: - len = qad_utils.getDistance(self.Pt1NewLen, self.tmpPoint) - scale = len / self.ReferenceLen - self.addScaledGeometries(scale) - - - def activate(self): - QadGetPoint.activate(self) - self.__highlight.show() - - def deactivate(self): - try: # necessario perché se si chiude QGIS parte questo evento nonostante non ci sia più l'oggetto maptool ! - QadGetPoint.deactivate(self) - self.__highlight.hide() - except: - pass - - def setMode(self, mode): - self.mode = mode - # noto niente si richiede il punto base - if self.mode == Qad_scale_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_BASE_PT: - self.clear() - self.setDrawMode(QadGetPointDrawModeEnum.NONE) - self.__highlight.reset() - # noto il punto base si richiede il secondo punto per la scala - elif self.mode == Qad_scale_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_SCALE_PT: - self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.setStartPoint(self.basePt) - # si richiede il primo punto per la lunghezza di riferimento - elif self.mode == Qad_scale_maptool_ModeEnum.ASK_FOR_FIRST_PT_REFERENCE_LEN: - self.setDrawMode(QadGetPointDrawModeEnum.NONE) - self.__highlight.reset() - # noto il primo punto si richiede il secondo punto per la lunghezza di riferimento - elif self.mode == Qad_scale_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT_REFERENCE_LEN: - self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.setStartPoint(self.Pt1ReferenceLen) - # noto il punto base si richiede il secondo punto per la nuova lunghezza - elif self.mode == Qad_scale_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_NEW_LEN_PT: - self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.setStartPoint(self.basePt) - # si richiede il primo punto per la nuova lunghezza - elif self.mode == Qad_scale_maptool_ModeEnum.ASK_FOR_FIRST_NEW_LEN_PT: - self.setDrawMode(QadGetPointDrawModeEnum.NONE) - self.__highlight.reset() - # noto il primo punto si richiede il secondo punto per la nuova lunghezza - elif self.mode == Qad_scale_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_NEW_LEN_PT: - self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.setStartPoint(self.Pt1NewLen) diff --git a/qad_shortcuts.py b/qad_shortcuts.py new file mode 100644 index 00000000..46e934b8 --- /dev/null +++ b/qad_shortcuts.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + classe per la gestione delle scorciatoie + + ------------------- + begin : 2020-04-23 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from qgis.PyQt.QtCore import * +from qgis.PyQt.QtGui import QKeySequence +from qgis.gui import QgsGui +import string + + +# =============================================================================== +# QadShortcuts class +# =============================================================================== +class QadShortcuts(): + + def __init__(self): + self.sManager = QgsGui.shortcutsManager() + self.objList = [] # lista di coppie (shortcut KeySequence) o (action KeySequence) + + + def __del__(self): + self.registerForPrintable() + del self.objList[:] # svuoto la lista + + + def registerForPrintableAndQadFKeys(self): + # setta una lista di shortcut e una di action + for item in self.objList: + self.sManager.setObjectKeySequence(item[0], item[1].toString()) + + return + + + def unregisterForPrintableAndQadFKeys(self): + # rimuove gli shortcut e le action relative ai caratteri stampabili + # e li memorizza in 2 liste interne: una per gi shortcut ed una per le action che sono state rimosse + del self.objList[:] # svuoto la lista + s = string.printable + for i in range(0, len(s)): + seq = QKeySequence(s[i]) + obj = self.sManager.objectForSequence(seq) + if obj is not None: + self.objList.append([obj, seq]) + self.sManager.setObjectKeySequence(obj, QKeySequence().toString()) # lo annulla + + # rimuove gli shortcut e le action relative ai caratteri F2, F3, F8, F12, ESC + seq = QKeySequence(Qt.Key_F2) + obj = self.sManager.objectForSequence(seq) + if obj is not None: + self.objList.append([obj, seq]) + self.sManager.setObjectKeySequence(obj, QKeySequence().toString()) # lo annulla + + seq = QKeySequence(Qt.Key_F3) + obj = self.sManager.objectForSequence(seq) + if obj is not None: + self.objList.append([obj, seq]) + self.sManager.setObjectKeySequence(obj, QKeySequence().toString()) # lo annulla + + seq = QKeySequence(Qt.Key_F8) + obj = self.sManager.objectForSequence(seq) + if obj is not None: + self.objList.append([obj, seq]) + self.sManager.setObjectKeySequence(obj, QKeySequence().toString()) # lo annulla + + seq = QKeySequence(Qt.Key_F12) + obj = self.sManager.objectForSequence(seq) + if obj is not None: + self.objList.append([obj, seq]) + self.sManager.setObjectKeySequence(obj, QKeySequence().toString()) # lo annulla + + seq = QKeySequence(Qt.Key_Escape) + obj = self.sManager.objectForSequence(seq) + if obj is not None: + self.objList.append([obj, seq]) + self.sManager.setObjectKeySequence(obj, QKeySequence().toString()) # lo annulla + + return diff --git a/qad_snapper.py b/qad_snapper.py index 4388bfb4..74895f29 100644 --- a/qad_snapper.py +++ b/qad_snapper.py @@ -1,1636 +1,1673 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - classe per gestire gli snap - - ------------------- - begin : 2013-05-22 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -from PyQt4.QtCore import * -import os.path -from qgis.core import * -import math -import sys - - -import qad_utils -from qad_arc import * -from qad_circle import * - - -#=============================================================================== -# QadSnapTypeEnum class. -#=============================================================================== -class QadSnapTypeEnum(): - NONE = 0 # nessuno - END = 1 # punti finali di ogni segmento - MID = 2 # punto medio - CEN = 4 # centro (centroide) - NOD = 8 # oggetto punto - QUA = 16 # punto quadrante - INT = 32 # intersezione - INS = 64 # punto di inserimento - PER = 128 # punto perpendicolare - TAN = 256 # tangente - NEA = 512 # punto più vicino - C = 1024 # pulisci all object snaps - APP = 2048 # intersezione apparente - EXT = 4096 # estensione - PAR = 8192 # parallelo - DISABLE = 16384 # osnap off - PR = 65536 # distanza progressiva - EXT_INT = 131072 # intersezione sull'estensione - PER_DEF = 262144 # perpendicolare differita (come NEA) - TAN_DEF = 524288 # tangente differita (come NEA) - POLAR = 1048576 # puntamento polare - END_PLINE = 2097152 # punti finali dell'intera polilinea - -#=============================================================================== -# QadSnapModeEnum class. -#=============================================================================== -class QadSnapModeEnum(): - ONE_RESULT = 0 # Viene restituito solo il punto più vicino - RESULTS_FOR_SAME_POS = 1 # vengono restituiti diversi punti che hanno la stessa posizione. - # Questo é utile per l'editing topologico - ALL_RESULTS = 2 # Tutti i punti - -#=============================================================================== -# QadVertexSearchModeEnum class. -#=============================================================================== -class QadVertexSearchModeEnum(): - ALL = 0 # tutti i vertici - EXCLUDE_START_END = 1 # escludi il punto iniziale e finale - ONLY_START_END = 2 # solo il punto iniziale e finale - - -#=============================================================================== -# Qad snapper class. -#=============================================================================== -class QadSnapper(): - """ - Classe che gestisce i punti di snap - """ - - - #============================================================================ - # __init__ - #============================================================================ - def __init__(self): - self.__snapType = QadSnapTypeEnum.NONE - self.__snapLayers = None - self.__snapMode = QadSnapModeEnum.ONE_RESULT - self.__snapPointCRS = None # sistema di coordinate in cui memorizzare i punti di snap - self.__startPoint = None - self.__toleranceExtParlines = 0 - self.__extLines = [] # lista delle linee da estendere (ogni elemento é una lista di 2 punti = linea) - self.__extArcs = [] # lista degli archi da estendere (ogni elemento é un arco) - self.__parLines = [] # lista delle linee per modo parallelo (ogni elemento é una lista di 2 punti = linea) - self.__intExtLine = [] # linea per intersezione su estensione (lista di 2 punti = linea) - self.__intExtArc = [] # arco per intersezione su estensione - self.__cacheSnapPoints = [] - self.__progressDistance = 0.0 # distanza progressiva dall'inizio della linea - self.__distToExcludeNea = 0.0 # distanza entro la quale se ci sono dei punti di snap - # diversi da nearest questi hanno priorità su nearest - # altrimenti nearest vincerebbe sempre - self.tmpGeometries = [] # lista di geometria non ancora esistenti ma da contare per i punti di osnap (in map coordinates) - - - #============================================================================ - # SnapType - #============================================================================ - def setSnapType(self, snapType): - """ - Imposta il tipo di snapping - """ - if self.__snapType != snapType: - self.__snapType = snapType - self.clearCacheSnapPoints() - self.removeReferenceLines() - def getSnapType(self): - """ - Restituisce il tipo di snapping - """ - return self.__snapType - - - #============================================================================ - # SnapType - #============================================================================ - def getGeometryTypesAccordingToSnapType(self): - """ - Verifica quali geometrie vengono coinvolte dal tipo di snap impostato - Ritorna una lista di 3 elementi: (point, line, polygon) - - se il primo elemento é vero il tipo punto é coinvolto altrimenti falso - - se il secondo elemento é vero il tipo linea é coinvolto altrimenti falso - - se il terzo elemento é vero il tipo poligono é coinvolto altrimenti falso - """ - if self.getSnapType() == QadSnapTypeEnum.NONE or \ - self.getSnapType() & QadSnapTypeEnum.DISABLE: - return False, False, False - - point = False - line = False - polygon = False - - # o o - if self.getSnapType() & QadSnapTypeEnum.NOD or \ - self.getSnapType() & QadSnapTypeEnum.INS or \ - self.getSnapType() & QadSnapTypeEnum.NEA: - point = True - - # o o o - # o o o - # o o - # o o - if self.getSnapType() & QadSnapTypeEnum.END or \ - self.getSnapType() & QadSnapTypeEnum.END_PLINE or \ - self.getSnapType() & QadSnapTypeEnum.MID or \ - self.getSnapType() & QadSnapTypeEnum.CEN or \ - self.getSnapType() & QadSnapTypeEnum.QUA or \ - self.getSnapType() & QadSnapTypeEnum.INT or \ - self.getSnapType() & QadSnapTypeEnum.PER or \ - self.getSnapType() & QadSnapTypeEnum.TAN or \ - self.getSnapType() & QadSnapTypeEnum.NEA or \ - self.getSnapType() & QadSnapTypeEnum.APP or \ - self.getSnapType() & QadSnapTypeEnum.EXT or \ - self.getSnapType() & QadSnapTypeEnum.PAR or \ - self.getSnapType() & QadSnapTypeEnum.PR or \ - self.getSnapType() & QadSnapTypeEnum.EXT_INT or \ - self.getSnapType() & QadSnapTypeEnum.PER_DEF or \ - self.getSnapType() & QadSnapTypeEnum.TAN_DEF: - line = True - - # o o o - # o o o o - # o o - # o o - if self.getSnapType() & QadSnapTypeEnum.END or \ - self.getSnapType() & QadSnapTypeEnum.MID or \ - self.getSnapType() & QadSnapTypeEnum.CEN or \ - self.getSnapType() & QadSnapTypeEnum.QUA or \ - self.getSnapType() & QadSnapTypeEnum.INT or \ - self.getSnapType() & QadSnapTypeEnum.PER or \ - self.getSnapType() & QadSnapTypeEnum.TAN or \ - self.getSnapType() & QadSnapTypeEnum.NEA or \ - self.getSnapType() & QadSnapTypeEnum.APP or \ - self.getSnapType() & QadSnapTypeEnum.EXT or \ - self.getSnapType() & QadSnapTypeEnum.PAR or \ - self.getSnapType() & QadSnapTypeEnum.PR or \ - self.getSnapType() & QadSnapTypeEnum.EXT_INT or \ - self.getSnapType() & QadSnapTypeEnum.PER_DEF or \ - self.getSnapType() & QadSnapTypeEnum.TAN_DEF: - polygon = True - - return point, line, polygon - - - #============================================================================ - # Snapmode - #============================================================================ - def setSnapMode(self, snapMode): - """ - Imposta la modalità di snapping - """ - self.__snapMode = snapMode - def getSnapMode(self): - """ - Restituisce il modo di snapping - """ - return self.__snapMode - - - #============================================================================ - # SnapLayers - #============================================================================ - def setSnapLayers(self, snapLayers): - """ - Imposta i layer da considerare nello snapping - """ - self.__snapLayers = snapLayers - self.clearCacheSnapPoints() - def getSnapLayers(self): - """ - Restituisce la lista dei layer da considerare per lo snapping - """ - return self.__snapLayers - - - #============================================================================ - # SnapPointCRS - #============================================================================ - def setSnapPointCRS(self, snapPointCRS): - """ - Imposta il sistema di coordinate in cui memorizzare i punti di snap - CRS é QgsCoordinateReferenceSystem - """ - if self.__snapPointCRS != snapPointCRS: - self.__snapPointCRS = snapPointCRS - self.clearCacheSnapPoints() - def getSnapPointCRS(self): - """ - Restituisce il sistema di coordinate in cui memorizzare i punti di snap - """ - return self.__snapPointCRS - - - #============================================================================ - # setStartPoint - #============================================================================ - def setStartPoint(self, startPoint, CRS = None): - """ - il punto é espresso in __snapPointCRS se CRS = None - """ - if startPoint is not None and CRS is not None: - self.__startPoint = self.__transformPoint(startPoint, CRS, self.getSnapPointCRS()) # trasformo il punto - else: - self.__startPoint = startPoint - - - #============================================================================ - # setDistToExcludeNea - #============================================================================ - def setDistToExcludeNea(self, distToExcludeNea): - """ - setta la distanza entro la quale se ci sono dei punti di snap diversi da nearest - questi hanno priorità su nearest altrimenti nearest vincerebbe sempre - """ - self.__distToExcludeNea = distToExcludeNea - - - #=========================================================================== - # ReferenceLines - #=========================================================================== - def toggleReferenceLines(self, geom, point, CRS = None): - # usato solo per snap EXT o PAR - if not(self.__snapType & QadSnapTypeEnum.EXT) and \ - not(self.__snapType & QadSnapTypeEnum.PAR): - return - - # ritorna una tupla (, - # - # ) - dummy = qad_utils.closestSegmentWithContext(point, geom) - afterVertex = dummy[2] - if afterVertex is None: - return - - g = self.__transformGeomToSnapPointCRS(geom, CRS) # trasformo la geometria in coord dei punti di snap - - # verifico se ci sono archi - arc = None - arcList = QadArcList() - if arcList.fromGeom(g) > 0: - info = arcList.arcAt(afterVertex) - if info is not None: - arc = info[0] - - # verifico se ci sono cerchi - circle = None - circleList = QadCircleList() - if circleList.fromGeom(g) > 0: - info = circleList.circleAt(afterVertex) - if info is not None: - circle = info[0] - - pt1 = g.vertexAt(afterVertex - 1) - pt2 = g.vertexAt(afterVertex) - - if self.__snapType & QadSnapTypeEnum.EXT: - if arc is not None: # se fa parte di un arco - self.toggleExtArc(arc, CRS) - elif circle is None: # se non fa parte di un cerchio - self.toggleExtLine(pt1, pt2) - if self.__snapType & QadSnapTypeEnum.PAR: - if (arc is None) and (circle is None): # solo se non fa parte di un arco o di un cerchio - self.toggleParLine(pt1, pt2) - - def removeReferenceLines(self): - self.removeExtLines() - self.removeExtArcs() - self.removeParLines() - self.removeIntExtLine() - self.removeIntExtArc() - - - #============================================================================ - # setToleranceExtParLines - #============================================================================ - def setToleranceExtParLines(self, tolerance): - self.__toleranceExtParlines = tolerance - - - #============================================================================ - # tmpGeometries - #============================================================================ - def clearTmpGeometries(self): - del self.tmpGeometries[:] # svuoto la lista - - def setTmpGeometry(self, geom, CRS = None): - self.clearTmpGeometries() - self.appendTmpGeometry(geom) - - def appendTmpGeometry(self, geom, CRS = None): - if geom is None: - return - if CRS is not None: - g = self.__transformGeomToSnapPointCRS(geom, CRS) # trasformo la geometria in coord dei punti di snap - self.tmpGeometries.append(g) - else: - self.tmpGeometries.append(geom) - - def setTmpGeometries(self, geoms, CRS = None): - self.clearTmpGeometries() - for g in geoms: - self.appendTmpGeometry(g, CRS) - - - #=========================================================================== - # getSnapPoint - #=========================================================================== - def getSnapPoint(self, geom, point, CRS, excludePoints = None, polarAng = None, isTemporaryGeom = False): - """ - Data una geometria ed un punto (posizione del cursore) nel sistema di coordinate CRS - ottiene i punti di snap (con esclusione dei punti in excludePoints). - Resituisce un dizionario di liste di punti di snap - suddivisi per tipi di snap (es. {END : [pt1 .. ptn] MID : [pt1 .. ptn]}) - - CRS = sistema di coordinate in cui é espressa la geom e il punto (QgsCoordinateReferenceSystem) - - excludePoints = lista di punti da escludere espressa in __snapPointCRS - - polarAng angolo in radianti per il puntamento polare - - isTemporaryGeom flag che indica se geom é un oggetto temporaneo che ancora non esiste - """ - - p = self.__transformPoint(point, CRS, self.getSnapPointCRS()) # trasformo il punto in coord dei punti di snap - g = self.__transformGeomToSnapPointCRS(geom, CRS) # trasformo la geometria in coord dei punti di snap - - # cerca nella cache i punti di snap statici per una geometria - if geom is not None: - staticSnapPoints = self.__getCacheSnapPoints(g) - if staticSnapPoints is None: - staticSnapPoints = self.getStaticSnapPoints(g, None, isTemporaryGeom) - self.__setCacheSnapPoints(g, staticSnapPoints) - else: - staticSnapPoints = dict() - - # snap dinamici - dynamicSnapPoints = self.getDynamicSnapPoints(g, p) - - allSnapPoints = staticSnapPoints - for item in dynamicSnapPoints.items(): - allSnapPoints[item[0]] = item[1] - - # puntamento polare - if (self.__startPoint is not None) and (polarAng is not None): - allSnapPoints[QadSnapTypeEnum.POLAR] = self.getPolarCoord(point, polarAng) - - if self.__snapMode == QadSnapModeEnum.ONE_RESULT: - # Viene restituito solo il punto più vicino - result = self.__getNearestPoints(p, allSnapPoints) - elif self.__snapMode == QadSnapModeEnum.RESULTS_FOR_SAME_POS: - # take all snapping Results within a certain tolerance because rounding differences may occur - result = self.__getNearestPoints(p, allSnapPoints, 0.000001) - else: - result = allSnapPoints # Vengono restituiti tutti i punti - - if excludePoints is not None: - for p in excludePoints: - self.__delPoint(p, result) - - return result - - #============================================================================ - # CacheSnapPoints - #============================================================================ - def clearCacheSnapPoints(self): - del self.__cacheSnapPoints[:] # svuota la cache - def __getCacheSnapPoints(self, geom): - # cerca i punti di snap per una geometria - for item in self.__cacheSnapPoints: - if geom.equals(item[0]) == True: - return item[1] - return None - def __setCacheSnapPoints(self, geom, snapPoints): - g = QgsGeometry(geom) # copy constructor will prompt a deep copy of the object - self.__cacheSnapPoints.append([g, snapPoints]) - - - #============================================================================ - # getStaticSnapPoints - #============================================================================ - def getStaticSnapPoints(self, geom, CRS = None, isTemporaryGeom = False): - """ - Data una geometria ottiene i punti di snap statici che non dipendono dalla - posizione del cursore. - Resituisce un dizionario di liste di punti di snap - suddivisi per tipi di snap (es. {END : [pt1 .. ptn] MID : [pt1 .. ptn]}) - - CRS = sistema di coordinate in cui é espressa la geom (QgsCoordinateReferenceSystem) - - isTemporaryGeom flag che indica se geom é un oggetto temporaneo che ancora non esiste - """ - - result = dict() - - if (self.__snapType & QadSnapTypeEnum.DISABLE): - return result - - if self.__snapType & QadSnapTypeEnum.END: - result[QadSnapTypeEnum.END] = self.getEndPoints(geom, CRS) - if self.__snapType & QadSnapTypeEnum.END_PLINE: - result[QadSnapTypeEnum.END_PLINE] = self.getEndPoints(geom, CRS, QadVertexSearchModeEnum.ONLY_START_END) - if self.__snapType & QadSnapTypeEnum.MID: - result[QadSnapTypeEnum.MID] = self.getMidPoints(geom, CRS) - if self.__snapType & QadSnapTypeEnum.NOD: - result[QadSnapTypeEnum.NOD] = self.getNodPoint(geom, CRS) - if self.__snapType & QadSnapTypeEnum.QUA: - result[QadSnapTypeEnum.QUA] = self.getQuaPoints(geom, CRS) - if self.__snapType & QadSnapTypeEnum.INT: - result[QadSnapTypeEnum.INT] = self.getIntPoints(geom, CRS, isTemporaryGeom) - if self.__snapType & QadSnapTypeEnum.INS: - result[QadSnapTypeEnum.INS] = self.getNodPoint(geom, CRS) - if self.__snapType & QadSnapTypeEnum.APP: - result[QadSnapTypeEnum.APP] = self.getIntPoints(geom, CRS, isTemporaryGeom) - - return result - - - #============================================================================ - # getDynamicSnapPoints - #============================================================================ - def getDynamicSnapPoints(self, geom, point, CRS = None): - """ - Data una geometria ottiene i punti di snap dinamici che dipendono dalla - posizione del cursore (nel sistema di coordinate di geomLayer) o - o da __startPoint (nel sistema di coordinate __snapPointCRS). - Resituisce un dizionario di liste di punti di snap - suddivisi per tipi di snap (es. {END : [pt1 .. ptn] MID : [pt1 .. ptn]}) - - CRS = sistema di coordinate in cui sono espressi geom e point (QgsCoordinateReferenceSystem) - """ - - result = dict() - - if (self.__snapType & QadSnapTypeEnum.DISABLE): - return result - - if self.__snapType & QadSnapTypeEnum.CEN: - result[QadSnapTypeEnum.CEN] = self.getCenPoint(geom, point, CRS) - if self.__snapType & QadSnapTypeEnum.PER: - result[QadSnapTypeEnum.PER] = self.getPerPoints(geom, point, CRS) - if self.__snapType & QadSnapTypeEnum.TAN: - result[QadSnapTypeEnum.TAN] = self.getTanPoints(geom, CRS) - if self.__snapType & QadSnapTypeEnum.NEA: - result[QadSnapTypeEnum.NEA] = self.getNeaPoints(geom, point, CRS) - if self.__snapType & QadSnapTypeEnum.EXT: - result[QadSnapTypeEnum.EXT] = self.getExtPoints(point, CRS) - if self.__snapType & QadSnapTypeEnum.PAR: - result[QadSnapTypeEnum.PAR] = self.getParPoints(point, CRS) - if self.__snapType & QadSnapTypeEnum.PR: - result[QadSnapTypeEnum.PR] = self.getProgressPoint(geom, point, CRS)[0] - if self.__snapType & QadSnapTypeEnum.EXT_INT: - result[QadSnapTypeEnum.EXT_INT] = self.getIntExtPoint(geom, point, CRS) - if self.__snapType & QadSnapTypeEnum.PER_DEF: - result[QadSnapTypeEnum.PER_DEF] = self.getNeaPoints(geom, point, CRS) - if self.__snapType & QadSnapTypeEnum.TAN_DEF: - if geom is not None: - whatIs = qad_utils.whatGeomIs(point, geom) - if (type(whatIs) != list and type(whatIs) != tuple): # se non é una linea - result[QadSnapTypeEnum.TAN_DEF] = self.getNeaPoints(geom, point, CRS) - - return result - - - #============================================================================ - # getEndPoints - #============================================================================ - def getEndPoints(self, geom, CRS = None, VertexSearchMode = QadVertexSearchModeEnum.ALL): - """ - Cerca i punti iniziali e finali dei segmenti di una linea o di una multi-linea. - - CRS = sistema di coordinate in cui é espressa la geom (QgsCoordinateReferenceSystem) - Ritorna una lista di punti QgsPoint - """ - result = [] - - if geom is None: - return result - - g = self.__transformGeomToSnapPointCRS(geom, CRS) # trasformo la geometria in coord dei punti di snap - - geoms = qad_utils.asPointOrPolyline(g) - for igeom in geoms: - if (igeom.wkbType() == QGis.WKBLineString): - # verifico se ci sono archi - arcList = QadArcList() - arcList.fromGeom(igeom) - - # verifico se ci sono cerchi - circleList = QadCircleList() - circleList.fromGeom(igeom) - - points = igeom.asPolyline() # vettore di punti - i = 1 - vertexCount = len(points) - for ipoint in points: - _arc = arcList.arcAt(i - 1) - _circle = circleList.circleAt(i - 1) - - if _arc is not None: # se questo punto appartiene ad un arco - startEnd = _arc[1] - # se il punto corrisponde al punto iniziale o finale dell'arco - # (i - 1) perché arcAt vuole il secondo punto del segmento - if i - 1 == startEnd[0] or i - 1 == startEnd[1]: - inser = True - else: - inser = False - elif _circle is not None: # se questo punto appartiene ad un cerchio - inser = False - else: # se questo punto non appartiene né ad un arco ne ad un cerchio - inser = True - - if inser == True: - if (VertexSearchMode == QadVertexSearchModeEnum.EXCLUDE_START_END): - if (i != 1 and i != vertexCount): - self.__appendUniquePoint(result, ipoint, CRS) # aggiungo senza duplicazione - elif (VertexSearchMode == QadVertexSearchModeEnum.ONLY_START_END): - if (i == 1 or i == vertexCount): - self.__appendUniquePoint(result, ipoint) # aggiungo senza duplicazione - else: - self.__appendUniquePoint(result, ipoint) # aggiungo senza duplicazione - - i = i + 1 - - return result - - - #============================================================================ - # getMidPoints - #============================================================================ - def getMidPoints(self, geom, CRS = None): - """ - Cerca i punti medi dei segmenti di una linea o di una multi-linea. - - CRS = sistema di coordinate in cui é espressa la geom (QgsCoordinateReferenceSystem) - Ritorna una lista di punti QgsPoint - """ - result = [] - - if geom is None: - return result - - g = self.__transformGeomToSnapPointCRS(geom, CRS) # trasformo la geometria in coord dei punti di snap - - geoms = qad_utils.asPointOrPolyline(g) - for igeom in geoms: - if (igeom.wkbType() == QGis.WKBLineString): - # verifico se ci sono archi - arcList = QadArcList() - if arcList.fromGeom(igeom) > 0: - for arc in arcList.arcList: - self.__appendUniquePoint(result, arc.getMiddlePt()) # senza duplicazione - - # verifico se ci sono cerchi - circleList = QadCircleList() - circleList.fromGeom(igeom) - - points = igeom.asPolyline() # vettore di punti - first = True - i = 0 - for ipoint in points: - if first == True: - first = False - else: - # se questo punto non appartiene ad un arco né ad un cerchio - if (arcList.arcAt(i) is None) and (circleList.circleAt(i) is None): - point = qad_utils.getMiddlePoint(prevPoint, ipoint) - self.__appendUniquePoint(result, point) # senza duplicazione - prevPoint = ipoint - i = i + 1 - - return result - - - #============================================================================ - # getNodPoint - #============================================================================ - def getNodPoint(self, geom, CRS = None): - """ - Cerca il punto di inserimento di un punto. - - CRS = sistema di coordinate in cui é espressa la geom (QgsCoordinateReferenceSystem) - Ritorna una lista di punti QgsPoint - """ - result = [] - - if geom is None: - return result - - wkbType = geom.wkbType() - if wkbType != QGis.WKBPoint and wkbType != QGis.WKBMultiPoint: - return result - - g = self.__transformGeomToSnapPointCRS(geom, CRS) # trasformo la geometria in coord dei punti di snap - - if wkbType == QGis.WKBPoint: - self.__appendUniquePoint(result, g.asPoint()) # senza duplicazione - elif wkbType == QGis.WKBMultiPoint: - for point in g.asMultiPoint(): # vettore di punti - self.__appendUniquePoint(result, point) # senza duplicazione - return result - - - #============================================================================ - # getQuaPoints - #============================================================================ - def getQuaPoints(self, geom, CRS = None): - """ - Cerca i punti quadrante. - - CRS = sistema di coordinate in cui é espressa la geom (QgsCoordinateReferenceSystem) - Ritorna una lista di punti QgsPoint - """ - result = [] - - if geom is None: - return result - - wkbType = geom.wkbType() - if wkbType == QGis.WKBPoint or wkbType == QGis.WKBMultiPoint: - return result - - g = self.__transformGeomToSnapPointCRS(geom, CRS) # trasformo la geometria in coord dei punti di snap - - if wkbType == QGis.WKBLineString or wkbType == QGis.WKBMultiLineString: - # verifico se ci sono archi - arcList = QadArcList() - if arcList.fromGeom(g) > 0: - for arc in arcList.arcList: - points = arc.getQuadrantPoints() - for point in points: - self.__appendUniquePoint(result, point) # senza duplicazione - else: - # verifico se ci sono cerchi - circleList = QadCircleList() - if circleList.fromGeom(g) > 0: - for circle in circleList.circleList: - points = circle.getQuadrantPoints() - for point in points: - self.__appendUniquePoint(result, point) # senza duplicazione - - return result - - - #============================================================================ - # getIntPoints - #============================================================================ - def getIntPoints(self, geom, CRS = None, isTemporaryGeom = False): - """ - Cerca i punti di intersezione di un oggetto. - - CRS = sistema di coordinate in cui é espressa la geom (QgsCoordinateReferenceSystem) - - isTemporaryGeom flag che indica se geom é un oggetto temporaneo che ancora non esiste - Ritorna una lista di punti QgsPoint - """ - result = [] - - if geom is None: - return result - - g = self.__transformGeomToSnapPointCRS(geom, CRS) # trasformo la geometria in coord dei punti di snap - - for iLayer in self.__snapLayers: # ciclo sui layer da controllare - if (iLayer.type() == QgsMapLayer.VectorLayer): - iLayerCRS = iLayer.crs() - geom_iLayerCoords = QgsGeometry(g) - if CRS is None: # se non c'é CRS la geom si intende nel sistema di coord dei punti di snap - coordTransform = QgsCoordinateTransform(self.getSnapPointCRS(), iLayerCRS) # trasformo in coord ilayer - geom_iLayerCoords.transform(coordTransform) - else: - coordTransform = QgsCoordinateTransform(CRS, iLayerCRS) # trasformo in coord ilayer - geom_iLayerCoords.transform(coordTransform) - - feature = QgsFeature() - # cerco le entità che intersecano il rettangolo - # fetchAttributes, fetchGeometry, rectangle, useIntersect - for feature in iLayer.getFeatures(qad_utils.getFeatureRequest([], True, geom_iLayerCoords.boundingBox(), True)): - g2 = self.__transformGeomToSnapPointCRS(feature.geometry(), iLayerCRS) # trasformo la geometria in coord dei punti di snap - intersectionPoints = qad_utils.getIntersectionPoints(g, g2) - if intersectionPoints is not None: - for point in intersectionPoints: - # trasformo il punto in coord layer - self.__appendUniquePoint(result, point) # senza duplicazione - - if isTemporaryGeom: - intersectionPoints = qad_utils.getIntersectionPoints(g, g) - if intersectionPoints is not None: - for point in intersectionPoints: - # trasformo il punto in coord layer - self.__appendUniquePoint(result, point) # senza duplicazione - - # lista di geometria non ancora esistenti ma da contare per i punti di osnap (in map coordinates) - for tmpGeometry in self.tmpGeometries: - intersectionPoints = qad_utils.getIntersectionPoints(g, tmpGeometry) - if intersectionPoints is not None: - for point in intersectionPoints: - # trasformo il punto in coord layer - self.__appendUniquePoint(result, point) # senza duplicazione - - return result - - - #============================================================================ - # Inizio punti dinamici - #============================================================================ - - - #============================================================================ - # getCenPoint - #============================================================================ - def getCenPoint(self, geom, point, CRS = None): - """ - Cerca il punto centro di un arco o di un cerchio o il centroide di un poligono. - - CRS = sistema di coordinate in cui é espressa la geom (QgsCoordinateReferenceSystem) - Ritorna una lista di punti QgsPoint - """ - result = [] - - if geom is None: - return result - - wkbType = geom.wkbType() - if wkbType == QGis.WKBPoint or wkbType == QGis.WKBMultiPoint: - return result - elif wkbType == QGis.WKBPolygon or wkbType == QGis.WKBMultiPolygon: - centroidGeom = geom.centroid() - wkbType = centroidGeom.wkbType() - if wkbType == QGis.WKBPoint: - self.__appendUniquePoint(result, centroidGeom.asPoint()) # senza duplicazione - elif wkbType == QGis.WKBMultiPoint: - for centroidPt in centroidGeom.asMultiPoint(): # vettore di punti - self.__appendUniquePoint(result, centroidGeom.asPoint()) # senza duplicazione - - # ritorna una tupla (, - # - # ) - dummy = qad_utils.closestSegmentWithContext(point, geom) - afterVertex = dummy[2] - if afterVertex is None: - return result - - g = self.__transformGeomToSnapPointCRS(geom, CRS) # trasformo la geometria in coord dei punti di snap - - # verifico se ci sono cerchi - circleList = QadCircleList() - if circleList.fromGeom(g) > 0: - info = circleList.circleAt(afterVertex) - if info is not None: - circle = info[0] - self.__appendUniquePoint(result, circle.center) # senza duplicazione - - # verifico se ci sono archi - arcList = QadArcList() - if arcList.fromGeom(g) > 0: - info = arcList.arcAt(afterVertex) - if info is not None: - arc = info[0] - self.__appendUniquePoint(result, arc.center) # senza duplicazione - - return result - - - #============================================================================ - # getPerPoints - #============================================================================ - def getPerPoints(self, geom, point, CRS = None): - """ - Cerca il punto proiezione perpendicolare di self.__startPoint - (espresso in __snapPointCRS) sul lato di geom più vicino a point. - - CRS = sistema di coordinate in cui sono espressi geom e point (QgsCoordinateReferenceSystem) - Ritorna una lista di punti QgsPoint - """ - result = [] - - if geom is None: - return result - - if self.__startPoint is None: - return result - - # trasformo il self.__startPoint dal sistema __snapPointCRS a quello della geometria - startPointCRS = self.__transformPoint(self.__startPoint, self.getSnapPointCRS(), CRS) - - # ritorna una tupla (, - # - # ) - dummy = qad_utils.closestSegmentWithContext(point, geom) - afterVertex = dummy[2] - if afterVertex is None: - return result - - g = self.__transformGeomToSnapPointCRS(geom, CRS) # trasformo la geometria in coord dei punti di snap - - # verifico se ci sono archi - arcList = QadArcList() - if arcList.fromGeom(g) > 0: - info = arcList.arcAt(afterVertex) - if info is not None: - arc = info[0] - PerpendicularPoints = arc.getPerpendicularPoints(startPointCRS) - if PerpendicularPoints is not None: - for PerpendicularPoint in PerpendicularPoints: - # trasformo il punto in coord layer - self.__appendUniquePoint(result, PerpendicularPoint) # senza duplicazione - return result - - # verifico se ci sono cerchi - circleList = QadCircleList() - if circleList.fromGeom(g) > 0: - info = circleList.circleAt(afterVertex) - if info is not None: - circle = info[0] - PerpendicularPoints = circle.getPerpendicularPoints(startPointCRS) - if PerpendicularPoints is not None: - for PerpendicularPoint in PerpendicularPoints: - # trasformo il punto in coord layer - self.__appendUniquePoint(result, PerpendicularPoint) # senza duplicazione - return result - - pt1 = g.vertexAt(afterVertex - 1) - pt2 = g.vertexAt(afterVertex) - PerpendicularPoint = qad_utils.getPerpendicularPointOnInfinityLine(pt1, pt2, startPointCRS) - self.__appendUniquePoint(result, PerpendicularPoint) # senza duplicazione - - return result - - - #============================================================================ - # getTanPoints - #============================================================================ - def getTanPoints(self, geom, CRS = None): - """ - Cerca i punti di un oggetto che sono tangenti alla retta passante per self.__startPoint - (espresso in __snapPointCRS). - - CRS = sistema di coordinate in cui sono espressi geom e point (QgsCoordinateReferenceSystem) - Ritorna una lista di punti QgsPoint - """ - result = [] - - if geom is None: - return result - - if self.__startPoint is None: - return result - - g = self.__transformGeomToSnapPointCRS(geom, CRS) # trasformo la geometria in coord dei punti di snap - - # verifico se ci sono archi - arcList = QadArcList() - if arcList.fromGeom(g) > 0: - for arc in arcList.arcList: - points = arc.getTanPoints(self.__startPoint) - for point in points: - self.__appendUniquePoint(result, point) # senza duplicazione - - # verifico se ci sono cerchi - circleList = QadCircleList() - if circleList.fromGeom(g) > 0: - for circle in circleList.circleList: - points = circle.getTanPoints(self.__startPoint) - for point in points: - self.__appendUniquePoint(result, point) # senza duplicazione - - return result - - - #============================================================================ - # getNeaPoints - #============================================================================ - def getNeaPoints(self, geom, point, CRS = None): - """ - Cerca il punto di un oggetto che é più vicino a point. - - CRS = sistema di coordinate in cui sono espressi geom e point (QgsCoordinateReferenceSystem) - Ritorna una lista di punti QgsPoint - """ - result = [] - - if geom is None: - return result - - p = self.__transformPoint(point, CRS, self.getSnapPointCRS()) # trasformo il punto in coord dei punti di snap - g = self.__transformGeomToSnapPointCRS(geom, CRS) # trasformo la geometria in coord dei punti di snap - - # Riduco le geometrie in point o polyline - geoms = qad_utils.asPointOrPolyline(g) - - first = True - for g in geoms: - if g.wkbType() == QGis.WKBPoint: - pt = g.asPoint() - else: - # ritorna una tupla (, - # - # ) - dummy = qad_utils.closestSegmentWithContext(p, g) - pt = dummy[1] - - dist = qad_utils.getDistance(p, pt) - if first == True: - first = False - minDist = dist - closestPoint = pt - elif dist < minDist: - minDist = dist - closestPoint = pt - - self.__appendUniquePoint(result, closestPoint) # senza duplicazione - - return result - - - #============================================================================ - # getExtPoints - #============================================================================ - def toggleExtLine(self, pt1, pt2, CRS = None): - """ - Aggiunge una linea per la ricerca di punti con modalità EXT (estensione) - se non ancora inserita in lista altrimenti la rimuove dalla lista - pt1 e pt2 sono QgsPoint - sourceCRS é QgsCoordinateReferenceSystem opzionale - """ - self.toggleLine(QadSnapTypeEnum.EXT, pt1, pt2, CRS) - - def removeExtLines(self): - """ - Elimina tutte le linee per la ricerca di punti con modalità EXT (estensione) - """ - del self.__extLines[:] # svuoto la lista - - def getExtLines(self): - return self.__extLines - - def toggleExtArc(self, arc, CRS = None): - """ - Aggiunge un arco per la ricerca di punti con modalità EXT (estensione) - se non ancora inserito in lista altrimenti lo rimuove dalla lista - sourceCRS é QgsCoordinateReferenceSystem opzionale - """ - self.toggleArc(QadSnapTypeEnum.EXT, arc, CRS) - - def removeExtArcs(self): - """ - Elimina tutte gli archi per la ricerca di punti con modalità EXT (estensione) - """ - del self.__extArcs[:] # svuoto la lista - - def getExtArcs(self): - return self.__extArcs - - def getExtPoints(self, point, CRS): - """ - Cerca i punti sui prolungamenti delle linee memorizzate nella lista __extLines e __extArcs. - N.B. __extLines e point vanno espressi nello stesso sistema di coordinate - - point é un QgsPoint - - CRS = sistema di coordinate in cui é espresso point (QgsCoordinateReferenceSystem) - Ritorna una lista di punti QgsPoint - """ - result = [] - pt = self.__transformPoint(point, CRS, self.getSnapPointCRS()) # trasformo il punto - - if len(self.__extLines) > 0: - for line in self.__extLines: - ExtPoint = qad_utils.getPerpendicularPointOnInfinityLine(line[0], line[1], pt) - if qad_utils.getDistance(pt, ExtPoint) <= self.__toleranceExtParlines: - self.__appendUniquePoint(result, ExtPoint) # senza duplicazione - - if len(self.__extArcs) > 0: - circle = QadCircle() - for arc in self.__extArcs: - circle.set(arc.center,arc.radius) - ExtPoints = circle.getPerpendicularPoints(pt) - if ExtPoints is not None: - for ExtPoint in ExtPoints: - if qad_utils.getDistance(pt, ExtPoint) <= self.__toleranceExtParlines: - self.__appendUniquePoint(result, ExtPoint) # senza duplicazione - - return result - - - #============================================================================ - # getParPoints - #============================================================================ - def toggleParLine(self, pt1, pt2, CRS = None): - """ - Aggiunge una linea per la ricerca di punti con modalità PAR (parallela) - se non ancora inserita in lista altrimenti la rimuove dalla lista - pt1 e pt2 sono QgsPoint - sourceCRS é QgsCoordinateReferenceSystem opzionale - """ - self.toggleLine(QadSnapTypeEnum.PAR, pt1, pt2, CRS) - - def removeParLines(self): - """ - Elimina tutte le linee per la ricerca di punti con modalità PAR (parallela) - """ - del self.__parLines[:] # svuoto la lista - - def getParLines(self): - return self.__parLines - - def getParPoints(self, point, CRS): - """ - Cerca i punti sulle rette parallele alle linee memorizzate nella lista __partLines - che passano per __startPoint e che sono più vicino a point. - N.B. __parLines, __startPoint e point vanno espressi nello stesso sistema di coordinate - - line é una lista di 2 punti - - point é un QgsPoint - - CRS = sistema di coordinate in cui é espresso point (QgsCoordinateReferenceSystem) - Ritorna una lista di punti QgsPoint - """ - result = [] - - if (self.__startPoint is None) or len(self.__parLines) == 0: - return result - - p2 = QgsPoint(0, 0) - pt = self.__transformPoint(point, CRS, self.getSnapPointCRS()) # trasformo il punto - - for line in self.__parLines: - pt1 = line[0] - pt2 = line[1] - diffX = pt2.x() - pt1.x() - diffY = pt2.y() - pt1.y() - - if diffX == 0: # se la retta passante per pt1 e pt2 é verticale - parPoint = QgsPoint(self.__startPoint.x(), pt.y()) - elif diffY == 0: # se la retta passante per pt1 e pt2 é orizzontle - parPoint = QgsPoint(pt.x(), self.__startPoint.y()) - else: - # Calcolo l'equazione della retta passante per __startPoint con coefficente angolare noto - p2.setX(self.__startPoint.x() + diffX) - p2.setY(self.__startPoint.y() + diffY) - parPoint = qad_utils.getPerpendicularPointOnInfinityLine(self.__startPoint, p2, pt) - - if qad_utils.getDistance(pt, parPoint) <= self.__toleranceExtParlines: - self.__appendUniquePoint(result, parPoint, CRS) # senza duplicazione - - return result - - - #============================================================================ - # getProgressPoint - #============================================================================ - def setProgressDistance(self, progressDistance): - """ - Setta la distanza progressiva dall'inizio nel sistema __snapPointCRS - per la ricerca con modalità PR (progressiva) - """ - self.__progressDistance = progressDistance - - def getProgressDistance(self,): - return self.__progressDistance - - - def __getOverPoint(self, geom, points, pt1, pt2, dist): - """ - Cerca il punto sulla geometria che si estende oltre il punto pt2 - Ritorna il punto e il coeff. angolare in quel punto. usato da - """ - # verifico se ci sono archi - arc = None - arcList = QadArcList() - if arcList.fromGeom(geom) > 0: - info = arcList.arcAt(len(points) - 1) - if info is not None: - arc = info[0] - - if arc is None: # se questo punto non appartiene ad un arco - overPoint = qad_utils.getPolarPointBy2Pts(pt1, pt2, dist) - overAngle = qad_utils.getAngleBy2Pts(pt1, pt2) - else: # se questo punto appartiene ad un arco - angle = dist / arc.radius - if arc.getStartPt() == pt2: - overPoint = qad_utils.getPolarPointByPtAngle(arc.center, - arc.startAngle - angle, - arc.radius) - overAngle = angle - (math.pi / 2) - else: - overPoint = qad_utils.getPolarPointByPtAngle(arc.center, - arc.endAngle + angle, - arc.radius) - overAngle = angle + (math.pi / 2) - - return [overPoint, overAngle] - - - def getProgressPoint(self, geom, point, CRS = None): - """ - Cerca il punto sulla geometria ad un certa distanza dal vertice più vicino al punto - (se la distanza >=0 significa verso dall'inizio alla fine della linea, - se la distanza < 0 significa verso dalla fine all'inizio della linea. - - CRS = sistema di coordinate in cui sono espressi geom e point (QgsCoordinateReferenceSystem) - Ritorna una lista di punti QgsPoint + una lista di coefficienti angolari dei segmenti - su cui ricadono i punti - """ - result = [[],[]] - if geom is None: - return result - - if geom.wkbType() != QGis.WKBLineString: - return result - - ProgressPoints = [] - segmentAngles = [] - - p = self.__transformPoint(point, CRS, self.getSnapPointCRS()) # trasformo il punto in coord dei punti di snap - g = self.__transformGeomToSnapPointCRS(geom, CRS) # trasformo la geometria in coord dei punti di snap - - # Cerca i punti iniziali e finali dei segmenti di una linea. - Vertexes = self.getEndPoints(g, CRS) - - # Cerco il vertice più vicino - points = qad_utils.getNearestPoints(p, Vertexes) - if len(points) == 0: - return result - nearestPoint = points[0] - points = g.asPolyline() # vettore di punti - i = 0 - for iPoint in points: - if iPoint == nearestPoint: - break - i = i + 1 - - if self.__progressDistance == 0: - ProgressPoints.append(iPoint) - if i == (len(points) - 1): # ultimo punto - segmentAngles.append(qad_utils.getAngleBy2Pts(points[i - 1], points[i])) - else: - segmentAngles.append(qad_utils.getAngleBy2Pts(points[i], points[i + 1])) - - elif self.__progressDistance > 0: - # dall'inizio della linea verso la fine - remain = self.__progressDistance - if i == len(points) - 1: # selezionato ultimo vertice - pt1 = points[i - 1] - pt2 = points[i] - - info = self.__getOverPoint(geom, points, pt1, pt2, remain) - ProgressPoints.append(info[0]) - segmentAngles.append(info[1]) - else: - while remain > 0 and i < len(points) - 1: - pt1 = points[i] - pt2 = points[i + 1] - segmentLength = qad_utils.getDistance(pt1, pt2) - if segmentLength < remain: - # vado al segmento successivo - i = i + 1 - remain = remain - segmentLength - if i == len(points) - 1: # ultimo segmento quindi vado oltre - info = self.__getOverPoint(geom, points, pt1, pt2, remain) - ProgressPoints.append(info[0]) - segmentAngles.append(info[1]) - break - elif segmentLength > remain: - # é in questo segmento - ProgressPoints.append(qad_utils.getPolarPointBy2Pts(pt1, pt2, remain)) - segmentAngles.append(qad_utils.getAngleBy2Pts(pt1, pt2)) - break - else: # era esattamente il punto p2 - ProgressPoints.append(p2) - segmentAngles.append(qad_utils.getAngleBy2Pts(pt1, pt2)) - break - else: - # dalla fine della linea verso l'inizio - remain = self.__progressDistance * -1 - - if i == 0: # selezionato primo vertice - pt1 = points[1] - pt2 = points[0] - ProgressPoints.append(qad_utils.getPolarPointBy2Pts(pt2, pt1, -1 * remain)) - segmentAngles.append(qad_utils.getAngleBy2Pts(pt2, pt1)) - else: - while remain > 0 and i > 0: - pt1 = points[i] - pt2 = points[i - 1] - segmentLength = qad_utils.getDistance(pt1, pt2) - if segmentLength < remain: - # vado al segmento precedente - i = i - 1 - remain = remain - segmentLength - if i == 0: # primo segmento quindi vado oltre - info = self.__getOverPoint(geom, points, pt1, pt2, remain) - ProgressPoints.append(info[0]) - segmentAngles.append(info[1]) - break - elif segmentLength > remain: - # cerco nel segmento corrente - ProgressPoints.append(qad_utils.getPolarPointBy2Pts(pt1, pt2, remain)) - segmentAngles.append(qad_utils.getAngleBy2Pts(pt2, pt1)) - break - else: # era esattamente il punto p2 - ProgressPoints.append(p2) - segmentAngles.append(qad_utils.getAngleBy2Pts(pt2, pt1)) - break - - return (ProgressPoints, segmentAngles) - - - #============================================================================ - # toggleIntExtLine - #============================================================================ - def toggleIntExtLine(self, geom, point, CRS = None): - """ - Aggiunge una linea per la ricerca di punti con modalità EXT_INT (intersezione su estensione) - se non ancora inserita altrimenti la rimuove dalla lista - CRS é QgsCoordinateReferenceSystem opzionale - """ - # usato solo per snap EXT_INT - if not (self.__snapType & QadSnapTypeEnum.EXT_INT): - return - - # ritorna una tupla (, - # - # ) - dummy = qad_utils.closestSegmentWithContext(point, geom) - afterVertex = dummy[2] - if afterVertex is None: - return result - - g = self.__transformGeomToSnapPointCRS(geom, CRS) # trasformo la geometria in coord dei punti di snap - - # verifico se ci sono archi - arc = None - arcList = QadArcList() - if arcList.fromGeom(g) > 0: - info = arcList.arcAt(afterVertex) - if info is not None: - arc = info[0] - - circle = None - - if arc is not None: # se fa parte di un arco - _arc = QadArc(arc) - _arc.transformFromCRSToCRS(CRS, self.getSnapPointCRS()) # trasformo l'arco in coord layer - - # se non é stato selezionato alcun arco o alcuna linea lo aggiungo - if len(self.__intExtArc) == 0 and len(self.__intExtLine) == 0: - self.__intExtArc.append(_arc) - elif len(self.__intExtArc) > 0: - # se era già stato selezionato lo rimuovo - if (_arc == self.__intExtArc[0]): - self.removeIntExtArc() - elif circle is None: # se non fa parte di un cerchio - pt1 = g.vertexAt(afterVertex - 1) - pt2 = g.vertexAt(afterVertex) - - if CRS is not None: - __pt1 = self.__transformPoint(pt1, CRS, self.getSnapPointCRS()) # trasformo il punto in coord layer - __pt2 = self.__transformPoint(pt2, CRS, self.getSnapPointCRS()) # trasformo il punto in coord layer - else: - __pt1 = pt1 - __pt2 = pt2 - - # se non é stato selezionato alcun arco o alcuna linea la aggiungo - if len(self.__intExtArc) == 0 and len(self.__intExtLine) == 0: - self.__intExtLine.append(__pt1) - self.__intExtLine.append(__pt2) - elif len(self.__intExtLine) > 0: - # se era già stata selezionata la rimuovo - if (__pt1 == self.__intExtLine[0] or __pt1 == self.__intExtLine[1]) and \ - (__pt2 == self.__intExtLine[0] or __pt2 == self.__intExtLine[1]): - self.removeIntExtLine() - - - def removeIntExtLine(self): - """ - Elimina la linea per la ricerca di punti con modalità EXT_INT (intersezione su estensione) - """ - del self.__intExtLine[:] # svuoto la lista - - def getIntExtLine(self): - return self.__intExtLine - - def removeIntExtArc(self): - """ - Elimina l'arco per la ricerca di punti con modalità EXT_INT (intersezione su estensione) - """ - del self.__intExtArc[:] # svuoto la lista - - def getIntExtArc(self): - return self.__intExtArc - - def getIntExtPoint(self, geom, point, CRS = None): - """ - Cerca il punto di intersezione tra la geometria e una linea memorizzata in __intExtLine - - __intExtLine é lista di 2 punti = linea, > 2 punti = arco - - CRS = sistema di coordinate in cui é espressa geom (QgsCoordinateReferenceSystem) - Ritorna una lista di punti QgsPoint - """ - result = [] - - if geom is None: - return result - - # se non é stato selezionato alcun arco o alcuna linea - if len(self.__intExtArc) == 0 and len(self.__intExtLine) == 0: - return result - - # ritorna una tupla (, - # - # ) - dummy = qad_utils.closestSegmentWithContext(point, geom) - afterVertex = dummy[2] - if afterVertex is None: - return result - g = self.__transformGeomToSnapPointCRS(geom, CRS) # trasformo la geometria in coord dei punti di snap - - # verifico se ci sono archi - arc = None - arcList = QadArcList() - if arcList.fromGeom(g) > 0: - info = arcList.arcAt(afterVertex) - if info is not None: - arc = info[0] - - if arc is None: - # verifico se ci sono cerchi - circle = None - circleList = QadCircleList() - if circleList.fromGeom(g) > 0: - info = circleList.circleAt(afterVertex) - if info is not None: - circle = info[0] - - if (arc is None) and (circle is None): # nessun arco e cerchio - p1 = self.__transformPoint(g.vertexAt(afterVertex - 1), CRS, self.getSnapPointCRS()) # trasformo il punto - p2 = self.__transformPoint(g.vertexAt(afterVertex), CRS, self.getSnapPointCRS()) # trasformo il punto - - if len(self.__intExtArc) > 0: - circle1 = QadCircle() - circle1.set(self.__intExtArc[0].center, self.__intExtArc[0].radius) - - if arc is not None: # intersezione tra arco ed arco - circle2 = QadCircle() - circle2.set(arc.center, arc.radius) - intExtPoints = circle1.getIntersectionPointsWithCircle(circle2) - elif circle is not None: # intersezione tra cerchio ed arco - intExtPoints = circle1.getIntersectionPointsWithCircle(circle) - else: # intersezione tra linea ed arco - intExtPoints = circle1.getIntersectionPointsWithInfinityLine(p1, p2) - else: - if arc is not None: # intersezione tra arco e linea - circle1 = QadCircle() - circle1.set(arc.center, arc.radius) - intExtPoints = circle1.getIntersectionPointsWithInfinityLine(self.__intExtLine[0], self.__intExtLine[1]) - elif circle is not None: # intersezione tra cerchio e linea - intExtPoints = circle.getIntersectionPointsWithInfinityLine(self.__intExtLine[0], self.__intExtLine[1]) - else: # intersezione tra linea e linea - intExtPoints = [] - intExtPoint = qad_utils.getIntersectionPointOn2InfinityLines(self.__intExtLine[0], \ - self.__intExtLine[1], \ - p1, p2) - if intExtPoint is not None: - intExtPoints.append(intExtPoint) - - for intExtPoint in intExtPoints: - self.__appendUniquePoint(result, intExtPoint, CRS) # senza duplicazione - - return result - - - #============================================================================ - # utiliy functions - #============================================================================ - def __appendUniquePoint(self, pointList, point, CRS = None): - """ - Aggiunge un punto alla lista verificando che non sia già presente. - Resituisce True se l'inserimento é avvenuto False se il punto c'era già. - """ - _point = self.__transformPoint(point, CRS, self.getSnapPointCRS()) # trasformo il punto - return qad_utils.appendUniquePointToList(pointList, _point) - - - def __layerToMapCoordinatesPointList(self, pointList, layer, mQgsMapRenderer): - """ - Trasforma una lista punti da coordinate layer a coordinate map. - """ - i = 0 - for point in pointList: - pointList[i] = mQgsMapRenderer.layerToMapCoordinates(layer, point) - i = i + 1 - - - def __layerToMapCoordinatesRect(self, rect, layer, mQgsMapRenderer): - """ - Trasforma un rettangolo da coordinate layer a coordinate map. - """ - point = QgsPoint() - point.set(rect.xMinimum(), rect.yMinimum()) - point = mQgsMapRenderer.layerToMapCoordinates(layer, point) - rect.setXMinimum(point.x()) - rect.setYMinimum(point.y()) - point.set(rect.xMaximum(), rect.yMaximum()) - rect.setXMaximum(point.x()) - rect.setYMaximum(point.y()) - return rect - - def __transformPoint(self, point, sourceCRS, destCRS): - """ - Trasforma un punto dal sistema di coordinate sorgente a quello di destinazione. - sourceCRS e destCRS sono QgsCoordinateReferenceSystem - """ - if (sourceCRS is not None) and (destCRS is not None) and sourceCRS != destCRS: - coordTransform = QgsCoordinateTransform(sourceCRS, destCRS) # trasformo le coord - return coordTransform.transform(point) - else: - return point - - def __transformGeomToSnapPointCRS(self, geom, CRS = None): - """ - Trasformala geometria nel sistema di coordinate dei punti di snap - CRS é QgsCoordinateReferenceSystem della geometria - """ - if geom is None: - return None - - g = QgsGeometry(geom) - if (CRS is not None) and (self.getSnapPointCRS() is not None) and CRS != self.getSnapPointCRS(): - coordTransform = QgsCoordinateTransform(CRS, self.getSnapPointCRS()) # trasformo la geometria - g.transform(coordTransform) - return g - - def __getNearestPoints(self, point, SnapPoints, tolerance = 0): - """ - Ritorna una lista con il primo elemento che é il tipo di snap e - il secondo elemento é il punto più vicino a point. - SnapPoints é un dizionario di liste di punti di snap - suddivisi per tipi di snap (es. {END : [pt1 .. ptn] MID : [pt1 .. ptn]}) - """ - result = dict() - minDist = sys.float_info.max - - if tolerance == 0: # solo il punto più vicino - for item in SnapPoints.items(): - # escludo NEA che tratto dopo - if (item[0] != QadSnapTypeEnum.NEA) and (item[1] is not None): - for pt in item[1]: - dist = qad_utils.getDistance(point, pt) - if dist < minDist: - minDist = dist - snapType = item[0] - NearestPoint = pt - - # se il punto trovato é più distante di <__distToExcludeNea> allora considero anche - # eventuali punti NEA - if minDist > self.__distToExcludeNea: - # se é stato selezionato lo snap di tipo NEA - if QadSnapTypeEnum.NEA in SnapPoints.keys(): - items = SnapPoints[QadSnapTypeEnum.NEA] - if (items is not None): - for pt in items: - dist = qad_utils.getDistance(point, pt) - if dist < minDist: - minDist = dist - snapType = QadSnapTypeEnum.NEA - NearestPoint = pt - - if minDist != sys.float_info.max: # trovato - result[snapType] = [NearestPoint] - - else: - nearest = self.__getNearestPoints(point, SnapPoints) # punto più vicino - dummy = nearest.items() - dummy = dummy[0] - NearestPoint = dummy[1] - - for item in SnapPoints.items(): - NearestPoints = [] - for pt in item[1]: - dist = qad_utils.getDistance(NearestPoint, pt) - if dist <= tolerance: - NearestPoints.append(pt) - - if len(NearestPoints) > 0: - snapType = item[0] - result[snapType] = NearestPoint - - return result - - def __delPoint(self, point, SnapPoints): - """ - Cancella dalla lista SnapPoints il punto point (se esiste) - SnapPoints é un dizionario di liste di punti di snap - suddivisi per tipi di snap (es. {END : [pt1 .. ptn] MID : [pt1 .. ptn]}) - """ - for item in SnapPoints.items(): - i = 0 - for pt in item[1]: - if pt == point: - del item[1][i] - i = i + 1 - - def toggleLine(self, snapType, pt1, pt2, CRS = None): - """ - Aggiunge una linea per la ricerca di punti con modalità EXT p PAR - se non ancora inserita in lista altrimenti la rimuove dalla lista - pt1 e pt2 sono QgsPoint - CRS é QgsCoordinateReferenceSystem opzionale - """ - __pt1 = QgsPoint(0, 0) - __pt2 = QgsPoint(0, 0) - - if snapType == QadSnapTypeEnum.EXT: - lines = self.__extLines - elif snapType == QadSnapTypeEnum.PAR: - lines = self.__parLines - else: - return - - if CRS is not None: - __pt1 = self.__transformPoint(pt1, CRS, self.getSnapPointCRS()) # trasformo il punto in coord layer - __pt2 = self.__transformPoint(pt2, CRS, self.getSnapPointCRS()) # trasformo il punto in coord layer - else: - __pt1 = pt1 - __pt2 = pt2 - - # verifico che non ci sia già - exist = False - for line in lines: - if (__pt1 == line[0] or __pt1 == line[1]) and (__pt2 == line[0] or __pt2 == line[1]): - exist = True - if __pt1 != line[0]: - invertPoints = True - else: - invertPoints = False - break - - if exist == False: - # se non esiste ancora la aggiungo - line = [__pt1, __pt2] - lines.append(line) - else: - # se esiste di già la rimuovo - if invertPoints == True: - line = [__pt2, __pt1] - else: - line = [__pt1, __pt2] - lines.remove(line) - - def toggleArc(self, snapType, arc, CRS = None): - """ - Aggiunge una linea per la ricerca di punti con modalità EXT p PAR - se non ancora inserita in lista altrimenti la rimuove dalla lista - pt1 e pt2 sono QgsPoint - CRS é QgsCoordinateReferenceSystem opzionale - """ - if snapType == QadSnapTypeEnum.EXT: - arcs = self.__extArcs - else: - return - - _arc = QadArc(arc) - _arc.transformFromCRSToCRS(CRS, self.getSnapPointCRS()) # trasformo l'arco in coord layer - - # verifico che non ci sia già - exist = False - i = 0 - for iArc in arcs: - if _arc == iArc: - # se esiste di già lo rimuovo - del arcs[i] - return - i = i + 1 - - # se non esiste ancora lo aggiungo - arcs.append(_arc) - - - #============================================================================ - # getPolarCoord - #============================================================================ - def getPolarCoord(self, point, polarAng): - result = [] - - angle = qad_utils.getAngleBy2Pts(self.__startPoint, point) - value = math.modf(angle / polarAng) # ritorna una lista -> ( ) - if value[0] >= 0.5: # prendo intervallo successivo - angle = (value[1] + 1) * polarAng - else: - angle = value[1] * polarAng - - dist = qad_utils.getDistance(self.__startPoint, point) - pt2 = qad_utils.getPolarPointByPtAngle(self.__startPoint, angle, dist) - - polarPt = qad_utils.getPerpendicularPointOnInfinityLine(self.__startPoint, pt2, point) - if qad_utils.getDistance(polarPt, point) <= self.__toleranceExtParlines: - self.__appendUniquePoint(result, polarPt) # senza duplicazione - - return result +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + classe per gestire gli snap + + ------------------- + begin : 2013-05-22 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from qgis.core import * + +import math +import sys + +from . import qad_utils +from .qad_multi_geom import * +from .qad_geom_relations import * +from .qad_entity import * +from .qad_msg import QadMsg +from .qad_variables import QadVariables + + +# =============================================================================== +# QadSnapTypeEnum class. +# =============================================================================== +class QadSnapTypeEnum(): + NONE = 0 # nessuno + END = 1 # punti finali di ogni segmento + MID = 2 # punto medio + CEN = 4 # centro (centroide) + NOD = 8 # oggetto punto + QUA = 16 # punto quadrante + INT = 32 # intersezione + INS = 64 # punto di inserimento + PER = 128 # punto perpendicolare + TAN = 256 # tangente + NEA = 512 # punto più vicino + C = 1024 # pulisci all object snaps + APP = 2048 # intersezione apparente + EXT = 4096 # estensione + PAR = 8192 # parallelo + DISABLE = 16384 # osnap off + PR = 65536 # distanza progressiva + EXT_INT = 131072 # intersezione sull'estensione + PER_DEF = 262144 # perpendicolare differita (come NEA) + TAN_DEF = 524288 # tangente differita (come NEA) + POLAR = 1048576 # puntamento polare + END_PLINE = 2097152 # punti finali dell'intera polilinea + +# =============================================================================== +# QadSnapModeEnum class. +# =============================================================================== +class QadSnapModeEnum(): + ONE_RESULT = 0 # Viene restituito solo il punto più vicino + ALL_RESULTS = 2 # Tutti i punti + +# =============================================================================== +# QadVertexSearchModeEnum class. +# =============================================================================== +class QadVertexSearchModeEnum(): + ALL = 0 # tutti i vertici + EXCLUDE_START_END = 1 # escludi il punto iniziale e finale + ONLY_START_END = 2 # solo il punto iniziale e finale + + +# =============================================================================== +# Qad snapper class. +# =============================================================================== +class QadSnapper(): + """ + Classe che gestisce i punti di snap, i punti di snap sono sempre memorizzati nel sistema di coordinate del canvas + che utilizza un sistema piano (no coordinate geografiche) + """ + + + # ============================================================================ + # __init__ + # ============================================================================ + def __init__(self): + self.__snapType = QadSnapTypeEnum.NONE + self.__snapLayers = None + self.__snapMode = QadSnapModeEnum.ONE_RESULT + + # sistema di coordinate del canvas in cui memorizzare i punti di snap (per lavorare con coordinate piane xy) + self.__snapPointCRS = qgis.utils.iface.mapCanvas().mapSettings().destinationCrs() + self.__startPoint = None + self.__toleranceExtParLines = 0 + + self.__extLinearObjs = [] # lista degli oggetti lineari da estendere (QadLine o QadArc o QadEllipseArc) + self.__parLines = [] # lista delle linee per modo parallelo (ogni elemento é un QadLine) + self.__intExtLinearObjs = [] # lista degli oggetti lineari per intersezione su estensione (QadLine o QadArc o QadEllipseArc) + + self.__cacheEntitySet = QadCacheEntitySet() # cache delle entità qad + self.__oSnapPointsForPolar = dict() # dictionary di punti di osnap selezionati per l'opzione polare + self.__oSnapLinesForPolar = [] # lista delle linee (QadLine) per l'opzione polare + self.__progressDistance = 0.0 # distanza progressiva dall'inizio della linea + self.__distToExcludeNea = 0.0 # distanza entro la quale se ci sono dei punti di snap + # diversi da nearest questi hanno priorità su nearest + # altrimenti nearest vincerebbe sempre + self.tmpGeometries = [] # lista di geometrie qad non ancora esistenti ma da contare per i punti di osnap (in map coordinates) + + + # ============================================================================ + # SnapType + # ============================================================================ + def setSnapType(self, snapType): + """ + Imposta il tipo di snapping + """ + if self.__snapType != snapType: + self.__snapType = snapType + self.removeReferenceLines() + def getSnapType(self): + """ + Restituisce il tipo di snapping + """ + return self.__snapType + + + # ============================================================================ + # SnapType + # ============================================================================ + def getGeometryTypesAccordingToSnapType(self): + """ + Verifica quali geometrie vengono coinvolte dal tipo di snap impostato + Ritorna una lista di 3 elementi: (point, line, polygon) + - se il primo elemento é vero il tipo punto é coinvolto altrimenti falso + - se il secondo elemento é vero il tipo linea é coinvolto altrimenti falso + - se il terzo elemento é vero il tipo poligono é coinvolto altrimenti falso + """ + if self.getSnapType() == QadSnapTypeEnum.NONE or \ + self.getSnapType() & QadSnapTypeEnum.DISABLE: + return False, False, False + + point = False + line = False + polygon = False + + # o o + if self.getSnapType() & QadSnapTypeEnum.NOD or \ + self.getSnapType() & QadSnapTypeEnum.INS or \ + self.getSnapType() & QadSnapTypeEnum.NEA: + point = True + + # o o o + # o o o + # o o + # o o + if self.getSnapType() & QadSnapTypeEnum.END or \ + self.getSnapType() & QadSnapTypeEnum.END_PLINE or \ + self.getSnapType() & QadSnapTypeEnum.MID or \ + self.getSnapType() & QadSnapTypeEnum.CEN or \ + self.getSnapType() & QadSnapTypeEnum.QUA or \ + self.getSnapType() & QadSnapTypeEnum.INT or \ + self.getSnapType() & QadSnapTypeEnum.PER or \ + self.getSnapType() & QadSnapTypeEnum.TAN or \ + self.getSnapType() & QadSnapTypeEnum.NEA or \ + self.getSnapType() & QadSnapTypeEnum.APP or \ + self.getSnapType() & QadSnapTypeEnum.EXT or \ + self.getSnapType() & QadSnapTypeEnum.PAR or \ + self.getSnapType() & QadSnapTypeEnum.PR or \ + self.getSnapType() & QadSnapTypeEnum.EXT_INT or \ + self.getSnapType() & QadSnapTypeEnum.PER_DEF or \ + self.getSnapType() & QadSnapTypeEnum.TAN_DEF: + line = True + + # o o o + # o o o o + # o o + # o o + if self.getSnapType() & QadSnapTypeEnum.END or \ + self.getSnapType() & QadSnapTypeEnum.MID or \ + self.getSnapType() & QadSnapTypeEnum.CEN or \ + self.getSnapType() & QadSnapTypeEnum.QUA or \ + self.getSnapType() & QadSnapTypeEnum.INT or \ + self.getSnapType() & QadSnapTypeEnum.PER or \ + self.getSnapType() & QadSnapTypeEnum.TAN or \ + self.getSnapType() & QadSnapTypeEnum.NEA or \ + self.getSnapType() & QadSnapTypeEnum.APP or \ + self.getSnapType() & QadSnapTypeEnum.EXT or \ + self.getSnapType() & QadSnapTypeEnum.PAR or \ + self.getSnapType() & QadSnapTypeEnum.PR or \ + self.getSnapType() & QadSnapTypeEnum.EXT_INT or \ + self.getSnapType() & QadSnapTypeEnum.PER_DEF or \ + self.getSnapType() & QadSnapTypeEnum.TAN_DEF: + polygon = True + + return point, line, polygon + + + # ============================================================================ + # Snapmode + # ============================================================================ + def setSnapMode(self, snapMode): + """ + Imposta la modalità di snapping + """ + self.__snapMode = snapMode + def getSnapMode(self): + """ + Restituisce il modo di snapping + """ + return self.__snapMode + + + # ============================================================================ + # SnapLayers + # ============================================================================ + def setSnapLayers(self, snapLayers): + """ + Imposta i layer da considerare nello snapping + """ + self.__snapLayers = snapLayers + def getSnapLayers(self): + """ + Restituisce la lista dei layer da considerare per lo snapping + """ + return self.__snapLayers + + + # ============================================================================ + # setStartPoint + # ============================================================================ + def setStartPoint(self, startPoint): + """ + setta il punto di partenza usato per calcolare i punti di snap + """ + self.__startPoint = startPoint + + + # ============================================================================ + # setDistToExcludeNea + # ============================================================================ + def setDistToExcludeNea(self, distToExcludeNea): + """ + setta la distanza entro la quale se ci sono dei punti di snap diversi da nearest + questi hanno priorità su nearest altrimenti nearest vincerebbe sempre + """ + self.__distToExcludeNea = distToExcludeNea + + + # =========================================================================== + # getOsnapPtAndLinesForPolar + # =========================================================================== + def getOsnapPtAndLinesForPolar(self, point, polarAng, polarAngOffset, polarAddAngles): + # calcola i punti polari per tutti i punti di osnap selezionati per l'opzione polare e per per il punto corrente + # i punti vanno in result, le linee vanno in self.__oSnapLinesForPolar + + result = [] + del self.__oSnapLinesForPolar[:] + # per tutti i punti di osnap selezionati per l'opzione polare + for item in self.__oSnapPointsForPolar.items(): + # salto il tipo POLAR + if item[0] != QadSnapTypeEnum.POLAR: + for startPoint in item[1]: + pts = self.getPolarCoord(startPoint, point, polarAng, polarAngOffset, polarAddAngles) # ritorna una lista con un solo punto + if len(pts) > 0: + self.__appendUniquePoint(result, pts[0]) # senza duplicazione + l = QadLine().set(startPoint, pts[0]) + self.__oSnapLinesForPolar.append(l) + + # per il punto di partenza + if self.__startPoint is not None: + pts = self.getPolarCoord(self.__startPoint, point, polarAng, polarAngOffset, polarAddAngles) # ritorna una lista con un solo punto + if len(pts) > 0: + self.__appendUniquePoint(result, pts[0]) # senza duplicazione + l = QadLine().set(self.__startPoint, pts[0]) + self.__oSnapLinesForPolar.append(l) + + return result + + + # ============================================================================ + # getIntPtsBetweenOSnapLinesForPolar + # ============================================================================ + def getIntPtsBetweenOSnapLinesForPolar(self): + # calcolo le intersezioni delle linee polari + result = [] + i = 0 + totLines = len(self.__oSnapLinesForPolar) + while i < totLines: + line1 = self.__oSnapLinesForPolar[i] + j = i + 1 + while j < totLines: + line2 = self.__oSnapLinesForPolar[j] + point = QadIntersections.twoInfinityLines(line1, line2) + if point is not None: + self.__appendUniquePoint(result, point) # senza duplicazione + j = j + 1 + i = i + 1 + + return result + + + # ============================================================================ + # OSnapPointsForPolar + # ============================================================================ + def __toggleOSnapPointsForPolar(self, mousePoint, oSnapPointsForPolar, snapMarkerSizeInMapUnits = None): + """ + Aggiunge un punto di osnap usati per l'opzione polare + se non ancora inserito in lista altrimenti lo rimuove dalla lista + __oSnapPointsForPolar é un dizionario di liste di punti di snap + suddivisi per tipi di snap (es. {END : [pt1 .. ptn] MID : [pt1 .. ptn]}) + """ + del self.__oSnapLinesForPolar[:] + + markerSize = QadVariables.get(QadMsg.translate("Environment variables", "AUTOSNAPSIZE")) if snapMarkerSizeInMapUnits is None else snapMarkerSizeInMapUnits + + for itemToToggle in oSnapPointsForPolar.items(): + for ptToToggle in itemToToggle[1]: # per ogni punto + # se il punto è dentro il marker del punto di snap che ha dimensioni markerSize + if mousePoint.x() >= ptToToggle.x() - markerSize and mousePoint.x() <= ptToToggle.x() + markerSize and \ + mousePoint.y() >= ptToToggle.y() - markerSize and mousePoint.y() <= ptToToggle.y() + markerSize: + add = True + for item in self.__oSnapPointsForPolar.items(): + polarPts = item[1] + for i in range(len(polarPts) - 1, -1, -1): + polarPt = polarPts[i] + # se il punto è dentro il marker del punto di snap polare che ha dimensioni markerSize + if mousePoint.x() >= polarPt.x() - markerSize and mousePoint.x() <= polarPt.x() + markerSize and \ + mousePoint.y() >= polarPt.y() - markerSize and mousePoint.y() <= polarPt.y() + markerSize: + del polarPts[i] + add = False + + if add: + key = itemToToggle[0] + if key in self.__oSnapPointsForPolar: # se già presente + self.__oSnapPointsForPolar[key].append(ptToToggle) + else: + self.__oSnapPointsForPolar[key] = [ptToToggle] + +# autoSnapSize = QadVariables.get(QadMsg.translate("Environment variables", "AUTOSNAPSIZE")) +# +# for itemToToggle in oSnapPointsForPolar.items(): +# key = itemToToggle[0] +# # non considero alcuni tipi di snap +# if key == QadSnapTypeEnum.INT or key == QadSnapTypeEnum.PER or key == QadSnapTypeEnum.TAN or \ +# key == QadSnapTypeEnum.NEA or key == QadSnapTypeEnum.APP or key == QadSnapTypeEnum.EXT or \ +# key == QadSnapTypeEnum.PAR or key == QadSnapTypeEnum.PR or key == QadSnapTypeEnum.EXT_INT or \ +# key == QadSnapTypeEnum.PER_DEF or key == QadSnapTypeEnum.TAN_DEF or key == QadSnapTypeEnum.POLAR: +# continue +# +# for ptToToggle in itemToToggle[1]: # per ogni punto +# # il punto deve essere dentro il punto di snap che ha dimensioni snapMarkerSizeInMapUnits +# if point.x() >= ptToToggle.x() - snapMarkerSizeInMapUnits and point.x() <= ptToToggle.x() + snapMarkerSizeInMapUnits and \ +# point.y() >= ptToToggle.y() - snapMarkerSizeInMapUnits and point.y() <= ptToToggle.y() + snapMarkerSizeInMapUnits: +# add = True +# for item in self.__oSnapPointsForPolar.items(): +# i = 0 +# for pt in item[1]: +# if pt == ptToToggle: +# del item[1][i] +# add = False +# i = i + 1 +# +# if add: +# if key in self.__oSnapPointsForPolar: # se già presente +# self.__oSnapPointsForPolar[key].append(ptToToggle) +# else: +# self.__oSnapPointsForPolar[key] = [ptToToggle] + + + def removeOSnapPointsForPolar(self): + """ + Elimina tutti punti di osnap usati per l'opzione polare + """ + self.__oSnapPointsForPolar.clear() # svuoto il dizionario + del self.__oSnapLinesForPolar[:] # svuoto la lista + + def getOSnapPointsForPolar(self): + return self.__oSnapPointsForPolar + + def getOSnapLinesForPolar(self): + return self.__oSnapLinesForPolar + + + # =========================================================================== + # ReferenceLines + # =========================================================================== + def toggleReferenceLines(self, geomEntity, point, oSnapPointsForPolar = None, snapMarkerSizeInMapUnits = None): + """ + geomEntity può essere una QgsGeometry con coordinate del canvas oppure una QadEntity + se si passa una QadEntity lo snapper utilizza la sua cache + point è in coordinate del canvas + """ + if oSnapPointsForPolar is not None: + self.__toggleOSnapPointsForPolar(point, oSnapPointsForPolar, snapMarkerSizeInMapUnits) + + # usato solo per snap EXT o PAR + if not(self.__snapType & QadSnapTypeEnum.EXT) and \ + not(self.__snapType & QadSnapTypeEnum.PAR): + return + + if type(geomEntity) == QgsGeometry: # se è una geometria di QGIS + qadGeom = fromQgsGeomToQadGeom(geomEntity) + else: # è un'entità di QAD + # uso la cache delle entità di QAD + cacheEntity = self.__cacheEntitySet.getEntity(geomEntity.layerId(), geomEntity.featureId) + if cacheEntity is None: + cacheEntity = self.__cacheEntitySet.appendEntity(geomEntity) # la aggiungo alla cache + qadGeom = cacheEntity.getQadGeom() + + # la funzione ritorna una lista con + # ( + # + # + # + # se geometria chiusa è tipo polyline la lista contiene anche + # + # <"a sinistra di" se il punto é alla sinista della parte (< 0 -> sinistra, > 0 -> destra) + # ) + result = getQadGeomClosestPart(qadGeom, point) + g = getQadGeomPartAt(qadGeom, result[2], result[3], result[4]) + + geomType = g.whatIs() + if self.__snapType & QadSnapTypeEnum.EXT: + self.toggleExtLinearObj(g) + if self.__snapType & QadSnapTypeEnum.PAR: + self.toggleParLine(g) + + + def removeReferenceLines(self): + self.removeExtLinearObjs() + self.removeParLines() + self.removeIntExtLinearObj() + self.removeOSnapPointsForPolar() + + + # ============================================================================ + # setToleranceExtParLines + # ============================================================================ + def setToleranceExtParLines(self, tolerance): + self.__toleranceExtParlines = tolerance + + + # ============================================================================ + # tmpGeometries (le geometrie temporanee sono in crs del canvas + # ============================================================================ + def clearTmpGeometries(self): + del self.tmpGeometries[:] # svuoto la lista + + def setTmpGeometry(self, geom): + self.clearTmpGeometries() + self.appendTmpGeometry(geom) + + def appendTmpGeometry(self, geom): + if geom is None: + return + if type(geom) == QgsGeometry: # se è una geometria di QGIS + qadGeom = fromQgsGeomToQadGeom(geom) + self.tmpGeometries.append(qadGeom) + else: # è una geometria di QAD + self.tmpGeometries.append(geom) + + + def setTmpGeometries(self, geoms, CRS = None): + self.clearTmpGeometries() + for g in geoms: + self.appendTmpGeometry(g, CRS) + + + # =========================================================================== + # getSnapPoint + # =========================================================================== + def getSnapPoint(self, geomEntity, point, excludePoints = None, \ + polarAng = None, polarAngOffset = None, polarAddAngles = None, isTemporaryGeom = False): + """ + Data una geometria (QgsGeometry) o un'entita qad (QadEntity) ed un punto (posizione del cursore) nel sistema di coordinate map canvas + ottiene i punti di snap (con esclusione dei punti in excludePoints). + Resituisce un dizionario di liste di punti di snap + suddivisi per tipi di snap (es. {END : [pt1 .. ptn] MID : [pt1 .. ptn]}) + - excludePoints = lista di punti da escludere espressa in __snapPointCRS + - polarAng angolo in radianti per il puntamento polare + - polarAngOffset angolo in radianti relativo all'ultimo segmento + - polarAddAngles lista di angoli (in raduianti) in aggiunta a polarAng + - isTemporaryGeom flag che indica se geom é un oggetto temporaneo che ancora non esiste + """ + g = None + gPart = None + + if geomEntity is not None: + cacheEntity = None + qadGeom = None + if type(geomEntity) == QgsGeometry: # se è una geometria di QGIS + qadGeom = fromQgsGeomToQadGeom(geomEntity) + else: # è un'entità di QAD + # la geometria deve essere in un layer abilitato allo snapping + if geomEntity.layer in self.__snapLayers: + # uso la cache delle entità di QAD + cacheEntity = self.__cacheEntitySet.getEntity(geomEntity.layerId(), geomEntity.featureId) + if cacheEntity is None: + cacheEntity = self.__cacheEntitySet.appendEntity(geomEntity) # la aggiungo alla cache + qadGeom = cacheEntity.getQadGeom() + + if qadGeom is not None: + # la funzione ritorna una lista con + # ( + # + # + # + # se geometria chiusa è tipo polyline la lista contiene anche + # + # <"a sinistra di" se il punto é alla sinista della parte (< 0 -> sinistra, > 0 -> destra) + # ) + result = getQadGeomClosestPart(qadGeom, point) + atGeom = result[2] + atSubGeom = result[3] + g = getQadGeomAt(qadGeom, atGeom, atSubGeom) + if self.__snapMode == QadSnapModeEnum.ONE_RESULT: + gPart = getQadGeomPartAt(qadGeom, atGeom, atSubGeom, result[4]) + + # snap statici + staticSnapPoints = self.getStaticSnapPoints(g, gPart, isTemporaryGeom) + else: + staticSnapPoints = dict() + else: + staticSnapPoints = dict() + + # snap dinamici + dynamicSnapPoints = self.getDynamicSnapPoints(g, gPart, point) + + allSnapPoints = staticSnapPoints + for item in dynamicSnapPoints.items(): + allSnapPoints[item[0]] = item[1] + + # puntamento polare oppure angoli in aggiunta a polarAng + if polarAng is not None or polarAddAngles is not None: + # per tutti i punti di osnap selezionati per l'opzione polare e per per il punto corrente + allSnapPoints[QadSnapTypeEnum.POLAR] = self.getOsnapPtAndLinesForPolar(point, polarAng, polarAngOffset, polarAddAngles) + # calcolo le intersezioni delle linee polari e le aggiungo in allSnapPoints[QadSnapTypeEnum.INT] + intPts = self.getIntPtsBetweenOSnapLinesForPolar() + if len(intPts) > 0: + if QadSnapTypeEnum.INT in allSnapPoints: + for intPt in intPts: + self.__appendUniquePoint(allSnapPoints[QadSnapTypeEnum.INT], point) # senza duplicazione + else: + allSnapPoints[QadSnapTypeEnum.INT] = intPts + + if self.__snapMode == QadSnapModeEnum.ONE_RESULT: + # Viene restituito solo il punto più vicino + result = self.getNearestPoints(point, allSnapPoints) + elif self.__snapMode == QadSnapModeEnum.ALL_RESULTS: + result = allSnapPoints # Vengono restituiti tutti i punti + + if excludePoints is not None: + for p in excludePoints: + self.__delPoint(p, result) + + return result + + + # ============================================================================ + # getStaticSnapPoints + # ============================================================================ + def getStaticSnapPoints(self, geom, gPart, isTemporaryGeom = False): + """ + Data una geometria qad, la geometria di una parte della geometria qad, ottiene i punti di snap statici che non dipendono dalla + posizione del cursore. + La parte, se esistente viene usata per i punti di tipo: END, MID, INT, APP + Restituisce un dizionario di liste di punti di snap + suddivisi per tipi di snap (es. {END : [pt1 .. ptn] MID : [pt1 .. ptn]}) + - isTemporaryGeom flag che indica se geom é un oggetto temporaneo che ancora non esiste + """ + + result = dict() + + if (self.__snapType & QadSnapTypeEnum.DISABLE) or geom is None: + return result + + if self.__snapType & QadSnapTypeEnum.END: + if gPart is None: + result[QadSnapTypeEnum.END] = self.getEndPoints(geom, QadVertexSearchModeEnum.ALL) + else: + result[QadSnapTypeEnum.END] = self.getEndPoints(gPart, QadVertexSearchModeEnum.ALL) + + if self.__snapType & QadSnapTypeEnum.END_PLINE: + result[QadSnapTypeEnum.END_PLINE] = self.getEndPoints(geom, QadVertexSearchModeEnum.ONLY_START_END) + + if self.__snapType & QadSnapTypeEnum.MID: + if gPart is None: + result[QadSnapTypeEnum.MID] = self.getMidPoints(geom) + else: + result[QadSnapTypeEnum.MID] = self.getMidPoints(gPart) + + if self.__snapType & QadSnapTypeEnum.NOD: + result[QadSnapTypeEnum.NOD] = self.getNodPoint(geom) + + if self.__snapType & QadSnapTypeEnum.QUA: + if gPart is None: + result[QadSnapTypeEnum.QUA] = self.getQuaPoints(geom) + else: + result[QadSnapTypeEnum.QUA] = self.getQuaPoints(gPart) + + if self.__snapType & QadSnapTypeEnum.INT: + if gPart is None or isTemporaryGeom: + result[QadSnapTypeEnum.INT] = self.getIntPoints(geom, isTemporaryGeom) + else: + result[QadSnapTypeEnum.INT] = self.getIntPoints(gPart, isTemporaryGeom) + + if self.__snapType & QadSnapTypeEnum.INS: + result[QadSnapTypeEnum.INS] = self.getNodPoint(geom) + + if self.__snapType & QadSnapTypeEnum.APP: + if gPart is None or isTemporaryGeom: + result[QadSnapTypeEnum.APP] = self.getIntPoints(geom, isTemporaryGeom) + else: + result[QadSnapTypeEnum.APP] = self.getIntPoints(gPart, isTemporaryGeom) + + if self.__snapType & QadSnapTypeEnum.CEN: + if gPart is None: + result[QadSnapTypeEnum.CEN] = self.getCenPoint(geom) + else: + result[QadSnapTypeEnum.CEN] = self.getCenPoint(gPart) + + return result + + + # ============================================================================ + # getDynamicSnapPoints + # ============================================================================ + def getDynamicSnapPoints(self, geom, gPart, point): + """ + Data una geometria qad, la geometria di una parte della geometria qad, ottiene i punti di snap dinamici che dipendono dalla + posizione del cursore (nel sistema di coordinate del canvas) o + da __startPoint (nel sistema di coordinate canvas). + La parte, se esistente viene usata per i punti di tipo: NEA, MID, INT, APP + Resituisce un dizionario di liste di punti di snap + suddivisi per tipi di snap (es. {END : [pt1 .. ptn] MID : [pt1 .. ptn]}) + """ + + result = dict() + + if (self.__snapType & QadSnapTypeEnum.DISABLE): + return result + + if self.__snapType & QadSnapTypeEnum.PER: + if gPart is None: + result[QadSnapTypeEnum.PER] = self.getPerPoints(geom) + else: + result[QadSnapTypeEnum.PER] = self.getPerPoints(gPart) + + if self.__snapType & QadSnapTypeEnum.TAN: + if gPart is None: + result[QadSnapTypeEnum.TAN] = self.getTanPoints(geom) + else: + result[QadSnapTypeEnum.TAN] = self.getTanPoints(gPart) + + if self.__snapType & QadSnapTypeEnum.NEA: + if gPart is None: + result[QadSnapTypeEnum.NEA] = self.getNeaPoints(geom, point) + else: + result[QadSnapTypeEnum.NEA] = self.getNeaPoints(gPart, point) + + if self.__snapType & QadSnapTypeEnum.EXT: + result[QadSnapTypeEnum.EXT] = self.getExtPoints(point) + + if self.__snapType & QadSnapTypeEnum.PAR: + result[QadSnapTypeEnum.PAR] = self.getParPoints(point) + + if self.__snapType & QadSnapTypeEnum.PR: + if geom is not None: + geomType = geom.whatIs() + else: + geomType = ""; + # se la geometria principale è multi... devo considerare la parte selezionata + if geomType == "MULTI_LINEAR_OBJ" or geomType == "MULTI_POLYGON": + result[QadSnapTypeEnum.PR] = self.getProgressPoint(gPart, point)[0] + else: + result[QadSnapTypeEnum.PR] = self.getProgressPoint(geom, point)[0] + + if self.__snapType & QadSnapTypeEnum.EXT_INT: + result[QadSnapTypeEnum.EXT_INT] = self.getIntExtPoint(geom, point) + + if self.__snapType & QadSnapTypeEnum.PER_DEF: + if gPart is None: + result[QadSnapTypeEnum.PER_DEF] = self.getNeaPoints(geom, point) + else: + result[QadSnapTypeEnum.PER_DEF] = self.getNeaPoints(gPart, point) + + if self.__snapType & QadSnapTypeEnum.TAN_DEF: + if gPart is None: + g = geom + else: + g = gPart + + if g is not None: + # solo per geometria curva + geomType = g.whatIs() + if geomType == "ARC" or geomType == "CIRCLE" or geomType == "ELLIPSE_ARC" or geomType == "ELLIPSE": + result[QadSnapTypeEnum.TAN_DEF] = self.getNeaPoints(g, point) + + return result + + + # ============================================================================ + # getEndPoints + # ============================================================================ + def getEndPoints(self, geom, VertexSearchMode = QadVertexSearchModeEnum.ALL): + """ + Cerca i punti iniziali e finali di una geometria qad. + - VertexSearchMode = modalità di ricerca dei punti finali + Ritorna una lista di punti QgsPointXY + """ + result = [] + + if geom is None: + return result + + geomType = geom.whatIs() + if geomType == "LINE" or geomType == "ARC" or geomType == "ELLIPSE_ARC": + if VertexSearchMode == QadVertexSearchModeEnum.ONLY_START_END or \ + VertexSearchMode == QadVertexSearchModeEnum.ALL: + self.__appendUniquePoint(result, geom.getStartPt()) # aggiungo senza duplicazione + self.__appendUniquePoint(result, geom.getEndPt()) # aggiungo senza duplicazione + elif geomType == "POLYLINE": + if VertexSearchMode == QadVertexSearchModeEnum.ONLY_START_END or \ + VertexSearchMode == QadVertexSearchModeEnum.ALL: + self.__appendUniquePoint(result, geom.getStartPt()) # aggiungo senza duplicazione + + if VertexSearchMode == QadVertexSearchModeEnum.EXCLUDE_START_END or \ + VertexSearchMode == QadVertexSearchModeEnum.ALL: + i = 1 # secondo oggetto lineare della geometria polilinea + while i < geom.qty(): + linearObject = geom.getLinearObjectAt(i) + self.__appendUniquePoint(result, linearObject.getStartPt()) # aggiungo senza duplicazione + i = i + 1 + + if VertexSearchMode == QadVertexSearchModeEnum.ONLY_START_END or \ + VertexSearchMode == QadVertexSearchModeEnum.ALL: + self.__appendUniquePoint(result, geom.getEndPt()) # aggiungo senza duplicazione + + return result + + + # ============================================================================ + # getMidPoints + # ============================================================================ + def getMidPoints(self, geom): + """ + Cerca i punti medi dei segmenti di una geometria qad. + Ritorna una lista di punti QgsPointXY + """ + result = [] + + if geom is None: + return result + + geomType = geom.whatIs() + if geomType == "LINE" or geomType == "ARC" or geomType == "ELLIPSE_ARC": + self.__appendUniquePoint(result, QgsPointXY(geom.getMiddlePt())) # aggiungo senza duplicazione + elif geomType == "POLYLINE": + i = 0 + while i < geom.qty(): + linearObject = self.getLinearObjectAt(i) + self.__appendUniquePoint(result, QgsPointXY(linearObject.getMiddlePt())) # aggiungo senza duplicazione + i = i + 1 + + return result + + + # ============================================================================ + # getCenPoint + # ============================================================================ + def getCenPoint(self, geom): + """ + Cerca i punti centrali di archi, cerchi, archi di ellisse, ellissi presenti nella geometria qad. + Ritorna una lista di punti QgsPointXY + """ + result = [] + + if geom is None: + return result + + geomType = geom.whatIs() + if geomType == "ARC" or geomType == "CIRCLE" or geomType == "ELLIPSE" or geomType == "ELLIPSE_ARC": + self.__appendUniquePoint(result, geom.center) # aggiungo senza duplicazione + + elif geomType == "POLYLINE": + i = 0 + while i < geom.qty(): + linearObject = geom.getLinearObjectAt(i) + result.extend(self.getCenPoint(linearObject)) + i = i + 1 + + return result + + + # ============================================================================ + # getNodPoint + # ============================================================================ + def getNodPoint(self, geom): + """ + Cerca il punto di inserimento di un punto qad. + Ritorna una lista di punti QgsPointXY + """ + result = [] + + if geom is None: + return result + + geomType = geom.whatIs() + if geomType == "POINT": + self.__appendUniquePoint(result, QgsPointXY(geom)) # aggiungo senza duplicazione + elif geomType == "MULTI_POINT": + i = 0 + while i < geom.qty(): + self.__appendUniquePoint(result, self.getPointAt(i)) # aggiungo senza duplicazione + i = i + 1 + + return result + + + # ============================================================================ + # getQuaPoints + # ============================================================================ + def getQuaPoints(self, geom): + """ + Cerca i punti quadrante della geometria qad. + Ritorna una lista di punti QgsPointXY + """ + result = [] + + if geom is None: + return result + + geomType = geom.whatIs() + if geomType == "ARC" or geomType == "CIRCLE" or geomType == "ELLIPSE" or geomType == "ELLIPSE_ARC": + points = geom.getQuadrantPoints() + for point in points: + if points is not None: # perchè l'arco di ellisse ritorna i punti quadrante nulli se non sono nell'arco + self.__appendUniquePoint(result, point) # senza duplicazione + + return result + + + # ============================================================================ + # getIntPoints + # ============================================================================ + def getIntPoints(self, geom, isTemporaryGeom = False): + """ + Cerca i punti di intersezione di una geometria qad con altre geometrie sui layer __snapLayers. + - isTemporaryGeom flag che indica se geom é un oggetto temporaneo che ancora non esiste + Ritorna una lista di punti QgsPointXY + """ + result = [] + + if geom is None: + return result + + geomBoundingBoxCache = QadGeomBoundingBoxCache(geom) + boundingBox = geomBoundingBoxCache.getTotalBoundingBox() + + for iLayer in self.__snapLayers: # ciclo sui layer da controllare + if (iLayer.type() == QgsMapLayer.VectorLayer): + iLayerCRS = iLayer.crs() + coordTransform = QgsCoordinateTransform(self.__snapPointCRS, iLayerCRS, QgsProject.instance()) # trasformo in coord ilayer + iLayerBoundingBox = coordTransform.transformBoundingBox(boundingBox) + + feature = QgsFeature() + # cerco le entità che intersecano il rettangolo + # fetchAttributes, fetchGeometry, rectangle, useIntersect + for feature in iLayer.getFeatures(qad_utils.getFeatureRequest([], True, iLayerBoundingBox, True)): + g2 = fromQgsGeomToQadGeom(feature.geometry(), iLayerCRS) # ottengo una geometria di qad + if geom.whatIs() == g2.whatIs() and geom.equals(g2): continue # salta se stessa + + intersectionPoints = QadIntersections.twoGeomObjects(g2, geom, geomBoundingBoxCache) + for point in intersectionPoints: + self.__appendUniquePoint(result, point) # senza duplicazione + + if isTemporaryGeom: + intersectionPoints = QadIntersections.twoGeomObjects(geom, geom, geomBoundingBoxCache) + for point in intersectionPoints: + self.__appendUniquePoint(result, point) # senza duplicazione + + # lista di geometria non ancora esistenti ma da contare per i punti di osnap (in map coordinates) + for tmpGeometry in self.tmpGeometries: + intersectionPoints = QadIntersections.twoGeomObjects(tmpGeometry, geom, geomBoundingBoxCache) + for point in intersectionPoints: + self.__appendUniquePoint(result, point) # senza duplicazione + + return result + + + # ============================================================================ + # Inizio punti dinamici + # ============================================================================ + + + # ============================================================================ + # getPerPoints + # ============================================================================ + def getPerPoints(self, geom): + """ + Cerca il punto proiezione perpendicolare di self.__startPoint + (espresso in __snapPointCRS) sul lato di geom più vicino a point. + Ritorna una lista di punti QgsPointXY + """ + result = [] + + if geom is None: + return result + + if self.__startPoint is None: + return result + + PerpendicularPoints = QadPerpendicularity.fromPointToBasicGeomObjectExtensions(self.__startPoint, geom) + for PerpendicularPoint in PerpendicularPoints: + self.__appendUniquePoint(result, PerpendicularPoint) # senza duplicazione + + return result + + + # ============================================================================ + # getTanPoints + # ============================================================================ + def getTanPoints(self, geom): + """ + Cerca i punti di un oggetto che sono tangenti alla retta passante per self.__startPoint + (espresso in __snapPointCRS). + Ritorna una lista di punti QgsPointXY + """ + result = [] + + if geom is None: + return result + + if self.__startPoint is None: + return result + + result = [] + tangencyPoints = QadTangency.fromPointToBasicGeomObject(self.__startPoint, geom) + for tangencyPoint in tangencyPoints: + self.__appendUniquePoint(result, tangencyPoint) # senza duplicazione + + return result + + + # ============================================================================ + # getNeaPoints + # ============================================================================ + def getNeaPoints(self, geom, point): + """ + Cerca il punto di un oggetto che é più vicino a point. + Ritorna una lista di punti QgsPointXY + """ + if geom is None: return [] + + # la funzione ritorna una lista con + # ( + # + # + # + # se geometria chiusa è tipo polyline la lista contiene anche + # + # <"a sinistra di" se il punto é alla sinista della parte (< 0 -> sinistra, > 0 -> destra) + # ) + result = getQadGeomClosestPart(geom, point) + closestPoint = result[1] + result = [] + self.__appendUniquePoint(result, closestPoint) # senza duplicazione + + return result + + + # ============================================================================ + # toggleExtLinearObj + # ============================================================================ + def toggleExtLinearObj(self, linearObj): + """ + Aggiunge un oggetto lineare (QadLine o QadArc o QadEllipseArc) per la ricerca di punti con modalità EXT (estensione) + se non ancora inserito in lista altrimenti lo rimuove dalla lista + """ + geomType = linearObj.whatIs() + if geomType == "ARC" or geomType == "ELLIPSE_ARC" or geomType == "LINE": + # verifico che non ci sia già + i = 0 + for iObj in self.__extLinearObjs: + if geomType == iObj.whatIs(): + if geomType == "LINE": + if linearObj.equals(iObj): # uguali geometricamente (NON conta il verso) + # se esiste di già lo rimuovo + del self.__extLinearObjs[i] + return + elif linearObj == iObj: + # se esiste di già lo rimuovo + del self.__extLinearObjs[i] + return + i = i + 1 + + # se non esiste ancora lo aggiungo + self.__extLinearObjs.append(linearObj) + + + def removeExtLinearObjs(self): + """ + Elimina tutte gli oggetti lineari per la ricerca di punti con modalità EXT (estensione) + """ + del self.__extLinearObjs[:] # svuoto la lista + + def getExtLinearObjs(self): + return self.__extLinearObjs + + + def getExtPoints(self, point): + """ + Cerca i punti sui prolungamenti delle linee memorizzate nella lista __extLinearObjs. + - point é un QgsPointXY + Ritorna una lista di punti QgsPointXY + """ + result = [] + + for g in self.__extLinearObjs: + ExtPoints = QadPerpendicularity.fromPointToBasicGeomObjectExtensions(point, g) + for ExtPoint in ExtPoints: + if qad_utils.getDistance(point, ExtPoint) <= self.__toleranceExtParlines: + self.__appendUniquePoint(result, ExtPoint) # senza duplicazione + + return result + + + # ============================================================================ + # getParPoints + # ============================================================================ + def toggleParLine(self, line): + """ + Aggiunge una linea per la ricerca di punti con modalità PAR (parallela) + se non ancora inserita in lista altrimenti la rimuove dalla lista + """ + """ + Aggiunge una linea per la ricerca di punti con modalità EXT o PAR + se non ancora inserita in lista altrimenti la rimuove dalla lista + """ + if line.whatIs() != "LINE": return + + # verifico che non ci sia già + i = 0 + for iObj in self.__parLines: + if line.equals(iObj): # uguali geometricamente (NON conta il verso) + # se esiste di già lo rimuovo + del self.__parLines[i] + return + i = i + 1 + + # se non esiste ancora lo aggiungo + self.__parLines.append(line) + + + def removeParLines(self): + """ + Elimina tutte le linee per la ricerca di punti con modalità PAR (parallela) + """ + del self.__parLines[:] # svuoto la lista + + def getParLines(self): + return self.__parLines + + + def getParPoints(self, point): + """ + Cerca i punti sulle rette parallele alle linee memorizzate nella lista __partLines + che passano per __startPoint e che sono più vicino a point. + N.B. __parLines, __startPoint e point vanno espressi nello stesso sistema di coordinate + - line é una lista di 2 punti + - point é un QgsPointXY + Ritorna una lista di punti QgsPointXY + """ + result = [] + + if (self.__startPoint is None) or len(self.__parLines) == 0: + return result + + p2 = QgsPointXY() + + for line in self.__parLines: + pt1 = line.getStartPt() + pt2 = line.getEndPt() + diffX = pt2.x() - pt1.x() + diffY = pt2.y() - pt1.y() + + if diffX == 0: # se la retta passante per pt1 e pt2 é verticale + parPoint = QgsPointXY(self.__startPoint.x(), point.y()) + elif diffY == 0: # se la retta passante per pt1 e pt2 é orizzontle + parPoint = QgsPointXY(point.x(), self.__startPoint.y()) + else: + # Calcolo l'equazione della retta passante per __startPoint con coefficente angolare noto + p2.setX(self.__startPoint.x() + diffX) + p2.setY(self.__startPoint.y() + diffY) + parPoint = qad_utils.getPerpendicularPointOnInfinityLine(self.__startPoint, p2, point) + + if qad_utils.getDistance(point, parPoint) <= self.__toleranceExtParlines: + self.__appendUniquePoint(result, parPoint) # senza duplicazione + + return result + + + # ============================================================================ + # getProgressPoint + # ============================================================================ + def setProgressDistance(self, progressDistance): + """ + Setta la distanza progressiva dall'inizio nel sistema __snapPointCRS + per la ricerca con modalità PR (progressiva) + """ + self.__progressDistance = progressDistance + + + def getProgressDistance(self,): + return self.__progressDistance + + + def getProgressPoint(self, geom, point): + """ + Cerca il punto sulla geometria ad un certa distanza dal vertice più vicino al punto + (se la distanza >=0 significa verso dall'inizio alla fine della linea, + se la distanza < 0 significa verso dalla fine all'inizio della linea. + Ritorna una lista di punti QgsPointXY + una lista di coefficienti angolari dei segmenti + su cui ricadono i punti + """ + result = [[],[]] + if geom is None: + return result + + geomType = geom.whatIs() + if geomType != "LINE" and geomType != "ARC" and geomType != "ELLIPSE_ARC" and geomType != "POLYLINE": + return result + + g = geom.copy() + ProgressPoints = [] + segmentAngles = [] + + if self.__progressDistance < 0: + g.reverse() + progressDistance = -self.__progressDistance + else: + progressDistance = self.__progressDistance + + # la funzione ritorna una lista con + # ( + # + # + # + # + # + result = getQadGeomClosestVertex(g, point) + iVertex = result[5] + + lengthFromStart = 0 + if geomType == "POLYLINE": + # calcolo la distanza del vertice dall'inizio della geometria + for i in range(0, iVertex, 1): + lengthFromStart = lengthFromStart + g.getLinearObjectAt(i).length() + + delta = (lengthFromStart + progressDistance) - g.length() + # la funzione sposta il punto finale di una distanza delta allungando (se delta > 0) o accorciando (se delta < 0) la polilinea + if g.lengthen_delta(False, delta) == True: + linearObject = g.getLinearObjectAt(-1) # ultimo oggetto lineare + ProgressPoints.append(QgsPointXY(linearObject.getEndPt())) + if self.__progressDistance < 0: + linearObject.reverse() + segmentAngles.append(linearObject.getTanDirectionOnStartPt()) + else: + segmentAngles.append(linearObject.getTanDirectionOnEndPt()) + else: + # calcolo la distanza del vertice dall'inizio della geometria + if iVertex == 1: # punto finale + lengthFromStart = g.length() + delta = (lengthFromStart + progressDistance) - g.length() + + # la funzione sposta il punto finale di una distanza delta allungando (se delta > 0) o accorciando (se delta < 0) la polilinea + if g.lengthen_delta(False, delta) == True: + ProgressPoints.append(QgsPointXY(g.getEndPt())) + if self.__progressDistance < 0: + g.reverse() + segmentAngles.append(g.getTanDirectionOnStartPt()) + else: + segmentAngles.append(g.getTanDirectionOnEndPt()) + + return (ProgressPoints, segmentAngles) + + + # ============================================================================ + # toggleIntExtLinearObj + # ============================================================================ + def toggleIntExtLinearObj(self, geomEntity, point): + """ + Aggiunge un oggetto lineare (QadLine o QadArc o QadEllipseArc) per la ricerca di punti con modalità EXT_INT (intersezione su estensione) + se non ancora inserita altrimenti la rimuove dalla lista + """ + # usato solo per snap EXT_INT + if not (self.__snapType & QadSnapTypeEnum.EXT_INT): + return + + if type(geomEntity) == QgsGeometry: # se è una geometria di QGIS + qadGeom = fromQgsGeomToQadGeom(geomEntity) + else: # è un'entità di QAD + # uso la cache delle entità di QAD + cacheEntity = self.__cacheEntitySet.getEntity(geomEntity.layerId(), geomEntity.featureId) + if cacheEntity is None: + cacheEntity = self.__cacheEntitySet.appendEntity(geomEntity) # la aggiungo alla cache + qadGeom = cacheEntity.getQadGeom() + + # la funzione ritorna una lista con + # ( + # + # + # + # se la sotto-geometria è tipo polyline la lista contiene anche + # + # <"a sinistra di" se il punto é alla sinista della parte (< 0 -> sinistra, > 0 -> destra) + # ) + geomType = qadGeom.whatIs() + result = getQadGeomClosestPart(qadGeom, point) + if len(result) > 4: # se la funzione restituisce il numero della parte + linearObj = getQadGeomPartAt(qadGeom, result[2], result[3], result[4]) + geomType = linearObj.whatIs() + else: + linearObj = geom + + if geomType != "LINE" and geomType != "ARC" and geomType != "ELLIPSE_ARC": return + + # se non é stato selezionato alcun oggetto lineare lo aggiungo + if len(self.__intExtLinearObjs) == 0: + self.__intExtLinearObjs.append(linearObj.copy()) + else: + if geomType == self.__intExtLinearObjs[0].whatIs(): + if geomType == "LINE": + if linearObj.equals(self.__intExtLinearObjs[0]): # uguali geometricamente (NON conta il verso) + # se esiste di già lo rimuovo + self.removeIntExtLinearObj() + return + elif linearObj == self.__intExtLinearObjs[0]: + # se esiste di già lo rimuovo + self.removeIntExtLinearObj() + return + + + def removeIntExtLinearObj(self): + """ + Elimina 'oggetto lineare per la ricerca di punti con modalità EXT_INT (intersezione su estensione) + """ + del self.__intExtLinearObjs[:] # svuoto la lista + + + def getIntExtLinearObjs(self): + return self.__intExtLinearObjs + + + def getIntExtPoint(self, geom, point): + """ + Cerca il punto di intersezione tra la geometria e un oggetto lineare memorizzato in __intExtLinearObjs + Ritorna una lista di punti QgsPointXY + """ + if geom is None: return [] + + # se non é stato selezionato alcun oggetto lineare + if len(self.__intExtLinearObjs) == 0: return [] + + # la funzione ritorna una lista con + # ( + # + # + # + # se geometria chiusa è tipo polyline la lista contiene anche + # + # <"a sinistra di" se il punto é alla sinista della parte (< 0 -> sinistra, > 0 -> destra) + # ) + result = getQadGeomClosestPart(geom, point) + g = getQadGeomPartAt(geom, result[2], result[3], result[4]) + + intExtPoints = QadIntersections.twoBasicGeomObjectExtensions(g, self.__intExtLinearObjs[0]) + + result = [] + for intExtPoint in intExtPoints: + self.__appendUniquePoint(result, intExtPoint) # senza duplicazione + + return result + + + # ============================================================================ + # utiliy functions + # ============================================================================ + def __appendUniquePoint(self, pointList, point): + """ + Aggiunge un punto alla lista verificando che non sia già presente. + Resituisce True se l'inserimento é avvenuto False se il punto c'era già. + """ + # Si assume che la lista sia ordinata, l'inserimento avverà mantenendo l'ordinamento + lo = 0 + hi = len(pointList) + while lo < hi: + mid = (lo + hi) // 2 # digits after the decimal point are removed + + if self.__comparePts(pointList[mid], point) == -1: lo = mid+1 + else: hi = mid + + if lo != len(pointList) and self.__comparePts(pointList[lo], point) == 0: # il punto c'era già + return False + pointList.insert(lo, point) + return True + + #return qad_utils.appendUniquePointToList(pointList, _point) + + + # ============================================================================ + # __comparePts + # ============================================================================ + def __comparePts(self, p1, p2): + # compara 2 punti, ritorna 0 se sono uguali, -1 se il primo < del secondo, 1 se il primo > del secondo + if p1.x() > p2.x(): return 1 + if p1.x() < p2.x(): return -1 + # le x sono uguali quindi verifici le y + if p1.y() > p2.y(): return 1 + if p1.y() < p2.y(): return -1 + return 0 # numeri uguali + + + # ============================================================================ + # getNearestPoints + # ============================================================================ + def getNearestPoints(self, point, SnapPoints, tolerance = 0): + """ + Ritorna una lista con il primo elemento che é il tipo di snap e + il secondo elemento é il punto più vicino a point. + SnapPoints é un dizionario di liste di punti di snap + suddivisi per tipi di snap (es. {END : [pt1 .. ptn] MID : [pt1 .. ptn]}) + """ + result = dict() + minDist = sys.float_info.max + + if tolerance == 0: # solo il punto più vicino + for item in SnapPoints.items(): + # escludo NEA e POLAR che tratto dopo + if (item[0] != QadSnapTypeEnum.NEA and item[0] != QadSnapTypeEnum.POLAR) and (item[1] is not None): + for pt in item[1]: + dist = qad_utils.getDistance(point, pt) + if dist < minDist: + minDist = dist + snapType = item[0] + NearestPoint = pt + + # se il punto trovato é più distante di <__distToExcludeNea> allora considero anche + # eventuali punti NEA + if minDist > self.__distToExcludeNea: + # se é stato selezionato lo snap di tipo NEA + if QadSnapTypeEnum.NEA in SnapPoints.keys(): + items = SnapPoints[QadSnapTypeEnum.NEA] + if (items is not None): + for pt in items: + dist = qad_utils.getDistance(point, pt) + if dist < minDist: + minDist = dist + snapType = QadSnapTypeEnum.NEA + NearestPoint = pt + + # se il punto trovato é più distante di <__distToExcludeNea> allora considero anche + # eventuali punti POLAR + if minDist > self.__distToExcludeNea: + # se é stato selezionato lo snap di tipo POLAR + if QadSnapTypeEnum.POLAR in SnapPoints.keys(): + items = SnapPoints[QadSnapTypeEnum.POLAR] + if (items is not None): + for pt in items: + dist = qad_utils.getDistance(point, pt) + if dist < minDist: + minDist = dist + snapType = QadSnapTypeEnum.POLAR + NearestPoint = pt + + if minDist != sys.float_info.max: # trovato + result[snapType] = [NearestPoint] + + else: + nearest = self.getNearestPoints(point, SnapPoints) # punto più vicino + dummy = nearest.items() + dummy = dummy[0] + NearestPoint = dummy[1] + + for item in SnapPoints.items(): + NearestPoints = [] + for pt in item[1]: + dist = qad_utils.getDistance(NearestPoint, pt) + if dist <= tolerance: + NearestPoints.append(pt) + + if len(NearestPoints) > 0: + snapType = item[0] + result[snapType] = NearestPoint + + return result + + + def __delPoint(self, point, SnapPoints): + """ + Cancella dalla lista SnapPoints il punto point (se esiste) + SnapPoints é un dizionario di liste di punti di snap + suddivisi per tipi di snap (es. {END : [pt1 .. ptn] MID : [pt1 .. ptn]}) + """ + for item in SnapPoints.items(): + i = 0 + for pt in item[1]: + if pt == point: + del item[1][i] + i = i + 1 + + + # ============================================================================ + # getPolarCoord + # ============================================================================ + def getPolarCoord(self, startPoint, point, polarAng, polarAngOffset, polarAddAngles): + result = [] + + angle = qad_utils.getAngleBy2Pts(startPoint, point) + offsetAngle = angle - polarAngOffset + value = math.modf(offsetAngle / polarAng) # ritorna una lista -> ( ) + if value[0] >= 0.5: # prendo intervallo successivo + offsetAngle = (value[1] + 1) * polarAng + else: + offsetAngle = value[1] * polarAng + offsetAngle = offsetAngle + polarAngOffset + + dist = qad_utils.getDistance(startPoint, point) + pt2 = qad_utils.getPolarPointByPtAngle(startPoint, offsetAngle, dist) + + polarPt = qad_utils.getPerpendicularPointOnInfinityLine(startPoint, pt2, point) + if qad_utils.getDistance(polarPt, point) <= self.__toleranceExtParlines: + self.__appendUniquePoint(result, polarPt) # senza duplicazione + + if polarAddAngles is not None and len(polarAddAngles) > 0: + for polarAddAngle in polarAddAngles: + polarPt = qad_utils.getPolarPointByPtAngle(startPoint, polarAddAngle + polarAngOffset, dist) + if qad_utils.getDistance(polarPt, point) <= self.__toleranceExtParlines: + self.__appendUniquePoint(result, polarPt) # senza duplicazione + + return result + + +# ============================================================================ +# funzioni generiche +# ============================================================================ + + +# =============================================================================== +# str2snapTypeEnum +# =============================================================================== +def str2snapTypeEnum(s): + """ + Ritorna la conversione di una stringa in una combinazione di tipi di snap + oppure -1 se non ci sono snap indicati. + """ + snapType = QadSnapTypeEnum.NONE + snapTypeStrList = s.strip().split(",") + for snapTypeStr in snapTypeStrList: + snapTypeStr = snapTypeStr.strip().upper() + + # "NES" nessuno snap + if snapTypeStr == QadMsg.translate("Snap", "NONE") or snapTypeStr == "_NONE": + return QadSnapTypeEnum.NONE + # "FIN" punti finali di ogni segmento + elif snapTypeStr == QadMsg.translate("Snap", "END") or snapTypeStr == "_END": + snapType = snapType | QadSnapTypeEnum.END + # "FIN_PL" punti finali dell'intera polilinea + elif snapTypeStr == QadMsg.translate("Snap", "END_PL") or snapTypeStr == "_END_PL": + snapType = snapType | QadSnapTypeEnum.END_PLINE + # "MED" punto medio + elif snapTypeStr == QadMsg.translate("Snap", "MID") or snapTypeStr == "_MID": + snapType = snapType | QadSnapTypeEnum.MID + # "CEN" centro (centroide) + elif snapTypeStr == QadMsg.translate("Snap", "CEN") or snapTypeStr == "_CEN": + snapType = snapType | QadSnapTypeEnum.CEN + # "NOD" oggetto punto + elif snapTypeStr == QadMsg.translate("Snap", "NOD") or snapTypeStr == "_NOD": + snapType = snapType | QadSnapTypeEnum.NOD + # "QUA" punto quadrante + elif snapTypeStr == QadMsg.translate("Snap", "QUA") or snapTypeStr == "_QUA": + snapType = snapType | QadSnapTypeEnum.QUA + # "INT" intersezione + elif snapTypeStr == QadMsg.translate("Snap", "INT") or snapTypeStr == "_INT": + snapType = snapType | QadSnapTypeEnum.INT + # "INS" punto di inserimento + elif snapTypeStr == QadMsg.translate("Snap", "INS") or snapTypeStr == "_INS": + snapType = snapType | QadSnapTypeEnum.INS + # "PER" punto perpendicolare + elif snapTypeStr == QadMsg.translate("Snap", "PER") or snapTypeStr == "_PER": + snapType = snapType | QadSnapTypeEnum.PER + # "TAN" tangente + elif snapTypeStr == QadMsg.translate("Snap", "TAN") or snapTypeStr == "_TAN": + snapType = snapType | QadSnapTypeEnum.TAN + # "VIC" punto più vicino + elif snapTypeStr == QadMsg.translate("Snap", "NEA") or snapTypeStr == "_NEA": + snapType = snapType | QadSnapTypeEnum.NEA + # "APP" intersezione apparente + elif snapTypeStr == QadMsg.translate("Snap", "APP") or snapTypeStr == "_APP": + snapType = snapType | QadSnapTypeEnum.APP + # "EST" Estensione + elif snapTypeStr == QadMsg.translate("Snap", "EXT") or snapTypeStr == "_EXT": + snapType = snapType | QadSnapTypeEnum.EXT + # "PAR" Parallelo + elif snapTypeStr == QadMsg.translate("Snap", "PAR") or snapTypeStr == "_PAR": + snapType = snapType | QadSnapTypeEnum.PAR + # se inizia per "PR" distanza progressiva + elif snapTypeStr.find(QadMsg.translate("Snap", "PR")) == 0 or \ + snapTypeStr.find("_PR") == 0: + # la parte successiva PR può essere vuota o numerica + if snapTypeStr.find(QadMsg.translate("Snap", "PR")) == 0: + param = snapTypeStr[len(QadMsg.translate("Snap", "PR")):] + else: + param = snapTypeStr[len("_PR"):] + if len(param) == 0 or qad_utils.str2float(param) is not None: + snapType = snapType | QadSnapTypeEnum.PR + # "EST_INT" intersezione su estensione + elif snapTypeStr == QadMsg.translate("Snap", "EXT_INT") or snapTypeStr == "_EXT_INT": + snapType = snapType | QadSnapTypeEnum.EXT_INT + + return snapType if snapType != QadSnapTypeEnum.NONE else -1 + + +# =============================================================================== +# snapTypeEnum2Str +# =============================================================================== +def snapTypeEnum2Str(snapType): + """ + Ritorna la conversione di un tipo di snap in una stringa. + """ + # "FIN" punti finali di ogni segmento + if snapType == QadSnapTypeEnum.END: + return QadMsg.translate("Snap", "END") + # "FIN_PL" punti finali dell'intera polilinea + elif snapType == QadSnapTypeEnum.END_PLINE: + return QadMsg.translate("Snap", "END_PL") + # "MED" punto medio + elif snapType == QadSnapTypeEnum.MID: + return QadMsg.translate("Snap", "MID") + # "CEN" centro (centroide) + elif snapType == QadSnapTypeEnum.CEN: + return QadMsg.translate("Snap", "CEN") + # "NOD" oggetto punto + elif snapType == QadSnapTypeEnum.NOD: + return QadMsg.translate("Snap", "NOD") + # "QUA" punto quadrante + elif snapType == QadSnapTypeEnum.QUA: + return QadMsg.translate("Snap", "QUA") + # "INT" intersezione + elif snapType == QadSnapTypeEnum.INT: + return QadMsg.translate("Snap", "INT") + # "INS" punto di inserimento + elif snapType == QadSnapTypeEnum.INS: + return QadMsg.translate("Snap", "INS") + # "PER" punto perpendicolare + elif snapType == QadSnapTypeEnum.PER: + return QadMsg.translate("Snap", "PER") + # "TAN" tangente + elif snapType == QadSnapTypeEnum.TAN: + return QadMsg.translate("Snap", "TAN") + # "VIC" punto più vicino + elif snapType == QadSnapTypeEnum.NEA: + return QadMsg.translate("Snap", "NEA") + # "APP" intersezione apparente + elif snapType == QadSnapTypeEnum.APP: + return QadMsg.translate("Snap", "APP") + # "EST" Estensione + elif snapType == QadSnapTypeEnum.EXT: + return QadMsg.translate("Snap", "EXT") + # "PAR" Parallelo + elif snapType == QadSnapTypeEnum.PAR: + return QadMsg.translate("Snap", "PAR") + # "PR" distanza progressiva + elif snapType == QadSnapTypeEnum.PR: + return QadMsg.translate("Snap", "PR") + # "EST_INT" intersezione su estensione + elif snapType == QadSnapTypeEnum.EXT_INT: + return QadMsg.translate("Snap", "EXT_INT") + + return "" + + +# =============================================================================== +# snapTypeEnum2Descr +# =============================================================================== +def snapTypeEnum2Descr(snapType): + """ + Ritorna la conversione di un tipo di snap in una stringa descrittiva. + """ + # "FIN" punti finali di ogni segmento + if snapType == QadSnapTypeEnum.END: + return QadMsg.translate("Snap", "Segment end point") + # "FIN_PL" punti finali dell'intera polilinea + elif snapType == QadSnapTypeEnum.END_PLINE: + return QadMsg.translate("Snap", "Polyline end point") + # "MED" punto medio + elif snapType == QadSnapTypeEnum.MID: + return QadMsg.translate("Snap", "Middle point") + # "CEN" centro (centroide) + elif snapType == QadSnapTypeEnum.CEN: + return QadMsg.translate("Snap", "Center point") + # "NOD" oggetto punto + elif snapType == QadSnapTypeEnum.NOD: + return QadMsg.translate("Snap", "Node") + # "QUA" punto quadrante + elif snapType == QadSnapTypeEnum.QUA: + return QadMsg.translate("Snap", "Quadrant") + # "INT" intersezione + elif snapType == QadSnapTypeEnum.INT: + return QadMsg.translate("Snap", "Intersection") + # "INS" punto di inserimento + elif snapType == QadSnapTypeEnum.INS: + return QadMsg.translate("Snap", "Insertion point") + # "PER" punto perpendicolare + elif snapType == QadSnapTypeEnum.PER: + return QadMsg.translate("Snap", "Perpendicular") + # "TAN" tangente + elif snapType == QadSnapTypeEnum.TAN: + return QadMsg.translate("Snap", "Tangent") + # "VIC" punto più vicino + elif snapType == QadSnapTypeEnum.NEA: + return QadMsg.translate("Snap", "Near") + # "APP" intersezione apparente + elif snapType == QadSnapTypeEnum.APP: + return QadMsg.translate("Snap", "Apparent intersection") + # "EST" Estensione + elif snapType == QadSnapTypeEnum.EXT: + return QadMsg.translate("Snap", "Extension") + # "PAR" Parallelo + elif snapType == QadSnapTypeEnum.PAR: + return QadMsg.translate("Snap", "Parallel") + # "PR" distanza progressiva + elif snapType == QadSnapTypeEnum.PR: + return QadMsg.translate("Snap", "Progressive distance") + # "EST_INT" intersezione su estensione + elif snapType == QadSnapTypeEnum.EXT_INT: + return QadMsg.translate("Snap", "Intersection on extension") + + return "" + + +# =============================================================================== +# str2snapParam +# =============================================================================== +def str2snapParams(s): + """ + Ritorna la conversione di una stringa in una lista di parametri per i tipi di snap + """ + params = [] + snapTypeStrList = s.strip().split(",") + for snapTypeStr in snapTypeStrList: + snapTypeStr = snapTypeStr.strip().upper() + # se inizia per "PR" distanza progressiva + if snapTypeStr.find(QadMsg.translate("Snap", "PR")) == 0 or \ + snapTypeStr.find("_PR") == 0: + # la parte successiva PR può essere vuota o numerica + if snapTypeStr.find(QadMsg.translate("Snap", "PR")) == 0: + param = qad_utils.str2float(snapTypeStr[len(QadMsg.translate("Snap", "PR")):]) # fino alla fine della stringa + else: + param = qad_utils.str2float(snapTypeStr[len("_PR"):]) # fino alla fine della stringa + if param is not None: + params.append([QadSnapTypeEnum.PR, param]) + + return params diff --git a/qad_snappointsdisplaymanager.py b/qad_snappointsdisplaymanager.py index 30a1cbf3..158d1122 100644 --- a/qad_snappointsdisplaymanager.py +++ b/qad_snappointsdisplaymanager.py @@ -1,402 +1,425 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - classe per visualizzare i punti di snap - - ------------------- - begin : 2013-05-22 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -from qgis.gui import * - - -from qad_snapper import * -from qad_vertexmarker import * -from qad_rubberband import createRubberBand - - -class QadSnapPointsDisplayManager(): - """ - Classe che gestisce la visualizzazione dei punti di snap - """ - - - #============================================================================ - # __init__ - #============================================================================ - def __init__(self, mapCanvas): - self.__mapCanvas = mapCanvas - self.__vertexMarkers = [] # lista dei marcatori puntuali visualizzati - self.__startPoint = QgsPoint() - self.__iconSize = 13 # size - self.__color = QColor(255, 0, 0) # color of the marker - self.__penWidth = 2 # pen width - self.__lineMarkers = [] # lista dei RubberBand visualizzati - - #============================================================================ - # __del__ - #============================================================================ - def __del__(self): - self.removeItems() - - - def removeItems(self): - self.hide() - - # svuoto la lista dei marker rimuovendoli dal canvas - for vertexMarker in self.__vertexMarkers: - vertexMarker.removeItem() - del self.__vertexMarkers[:] - - # svuoto la linea di estensione rimuovendoli dal canvas - for lineMarker in self.__lineMarkers: - self.__mapCanvas.scene().removeItem(lineMarker) - del self.__lineMarkers[:] - - - def setIconSize(self, iconSize): - self.__iconSize = iconSize - - - def setColor(self, color): - self.__color = color - - - def setPenWidth(self, width): - self.__penWidth = width - - - def setStartPoint(self, point): - """ - Setta il punto di partenza per la modalità di snap PAR - """ - self.__startPoint = point - - - def __getIconType(self, snapType): - if snapType == QadSnapTypeEnum.END or snapType == QadSnapTypeEnum.END_PLINE: - return QadVertexmarkerIconTypeEnum.BOX - elif snapType == QadSnapTypeEnum.MID: - return QadVertexmarkerIconTypeEnum.TRIANGLE - elif snapType == QadSnapTypeEnum.CEN: - return QadVertexmarkerIconTypeEnum.CIRCLE - elif snapType == QadSnapTypeEnum.NOD: - return QadVertexmarkerIconTypeEnum.CIRCLE_X - elif snapType == QadSnapTypeEnum.QUA: - return QadVertexmarkerIconTypeEnum.RHOMBUS - elif snapType == QadSnapTypeEnum.INT: - return QadVertexmarkerIconTypeEnum.X - elif snapType == QadSnapTypeEnum.INS: - return QadVertexmarkerIconTypeEnum.DOUBLE_BOX - elif snapType == QadSnapTypeEnum.PER: - return QadVertexmarkerIconTypeEnum.PERP - elif snapType == QadSnapTypeEnum.TAN: - return QadVertexmarkerIconTypeEnum.TANGENT - elif snapType == QadSnapTypeEnum.NEA: - return QadVertexmarkerIconTypeEnum.DOUBLE_TRIANGLE - elif snapType == QadSnapTypeEnum.APP: - return QadVertexmarkerIconTypeEnum.BOX_X - elif snapType == QadSnapTypeEnum.EXT: - return QadVertexmarkerIconTypeEnum.INFINITY_LINE - elif snapType == QadSnapTypeEnum.PAR: - return QadVertexmarkerIconTypeEnum.PARALLEL - elif snapType == QadSnapTypeEnum.PR: - return QadVertexmarkerIconTypeEnum.PROGRESS - elif snapType == QadSnapTypeEnum.EXT_INT: - return QadVertexmarkerIconTypeEnum.X_INFINITY_LINE - elif snapType == QadSnapTypeEnum.PER_DEF: - return QadVertexmarkerIconTypeEnum.PERP_DEFERRED - elif snapType == QadSnapTypeEnum.TAN_DEF: - return QadVertexmarkerIconTypeEnum.TANGENT_DEFERRED - else: - return QadVertexmarkerIconTypeEnum.NONE - - - def hide(self): - """ - Nasconde i marcatori precedentemente visualizzati - """ - for vertexMarker in self.__vertexMarkers: - vertexMarker.hide() - - for lineMarker in self.__lineMarkers: - lineMarker.hide() - - - def show(self, SnapPoints, \ - extLines = None, extArcs = None, \ - parLines = None, \ - intExtLine = None, intExtArc = None): - """ - Visualizza i punti di snap, riceve un dizionario di liste di punti di snap - suddivisi per tipi di snap (es. {END : [pt1 .. ptn] MID : [pt1 .. ptn]}) - e - lista delle linee da estendere (ogni elemento é una lista di 2 punti = linea) per la modalità di snap EXT - lista degli archi da estendere (ogni elemento é un arco) per la modalità di snap EXT - lista delle linee per modo parallelo (ogni elemento é una lista di 2 punti = linea) per la modalità di snap PAR - linea per intersezione su estensione (lista di 2 punti = linea) per la modalità di snap EXT_INT - arco per intersezione su estensione per la modalità di snap EXT_INT - """ - self.hide() - - # svuoto la lista dei marker - for vertexMarker in self.__vertexMarkers: - vertexMarker.removeItem() - del self.__vertexMarkers[:] - - # svuoto la linea di estensione - for lineMarker in self.__lineMarkers: - self.__mapCanvas.scene().removeItem(lineMarker) - del self.__lineMarkers[:] - - # punti di snap - for snapPoint in SnapPoints.items(): - snapType = snapPoint[0] - for point in snapPoint[1]: - # disegno il marcatore di snap - self.__vertexMarkers.append(self.getVertexMarker(snapType, point)) - - # linee di estensione - if snapType == QadSnapTypeEnum.EXT and (extLines is not None): - for extLine in extLines: - dummyPt = qad_utils.getPerpendicularPointOnInfinityLine(extLine[0], extLine[1], point) - # se dummyPt e point sono così vicini da essere considerati uguali - if qad_utils.ptNear(point, dummyPt): - # prendo il vertice più vicino a point - if qad_utils.getDistance(point, extLine[0]) < qad_utils.getDistance(point, extLine[1]): - dummyPt = extLine[0] - else: - dummyPt = extLine[1] - - # per un baco non ancora capito: se la linea ha solo 2 vertici e - # hanno la stessa x o y (linea orizzontale o verticale) - # la linea non viene disegnata perciò sposto un pochino la x o la y - dummyPt = qad_utils.getAdjustedRubberBandVertex(point, dummyPt) - # disegno la linea di estensione - self.__lineMarkers.append(self.getLineMarker(point, dummyPt)) - - # archi di estensione - if snapType == QadSnapTypeEnum.EXT and (extArcs is not None): - for extArc in extArcs: - angle = qad_utils.getAngleBy2Pts(extArc.center, point) - arc = QadArc(extArc) - - if qad_utils.getDistance(point, arc.getStartPt()) > \ - qad_utils.getDistance(point, arc.getEndPt()): - arc.endAngle = angle - else: - arc.startAngle = angle - - # disegno l'arco di estensione - arcMarker = self.getArcMarker(arc) - if arcMarker is not None: - self.__lineMarkers.append(arcMarker) - - # linee di parallelismo - if snapType == QadSnapTypeEnum.PAR and (self.__startPoint is not None): - boundBox = self.__mapCanvas.extent() - xMin = boundBox.xMinimum() - yMin = boundBox.yMinimum() - xMax = boundBox.xMaximum() - yMax = boundBox.yMaximum() - - upperIntersX = qad_utils.getXOnInfinityLine(self.__startPoint, point, yMax) - if upperIntersX > xMax or upperIntersX < xMin: - upperIntersX = None - - lowerIntersX = qad_utils.getXOnInfinityLine(self.__startPoint, point, yMin) - if lowerIntersX > xMax or lowerIntersX < xMin: - lowerIntersX = None - - leftIntersY = qad_utils.getYOnInfinityLine(self.__startPoint, point, xMin) - if leftIntersY > yMax or leftIntersY < yMin: - leftIntersY = None - - rightIntersY = qad_utils.getYOnInfinityLine(self.__startPoint, point, xMax) - if rightIntersY > yMax or rightIntersY < yMin: - rightIntersY = None - - p1 = None - p2 = None - - if upperIntersX is not None: - p1 = QgsPoint(upperIntersX, yMax) - - if leftIntersY is not None: - if leftIntersY != yMax: - if p1 is None: - p1 = QgsPoint(xMin, leftIntersY) - else: - p2 = QgsPoint(xMin, leftIntersY) - - if lowerIntersX is not None: - if lowerIntersX != xMin: - if p1 is None: - p1 = QgsPoint(lowerIntersX, yMin) - elif p2 is None: - p2 = QgsPoint(lowerIntersX, yMin) - - if rightIntersY is not None: - if rightIntersY != yMin: - if p2 is None: - p2 = QgsPoint(xMax, rightIntersY) - - if (p1 is not None) and (p2 is not None): - # per un baco non ancora capito: se la linea ha solo 2 vertici e - # hanno la stessa x o y (linea orizzontale o verticale) - # la linea non viene disegnata perciò sposto un pochino la x o la y - p2 = qad_utils.getAdjustedRubberBandVertex(p1, p2) - # disegno la linea parallela - self.__lineMarkers.append(self.getLineMarker(p1, p2)) - - # linea per il puntamento polare - if snapType == QadSnapTypeEnum.POLAR and (self.__startPoint is not None): - boundBox = self.__mapCanvas.extent() - xMin = boundBox.xMinimum() - yMin = boundBox.yMinimum() - xMax = boundBox.xMaximum() - yMax = boundBox.yMaximum() - - p1 = self.__startPoint - p2 = point - - x2 = None - if p2.y() > p1.y(): # semiretta che va verso l'alto - x2 = qad_utils.getXOnInfinityLine(p1, p2, yMax) - elif p2.y() < p1.y(): # semiretta che va verso il basso - x2 = qad_utils.getXOnInfinityLine(p1, p2, yMin) - else: # semiretta retta orizzontale - if p2.x() > p1.x(): # semiretta che va verso destra - x2 = xMax - elif p2.x() < p1.x(): # semiretta che va verso sinistra - x2 = xMin - - y2 = None - if p2.x() > p1.x(): # semiretta che va verso destra - y2 = qad_utils.getYOnInfinityLine(p1, p2, xMax) - elif p2.x() < p1.x(): # semiretta che va verso sinistra - y2 = qad_utils.getYOnInfinityLine(p1, p2, xMin) - else: # semiretta retta verticale - if p2.y() > p1.y(): # semiretta che va verso l'alto - y2 = yMax - elif p2.y() < p1.y(): # semiretta che va verso il basso - y2 = yMin - - if x2 is not None: - if x2 > xMax: - x2 = xMax - elif x2 < xMin: - x2 = xMin - - if y2 is not None: - if y2 > yMax: - y2 = yMax - elif y2 < yMin: - y2 = yMin - - if (x2 is not None) and (y2 is not None): - p2 = QgsPoint(x2, y2) - # per un baco non ancora capito: se la linea ha solo 2 vertici e - # hanno la stessa x o y (linea orizzontale o verticale) - # la linea non viene disegnata perciò sposto un pochino la x o la y - p2 = qad_utils.getAdjustedRubberBandVertex(p1, p2) - # disegno la linea - self.__lineMarkers.append(self.getLineMarker(p1, p2)) - - # punti medi delle linee marcate come da estendere - if extLines is not None: - for extLine in extLines: - point = qad_utils.getMiddlePoint(extLine[0], extLine[1]) - # disegno il marcatore di estensione - self.__vertexMarkers.append(self.getVertexMarker(QadSnapTypeEnum.EXT, point)) - - # punti medi degli archi marcati come da estendere - if extArcs is not None: - for extArc in extArcs: - point = extArc.getMiddlePt() - # disegno il marcatore di estensione - self.__vertexMarkers.append(self.getVertexMarker(QadSnapTypeEnum.EXT, point)) - - # punti medi delle linee marcate come parallele - if parLines is not None: - for parLine in parLines: - point = qad_utils.getMiddlePoint(parLine[0], parLine[1]) - # disegno il marcatore di parallelo - self.__vertexMarkers.append(self.getVertexMarker(QadSnapTypeEnum.PAR, point)) - - # punto medio della linea marcata come intersezione estesa - if intExtLine is not None and len(intExtLine) > 1: - point = qad_utils.getMiddlePoint(intExtLine[0], intExtLine[1]) - # disegno il marcatore - self.__vertexMarkers.append(self.getVertexMarker(QadSnapTypeEnum.EXT_INT, point)) - - # punto medio dell'arco marcato come intersezione estesa - if intExtArc is not None and len(intExtArc) == 1: - point = intExtArc[0].getMiddlePt() - # disegno il marcatore - self.__vertexMarkers.append(self.getVertexMarker(QadSnapTypeEnum.EXT_INT, point)) - - - def getVertexMarker(self, snapType, point): - """ - Crea un marcatore puntuale - """ - vertexMarker = QadVertexMarker(self.__mapCanvas) - vertexMarker.setIconSize(self.__iconSize) - vertexMarker.setColor(self.__color) - vertexMarker.setPenWidth(self.__penWidth) - vertexMarker.setIconType(self.__getIconType(snapType)) - vertexMarker.setCenter(point) - return vertexMarker - - - def getLineMarker(self, pt1, pt2): - """ - Crea un marcatore lineare - """ - lineMarker = createRubberBand(self.__mapCanvas, QGis.Line, True) - lineMarker.setColor(self.__color) - lineMarker.setLineStyle(Qt.DotLine) - lineMarker.addPoint(pt1, False) - lineMarker.addPoint(pt2, True) - return lineMarker - - - def getArcMarker(self, arc): - """ - Crea un marcatore lineare x arco - """ - lineMarker = createRubberBand(self.__mapCanvas, QGis.Line, True) - lineMarker.setColor(self.__color) - lineMarker.setLineStyle(Qt.DotLine) - points = arc.asPolyline() - if points is None: - return None - tot = len(points) - i = 0 - while i < (tot - 1): - lineMarker.addPoint(points[i], False) - i = i + 1 - lineMarker.addPoint(points[i], True) - return lineMarker - +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + classe per visualizzare i punti di snap + + ------------------- + begin : 2013-05-22 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from qgis.PyQt.QtCore import * +from qgis.PyQt.QtGui import * +from qgis.core import * +from qgis.gui import * +import time # profiling + +from . import qad_utils +from .qad_variables import QadVariables, QadAUTOSNAPEnum +from .qad_snapper import QadSnapTypeEnum, snapTypeEnum2Descr +from .qad_vertexmarker import QadVertexmarkerIconTypeEnum, QadVertexMarker +from .qad_rubberband import createRubberBand +from .qad_msg import QadMsg +from .qad_geom_relations import QadPerpendicularity + + +class QadSnapPointsDisplayManager(): + """ + Classe che gestisce la visualizzazione dei punti di snap + """ + + + # ============================================================================ + # __init__ + # ============================================================================ + def __init__(self, mapCanvas): + self.__mapCanvas = mapCanvas + self.__vertexMarkers = [] # lista dei marcatori puntuali visualizzati + self.__startPoint = QgsPointXY() + self.__iconSize = QadVariables.get(QadMsg.translate("Environment variables", "AUTOSNAPSIZE")) + self.__color = QColor(255, 0, 0) # color of the marker + self.__penWidth = 2 # pen width + self.__lineMarkers = [] # lista dei RubberBand visualizzati + + # ============================================================================ + # __del__ + # ============================================================================ + def __del__(self): + self.removeItems() + + + def removeItems(self): + self.hide() + + # svuoto la lista dei marker rimuovendoli dal canvas + for vertexMarker in self.__vertexMarkers: + vertexMarker.removeItem() + del self.__vertexMarkers[:] + + # svuoto la linea di estensione rimuovendoli dal canvas + for lineMarker in self.__lineMarkers: + self.__mapCanvas.scene().removeItem(lineMarker) + del self.__lineMarkers[:] + + + def setIconSize(self, iconSize): + self.__iconSize = iconSize + + + def setColor(self, color): + self.__color = color + + + def setPenWidth(self, width): + self.__penWidth = width + + + def setStartPoint(self, point): + """ + Setta il punto di partenza per la modalità di snap PAR + """ + self.__startPoint = point + + + def __getIconType(self, snapType): + if snapType == QadSnapTypeEnum.END or snapType == QadSnapTypeEnum.END_PLINE: + return QadVertexmarkerIconTypeEnum.BOX + elif snapType == QadSnapTypeEnum.MID: + return QadVertexmarkerIconTypeEnum.TRIANGLE + elif snapType == QadSnapTypeEnum.CEN: + return QadVertexmarkerIconTypeEnum.CIRCLE + elif snapType == QadSnapTypeEnum.NOD: + return QadVertexmarkerIconTypeEnum.CIRCLE_X + elif snapType == QadSnapTypeEnum.QUA: + return QadVertexmarkerIconTypeEnum.RHOMBUS + elif snapType == QadSnapTypeEnum.INT: + return QadVertexmarkerIconTypeEnum.X + elif snapType == QadSnapTypeEnum.INS: + return QadVertexmarkerIconTypeEnum.DOUBLE_BOX + elif snapType == QadSnapTypeEnum.PER: + return QadVertexmarkerIconTypeEnum.PERP + elif snapType == QadSnapTypeEnum.TAN: + return QadVertexmarkerIconTypeEnum.TANGENT + elif snapType == QadSnapTypeEnum.NEA: + return QadVertexmarkerIconTypeEnum.DOUBLE_TRIANGLE + elif snapType == QadSnapTypeEnum.APP: + return QadVertexmarkerIconTypeEnum.BOX_X + elif snapType == QadSnapTypeEnum.EXT: + return QadVertexmarkerIconTypeEnum.INFINITY_LINE + elif snapType == QadSnapTypeEnum.PAR: + return QadVertexmarkerIconTypeEnum.PARALLEL + elif snapType == QadSnapTypeEnum.PR: + return QadVertexmarkerIconTypeEnum.PROGRESS + elif snapType == QadSnapTypeEnum.EXT_INT: + return QadVertexmarkerIconTypeEnum.X_INFINITY_LINE + elif snapType == QadSnapTypeEnum.PER_DEF: + return QadVertexmarkerIconTypeEnum.PERP_DEFERRED + elif snapType == QadSnapTypeEnum.TAN_DEF: + return QadVertexmarkerIconTypeEnum.TANGENT_DEFERRED + else: + return QadVertexmarkerIconTypeEnum.NONE + + + def hide(self): + """ + Nasconde i marcatori precedentemente visualizzati + """ + for vertexMarker in self.__vertexMarkers: + vertexMarker.hide() + + for lineMarker in self.__lineMarkers: + lineMarker.hide() + + + def show(self, SnapPoints, \ + extLinearObjs = None, \ + parLines = None, \ + intExtLinearObjs = None, \ + oSnapPointsForPolar = None, \ + oSnapLinesForPolar = None): + """ + Visualizza i punti di snap, riceve un dizionario di liste di punti di snap + suddivisi per tipi di snap (es. {END : [pt1 .. ptn] MID : [pt1 .. ptn]}) + e + lista degli oggetti lineari da estendere (lista di QadLine o QadArc o QadEllipseArc) per la modalità di snap EXT + lista delle linee per modo parallelo (lista di QadLine) per la modalità di snap PAR + linea degli oggetti lineari per intersezione su estensione (lista di QadLine o QadArc o QadEllipseArc) per la modalità di snap EXT_INT + """ + self.hide() + + # svuoto la lista dei marker + for vertexMarker in self.__vertexMarkers: + vertexMarker.removeItem() + del self.__vertexMarkers[:] + + # svuoto la linea di estensione + for lineMarker in self.__lineMarkers: + self.__mapCanvas.scene().removeItem(lineMarker) + del self.__lineMarkers[:] + + autoSnap = QadVariables.get(QadMsg.translate("Environment variables", "AUTOSNAP")) + + self.__mapCanvas.setToolTip("") + + # punti di snap + for snapPoint in SnapPoints.items(): + snapType = snapPoint[0] + i = -1 + for point in snapPoint[1]: + i = i + 1 + + if autoSnap & QadAUTOSNAPEnum.DISPLAY_MARK: # Turns on the AutoSnap mark + self.__vertexMarkers.append(self.getVertexMarker(snapType, point)) + # disegno il marcatore di snap + #self.__vertexMarkers.append(self.getVertexMarker(snapType, point)) + + if autoSnap & QadAUTOSNAPEnum.DISPLAY_TOOLTIPS: # Turns on the AutoSnap tooltips + qad_utils.setMapCanvasToolTip(snapTypeEnum2Descr(snapType)) + + # linee di estensione + if snapType == QadSnapTypeEnum.EXT and (extLinearObjs is not None): + for extLinearObj in extLinearObjs: + geomType = extLinearObj.whatIs() + if geomType == "LINE": + dummyPt = QadPerpendicularity.fromPointToInfinityLine(point, extLinearObj) + # se dummyPt e point sono così vicini da essere considerati uguali + if qad_utils.ptNear(point, dummyPt): + # prendo il vertice più vicino a point + if qad_utils.getDistance(point, extLinearObj.getStartPt()) < qad_utils.getDistance(point, extLinearObj.getEndPt()): + dummyPt = extLinearObj.getStartPt() + else: + dummyPt = extLinearObj.getEndPt() + + # per un baco non ancora capito: se la linea ha solo 2 vertici e + # hanno la stessa x o y (linea orizzontale o verticale) + # la linea non viene disegnata perciò sposto un pochino la x o la y + dummyPt = qad_utils.getAdjustedRubberBandVertex(point, dummyPt) + # disegno la linea di estensione + self.__lineMarkers.append(self.getLineMarker(point, dummyPt)) + else: # ARC o ELLIPSE_ARC + clonedLinearObj = extLinearObj.copy() + + if qad_utils.getDistance(point, clonedLinearObj.getStartPt()) > \ + qad_utils.getDistance(point, clonedLinearObj.getEndPt()): + clonedLinearObj.setEndAngleByPt(point) + else: + clonedLinearObj.setStartAngleByPt(point) + + # disegno l'arco di estensione + arcMarker = self.getLinearObjMarker(clonedLinearObj) + if arcMarker is not None: + self.__lineMarkers.append(arcMarker) + + # linee di parallelismo + if snapType == QadSnapTypeEnum.PAR and (self.__startPoint is not None): + boundBox = self.__mapCanvas.extent() + xMin = boundBox.xMinimum() + yMin = boundBox.yMinimum() + xMax = boundBox.xMaximum() + yMax = boundBox.yMaximum() + + upperIntersX = qad_utils.getXOnInfinityLine(self.__startPoint, point, yMax) + if upperIntersX > xMax or upperIntersX < xMin: + upperIntersX = None + + lowerIntersX = qad_utils.getXOnInfinityLine(self.__startPoint, point, yMin) + if lowerIntersX > xMax or lowerIntersX < xMin: + lowerIntersX = None + + leftIntersY = qad_utils.getYOnInfinityLine(self.__startPoint, point, xMin) + if leftIntersY > yMax or leftIntersY < yMin: + leftIntersY = None + + rightIntersY = qad_utils.getYOnInfinityLine(self.__startPoint, point, xMax) + if rightIntersY > yMax or rightIntersY < yMin: + rightIntersY = None + + p1 = None + p2 = None + + if upperIntersX is not None: + p1 = QgsPointXY(upperIntersX, yMax) + + if leftIntersY is not None: + if leftIntersY != yMax: + if p1 is None: + p1 = QgsPointXY(xMin, leftIntersY) + else: + p2 = QgsPointXY(xMin, leftIntersY) + + if lowerIntersX is not None: + if lowerIntersX != xMin: + if p1 is None: + p1 = QgsPointXY(lowerIntersX, yMin) + elif p2 is None: + p2 = QgsPointXY(lowerIntersX, yMin) + + if rightIntersY is not None: + if rightIntersY != yMin: + if p2 is None: + p2 = QgsPointXY(xMax, rightIntersY) + + if (p1 is not None) and (p2 is not None): + # per un baco non ancora capito: se la linea ha solo 2 vertici e + # hanno la stessa x o y (linea orizzontale o verticale) + # la linea non viene disegnata perciò sposto un pochino la x o la y + p2 = qad_utils.getAdjustedRubberBandVertex(p1, p2) + # disegno la linea parallela + self.__lineMarkers.append(self.getLineMarker(p1, p2)) + + # linee per il puntamento polare + if oSnapLinesForPolar is not None: + for line in oSnapLinesForPolar: + lineMarker = self.getLineMarkerForPolar(line.getStartPt(), line.getEndPt()) + if lineMarker is not None: + # disegno la linea + self.__lineMarkers.append(lineMarker) + + # punti medi degli oggetti lineari marcati come da estendere + if extLinearObjs is not None: + for extLinearObj in extLinearObjs: + point = extLinearObj.getMiddlePt() + # disegno il marcatore di estensionel + self.__vertexMarkers.append(self.getVertexMarker(QadSnapTypeEnum.EXT, point)) + + # punti medi delle linee marcate come parallele + if parLines is not None: + for parLine in parLines: + point = parLine.getMiddlePt() + # disegno il marcatore di parallelo + self.__vertexMarkers.append(self.getVertexMarker(QadSnapTypeEnum.PAR, point)) + + # punto medio della linea marcata come intersezione estesa + if intExtLinearObjs is not None: + for intExtLinearObj in intExtLinearObjs: + point = intExtLinearObj.getMiddlePt() + # disegno il marcatore + self.__vertexMarkers.append(self.getVertexMarker(QadSnapTypeEnum.EXT_INT, point)) + + # punti di osnap usati per l'opzione polare + if oSnapPointsForPolar is not None: + for snapPoint in oSnapPointsForPolar.items(): + snapType = snapPoint[0] + for item in snapPoint[1]: + # disegno il marcatore di snap + self.__vertexMarkers.append(self.getVertexMarker(snapType, item)) + + + def getVertexMarker(self, snapType, point): + """ + Crea un marcatore puntuale + """ + vertexMarker = QadVertexMarker(self.__mapCanvas) + vertexMarker.setIconSize(self.__iconSize) + vertexMarker.setColor(self.__color) + vertexMarker.setPenWidth(self.__penWidth) + vertexMarker.setIconType(self.__getIconType(snapType)) + vertexMarker.setCenter(point) + return vertexMarker + + + def getLineMarker(self, pt1, pt2): + """ + Crea un marcatore lineare + """ + lineMarker = createRubberBand(self.__mapCanvas, QgsWkbTypes.LineGeometry, True) + lineMarker.setColor(self.__color) + lineMarker.setLineStyle(Qt.DashLine) + lineMarker.addPoint(pt1, False) + lineMarker.addPoint(pt2, True) + return lineMarker + + + def getLineMarkerForPolar(self, startPoint, point): + """ + Crea un marcatore lineare per il puntamento polare + """ + boundBox = self.__mapCanvas.extent() + xMin = boundBox.xMinimum() + yMin = boundBox.yMinimum() + xMax = boundBox.xMaximum() + yMax = boundBox.yMaximum() + + p1 = startPoint + p2 = point + + x2 = None + if p2.y() > p1.y(): # semiretta che va verso l'alto + x2 = qad_utils.getXOnInfinityLine(p1, p2, yMax) + elif p2.y() < p1.y(): # semiretta che va verso il basso + x2 = qad_utils.getXOnInfinityLine(p1, p2, yMin) + else: # semiretta retta orizzontale + if p2.x() > p1.x(): # semiretta che va verso destra + x2 = xMax + elif p2.x() < p1.x(): # semiretta che va verso sinistra + x2 = xMin + + y2 = None + if p2.x() > p1.x(): # semiretta che va verso destra + y2 = qad_utils.getYOnInfinityLine(p1, p2, xMax) + elif p2.x() < p1.x(): # semiretta che va verso sinistra + y2 = qad_utils.getYOnInfinityLine(p1, p2, xMin) + else: # semiretta retta verticale + if p2.y() > p1.y(): # semiretta che va verso l'alto + y2 = yMax + elif p2.y() < p1.y(): # semiretta che va verso il basso + y2 = yMin + + if x2 is not None: + if x2 > xMax: + x2 = xMax + elif x2 < xMin: + x2 = xMin + + if y2 is not None: + if y2 > yMax: + y2 = yMax + elif y2 < yMin: + y2 = yMin + + if (x2 is not None) and (y2 is not None): + p2 = QgsPointXY(x2, y2) + # per un baco non ancora capito: se la linea ha solo 2 vertici e + # hanno la stessa x o y (linea orizzontale o verticale) + # la linea non viene disegnata perciò sposto un pochino la x o la y + p2 = qad_utils.getAdjustedRubberBandVertex(p1, p2) + # disegno la linea + return self.getLineMarker(p1, p2) + else: + return None + + + def getLinearObjMarker(self, linearObj): + """ + Crea un marcatore lineare x un oggetto lineare + """ + lineMarker = createRubberBand(self.__mapCanvas, QgsWkbTypes.LineGeometry, True) + lineMarker.setColor(self.__color) + lineMarker.setLineStyle(Qt.DotLine) + points = linearObj.asPolyline() + if points is None: + return None + tot = len(points) + i = 0 + while i < (tot - 1): + lineMarker.addPoint(points[i], False) + i = i + 1 + lineMarker.addPoint(points[i], True) + return lineMarker + \ No newline at end of file diff --git a/qad_stretch_cmd.py b/qad_stretch_cmd.py deleted file mode 100644 index c75535c1..00000000 --- a/qad_stretch_cmd.py +++ /dev/null @@ -1,866 +0,0 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - comando STRETCH per stirare oggetti grafici - - ------------------- - begin : 2013-07-15 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -from qad_stretch_maptool import * -from qad_getpoint import * -from qad_textwindow import * -from qad_mpolygon_cmd import QadMPOLYGONCommandClass -from qad_rectangle_cmd import QadRECTANGLECommandClass -from qad_generic_cmd import QadCommandClass -from qad_msg import QadMsg -import qad_utils -import qad_layer -import qad_stretch_fun -import qad_grip - - -# Classe che gestisce il comando STRETCH -class QadSTRETCHCommandClass(QadCommandClass): - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadSTRETCHCommandClass(self.plugIn) - - def getName(self): - return QadMsg.translate("Command_list", "STRETCH") - - def getEnglishName(self): - return "STRETCH" - - def connectQAction(self, action): - QObject.connect(action, SIGNAL("triggered()"), self.plugIn.runSTRETCHCommand) - - def getIcon(self): - return QIcon(":/plugins/qad/icons/stretch.png") - - def getNote(self): - # impostare le note esplicative del comando - return QadMsg.translate("Command_STRETCH", "Stretches objects.") - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - self.AddOnSelection = True # se = False significa remove - self.points = [] - self.MPOLYGONCommand = None - self.SSGeomList = [] # lista di entità da stirare con geom di selezione - self.basePt = QgsPoint() - - def __del__(self): - QadCommandClass.__del__(self) - if self.MPOLYGONCommand is not None: - del self.MPOLYGONCommand - for SSGeom in self.SSGeomList: - SSGeom[0].deselectOnLayer() - - def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): - if self.step == 2: # quando si é in fase di disegno linea - return self.MPOLYGONCommand.getPointMapTool(drawMode) - else: - if (self.plugIn is not None): - if self.PointMapTool is None: - self.PointMapTool = Qad_stretch_maptool(self.plugIn) - return self.PointMapTool - else: - return None - - - def stretch(self, entity, containerGeom, offSetX, offSetY, tolerance2ApproxCurve): - # entity = entità da stirare - # ptList = lista dei punti da stirare - # offSetX, offSetY = spostamento da fare - # tolerance2ApproxCurve = tolleranza per ricreare le curve - # verifico se l'entità appartiene ad uno stile di quotatura - if entity.whatIs() == "ENTITY": - stretchedGeom = entity.getGeometry() - # controllo inserito perchè con le quote, questa viene cancellata e ricreata quindi alcuni oggetti potrebbero non esistere più - if stretchedGeom is None: # se non c'è lo salto senza errore - return True - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - coordTransform = QgsCoordinateTransform(entity.layer.crs(), self.plugIn.canvas.mapRenderer().destinationCrs()) - stretchedGeom.transform(coordTransform) - # stiro la feature - stretchedGeom = qad_stretch_fun.stretchQgsGeometry(stretchedGeom, containerGeom, \ - offSetX, offSetY, \ - tolerance2ApproxCurve) - - if stretchedGeom is not None: - # trasformo la geometria nel crs del layer - coordTransform = QgsCoordinateTransform(self.plugIn.canvas.mapRenderer().destinationCrs(), entity.layer.crs()) - stretchedGeom.transform(coordTransform) - - f = entity.getFeature() - f.setGeometry(stretchedGeom) - # plugIn, layer, feature, refresh, check_validity - if qad_layer.updateFeatureToLayer(self.plugIn, entity.layer, f, False, False) == False: - return False - - elif entity.whatIs() == "DIMENTITY": - # stiro la quota - if entity.deleteToLayers(self.plugIn) == False: - return False - entity.stretch(self.plugIn, containerGeom, offSetX, offSetY) - if entity.addToLayers(self.plugIn) == False: - return False - - return True - - - #============================================================================ - # stretchFeatures - #============================================================================ - def stretchFeatures(self, newPt): - # mi ricavo un unico QadEntitySet con le entità selezionate - entitySet = QadEntitySet() - for SSGeom in self.SSGeomList: - entitySet.unite(SSGeom[0]) - self.plugIn.beginEditCommand("Feature stretched", entitySet.getLayerList()) - - dimElaboratedList = [] # lista delle quotature già elaborate - - tolerance2ApproxCurve = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) - offSetX = newPt.x() - self.basePt.x() - offSetY = newPt.y() - self.basePt.y() - - entity = QadEntity() - for SSGeom in self.SSGeomList: - # copio entitySet - entitySet = QadEntitySet(SSGeom[0]) - geomSel = SSGeom[1] - - for layerEntitySet in entitySet.layerEntitySetList: - layer = layerEntitySet.layer - - for featureId in layerEntitySet.featureIds: - entity.set(layer, featureId) - - # verifico se l'entità appartiene ad uno stile di quotatura - dimEntity = QadDimStyles.getDimEntity(entity) - if dimEntity is None: - if self.stretch(entity, geomSel, offSetX, offSetY, tolerance2ApproxCurve) == False: - self.plugIn.destroyEditCommand() - return - else: - found = False - for dimElaborated in dimElaboratedList: - if dimElaborated == dimEntity: - found = True - - if found == False: # quota non ancora elaborata - dimElaboratedList.append(dimEntity) - if self.stretch(dimEntity, geomSel, offSetX, offSetY, tolerance2ApproxCurve) == False: - self.plugIn.destroyEditCommand() - return - - self.plugIn.endEditCommand() - - - #============================================================================ - # setEntitySetGeom - #============================================================================ - def setEntitySetGeom(self, entitySet, selGeom): - for SSGeom in self.SSGeomList: - SSGeom[0].deselectOnLayer() - del self.SSGeomList[:] # svuoto la lista - # aggiuge il gruppo di selezione con la geometria usata per la selezione - self.SSGeomList.append([entitySet, selGeom]) - entitySet.selectOnLayer(False) # incremental = False - - #============================================================================ - # addEntitySetGeom - #============================================================================ - def addEntitySetGeom(self, entitySet, selGeom): - # elimino dai gruppi precedenti gli oggetti presenti in entitySet - self.removeEntitySet(entitySet) - # aggiuge il gruppo di selezione con la geometria usata per la selezione - self.SSGeomList.append([entitySet, selGeom]) - entitySet.selectOnLayer(True) # incremental = True - - - #============================================================================ - # removeEntitySet - #============================================================================ - def removeEntitySet(self, entitySet): - # elimino dai gruppi precedenti gli oggetti presenti in entitySet - for SSGeom in self.SSGeomList: - SSGeom[0].subtract(entitySet) - for SSGeom in self.SSGeomList: - SSGeom[0].selectOnLayer(False) # incremental = False - - - #============================================================================ - # SSGeomListIsEmpty - #============================================================================ - def SSGeomListIsEmpty(self): - if len(self.SSGeomList) == 0: - return True - for SSGeom in self.SSGeomList: - if SSGeom[0].isEmpty() == False: - return False - return True - - - #============================================================================ - # waitForObjectSel - #============================================================================ - def waitForObjectSel(self): - self.step = 1 - # imposto il map tool - self.getPointMapTool().setMode(Qad_stretch_maptool_ModeEnum.ASK_FOR_FIRST_PT_RECTANGLE) - - keyWords = QadMsg.translate("Command_STRETCH", "Polygon") + "/" + \ - QadMsg.translate("Command_STRETCH", "Add") + "/" + \ - QadMsg.translate("Command_STRETCH", "Remove") - - if self.AddOnSelection == True: - prompt = QadMsg.translate("Command_STRETCH", "Select vertices") - else: - prompt = QadMsg.translate("Command_STRETCH", "Remove vertices") - prompt = prompt + QadMsg.translate("Command_STRETCH", " to stretch crossed by a selection window or [{0}]: ").format(keyWords) - - englishKeyWords = "Polygon" + "/" + "Add" + "/" + "Remove" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o enter o una parola chiave - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(prompt, QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - None, \ - keyWords, QadInputModeEnum.NONE) - - - #============================================================================ - # waitForBasePt - #============================================================================ - def waitForBasePt(self): - self.step = 4 - # imposto il map tool - self.getPointMapTool().setMode(Qad_stretch_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_BASE_PT) - - keyWords = QadMsg.translate("Command_STRETCH", "Displacement") - prompt = QadMsg.translate("Command_STRETCH", "Specify base point or [{0}] <{0}>: ").format(keyWords) - - englishKeyWords = "Displacement" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o enter o una parola chiave - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - None, \ - keyWords, QadInputModeEnum.NONE) - - - #============================================================================ - # run - #============================================================================ - def run(self, msgMapTool = False, msg = None): - if self.plugIn.canvas.mapRenderer().destinationCrs().geographicFlag(): - self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) - return True # fine comando - - #========================================================================= - # RICHIESTA SELEZIONE OGGETTI - if self.step == 0: # inizio del comando - # si appresta ad attendere la selezione degli oggetti da stirare - self.waitForObjectSel() - return False - - #========================================================================= - # RISPOSTA ALLA SELEZIONE OGGETTI DA STIRARE - elif self.step == 1: - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - value = None - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - else: - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("Command_STRETCH", "Polygon") or value == "Polygon": - # Seleziona tutti gli oggetti che sono interni al poligono - self.MPOLYGONCommand = QadMPOLYGONCommandClass(self.plugIn) - # se questo flag = True il comando serve all'interno di un altro comando per disegnare una linea - # che non verrà salvata su un layer - self.MPOLYGONCommand.virtualCmd = True - self.MPOLYGONCommand.run(msgMapTool, msg) - self.step = 2 - return False - elif value == QadMsg.translate("Command_SSGET", "Add") or value == "Add": - # Passa al metodo Aggiungi: gli oggetti selezionati possono essere aggiunti al gruppo di selezione - self.AddOnSelection = True - elif value == QadMsg.translate("Command_SSGET", "Remove") or value == "Remove": - # Passa al metodo Rimuovi: gli oggetti possono essere rimossi dal gruppo di selezione - self.AddOnSelection = False - elif type(value) == QgsPoint: # se é stato selezionato un punto - del self.points[:] # svuoto la lista - self.points.append(value) - # imposto il map tool - self.getPointMapTool().setMode(Qad_stretch_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT_RECTANGLE) - self.getPointMapTool().setStartPoint(value) - - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_STRETCH", "Specify opposite corner: ")) - self.step = 3 - return False - else: - if self.SSGeomListIsEmpty(): - return True - # si appresta ad attendere il punto base o lo spostamento - self.waitForBasePt() - return False - - # si appresta ad attendere la selezione degli oggetti da stirare - self.waitForObjectSel() - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA PUNTO PER MODALITA' POLIGONO (da step = 1) - elif self.step == 2: # dopo aver atteso un punto si riavvia il comando - if self.MPOLYGONCommand.run(msgMapTool, msg) == True: - if len(self.MPOLYGONCommand.vertices) > 1: - # cerco tutte le geometrie intersecanti il poligono - # e considerando solo layer editabili - selSet = qad_utils.getSelSet("CP", self.getPointMapTool(), self.MPOLYGONCommand.vertices, \ - None, True, True, True, \ - True) - # se la selezione é avvenuta con shift premuto o se si deve rimuovere il gruppo selSet dal gruppo - if self.AddOnSelection == False: - self.removeEntitySet(selSet) - else: - self.setEntitySetGeom(selSet, QgsGeometry.fromPolygon([self.MPOLYGONCommand.vertices])) - - del self.MPOLYGONCommand - self.MPOLYGONCommand = None - - # si appresta ad attendere la selezione degli oggetti da stirare - self.waitForObjectSel() - self.getPointMapTool().refreshSnapType() # aggiorno lo snapType che può essere variato dal maptool di mpolygon - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA PUNTO PER MODALITA' FINESTRA (da step = 1) - elif self.step == 3: # dopo aver atteso un punto si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - self.showMsg(QadMsg.translate("Command_STRETCH", "Window not correct.")) - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_STRETCH", "Specify opposite corner: ")) - return False - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - shiftKey = self.getPointMapTool().shiftKey - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - shiftKey = False - value = msg - - - if type(value) == QgsPoint: - self.points.append(value) - # cerco tutte le geometrie intersecanti il rettangolo - # e considerando solo layer editabili - selSet = qad_utils.getSelSet("C", self.getPointMapTool(), self.points, \ - None, True, True, True, \ - True) - # se si deve rimuovere il gruppo entitySet dal gruppo - if self.AddOnSelection == False: - self.removeEntitySet(selSet) - else: - if shiftKey: # se la selezione é avvenuta con shift premuto - self.addEntitySetGeom(selSet, QgsGeometry.fromRect(QgsRectangle(self.points[0], self.points[1]))) - else: - self.setEntitySetGeom(selSet, QgsGeometry.fromRect(QgsRectangle(self.points[0], self.points[1]))) - # si appresta ad attendere la selezione degli oggetti da stirare - self.waitForObjectSel() - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA PUNTO BASE (da step = 1) - elif self.step == 4: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - pass # opzione di default "spostamento" - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - # imposto il map tool - self.getPointMapTool().SSGeomList = self.SSGeomList - - if value is None or type(value) == unicode: - self.basePt.set(0, 0) - self.getPointMapTool().basePt = self.basePt - self.getPointMapTool().setMode(Qad_stretch_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_MOVE_PT) - # si appresta ad attendere un punto - msg = QadMsg.translate("Command_STRETCH", "Specify the displacement from the origin point 0,0 <{0}, {1}>: ") - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(msg.format(str(self.plugIn.lastOffsetPt.x()), str(self.plugIn.lastOffsetPt.y())), \ - QadInputTypeEnum.POINT2D, \ - self.plugIn.lastOffsetPt, \ - "", QadInputModeEnum.NONE) - self.step = 5 - elif type(value) == QgsPoint: # se é stato inserito il punto base - self.basePt.set(value.x(), value.y()) - - # imposto il map tool - self.getPointMapTool().basePt = self.basePt - self.getPointMapTool().setMode(Qad_stretch_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_MOVE_PT) - - # si appresta ad attendere un punto o enter o una parola chiave - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(QadMsg.translate("Command_STRETCH", "Specify second point or [Array] : "), \ - QadInputTypeEnum.POINT2D, \ - None, \ - "", QadInputModeEnum.NONE) - self.step = 6 - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DEL PUNTO DI SPOSTAMENTO (da step = 2) - elif self.step == 5: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - self.plugIn.setLastOffsetPt(value) - self.stretchFeatures(value) - return True # fine comando - - #========================================================================= - # RISPOSTA ALLA RICHIESTA SECONDO PUNTO PER SPOSTAMENTO (da step = 2) - elif self.step == 6: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if value is None: - newPt = QgsPoint(self.basePt.x() * 2, self.basePt.y() * 2) - self.stretchFeatures(newPt) - elif type(value) == QgsPoint: # se é stato inserito lo spostamento con un punto - self.stretchFeatures(value) - - return True # fine comando - - - -#============================================================================ -# Classe che gestisce il comando STRETCH per i grip -#============================================================================ -class QadGRIPSTRETCHCommandClass(QadCommandClass): - - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadGRIPSTRETCHCommandClass(self.plugIn) - - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - self.selectedEntityGripPoints = [] # lista in cui ogni elemento è una entità + una lista di punti da stirare - self.basePt = QgsPoint() - self.skipToNextGripCommand = False - self.copyEntities = False - self.nOperationsToUndo = 0 - - - def __del__(self): - QadCommandClass.__del__(self) - - - def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): - if (self.plugIn is not None): - if self.PointMapTool is None: - self.PointMapTool = Qad_gripStretch_maptool(self.plugIn) - return self.PointMapTool - else: - return None - - - #============================================================================ - # addToSelectedEntityGripPoints - #============================================================================ - def addToSelectedEntityGripPoints(self, entityGripPoints): - # entità con lista dei grip point - i = 0 - gripPoints = entityGripPoints.gripPoints - gripPointsLen = len(gripPoints) - ptList = [] - while i < gripPointsLen: - gripPoint = gripPoints[i] - # grip point selezionato - if gripPoint.getStatus() == qad_grip.QadGripStatusEnum.SELECTED: - if gripPoint.gripType == qad_grip.QadGripPointTypeEnum.CENTER: - ptList.append(gripPoint.getPoint()) - elif gripPoint.gripType == qad_grip.QadGripPointTypeEnum.LINE_MID_POINT: - # aggiungo il vertice precedente e successivo di quello intermedio - if i > 0: - ptList.append(gripPoints[i - 1].getPoint()) - if i < gripPointsLen - 1: - ptList.append(gripPoints[i + 1].getPoint()) - elif gripPoint.gripType == qad_grip.QadGripPointTypeEnum.QUA_POINT: - ptList.append(gripPoint.getPoint()) - elif gripPoint.gripType == qad_grip.QadGripPointTypeEnum.VERTEX or \ - gripPoint.gripType == qad_grip.QadGripPointTypeEnum.END_VERTEX: - ptList.append(gripPoint.getPoint()) - elif gripPoint.gripType == qad_grip.QadGripPointTypeEnum.ARC_MID_POINT: - ptList.append(gripPoint.getPoint()) - i = i + 1 - - if len(ptList) > 0: - self.selectedEntityGripPoints.append([entityGripPoints.entity, ptList]) - - - #============================================================================ - # setSelectedEntityGripPoints - #============================================================================ - def setSelectedEntityGripPoints(self, entitySetGripPoints): - # lista delle entityGripPoint con dei grip point selezionati - # ritorna una lista in cui ogni elemento è una entità + una lista di punti da stirare - del self.selectedEntityGripPoints[:] # svuoto la lista - - for entityGripPoints in entitySetGripPoints.entityGripPoints: - self.addToSelectedEntityGripPoints(entityGripPoints) - self.getPointMapTool().selectedEntityGripPoints = self.selectedEntityGripPoints - - - #============================================================================ - # getSelectedEntityGripPointNdx - #============================================================================ - def getSelectedEntityGripPointNdx(self, entity): - # lista delle entityGripPoint con dei grip point selezionati - # cerca la posizione di un'entità nella lista in cui ogni elemento è una entità + una lista di punti da stirare - i = 0 - tot = len(self.selectedEntityGripPoints) - while i < tot: - selectedEntityGripPoint = self.selectedEntityGripPoints[i] - if selectedEntityGripPoint[0] == entity: - return i - i = i + 1 - return -1 - - - #============================================================================ - # stretch - #============================================================================ - def stretch(self, entity, ptList, offSetX, offSetY, tolerance2ApproxCurve): - # entity = entità da stirare - # ptList = lista dei punti da stirare - # offSetX, offSetY = spostamento da fare - # tolerance2ApproxCurve = tolleranza per ricreare le curve - # verifico se l'entità appartiene ad uno stile di quotatura - if entity.whatIs() == "ENTITY": - stretchedGeom = entity.getGeometry() - # controllo inserito perchè con le quote, questa viene cancellata e ricreata quindi alcuni oggetti potrebbero non esistere più - if stretchedGeom is None: # se non c'è lo salto senza errore - return True - - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - coordTransform = QgsCoordinateTransform(entity.layer.crs(), self.plugIn.canvas.mapRenderer().destinationCrs()) - stretchedGeom.transform(coordTransform) - # stiro la feature - stretchedGeom = qad_stretch_fun.gripStretchQgsGeometry(stretchedGeom, self.basePt, ptList, \ - offSetX, offSetY, \ - tolerance2ApproxCurve) - - if stretchedGeom is not None: - # trasformo la geometria nel crs del layer - coordTransform = QgsCoordinateTransform(self.plugIn.canvas.mapRenderer().destinationCrs(), entity.layer.crs()) - stretchedGeom.transform(coordTransform) - - f = entity.getFeature() - f.setGeometry(stretchedGeom) - if self.copyEntities == False: - # plugIn, layer, feature, refresh, check_validity - if qad_layer.updateFeatureToLayer(self.plugIn, entity.layer, f, False, False) == False: - return False - else: - # plugIn, layer, features, coordTransform, refresh, check_validity - if qad_layer.addFeatureToLayer(self.plugIn, entity.layer, f, None, False, False) == False: - return False - - elif entity.whatIs() == "DIMENTITY": - # stiro la quota - if self.copyEntities == False: - if entity.deleteToLayers(self.plugIn) == False: - return False - entity.stretch(self.plugIn, ptList, offSetX, offSetY) - if entity.addToLayers(self.plugIn) == False: - return False - - return True - - - #============================================================================ - # stretchFeatures - #============================================================================ - def stretchFeatures(self, newPt): - # mi ricavo un unico QadEntitySet con le entità selezionate - entitySet = QadEntitySet() - for selectedEntity in self.selectedEntityGripPoints: - entitySet.addEntity(selectedEntity[0]) - self.plugIn.beginEditCommand("Feature stretched", entitySet.getLayerList()) - - dimElaboratedList = [] # lista delle quotature già elaborate - - for selectedEntity in self.selectedEntityGripPoints: - entity = selectedEntity[0] - ptList = selectedEntity[1] - layer = entity.layer - - tolerance2ApproxCurve = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) - offSetX = newPt.x() - self.basePt.x() - offSetY = newPt.y() - self.basePt.y() - - # verifico se l'entità appartiene ad uno stile di quotatura - dimEntity = QadDimStyles.getDimEntity(entity) - if dimEntity is None: - if self.stretch(entity, ptList, offSetX, offSetY, tolerance2ApproxCurve) == False: - self.plugIn.destroyEditCommand() - return - else: - found = False - for dimElaborated in dimElaboratedList: - if dimElaborated == dimEntity: - found = True - - if found == False: # quota non ancora elaborata - dimEntitySet = dimEntity.getEntitySet() - # creo un'unica lista contenente i grip points di tutti i componenti della quota - dimPtlist = [] - for layerEntitySet in dimEntitySet.layerEntitySetList: - for featureId in layerEntitySet.featureIds: - componentDim = QadEntity() - componentDim.set(layerEntitySet.layer, featureId) - i = self.getSelectedEntityGripPointNdx(componentDim) - if i >= 0: - dimPtlist.extend(self.selectedEntityGripPoints[i][1]) - - dimElaboratedList.append(dimEntity) - if self.stretch(dimEntity, dimPtlist, offSetX, offSetY, tolerance2ApproxCurve) == False: - self.plugIn.destroyEditCommand() - return - - self.plugIn.endEditCommand() - self.nOperationsToUndo = self.nOperationsToUndo + 1 - - - #============================================================================ - # waitForStretchPoint - #============================================================================ - def waitForStretchPoint(self): - self.step = 1 - self.plugIn.setLastPoint(self.basePt) - # imposto il map tool - self.getPointMapTool().basePt = self.basePt - self.getPointMapTool().setMode(Qad_stretch_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_MOVE_PT) - - keyWords = QadMsg.translate("Command_GRIP", "Base point") + "/" + \ - QadMsg.translate("Command_GRIP", "Copy") + "/" + \ - QadMsg.translate("Command_GRIP", "Undo") + "/" + \ - QadMsg.translate("Command_GRIP", "eXit") - - prompt = QadMsg.translate("Command_GRIPSTRETCH", "Specify stretch point or [{0}]: ").format(keyWords) - - englishKeyWords = "Base point" + "/" + "Copy" + "/" + "Undo" + "/" + "eXit" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o enter o una parola chiave - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(prompt, QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - None, \ - keyWords, QadInputModeEnum.NONE) - - - #============================================================================ - # waitForBasePt - #============================================================================ - def waitForBasePt(self): - self.step = 2 - # imposto il map tool - self.getPointMapTool().setMode(Qad_stretch_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_BASE_PT) - - # si appresta ad attendere un punto - self.waitForPoint(QadMsg.translate("Command_GRIPSTRETCH", "Specify base point: ")) - - - #============================================================================ - # run - #============================================================================ - def run(self, msgMapTool = False, msg = None): - if self.plugIn.canvas.mapRenderer().destinationCrs().geographicFlag(): - self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) - return True # fine comando - - #========================================================================= - # RICHIESTA SELEZIONE OGGETTI - if self.step == 0: # inizio del comando - if len(self.selectedEntityGripPoints) == 0: # non ci sono oggetti da stirare - return True - self.showMsg(QadMsg.translate("Command_GRIPSTRETCH", "\n** STRETCH **\n")) - # si appresta ad attendere un punto di stiramento - self.waitForStretchPoint() - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DI UN PUNTO DI STIRAMENTO - elif self.step == 1: - ctrlKey = False - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - value = None - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - else: - value = self.getPointMapTool().point - - ctrlKey = self.getPointMapTool().ctrlKey - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("Command_GRIP", "Base point") or value == "Base point": - # si appresta ad attendere il punto base - self.waitForBasePt() - elif value == QadMsg.translate("Command_GRIP", "Copy") or value == "Copy": - # Copia entità lasciando inalterate le originali - self.copyEntities = True - # si appresta ad attendere un punto di stiramento - self.waitForStretchPoint() - elif value == QadMsg.translate("Command_GRIP", "Undo") or value == "Undo": - if self.nOperationsToUndo > 0: - self.nOperationsToUndo = self.nOperationsToUndo - 1 - self.plugIn.undoEditCommand() - else: - self.showMsg(QadMsg.translate("QAD", "\nThe command has been canceled.")) - # si appresta ad attendere un punto di stiramento - self.waitForStretchPoint() - elif value == QadMsg.translate("Command_GRIP", "eXit") or value == "eXit": - return True # fine comando - elif type(value) == QgsPoint: # se é stato selezionato un punto - if ctrlKey: - self.copyEntities = True - - self.stretchFeatures(value) - - if self.copyEntities == False: - return True - # si appresta ad attendere un punto di stiramento - self.waitForStretchPoint() - - else: - if self.copyEntities == False: - self.skipToNextGripCommand = True - return True # fine comando - - return False - - - #========================================================================= - # RISPOSTA ALLA RICHIESTA PUNTO BASE (da step = 1) - elif self.step == 2: # dopo aver atteso un punto - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - pass # opzione di default "spostamento" - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == QgsPoint: # se é stato inserito il punto base - self.basePt.set(value.x(), value.y()) - # imposto il map tool - self.getPointMapTool().basePt = self.basePt - - # si appresta ad attendere un punto di stiramento - self.waitForStretchPoint() - - return False \ No newline at end of file diff --git a/qad_stretch_fun.py b/qad_stretch_fun.py index 76b7c23d..fd80f6e0 100644 --- a/qad_stretch_fun.py +++ b/qad_stretch_fun.py @@ -1,540 +1,501 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - funzioni per stirare oggetti grafici - - ------------------- - begin : 2013-11-11 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -import qad_utils -import qad_arc -import qad_circle -from qad_snapper import * - - -#=============================================================================== -# isPtContainedForStretch -#=============================================================================== -def isPtContainedForStretch(point, containerGeom, tolerance=None): - """ - Funzione di ausilio per le funzioni di stretch (stretchPoint e stretchQgsLineStringGeometry). - Se containerGeom è un oggetto QgsGeometry allora ritorna True se il punto è contenuto a livello spaziale - dalla geometria containerGeom. - Se containerGeom è una lista di punti allora ritorna True se il punto è fra quelli della lista. - """ - if tolerance is None: - tolerance = qad_utils.TOLERANCE - if type(containerGeom) == QgsGeometry: # geometria - return containerGeom.contains(point) - elif type(containerGeom) == list: # lista di punti - for containerPt in containerGeom: - if qad_utils.ptNear(containerPt, point, tolerance): # se i punti sono sufficientemente vicini - return True - return False - - -#=============================================================================== -# stretchPoint -#=============================================================================== -def stretchPoint(point, containerGeom, offSetX, offSetY): - """ - Stira il punto se è contenuto in containerGeom - point = punto da stirare - containerGeom = può essere una QgsGeometry rappresentante un poligono contenente i punti di geom da stirare - oppure una lista dei punti da stirare - offSetX = spostamento X - offSetY = spostamento Y - """ - if isPtContainedForStretch(point, containerGeom): # se il punto è contenuto in containerGeom - return qad_utils.movePoint(point, offSetX, offSetY) - - return None - - -#=============================================================================== -# stretchQgsGeometry -#=============================================================================== -def stretchQgsGeometry(geom, containerGeom, offSetX, offSetY, tolerance2ApproxCurve): - """ - Stira una geometria - geom = geometria da tirare - containerGeom = può essere una QgsGeometry rappresentante un poligono contenente i punti di geom da stirare - oppure una lista dei punti di geom da stirare - offSetX = spostamento X - offSetY = spostamento Y - tolerance2ApproxCurve = tolleranza per rigenerare le curve - """ - wkbType = geom.wkbType() - if wkbType == QGis.WKBPoint or wkbType == QGis.WKBPoint25D: - pt = stretchPoint(geom.asPoint(), containerGeom, offSetX, offSetY) - if pt is not None: - return QgsGeometry.fromPoint(pt) - - if wkbType == QGis.WKBMultiPoint: - stretchedGeom = QgsGeometry(geom) - points = stretchedGeom.asMultiPoint() # vettore di punti - atSubGeom = 0 - for pt in points: - subGeom = QgsGeometry.fromPoint(pt) - stretchedSubGeom = stretchQgsGeometry(subGeom, containerGeom, offSetX, offSetY, tolerance2ApproxCurve) - stretchedGeom = qad_utils.setSubGeom(stretchedGeom, stretchedSubGeom, [atSubGeom]) - atSubGeom = atSubGeom + 1 - return stretchedGeom - - if wkbType == QGis.WKBLineString: - return stretchQgsLineStringGeometry(geom, containerGeom, offSetX, offSetY, tolerance2ApproxCurve) - - if wkbType == QGis.WKBMultiLineString: - stretchedGeom = QgsGeometry(geom) - lines = stretchedGeom.asMultiPolyline() # lista di linee - atSubGeom = 0 - for line in lines: - subGeom = QgsGeometry.fromPolyline(line) - stretchedSubGeom = stretchQgsGeometry(subGeom, containerGeom, offSetX, offSetY, tolerance2ApproxCurve) - stretchedGeom = qad_utils.setSubGeom(stretchedGeom, stretchedSubGeom, [atSubGeom]) - atSubGeom = atSubGeom + 1 - return stretchedGeom - - if wkbType == QGis.WKBPolygon: - stretchedGeom = QgsGeometry(geom) - lines = stretchedGeom.asPolygon() # lista di linee - atSubGeom = 0 - for line in lines: - subGeom = QgsGeometry.fromPolyline(line) - stretchedSubGeom = stretchQgsGeometry(subGeom, containerGeom, offSetX, offSetY, tolerance2ApproxCurve) - stretchedGeom = qad_utils.setSubGeom(stretchedGeom, stretchedSubGeom, [atSubGeom]) - atSubGeom = atSubGeom + 1 - return stretchedGeom - - if wkbType == QGis.WKBMultiPolygon: - stretchedGeom = QgsGeometry(geom) - polygons = geom.asMultiPolygon() # vettore di poligoni - atSubGeom = 0 - for polygon in polygons: - subGeom = QgsGeometry.fromPolygon(polygon) - stretchedSubGeom = stretchQgsGeometry(subGeom, containerGeom, offSetX, offSetY, tolerance2ApproxCurve) - stretchedGeom = qad_utils.setSubGeom(stretchedGeom, stretchedSubGeom, [atSubGeom]) - atSubGeom = atSubGeom + 1 - return stretchedGeom - - return None - - -#=============================================================================== -# stretchQgsLineStringGeometry -#=============================================================================== -def stretchQgsLineStringGeometry(geom, containerGeom, offSetX, offSetY, tolerance2ApproxCurve): - """ - Stira i punti di una linestring che sono contenuti in containerGeom - point = punto da tirare - containerGeom = può essere una QgsGeometry rappresentante un poligono contenente i punti di geom da stirare - oppure una lista dei punti da stirare - offSetX = spostamento X - offSetY = spostamento Y - """ - obj = qad_utils.whatGeomIs(0, geom) - if (type(obj) != list and type(obj) != tuple): - objType = obj.whatIs() - if objType == "CIRCLE": # se é cerchio - if isPtContainedForStretch(obj.center, containerGeom): # se il punto è contenuto in containerGeom - obj.center.setX(obj.center.x() + offSetX) - obj.center.setY(obj.center.y() + offSetY) - return QgsGeometry.fromPolyline(obj.asPolyline(tolerance2ApproxCurve)) - - stretchedGeom = QgsGeometry(geom) - snapper = QadSnapper() - points = snapper.getEndPoints(stretchedGeom) - del snapper - - linearObjectListToStretch = qad_utils.QadLinearObjectList() - linearObjectListToStretch.fromPolyline(geom.asPolyline()) - - for point in points: - if isPtContainedForStretch(point, containerGeom): # se il punto è contenuto in containerGeom - atPart = linearObjectListToStretch.containsPt(point) - while atPart >= 0: - linearObject = linearObjectListToStretch.getLinearObjectAt(atPart) - pt = linearObject.getStartPt() - if qad_utils.ptNear(pt, point): # cambio punto iniziale - pt.setX(pt.x() + offSetX) - pt.setY(pt.y() + offSetY) - if linearObject.isSegment(): - linearObject.setStartPt(pt) - else: - oldArc = linearObject.getArc() - middlePt = oldArc.getMiddlePt() - distFromMiddleChord = qad_utils.getDistance(middlePt, qad_utils.getPerpendicularPointOnInfinityLine(oldArc.getStartPt(), oldArc.getEndPt(), middlePt)) - - newArc = QadArc() - if linearObject.isInverseArc(): - middlePt = qad_utils.getMiddlePoint(pt, oldArc.getStartPt()) - middlePt = qad_utils.getPolarPointByPtAngle(middlePt, \ - qad_utils.getAngleBy2Pts(pt, oldArc.getStartPt()) + math.pi / 2, \ - distFromMiddleChord) - if newArc.fromStartSecondEndPts(oldArc.getStartPt(), middlePt, pt) == False: - return None - else: - middlePt = qad_utils.getMiddlePoint(pt, oldArc.getEndPt()) - middlePt = qad_utils.getPolarPointByPtAngle(middlePt, \ - qad_utils.getAngleBy2Pts(pt, oldArc.getEndPt()) - math.pi / 2, \ - distFromMiddleChord) - if newArc.fromStartSecondEndPts(pt, middlePt, oldArc.getEndPt()) == False: - return None - linearObject.setArc(newArc, linearObject.isInverseArc()) - else: - pt = linearObject.getEndPt() - if qad_utils.ptNear(pt, point): # cambio punto finale - pt.setX(pt.x() + offSetX) - pt.setY(pt.y() + offSetY) - if linearObject.isSegment(): - linearObject.setEndPt(pt) - else: - oldArc = linearObject.getArc() - middlePt = oldArc.getMiddlePt() - distFromMiddleChord = qad_utils.getDistance(middlePt, qad_utils.getPerpendicularPointOnInfinityLine(oldArc.getStartPt(), oldArc.getEndPt(), middlePt)) - - newArc = QadArc() - if linearObject.isInverseArc(): - middlePt = qad_utils.getMiddlePoint(pt, oldArc.getEndPt()) - middlePt = qad_utils.getPolarPointByPtAngle(middlePt, \ - qad_utils.getAngleBy2Pts(pt, oldArc.getEndPt()) - math.pi / 2, \ - distFromMiddleChord) - if newArc.fromStartSecondEndPts(pt, middlePt, oldArc.getEndPt()) == False: - return None - else: - middlePt = qad_utils.getMiddlePoint(pt, oldArc.getStartPt()) - middlePt = qad_utils.getPolarPointByPtAngle(middlePt, \ - qad_utils.getAngleBy2Pts(pt, oldArc.getStartPt()) + math.pi / 2, \ - distFromMiddleChord) - if newArc.fromStartSecondEndPts(oldArc.getStartPt(), middlePt, pt) == False: - return None - linearObject.setArc(newArc, linearObject.isInverseArc()) - - atPart = linearObjectListToStretch.containsPt(point, atPart + 1) - - pts = linearObjectListToStretch.asPolyline(tolerance2ApproxCurve) - stretchedGeom = QgsGeometry.fromPolyline(pts) - - return stretchedGeom - - -#################################################################### -# Funzioni di stretch per grip point -#################################################################### - - -#=============================================================================== -# gripStretchQgsGeometry -#=============================================================================== -def gripStretchQgsGeometry(geom, basePt, ptListToStretch, offSetX, offSetY, tolerance2ApproxCurve): - """ - Stira una geometria in coordinate piane mediante grip point - geom = geometria da stirare - ptListToStretch = lista dei punti di geom da stirare - offSetX = spostamento X - offSetY = spostamento Y - tolerance2ApproxCurve = tolleranza per rigenerare le curve - """ - wkbType = geom.wkbType() - if wkbType == QGis.WKBPoint or wkbType == QGis.WKBPoint25D: - pt = stretchPoint(geom.asPoint(), ptListToStretch, offSetX, offSetY) - if pt is not None: - return QgsGeometry.fromPoint(pt) - - if wkbType == QGis.WKBMultiPoint: - stretchedGeom = QgsGeometry(geom) - points = stretchedGeom.asMultiPoint() # vettore di punti - atSubGeom = 0 - for pt in points: - subGeom = QgsGeometry.fromPoint(pt) - stretchedSubGeom = gripStretchQgsGeometry(subGeom, basePt, ptListToStretch, offSetX, offSetY, tolerance2ApproxCurve) - stretchedGeom = qad_utils.setSubGeom(stretchedGeom, stretchedSubGeom, [atSubGeom]) - atSubGeom = atSubGeom + 1 - return stretchedGeom - - if wkbType == QGis.WKBLineString: - return gripStretchQgsLineStringGeometry(geom, basePt, ptListToStretch, offSetX, offSetY, tolerance2ApproxCurve) - - if wkbType == QGis.WKBMultiLineString: - stretchedGeom = QgsGeometry(geom) - lines = stretchedGeom.asMultiPolyline() # lista di linee - atSubGeom = 0 - for line in lines: - subGeom = QgsGeometry.fromPolyline(line) - stretchedSubGeom = gripStretchQgsGeometry(subGeom, basePt, ptListToStretch, offSetX, offSetY, tolerance2ApproxCurve) - stretchedGeom = qad_utils.setSubGeom(stretchedGeom, stretchedSubGeom, [atSubGeom]) - atSubGeom = atSubGeom + 1 - return stretchedGeom - - if wkbType == QGis.WKBPolygon: - stretchedGeom = QgsGeometry(geom) - lines = stretchedGeom.asPolygon() # lista di linee - atSubGeom = 0 - for line in lines: - subGeom = QgsGeometry.fromPolyline(line) - stretchedSubGeom = gripStretchQgsGeometry(subGeom, basePt, ptListToStretch, offSetX, offSetY, tolerance2ApproxCurve) - stretchedGeom = qad_utils.setSubGeom(stretchedGeom, stretchedSubGeom, [atSubGeom]) - atSubGeom = atSubGeom + 1 - return stretchedGeom - - if wkbType == QGis.WKBMultiPolygon: - stretchedGeom = QgsGeometry(geom) - polygons = geom.asMultiPolygon() # vettore di poligoni - atSubGeom = 0 - for polygon in polygons: - subGeom = QgsGeometry.fromPolygon(polygon) - stretchedSubGeom = gripStretchQgsGeometry(subGeom, basePt, ptListToStretch, offSetX, offSetY, tolerance2ApproxCurve) - stretchedGeom = qad_utils.setSubGeom(stretchedGeom, stretchedSubGeom, [atSubGeom]) - atSubGeom = atSubGeom + 1 - return stretchedGeom - - return None - - -#=============================================================================== -# gripStretchQadGeometry -#=============================================================================== -def gripStretchQadGeometry(geom, basePt, ptListToStretch, offSetX, offSetY, tolerance2ApproxCurve): - """ - Stira una entità qad in coordinate piane mediante grip point - geom = entità qad da stirare - ptListToStretch = lista dei punti di geom da stirare - offSetX = spostamento X - offSetY = spostamento Y - tolerance2ApproxCurve = tolleranza per rigenerare le curve - """ - if type(geom) == list: # entità composta da più geometrie - res = [] - for subGeom in geom: - res.append(gripStretchQadGeometry(subGeom, basePt, ptListToStretch, offSetX, offSetY, tolerance2ApproxCurve)) - return res - else: - if type(geom) == QgsPoint: - return stretchPoint(geom, ptListToStretch, offSetX, offSetY) - elif geom.whatIs() == "ARC": - return gripStretchArc(geom, ptListToStretch, offSetX, offSetY, tolerance2ApproxCurve) - elif geom.whatIs() == "CIRCLE": - return gripStretchCircle(geom, basePt, ptListToStretch, offSetX, offSetY, tolerance2ApproxCurve) - elif geom.whatIs() == "LINEAROBJS": - return gripStretchQgsLinearObjectList(geom, ptListToStretch, offSetX, offSetY, tolerance2ApproxCurve) - - return None - - -#=============================================================================== -# gripStretchCircle -#=============================================================================== -def gripStretchCircle(circle, basePt, ptListToStretch, offSetX, offSetY, tolerance2ApproxCurve): - """ - Stira i punti di grip di un cerchio che sono contenuti in ptListToStretch - circle = cerchio da stirare - basePt = punto base - ptListToStretch = lista dei punti da stirare - offSetX = spostamento X - offSetY = spostamento Y - """ - newCenter = QgsPoint(circle.center) - newRadius = circle.radius - - for ptToStretch in ptListToStretch: - if qad_utils.ptNear(ptToStretch, circle.center): # se i punti sono sufficientemente vicini - newCenter.set(circle.center.x() + offSetX, circle.center.y() + offSetY) - elif circle.isPtOnCircle(ptToStretch): - newPt = QgsPoint(basePt.x() + offSetX, basePt.y() + offSetY) - newRadius = qad_utils.getDistance(circle.center, newPt) - - newCircle = qad_circle.QadCircle() - if newCircle.set(newCenter, newRadius) == False: - return None - - return newCircle - - -#=============================================================================== -# gripStretchArc -#=============================================================================== -def gripStretchArc(arc, ptListToStretch, offSetX, offSetY, tolerance2ApproxCurve, inverseArc = None): - """ - Stira i punti di grip di un arco che sono contenuti in ptListToStretch - arc = arco da stirare - ptListToStretch = lista dei punti da stirare - offSetX = spostamento X - offSetY = spostamento Y - """ - startPt = arc.getStartPt() - endPt = arc.getEndPt() - middlePt = arc.getMiddlePt() - newStartPt = QgsPoint(startPt) - newEndPt = QgsPoint(endPt) - newMiddlePt = QgsPoint(middlePt) - newCenter = None - startPtChanged = endPtChanged = middlePtPtChanged = False - for ptToStretch in ptListToStretch: - if qad_utils.ptNear(ptToStretch, arc.center): # se i punti sono sufficientemente vicini - newCenter = QgsPoint(arc.center.x() + offSetX, arc.center.y() + offSetY) - else: - if qad_utils.ptNear(startPt, ptToStretch): - newStartPt.set(startPt.x() + offSetX, startPt.y() + offSetY) - startPtChanged = True - elif qad_utils.ptNear(endPt, ptToStretch): - newEndPt.set(endPt.x() + offSetX, endPt.y() + offSetY) - endPtChanged = True - elif qad_utils.ptNear(middlePt, ptToStretch): - newMiddlePt.set(middlePt.x() + offSetX, middlePt.y() + offSetY) - middlePtPtChanged = True - - newArc = qad_arc.QadArc() - if newArc.fromStartSecondEndPts(newStartPt, newMiddlePt, newEndPt) == False: - return None - - # se il centro era nei punti di grip - if newCenter is not None: - # se i tre punti dell'arco erano nei punti di grip oppure - # allora non cambio il centro - if (startPtChanged and endPtChanged and middlePtPtChanged): - pass - else: - newArc.center.set(newCenter.x(), newCenter.y()) - - if inverseArc is not None: # se l'arco faceva parte di una linestring - # verifico il verso del nuovo arco - if qad_utils.ptNear(newStartPt, newArc.getStartPt()): - # stesso verso del vecchio arco - return newArc, inverseArc - else: - return newArc, not inverseArc - - return newArc - - -#=============================================================================== -# gripStretchQgsLineStringGeometry -#=============================================================================== -def gripStretchQgsLineStringGeometry(geom, basePt, ptListToStretch, offSetX, offSetY, tolerance2ApproxCurve): - """ - Stira i punti di grip di una linestring che sono contenuti in ptListToStretch - geom = geometria da stirare - basePt = punto base - ptListToStretch = lista dei punti da stirare - offSetX = spostamento X - offSetY = spostamento Y - """ - obj = qad_utils.whatGeomIs(0, geom) - if (type(obj) != list and type(obj) != tuple): - objType = obj.whatIs() - if objType == "CIRCLE": # se é cerchio - newCircle = gripStretchCircle(obj, basePt, ptListToStretch, offSetX, offSetY, tolerance2ApproxCurve) - if newCircle is not None: - return QgsGeometry.fromPolyline(newCircle.asPolyline(tolerance2ApproxCurve)) - elif objType == "ARC": # se é arco - newArc = gripStretchArc(obj, ptListToStretch, offSetX, offSetY, tolerance2ApproxCurve) - if newArc is not None: - return QgsGeometry.fromPolyline(newArc.asPolyline(tolerance2ApproxCurve)) - return None - - linearObjectListToStretch = qad_utils.QadLinearObjectList() - linearObjectListToStretch.fromPolyline(geom.asPolyline()) - - atPart = 0 - while atPart < linearObjectListToStretch.qty(): - linearObject = linearObjectListToStretch.getLinearObjectAt(atPart) - if linearObject.isSegment(): - pt = linearObject.getStartPt() - if isPtContainedForStretch(pt, ptListToStretch): # se il punto è contenuto in ptListToStretch - # cambio punto iniziale - pt.setX(pt.x() + offSetX) - pt.setY(pt.y() + offSetY) - linearObject.setStartPt(pt) - - pt = linearObject.getEndPt() - if isPtContainedForStretch(pt, ptListToStretch): # se il punto è contenuto in ptListToStretch - # cambio punto finale - pt.setX(pt.x() + offSetX) - pt.setY(pt.y() + offSetY) - linearObject.setEndPt(pt) - else: # se è arco - newArc, newInverseFlag = gripStretchArc(linearObject.getArc(), ptListToStretch, offSetX, offSetY, tolerance2ApproxCurve, linearObject.isInverseArc()) - if newArc is None: - return None - linearObject.setArc(newArc, newInverseFlag) - - atPart = atPart + 1 - - pt = linearObjectListToStretch.getCentroid(tolerance2ApproxCurve) # verifico se polilinea ha un centroide - if pt is not None: - if isPtContainedForStretch(pt, ptListToStretch): # se il punto è contenuto in ptListToStretch - linearObjectListToStretch.move(offSetX, offSetY) - - pts = linearObjectListToStretch.asPolyline(tolerance2ApproxCurve) - stretchedGeom = QgsGeometry.fromPolyline(pts) - - return stretchedGeom - - -#=============================================================================== -# gripStretchQgsLinearObjectList -#=============================================================================== -def gripStretchQgsLinearObjectList(linearObjectList, ptListToStretch, offSetX, offSetY, tolerance2ApproxCurve): - """ - Stira i punti di grip di una linestring che sono contenuti in ptListToStretch - linearObjectListToStretch = geometria da stirare - ptListToStretch = lista dei punti da stirare - offSetX = spostamento X - offSetY = spostamento Y - """ - linearObjectListToStretch = qad_utils.QadLinearObjectList(linearObjectList) - - atPart = 0 - while atPart < linearObjectListToStretch.qty(): - linearObject = linearObjectListToStretch.getLinearObjectAt(atPart) - if linearObject.isSegment(): - pt = linearObject.getStartPt() - if isPtContainedForStretch(pt, ptListToStretch): # se il punto è contenuto in ptListToStretch - # cambio punto iniziale - pt.setX(pt.x() + offSetX) - pt.setY(pt.y() + offSetY) - linearObject.setStartPt(pt) - - pt = linearObject.getEndPt() - if isPtContainedForStretch(pt, ptListToStretch): # se il punto è contenuto in ptListToStretch - # cambio punto finale - pt.setX(pt.x() + offSetX) - pt.setY(pt.y() + offSetY) - linearObject.setEndPt(pt) - else: # se è arco - newArc, newInverseFlag = gripStretchArc(linearObject.getArc(), ptListToStretch, offSetX, offSetY, tolerance2ApproxCurve, linearObject.isInverseArc()) - if newArc is None: - return None - linearObject.setArc(newArc, newInverseFlag) - - atPart = atPart + 1 - - pt = linearObjectListToStretch.getCentroid(tolerance2ApproxCurve) # verifico se polilinea ha un centroide - if pt is not None: - if isPtContainedForStretch(pt, ptListToStretch): # se il punto è contenuto in ptListToStretch - linearObjectListToStretch.move(offSetX, offSetY) - - return linearObjectListToStretch \ No newline at end of file +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + funzioni per stirare oggetti grafici + + ------------------- + begin : 2013-11-11 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.PyQt.QtCore import * +from qgis.PyQt.QtGui import * +from qgis.core import * + + +from . import qad_utils +from .qad_variables import QadVariables +from .qad_msg import QadMsg +from .qad_snapper import * +from .qad_point import QadPoint +from .qad_ellipse import QadEllipse +from .qad_ellipse_arc import QadEllipseArc + + +# =============================================================================== +# isPtContainedForStretch +# =============================================================================== +def isPtContainedForStretch(point, containerGeom, tolerance=None): + """ + Funzione di ausilio per le funzioni di stretch (stretchPoint e stretchQgsLineStringGeometry). + Se containerGeom è un oggetto QgsGeometry allora ritorna True se il punto è contenuto a livello spaziale + dalla geometria containerGeom. + Se containerGeom è una lista di punti allora ritorna True se il punto è fra quelli della lista. + """ + if type(containerGeom) == QgsGeometry: # geometria + return containerGeom.contains(point) + elif type(containerGeom) == list: # lista di punti + if tolerance is None: + myTolerance = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2COINCIDENT")) + else: + myTolerance = tolerance + + for containerPt in containerGeom: + if qad_utils.ptNear(containerPt, point, myTolerance): # se i punti sono sufficientemente vicini + return True + return False + + +# =============================================================================== +# stretchQadGeometry +# =============================================================================== +def stretchQadGeometry(geom, ptListToStretch, offsetX, offsetY): + """ + Stira una entità qad in coordinate piane mediante grip point + geom = entità qad da stirare + ptListToStretch = lista dei punti di geom da stirare + offsetX = spostamento X + offsetY = spostamento Y + """ + if type(geom) == list: # entità composta da più geometrie + res = [] + iSub = 0 + for subGeom in geom: + res.append(stretchQadGeometry(subGeom, ptListToStretch, offsetX, offsetY)) + iSub = iSub + 1 + return res + else: + gType = geom.whatIs() + if gType == "POINT": + return stretchPoint(geom, ptListToStretch, offsetX, offsetY) + if gType == "MULTI_POINT": + return stretchMultiPoint(geom, ptListToStretch, offsetX, offsetY) + elif gType == "LINE": + return stretchLine(geom, ptListToStretch, offsetX, offsetY) + elif gType == "ARC": + return stretchArc(geom, ptListToStretch, offsetX, offsetY) + elif gType == "CIRCLE": + return stretchCircle(geom, ptListToStretch, offsetX, offsetY) + elif gType == "ELLIPSE": + return stretchEllipse(geom, ptListToStretch, offsetX, offsetY) + elif gType == "ELLIPSE_ARC": + return stretchEllipseArc(geom, ptListToStretch, offsetX, offsetY) + elif gType == "POLYLINE": + return stretchPolyline(geom, ptListToStretch, offsetX, offsetY) + elif gType == "MULTI_LINEAR_OBJ": + return stretchMultiLinearObj(geom, ptListToStretch, offsetX, offsetY) + elif gType == "POLYGON": + return stretchPolygon(geom, ptListToStretch, offsetX, offsetY) + elif gType == "MULTI_POLYGON": + return stretchMultiPolygon(geom, ptListToStretch, offsetX, offsetY) + + return None + + +# =============================================================================== +# stretchPoint +# =============================================================================== +def stretchPoint(point, containerGeom, offsetX, offsetY): + """ + Restituisce un nuovo punto stirato se è contenuto in containerGeom + point = punto da stirare + containerGeom = può essere una QgsGeometry rappresentante un poligono contenente i punti di geom da stirare + oppure una lista dei punti da stirare + offsetX = spostamento X + offsetY = spostamento Y + """ + stretchedGeom = QadPoint(point) + if isPtContainedForStretch(point, containerGeom): # se il punto è contenuto in containerGeom + stretchedGeom.move(offsetX, offsetY) + + return stretchedGeom + + +# =============================================================================== +# stretchMultiPoint +# =============================================================================== +def stretchMultiPoint(multiPoint, containerGeom, offsetX, offsetY): + """ + Restituisce un nuovo multi punto stirato se è contenuto in containerGeom + point = punto da stirare + containerGeom = può essere una QgsGeometry rappresentante un poligono contenente i punti di geom da stirare + oppure una lista dei punti da stirare + offsetX = spostamento X + offsetY = spostamento Y + """ + multiPointToStretch = multiPoint.copy() + i = 0 + while i < multiPointToStretch.qty(): + point = multiPointToStretch.getPointAt(i) + newPoint = stretchPoint(point, containerGeom, offsetX, offsetY) + point.set(newPoint) + i = i + 1 + + return multiPointToStretch + + +# =============================================================================== +# stretchCircle +# =============================================================================== +def stretchCircle(circle, containerGeom, offsetX, offsetY): + """ + Stira un cerchio usando i punti che sono contenuti in containerGeom + circle = cerchio da stirare + containerGeom = può essere una QgsGeometry rappresentante un poligono contenente i punti di geom da stirare + oppure una lista dei punti da stirare + offsetX = spostamento X + offsetY = spostamento Y + """ + + newCircle = circle.copy() + newCenter = QgsPointXY(circle.center) + newRadius = circle.radius + + if isPtContainedForStretch(circle.center, containerGeom): # se il centro è contenuto in containerGeom + newCenter.set(circle.center.x() + offsetX, circle.center.y() + offsetY) # sposto il cerchio + else: + if type(containerGeom) == list: # lista di punti + for containerPt in containerGeom: + # whereIsPt ritorna -1 se il punto è interno, 0 se è sulla circonferenza, 1 se è esterno + if circle.whereIsPt(containerPt) == 0: + newPt = QgsPointXY(containerPt.x() + offsetX, containerPt.y() + offsetY) + newRadius = qad_utils.getDistance(circle.center, newPt) + break + else: # geometria + # ritorna i punti quadranti + quadrants = circle.getQuadrantPoints() + for quadrant in quadrants: + if isPtContainedForStretch(quadrant, containerGeom): # se il quandrante è contenuto in containerGeom + newPt = QgsPointXY(quadrant.x() + offsetX, quadrant.y() + offsetY) + newRadius = qad_utils.getDistance(circle.center, newPt) + break + + return newCircle.set(newCenter, newRadius) + + +# =============================================================================== +# stretchArc +# =============================================================================== +def stretchArc(arc, containerGeom, offsetX, offsetY): + """ + Stira i punti di grip di un arco che sono contenuti in containerGeom + arc = arco da stirare + containerGeom = può essere una QgsGeometry rappresentante un poligono contenente i punti di geom da stirare + oppure una lista dei punti da stirare + offsetX = spostamento X + offsetY = spostamento Y + """ + newArc = arc.copy() + + if isPtContainedForStretch(arc.center, containerGeom): # se il centro è contenuto in containerGeom + newArc.center.set(arc.center.x() + offsetX, arc.center.y() + offsetY) + else: + startPt = arc.getStartPt() + endPt = arc.getEndPt() + middlePt = arc.getMiddlePt() + newStartPt = QgsPointXY(startPt) + newEndPt = QgsPointXY(endPt) + newMiddlePt = QgsPointXY(middlePt) + + if isPtContainedForStretch(startPt, containerGeom): # se il punto iniziale è contenuto in containerGeom + newStartPt.set(startPt.x() + offsetX, startPt.y() + offsetY) + + if isPtContainedForStretch(endPt, containerGeom): # se il punto finale è contenuto in containerGeom + newEndPt.set(endPt.x() + offsetX, endPt.y() + offsetY) + + if isPtContainedForStretch(middlePt, containerGeom): # se il punto medio è contenuto in containerGeom + newMiddlePt.set(middlePt.x() + offsetX, middlePt.y() + offsetY) + + if newArc.reversed: + if newArc.fromStartSecondEndPts(newEndPt, newMiddlePt, newStartPt) == False: + return None + else: + if newArc.fromStartSecondEndPts(newStartPt, newMiddlePt, newEndPt) == False: + return None + + return newArc + + +# =============================================================================== +# stretchEllipse +# =============================================================================== +def stretchEllipse(ellipse, containerGeom, offsetX, offsetY): + """ + Stira i punti di grip di una ellisse che sono contenuti in containerGeom + ellipse = ellisse da stirare + containerGeom = può essere una QgsGeometry rappresentante un poligono contenente i punti di geom da stirare + oppure una lista dei punti da stirare + offsetX = spostamento X + offsetY = spostamento Y + """ + newCenter = QgsPointXY(ellipse.center) + newMajorAxisFinalPt = QgsPointXY(ellipse.majorAxisFinalPt) + a = qad_utils.getDistance(ellipse.center, ellipse.majorAxisFinalPt) # semiasse maggiore + b = a * ellipse.axisRatio # semiasse minore + angle = ellipse.getRotation() + newAxisRatio = ellipse.axisRatio + + if isPtContainedForStretch(ellipse.center, containerGeom): # se il centro è contenuto in containerGeom + newCenter.set(ellipse.center.x() + offsetX, ellipse.center.y() + offsetY) + newMajorAxisFinalPt.set(ellipse.majorAxisFinalPt.x() + offsetX, ellipse.majorAxisFinalPt.y() + offsetY) + else: + # ritorna i punti quadranti: partendo da majorAxisFinalPt in ordine antiorario + quadrants = ellipse.getQuadrantPoints() + majorAxisFinalPt1 = quadrants[0] + majorAxisFinalPt2 = quadrants[2] + minorAxisFinalPt1 = quadrants[1] + minorAxisFinalPt2 = quadrants[3] + + if isPtContainedForStretch(majorAxisFinalPt1, containerGeom): # se il quandrante è contenuto in containerGeom + pt = QgsPointXY(majorAxisFinalPt1.x() + offsetX, majorAxisFinalPt1.y() + offsetY) + newA = qad_utils.getDistance(ellipse.center, pt) # nuovo semiasse maggiore + newMajorAxisFinalPt = qad_utils.getPolarPointByPtAngle(ellipse.center, angle, newA) + newAxisRatio = b / newA + elif isPtContainedForStretch(majorAxisFinalPt2, containerGeom): # se il quandrante è contenuto in containerGeom + pt = QgsPointXY(majorAxisFinalPt2.x() + offsetX, majorAxisFinalPt2.y() + offsetY) + newA = qad_utils.getDistance(ellipse.center, pt) # nuovo semiasse maggiore + newMajorAxisFinalPt = qad_utils.getPolarPointByPtAngle(ellipse.center, angle, newA) + newAxisRatio = b / newA + elif isPtContainedForStretch(minorAxisFinalPt1, containerGeom): # se il quandrante è contenuto in containerGeom + pt = QgsPointXY(minorAxisFinalPt1.x() + offsetX, minorAxisFinalPt1.y() + offsetY) + newB = qad_utils.getDistance(ellipse.center, pt) # nuovo semiasse minore + newAxisRatio = newB / a + elif isPtContainedForStretch(minorAxisFinalPt2, containerGeom): # se il quandrante è contenuto in containerGeom + pt = QgsPointXY(minorAxisFinalPt2.x() + offsetX, minorAxisFinalPt2.y() + offsetY) + newB = qad_utils.getDistance(ellipse.center, pt) # nuovo semiasse minore + newAxisRatio = newB / a + + newEllipse = QadEllipse() + return newEllipse.set(newCenter, newMajorAxisFinalPt, newAxisRatio) + + +# =============================================================================== +# stretchEllipseArc +# =============================================================================== +def stretchEllipseArc(ellipseArc, containerGeom, offsetX, offsetY): + """ + Stira i punti di grip di un arco di ellisse che sono contenuti in containerGeom + ellipseArc = arco di ellisse da stirare + containerGeom = può essere una QgsGeometry rappresentante un poligono contenente i punti di geom da stirare + oppure una lista dei punti da stirare + offsetX = spostamento X + offsetY = spostamento Y + """ + newCenter = QgsPointXY(ellipseArc.center) + newMajorAxisFinalPt = QgsPointXY(ellipseArc.majorAxisFinalPt) + a = qad_utils.getDistance(ellipseArc.center, ellipseArc.majorAxisFinalPt) # semiasse maggiore + b = a * ellipseArc.axisRatio # semiasse minore + angle = ellipseArc.getRotation() + startPt = ellipseArc.getStartPt() + endPt = ellipseArc.getEndPt() + newAxisRatio = ellipseArc.axisRatio + newStartAngle = ellipseArc.startAngle + newEndAngle = ellipseArc.endAngle + + if isPtContainedForStretch(ellipseArc.center, containerGeom): # se il centro è contenuto in containerGeom + newCenter.set(ellipseArc.center.x() + offsetX, ellipseArc.center.y() + offsetY) + newMajorAxisFinalPt.set(ellipseArc.majorAxisFinalPt.x() + offsetX, ellipseArc.majorAxisFinalPt.y() + offsetY) + else: + # ritorna i punti quadranti: partendo da majorAxisFinalPt in ordine antiorario + quadrants = ellipseArc.getQuadrantPoints() + majorAxisFinalPt1 = quadrants[0] + majorAxisFinalPt2 = quadrants[2] + minorAxisFinalPt1 = quadrants[1] + minorAxisFinalPt2 = quadrants[3] + + if majorAxisFinalPt1 is not None and isPtContainedForStretch(majorAxisFinalPt1, containerGeom): # se il quandrante è contenuto in containerGeom + pt = QgsPointXY(majorAxisFinalPt1.x() + offsetX, majorAxisFinalPt1.y() + offsetY) + newA = qad_utils.getDistance(ellipseArc.center, pt) # nuovo semiasse maggiore + newMajorAxisFinalPt = qad_utils.getPolarPointByPtAngle(ellipseArc.center, angle, newA) + newAxisRatio = b / newA + elif majorAxisFinalPt2 is not None and isPtContainedForStretch(majorAxisFinalPt2, containerGeom): # se il quandrante è contenuto in containerGeom + pt = QgsPointXY(majorAxisFinalPt2.x() + offsetX, majorAxisFinalPt2.y() + offsetY) + newA = qad_utils.getDistance(ellipseArc.center, pt) # nuovo semiasse maggiore + newMajorAxisFinalPt = qad_utils.getPolarPointByPtAngle(ellipseArc.center, angle, newA) + newAxisRatio = b / newA + elif minorAxisFinalPt1 is not None and isPtContainedForStretch(minorAxisFinalPt1, containerGeom): # se il quandrante è contenuto in containerGeom + pt = QgsPointXY(minorAxisFinalPt1.x() + offsetX, minorAxisFinalPt1.y() + offsetY) + newB = qad_utils.getDistance(ellipseArc.center, pt) # nuovo semiasse minore + newAxisRatio = newB / a + elif minorAxisFinalPt2 is not None and isPtContainedForStretch(minorAxisFinalPt2, containerGeom): # se il quandrante è contenuto in containerGeom + pt = QgsPointXY(minorAxisFinalPt2.x() + offsetX, minorAxisFinalPt2.y() + offsetY) + newB = qad_utils.getDistance(ellipseArc.center, pt) # nuovo semiasse minore + newAxisRatio = newB / a + elif isPtContainedForStretch(startPt, containerGeom): # se il punto iniziale è contenuto in containerGeom + newStartPt = QgsPointXY() + newStartPt.set(startPt.x() + offsetX, startPt.y() + offsetY) + newStartAngle = qad_utils.getAngleBy2Pts(ellipseArc.center, newStartPt) - angle + elif isPtContainedForStretch(endPt, containerGeom): # se il punto finale è contenuto in containerGeom + newEndPt = QgsPointXY() + newEndPt.set(endPt.x() + offsetX, endPt.y() + offsetY) + newEndAngle = qad_utils.getAngleBy2Pts(ellipseArc.center, newEndPt) - angle + + newEllipseArc = QadEllipseArc() + return newEllipseArc.set(newCenter, newMajorAxisFinalPt, newAxisRatio, newStartAngle, newEndAngle) + + +# =============================================================================== +# stretchLine +# =============================================================================== +def stretchLine(line, containerGeom, offsetX, offsetY): + """ + Stira i punti di grip di una qadLine che sono contenuti in containerGeom + line = geometria da stirare + containerGeom = può essere una QgsGeometry rappresentante un poligono contenente i punti di geom da stirare + oppure una lista dei punti da stirare + offsetX = spostamento X + offsetY = spostamento Y + """ + lineToStretch = line.copy() + + pt = lineToStretch.getStartPt() + if isPtContainedForStretch(pt, containerGeom): # se il punto è contenuto in containerGeom + # cambio punto iniziale + pt.setX(pt.x() + offsetX) + pt.setY(pt.y() + offsetY) + lineToStretch.setStartPt(pt) + + pt = lineToStretch.getEndPt() + if isPtContainedForStretch(pt, containerGeom): # se il punto è contenuto in containerGeom + # cambio punto finale + pt.setX(pt.x() + offsetX) + pt.setY(pt.y() + offsetY) + lineToStretch.setEndPt(pt) + + return lineToStretch + + +# =============================================================================== +# stretchPolyline +# =============================================================================== +def stretchPolyline(polyline, containerGeom, offsetX, offsetY): + """ + Crea una nuova polyline stirando i punti di grip che sono contenuti in containerGeom + polyline = polilinea da stirare + containerGeom = può essere una QgsGeometry rappresentante un poligono contenente i punti di geom da stirare + oppure una lista dei punti da stirare + offsetX = spostamento X + offsetY = spostamento Y + """ + polylineToStretch = polyline.copy() + + pt = polylineToStretch.getCentroid() # verifico se polilinea ha un centroide + if pt is not None and isPtContainedForStretch(pt, containerGeom): # se il punto è contenuto in containerGeom + polylineToStretch.move(offsetX, offsetY) + else: + i = 0 + while i < polylineToStretch.qty(): + linearObject = polylineToStretch.getLinearObjectAt(i) + newLinearObject = stretchQadGeometry(linearObject, containerGeom, offsetX, offsetY) + if (newLinearObject is not None): + polylineToStretch.insert(i, newLinearObject) + polylineToStretch.remove(i + 1) + + i = i + 1 + + # verifico e correggo i versi delle parti della polilinea + polylineToStretch.reverseCorrection() + + return polylineToStretch + + +# =============================================================================== +# stretchMultiLinearObj +# =============================================================================== +def stretchMultiLinearObj(multiLinear, containerGeom, offsetX, offsetY): + """ + Crea un nuovo multi lineare stirando i punti di grip che sono contenuti in containerGeom + polygon = multi lineare da stirare + containerGeom = può essere una QgsGeometry rappresentante un poligono contenente i punti di geom da stirare + oppure una lista dei punti da stirare + offsetX = spostamento X + offsetY = spostamento Y + """ + multiLinearToStretch = multiLinear.copy() + + i = 0 + while i < multiLinearToStretch.qty(): + linearObject = multiLinearToStretch.getLinearObjectAt(i) + newLinearObject = stretchQadGeometry(linearObject, containerGeom, offsetX, offsetY) + multiLinearToStretch.insert(i, newLinearObject) + multiLinearToStretch.remove(i + 1) + + i = i + 1 + + return multiLinearToStretch + + +# =============================================================================== +# stretchPolygon +# =============================================================================== +def stretchPolygon(polygon, containerGeom, offsetX, offsetY): + """ + Crea un nuovo poligono stirando i punti di grip che sono contenuti in containerGeom + polygon = poligono da stirare + containerGeom = può essere una QgsGeometry rappresentante un poligono contenente i punti di geom da stirare + oppure una lista dei punti da stirare + offsetX = spostamento X + offsetY = spostamento Y + """ + polygonToStretch = polygon.copy() + + pt = polygonToStretch.getCentroid() # verifico se polilinea ha un centroide + if pt is not None and isPtContainedForStretch(pt, containerGeom): # se il punto è contenuto in containerGeom + polygonToStretch.move(offsetX, offsetY) + else: + i = 0 + while i < polygonToStretch.qty(): + closedObject = polygonToStretch.getClosedObjectAt(i) + newClosedObject = stretchQadGeometry(closedObject, containerGeom, offsetX, offsetY) + polygonToStretch.insert(i, newClosedObject) + polygonToStretch.remove(i + 1) + + i = i + 1 + + return polygonToStretch + + +# =============================================================================== +# stretchMultiPolygon +# =============================================================================== +def stretchMultiPolygon(multiPolygon, containerGeom, offsetX, offsetY): + """ + Crea un nuovo multi poligono stirando i punti di grip che sono contenuti in containerGeom + multiPolygon = multi poligono da stirare + containerGeom = può essere una QgsGeometry rappresentante un poligono contenente i punti di geom da stirare + oppure una lista dei punti da stirare + offsetX = spostamento X + offsetY = spostamento Y + """ + multiPolygonToStretch = multiPolygon.copy() + + pt = multiPolygonToStretch.getCentroid() # verifico se polilinea ha un centroide + if pt is not None and isPtContainedForStretch(pt, containerGeom): # se il punto è contenuto in containerGeom + multiPolygonToStretch.move(offsetX, offsetY) + else: + i = 0 + while i < multiPolygonToStretch.qty(): + polygon = multiPolygonToStretch.getPolygonAt(i) + newPolygon = stretchQadGeometry(polygon, containerGeom, offsetX, offsetY) + multiPolygonToStretch.insert(i, newPolygon) + multiPolygonToStretch.remove(i + 1) + + i = i + 1 + + return multiPolygonToStretch \ No newline at end of file diff --git a/qad_stretch_maptool.py b/qad_stretch_maptool.py deleted file mode 100644 index 69d4a9d6..00000000 --- a/qad_stretch_maptool.py +++ /dev/null @@ -1,337 +0,0 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - classe per gestire il map tool in ambito del comando stretch - - ------------------- - begin : 2014-01-08 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -from qgis.gui import * -import math - - -import qad_utils -from qad_snapper import * -from qad_snappointsdisplaymanager import * -from qad_variables import * -from qad_getpoint import * -from qad_dim import * -import qad_stretch_fun -from qad_highlight import QadHighlight - - -#=============================================================================== -# Qad_stretch_maptool_ModeEnum class. -#=============================================================================== -class Qad_stretch_maptool_ModeEnum(): - # si richiede la selezione del primo punto del rettangolo per selezionare gli oggetti - ASK_FOR_FIRST_PT_RECTANGLE = 1 - # noto niente il primo punto del rettangolo si richiede il secondo punto - FIRST_PT_KNOWN_ASK_FOR_SECOND_PT_RECTANGLE = 2 - # noto niente si richiede il punto base - NONE_KNOWN_ASK_FOR_BASE_PT = 3 - # noto il punto base si richiede il secondo punto per lo spostamento - BASE_PT_KNOWN_ASK_FOR_MOVE_PT = 4 - - -#=============================================================================== -# Qad_stretch_maptool class -#=============================================================================== -class Qad_stretch_maptool(QadGetPoint): - - def __init__(self, plugIn): - QadGetPoint.__init__(self, plugIn) - - self.basePt = None - self.SSGeomList = [] # lista di entità da stirare con geom di selezione - self.__highlight = QadHighlight(self.canvas) - - def hidePointMapToolMarkers(self): - QadGetPoint.hidePointMapToolMarkers(self) - self.__highlight.hide() - - def showPointMapToolMarkers(self): - QadGetPoint.showPointMapToolMarkers(self) - self.__highlight.show() - - def clear(self): - QadGetPoint.clear(self) - self.__highlight.reset() - self.mode = None - - - #============================================================================ - # stretch - #============================================================================ - def stretch(self, entity, containerGeom, offSetX, offSetY, tolerance2ApproxCurve): - # entity = entità da stirare - # ptList = lista dei punti da stirare - # offSetX, offSetY = spostamento da fare - # tolerance2ApproxCurve = tolleranza per ricreare le curve - # verifico se l'entità appartiene ad uno stile di quotatura - if entity.whatIs() == "ENTITY": - stretchedGeom = entity.getGeometry() - # controllo inserito perchè con le quote, questa viene cancellata e ricreata quindi alcuni oggetti potrebbero non esistere più - if stretchedGeom is None: # se non c'è lo salto senza errore - return True - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - coordTransform = QgsCoordinateTransform(entity.layer.crs(), self.canvas.mapRenderer().destinationCrs()) - stretchedGeom.transform(coordTransform) - # stiro la feature - stretchedGeom = qad_stretch_fun.stretchQgsGeometry(stretchedGeom, containerGeom, \ - offSetX, offSetY, \ - tolerance2ApproxCurve) - - if stretchedGeom is not None: - # trasformo la geometria nel crs del layer - coordTransform = QgsCoordinateTransform(self.canvas.mapRenderer().destinationCrs(), entity.layer.crs()) - stretchedGeom.transform(coordTransform) - self.__highlight.addGeometry(stretchedGeom, entity.layer) - - elif entity.whatIs() == "DIMENTITY": - # stiro la quota - entity.stretch(self.plugIn, containerGeom, offSetX, offSetY) - self.__highlight.addGeometry(entity.textualFeature.geometry(), entity.getTextualLayer()) - self.__highlight.addGeometries(entity.getLinearGeometryCollection(), entity.getLinearLayer()) - self.__highlight.addGeometries(entity.getSymbolGeometryCollection(), entity.getSymbolLayer()) - - return True - - - #============================================================================ - # addStretchedGeometries - #============================================================================ - def addStretchedGeometries(self, newPt): - self.__highlight.reset() - - dimElaboratedList = [] # lista delle quotature già elaborate - - tolerance2ApproxCurve = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) - offSetX = newPt.x() - self.basePt.x() - offSetY = newPt.y() - self.basePt.y() - - entity = QadEntity() - for SSGeom in self.SSGeomList: - # copio entitySet - entitySet = QadEntitySet(SSGeom[0]) - geomSel = SSGeom[1] - - for layerEntitySet in entitySet.layerEntitySetList: - layer = layerEntitySet.layer - - for featureId in layerEntitySet.featureIds: - entity.set(layer, featureId) - - # verifico se l'entità appartiene ad uno stile di quotatura - dimEntity = QadDimStyles.getDimEntity(entity) - if dimEntity is None: - self.stretch(entity, geomSel, offSetX, offSetY, tolerance2ApproxCurve) - else: - found = False - for dimElaborated in dimElaboratedList: - if dimElaborated == dimEntity: - found = True - - if found == False: # quota non ancora elaborata - dimElaboratedList.append(dimEntity) - self.stretch(dimEntity, geomSel, offSetX, offSetY, tolerance2ApproxCurve) - - - def canvasMoveEvent(self, event): - QadGetPoint.canvasMoveEvent(self, event) - - # noto il punto base si richiede il secondo punto per l'angolo di rotazione - if self.mode == Qad_stretch_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_MOVE_PT: - self.addStretchedGeometries(self.tmpPoint) - - - def activate(self): - QadGetPoint.activate(self) - self.__highlight.show() - - def deactivate(self): - try: # necessario perché se si chiude QGIS parte questo evento nonostante non ci sia più l'oggetto maptool ! - QadGetPoint.deactivate(self) - self.__highlight.hide() - except: - pass - - def setMode(self, mode): - self.mode = mode - - # si richiede la selezione del primo punto del rettangolo per selezionare gli oggetti - if self.mode == Qad_stretch_maptool_ModeEnum.ASK_FOR_FIRST_PT_RECTANGLE: - self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) - self.setDrawMode(QadGetPointDrawModeEnum.NONE) - # noto niente il primo punto del rettangolo si richiede il secondo punto - elif self.mode == Qad_stretch_maptool_ModeEnum.FIRST_PT_KNOWN_ASK_FOR_SECOND_PT_RECTANGLE: - self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_RECTANGLE) - # noto niente si richiede il punto base - elif self.mode == Qad_stretch_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_BASE_PT: - self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) - self.setDrawMode(QadGetPointDrawModeEnum.NONE) - # noto il punto base si richiede il secondo punto - elif self.mode == Qad_stretch_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_MOVE_PT: - self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.setStartPoint(self.basePt) - - -#=============================================================================== -# Qad_gripStretch_maptool class -#=============================================================================== -class Qad_gripStretch_maptool(QadGetPoint): - - def __init__(self, plugIn): - QadGetPoint.__init__(self, plugIn) - - self.basePt = None - self.selectedEntityGripPoints = [] # lista in cui ogni elemento è una entità + una lista di punti da stirare - self.__highlight = QadHighlight(self.canvas) - - def hidePointMapToolMarkers(self): - QadGetPoint.hidePointMapToolMarkers(self) - self.__highlight.hide() - - def showPointMapToolMarkers(self): - QadGetPoint.showPointMapToolMarkers(self) - self.__highlight.show() - - def clear(self): - QadGetPoint.clear(self) - self.__highlight.reset() - self.mode = None - - - #============================================================================ - # getSelectedEntityGripPointNdx - #============================================================================ - def getSelectedEntityGripPointNdx(self, entity): - # lista delle entityGripPoint con dei grip point selezionati - # cerca la posizione di un'entità nella lista in cui ogni elemento è una entità + una lista di punti da stirare - i = 0 - tot = len(self.selectedEntityGripPoints) - while i < tot: - selectedEntityGripPoint = self.selectedEntityGripPoints[i] - if selectedEntityGripPoint[0] == entity: - return i - i = i + 1 - return -1 - - - #============================================================================ - # stretch - #============================================================================ - def stretch(self, entity, ptList, offSetX, offSetY, tolerance2ApproxCurve): - # entity = entità da stirare - # ptList = lista dei punti da stirare - # offSetX, offSetY = spostamento da fare - # tolerance2ApproxCurve = tolleranza per ricreare le curve - # entitySet = gruppo di selezione delle entità da stirare - # verifico se l'entità appartiene ad uno stile di quotatura - if entity.whatIs() == "ENTITY": - stretchedGeom = entity.gripGeomStretch(self.basePt, ptList, offSetX, offSetY, tolerance2ApproxCurve) - - if stretchedGeom is not None: - self.__highlight.addGeometry(stretchedGeom, entity.layer) - elif entity.whatIs() == "DIMENTITY": - # stiro la quota - entity.stretch(self.plugIn, ptList, offSetX, offSetY) - self.__highlight.addGeometry(entity.textualFeature.geometry(), entity.getTextualLayer()) - self.__highlight.addGeometries(entity.getLinearGeometryCollection(), entity.getLinearLayer()) - self.__highlight.addGeometries(entity.getSymbolGeometryCollection(), entity.getSymbolLayer()) - - - #============================================================================ - # addStretchedGeometries - #============================================================================ - def addStretchedGeometries(self, newPt): - self.__highlight.reset() - - dimElaboratedList = [] # lista delle quotature già elaborate - - for selectedEntity in self.selectedEntityGripPoints: - entity = selectedEntity[0] - ptList = selectedEntity[1] - layer = entity.layer - - tolerance2ApproxCurve = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) - offSetX = newPt.x() - self.basePt.x() - offSetY = newPt.y() - self.basePt.y() - - # verifico se l'entità appartiene ad uno stile di quotatura - if entity.isDimensionComponent() == False: - self.stretch(entity, ptList, offSetX, offSetY, tolerance2ApproxCurve) - else: - dimEntity = QadDimEntity() - if dimEntity.initByDimId(entity.dimStyle, entity.dimId): - found = False - for dimElaborated in dimElaboratedList: - if dimElaborated == dimEntity: - found = True - if found == False: # quota non ancora elaborata - dimEntitySet = dimEntity.getEntitySet() - # creo un'unica lista contenente i grip points di tutti i componenti della quota - dimPtlist = [] - for layerEntitySet in dimEntitySet.layerEntitySetList: - for featureId in layerEntitySet.featureIds: - componentDim = QadEntity() - componentDim.set(layerEntitySet.layer, featureId) - i = self.getSelectedEntityGripPointNdx(componentDim) - if i >= 0: - dimPtlist.extend(self.selectedEntityGripPoints[i][1]) - - dimElaboratedList.append(dimEntity) - self.stretch(dimEntity, dimPtlist, offSetX, offSetY, tolerance2ApproxCurve) - - - def canvasMoveEvent(self, event): - QadGetPoint.canvasMoveEvent(self, event) - - # noto il punto base si richiede il secondo punto per l'angolo di rotazione - if self.mode == Qad_stretch_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_MOVE_PT: - self.addStretchedGeometries(self.tmpPoint) - - - def activate(self): - QadGetPoint.activate(self) - self.__highlight.show() - - def deactivate(self): - try: # necessario perché se si chiude QGIS parte questo evento nonostante non ci sia più l'oggetto maptool ! - QadGetPoint.deactivate(self) - self.__highlight.hide() - except: - pass - - def setMode(self, mode): - self.mode = mode - - # noto niente si richiede il punto base - if self.mode == Qad_stretch_maptool_ModeEnum.NONE_KNOWN_ASK_FOR_BASE_PT: - self.setSelectionMode(QadGetPointSelectionModeEnum.POINT_SELECTION) - self.setDrawMode(QadGetPointDrawModeEnum.NONE) - self.__highlight.reset() - # noto il punto base si richiede il secondo punto - elif self.mode == Qad_stretch_maptool_ModeEnum.BASE_PT_KNOWN_ASK_FOR_MOVE_PT: - self.setDrawMode(QadGetPointDrawModeEnum.ELASTIC_LINE) - self.setStartPoint(self.basePt) diff --git a/qad_textwindow.py b/qad_textwindow.py index 948eab26..19091638 100644 --- a/qad_textwindow.py +++ b/qad_textwindow.py @@ -1,1297 +1,1420 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - classe per gestire la finestra testuale - - ------------------- - begin : 2014-09-21 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -from qgis.gui import * -import sys -import string -import difflib - - -from qad_ui_textwindow import Ui_QadTextWindow, Ui_QadCmdSuggestWindow -from qad_msg import QadMsg -import qad_utils -from qad_snapper import * -from qad_variables import QadVariables - - -#=============================================================================== -# QadInputTypeEnum class. -#=============================================================================== -class QadInputTypeEnum(): - NONE = 0 # nessuno - COMMAND = 1 # nome di un comando - POINT2D = 2 # punto - POINT3D = 4 # punto - KEYWORDS = 8 # una parola chiave - STRING = 16 # una stringa - INT = 32 # un numero intero - LONG = 64 # un numero intero - FLOAT = 128 # un numero reale - BOOL = 256 # un valore booleano - ANGLE = 512 # un valore reale in gradi - - -#=============================================================================== -# QadInputModeEnum class. -#=============================================================================== -class QadInputModeEnum(): - NONE = 0 - NOT_NULL = 1 # inserimento nullo non permesso - NOT_ZERO = 2 # valore zero non permesso - NOT_NEGATIVE = 4 # valore negativo non permesso - NOT_POSITIVE = 8 # valore positivo non permesso - - -#=============================================================================== -# QadCmdOptionPos -#=============================================================================== -class QadCmdOptionPos(): - def __init__(self, name = "", initialPos = 0, finalPos = 0): - self.name = name - self.initialPos = initialPos - self.finalPos = finalPos - - def isSelected(self, pos): - return True if pos >= self.initialPos and pos <= self.finalPos else False - - -#=============================================================================== -# QadTextWindow -#=============================================================================== -class QadTextWindow(QDockWidget, Ui_QadTextWindow, object): - """This class - """ - - def __init__(self, plugin): - """The constructor.""" - - QDockWidget.__init__(self, None) - self.setupUi(self) - self.setAllowedAreas(Qt.TopDockWidgetArea | Qt.BottomDockWidgetArea) - self.plugin = plugin - self.cmdSuggestWindow = None - self.connect(self, SIGNAL("topLevelChanged(bool)"), self.topLevelChanged) - - title = self.windowTitle() - self.setWindowTitle(title + " - " + plugin.version()) - - def initGui(self): - self.chronologyEdit = QadChronologyEdit(self) - self.chronologyEdit.setObjectName("QadChronologyEdit") - - self.edit = QadEdit(self, self.chronologyEdit) - self.edit.setObjectName("QadTextEdit") - - self.edit.displayPrompt(QadMsg.translate("QAD", "Command: ")) - - # Creo la finestra per il suggerimento dei comandi - # lista composta da elementi con: - # , , , - infoCmds = [] - for cmdName in self.getCommandNames(): - cmd = self.getCommandObj(cmdName[0]) - if cmd is not None: - infoCmds.append([cmdName[0], cmd.getEnglishName(), cmd.getIcon(), cmd.getNote()]) - - # Creo la finestra per il suggerimento delle variabili di ambiente - # lista composta da elementi con: - # , "", , - infoVars = [] - icon = QIcon(":/plugins/qad/icons/variable.png") - for varName in QadVariables.getVarNames(): - var = QadVariables.getVariable(varName) - infoVars.append([varName, "", icon, var.descr]) - - self.cmdSuggestWindow = QadCmdSuggestWindow(self, infoCmds, infoVars) - self.cmdSuggestWindow.initGui() - self.cmdSuggestWindow.show(False) - - - def getDockWidgetArea(self): - return self.parentWidget().dockWidgetArea(self) - - def setFocus(self): - self.edit.setFocus() - - def keyPressEvent(self, e): - self.edit.keyPressEvent(e) - - def topLevelChanged(self, topLevel): - self.resizeEdits - self.setFocus() - - def toggleShow(self): - if self.isVisible(): - self.hide() - else: - self.show() - - def showMsg(self, msg, displayPromptAfterMsg = False, append = True): - self.edit.showMsg(msg, displayPromptAfterMsg, append) - - def showInputMsg(self, inputMsg = None, inputType = QadInputTypeEnum.COMMAND, \ - default = None, keyWords = "", inputMode = QadInputModeEnum.NONE): - # il valore di default del parametro di una funzione non può essere una traduzione - # perché lupdate.exe non lo riesce ad interpretare - self.edit.showInputMsg(inputMsg, inputType, default, keyWords, inputMode) - - def showErr(self, err): - self.showMsg(err, True) # ripete il prompt - - def showMsgOnChronologyEdit(self, msg): - if self.chronologyEdit is not None: - self.chronologyEdit.insertText(msg) - - def showCmdSuggestWindow(self, mode = True, filter = ""): - if self.cmdSuggestWindow is not None: - inputSearchOptions = QadVariables.get(QadMsg.translate("Environment variables", "INPUTSEARCHOPTIONS")) - # inputSearchOptions & 1 = Turns on all automated keyboard features when typing at the Command prompt - # inputSearchOptions & 4 = Displays a list of suggestions as keystrokes are entered - if inputSearchOptions & 1 and inputSearchOptions & 4: - self.cmdSuggestWindow.show(mode, filter) - else: - self.cmdSuggestWindow.show(False) - - - def showEvaluateMsg(self, msg = None): - self.edit.showEvaluateMsg(msg) - - def getCurrMsg(self): - return self.edit.getCurrMsg() - - def getHistory(self): - return self.edit.history # list - - def updateHistory(self, command): - return self.edit.updateHistory(command) - - def runCommand(self, cmd): - self.plugin.runCommand(cmd) - - def continueCommand(self, cmd): - self.plugin.continueCommandFromTextWindow(cmd) - - def abortCommand(self): - self.plugin.abortCommand() - - def clearCurrentObjsSelection(self): - self.plugin.clearCurrentObjsSelection() - - def isValidCommand(self, cmd): - return self.plugin.isValidCommand(cmd) - - def getCommandNames(self): - return self.plugin.getCommandNames() - - def getCommandObj(self, cmdName): - return self.plugin.getCommandObj(cmdName) - - def isValidEnvVariable(self, variable): - return self.plugin.isValidEnvVariable(variable) - - def forceCommandMapToolSnapTypeOnce(self, snapType, snapParams = None): - return self.plugin.forceCommandMapToolSnapTypeOnce(snapType, snapParams) - - def toggleOsMode(self): - return self.plugin.toggleOsMode() - - def toggleOrthoMode(self): - return self.plugin.toggleOrthoMode() - - def togglePolarMode(self): - return self.plugin.togglePolarMode() - - def getLastPoint(self): - return self.plugin.lastPoint - - def setLastPoint(self, pt): - return self.plugin.setLastPoint(pt) - - def getCurrenPointFromCommandMapTool(self): - return self.plugin.getCurrenPointFromCommandMapTool() - - def resizeEdits(self): - if self.edit is None or self.chronologyEdit is None: - return - - rect = self.rect() - h = rect.height() - w = rect.width() - - editHeight = self.edit.getOptimalHeight() - if editHeight > h: - editHeight = h - chronologyEditHeight = h - editHeight - if not self.isFloating(): - offsetY = 20 - chronologyEditHeight = chronologyEditHeight - offsetY - else: - offsetY = 0 - - if chronologyEditHeight < 0: - chronologyEditHeight = 0 - - self.chronologyEdit.move(0, offsetY) - self.chronologyEdit.resize(w, chronologyEditHeight) - self.chronologyEdit.ensureCursorVisible() - - self.edit.resize(w, editHeight) - self.edit.move(0, chronologyEditHeight + offsetY) - self.edit.ensureCursorVisible() - - - def resizeEvent(self, e): - if self: - self.resizeEdits() - self.cmdSuggestWindow.resizeEvent(e) - -#=============================================================================== -# QadChronologyEdit -#=============================================================================== -class QadChronologyEdit(QTextEdit): - - def __init__(self, parent): - QTextEdit.__init__(self, parent) - - self.set_Colors() - self.setReadOnly(True) - self.setMinimumSize(0, 1) - - def set_Colors(self, foregroundColor = Qt.black, backGroundColor = Qt.lightGray): - p = self.palette() - p.setColor(QPalette.Base, backGroundColor) - self.setPalette(p) - self.setTextColor(foregroundColor) - self.setTextBackgroundColor(backGroundColor) - - def insertText(self, txt): - cursor = self.textCursor() - for line in txt.split('\n'): - if len(line) > 0: # to avoid one more empty line - cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor) # fine documento - self.setTextCursor(cursor) - self.insertPlainText('\n' + line) - cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor) # fine documento - self.setTextCursor(cursor) - self.ensureCursorVisible() - - -#=============================================================================== -# QadEdit -#=============================================================================== -class QadEdit(QTextEdit): - PROMPT, KEY_WORDS = range(2) - - def __init__(self, parent, chronologyEdit): - QTextEdit.__init__(self, parent) - - self.currentPrompt = "" - self.currentPromptLength = 0 - - self.inputType = QadInputTypeEnum.COMMAND - self.default = None - self.inputMode = QadInputModeEnum.NONE - - self.setTextInteractionFlags(Qt.TextEditorInteraction) - self.setMinimumSize(30, 21) - self.setUndoRedoEnabled(False) - self.setAcceptRichText(False) - self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) - self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) - - self.buffer = [] - - self.history = [] - self.historyIndex = 0 - - # stringa contenente le parole chiave separate da "/". - # la stringa può contenere il carattere speciale "_" per separare le parole chiave - # in lingua locale da quelle in inglese (es. "Si/No/Altra opzione_Yes/No/Other option") - self.englishKeyWords = [] # parole chiave in inglese - self.cmdOptionPosList = [] # lista delle posizioni delle opzioni del comando corrente - self.currentCmdOptionPos = None - - self.upperKeyWordForegroundColor = Qt.blue - self.keyWordBackGroundColor = QColor(210, 210, 210) - self.keyWordHighlightBackGroundColor = Qt.gray - - self.tcf_normal = QTextCharFormat() - self.tcf_history = QTextCharFormat() - self.tcf_keyWord = QTextCharFormat() - self.tcf_upperKeyWord = QTextCharFormat() - self.tcf_highlightKeyWord = QTextCharFormat() - self.tcf_highlightUpperKeyWord = QTextCharFormat() - self.set_Colors() - self.set_keyWordColors() - - self.setMouseTracking(True) - QObject.connect(self, SIGNAL("textChanged()"), self.onTextChanged) - - self.timerForCmdSuggestWindow = QTimer() - self.timerForCmdSuggestWindow.setSingleShot(True) - self.timerForCmdAutoComplete = QTimer() - self.timerForCmdAutoComplete.setSingleShot(True) - - - def set_Colors(self, foregroundColor = Qt.black, backGroundColor = Qt.white, history_ForegroundColor = Qt.blue, \ - history_BackGroundColor = Qt.gray): - self.tcf_normal.setForeground(foregroundColor) - self.tcf_normal.setBackground(backGroundColor) - self.tcf_normal.setFontWeight(QFont.Normal) - - self.tcf_history.setForeground(history_ForegroundColor) - self.tcf_history.setBackground(history_BackGroundColor) - self.tcf_history.setFontWeight(QFont.Normal) - - def set_keyWordColors(self, backGroundColor = QColor(210, 210, 210), upperKeyWord_ForegroundColor = Qt.blue, \ - highlightKeyWord_BackGroundColor = Qt.gray): - self.tcf_keyWord.setBackground(backGroundColor) - self.tcf_upperKeyWord.setForeground(upperKeyWord_ForegroundColor) - self.tcf_upperKeyWord.setBackground(backGroundColor) - self.tcf_upperKeyWord.setFontWeight(QFont.Bold) - - self.tcf_highlightKeyWord.setBackground(highlightKeyWord_BackGroundColor) - self.tcf_highlightUpperKeyWord.setForeground(upperKeyWord_ForegroundColor) - self.tcf_highlightUpperKeyWord.setBackground(highlightKeyWord_BackGroundColor) - self.tcf_highlightUpperKeyWord.setFontWeight(QFont.Bold) - - def setFormat(self, start, count, fmt): # 1-indexed - if count == 0: - return - cursor = QTextCursor(self.textCursor()) - cursor.movePosition(QTextCursor.Start, QTextCursor.MoveAnchor) # inizio documento - cursor.movePosition(QTextCursor.Right, QTextCursor.MoveAnchor, start) - cursor.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor, count) - cursor.setCharFormat(fmt); - self.setCurrentCharFormat(self.tcf_normal) - - def highlightKeyWords(self): - lastBlock = self.document().lastBlock() - txt = lastBlock.text() - size = len(txt) - - # messaggio + "[" + opz1 + "/" + opz2 + "]" - i = txt.find("[") - final = txt.rfind("]") - if i >= 0 and final > i: # se ci sono opzioni - i = i + 1 - pos = lastBlock.position() + i - while i < final: - if txt[i] != "/": - # se c'é un'opzione corrente deve essere evidenziata in modo diverso - if self.currentCmdOptionPos is not None and \ - pos >= self.currentCmdOptionPos.initialPos and \ - pos <= self.currentCmdOptionPos.finalPos : - if txt[i].isupper(): - self.setFormat(pos, 1, self.tcf_highlightUpperKeyWord) - else: - self.setFormat(pos, 1, self.tcf_highlightKeyWord) - else: - if txt[i].isupper(): - self.setFormat(pos, 1, self.tcf_upperKeyWord) - else: - self.setFormat(pos, 1, self.tcf_keyWord) - i = i + 1 - pos = pos + 1 - - - def isCursorInEditionZone(self, newPos = None): - cursor = self.textCursor() - if newPos is None: - pos = cursor.position() - else: - pos = newPos - block = self.document().lastBlock() - last = block.position() + self.currentPromptLength - return pos >= last - - def currentCommand(self): - block = self.textCursor().block() - text = block.text() - return text[self.currentPromptLength:] - - - def getTextUntilPrompt(self): - cursor = self.textCursor() - text = cursor.block().text() - return text[self.currentPromptLength : cursor.position()] - - def showMsgOnChronologyEdit(self, msg): - self.parentWidget().showMsgOnChronologyEdit(msg) - - def showCmdSuggestWindow(self, mode = True, filter = ""): - if mode == False: # se spengo la finestra - self.timerForCmdSuggestWindow.stop() - self.parentWidget().showCmdSuggestWindow(mode, filter) - - def showCmdAutoComplete(self, filter = ""): - # autocompletamento - self.timerForCmdAutoComplete.stop() - - # autocompletamento - inputSearchOptions = QadVariables.get(QadMsg.translate("Environment variables", "INPUTSEARCHOPTIONS")) - filterLen = len(filter) - if filterLen < 2: - return - # inputSearchOptions & 2 = Automatically appends suggestions as each keystroke is entered after the second keystroke. - if inputSearchOptions & 2: - if filterLen >= 2: - cmdName, qty = self.parentWidget().plugin.getMoreUsedCmd(filter) - else: - cmdName = "" - - cursor = self.textCursor() - cursor.movePosition(QTextCursor.End, QTextCursor.KeepAnchor) - self.setTextCursor(cursor) - if filterLen < len(cmdName): # se c'è qualcosa da aggiungere - self.insertPlainText(cmdName[filterLen:]) - else: - self.insertPlainText("") - #cursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, len(cmdName) - filterLen) - cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor) - cursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, len(cmdName) - filterLen) - self.setTextCursor(cursor) - - - - - - - - - - def showMsg(self, msg, displayPromptAfterMsg = False, append = True): - if len(msg) > 0: - cursor = self.textCursor() - sep = msg.rfind("\n") - if sep >= 0: - self.showMsgOnChronologyEdit(self.toPlainText() + msg[0:sep]) - newMsg = msg[sep + 1:] - cursor.movePosition(QTextCursor.Start, QTextCursor.MoveAnchor) - cursor.movePosition(QTextCursor.End, QTextCursor.KeepAnchor) - else: - if append == True: - cursor = self.textCursor() - cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor) # fine documento - newMsg = msg - else: - cursor.movePosition(QTextCursor.Start, QTextCursor.MoveAnchor) - cursor.movePosition(QTextCursor.End, QTextCursor.KeepAnchor) - newMsg = self.currentPrompt + msg - - self.setTextCursor(cursor) - self.insertPlainText(newMsg) - - if self.inputType & QadInputTypeEnum.KEYWORDS: - self.textCursor().block().setUserState(QadEdit.KEY_WORDS) - self.setCmdOptionPosList() # inizializzo la lista delle posizioni delle keyWords - self.highlightKeyWords() - else: - self.textCursor().block().setUserState(QadEdit.PROMPT) - del self.cmdOptionPosList[:] # svuoto la lista delle posizioni delle keyWords - - if displayPromptAfterMsg: - self.displayPrompt() # ripete il prompt - - - def displayPrompt(self, prompt = None): - if prompt is not None: - self.currentPrompt = prompt - self.currentPromptLength = len(self.currentPrompt) - self.showMsg("\n" + self.currentPrompt) - - def displayKeyWordsPrompt(self, prompt = None): - if prompt is not None: - self.currentPrompt = prompt - self.currentPromptLength = len(self.currentPrompt) - self.showMsg("\n" + self.currentPrompt) - - def showNext(self): - if self.historyIndex < len(self.history) and len(self.history) > 0: - self.historyIndex += 1 - if self.historyIndex < len(self.history): - # displayPromptAfterMsg = False, append = True - self.showMsg(self.history[self.historyIndex], False, False) # sostituisce il testo dopo il prompt - - def showPrevious(self): - if self.historyIndex > 0 and len(self.history) > 0: - self.historyIndex -= 1 - if self.historyIndex < len(self.history): - # displayPromptAfterMsg = False, append = True - self.showMsg(self.history[self.historyIndex], False, False) # sostituisce il testo dopo il prompt - - def showLast(self): - if len(self.history) > 0: - self.showMsg(self.history[len(self.history) - 1]) - return self.history[len(self.history) - 1] - else: - return "" - - def showInputMsg(self, inputMsg = None, inputType = QadInputTypeEnum.COMMAND, \ - default = None, keyWords = "", inputMode = QadInputModeEnum.NONE): - # il valore di default del parametro di una funzione non può essere una traduzione - # perché lupdate.exe non lo riesce ad interpretare - if inputMsg is None: - inputMsg = QadMsg.translate("QAD", "Command: ") - - cursor = self.textCursor() - actualPos = cursor.position() - - self.inputType = inputType - self.default = default - self.inputMode = inputMode - if inputType & QadInputTypeEnum.KEYWORDS and (keyWords is not None): - # carattere separatore tra le parole chiave in lingua locale e quelle in inglese - localEnglishKeyWords = keyWords.split("_") - self.keyWords = localEnglishKeyWords[0].split("/") # carattere separatore delle parole chiave - if len(localEnglishKeyWords) > 1: - self.englishKeyWords = localEnglishKeyWords[1].split("/") # carattere separatore delle parole chiave - else: - del self.englishKeyWords[:] - self.displayKeyWordsPrompt(inputMsg) - else: - self.displayPrompt(inputMsg) - - return - - def setCmdOptionPosList(self): - del self.cmdOptionPosList[:] # svuoto la lista - lenKeyWords = len(self.keyWords) - if lenKeyWords == 0 or len(self.currentPrompt) == 0: - return - # le opzioni sono racchiuse in parentesi quadre e separate tra loro da / - prompt = self.currentPrompt - initialPos = prompt.find("[", 0) - finalDelimiter = prompt.find("]", initialPos) - if initialPos == -1 or finalDelimiter == -1: - return - i = 0 - while i < lenKeyWords: - keyWord = self.keyWords[i] - initialPos = prompt.find(keyWord, initialPos + 1, finalDelimiter) - if initialPos >= 0: - finalPos = initialPos + len(keyWord) - self.cmdOptionPosList.append(QadCmdOptionPos(keyWord, initialPos, finalPos)) - initialPos = prompt.find("/", finalPos) - if initialPos == -1: - return - - i = i + 1 - - def getCmdOptionPosUnderMouse(self, pos): - cursor = self.cursorForPosition(pos) - pos = cursor.position() - for cmdOptionPos in self.cmdOptionPosList: - if cmdOptionPos.isSelected(pos): - return cmdOptionPos - return None - - def mouseMoveEvent(self, event): - self.currentCmdOptionPos = self.getCmdOptionPosUnderMouse(event.pos()) - self.highlightKeyWords() - self.currentCmdOptionPos = None - - def mouseDoubleClickEvent(self, event): - cursor = self.cursorForPosition(event.pos()) - pos = cursor.position() - if self.isCursorInEditionZone(pos): - QTextEdit.mouseDoubleClickEvent(self, event) - - def mousePressEvent(self, event): - cursor = self.cursorForPosition(event.pos()) - pos = cursor.position() - if self.isCursorInEditionZone(pos): - QTextEdit.mousePressEvent(self, event) - - def mouseReleaseEvent(self, event): - # se sono sull'ultima riga - if self.textCursor().position() >= self.document().lastBlock().position(): - if event.button() == Qt.LeftButton: - cmdOptionPos = self.getCmdOptionPosUnderMouse(event.pos()) - if cmdOptionPos is not None: - self.showEvaluateMsg(cmdOptionPos.name, False) - - def updateHistory(self, command): - # Se command é una lista di comandi - if isinstance(command, list): - for line in command: - self.updateHistory(line) - elif not command == "": - # se lo storico é vuoto o se il comando da inserire é diverso dall'ultimo - if len(self.history) <= 0 or command != self.history[-1]: - self.history.append(command) - - self.historyIndex = len(self.history) - - - def keyPressEvent(self, e): - cursor = self.textCursor() - - if self.inputType & QadInputTypeEnum.COMMAND: # nascondo la finestra di suggerimento - self.showCmdSuggestWindow(False) - - #QMessageBox.warning(self.plugIn.TextWindow, "titolo" , 'msg') - - # Se é stato premuto il tasto CTRL (o META) + 9 - if ((e.modifiers() & Qt.ControlModifier) or (e.modifiers() & Qt.MetaModifier)) and \ - e.key() == Qt.Key_9: - # Accendo o spengo la finestra di testo - self.parentWidget().toggleShow() - return - - # Se é stato premuto il tasto F10 - if e.key() == Qt.Key_F10: - # Attivo o disattivo il modo polare - self.parentWidget().togglePolarMode() - return - - # Se é stato premuto il tasto F3 - if e.key() == Qt.Key_F3: - # Attivo o disattivo lo snap - self.parentWidget().toggleOsMode() - return - - # Se é stato premuto il tasto F8 - if e.key() == Qt.Key_F8: - # Attivo o disattivo la modalità ortogonale - self.parentWidget().toggleOrthoMode() - return - - if e.key() == Qt.Key_Escape: - self.parentWidget().abortCommand() - self.parentWidget().clearCurrentObjsSelection() - return - - # if the cursor isn't in the edit zone, don't do anything except Ctrl+C - if not self.isCursorInEditionZone(): - if e.modifiers() & Qt.ControlModifier or e.modifiers() & Qt.MetaModifier: - if e.key() == Qt.Key_C or e.key() == Qt.Key_A: - QTextEdit.keyPressEvent(self, e) - else: - # all other keystrokes get sent to the input line - cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor) - self.setTextCursor(cursor) - QTextEdit.keyPressEvent(self, e) - - self.setTextCursor(cursor) - self.ensureCursorVisible() - else: - # if Return is pressed, then perform the commands - if e.key() == Qt.Key_Return: - self.entered() - # if Space is pressed during command request or value not string request - elif e.key() == Qt.Key_Space and \ - (self.inputType & QadInputTypeEnum.COMMAND or not(self.inputType & QadInputTypeEnum.STRING)): - self.entered() - # if Up or Down is pressed - elif e.key() == Qt.Key_Down: - self.showNext() - elif e.key() == Qt.Key_Up: - self.showPrevious() - # if backspace is pressed, delete until we get to the prompt - elif e.key() == Qt.Key_Backspace: - if not cursor.hasSelection() and cursor.columnNumber() == self.currentPromptLength: - return - QTextEdit.keyPressEvent(self, e) - # if the left key is pressed, move left until we get to the prompt - elif e.key() == Qt.Key_Left and cursor.position() > self.document().lastBlock().position() + self.currentPromptLength: - anchor = QTextCursor.KeepAnchor if e.modifiers() & Qt.ShiftModifier else QTextCursor.MoveAnchor - move = QTextCursor.WordLeft if e.modifiers() & Qt.ControlModifier or e.modifiers() & Qt.MetaModifier else QTextCursor.Left - cursor.movePosition(move, anchor) - # use normal operation for right key - elif e.key() == Qt.Key_Right: - anchor = QTextCursor.KeepAnchor if e.modifiers() & Qt.ShiftModifier else QTextCursor.MoveAnchor - move = QTextCursor.WordRight if e.modifiers() & Qt.ControlModifier or e.modifiers() & Qt.MetaModifier else QTextCursor.Right - cursor.movePosition(move, anchor) - # if home is pressed, move cursor to right of prompt - elif e.key() == Qt.Key_Home: - anchor = QTextCursor.KeepAnchor if e.modifiers() & Qt.ShiftModifier else QTextCursor.MoveAnchor - cursor.movePosition(QTextCursor.StartOfBlock, anchor, 1) - cursor.movePosition(QTextCursor.Right, anchor, self.currentPromptLength) - # use normal operation for end key - elif e.key() == Qt.Key_End: - anchor = QTextCursor.KeepAnchor if e.modifiers() & Qt.ShiftModifier else QTextCursor.MoveAnchor - cursor.movePosition(QTextCursor.EndOfBlock, anchor, 1) - # use normal operation for all remaining keys - else: - QTextEdit.keyPressEvent(self, e) - - self.setTextCursor(cursor) - self.ensureCursorVisible() - - if self.inputType & QadInputTypeEnum.COMMAND: - # leggo il tempo di ritardo in msec - inputSearchDelay = QadVariables.get(QadMsg.translate("Environment variables", "INPUTSEARCHDELAY")) - - # lista suggerimento dei comandi simili - currMsg = self.getCurrMsg() - shot1 = lambda: self.showCmdSuggestWindow(True, currMsg) - - del self.timerForCmdSuggestWindow - self.timerForCmdSuggestWindow = QTimer() - self.timerForCmdSuggestWindow.setSingleShot(True) - self.timerForCmdSuggestWindow.timeout.connect(shot1) - self.timerForCmdSuggestWindow.start(inputSearchDelay) - - if e.text().isalnum(): # autocompletamento se è stato premuto un tasto alfanumerico - self.textUntilPrompt = self.getTextUntilPrompt() - shot2 = lambda: self.showCmdAutoComplete(self.textUntilPrompt) - del self.timerForCmdAutoComplete - self.timerForCmdAutoComplete = QTimer() - self.timerForCmdAutoComplete.setSingleShot(True) - - self.timerForCmdAutoComplete.timeout.connect(shot2) - self.timerForCmdAutoComplete.start(inputSearchDelay) - - - def entered(self): - if self.inputType & QadInputTypeEnum.COMMAND: - self.showCmdSuggestWindow(False) - - cursor = self.textCursor() - cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor) - self.setTextCursor(cursor) - self.evaluate(unicode(self.currentCommand())) - - def showEvaluateMsg(self, msg = None, append = True): - """ - mostra e valuta il messaggio msg se diverso da None altrimenti usa il messaggio corrente - """ - if msg is not None: - self.showMsg(msg, False, append) - self.entered() - - def getCurrMsg(self): - """ - restituisce il messaggio già presente nella finestra di testo - """ - cursor = self.textCursor() - prevPos = cursor.position() - cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor) - self.setTextCursor(cursor) - msg = unicode(self.currentCommand()) - cursor.setPosition(prevPos) - self.setTextCursor(cursor) - return msg - - - def getInvalidInputMsg(self): - """ - restituisce il messaggio di input non valido - """ - if self.inputType & QadInputTypeEnum.POINT2D or \ - self.inputType & QadInputTypeEnum.POINT3D: - if self.inputType & QadInputTypeEnum.KEYWORDS and \ - (self.inputType & QadInputTypeEnum.FLOAT or self.inputType & QadInputTypeEnum.ANGLE): - return QadMsg.translate("QAD", "\nEnter a point, a real number or a keyword.\n") - elif self.inputType & QadInputTypeEnum.KEYWORDS: - return QadMsg.translate("QAD", "\nEnter a point or a keyword.\n") - elif self.inputType & QadInputTypeEnum.FLOAT or self.inputType & QadInputTypeEnum.ANGLE: - return QadMsg.translate("QAD", "\nEnter a point or a real number.\n") - else: - return QadMsg.translate("QAD", "\nPoint not valid.\n") - elif self.inputType & QadInputTypeEnum.KEYWORDS: - return QadMsg.translate("QAD", "\nKeyword not valid.\n") - elif self.inputType & QadInputTypeEnum.STRING: - return QadMsg.translate("QAD", "\nString not valid.\n") - elif self.inputType & QadInputTypeEnum.INT: - return QadMsg.translate("QAD", "\nInteger number not valid.\n") - elif self.inputType & QadInputTypeEnum.LONG: - return QadMsg.translate("QAD", "\nLong integer number not valid.\n") - elif self.inputType & QadInputTypeEnum.FLOAT or self.inputType & QadInputTypeEnum.ANGLE: - return QadMsg.translate("QAD", "\nReal number not valid.\n") - elif self.inputType & QadInputTypeEnum.BOOL: - return QadMsg.translate("QAD", "\nBoolean not valid.\n") - else: - return "" - - - def __evaluateKeyWords(self, cmd, keyWordList): - # The required portion of the keyword is specified in uppercase characters, - # and the remainder of the keyword is specified in lowercase characters. - # The uppercase abbreviation can be anywhere in the keyword - if cmd == "": # se cmd = "" la funzione find ritorna 0 (no comment) - return None - upperCmd = cmd.upper() - selectedKeyWords = [] - for keyWord in keyWordList: - # estraggo la parte maiuscola della parola chiave - upperPart = "" - for letter in keyWord: - if letter.isupper(): - upperPart = upperPart + letter - elif len(upperPart) > 0: - break - - if upperPart.find(upperCmd) == 0: # se la parte maiuscola della parola chiave inizia per upperCmd - if upperPart == upperCmd: # Se uguale - return keyWord - else: - selectedKeyWords.append(keyWord) - elif keyWord.upper().find(upperCmd) == 0: # se la parola chiave inizia per cmd (insensitive) - if keyWord.upper() == upperCmd: # Se uguale - return keyWord - else: - selectedKeyWords.append(keyWord) - - selectedKeyWordsLen = len(selectedKeyWords) - if selectedKeyWordsLen == 0: - return None - elif selectedKeyWordsLen == 1: - return selectedKeyWords[0] - else: - self.showMsg(QadMsg.translate("QAD", "\nAmbiguous answer: specify with greater clarity...\n")) - Msg = "" - for keyWord in selectedKeyWords: - if Msg == "": - Msg = keyWord - else: - Msg = Msg + QadMsg.translate("QAD", " or ") + keyWord - - Msg = Msg + QadMsg.translate("QAD", " ?\n") - self.showMsg(Msg) - - return None - - def evaluateKeyWords(self, cmd): - # The required portion of the keyword is specified in uppercase characters, - # and the remainder of the keyword is specified in lowercase characters. - # The uppercase abbreviation can be anywhere in the keyword - if cmd == "": # se cmd = "" la funzione find ritorna 0 (no comment) - return None - - if cmd[0] == "_": # versione inglese - keyWord = self.__evaluateKeyWords(cmd[1:], self.englishKeyWords) - if keyWord is None: - return None - # cerco la corrispondente parola chiave in lingua locale - i = 0 - for k in self.englishKeyWords: - if k == keyWord: - return self.keyWords[i] - i = i + 1 - return None - else: - return self.__evaluateKeyWords(cmd, self.keyWords) - - - def evaluate(self, cmd): - #------------------------------------------------------------------------------ - # nome di un comando - #------------------------------------------------------------------------------ - if self.inputType & QadInputTypeEnum.COMMAND: - if cmd == "": - cmd = unicode(self.showLast()) # ripeto ultimo comando - - if self.parentWidget().isValidCommand(cmd) or self.parentWidget().isValidEnvVariable(cmd): - self.updateHistory(cmd) - self.parentWidget().runCommand(cmd) - else: - msg = QadMsg.translate("QAD", "\nInvalid command \"{0}\".") - self.showMsg(msg.format(cmd.encode('ascii','ignore')), True) # ripete il prompt - return - - if cmd == "": - if self.default is not None: - if type(self.default) == QgsPoint: - cmd = self.default.toString() - else: - cmd = unicode(self.default) - - if cmd == "" and \ - not (self.inputMode & QadInputModeEnum.NOT_NULL): # permesso input nullo - self.parentWidget().continueCommand(None) - return - - #------------------------------------------------------------------------------ - # punto 2D - #------------------------------------------------------------------------------ - if self.inputType & QadInputTypeEnum.POINT2D: - snapType = qad_utils.str2snapTypeEnum(cmd) - if snapType != -1: - # se é stato forzato uno snap - snapParams = qad_utils.str2snapParams(cmd) - self.parentWidget().forceCommandMapToolSnapTypeOnce(snapType, snapParams) - self.showMsg(QadMsg.translate("QAD", "\n(temporary snap)\n"), True) # ripeti il prompt - return - if (self.inputType & QadInputTypeEnum.INT) or \ - (self.inputType & QadInputTypeEnum.LONG) or \ - (self.inputType & QadInputTypeEnum.FLOAT) or \ - (self.inputType & QadInputTypeEnum.ANGLE) or \ - (self.inputType & QadInputTypeEnum.BOOL): - oneNumberAllowed = False - else: - oneNumberAllowed = True - - pt = qad_utils.str2QgsPoint(cmd, \ - self.parentWidget().getLastPoint(), \ - self.parentWidget().getCurrenPointFromCommandMapTool(), \ - oneNumberAllowed) - - if pt is not None: - self.parentWidget().setLastPoint(pt) - self.parentWidget().continueCommand(pt) - return - - #------------------------------------------------------------------------------ - # punto 3D - #------------------------------------------------------------------------------ - if self.inputType & QadInputTypeEnum.POINT3D: # punto - pass - - #------------------------------------------------------------------------------ - # una parola chiave - #------------------------------------------------------------------------------ - if self.inputType & QadInputTypeEnum.KEYWORDS: - keyWord = self.evaluateKeyWords(cmd) - - if keyWord is not None: - self.parentWidget().continueCommand(keyWord) - return - - #------------------------------------------------------------------------------ - # una stringa - #------------------------------------------------------------------------------ - if self.inputType & QadInputTypeEnum.STRING: - if cmd is not None: - self.parentWidget().continueCommand(cmd) - return - - #------------------------------------------------------------------------------ - # un numero intero - #------------------------------------------------------------------------------ - if self.inputType & QadInputTypeEnum.INT: - num = qad_utils.str2int(cmd) - if num == 0 and (self.inputMode & QadInputModeEnum.NOT_ZERO): # non permesso valore = 0 - num = None - elif num < 0 and (self.inputMode & QadInputModeEnum.NOT_NEGATIVE): # non permesso valore < 0 - num = None - elif num > 0 and (self.inputMode & QadInputModeEnum.NOT_POSITIVE): # non permesso valore > 0 - num = None - - if num is not None: - self.parentWidget().continueCommand(int(num)) - return - - #------------------------------------------------------------------------------ - # un numero lungo - #------------------------------------------------------------------------------ - if self.inputType & QadInputTypeEnum.LONG: - num = qad_utils.str2long(cmd) - if num == 0 and (self.inputMode & QadInputModeEnum.NOT_ZERO): # non permesso valore = 0 - num = None - elif num < 0 and (self.inputMode & QadInputModeEnum.NOT_NEGATIVE): # non permesso valore < 0 - num = None - elif num > 0 and (self.inputMode & QadInputModeEnum.NOT_POSITIVE): # non permesso valore > 0 - num = None - - if num is not None: - self.parentWidget().continueCommand(long(num)) - return - - #------------------------------------------------------------------------------ - # un numero reale - #------------------------------------------------------------------------------ - if self.inputType & QadInputTypeEnum.FLOAT or self.inputType & QadInputTypeEnum.ANGLE: - num = qad_utils.str2float(cmd) - if num == 0 and (self.inputMode & QadInputModeEnum.NOT_ZERO): # non permesso valore = 0 - num = None - elif num < 0 and (self.inputMode & QadInputModeEnum.NOT_NEGATIVE): # non permesso valore < 0 - num = None - elif num > 0 and (self.inputMode & QadInputModeEnum.NOT_POSITIVE): # non permesso valore > 0 - num = None - - if num is not None: - if self.inputType & QadInputTypeEnum.ANGLE: # se é un angolo in gradi - # i gradi vanno convertiti in radianti - num = qad_utils.toRadians(num) - self.parentWidget().continueCommand(float(num)) - return - - #------------------------------------------------------------------------------ - # un valore booleano - #------------------------------------------------------------------------------ - elif self.inputType & QadInputTypeEnum.BOOL: - value = qad_utils.str2bool(cmd) - - if value is not None: - self.parentWidget().continueCommand(value) - return - - self.showMsg(self.getInvalidInputMsg()) - - if self.inputType & QadInputTypeEnum.KEYWORDS: - self.displayKeyWordsPrompt() - else: - self.displayPrompt() - - return - - def getOptimalHeight(self): - fm = QFontMetrics(self.currentFont()) - pixelsWidth = fm.width(QadMsg.translate("QAD", "Command: ")) - pixelsHeight = fm.height() - # + 8 perché la QTextEdit ha un offset verticale sopra e sotto il testo - return max(self.document().size().height(), pixelsHeight + 8) - - def onTextChanged(self): - self.parentWidget().resizeEdits() - self.timerForCmdAutoComplete.stop() - - -#=============================================================================== -# QadCmdSuggestWindow -#=============================================================================== -class QadCmdSuggestWindow(QWidget, Ui_QadCmdSuggestWindow, object): - - def __init__(self, parent, infoCmds, infoVars): - # lista composta da elementi con: - # , , , - QWidget.__init__(self, parent, Qt.Popup) # test - #QWidget.__init__(self, parent, Qt.Widget) # test - self.setupUi(self) - self.infoCmds = infoCmds[:] # copio la lista comandi - self.infoVars = infoVars[:] # copio la lista variabili ambiente - - def initGui(self): - self.cmdNamesListView = QadCmdSuggestListView(self) - self.cmdNamesListView.setObjectName("QadCmdNamesListView") - self.vboxlayout.addWidget(self.cmdNamesListView) - - def setFocus(self): - self.cmdNamesListView.setFocus() - - def keyPressEvent(self, e): - self.cmdNamesListView.keyPressEvent(e) - - - def inFilteredInfoList(self, filteredInfoList, cmdName): - for filteredInfo in filteredInfoList: - if filteredInfo[0] == cmdName: - return True - return False - - - def getFilteredInfoList(self, infoList, filter = ""): - inputSearchOptions = QadVariables.get(QadMsg.translate("Environment variables", "INPUTSEARCHOPTIONS")) - # inputSearchOptions & 1 = Turns on all automated keyboard features when typing at the Command prompt - # inputSearchOptions & 8 = Displays the icon of the command or system variable, if available. - dispIcons = inputSearchOptions & 1 and inputSearchOptions & 8 - - filteredInfoList = [] - upperFilter = filter.strip().upper() - if len(upperFilter) > 0: - if filter == "*": # se incomincia per * significa tutti i comandi in lingua locale - # lista composta da elementi con: - # , , , - for info in infoList: - if not self.inFilteredInfoList(filteredInfoList, info[0]): - filteredInfoList.append([info[0], info[2] if dispIcons else None, info[3]]) - else: - if upperFilter[0] == "_": # versione inglese - upperFilter = upperFilter[1:] - # lista composta da elementi con: - # , , , - for info in infoList: - # se "incomincia per" o se "abbastanza simile" - if string.find(info[1].upper(), upperFilter) == 0 or \ - difflib.SequenceMatcher(None, info[1].upper(), upperFilter).ratio() > 0.6: - if not self.inFilteredInfoList(filteredInfoList, "_" + info[1]): - filteredInfoList.append(["_" + info[1], info[2] if dispIcons else None, info[3]]) - else: # versione italiana - # lista composta da elementi con: - # , , , - for info in infoList: - # se "incomincia per" o se "abbastanza simile" - if string.find(info[0].upper(), upperFilter) == 0 or \ - difflib.SequenceMatcher(None, info[0].upper(), upperFilter).ratio() > 0.6: - if not self.inFilteredInfoList(filteredInfoList, info[0]): - filteredInfoList.append([info[0], info[2] if dispIcons else None, info[3]]) - - return filteredInfoList - - - def show(self, mode = True, filter = ""): - if mode == True: - itemList = [] - itemList.extend(self.infoCmds) - - inputSearchOptions = QadVariables.get(QadMsg.translate("Environment variables", "INPUTSEARCHOPTIONS")) - # inputSearchOptions & 1 = Turns on all automated keyboard features when typing at the Command prompt - # inputSearchOptions & 16 = Excludes the display of system variables - if inputSearchOptions & 1 and (not inputSearchOptions & 16): - itemList.extend(self.infoVars) - - # filtro i nomi - filteredInfo = self.getFilteredInfoList(itemList, filter) - - if len(filteredInfo) == 0: - self.setVisible(False) - else: - self.cmdNamesListView.set(filteredInfo) - # seleziono il primo che incomincia per filter - items = self.cmdNamesListView.model.findItems(filter, Qt.MatchStartsWith) - if len(items) > 0: - self.cmdNamesListView.setCurrentIndex(self.cmdNamesListView.model.indexFromItem(items[0])) - - self.moveAndResize() - self.setVisible(True) - else: - self.setVisible(False) - - def getDataHeight(self): - n = self.cmdNamesListView.model.rowCount() - if n == 0: - return 0 - - OffSet = 4 # un pò di spazio in più per mostrare anche l'icona dei comandi - return self.cmdNamesListView.sizeHintForRow(0) * n + OffSet - - - def moveAndResize(self): - dataHeight = self.getDataHeight() - if dataHeight > 0: - self.cmdNamesListView.setMinimumHeight(self.cmdNamesListView.sizeHintForRow(0)) - - if self.parentWidget().isFloating(): - ptUp = self.parentWidget().edit.mapToGlobal(QPoint(0,0)) - spaceUp = ptUp.y() if ptUp.y() - dataHeight < 0 else dataHeight - - ptDown = QPoint(ptUp.x(), ptUp.y() + self.parentWidget().edit.height()) - rect = QApplication.desktop().screenGeometry() - spaceDown = rect.height() - ptDown.y() if ptDown.y() + dataHeight > rect.height() else dataHeight - - # verifico se c'è più spazio sopra o sotto la finestra - if spaceUp > spaceDown: - pt = QPoint(ptUp.x(), ptUp.y() - spaceUp) - dataHeight = spaceUp - else: - pt = QPoint(ptDown.x(), ptDown.y()) - dataHeight = spaceDown - elif self.parentWidget().getDockWidgetArea() == Qt.BottomDockWidgetArea: - pt = self.parentWidget().edit.mapToGlobal(QPoint(0,0)) - if pt.y() - dataHeight < 0: - dataHeight = pt.y() - pt.setY(pt.y() - dataHeight) - elif self.parentWidget().getDockWidgetArea() == Qt.TopDockWidgetArea: - pt = self.parentWidget().edit.mapToGlobal(QPoint(0,0)) - pt.setY(pt.y() + self.parentWidget().edit.height()) - rect = QApplication.desktop().screenGeometry() - if pt.y() + dataHeight > rect.height(): - dataHeight = rect.height() - pt.y() - - if pt.x() < 0: - pt.setX(0) - - self.move(pt) - self.resize(200, dataHeight) - - def showEvaluateMsg(self, cmd = None): - self.show(False) - self.parentWidget().setFocus() - self.parentWidget().showEvaluateMsg(cmd) - - def showMsg(self, cmd): - # sostituisco il testo con il nuovo comando e riporto il cursore nella posizione di prima - parent = self.parentWidget() - cursor = parent.edit.textCursor() - prevPos = cursor.position() - parent.showMsg(cmd, False, False) - cursor.setPosition(prevPos) - parent.edit.setTextCursor(cursor) - parent.edit.setFocus() - self.parentWidget().setFocus() - - def keyPressEventToParent(self, e): - self.parentWidget().keyPressEvent(e) - - -#=============================================================================== -# QadCmdListView -#=============================================================================== -class QadCmdSuggestListView(QListView): - - def __init__(self, parent): - QListView.__init__(self, parent) - - self.setViewMode(QListView.ListMode) - self.setSelectionBehavior(QAbstractItemView.SelectItems) - self.setUniformItemSizes(True) - self.model = QStandardItemModel() - self.setModel(self.model) - - def set(self, filteredCmdNames): - # lista composta da elementi con , , - self.model.clear() - - for infoCmd in filteredCmdNames: - cmdName = infoCmd[0] - cmdIcon = infoCmd[1] - cmdNote = infoCmd[2] - if cmdIcon is None: - item = QStandardItem(cmdName) - else: - item = QStandardItem(cmdIcon, cmdName) - - if cmdNote is not None and len(cmdNote) > 0: - item.setToolTip(cmdNote) - - item.setEditable(False) - self.model.appendRow(item) - - self.model.sort(0) - - -# def selectionChanged(self, i1, i2): -# inputSearchOptions = QadVariables.get(QadMsg.translate("Environment variables", "INPUTSEARCHOPTIONS")) -# # inputSearchOptions & 2 = Automatically appends suggestions as each keystroke is entered after the second keystroke. -# if inputSearchOptions & 2: -# cmd = self.selectionModel().currentIndex().data() -# self.parentWidget().showMsg(cmd) - - - def keyPressEvent(self, e): - if e.key() == Qt.Key_Up or e.key() == Qt.Key_Down or \ - e.key() == Qt.Key_PageUp or e.key() == Qt.Key_PageDown or \ - e.key() == Qt.Key_End or e.key() == Qt.Key_Home: - QListView.keyPressEvent(self, e) - # if Return is pressed, then perform the commands - elif e.key() == Qt.Key_Return: - cmd = self.selectionModel().currentIndex().data() - if cmd is not None: - self.parentWidget().showMsg(cmd) - self.parentWidget().showEvaluateMsg() - else: - self.parentWidget().keyPressEventToParent(e) - - - def mouseReleaseEvent(self, e): - cmd = self.selectionModel().currentIndex().data() - self.parentWidget().showMsg(cmd) - self.parentWidget().showEvaluateMsg() - +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + classe per gestire la finestra testuale + + ------------------- + begin : 2014-09-21 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from qgis.PyQt.QtCore import Qt, QTimer, QPoint, QRect +from qgis.PyQt.QtGui import QIcon, QColor, QTextCursor, QTextCharFormat, QFont, \ + QFontMetrics, QStandardItemModel, QStandardItem +from qgis.PyQt.QtWidgets import QDockWidget, QListView, QAbstractItemView, QApplication, QWidget, QTextEdit, QMessageBox +from qgis.core import QgsPointXY, QgsSettings +import sys +import string +import difflib + + +from .qad_ui_textwindow import Ui_QadTextWindow, Ui_QadCmdSuggestWindow +from .qad_msg import QadMsg +from . import qad_utils +from .qad_snapper import str2snapTypeEnum, str2snapParams +from .qad_variables import QadVariables, QadINPUTSEARCHOPTIONSEnum + + +# =============================================================================== +# QadInputTypeEnum class. +# =============================================================================== +class QadInputTypeEnum(): + NONE = 0 # nessuno + COMMAND = 1 # nome di un comando + POINT2D = 2 # punto + POINT3D = 4 # punto + KEYWORDS = 8 # una parola chiave + STRING = 16 # una stringa + INT = 32 # un numero intero + LONG = 64 # un numero intero + FLOAT = 128 # un numero reale + BOOL = 256 # un valore booleano + ANGLE = 512 # un valore reale in gradi + + +# =============================================================================== +# QadInputModeEnum class. +# =============================================================================== +class QadInputModeEnum(): + NONE = 0 + NOT_NULL = 1 # inserimento nullo non permesso + NOT_ZERO = 2 # valore zero non permesso + NOT_NEGATIVE = 4 # valore negativo non permesso + NOT_POSITIVE = 8 # valore positivo non permesso + + +# =============================================================================== +# QadCmdOptionPos +# =============================================================================== +class QadCmdOptionPos(): + def __init__(self, name = "", initialPos = 0, finalPos = 0): + self.name = name + self.initialPos = initialPos + self.finalPos = finalPos + + def isSelected(self, pos): + return True if pos >= self.initialPos and pos <= self.finalPos else False + + + +# =============================================================================== +# QadTextWindow +# =============================================================================== +class QadTextWindow(QDockWidget, Ui_QadTextWindow): + """This class + """ + + def __init__(self, plugin): + """The constructor.""" + + QDockWidget.__init__(self, plugin.iface.mainWindow()) + self.setupUi(self) + self.setAllowedAreas(Qt.TopDockWidgetArea | Qt.BottomDockWidgetArea) + self.plugin = plugin + self.cmdSuggestWindow = None + self.topLevelChanged['bool'].connect(self.onTopLevelChanged) + + title = self.windowTitle() + self.setWindowTitle(QadMsg.getQADTitle() + " - " + title + " - " + plugin.version()) + + + def __del__(self): + """The destructor.""" + + self.topLevelChanged['bool'].disconnect(self.onTopLevelChanged) + + QDockWidget.__del__(self) + + + def initGui(self): + self.chronologyEdit = QadChronologyEdit(self) + self.chronologyEdit.setObjectName("QadChronologyEdit") + + self.edit = QadEdit(self, self.chronologyEdit) + self.edit.setObjectName("QadTextEdit") + + self.edit.displayPrompt(QadMsg.translate("QAD", "Command: ")) + + # Creo la finestra per il suggerimento dei comandi + # lista composta da elementi con: + # , , , + infoCmds = [] + for cmdName in self.getCommandNames(): + cmd = self.getCommandObj(cmdName[0]) + if cmd is not None: + infoCmds.append([cmdName[0], cmd.getEnglishName(), cmd.getIcon(), cmd.getNote()]) + + # Creo la finestra per il suggerimento delle variabili di ambiente + # lista composta da elementi con: + # , "", , + infoVars = [] + icon = QIcon(":/plugins/qad/icons/variable.svg") + for varName in QadVariables.getVarNames(): + var = QadVariables.getVariable(varName) + infoVars.append([varName, "", icon, var.descr]) + + self.cmdSuggestWindow = QadCmdSuggestWindow(self, self.edit, infoCmds, infoVars) + self.cmdSuggestWindow.initGui() + self.cmdSuggestWindow.show(False) + + self.refreshColors() + + + # ============================================================================ + # writeDockWidgetSettings + # ============================================================================ + def writeDockWidgetSettings(self): + s = QgsSettings() + s.setValue("qad/text_window_x", self.x()) + s.setValue("qad/text_window_y", self.y()) + s.setValue("qad/text_window_width", self.width()) + s.setValue("qad/text_window_height", self.height()) + s.setValue("qad/text_window_floating", self.isFloating()) + s.setValue("qad/text_window_area", self.plugin.iface.mainWindow().dockWidgetArea(self)) + #QMessageBox.warning(None, "titolo" , "altezza: " + str(self.height())) + + + # ============================================================================ + # readDockWidgetSettings + # ============================================================================ + def readDockWidgetSettings(self): + s = QgsSettings() + x = s.value("qad/text_window_x", 0, type=int,) + y = s.value("qad/text_window_y", 0, type=int,) + width = s.value("qad/text_window_width", 400, type=int,) + height = s.value("qad/text_window_height", 400, type=int,) + isFloating = s.value("qad/text_window_floating", False, type=bool,) + dockWidgetArea = s.value("qad/text_window_area", Qt.BottomDockWidgetArea, type=int,) + + #QMessageBox.warning(None, "titolo" , "altezza: " + str(height)) + + return isFloating, QRect(x, y, width, height), dockWidgetArea + + + # ============================================================================ + # refreshColors + # ============================================================================ + def refreshColors(self): + history_ForegroundColor = QColor(QadVariables.get(QadMsg.translate("Environment variables", "CMDHISTORYFORECOLOR"))) + history_BackGroundColor = QColor(QadVariables.get(QadMsg.translate("Environment variables", "CMDHISTORYBACKCOLOR"))) + self.chronologyEdit.set_Colors(history_ForegroundColor, history_BackGroundColor) + + foregroundColor = QColor(QadVariables.get(QadMsg.translate("Environment variables", "CMDLINEFORECOLOR"))) + backGroundColor = QColor(QadVariables.get(QadMsg.translate("Environment variables", "CMDLINEBACKCOLOR"))) + self.edit.set_Colors(foregroundColor, backGroundColor) + + upperKeyWord_ForegroundColor = QColor(QadVariables.get(QadMsg.translate("Environment variables", "CMDLINEOPTCOLOR"))) + KeyWord_BackgroundColor = QColor(QadVariables.get(QadMsg.translate("Environment variables", "CMDLINEOPTBACKCOLOR"))) + highlightKeyWord_BackGroundColor = QColor(QadVariables.get(QadMsg.translate("Environment variables", "CMDLINEOPTHIGHLIGHTEDCOLOR"))) + self.edit.set_keyWordColors(KeyWord_BackgroundColor, upperKeyWord_ForegroundColor, highlightKeyWord_BackGroundColor) + + + def getDockWidgetArea(self): + return self.parentWidget().dockWidgetArea(self) + + def setFocus(self): + self.edit.setFocus() + + def keyPressEvent(self, e): + self.edit.keyPressEvent(e) + + def onTopLevelChanged(self, topLevel): + self.resizeEdits + self.setFocus() + + def hideEvent(self, e): + self.showCmdSuggestWindow(False) + + def toggleShow(self): + if self.isVisible(): + self.hide() + else: + self.show() + + def showEvent(self, e): + QDockWidget.showEvent(self, e) + self.refreshColors() + + def showMsg(self, msg, displayPromptAfterMsg = False, append = True): + self.edit.showMsg(msg, displayPromptAfterMsg, append) + + def showInputMsg(self, inputMsg = None, inputType = QadInputTypeEnum.COMMAND, \ + default = None, keyWords = "", inputMode = QadInputModeEnum.NONE): + # il valore di default del parametro di una funzione non può essere una traduzione + # perché lupdate.exe non lo riesce ad interpretare + self.edit.showInputMsg(inputMsg, inputType, default, keyWords, inputMode) + + + def showErr(self, err): + self.edit.showErr(err) + + + def showMsgOnChronologyEdit(self, msg): + if self.chronologyEdit is not None: + self.chronologyEdit.insertText(msg) + + def isVisibleCmdSuggestWindow(self): + if self.cmdSuggestWindow is None: + return False + return self.cmdSuggestWindow.isVisible() + + + def showCmdSuggestWindow(self, mode = True, filter = ""): + if self.cmdSuggestWindow is None: + return + inputSearchOptions = QadVariables.get(QadMsg.translate("Environment variables", "INPUTSEARCHOPTIONS")) + # inputSearchOptions & QadINPUTSEARCHOPTIONSEnum.ON = Turns on all automated keyboard features when typing at the Command prompt + # inputSearchOptions & QadINPUTSEARCHOPTIONSEnum.DISPLAY_LIST = Displays a list of suggestions as keystrokes are entered + if inputSearchOptions & QadINPUTSEARCHOPTIONSEnum.ON and inputSearchOptions & QadINPUTSEARCHOPTIONSEnum.DISPLAY_LIST: + if mode == True: + if self.cmdSuggestWindow.setFilter(filter) == 0: + self.cmdSuggestWindow.show(False) + return + + dataHeight = self.cmdSuggestWindow.getDataHeight() + if dataHeight > 0: + self.cmdSuggestWindow.cmdNamesListView.setMinimumHeight(self.cmdSuggestWindow.cmdNamesListView.sizeHintForRow(0)) + + if self.isFloating(): + ptUp = self.edit.mapToGlobal(QPoint(0,0)) + spaceUp = ptUp.y() if ptUp.y() - dataHeight < 0 else dataHeight + + ptDown = QPoint(ptUp.x(), ptUp.y() + self.edit.height()) + rect = QApplication.desktop().screenGeometry() + spaceDown = rect.height() - ptDown.y() if ptDown.y() + dataHeight > rect.height() else dataHeight + + # verifico se c'è più spazio sopra o sotto la finestra + if spaceUp > spaceDown: + pt = QPoint(ptUp.x(), ptUp.y() - spaceUp) + dataHeight = spaceUp + else: + pt = QPoint(ptDown.x(), ptDown.y()) + dataHeight = spaceDown + elif self.getDockWidgetArea() == Qt.BottomDockWidgetArea: + pt = self.edit.mapToGlobal(QPoint(0,0)) + if pt.y() - dataHeight < 0: + dataHeight = pt.y() + pt.setY(pt.y() - dataHeight) + elif self.getDockWidgetArea() == Qt.TopDockWidgetArea: + pt = self.edit.mapToGlobal(QPoint(0,0)) + pt.setY(pt.y() + self.edit.height()) + rect = QApplication.desktop().screenGeometry() + if pt.y() + dataHeight > rect.height(): + dataHeight = rect.height() - pt.y() + + if pt.x() < 0: + pt.setX(0) + + self.cmdSuggestWindow.move(pt) + self.cmdSuggestWindow.resize(200, dataHeight) + + self.cmdSuggestWindow.show(mode) + else: + self.cmdSuggestWindow.show(False) + + + def showEvaluateMsg(self, msg = None, append = True): + self.edit.showEvaluateMsg(msg, append) + + def getCurrMsg(self): + return self.edit.getCurrMsg() + + def updateHistory(self, command): + return self.edit.updateHistory(command) + + def runCommand(self, cmd): + self.plugin.runCommand(cmd) + + def continueCommand(self, cmd): + self.plugin.continueCommandFromTextWindow(cmd) + + def abortCommand(self): + self.plugin.abortCommand() + + def clearCurrentObjsSelection(self): + self.plugin.clearCurrentObjsSelection() + + def isValidCommand(self, cmd): + return self.plugin.isValidCommand(cmd) + + def getCommandNames(self): + return self.plugin.getCommandNames() + + def getCommandObj(self, cmdName): + return self.plugin.getCommandObj(cmdName) + + def isValidEnvVariable(self, variable): + return self.plugin.isValidEnvVariable(variable) + + def forceCommandMapToolSnapTypeOnce(self, snapType, snapParams = None): + return self.plugin.forceCommandMapToolSnapTypeOnce(snapType, snapParams) + + def forceCommandMapToolM2P(self): + return self.plugin.forceCommandMapToolM2P() + + def toggleOsMode(self): + return self.plugin.toggleOsMode() + + def toggleOrthoMode(self): + return self.plugin.toggleOrthoMode() + + def togglePolarMode(self): + return self.plugin.togglePolarMode() + + def toggleObjectSnapTracking(self): + return self.plugin.toggleObjectSnapTracking() + + def getLastPoint(self): + return self.plugin.lastPoint + + def setLastPoint(self, pt): + return self.plugin.setLastPoint(pt) + + def getCurrenPointFromCommandMapTool(self): + return self.plugin.getCurrenPointFromCommandMapTool() + + def resizeEdits(self): + if self.edit is None or self.chronologyEdit is None: + return + + rect = self.rect() + h = rect.height() + w = rect.width() + + editHeight = self.edit.getOptimalHeight() + if editHeight > h: + editHeight = h + chronologyEditHeight = h - editHeight + if not self.isFloating(): + offsetY = 20 + chronologyEditHeight = chronologyEditHeight - offsetY + else: + offsetY = 0 + + if chronologyEditHeight < 0: + chronologyEditHeight = 0 + + self.chronologyEdit.move(0, offsetY) + self.chronologyEdit.resize(w, chronologyEditHeight) + self.chronologyEdit.ensureCursorVisible() + + self.edit.resize(w, editHeight) + self.edit.move(0, chronologyEditHeight + offsetY) + self.edit.ensureCursorVisible() + + + def resizeEvent(self, e): + if self: + self.resizeEdits() + self.cmdSuggestWindow.resizeEvent(e) + + +# =============================================================================== +# QadChronologyEdit +# =============================================================================== +class QadChronologyEdit(QTextEdit): + + def __init__(self, parent): + QTextEdit.__init__(self, parent) + + self.set_Colors() + self.setReadOnly(True) + self.setMinimumSize(0, 1) + + + # ============================================================================ + # set_Colors + # ============================================================================ + def set_Colors(self, foregroundColor = Qt.black, backGroundColor = Qt.lightGray): + f = QColor(foregroundColor) + b = QColor(backGroundColor) + rgbStrForeColor = "rgb({0},{1},{2})" + rgbStrForeColor = rgbStrForeColor.format(str(f.red()), str(f.green()), str(f.blue())) + rgbStrBackColor = "rgb({0},{1},{2})" + rgbStrBackColor = rgbStrBackColor.format(str(b.red()), str(b.green()), str(b.blue())) + + fmt = "color: " + rgbStrForeColor + ";" + \ + "background-color: " + rgbStrBackColor + ";" + \ + "selection-color: " + rgbStrBackColor + ";" + \ + "selection-background-color: " + rgbStrForeColor + ";" + self.setStyleSheet(fmt) + + + # ============================================================================ + # insertText + # ============================================================================ + def insertText(self, txt): + cursor = self.textCursor() + for line in txt.split('\n'): + if len(line) > 0: # to avoid one more empty line + cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor) # fine documento + self.setTextCursor(cursor) + self.insertPlainText('\n' + line) + cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor) # fine documento + self.setTextCursor(cursor) + self.ensureCursorVisible() + + +# =============================================================================== +# QadEdit +# =============================================================================== +class QadEdit(QTextEdit): + PROMPT, KEY_WORDS = range(2) + + def __init__(self, parent, chronologyEdit): + QTextEdit.__init__(self, parent) + + self.currentPrompt = "" + self.currentPromptLength = 0 + + self.inputType = QadInputTypeEnum.COMMAND + self.default = None + self.inputMode = QadInputModeEnum.NONE + + self.setTextInteractionFlags(Qt.TextEditorInteraction) + self.setMinimumSize(30, 21) + self.setUndoRedoEnabled(False) + self.setAcceptRichText(False) + self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + + self.historyIndex = 0 + + # stringa contenente le parole chiave separate da "/". + # la stringa può contenere il carattere speciale "_" per separare le parole chiave + # in lingua locale da quelle in inglese (es. "Si/No/Altra opzione_Yes/No/Other option") + self.englishKeyWords = [] # parole chiave in inglese + self.cmdOptionPosList = [] # lista delle posizioni delle opzioni del comando corrente + self.currentCmdOptionPos = None + + self.upperKeyWordForegroundColor = Qt.blue + self.keyWordBackGroundColor = QColor(210, 210, 210) + self.keyWordHighlightBackGroundColor = Qt.gray + + self.tcf_normal = QTextCharFormat() + self.tcf_keyWord = QTextCharFormat() + self.tcf_upperKeyWord = QTextCharFormat() + self.tcf_highlightKeyWord = QTextCharFormat() + self.tcf_highlightUpperKeyWord = QTextCharFormat() + + self.set_Colors() + self.set_keyWordColors() + + self.setMouseTracking(True) + self.textChanged.connect(self.onTextChanged) + + self.timerForCmdSuggestWindow = QTimer() + self.timerForCmdSuggestWindow.setSingleShot(True) + self.timerForCmdAutoComplete = QTimer() + self.timerForCmdAutoComplete.setSingleShot(True) + + + # ============================================================================ + # set_Colors + # ============================================================================ + def set_Colors(self, foregroundColor = Qt.black, backGroundColor = Qt.white): + f = QColor(foregroundColor) + b = QColor(backGroundColor) + rgbStrForeColor = "rgb({0},{1},{2})" + rgbStrForeColor = rgbStrForeColor.format(str(f.red()), str(f.green()), str(f.blue())) + rgbStrBackColor = "rgb({0},{1},{2})" + rgbStrBackColor = rgbStrBackColor.format(str(b.red()), str(b.green()), str(b.blue())) + + fmt = "color: " + rgbStrForeColor + ";" + \ + "background-color: " + rgbStrBackColor + ";" + \ + "selection-color: " + rgbStrBackColor + ";" + \ + "selection-background-color: " + rgbStrForeColor + ";" + self.setStyleSheet(fmt) + + self.tcf_normal.setForeground(foregroundColor) + self.tcf_normal.setBackground(backGroundColor) + self.tcf_normal.setFontWeight(QFont.Normal) + + + # ============================================================================ + # set_keyWordColors + # ============================================================================ + def set_keyWordColors(self, backGroundColor = QColor(210, 210, 210), upperKeyWord_ForegroundColor = Qt.blue, \ + highlightKeyWord_BackGroundColor = Qt.gray): + self.tcf_keyWord.setBackground(backGroundColor) + self.tcf_upperKeyWord.setForeground(upperKeyWord_ForegroundColor) + self.tcf_upperKeyWord.setBackground(backGroundColor) + self.tcf_upperKeyWord.setFontWeight(QFont.Bold) + + self.tcf_highlightKeyWord.setBackground(highlightKeyWord_BackGroundColor) + self.tcf_highlightUpperKeyWord.setForeground(upperKeyWord_ForegroundColor) + self.tcf_highlightUpperKeyWord.setBackground(highlightKeyWord_BackGroundColor) + self.tcf_highlightUpperKeyWord.setFontWeight(QFont.Bold) + + + def setFormat(self, start, count, fmt): # 1-indexed + if count == 0: + return + cursor = QTextCursor(self.textCursor()) + cursor.movePosition(QTextCursor.Start, QTextCursor.MoveAnchor) # inizio documento + cursor.movePosition(QTextCursor.Right, QTextCursor.MoveAnchor, start) + cursor.movePosition(QTextCursor.Right, QTextCursor.KeepAnchor, count) + cursor.setCharFormat(fmt); + self.setCurrentCharFormat(self.tcf_normal) + + + def highlightKeyWords(self): + lastBlock = self.document().lastBlock() + txt = lastBlock.text() + size = len(txt) + + # messaggio + "[" + opz1 + "/" + opz2 + "]" + i = txt.find("[") + final = txt.rfind("]") + if i >= 0 and final > i: # se ci sono opzioni + i = i + 1 + pos = lastBlock.position() + i + while i < final: + if txt[i] != "/": + # se c'é un'opzione corrente deve essere evidenziata in modo diverso + if self.currentCmdOptionPos is not None and \ + pos >= self.currentCmdOptionPos.initialPos and \ + pos <= self.currentCmdOptionPos.finalPos : + if txt[i].isupper(): + self.setFormat(pos, 1, self.tcf_highlightUpperKeyWord) + else: + self.setFormat(pos, 1, self.tcf_highlightKeyWord) + else: + if txt[i].isupper(): + self.setFormat(pos, 1, self.tcf_upperKeyWord) + else: + self.setFormat(pos, 1, self.tcf_keyWord) + i = i + 1 + pos = pos + 1 + + + def isCursorInEditionZone(self, newPos = None): + cursor = self.textCursor() + if newPos is None: + pos = cursor.position() + else: + pos = newPos + block = self.document().lastBlock() + last = block.position() + self.currentPromptLength + return pos >= last + + + def currentCommand(self): + block = self.textCursor().block() + text = block.text() + return text[self.currentPromptLength:] + + + def getTextUntilPrompt(self): + cursor = self.textCursor() + text = cursor.block().text() + return text[self.currentPromptLength : cursor.position()] + + + def showMsgOnChronologyEdit(self, msg): + self.parentWidget().showMsgOnChronologyEdit(msg) + + + def showCmdSuggestWindow(self, mode = True, filter = ""): + if mode == False: # se spengo la finestra + self.timerForCmdSuggestWindow.stop() + self.parentWidget().showCmdSuggestWindow(mode, filter) + + + def showCmdAutoComplete(self, filter = ""): + # autocompletamento + self.timerForCmdAutoComplete.stop() + + filterLen = len(filter) + if filterLen < 2: + return + + # autocompletamento + inputSearchOptions = QadVariables.get(QadMsg.translate("Environment variables", "INPUTSEARCHOPTIONS")) + + # inputSearchOptions & QadINPUTSEARCHOPTIONSEnum.ON = Turns on all automated keyboard features when typing at the Command prompt + # inputSearchOptions & QadINPUTSEARCHOPTIONSEnum.AUTOCOMPLETE = Automatically appends suggestions as each keystroke is entered after the second keystroke. + if inputSearchOptions & QadINPUTSEARCHOPTIONSEnum.ON and inputSearchOptions & QadINPUTSEARCHOPTIONSEnum.AUTOCOMPLETE: + cmdName, qty = self.parentWidget().plugin.getMoreUsedCmd(filter) + self.appendCmdTextForAutoComplete(cmdName, filterLen) + + + def appendCmdTextForAutoComplete(self, cmdName, filterLen): + cursor = self.textCursor() + #cursor.movePosition(QTextCursor.End, QTextCursor.KeepAnchor) + self.setTextCursor(cursor) + if filterLen < len(cmdName): # se c'è qualcosa da aggiungere + self.insertPlainText(cmdName[filterLen:]) + else: + self.insertPlainText("") + cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor) + cursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor, len(cmdName) - filterLen) + self.setTextCursor(cursor) + + + def showMsg(self, msg, displayPromptAfterMsg = False, append = True): + if len(msg) > 0: + cursor = self.textCursor() + sep = msg.rfind("\n") + if sep >= 0: + self.showMsgOnChronologyEdit(self.toPlainText() + msg[0:sep]) + newMsg = msg[sep + 1:] + cursor.movePosition(QTextCursor.Start, QTextCursor.MoveAnchor) + cursor.movePosition(QTextCursor.End, QTextCursor.KeepAnchor) + else: + if append == True: + cursor = self.textCursor() + cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor) # fine documento + newMsg = msg + else: + cursor.movePosition(QTextCursor.Start, QTextCursor.MoveAnchor) + cursor.movePosition(QTextCursor.End, QTextCursor.KeepAnchor) + newMsg = self.currentPrompt + msg + + self.setTextCursor(cursor) + self.insertPlainText(newMsg) + + if self.inputType & QadInputTypeEnum.KEYWORDS: + self.textCursor().block().setUserState(QadEdit.KEY_WORDS) + self.setCmdOptionPosList() # inizializzo la lista delle posizioni delle keyWords + self.highlightKeyWords() + else: + self.textCursor().block().setUserState(QadEdit.PROMPT) + del self.cmdOptionPosList[:] # svuoto la lista delle posizioni delle keyWords + + if displayPromptAfterMsg: + self.displayPrompt() # ripete il prompt + + + def showErr(self, err): + self.showMsg(err, True) # ripete il prompt + # se esiste un maptool con l'input dinamico attivo + mt = self.parentWidget().plugin.getCurrentMapTool() + if (mt is not None) and mt.getDynamicInput().isVisible: + mt.getDynamicInput().showErr(err) + + + def displayPrompt(self, prompt = None): + if prompt is not None: + self.currentPrompt = prompt + self.currentPromptLength = len(self.currentPrompt) + self.showMsg("\n" + self.currentPrompt) + + + def displayKeyWordsPrompt(self, prompt = None): + if prompt is not None: + self.currentPrompt = prompt + self.currentPromptLength = len(self.currentPrompt) + self.showMsg("\n" + self.currentPrompt) + + + def showNextCmd(self): + # mostra il comando successivo nella lista dei comandi usati + cmdsHistory = self.parentWidget().plugin.cmdsHistory + cmdsHistoryLen = len(cmdsHistory) + if self.historyIndex < cmdsHistoryLen and cmdsHistoryLen > 0: + self.historyIndex += 1 + if self.historyIndex < cmdsHistoryLen: + # displayPromptAfterMsg = False, append = True + self.showMsg(cmdsHistory[self.historyIndex], False, False) # sostituisce il testo dopo il prompt + + + def showPreviousCmd(self): + # mostra il comando precedente nella lista dei comandi usati + cmdsHistory = self.parentWidget().plugin.cmdsHistory + cmdsHistoryLen = len(cmdsHistory) + if self.historyIndex > 0 and cmdsHistoryLen > 0: + self.historyIndex -= 1 + if self.historyIndex < cmdsHistoryLen: + # displayPromptAfterMsg = False, append = True + self.showMsg(cmdsHistory[self.historyIndex], False, False) # sostituisce il testo dopo il prompt + + + def showLastCmd(self): + # mostra e ritorna l'ultimo comando nella lista dei comandi usati + cmdsHistory = self.parentWidget().plugin.cmdsHistory + cmdsHistoryLen = len(cmdsHistory) + if cmdsHistoryLen > 0: + self.showMsg(cmdsHistory[cmdsHistoryLen - 1]) + return cmdsHistory[cmdsHistoryLen - 1] + else: + return "" + + + def showInputMsg(self, inputMsg = None, inputType = QadInputTypeEnum.COMMAND, \ + default = None, keyWords = "", inputMode = QadInputModeEnum.NONE): + # il valore di default del parametro di una funzione non può essere una traduzione + # perché lupdate.exe non lo riesce ad interpretare + if inputMsg is None: + inputMsg = QadMsg.translate("QAD", "Command: ") + + cursor = self.textCursor() + actualPos = cursor.position() + + self.inputType = inputType + self.default = default + self.inputMode = inputMode + if inputType & QadInputTypeEnum.KEYWORDS and (keyWords is not None): + # carattere separatore tra le parole chiave in lingua locale e quelle in inglese + localEnglishKeyWords = keyWords.split("_") + self.keyWords = localEnglishKeyWords[0].split("/") # carattere separatore delle parole chiave + if len(localEnglishKeyWords) > 1: + self.englishKeyWords = localEnglishKeyWords[1].split("/") # carattere separatore delle parole chiave + else: + del self.englishKeyWords[:] + self.displayKeyWordsPrompt(inputMsg) + else: + self.displayPrompt(inputMsg) + + # se esiste un maptool con l'input dinamico attivo + mt = self.parentWidget().plugin.getCurrentMapTool() + if (mt is not None): + if inputType != QadInputTypeEnum.COMMAND: + # context va inizializzato prima dal comando + mt.getDynamicInput().showInputMsg(inputMsg, inputType, default, keyWords, inputMode) + + return + + + def setCmdOptionPosList(self): + del self.cmdOptionPosList[:] # svuoto la lista + lenKeyWords = len(self.keyWords) + if lenKeyWords == 0 or len(self.currentPrompt) == 0: + return + # le opzioni sono racchiuse in parentesi quadre e separate tra loro da / + prompt = self.currentPrompt + initialPos = prompt.find("[", 0) + finalDelimiter = prompt.find("]", initialPos) + if initialPos == -1 or finalDelimiter == -1: + return + i = 0 + while i < lenKeyWords: + keyWord = self.keyWords[i] + initialPos = prompt.find(keyWord, initialPos + 1, finalDelimiter) + if initialPos >= 0: + finalPos = initialPos + len(keyWord) + self.cmdOptionPosList.append(QadCmdOptionPos(keyWord, initialPos, finalPos)) + initialPos = prompt.find("/", finalPos) + if initialPos == -1: + return + + i = i + 1 + + + def getCmdOptionPosUnderMouse(self, pos): + cursor = self.cursorForPosition(pos) + pos = cursor.position() + for cmdOptionPos in self.cmdOptionPosList: + if cmdOptionPos.isSelected(pos): + return cmdOptionPos + return None + + + def mouseMoveEvent(self, event): + cursor = self.cursorForPosition(event.pos()) + pos = cursor.position() + if self.isCursorInEditionZone(pos): + QTextEdit.mouseMoveEvent(self, event) + + self.currentCmdOptionPos = self.getCmdOptionPosUnderMouse(event.pos()) + self.highlightKeyWords() + self.currentCmdOptionPos = None + + + def mouseDoubleClickEvent(self, event): + cursor = self.cursorForPosition(event.pos()) + pos = cursor.position() + if self.isCursorInEditionZone(pos): + QTextEdit.mouseDoubleClickEvent(self, event) + + + def mousePressEvent(self, event): + cursor = self.cursorForPosition(event.pos()) + pos = cursor.position() + if self.isCursorInEditionZone(pos): + QTextEdit.mousePressEvent(self, event) + + + def mouseReleaseEvent(self, event): + QTextEdit.mouseReleaseEvent(self, event) + # se sono sull'ultima riga + if self.textCursor().position() >= self.document().lastBlock().position(): + if event.button() == Qt.LeftButton: + cmdOptionPos = self.getCmdOptionPosUnderMouse(event.pos()) + if cmdOptionPos is not None: + # estraggo la parte maiuscola della parola chiave + # questo serve per evitare che ad es. l'opzione "End" si confonda con osnap "end" + upperPart = qad_utils.extractUpperCaseSubstr(cmdOptionPos.name) + self.showEvaluateMsg(upperPart, False) + + + def updateHistory(self, command): + self.parentWidget().plugin.updateCmdsHistory(command) + cmdsHistory = self.parentWidget().plugin.cmdsHistory + self.historyIndex = len(cmdsHistory) + + + def keyPressEvent(self, e): + if self.parentWidget().plugin.shortCutManagement(e): # se è stata gestita una sequenza di tasti scorciatoia + return + + cursor = self.textCursor() + + if self.inputType & QadInputTypeEnum.COMMAND: + # if Up or Down is pressed + if self.parentWidget().isVisibleCmdSuggestWindow() and \ + (e.key() == Qt.Key_Down or e.key() == Qt.Key_Up or e.key() == Qt.Key_PageDown or e.key() == Qt.Key_PageUp or + e.key() == Qt.Key_End or e.key() == Qt.Key_Home): + self.parentWidget().cmdSuggestWindow.keyPressEvent(e) + return + else: # nascondo la finestra di suggerimento + self.showCmdSuggestWindow(False) + + #QMessageBox.warning(self.plugIn.TextWindow, "titolo" , 'msg') + + + # if the cursor isn't in the edit zone, don't do anything except Ctrl+C + if not self.isCursorInEditionZone(): + if e.modifiers() & Qt.ControlModifier or e.modifiers() & Qt.MetaModifier: + if e.key() == Qt.Key_C or e.key() == Qt.Key_A: + QTextEdit.keyPressEvent(self, e) + else: + # all other keystrokes get sent to the input line + cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor) + self.setTextCursor(cursor) + QTextEdit.keyPressEvent(self, e) + + self.setTextCursor(cursor) + self.ensureCursorVisible() + else: + # if Return is pressed, then perform the commands + if e.key() == Qt.Key_Return or e.key == Qt.Key_Enter: + self.entered() + # if Space is pressed during command request or value not string request + elif e.key() == Qt.Key_Space and \ + (self.inputType & QadInputTypeEnum.COMMAND or not(self.inputType & QadInputTypeEnum.STRING)): + self.entered() + return + # if Up or Down is pressed + elif e.key() == Qt.Key_Down: + self.showNextCmd() + return # per non far comparire la finestra di suggerimento + elif e.key() == Qt.Key_Up: + self.showPreviousCmd() + return # per non far comparire la finestra di suggerimento + # if backspace is pressed, delete until we get to the prompt + elif e.key() == Qt.Key_Backspace: + if not cursor.hasSelection() and cursor.columnNumber() == self.currentPromptLength: + return + QTextEdit.keyPressEvent(self, e) + # if the left key is pressed, move left until we get to the prompt + elif e.key() == Qt.Key_Left and cursor.position() > self.document().lastBlock().position() + self.currentPromptLength: + anchor = QTextCursor.KeepAnchor if e.modifiers() & Qt.ShiftModifier else QTextCursor.MoveAnchor + move = QTextCursor.WordLeft if e.modifiers() & Qt.ControlModifier or e.modifiers() & Qt.MetaModifier else QTextCursor.Left + cursor.movePosition(move, anchor) + # use normal operation for right key + elif e.key() == Qt.Key_Right: + anchor = QTextCursor.KeepAnchor if e.modifiers() & Qt.ShiftModifier else QTextCursor.MoveAnchor + move = QTextCursor.WordRight if e.modifiers() & Qt.ControlModifier or e.modifiers() & Qt.MetaModifier else QTextCursor.Right + cursor.movePosition(move, anchor) + # if home is pressed, move cursor to right of prompt + elif e.key() == Qt.Key_Home: + anchor = QTextCursor.KeepAnchor if e.modifiers() & Qt.ShiftModifier else QTextCursor.MoveAnchor + cursor.movePosition(QTextCursor.StartOfBlock, anchor, 1) + cursor.movePosition(QTextCursor.Right, anchor, self.currentPromptLength) + # use normal operation for end key + elif e.key() == Qt.Key_End: + anchor = QTextCursor.KeepAnchor if e.modifiers() & Qt.ShiftModifier else QTextCursor.MoveAnchor + cursor.movePosition(QTextCursor.EndOfBlock, anchor, 1) + # use normal operation for all remaining keys + else: + QTextEdit.keyPressEvent(self, e) + + self.setTextCursor(cursor) + self.ensureCursorVisible() + + if self.inputType & QadInputTypeEnum.COMMAND: + # leggo il tempo di ritardo in msec + inputSearchDelay = QadVariables.get(QadMsg.translate("Environment variables", "INPUTSEARCHDELAY")) + + # lista suggerimento dei comandi simili + currMsg = self.getCurrMsg() + shot1 = lambda: self.showCmdSuggestWindow(True, currMsg) + + del self.timerForCmdSuggestWindow + self.timerForCmdSuggestWindow = QTimer() + self.timerForCmdSuggestWindow.setSingleShot(True) + self.timerForCmdSuggestWindow.timeout.connect(shot1) + self.timerForCmdSuggestWindow.start(inputSearchDelay) + + if e.text().isalnum(): # autocompletamento se è stato premuto un tasto alfanumerico + self.textUntilPrompt = self.getTextUntilPrompt() + shot2 = lambda: self.showCmdAutoComplete(self.textUntilPrompt) + del self.timerForCmdAutoComplete + self.timerForCmdAutoComplete = QTimer() + self.timerForCmdAutoComplete.setSingleShot(True) + + self.timerForCmdAutoComplete.timeout.connect(shot2) + self.timerForCmdAutoComplete.start(inputSearchDelay) + + + def entered(self): + if self.inputType & QadInputTypeEnum.COMMAND: + self.showCmdSuggestWindow(False) # hide suggestion window + + cmdsHistory = self.parentWidget().plugin.cmdsHistory + self.historyIndex = len(cmdsHistory) + cursor = self.textCursor() + cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor) + self.setTextCursor(cursor) + self.evaluate(unicode(self.currentCommand())) + + + def showEvaluateMsg(self, msg = None, append = True): + """ + mostra e valuta il messaggio msg se diverso da None altrimenti usa il messaggio corrente + """ + if msg is not None: + self.showMsg(msg, False, append) + self.entered() + + def getCurrMsg(self): + """ + restituisce il messaggio già presente nella finestra di testo + """ + cursor = self.textCursor() + prevPos = cursor.position() + cursor.movePosition(QTextCursor.End, QTextCursor.MoveAnchor) + self.setTextCursor(cursor) + msg = unicode(self.currentCommand()) + cursor.setPosition(prevPos) + self.setTextCursor(cursor) + return msg + + + def getInvalidInputMsg(self): + """ + restituisce il messaggio di input non valido + """ + if self.inputType & QadInputTypeEnum.POINT2D or \ + self.inputType & QadInputTypeEnum.POINT3D: + if self.inputType & QadInputTypeEnum.KEYWORDS and \ + (self.inputType & QadInputTypeEnum.FLOAT or self.inputType & QadInputTypeEnum.ANGLE): + return QadMsg.translate("QAD", "\nEnter a point, a real number or a keyword.\n") + elif self.inputType & QadInputTypeEnum.KEYWORDS: + return QadMsg.translate("QAD", "\nEnter a point or a keyword.\n") + elif self.inputType & QadInputTypeEnum.FLOAT or self.inputType & QadInputTypeEnum.ANGLE: + return QadMsg.translate("QAD", "\nEnter a point or a real number.\n") + else: + return QadMsg.translate("QAD", "\nPoint not valid.\n") + elif self.inputType & QadInputTypeEnum.KEYWORDS: + return QadMsg.translate("QAD", "\nKeyword not valid.\n") + elif self.inputType & QadInputTypeEnum.STRING: + return QadMsg.translate("QAD", "\nString not valid.\n") + elif self.inputType & QadInputTypeEnum.INT: + return QadMsg.translate("QAD", "\nInteger number not valid.\n") + elif self.inputType & QadInputTypeEnum.LONG: + return QadMsg.translate("QAD", "\nLong integer number not valid.\n") + elif self.inputType & QadInputTypeEnum.FLOAT or self.inputType & QadInputTypeEnum.ANGLE: + return QadMsg.translate("QAD", "\nReal number not valid.\n") + elif self.inputType & QadInputTypeEnum.BOOL: + return QadMsg.translate("QAD", "\nBoolean not valid.\n") + else: + return "" + + + def evaluateKeyWords(self, cmd): + # The required portion of the keyword is specified in uppercase characters, + # and the remainder of the keyword is specified in lowercase characters. + # The uppercase abbreviation can be anywhere in the keyword + if cmd == "": # se cmd = "" la funzione find ritorna 0 (no comment) + return None + + if cmd[0] == "_": # versione inglese + keyWord, Msg = qad_utils.evaluateCmdKeyWords(cmd[1:], self.englishKeyWords) + if keyWord is None: + if Msg is not None: + self.showMsg(Msg) + return None + # cerco la corrispondente parola chiave in lingua locale + i = 0 + for k in self.englishKeyWords: + if k == keyWord: + return self.keyWords[i] + i = i + 1 + return None + else: + keyWord, Msg = qad_utils.evaluateCmdKeyWords(cmd, self.keyWords) + if keyWord is None: + if Msg is not None: + self.showMsg(Msg) + return keyWord + + + def evaluate(self, cmd): + #------------------------------------------------------------------------------ + # nome di un comando + #------------------------------------------------------------------------------ + if self.inputType & QadInputTypeEnum.COMMAND: + if cmd == "": + cmd = unicode(self.showLastCmd()) # ripeto ultimo comando + if cmd == "": + return + + if self.parentWidget().isValidCommand(cmd) or self.parentWidget().isValidEnvVariable(cmd): + self.updateHistory(cmd) + self.parentWidget().runCommand(cmd) + else: + msg = QadMsg.translate("QAD", "\nInvalid command \"{0}\".") + self.showErr(msg.format(cmd.encode('utf-8','ignore').decode('utf-8'))) # ripete il prompt + return + + if cmd == "": + if self.default is not None: + if type(self.default) == QgsPointXY: + cmd = self.default.toString() + else: + cmd = unicode(self.default) + + if cmd == "" and \ + not (self.inputMode & QadInputModeEnum.NOT_NULL): # permesso input nullo + self.parentWidget().continueCommand(None) + return + + #------------------------------------------------------------------------------ + # punto 2D + #------------------------------------------------------------------------------ + if self.inputType & QadInputTypeEnum.POINT2D: + snapType = str2snapTypeEnum(cmd) + if snapType != -1: + # se é stato forzato uno snap + snapParams = str2snapParams(cmd) + self.parentWidget().forceCommandMapToolSnapTypeOnce(snapType, snapParams) + self.showMsg(QadMsg.translate("QAD", "\n(temporary snap)\n"), True) # ripeti il prompt + return + + # valuto il caso di "punto medio tra 2 punti" + if cmd.upper() == QadMsg.translate("Snap", "M2P") or cmd.upper() == "_M2P": + self.parentWidget().forceCommandMapToolM2P() + return; + + if (self.inputType & QadInputTypeEnum.INT) or \ + (self.inputType & QadInputTypeEnum.LONG) or \ + (self.inputType & QadInputTypeEnum.FLOAT) or \ + (self.inputType & QadInputTypeEnum.ANGLE) or \ + (self.inputType & QadInputTypeEnum.BOOL): + oneNumberAllowed = False + else: + oneNumberAllowed = True + + pt = qad_utils.str2QgsPoint(cmd, \ + self.parentWidget().getLastPoint(), \ + self.parentWidget().getCurrenPointFromCommandMapTool(), \ + oneNumberAllowed) + + if pt is not None: + self.parentWidget().setLastPoint(pt) + self.parentWidget().continueCommand(pt) + return + + #------------------------------------------------------------------------------ + # punto 3D + #------------------------------------------------------------------------------ + if self.inputType & QadInputTypeEnum.POINT3D: # punto + pass + + #------------------------------------------------------------------------------ + # una parola chiave + #------------------------------------------------------------------------------ + if self.inputType & QadInputTypeEnum.KEYWORDS: + keyWord = self.evaluateKeyWords(cmd) + + if keyWord is not None: + self.parentWidget().continueCommand(keyWord) + return + + #------------------------------------------------------------------------------ + # una stringa + #------------------------------------------------------------------------------ + if self.inputType & QadInputTypeEnum.STRING: + if cmd is not None: + self.parentWidget().continueCommand(cmd) + return + + #------------------------------------------------------------------------------ + # un numero intero + #------------------------------------------------------------------------------ + if self.inputType & QadInputTypeEnum.INT: + num = qad_utils.str2int(cmd) + if num is not None: + if num == 0 and (self.inputMode & QadInputModeEnum.NOT_ZERO): # non permesso valore = 0 + num = None + elif num < 0 and (self.inputMode & QadInputModeEnum.NOT_NEGATIVE): # non permesso valore < 0 + num = None + elif num > 0 and (self.inputMode & QadInputModeEnum.NOT_POSITIVE): # non permesso valore > 0 + num = None + + if num is not None: + self.parentWidget().continueCommand(int(num)) + return + + #------------------------------------------------------------------------------ + # un numero lungo + #------------------------------------------------------------------------------ + if self.inputType & QadInputTypeEnum.LONG: + num = qad_utils.str2long(cmd) + if num is not None: + if num == 0 and (self.inputMode & QadInputModeEnum.NOT_ZERO): # non permesso valore = 0 + num = None + elif num < 0 and (self.inputMode & QadInputModeEnum.NOT_NEGATIVE): # non permesso valore < 0 + num = None + elif num > 0 and (self.inputMode & QadInputModeEnum.NOT_POSITIVE): # non permesso valore > 0 + num = None + + if num is not None: + self.parentWidget().continueCommand(long(num)) + return + + #------------------------------------------------------------------------------ + # un numero reale + #------------------------------------------------------------------------------ + if self.inputType & QadInputTypeEnum.FLOAT or self.inputType & QadInputTypeEnum.ANGLE: + num = qad_utils.str2float(cmd) + if num is not None: + if num == 0 and (self.inputMode & QadInputModeEnum.NOT_ZERO): # non permesso valore = 0 + num = None + elif num < 0 and (self.inputMode & QadInputModeEnum.NOT_NEGATIVE): # non permesso valore < 0 + num = None + elif num > 0 and (self.inputMode & QadInputModeEnum.NOT_POSITIVE): # non permesso valore > 0 + num = None + + if num is not None: + self.parentWidget().continueCommand(num) + return + + #------------------------------------------------------------------------------ + # un valore booleano + #------------------------------------------------------------------------------ + elif self.inputType & QadInputTypeEnum.BOOL: + value = qad_utils.str2bool(cmd) + + if value is not None: + self.parentWidget().continueCommand(value) + return + + self.showMsg(self.getInvalidInputMsg()) + + if self.inputType & QadInputTypeEnum.KEYWORDS: + self.displayKeyWordsPrompt() + else: + self.displayPrompt() + + return + + def getOptimalHeight(self): + fm = QFontMetrics(self.currentFont()) + pixelsWidth = fm.width(QadMsg.translate("QAD", "Command: ")) + pixelsHeight = fm.height() + # + 8 perché la QTextEdit ha un offset verticale sopra e sotto il testo + return max(int(self.document().size().height()), pixelsHeight + 8) + + def onTextChanged(self): + self.parentWidget().resizeEdits() + self.timerForCmdAutoComplete.stop() + + +# =============================================================================== +# QadCmdSuggestWindow +# =============================================================================== +class QadCmdSuggestWindow(QWidget, Ui_QadCmdSuggestWindow, object): + + def __init__(self, parent, editWidget, infoCmds, infoVars): + # lista composta da elementi con: + # , , , + QWidget.__init__(self, parent, Qt.ToolTip) + + self.editWidget = editWidget + self.setupUi(self) + self.setWindowTitle(QadMsg.getQADTitle() + " - " + self.windowTitle()) + self.infoCmds = infoCmds[:] # copio la lista comandi + self.infoVars = infoVars[:] # copio la lista variabili ambiente + self.filter = "" + + def initGui(self): + self.cmdNamesListView = QadCmdSuggestListView(self) + self.cmdNamesListView.setObjectName("QadCmdNamesListView") + self.vboxlayout.addWidget(self.cmdNamesListView) + + def setFocus(self): + self.cmdNamesListView.setFocus() + + def keyPressEvent(self, e): + self.cmdNamesListView.keyPressEvent(e) + + + def inFilteredInfoList(self, filteredInfoList, cmdName): + for filteredInfo in filteredInfoList: + if filteredInfo[0] == cmdName: + return True + return False + + + def getFilteredInfoList(self, infoList): + inputSearchOptions = QadVariables.get(QadMsg.translate("Environment variables", "INPUTSEARCHOPTIONS")) + # inputSearchOptions & QadINPUTSEARCHOPTIONSEnum.ON = Turns on all automated keyboard features when typing at the Command prompt + # inputSearchOptions & QadINPUTSEARCHOPTIONSEnum.DISPLAY_ICON = Displays the icon of the command or system variable, if available. + dispIcons = inputSearchOptions & QadINPUTSEARCHOPTIONSEnum.ON and inputSearchOptions & QadINPUTSEARCHOPTIONSEnum.DISPLAY_ICON + + filteredInfoList = [] + upperFilter = self.filter + if len(upperFilter) > 0: + if filter == "*": # se incomincia per * significa tutti i comandi in lingua locale + # lista composta da elementi con: + # , , , + for info in infoList: + if not self.inFilteredInfoList(filteredInfoList, info[0]): + filteredInfoList.append([info[0], info[2] if dispIcons else None, info[3]]) + else: + if upperFilter[0] == "_": # versione inglese + upperFilter = upperFilter[1:] + # lista composta da elementi con: + # , , , + for info in infoList: + # se "incomincia per" o se "abbastanza simile" + if info[1].upper().find(upperFilter) == 0 or \ + difflib.SequenceMatcher(None, info[1].upper(), upperFilter).ratio() > 0.6: + if not self.inFilteredInfoList(filteredInfoList, "_" + info[1]): + filteredInfoList.append(["_" + info[1], info[2] if dispIcons else None, info[3]]) + else: # versione italiana + # lista composta da elementi con: + # , , , + for info in infoList: + # se "incomincia per" o se "abbastanza simile" + if info[0].upper().find(upperFilter) == 0 or \ + difflib.SequenceMatcher(None, info[0].upper(), upperFilter).ratio() > 0.6: + if not self.inFilteredInfoList(filteredInfoList, info[0]): + filteredInfoList.append([info[0], info[2] if dispIcons else None, info[3]]) + + return filteredInfoList + + + def setFilter(self, filter = ""): + itemList = [] + itemList.extend(self.infoCmds) + + inputSearchOptions = QadVariables.get(QadMsg.translate("Environment variables", "INPUTSEARCHOPTIONS")) + # inputSearchOptions & QadINPUTSEARCHOPTIONSEnum.ON = Turns on all automated keyboard features when typing at the Command prompt + # inputSearchOptions & QadINPUTSEARCHOPTIONSEnum.EXCLUDE_SYS_VAR = Excludes the display of system variables + if inputSearchOptions & QadINPUTSEARCHOPTIONSEnum.ON and (not inputSearchOptions & QadINPUTSEARCHOPTIONSEnum.EXCLUDE_SYS_VAR): + itemList.extend(self.infoVars) + + self.filter = filter.strip().upper() + + # filtro i nomi + filteredInfo = self.getFilteredInfoList(itemList) + + l = len(filteredInfo) + if l > 0: + self.cmdNamesListView.set(filteredInfo) + # seleziono il primo che incomincia per filter + items = self.cmdNamesListView.model.findItems(filter, Qt.MatchStartsWith) + if len(items) > 0: + self.cmdNamesListView.setCurrentIndex(self.cmdNamesListView.model.indexFromItem(items[0])) + + return l + + + def show(self, mode = True): + if mode == True: + inputSearchOptions = QadVariables.get(QadMsg.translate("Environment variables", "INPUTSEARCHOPTIONS")) + # inputSearchOptions & QadINPUTSEARCHOPTIONSEnum.ON = Turns on all automated keyboard features when typing at the Command prompt + # inputSearchOptions & QadINPUTSEARCHOPTIONSEnum.EXCLUDE_SYS_VAR = Excludes the display of system variables + if inputSearchOptions & QadINPUTSEARCHOPTIONSEnum.ON and (not inputSearchOptions & QadINPUTSEARCHOPTIONSEnum.EXCLUDE_SYS_VAR): + self.setVisible(True) + cmdName = self.cmdNamesListView.selectionModel().currentIndex().data() + if cmdName is not None: + self.appendCmdTextForAutoComplete(cmdName) + #self.showMsg(cmdName) + else: + self.setVisible(False) + + def getDataHeight(self): + n = self.cmdNamesListView.model.rowCount() + if n == 0: + return 0 + + OffSet = 4 # un pò di spazio in più per mostrare anche l'icona dei comandi + return self.cmdNamesListView.sizeHintForRow(0) * n + OffSet + + + def showEvaluateMsg(self, cmd = None, append = True): + self.show(False) + self.editWidget.setFocus() + self.editWidget.showEvaluateMsg(cmd, append) + + def showMsg(self, cmd): + # sostituisco il testo con il nuovo comando e riporto il cursore nella posizione di prima + parent = self.parentWidget() + cursor = self.editWidget.textCursor() + prevPos = cursor.position() + self.editWidget.showMsg(cmd, False, False) + cursor.setPosition(prevPos) + self.editWidget.setTextCursor(cursor) + self.editWidget.setFocus() + + def keyPressEventToParent(self, e): + self.editWidget.keyPressEvent(e) + + + def appendCmdTextForAutoComplete(self, cmdName): + if self.filter == "*": + self.editWidget.appendCmdTextForAutoComplete(cmdName, 0) + else: + self.editWidget.appendCmdTextForAutoComplete(cmdName, len(self.filter)) + + +# =============================================================================== +# QadCmdListView +# =============================================================================== +class QadCmdSuggestListView(QListView): + + def __init__(self, parent): + QListView.__init__(self, parent) + + self.setViewMode(QListView.ListMode) + self.setSelectionBehavior(QAbstractItemView.SelectItems) + self.setUniformItemSizes(True) + self.model = QStandardItemModel() + self.setModel(self.model) + self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + + def set(self, filteredCmdNames): + # lista composta da elementi con , , + self.model.clear() + + for infoCmd in filteredCmdNames: + cmdName = infoCmd[0] + cmdIcon = infoCmd[1] + cmdNote = infoCmd[2] + if cmdIcon is None: + item = QStandardItem(cmdName) + else: + item = QStandardItem(cmdIcon, cmdName) + + if cmdNote is not None and len(cmdNote) > 0: + item.setToolTip(cmdNote) + + item.setEditable(False) + self.model.appendRow(item) + + self.model.sort(0) + + + def keyPressEvent(self, e): + if e.key() == Qt.Key_Up or e.key() == Qt.Key_Down or \ + e.key() == Qt.Key_PageUp or e.key() == Qt.Key_PageDown or \ + e.key() == Qt.Key_End or e.key() == Qt.Key_Home: + QListView.keyPressEvent(self, e) + cmdName = self.selectionModel().currentIndex().data() + self.parentWidget().showMsg(cmdName) + # if Return is pressed, then perform the commands + elif e.key() == Qt.Key_Return or e.key == Qt.Key_Enter: + cmd = self.selectionModel().currentIndex().data() + if cmd is not None: + self.parentWidget().showMsg(cmd) + self.parentWidget().showEvaluateMsg() + else: + self.parentWidget().keyPressEventToParent(e) + + + def mouseReleaseEvent(self, e): + cmd = self.selectionModel().currentIndex().data() +# self.parentWidget().showMsg(cmd) +# self.parentWidget().showEvaluateMsg() + self.parentWidget().showEvaluateMsg(cmd, False) + diff --git a/qad_tooltip_appearance.ui b/qad_tooltip_appearance.ui new file mode 100644 index 00000000..0d98c436 --- /dev/null +++ b/qad_tooltip_appearance.ui @@ -0,0 +1,377 @@ + + + ToolTipAppearance_Dialog + + + + 0 + 0 + 450 + 366 + + + + + 450 + 366 + + + + + 450 + 366 + + + + Tooltip appearance + + + + + 10 + 20 + 191 + 71 + + + + + + + 10 + 100 + 75 + 23 + + + + Displays the Drawing Window Colors dialog box, where you can specify a color for drafting tooltips and their backgrounds in a specified context. + + + Colors... + + + + + + 10 + 130 + 431 + 51 + + + + Size + + + + + 10 + 20 + 113 + 20 + + + + + + + 0 + 0 + 431 + 51 + + + + Size + + + + + 10 + 20 + 113 + 20 + + + + Specifies a size for tooltips. The default size is 0. Use the slider to make tooltips larger or smaller. + + + + + + 130 + 20 + 291 + 22 + + + + Specifies a size for tooltips. The default size is 0. Use the slider to make tooltips larger or smaller. + + + 1 + + + Qt::Horizontal + + + + + + + + 10 + 190 + 431 + 51 + + + + Transparency + + + + + 130 + 20 + 291 + 22 + + + + Controls the transparency of tooltips. The lower the setting, the less transparent the tooltip. A value of 0 sets the tooltip to opaque. + + + Qt::Horizontal + + + + + + 10 + 20 + 113 + 20 + + + + Controls the transparency of tooltips. The lower the setting, the less transparent the tooltip. A value of 0 sets the tooltip to opaque. + + + + + + + 150 + 330 + 291 + 30 + + + + + + + OK + + + + + + + Cancel + + + + + + + ? + + + + + + + + + 10 + 250 + 431 + 71 + + + + Apply to: + + + + + 10 + 20 + 411 + 17 + + + + Override OS settings for all drafting tooltips + + + + + + 10 + 40 + 411 + 17 + + + + Use settings only for Dynamic Input tooltips + + + + + + + + Button_TooltipColors + clicked() + ToolTipAppearance_Dialog + Button_TooltipColors_Pressed() + + + 72 + 107 + + + 125 + 107 + + + + + Button_Cancel + clicked() + ToolTipAppearance_Dialog + reject() + + + 250 + 273 + + + 273 + 246 + + + + + slider_size + valueChanged(int) + ToolTipAppearance_Dialog + slider_size_moved() + + + 290 + 156 + + + 359 + 123 + + + + + edit_size + textChanged(QString) + ToolTipAppearance_Dialog + edit_size_textChanged() + + + 93 + 157 + + + 11 + 134 + + + + + edit_transparency + textChanged(QString) + ToolTipAppearance_Dialog + edit_transparency_textChanged() + + + 59 + 219 + + + 6 + 224 + + + + + slider_transparency + valueChanged(int) + ToolTipAppearance_Dialog + slider_transparency_moved() + + + 331 + 221 + + + 375 + 189 + + + + + Button_OK + clicked() + ToolTipAppearance_Dialog + ButtonBOX_Accepted() + + + 167 + 270 + + + 113 + 279 + + + + + Button_Help + clicked() + ToolTipAppearance_Dialog + ButtonHELP_Pressed() + + + 345 + 274 + + + 376 + 274 + + + + + + Button_TooltipColors_Pressed() + slider_size_moved() + edit_size_textChanged() + edit_transparency_textChanged() + slider_transparency_moved() + ButtonBOX_Accepted() + ButtonHELP_Pressed() + + diff --git a/qad_tooltip_appearance_ui.py b/qad_tooltip_appearance_ui.py new file mode 100644 index 00000000..0893f90f --- /dev/null +++ b/qad_tooltip_appearance_ui.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file '.\qad_tooltip_appearance.ui' +# +# Created by: PyQt5 UI code generator 5.15.10 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_ToolTipAppearance_Dialog(object): + def setupUi(self, ToolTipAppearance_Dialog): + ToolTipAppearance_Dialog.setObjectName("ToolTipAppearance_Dialog") + ToolTipAppearance_Dialog.resize(450, 366) + ToolTipAppearance_Dialog.setMinimumSize(QtCore.QSize(450, 366)) + ToolTipAppearance_Dialog.setMaximumSize(QtCore.QSize(450, 366)) + self.widget_Preview = QtWidgets.QWidget(ToolTipAppearance_Dialog) + self.widget_Preview.setGeometry(QtCore.QRect(10, 20, 191, 71)) + self.widget_Preview.setObjectName("widget_Preview") + self.Button_TooltipColors = QtWidgets.QPushButton(ToolTipAppearance_Dialog) + self.Button_TooltipColors.setGeometry(QtCore.QRect(10, 100, 75, 23)) + self.Button_TooltipColors.setObjectName("Button_TooltipColors") + self.groupBox = QtWidgets.QGroupBox(ToolTipAppearance_Dialog) + self.groupBox.setGeometry(QtCore.QRect(10, 130, 431, 51)) + self.groupBox.setObjectName("groupBox") + self.lineEdit = QtWidgets.QLineEdit(self.groupBox) + self.lineEdit.setGeometry(QtCore.QRect(10, 20, 113, 20)) + self.lineEdit.setObjectName("lineEdit") + self.groupBox_2 = QtWidgets.QGroupBox(self.groupBox) + self.groupBox_2.setGeometry(QtCore.QRect(0, 0, 431, 51)) + self.groupBox_2.setObjectName("groupBox_2") + self.edit_size = QtWidgets.QLineEdit(self.groupBox_2) + self.edit_size.setGeometry(QtCore.QRect(10, 20, 113, 20)) + self.edit_size.setObjectName("edit_size") + self.slider_size = QtWidgets.QSlider(self.groupBox_2) + self.slider_size.setGeometry(QtCore.QRect(130, 20, 291, 22)) + self.slider_size.setPageStep(1) + self.slider_size.setOrientation(QtCore.Qt.Horizontal) + self.slider_size.setObjectName("slider_size") + self.groupBox_3 = QtWidgets.QGroupBox(ToolTipAppearance_Dialog) + self.groupBox_3.setGeometry(QtCore.QRect(10, 190, 431, 51)) + self.groupBox_3.setObjectName("groupBox_3") + self.slider_transparency = QtWidgets.QSlider(self.groupBox_3) + self.slider_transparency.setGeometry(QtCore.QRect(130, 20, 291, 22)) + self.slider_transparency.setOrientation(QtCore.Qt.Horizontal) + self.slider_transparency.setObjectName("slider_transparency") + self.edit_transparency = QtWidgets.QLineEdit(self.groupBox_3) + self.edit_transparency.setGeometry(QtCore.QRect(10, 20, 113, 20)) + self.edit_transparency.setObjectName("edit_transparency") + self.horizontalLayoutWidget = QtWidgets.QWidget(ToolTipAppearance_Dialog) + self.horizontalLayoutWidget.setGeometry(QtCore.QRect(150, 330, 291, 30)) + self.horizontalLayoutWidget.setObjectName("horizontalLayoutWidget") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget) + self.horizontalLayout.setContentsMargins(0, 0, 0, 0) + self.horizontalLayout.setObjectName("horizontalLayout") + self.Button_OK = QtWidgets.QPushButton(self.horizontalLayoutWidget) + self.Button_OK.setObjectName("Button_OK") + self.horizontalLayout.addWidget(self.Button_OK) + self.Button_Cancel = QtWidgets.QPushButton(self.horizontalLayoutWidget) + self.Button_Cancel.setObjectName("Button_Cancel") + self.horizontalLayout.addWidget(self.Button_Cancel) + self.Button_Help = QtWidgets.QPushButton(self.horizontalLayoutWidget) + self.Button_Help.setObjectName("Button_Help") + self.horizontalLayout.addWidget(self.Button_Help) + self.groupBox_4 = QtWidgets.QGroupBox(ToolTipAppearance_Dialog) + self.groupBox_4.setGeometry(QtCore.QRect(10, 250, 431, 71)) + self.groupBox_4.setObjectName("groupBox_4") + self.radio_for_all_tooltips = QtWidgets.QRadioButton(self.groupBox_4) + self.radio_for_all_tooltips.setGeometry(QtCore.QRect(10, 20, 411, 17)) + self.radio_for_all_tooltips.setObjectName("radio_for_all_tooltips") + self.radio_for_DI_tooltips = QtWidgets.QRadioButton(self.groupBox_4) + self.radio_for_DI_tooltips.setGeometry(QtCore.QRect(10, 40, 411, 17)) + self.radio_for_DI_tooltips.setObjectName("radio_for_DI_tooltips") + + self.retranslateUi(ToolTipAppearance_Dialog) + self.Button_TooltipColors.clicked.connect(ToolTipAppearance_Dialog.Button_TooltipColors_Pressed) # type: ignore + self.Button_Cancel.clicked.connect(ToolTipAppearance_Dialog.reject) # type: ignore + self.slider_size.valueChanged['int'].connect(ToolTipAppearance_Dialog.slider_size_moved) # type: ignore + self.edit_size.textChanged['QString'].connect(ToolTipAppearance_Dialog.edit_size_textChanged) # type: ignore + self.edit_transparency.textChanged['QString'].connect(ToolTipAppearance_Dialog.edit_transparency_textChanged) # type: ignore + self.slider_transparency.valueChanged['int'].connect(ToolTipAppearance_Dialog.slider_transparency_moved) # type: ignore + self.Button_OK.clicked.connect(ToolTipAppearance_Dialog.ButtonBOX_Accepted) # type: ignore + self.Button_Help.clicked.connect(ToolTipAppearance_Dialog.ButtonHELP_Pressed) # type: ignore + QtCore.QMetaObject.connectSlotsByName(ToolTipAppearance_Dialog) + + def retranslateUi(self, ToolTipAppearance_Dialog): + _translate = QtCore.QCoreApplication.translate + ToolTipAppearance_Dialog.setWindowTitle(_translate("ToolTipAppearance_Dialog", "Tooltip appearance")) + self.Button_TooltipColors.setToolTip(_translate("ToolTipAppearance_Dialog", "Displays the Drawing Window Colors dialog box, where you can specify a color for drafting tooltips and their backgrounds in a specified context.")) + self.Button_TooltipColors.setText(_translate("ToolTipAppearance_Dialog", "Colors...")) + self.groupBox.setTitle(_translate("ToolTipAppearance_Dialog", "Size")) + self.groupBox_2.setTitle(_translate("ToolTipAppearance_Dialog", "Size")) + self.edit_size.setToolTip(_translate("ToolTipAppearance_Dialog", "Specifies a size for tooltips. The default size is 0. Use the slider to make tooltips larger or smaller.")) + self.slider_size.setToolTip(_translate("ToolTipAppearance_Dialog", "Specifies a size for tooltips. The default size is 0. Use the slider to make tooltips larger or smaller.")) + self.groupBox_3.setTitle(_translate("ToolTipAppearance_Dialog", "Transparency")) + self.slider_transparency.setToolTip(_translate("ToolTipAppearance_Dialog", "Controls the transparency of tooltips. The lower the setting, the less transparent the tooltip. A value of 0 sets the tooltip to opaque.")) + self.edit_transparency.setToolTip(_translate("ToolTipAppearance_Dialog", "Controls the transparency of tooltips. The lower the setting, the less transparent the tooltip. A value of 0 sets the tooltip to opaque.")) + self.Button_OK.setText(_translate("ToolTipAppearance_Dialog", "OK")) + self.Button_Cancel.setText(_translate("ToolTipAppearance_Dialog", "Cancel")) + self.Button_Help.setText(_translate("ToolTipAppearance_Dialog", "?")) + self.groupBox_4.setTitle(_translate("ToolTipAppearance_Dialog", "Apply to:")) + self.radio_for_all_tooltips.setText(_translate("ToolTipAppearance_Dialog", "Override OS settings for all drafting tooltips")) + self.radio_for_DI_tooltips.setText(_translate("ToolTipAppearance_Dialog", "Use settings only for Dynamic Input tooltips")) diff --git a/qad_trim_cmd.py b/qad_trim_cmd.py deleted file mode 100644 index b6387939..00000000 --- a/qad_trim_cmd.py +++ /dev/null @@ -1,445 +0,0 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - comando TRIM per tagliare o estendere oggetti grafici - - ------------------- - begin : 2013-07-15 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * - - -from qad_getpoint import * -from qad_textwindow import * -from qad_pline_cmd import QadPLINECommandClass -from qad_rectangle_cmd import QadRECTANGLECommandClass -from qad_generic_cmd import QadCommandClass -from qad_msg import QadMsg -import qad_utils -import qad_layer -from qad_ssget_cmd import QadSSGetClass -from qad_dim import QadDimStyles - - -# Classe che gestisce il comando TRIM -class QadTRIMCommandClass(QadCommandClass): - - def instantiateNewCmd(self): - """ istanzia un nuovo comando dello stesso tipo """ - return QadTRIMCommandClass(self.plugIn) - - def getName(self): - return QadMsg.translate("Command_list", "TRIM") - - def getEnglishName(self): - return "TRIM" - - def connectQAction(self, action): - QObject.connect(action, SIGNAL("triggered()"), self.plugIn.runTRIMCommand) - - def getIcon(self): - return QIcon(":/plugins/qad/icons/trim.png") - - def getNote(self): - # impostare le note esplicative del comando - return QadMsg.translate("Command_TRIM", "Trims (or extends) objects to meet the edges of other objects.") - - def __init__(self, plugIn): - QadCommandClass.__init__(self, plugIn) - self.SSGetClass = QadSSGetClass(plugIn) - self.PLINECommand = None - self.RECTANGLECommand = None - self.entitySet = QadEntitySet() # entità da tagliare o estendere - self.limitEntitySet = QadEntitySet() # entità che fanno da limiti - self.edgeMode = QadVariables.get(QadMsg.translate("Environment variables", "EDGEMODE")) - self.defaultValue = None # usato per gestire il tasto dx del mouse - self.nOperationsToUndo = 0 - - def __del__(self): - QadCommandClass.__del__(self) - - def getPointMapTool(self, drawMode = QadGetPointDrawModeEnum.NONE): - if self.step == 3: # quando si é in fase di disegno linea - return self.PLINECommand.getPointMapTool(drawMode) - elif self.step == 4: # quando si é in fase di disegno rettangolo - return self.RECTANGLECommand.getPointMapTool(drawMode) - else: - return QadCommandClass.getPointMapTool(self, drawMode) - - #============================================================================ - # trimFeatures - #============================================================================ - def trimFeatures(self, geom, toExtend): - # geom è in map coordinates - tolerance2ApproxCurve = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) - LineTempLayer = None - self.plugIn.beginEditCommand("Feature extended" if toExtend else "Feature trimmed", \ - self.entitySet.getLayerList()) - - for layerEntitySet in self.entitySet.layerEntitySetList: - layer = layerEntitySet.layer - - for featureId in layerEntitySet.featureIds: - f = qad_utils.getFeatureById(layer, featureId) - if f is None: - continue - # trasformo la geometria nel crs del canvas per lavorare con coordinate piane xy - f_geom = self.layerToMapCoordinates(layer, f.geometry()) - - if geom.type() == QGis.Point: - # ritorna una tupla (, - # - # - # ) - dummy = qad_utils.closestSegmentWithContext(geom.asPoint(), f_geom) - if dummy[1] is not None: - intPts = [dummy[1]] - else: - intPts = qad_utils.getIntersectionPoints(geom, f_geom) - - for intPt in intPts: - if toExtend: - newGeom = qad_utils.extendQgsGeometry(self.plugIn.canvas.mapRenderer().destinationCrs(), f_geom, intPt, \ - self.limitEntitySet, self.edgeMode, \ - tolerance2ApproxCurve) - if newGeom is not None: - # aggiorno la feature con la geometria estesa - extendedFeature = QgsFeature(f) - # trasformo la geometria nel crs del layer - extendedFeature.setGeometry(self.mapToLayerCoordinates(layer, newGeom)) - # plugIn, layer, feature, refresh, check_validity - if qad_layer.updateFeatureToLayer(self.plugIn, layer, extendedFeature, False, False) == False: - self.plugIn.destroyEditCommand() - return - else: # trim - result = qad_utils.trimQgsGeometry(self.plugIn.canvas.mapRenderer().destinationCrs(), f_geom, intPt, \ - self.limitEntitySet, self.edgeMode, \ - tolerance2ApproxCurve) - if result is not None: - line1 = result[0] - line2 = result[1] - atSubGeom = result[2] - if layer.geometryType() == QGis.Line: - updGeom = qad_utils.setSubGeom(f_geom, line1, atSubGeom) - if updGeom is None: - self.plugIn.destroyEditCommand() - return - trimmedFeature1 = QgsFeature(f) - # trasformo la geometria nel crs del layer - trimmedFeature1.setGeometry(self.mapToLayerCoordinates(layer, updGeom)) - # plugIn, layer, feature, refresh, check_validity - if qad_layer.updateFeatureToLayer(self.plugIn, layer, trimmedFeature1, False, False) == False: - self.plugIn.destroyEditCommand() - return - if line2 is not None: - trimmedFeature2 = QgsFeature(f) - # trasformo la geometria nel crs del layer - trimmedFeature2.setGeometry(self.mapToLayerCoordinates(layer, line2)) - # plugIn, layer, feature, coordTransform, refresh, check_validity - if qad_layer.addFeatureToLayer(self.plugIn, layer, trimmedFeature2, None, False, False) == False: - self.plugIn.destroyEditCommand() - return - else: - # aggiungo le linee nei layer temporanei di QAD - if LineTempLayer is None: - LineTempLayer = qad_layer.createQADTempLayer(self.plugIn, QGis.Line) - self.plugIn.addLayerToLastEditCommand("Feature trimmed", LineTempLayer) - - lineGeoms = [line1] - if line2 is not None: - lineGeoms.append(line2) - - # trasformo la geometria in quella dei layer temporanei - # plugIn, pointGeoms, lineGeoms, polygonGeoms, coord, refresh - if qad_layer.addGeometriesToQADTempLayers(self.plugIn, None, lineGeoms, None, None, False) == False: - self.plugIn.destroyEditCommand() - return - - updGeom = qad_utils.delSubGeom(f_geom, atSubGeom) - - if updGeom is None or updGeom.isGeosEmpty(): # da cancellare - # plugIn, layer, feature id, refresh - if qad_layer.deleteFeatureToLayer(self.plugIn, layer, f.id(), False) == False: - self.plugIn.destroyEditCommand() - return - else: - trimmedFeature1 = QgsFeature(f) - # trasformo la geometria nel crs del layer - trimmedFeature1.setGeometry(self.mapToLayerCoordinates(layer, updGeom)) - # plugIn, layer, feature, refresh, check_validity - if qad_layer.updateFeatureToLayer(self.plugIn, layer, trimmedFeature1, False, False) == False: - self.plugIn.destroyEditCommand() - return - - self.plugIn.endEditCommand() - self.nOperationsToUndo = self.nOperationsToUndo + 1 - - - #============================================================================ - # waitForObjectSel - #============================================================================ - def waitForObjectSel(self): - self.step = 2 - # imposto il map tool - self.getPointMapTool().setSelectionMode(QadGetPointSelectionModeEnum.ENTITY_SELECTION_DYNAMIC) - # solo layer lineari editabili che non appartengano a quote - layerList = [] - for layer in self.plugIn.canvas.layers(): - if layer.type() == QgsMapLayer.VectorLayer and layer.geometryType() == QGis.Line and layer.isEditable(): - if len(QadDimStyles.getDimListByLayer(layer)) == 0: - layerList.append(layer) - - self.getPointMapTool().layersToCheck = layerList - self.getPointMapTool().setDrawMode(QadGetPointDrawModeEnum.NONE) - self.getPointMapTool().onlyEditableLayers = True - - keyWords = QadMsg.translate("Command_TRIM", "Fence") + "/" + \ - QadMsg.translate("Command_TRIM", "Crossing") + "/" + \ - QadMsg.translate("Command_TRIM", "Edge") + "/" + \ - QadMsg.translate("Command_TRIM", "Undo") - prompt = QadMsg.translate("Command_TRIM", "Select the object to trim or shift-select to extend or [{0}]: ").format(keyWords) - - englishKeyWords = "Fence" + "/" + "Crossing" + "/" + "Edge" + "/" + "Undo" - keyWords += "_" + englishKeyWords - # si appresta ad attendere un punto o enter o una parola chiave - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(prompt, \ - QadInputTypeEnum.POINT2D | QadInputTypeEnum.KEYWORDS, \ - None, \ - keyWords, QadInputModeEnum.NONE) - - - #============================================================================ - # run - #============================================================================ - def run(self, msgMapTool = False, msg = None): - if self.plugIn.canvas.mapRenderer().destinationCrs().geographicFlag(): - self.showMsg(QadMsg.translate("QAD", "\nThe coordinate reference system of the project must be a projected coordinate system.\n")) - return True # fine comando - - #========================================================================= - # RICHIESTA SELEZIONE OGGETTI LIMITI - if self.step == 0: # inizio del comando - CurrSettingsMsg = QadMsg.translate("QAD", "\nCurrent settings: ") - if self.edgeMode == 0: # 0 = nessuna estensione - CurrSettingsMsg = CurrSettingsMsg + QadMsg.translate("Command_TRIM", "Edge = No extend") - else: - CurrSettingsMsg = CurrSettingsMsg + QadMsg.translate("Command_TRIM", "Edge = Extend") - - self.showMsg(CurrSettingsMsg) - self.showMsg(QadMsg.translate("Command_TRIM", "\nSelect trim limits...")) - - if self.SSGetClass.run(msgMapTool, msg) == True: - # selezione terminata - self.step = 1 - return self.run(msgMapTool, msg) - - #========================================================================= - # RISPOSTA ALLA SELEZIONE OGGETTI LIMITI - elif self.step == 1: - self.limitEntitySet.set(self.SSGetClass.entitySet) - - if self.limitEntitySet.count() == 0: - return True # fine comando - - # si appresta ad attendere la selezione degli oggetti da estendere/tagliare - self.waitForObjectSel() - return False - - #========================================================================= - # RISPOSTA ALLA SELEZIONE OGGETTI DA ESTENDERE - elif self.step == 2: - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().point is None: # il maptool é stato attivato senza un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - return True # fine comando - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - else: - value = self.getPointMapTool().point - else: # il punto arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("Command_TRIM", "Fence") or value == "Fence": - # Seleziona tutti gli oggetti che intersecano una polilinea - self.PLINECommand = QadPLINECommandClass(self.plugIn) - # se questo flag = True il comando serve all'interno di un altro comando per disegnare una linea - # che non verrà salvata su un layer - self.PLINECommand.virtualCmd = True - self.PLINECommand.run(msgMapTool, msg) - self.step = 3 - return False - elif value == QadMsg.translate("Command_TRIM", "Crossing") or value == "Crossing": - # Seleziona tutti gli oggetti che intersecano un rettangolo - self.RECTANGLECommand = QadRECTANGLECommandClass(self.plugIn) - # se questo flag = True il comando serve all'interno di un altro comando per disegnare una linea - # che non verrà salvata su un layer - self.RECTANGLECommand.virtualCmd = True - self.RECTANGLECommand.run(msgMapTool, msg) - self.step = 4 - return False - elif value == QadMsg.translate("Command_TRIM", "Edge") or value == "Edge": - # Per estendere un oggetto usando anche le estensioni degli oggetti di riferimento - # vedi variabile EDGEMODE - keyWords = QadMsg.translate("Command_TRIM", "Extend") + "/" + \ - QadMsg.translate("Command_TRIM", "No extend") - if self.edgeMode == 0: # 0 = nessuna estensione - self.defaultValue = QadMsg.translate("Command_TRIM", "No") - else: - self.defaultValue = QadMsg.translate("Command_TRIM", "Extend") - prompt = QadMsg.translate("Command_TRIM", "Specify an extension mode [{0}] <{1}>: ").format(keyWords, self.defaultValue) - - englishKeyWords = "Extend" + "/" + "No extend" - keyWords += "_" + englishKeyWords - # si appresta ad attendere enter o una parola chiave - # msg, inputType, default, keyWords, nessun controllo - self.waitFor(prompt, \ - QadInputTypeEnum.KEYWORDS, \ - self.defaultValue, \ - keyWords, QadInputModeEnum.NONE) - self.step = 5 - return False - elif value == QadMsg.translate("Command_TRIM", "Undo") or value == "Undo": - if self.nOperationsToUndo > 0: - self.nOperationsToUndo = self.nOperationsToUndo - 1 - self.plugIn.undoEditCommand() - else: - self.showMsg(QadMsg.translate("QAD", "\nThe command has been canceled.")) - elif type(value) == QgsPoint: # se é stato selezionato un punto - self.entitySet.clear() - if self.getPointMapTool().entity.isInitialized(): - self.entitySet.addEntity(self.getPointMapTool().entity) - ToExtend = True if self.getPointMapTool().shiftKey == True else False - self.trimFeatures(QgsGeometry.fromPoint(value), ToExtend) - else: - # cerco se ci sono entità nel punto indicato considerando - # solo layer lineari editabili che non appartengano a quote - layerList = [] - for layer in self.plugIn.canvas.layers(): - if layer.type() == QgsMapLayer.VectorLayer and layer.geometryType() == QGis.Line and layer.isEditable(): - if len(QadDimStyles.getDimListByLayer(layer)) == 0: - layerList.append(layer) - - result = qad_utils.getEntSel(self.getPointMapTool().toCanvasCoordinates(value), - self.getPointMapTool(), \ - layerList) - if result is not None: - feature = result[0] - layer = result[1] - point = result[2] - self.entitySet.addEntity(QadEntity().set(layer, feature.id())) - self.trimFeatures(QgsGeometry.fromPoint(point), False) - else: - return True # fine comando - - # si appresta ad attendere la selezione degli oggetti da estendere/tagliare - self.waitForObjectSel() - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA PUNTO PER MODALITA' INTERCETTA (da step = 2) - elif self.step == 3: # dopo aver atteso un punto si riavvia il comando - if self.PLINECommand.run(msgMapTool, msg) == True: - if len(self.PLINECommand.vertices) > 1: - if msgMapTool == True: # se la polilinea arriva da una selezione grafica - ToExtend = True if self.getPointMapTool().shiftKey == True else False - else: - ToExtend = False - - # cerco tutte le geometrie passanti per la polilinea saltando i layer punto e poligono - # e considerando solo layer editabili - self.entitySet = qad_utils.getSelSet("F", self.getPointMapTool(), self.PLINECommand.vertices, \ - None, False, True, False, \ - True) - self.trimFeatures(QgsGeometry.fromPolyline(self.PLINECommand.vertices), ToExtend) - del self.PLINECommand - self.PLINECommand = None - - # si appresta ad attendere la selezione degli oggetti da estendere/tagliare - self.waitForObjectSel() - self.getPointMapTool().refreshSnapType() # aggiorno lo snapType che può essere variato dal maptool di pline - - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA PUNTO PER MODALITA' INTERSECA (da step = 2) - elif self.step == 4: # dopo aver atteso un punto si riavvia il comando - if self.RECTANGLECommand.run(msgMapTool, msg) == True: - if len(self.RECTANGLECommand.vertices) > 1: - if msgMapTool == True: # se la polilinea arriva da una selezione grafica - ToExtend = True if self.getPointMapTool().shiftKey == True else False - else: - ToExtend = False - - # cerco tutte le geometrie passanti per la polilinea saltando i layer punto e poligono - # e considerando solo layer editabili - self.entitySet = qad_utils.getSelSet("F", self.getPointMapTool(), self.RECTANGLECommand.vertices, \ - None, False, True, False, \ - True) - self.trimFeatures(QgsGeometry.fromPolyline(self.RECTANGLECommand.vertices), ToExtend) - del self.RECTANGLECommand - self.RECTANGLECommand = None - - # si appresta ad attendere la selezione degli oggetti da estendere/tagliare - self.waitForObjectSel() - self.getPointMapTool().refreshSnapType() # aggiorno lo snapType che può essere variato dal maptool di rectangle - return False - - #========================================================================= - # RISPOSTA ALLA RICHIESTA DI TIPO DI ESTENSIONE (da step = 2) - elif self.step == 5: # dopo aver atteso un punto o un numero reale si riavvia il comando - if msgMapTool == True: # il punto arriva da una selezione grafica - # la condizione seguente si verifica se durante la selezione di un punto - # é stato attivato un altro plugin che ha disattivato Qad - # quindi stato riattivato il comando che torna qui senza che il maptool - # abbia selezionato un punto - if self.getPointMapTool().rightButton == True: # se usato il tasto destro del mouse - value = self.defaultValue - else: - self.setMapTool(self.getPointMapTool()) # riattivo il maptool - return False - else: # il valore arriva come parametro della funzione - value = msg - - if type(value) == unicode: - if value == QadMsg.translate("Command_TRIM", "No") or value == "No": - self.edgeMode = 0 - QadVariables.set(QadMsg.translate("Environment variables", "EDGEMODE"), self.edgeMode) - QadVariables.save() - # si appresta ad attendere la selezione degli oggetti da estendere/tagliare - self.waitForObjectSel() - elif value == QadMsg.translate("Command_TRIM", "Extend") or value == "Extend": - self.edgeMode = 1 - QadVariables.set(QadMsg.translate("Environment variables", "EDGEMODE"), self.edgeMode) - QadVariables.save() - # si appresta ad attendere la selezione degli oggetti da estendere/tagliare - self.waitForObjectSel() - - return False diff --git a/qad_ui.py b/qad_ui.py index 980ed1d2..e7518359 100644 --- a/qad_ui.py +++ b/qad_ui.py @@ -1,43 +1,10 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file 'qad.ui' -# -# Created: Tue Sep 08 15:55:17 2015 -# by: PyQt4 UI code generator 4.10.2 -# -# WARNING! All changes made in this file will be lost! - -from PyQt4 import QtCore, QtGui - -try: - _fromUtf8 = QtCore.QString.fromUtf8 -except AttributeError: - def _fromUtf8(s): - return s - -try: - _encoding = QtGui.QApplication.UnicodeUTF8 - def _translate(context, text, disambig): - return QtGui.QApplication.translate(context, text, disambig, _encoding) -except AttributeError: - def _translate(context, text, disambig): - return QtGui.QApplication.translate(context, text, disambig) - -class Ui_QAD(object): - def setupUi(self, QAD): - QAD.setObjectName(_fromUtf8("QAD")) - QAD.resize(400, 300) - self.buttonBox = QtGui.QDialogButtonBox(QAD) - self.buttonBox.setGeometry(QtCore.QRect(30, 240, 341, 32)) - self.buttonBox.setOrientation(QtCore.Qt.Horizontal) - self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok) - self.buttonBox.setObjectName(_fromUtf8("buttonBox")) - - self.retranslateUi(QAD) - QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("accepted()")), QAD.accept) - QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("rejected()")), QAD.reject) - QtCore.QMetaObject.connectSlotsByName(QAD) - - def retranslateUi(self, QAD): - QAD.setWindowTitle(_translate("QAD", "QAD", None)) - +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'qad.ui' +# +# Created by: PyQt5 UI code generator 5.15.4 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + + diff --git a/qad_ui_textwindow.py b/qad_ui_textwindow.py index 60d0a192..26af059f 100644 --- a/qad_ui_textwindow.py +++ b/qad_ui_textwindow.py @@ -1,58 +1,57 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - ------------------- - begin : 2013-05-22 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -from PyQt4 import QtCore, QtGui - - -from qad_msg import QadMsg - - -class Ui_QadTextWindow(object): - def setupUi(self, QadTextWindow): - QadTextWindow.setObjectName("QadTextWindow") - QadTextWindow.setWindowModality(QtCore.Qt.NonModal) - QadTextWindow.setEnabled(True) - QadTextWindow.resize(642, 193) - QadTextWindow.setMinimumSize(100, 20) - QadTextWindow.setMaximumSize(QtCore.QSize(524287, 524287)) - - self.retranslateUi(QadTextWindow) - QtCore.QMetaObject.connectSlotsByName(QadTextWindow) - - def retranslateUi(self, QadTextWindow): - QadTextWindow.setWindowTitle(QadMsg.translate("Text_window", "QAD text window")) - -class Ui_QadCmdSuggestWindow(object): - def setupUi(self, QadCmdSuggestWindow): - QadCmdSuggestWindow.setObjectName("QadCmdsListWindow") - QadCmdSuggestWindow.setWindowModality(QtCore.Qt.NonModal) - QadCmdSuggestWindow.setEnabled(True) - self.dockWidgetContents = QtGui.QWidget() - self.dockWidgetContents.setObjectName("QadCmdsListWindowDockWidgetContents") - self.vboxlayout = QtGui.QVBoxLayout(self.dockWidgetContents) - self.vboxlayout.setObjectName("QadCmdsListWindowVBoxLayout") - self.vboxlayout.setMargin(0) - QadCmdSuggestWindow.setLayout(self.vboxlayout) - QtCore.QMetaObject.connectSlotsByName(QadCmdSuggestWindow) - - +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + ------------------- + begin : 2013-05-22 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from PyQt5 import QtCore, QtGui +from qgis.PyQt.QtWidgets import QWidget, QVBoxLayout, QMessageBox +from .qad_msg import QadMsg + + +class Ui_QadTextWindow(object): + def setupUi(self, QadTextWindow): + #QadTextWindow.setObjectName("QadTextWindow") + QadTextWindow.setWindowModality(QtCore.Qt.NonModal) + QadTextWindow.setEnabled(True) + QadTextWindow.resize(642, 193) + QadTextWindow.setMinimumSize(100, 20) + QadTextWindow.setMaximumSize(QtCore.QSize(524287, 524287)) + + self.retranslateUi(QadTextWindow) + QtCore.QMetaObject.connectSlotsByName(QadTextWindow) + + def retranslateUi(self, QadTextWindow): + QadTextWindow.setWindowTitle(QadMsg.translate("Text_window", "Text window")) + +class Ui_QadCmdSuggestWindow(object): + def setupUi(self, QadCmdSuggestWindow): + QadCmdSuggestWindow.setObjectName("QadCmdsListWindow") + QadCmdSuggestWindow.setWindowModality(QtCore.Qt.NonModal) + QadCmdSuggestWindow.setEnabled(True) + self.dockWidgetContents = QWidget() + self.dockWidgetContents.setObjectName("QadCmdsListWindowDockWidgetContents") + self.vboxlayout = QVBoxLayout(self.dockWidgetContents) + self.vboxlayout.setObjectName("QadCmdsListWindowVBoxLayout") + self.vboxlayout.setMargin(0) + QadCmdSuggestWindow.setLayout(self.vboxlayout) + QtCore.QMetaObject.connectSlotsByName(QadCmdSuggestWindow) + + diff --git a/qad_undoredo.py b/qad_undoredo.py index 4c1c8c52..5424ebd1 100644 --- a/qad_undoredo.py +++ b/qad_undoredo.py @@ -1,393 +1,393 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - funzioni per undo e redo - - ------------------- - begin : 2014-04-24 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -# Import the PyQt and QGIS libraries -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -from qgis.gui import * - - -#=============================================================================== -# QadUndoRecordTypeEnum class. -#=============================================================================== -class QadUndoRecordTypeEnum(): - NONE = 0 # nessuno - COMMAND = 1 # singolo comando - BEGIN = 2 # inizio di un gruppo di comandi - END = 3 # fine di un gruppo di comandi - BOOKMARK = 4 # flag di segnalibro, significa che si tratta di un segno a cui - # si può ritornare - - -#=============================================================================== -# QadUndoRecord classe x gestire un registrazione di UNDO -#=============================================================================== -class QadUndoRecord(): - - - def __init__(self): - self.text = "" # descrizione operazione - self.undoType = QadUndoRecordTypeEnum.NONE # tipo di undo (vedi QadUndoRecordTypeEnum) - self.layerList = None # lista di layer coinvolti nel comando di editazione - - - def setUndoType(self, text = "", undoType = QadUndoRecordTypeEnum.NONE): - # si sta impostando una tipologia di marcatore di undo - self.text = text - self.layerList = None # lista di layer coinvolti nel comando di editazione - self.undoType = undoType - - - def layerAt(self, layerId): - # ritorna la posizione nella lista 0-based), -1 se non trovato - if self.layerList is not None and self.undoType == QadUndoRecordTypeEnum.COMMAND: - for j in xrange(0, len(self.layerList), 1): - if self.layerList[j].id() == layerId: - return j - return -1 - - - def clearByLayer(self, layerId): - # elimino dalla lista il layer - pos = self.layerAt(layerId) - if pos >= 0: - del self.layerList[pos] - - - def beginEditCommand(self, text, layerList): - # si sta iniziando un comando che coinvolge una lista di layer - self.text = text # descrizione operazione - self.undoType = QadUndoRecordTypeEnum.COMMAND - # contiene la lista dei layer coinvolti nel comando di editazione - self.layerList = [] - for layer in layerList: # copio la lista - if self.layerAt(layer.id()) == -1: # non ammetto duplicazioni di layer - layer.beginEditCommand(text) - self.layerList.append(layer) - - - def destroyEditCommand(self): - # si sta distruggendo un comando che coinvolge una lista di layer - if self.layerList is not None and self.undoType == QadUndoRecordTypeEnum.COMMAND: - for layer in self.layerList: - layer.destroyEditCommand() # Destroy active command and reverts all changes in it - return True - else: - return False - - - def endEditCommand(self, canvas): - # si sta concludendo un comando che coinvolge una lista di layer - if self.layerList is not None and self.undoType == QadUndoRecordTypeEnum.COMMAND: - for layer in self.layerList: - layer.endEditCommand() - #layer.updateExtents() # non serve - canvas.refresh() - - - def undoEditCommand(self, canvas = None): - # si sta facendo un UNDO di un comando che coinvolge una lista di layer - if self.layerList is not None and self.undoType == QadUndoRecordTypeEnum.COMMAND: - for layer in self.layerList: - layer.undoStack().undo() - if canvas is not None: - canvas.refresh() - - - def redoEditCommand(self, canvas = None): - # si sta facendo un REDO di un comando che coinvolge una lista di layer - if self.layerList is not None and self.undoType == QadUndoRecordTypeEnum.COMMAND: - for layer in self.layerList: - layer.undoStack().redo() - if canvas is not None: - canvas.refresh() - - - def addLayer(self, layer): - # si sta aggiungendo un layer al comando corrente - if self.undoType != QadUndoRecordTypeEnum.COMMAND: # si deve trattare di un comando - return False - if self.layerAt(layer.id()) == -1: # non ammetto duplicazioni di layer - layer.beginEditCommand(self.text) - self.layerList.append(layer) - - -#=============================================================================== -# QadUndoStack classe x gestire lo stack delle operazioni -#=============================================================================== -class QadUndoStack(): - - - def __init__(self): - self.UndoRecordList = [] # lista di record di undo - self.index = -1 - - - def clear(self): - del self.UndoRecordList[:] # svuoto la lista - self.index = -1 - - - def clearByLayer(self, layerId): - # elimino il layer dalla lista dei record di undo - for i in xrange(len(self.UndoRecordList) - 1, -1, -1): - UndoRecord = self.UndoRecordList[i] - if UndoRecord.undoType == QadUndoRecordTypeEnum.COMMAND: - UndoRecord.clearByLayer(layerId) - if len(UndoRecord.layerList) == 0: - # elimino la lista dei layer (vuota) coinvolta nel comando di editazione - del self.UndoRecordList[i] - if self.index >= i: # aggiorno il puntatore - self.index = self.index - 1 - - - def insertBeginGroup(self, text): - UndoRecord = QadUndoRecord() - UndoRecord.setUndoType(text, QadUndoRecordTypeEnum.BEGIN) - self.UndoRecordList.append(UndoRecord) - self.index = len(self.UndoRecordList) - 1 - return True - - - def getOpenGroupPos(self, endGroupPos): - # dalla posizione di fine gruppo cerca la posizione di inizio gruppo - # -1 se non trovato - openFlag = 0 - for i in xrange(endGroupPos, -1, -1): - UndoRecord = self.UndoRecordList[i] - if UndoRecord.undoType == QadUndoRecordTypeEnum.BEGIN: - openFlag = openFlag + 1 - if openFlag >= 0: - return i - elif UndoRecord.undoType == QadUndoRecordTypeEnum.END: - openFlag = openFlag - 1 - return -1 - - - def getEndGroupPos(self, beginGroupPos): - # dalla posizione di inizio gruppo cerca la posizione di inizio gruppo - # -1 se non trovato - closeFlag = 0 - for i in xrange(beginGroupPos, len(self.UndoRecordList), 1): - UndoRecord = self.UndoRecordList[i] - if UndoRecord.undoType == QadUndoRecordTypeEnum.BEGIN: - closeFlag = closeFlag - 1 - elif UndoRecord.undoType == QadUndoRecordTypeEnum.END: - closeFlag = closeFlag + 1 - if closeFlag >= 0: - return i - return -1 - - - def insertEndGroup(self): - # non si può inserire un end gruppo se non si é rimasto aperto un gruppo - openGroupPos = self.getOpenGroupPos(len(self.UndoRecordList) - 1) - if openGroupPos == -1: - return False - - UndoRecord = QadUndoRecord() - UndoRecord.setUndoType(self.UndoRecordList[openGroupPos].text, QadUndoRecordTypeEnum.END) - self.UndoRecordList.append(UndoRecord) - self.index = len(self.UndoRecordList) - 1 - return True - - - def beginEditCommand(self, text, layerList): - tot = len(self.UndoRecordList) - if tot > 0 and self.index < tot - 1: - del self.UndoRecordList[self.index + 1 :] # cancello fino alla fine - - UndoRecord = QadUndoRecord() - UndoRecord.beginEditCommand(text, layerList) - self.UndoRecordList.append(UndoRecord) - self.index = len(self.UndoRecordList) - 1 - - - def destroyEditCommand(self): - if len(self.UndoRecordList) > 0: - UndoRecord = self.UndoRecordList[-1] - if UndoRecord.destroyEditCommand(): - del self.UndoRecordList[-1] - self.index = self.index - 1 - - - def endEditCommand(self, canvas): - if len(self.UndoRecordList) > 0: - UndoRecord = self.UndoRecordList[-1] - UndoRecord.endEditCommand(canvas) - - - def moveOnFirstUndoRecord(self): - # sposta il cursore dalla posizione attuale fino l'inizio - # e si ferma quando trova un record di tipo END o COMMAND - while self.index >= 0: - UndoRecord = self.UndoRecordList[self.index] - if UndoRecord.undoType == QadUndoRecordTypeEnum.END or \ - UndoRecord.undoType == QadUndoRecordTypeEnum.COMMAND: - return True - self.index = self.index - 1 - return False - - def undoEditCommand(self, canvas = None, nTimes = 1): - for i in xrange(0, nTimes, 1): - # cerco il primo record in cui ha senso fare UNDO - if self.moveOnFirstUndoRecord() == False: - break - UndoRecord = self.UndoRecordList[self.index] - # se incontro un end-group devo andare fino al begin-group - if UndoRecord.undoType == QadUndoRecordTypeEnum.END: - openGroupPos = self.getOpenGroupPos(self.index) - while self.index >= openGroupPos: - UndoRecord.undoEditCommand(None) # senza fare refresh - self.index = self.index - 1 - if self.moveOnFirstUndoRecord() == False: - break - UndoRecord = self.UndoRecordList[self.index] - else: - UndoRecord.undoEditCommand(None) - self.index = self.index - 1 - - if canvas is not None: - canvas.refresh() - - - def moveOnFirstRedoRecord(self): - # sposta il cursore dalla posizione attuale fino alla fine - # e si ferma quando trova un record di tipo BEGIN o COMMAND - tot = len(self.UndoRecordList) - 1 - while self.index < tot: - self.index = self.index + 1 - UndoRecord = self.UndoRecordList[self.index] - if UndoRecord.undoType == QadUndoRecordTypeEnum.BEGIN or \ - UndoRecord.undoType == QadUndoRecordTypeEnum.COMMAND: - return True - return False - - def redoEditCommand(self, canvas = None, nTimes = 1): - for i in xrange(0, nTimes, 1): - # cerco il primo record in cui ha senso fare REDO - if self.moveOnFirstRedoRecord() == False: - break - UndoRecord = self.UndoRecordList[self.index] - # se incontro un begin-group devo andare fino al end-group - if UndoRecord.undoType == QadUndoRecordTypeEnum.BEGIN: - endGroupPos = self.getEndGroupPos(self.index) - while self.index <= endGroupPos: - UndoRecord.redoEditCommand(None) # senza refresh - if self.moveOnFirstRedoRecord() == False: - break - UndoRecord = self.UndoRecordList[self.index] - else: - UndoRecord.redoEditCommand(None) - - if canvas is not None: - canvas.refresh() - - - def addLayerToLastEditCommand(self, text, layer): - if len(self.UndoRecordList) > 0: - self.UndoRecordList[-1].addLayer(layer) - - - def isUndoAble(self): - # cerca un record di tipo COMMAND dalla posizione attuale fino l'inizio - i = self.index - while i >= 0: - UndoRecord = self.UndoRecordList[i] - if UndoRecord.undoType == QadUndoRecordTypeEnum.COMMAND: - return True - i = i - 1 - return False - - - def isRedoAble(self): - # cerca un record di tipo COMMAND dalla posizione attuale fino alla fine - i = self.index + 1 - tot = len(self.UndoRecordList) - while i < tot: - UndoRecord = self.UndoRecordList[i] - if UndoRecord.undoType == QadUndoRecordTypeEnum.COMMAND: - return True - i = i + 1 - return False - - #=============================================================================== - # BOOKMARK - INIZIO - #=============================================================================== - - def undoUntilBookmark(self, canvas): - if self.index == -1: - return - for i in xrange(self.index, -1, -1): - UndoRecord = self.UndoRecordList[i] - if UndoRecord.undoType == QadUndoRecordTypeEnum.BOOKMARK: - break - - UndoRecord.undoEditCommand(None) # senza refresh - self.index = i - 1 - - canvas.refresh() - - - def redoUntilBookmark(self, canvas): - for i in xrange(self.index + 1, len(self.UndoRecordList), 1): - UndoRecord = self.UndoRecordList[i] - if UndoRecord.undoType == QadUndoRecordTypeEnum.BOOKMARK: - break - UndoRecord.redoEditCommand(None) # senza refresh - self.index = i - - canvas.refresh() - - - def getPrevBookmarkPos(self, pos): - # dalla posizione cerca la posizione di bookmark precedente - # -1 se non trovato - for i in xrange(pos - 1, -1, -1): - UndoRecord = self.UndoRecordList[i] - if UndoRecord.undoType == QadUndoRecordTypeEnum.BOOKMARK: - return i - return -1 - - - def insertBookmark(self, text): - # non si può inserire un bookmark all'interno di un gruppo begin-end - if self.getOpenGroupPos(self.index) >= 0: - return False - - tot = len(self.UndoRecordList) - if tot > 0 and self.index < tot - 1: - del self.UndoRecordList[self.index + 1 :] # cancello fino alla fine - - UndoRecord = QadUndoRecord() - UndoRecord.setUndoType(text, QadUndoRecordTypeEnum.BOOKMARK) - self.UndoRecordList.append(UndoRecord) - self.index = len(self.UndoRecordList) - 1 - return True - - #=============================================================================== - # BOOKMARK - FINE - #=============================================================================== \ No newline at end of file +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + funzioni per undo e redo + + ------------------- + begin : 2014-04-24 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.PyQt.QtCore import * +from qgis.PyQt.QtGui import * +from qgis.core import * +from qgis.gui import * + + +# =============================================================================== +# QadUndoRecordTypeEnum class. +# =============================================================================== +class QadUndoRecordTypeEnum(): + NONE = 0 # nessuno + COMMAND = 1 # singolo comando + BEGIN = 2 # inizio di un gruppo di comandi + END = 3 # fine di un gruppo di comandi + BOOKMARK = 4 # flag di segnalibro, significa che si tratta di un segno a cui + # si può ritornare + + +# =============================================================================== +# QadUndoRecord classe x gestire un registrazione di UNDO +# =============================================================================== +class QadUndoRecord(): + + + def __init__(self): + self.text = "" # descrizione operazione + self.undoType = QadUndoRecordTypeEnum.NONE # tipo di undo (vedi QadUndoRecordTypeEnum) + self.layerList = None # lista di layer coinvolti nel comando di editazione + + + def setUndoType(self, text = "", undoType = QadUndoRecordTypeEnum.NONE): + # si sta impostando una tipologia di marcatore di undo + self.text = text + self.layerList = None # lista di layer coinvolti nel comando di editazione + self.undoType = undoType + + + def layerAt(self, layerId): + # ritorna la posizione nella lista 0-based), -1 se non trovato + if self.layerList is not None and self.undoType == QadUndoRecordTypeEnum.COMMAND: + for j in range(0, len(self.layerList), 1): + if self.layerList[j].id() == layerId: + return j + return -1 + + + def clearByLayer(self, layerId): + # elimino dalla lista il layer + pos = self.layerAt(layerId) + if pos >= 0: + del self.layerList[pos] + + + def beginEditCommand(self, text, layerList): + # si sta iniziando un comando che coinvolge una lista di layer + self.text = text # descrizione operazione + self.undoType = QadUndoRecordTypeEnum.COMMAND + # contiene la lista dei layer coinvolti nel comando di editazione + self.layerList = [] + for layer in layerList: # copio la lista + if self.layerAt(layer.id()) == -1: # non ammetto duplicazioni di layer + layer.beginEditCommand(text) + self.layerList.append(layer) + + + def destroyEditCommand(self): + # si sta distruggendo un comando che coinvolge una lista di layer + if self.layerList is not None and self.undoType == QadUndoRecordTypeEnum.COMMAND: + for layer in self.layerList: + layer.destroyEditCommand() # Destroy active command and reverts all changes in it + return True + else: + return False + + + def endEditCommand(self, canvas): + # si sta concludendo un comando che coinvolge una lista di layer + if self.layerList is not None and self.undoType == QadUndoRecordTypeEnum.COMMAND: + for layer in self.layerList: + layer.endEditCommand() + layer.triggerRepaint() + canvas.refresh() + + + def undoEditCommand(self, canvas = None): + # si sta facendo un UNDO di un comando che coinvolge una lista di layer + if self.layerList is not None and self.undoType == QadUndoRecordTypeEnum.COMMAND: + for layer in self.layerList: + layer.undoStack().undo() + if canvas is not None: + canvas.refresh() + + + def redoEditCommand(self, canvas = None): + # si sta facendo un REDO di un comando che coinvolge una lista di layer + if self.layerList is not None and self.undoType == QadUndoRecordTypeEnum.COMMAND: + for layer in self.layerList: + layer.undoStack().redo() + if canvas is not None: + canvas.refresh() + + + def addLayer(self, layer): + # si sta aggiungendo un layer al comando corrente + if self.undoType != QadUndoRecordTypeEnum.COMMAND: # si deve trattare di un comando + return False + if self.layerAt(layer.id()) == -1: # non ammetto duplicazioni di layer + layer.beginEditCommand(self.text) + self.layerList.append(layer) + + +# =============================================================================== +# QadUndoStack classe x gestire lo stack delle operazioni +# =============================================================================== +class QadUndoStack(): + + + def __init__(self): + self.UndoRecordList = [] # lista di record di undo + self.index = -1 + + + def clear(self): + del self.UndoRecordList[:] # svuoto la lista + self.index = -1 + + + def clearByLayer(self, layerId): + # elimino il layer dalla lista dei record di undo + for i in range(len(self.UndoRecordList) - 1, -1, -1): + UndoRecord = self.UndoRecordList[i] + if UndoRecord.undoType == QadUndoRecordTypeEnum.COMMAND: + UndoRecord.clearByLayer(layerId) + if len(UndoRecord.layerList) == 0: + # elimino la lista dei layer (vuota) coinvolta nel comando di editazione + del self.UndoRecordList[i] + if self.index >= i: # aggiorno il puntatore + self.index = self.index - 1 + + + def insertBeginGroup(self, text): + UndoRecord = QadUndoRecord() + UndoRecord.setUndoType(text, QadUndoRecordTypeEnum.BEGIN) + self.UndoRecordList.append(UndoRecord) + self.index = len(self.UndoRecordList) - 1 + return True + + + def getOpenGroupPos(self, endGroupPos): + # dalla posizione di fine gruppo cerca la posizione di inizio gruppo + # -1 se non trovato + openFlag = 0 + for i in range(endGroupPos, -1, -1): + UndoRecord = self.UndoRecordList[i] + if UndoRecord.undoType == QadUndoRecordTypeEnum.BEGIN: + openFlag = openFlag + 1 + if openFlag >= 0: + return i + elif UndoRecord.undoType == QadUndoRecordTypeEnum.END: + openFlag = openFlag - 1 + return -1 + + + def getEndGroupPos(self, beginGroupPos): + # dalla posizione di inizio gruppo cerca la posizione di inizio gruppo + # -1 se non trovato + closeFlag = 0 + for i in range(beginGroupPos, len(self.UndoRecordList), 1): + UndoRecord = self.UndoRecordList[i] + if UndoRecord.undoType == QadUndoRecordTypeEnum.BEGIN: + closeFlag = closeFlag - 1 + elif UndoRecord.undoType == QadUndoRecordTypeEnum.END: + closeFlag = closeFlag + 1 + if closeFlag >= 0: + return i + return -1 + + + def insertEndGroup(self): + # non si può inserire un end gruppo se non si é rimasto aperto un gruppo + openGroupPos = self.getOpenGroupPos(len(self.UndoRecordList) - 1) + if openGroupPos == -1: + return False + + UndoRecord = QadUndoRecord() + UndoRecord.setUndoType(self.UndoRecordList[openGroupPos].text, QadUndoRecordTypeEnum.END) + self.UndoRecordList.append(UndoRecord) + self.index = len(self.UndoRecordList) - 1 + return True + + + def beginEditCommand(self, text, layerList): + tot = len(self.UndoRecordList) + if tot > 0 and self.index < tot - 1: + del self.UndoRecordList[self.index + 1 :] # cancello fino alla fine + + UndoRecord = QadUndoRecord() + UndoRecord.beginEditCommand(text, layerList) + self.UndoRecordList.append(UndoRecord) + self.index = len(self.UndoRecordList) - 1 + + + def destroyEditCommand(self): + if len(self.UndoRecordList) > 0: + UndoRecord = self.UndoRecordList[-1] + if UndoRecord.destroyEditCommand(): + del self.UndoRecordList[-1] + self.index = self.index - 1 + + + def endEditCommand(self, canvas): + if len(self.UndoRecordList) > 0: + UndoRecord = self.UndoRecordList[-1] + UndoRecord.endEditCommand(canvas) + + + def moveOnFirstUndoRecord(self): + # sposta il cursore dalla posizione attuale fino l'inizio + # e si ferma quando trova un record di tipo END o COMMAND + while self.index >= 0: + UndoRecord = self.UndoRecordList[self.index] + if UndoRecord.undoType == QadUndoRecordTypeEnum.END or \ + UndoRecord.undoType == QadUndoRecordTypeEnum.COMMAND: + return True + self.index = self.index - 1 + return False + + def undoEditCommand(self, canvas = None, nTimes = 1): + for i in range(0, nTimes, 1): + # cerco il primo record in cui ha senso fare UNDO + if self.moveOnFirstUndoRecord() == False: + break + UndoRecord = self.UndoRecordList[self.index] + # se incontro un end-group devo andare fino al begin-group + if UndoRecord.undoType == QadUndoRecordTypeEnum.END: + openGroupPos = self.getOpenGroupPos(self.index) + while self.index >= openGroupPos: + UndoRecord.undoEditCommand(None) # senza fare refresh + self.index = self.index - 1 + if self.moveOnFirstUndoRecord() == False: + break + UndoRecord = self.UndoRecordList[self.index] + else: + UndoRecord.undoEditCommand(None) + self.index = self.index - 1 + + if canvas is not None: + canvas.refresh() + + + def moveOnFirstRedoRecord(self): + # sposta il cursore dalla posizione attuale fino alla fine + # e si ferma quando trova un record di tipo BEGIN o COMMAND + tot = len(self.UndoRecordList) - 1 + while self.index < tot: + self.index = self.index + 1 + UndoRecord = self.UndoRecordList[self.index] + if UndoRecord.undoType == QadUndoRecordTypeEnum.BEGIN or \ + UndoRecord.undoType == QadUndoRecordTypeEnum.COMMAND: + return True + return False + + def redoEditCommand(self, canvas = None, nTimes = 1): + for i in range(0, nTimes, 1): + # cerco il primo record in cui ha senso fare REDO + if self.moveOnFirstRedoRecord() == False: + break + UndoRecord = self.UndoRecordList[self.index] + # se incontro un begin-group devo andare fino al end-group + if UndoRecord.undoType == QadUndoRecordTypeEnum.BEGIN: + endGroupPos = self.getEndGroupPos(self.index) + while self.index <= endGroupPos: + UndoRecord.redoEditCommand(None) # senza refresh + if self.moveOnFirstRedoRecord() == False: + break + UndoRecord = self.UndoRecordList[self.index] + else: + UndoRecord.redoEditCommand(None) + + if canvas is not None: + canvas.refresh() + + + def addLayerToLastEditCommand(self, text, layer): + if len(self.UndoRecordList) > 0: + self.UndoRecordList[-1].addLayer(layer) + + + def isUndoAble(self): + # cerca un record di tipo COMMAND dalla posizione attuale fino l'inizio + i = self.index + while i >= 0: + UndoRecord = self.UndoRecordList[i] + if UndoRecord.undoType == QadUndoRecordTypeEnum.COMMAND: + return True + i = i - 1 + return False + + + def isRedoAble(self): + # cerca un record di tipo COMMAND dalla posizione attuale fino alla fine + i = self.index + 1 + tot = len(self.UndoRecordList) + while i < tot: + UndoRecord = self.UndoRecordList[i] + if UndoRecord.undoType == QadUndoRecordTypeEnum.COMMAND: + return True + i = i + 1 + return False + + # =============================================================================== + # BOOKMARK - INIZIO + # =============================================================================== + + def undoUntilBookmark(self, canvas): + if self.index == -1: + return + for i in range(self.index, -1, -1): + UndoRecord = self.UndoRecordList[i] + if UndoRecord.undoType == QadUndoRecordTypeEnum.BOOKMARK: + break + + UndoRecord.undoEditCommand(None) # senza refresh + self.index = i - 1 + + canvas.refresh() + + + def redoUntilBookmark(self, canvas): + for i in range(self.index + 1, len(self.UndoRecordList), 1): + UndoRecord = self.UndoRecordList[i] + if UndoRecord.undoType == QadUndoRecordTypeEnum.BOOKMARK: + break + UndoRecord.redoEditCommand(None) # senza refresh + self.index = i + + canvas.refresh() + + + def getPrevBookmarkPos(self, pos): + # dalla posizione cerca la posizione di bookmark precedente + # -1 se non trovato + for i in range(pos - 1, -1, -1): + UndoRecord = self.UndoRecordList[i] + if UndoRecord.undoType == QadUndoRecordTypeEnum.BOOKMARK: + return i + return -1 + + + def insertBookmark(self, text): + # non si può inserire un bookmark all'interno di un gruppo begin-end + if self.getOpenGroupPos(self.index) >= 0: + return False + + tot = len(self.UndoRecordList) + if tot > 0 and self.index < tot - 1: + del self.UndoRecordList[self.index + 1 :] # cancello fino alla fine + + UndoRecord = QadUndoRecord() + UndoRecord.setUndoType(text, QadUndoRecordTypeEnum.BOOKMARK) + self.UndoRecordList.append(UndoRecord) + self.index = len(self.UndoRecordList) - 1 + return True + + # =============================================================================== + # BOOKMARK - FINE + # =============================================================================== \ No newline at end of file diff --git a/qad_utils.py b/qad_utils.py index e4b40c81..2b595719 100644 --- a/qad_utils.py +++ b/qad_utils.py @@ -1,9403 +1,2438 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - funzioni varie di utilità - - ------------------- - begin : 2013-05-22 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -from qgis.gui import * -#from qgis.utils import * -import qgis.utils - - -import math -import sys -import string -from ctypes import * -import ConfigParser -import time - - -from qad_variables import * -from qad_snapper import * -from qad_msg import QadMsg -from qad_circle import * -from qad_arc import * -from qad_entity import * - - -# Modulo che gestisce varie funzionalità di Qad - - -#=============================================================================== -# TOLERANCE = variabile globale -#=============================================================================== -TOLERANCE = 1.e-7 - - -#=============================================================================== -# isNumericField -#=============================================================================== -def isNumericField(field): - """ - La funzione verifica che il campo di tipo QgsField sia numerico - """ - fldType = field.type() - if fldType == QVariant.Double or fldType == QVariant.LongLong or fldType == QVariant.Int or \ - fldType == QVariant.ULongLong or fldType == QVariant.UInt: - return True - else: - return False - - -#=============================================================================== -# checkUniqueNewName -#=============================================================================== -def checkUniqueNewName(newName, nameList, prefix = None, suffix = None, caseSensitive = True): - """ - La funzione verifica che il nuovo nome non esistà già nella lista . - Se nella lista dovesse già esistere allora aggiunge un prefisso (se <> None) o un suffisso (se <> None) - finchè il nome non è più presnete nella lista - """ - ok = False - result = newName - while ok == False: - ok = True - for name in nameList: - if caseSensitive == True: - if name == result: - ok = False - break - else: - if name.upper() == result.upper(): - ok = False - break - - if ok == True: - return result - if prefix is not None: - result = prefix + result - else: - if suffix is not None: - result = result + suffix - - return None - -#=============================================================================== -# wildCard2regularExpr -#=============================================================================== -def wildCard2regularExpr(wildCard, ignoreCase = True): - """ - Ritorna la conversione di una stringa con wildcards (es. "gas*") - in forma di regular expression (es. "[g][a][s].*") - """ - # ? -> . - # * -> .* - # altri caratteri -> [carattere] - regularExpr = "" - for ch in wildCard: - if ch == "?": - regularExpr = regularExpr + "." - elif ch == "*": - regularExpr = regularExpr + ".*" - else: - if ignoreCase: - regularExpr = regularExpr + "[" + ch.upper() + ch.lower() + "]" - else: - regularExpr = regularExpr + "[" + ch + "]" - - return regularExpr - - -#=============================================================================== -# str2float -#=============================================================================== -def str2float(s): - """ - Ritorna la conversione di una stringa in numero reale - """ - try: - n = float(s) - return n - except ValueError: - return None - - -#=============================================================================== -# str2long -#=============================================================================== -def str2long(s): - """ - Ritorna la conversione di una stringa in numero lungo - """ - try: - n = long(s) - return n - except ValueError: - return None - - -#=============================================================================== -# str2int -#=============================================================================== -def str2int(s): - """ - Ritorna la conversione di una stringa in numero intero - """ - try: - n = int(s) - return n - except ValueError: - return None - - -#=============================================================================== -# str2bool -#=============================================================================== -def str2bool(s): - """ - Ritorna la conversione di una stringa in bool - """ - try: - upperS = s.upper() - # 16 = "N", 17 = "NO" - # "F" "FALSO" - if upperS == "0" or \ - upperS == QadMsg.translate("QAD", "N") or \ - upperS == QadMsg.translate("QAD", "NO") or \ - upperS == QadMsg.translate("QAD", "F") or \ - upperS == QadMsg.translate("QAD", "FALSE"): - return False - else: - return True - except ValueError: - return None - - -#=============================================================================== -# str2QgsPoint -#=============================================================================== -def str2QgsPoint(s, lastPoint = None, currenPoint = None, oneNumberAllowed = True): - """ - Ritorna la conversione di una stringa in punto QgsPoint - se = False significa che s non può essere un solo numero - che rappresenterebbe la distanza dall'ultimo punto con angolo in base al punto corrente - (questo viene vietato quando si vuole accettare un numero o un punto) - lastPoint viene usato solo per le espressioni tipo @10<45 (dall'ultimo punto, lunghezza 10, angolo 45 gradi) - o @ (dall'ultimo punto) - o @10,20 (dall'ultimo punto, + 10 per la X e + 20 per la Y) - o 100 (dall'ultimo punto, distanza 100, angolo in base al punto corrente) - """ - expression = s.strip() # senza spazi iniziali e finali - if len(expression) == 0: - return None - - if expression[0] == "@": # coordinate relative a lastpoint - if lastPoint is None: - return None - - if len(expression) == 1: - return lastPoint - - expression = expression[1:] # scarto il primo carattere "@" - coords = expression.split(",") - if len(coords) == 2: - OffSetX = str2float(coords[0].strip()) - OffSetY = str2float(coords[1].strip()) - if (OffSetX is None) or (OffSetY is None): - return None - return QgsPoint(lastPoint.x() + OffSetX, lastPoint.y() + OffSetY) - else: - if len(coords) != 1: - return None - # verifico se si sta usando la coordinata polare - expression = coords[0].strip() - values = expression.split("<") - if len(values) != 2: - return None - dist = str2float(values[0].strip()) - angle = str2float(values[1].strip()) - if (dist is None) or (angle is None): - return None - coords = getPolarPointByPtAngle(lastPoint, math.radians(angle), dist) - return QgsPoint(coords[0], coords[1]) - else: - # verifico se è specificato un CRS - CRS, newExpr = strFindCRS(expression) - if CRS is not None: - if CRS.geographicFlag(): - pt = strLatLon2QgsPoint(newExpr) - else: - coords = newExpr.split(",") - if len(coords) != 2: - return None - x = str2float(coords[0].strip()) - y = str2float(coords[1].strip()) - if (x is None) or (y is None): - return None - pt = QgsPoint(x, y) - - if pt is not None: - destCRS = qgis.utils.iface.mapCanvas().mapRenderer().destinationCrs() # CRS corrente - return QgsCoordinateTransform(CRS, destCRS).transform(pt) # trasformo le coord - - - coords = expression.split(",") - if len(coords) == 2: # coordinate assolute - x = str2float(coords[0].strip()) - y = str2float(coords[1].strip()) - if (x is None) or (y is None): - return None - return QgsPoint(x, y) - else: - if oneNumberAllowed == False: # vietato che la stringa sia un solo numero - return None - - dist = str2float(expression) - - if (dist is None) or (lastPoint is None) or (currenPoint is None): - return None - - angle = getAngleBy2Pts(lastPoint, currenPoint) - coords = getPolarPointByPtAngle(lastPoint, angle, dist) - return QgsPoint(coords[0], coords[1]) - -#=============================================================================== -# strLatLon2QgsPoint -#=============================================================================== -def strFindCRS(s): - """ - Cerca il sistema di coordinate in una stringa indicante un punto (usa authid). - Il sistema di coordinate va espresso in qualsiasi punto della stringa e deve essere - racchiuso tra parentesi tonde (es "111,222 (EPSG:3003)") - Ritorna il SR e la stringa depurata del SR (es "111,222") - """ - initial = string.find(s, "(") - if initial == -1: - return None, s - final = string.find(s, ")") - if initial > final: - return None, s - authId = s[initial+1:final] - authId = authId.strip() # senza spazi iniziali e finali - return QgsCoordinateReferenceSystem(authId), s.replace(s[initial:final+1], "") - - -#=============================================================================== -# strLatLon2QgsPoint -#=============================================================================== -def strLatLon2QgsPoint(s): - """ - Ritorna la conversione di una stringa contenente una coordinata in latitudine longitudine - in punto QgsPoint. - - Sono supportati i seguenti formati: - DDD gradi decimali (49.11675S o S49.11675 o 49.11675 S o S 49.11675 o -49.1167) - DMS gradi minuti secondi (49 7 20.06) - DMM gradi minuti con secondi decimali (49 7.0055) - - Sintassi latitudine longitudine: - Il separatore può essere uno spazio, puoi anche usare ' per i minuti e " per i secondi (47 7'20.06") - La notazione di direzione è N, S, E, W maiuscolo o minuscolo prima o dopo la coordinata - ("N 37 24 23.3" o "N37 24 23.3" o "37 24 23.3 N" o "37 24 23.3N") - Puoi usare anche le coordinate negative per l'ovest e il sud. - - La prima coordinata viene interpretata come latitudine a meno che specifichi una lettera di direzione (E o W) - ("122 05 08.40 W 37 25 19.07 N") - Puoi usare uno spazio, una virgola o una barra per delimitare le coppie di valori - ("37.7 N 122.2 W" o "37.7 N,122.2 W" o "37.7 N/122.2 W") - """ - expression = s.strip() # senza spazi iniziali e finali - if len(expression) == 0: - return None - - numbers = [] - directions = [] - word = "" - for ch in s: - if ch.isnumeric() or ch == "." or ch == "-": - word += ch - else: - if len(word) > 0: - n = str2float(word) - if n is None: - return None - numbers.append(n) - word = "" - if ch == "N" or ch == "n" or ch == "S" or ch == "s" or ch == "E" or ch == "e" or ch == "W" or ch == "w": - directions.append(ch.upper()) - word = "" - - directions_len = len(directions) - if directions_len != 0 and directions_len != 2: - return None - - numbers_len = len(numbers) - if numbers_len == 2: # DDD - lat = numbers[0] - lon = numbers[1] - elif numbers_len == 4: # DMM - degrees = numbers[0] - minutes = numbers[1] - lat = degrees + minutes / 60 - degrees = numbers[2] - minutes = numbers[3] - lon = degrees + minutes / 60 - elif numbers_len == 6: # DMS - degrees = numbers[0] - minutes = numbers[1] - seconds = numbers[2] - lat = degrees + minutes / 60 + seconds / 3600 - degrees = numbers[3] - minutes = numbers[4] - seconds = numbers[5] - lon = degrees + minutes / 60 + seconds / 3600 - else: - return None - - if directions_len == 2: - if lat < 0 or lon < 0: - return None - if directions[0] == "N" or directions[0] == "S": # latitude first - if directions[0] == "S": - lat = -lat - elif directions[0] == "E" or directions[0] == "W": # longitude first - dummy = lat - lat = lon if directions[0] == "E" else -lon - lon = dummy if directions[1] == "S" else -value2 - else: - return None - - return QgsPoint(lon, lat) - else: # latitude first - return QgsPoint(lon, lat) - - -#=============================================================================== -# str2snapTypeEnum -#=============================================================================== -def str2snapTypeEnum(s): - """ - Ritorna la conversione di una stringa in una combinazione di tipi di snap - oppure -1 se non ci sono snap indicati. - """ - snapType = QadSnapTypeEnum.NONE - snapTypeStrList = s.strip().split(",") - for snapTypeStr in snapTypeStrList: - snapTypeStr = snapTypeStr.strip().upper() - - # "NES" nessuno snap - if snapTypeStr == QadMsg.translate("Snap", "NONE") or snapTypeStr == "_NONE": - return QadSnapTypeEnum.NONE - # "FIN" punti finali di ogni segmento - elif snapTypeStr == QadMsg.translate("Snap", "END") or snapTypeStr == "_END": - snapType = snapType | QadSnapTypeEnum.END - # "FIN_PL" punti finali dell'intera polilinea - elif snapTypeStr == QadMsg.translate("Snap", "END_PL") or snapTypeStr == "_END_PL": - snapType = snapType | QadSnapTypeEnum.END_PLINE - # "MED" punto medio - elif snapTypeStr == QadMsg.translate("Snap", "MID") or snapTypeStr == "_MID": - snapType = snapType | QadSnapTypeEnum.MID - # "CEN" centro (centroide) - elif snapTypeStr == QadMsg.translate("Snap", "CEN") or snapTypeStr == "_CEN": - snapType = snapType | QadSnapTypeEnum.CEN - # "NOD" oggetto punto - elif snapTypeStr == QadMsg.translate("Snap", "NOD") or snapTypeStr == "_NOD": - snapType = snapType | QadSnapTypeEnum.NOD - # "QUA" punto quadrante - elif snapTypeStr == QadMsg.translate("Snap", "QUA") or snapTypeStr == "_QUA": - snapType = snapType | QadSnapTypeEnum.QUA - # "INT" intersezione - elif snapTypeStr == QadMsg.translate("Snap", "INT") or snapTypeStr == "_INT": - snapType = snapType | QadSnapTypeEnum.INT - # "INS" punto di inserimento - elif snapTypeStr == QadMsg.translate("Snap", "INS") or snapTypeStr == "_INS": - snapType = snapType | QadSnapTypeEnum.INS - # "PER" punto perpendicolare - elif snapTypeStr == QadMsg.translate("Snap", "PER") or snapTypeStr == "_PER": - snapType = snapType | QadSnapTypeEnum.PER - # "TAN" tangente - elif snapTypeStr == QadMsg.translate("Snap", "TAN") or snapTypeStr == "_TAN": - snapType = snapType | QadSnapTypeEnum.TAN - # "VIC" punto più vicino - elif snapTypeStr == QadMsg.translate("Snap", "NEA") or snapTypeStr == "_NEA": - snapType = snapType | QadSnapTypeEnum.NEA - # "APP" intersezione apparente - elif snapTypeStr == QadMsg.translate("Snap", "APP") or snapTypeStr == "_APP": - snapType = snapType | QadSnapTypeEnum.APP - # "EST" Estensione - elif snapTypeStr == QadMsg.translate("Snap", "EXT") or snapTypeStr == "_EXT": - snapType = snapType | QadSnapTypeEnum.EXT - # "PAR" Parallelo - elif snapTypeStr == QadMsg.translate("Snap", "PAR") or snapTypeStr == "_PAR": - snapType = snapType | QadSnapTypeEnum.PAR - # se inizia per "PR" distanza progressiva - elif string.find(snapTypeStr, QadMsg.translate("Snap", "PR")) == 0 or \ - string.find(snapTypeStr, "_PR") == 0: - # la parte successiva PR può essere vuota o numerica - if string.find(snapTypeStr, QadMsg.translate("Snap", "PR")) == 0: - param = snapTypeStr[len(QadMsg.translate("Snap", "PR")):] - else: - param = snapTypeStr[len("_PR"):] - if len(param) == 0 or str2float(param) is not None: - snapType = snapType | QadSnapTypeEnum.PR - # "EST_INT" intersezione su estensione - elif snapTypeStr == QadMsg.translate("Snap", "EXT_INT") or snapTypeStr == "_EXT_INT": - snapType = snapType | QadSnapTypeEnum.EXT_INT - - return snapType if snapType != QadSnapTypeEnum.NONE else -1 - - -#=============================================================================== -# str2snapParam -#=============================================================================== -def str2snapParams(s): - """ - Ritorna la conversione di una stringa in una lista di parametri per i tipi di snap - """ - params = [] - snapTypeStrList = s.strip().split(",") - for snapTypeStr in snapTypeStrList: - snapTypeStr = snapTypeStr.strip().upper() - # se inizia per "PR" distanza progressiva - if string.find(snapTypeStr, QadMsg.translate("Snap", "PR")) == 0 or \ - string.find(snapTypeStr, "_PR") == 0: - # la parte successiva PR può essere vuota o numerica - if string.find(snapTypeStr, QadMsg.translate("Snap", "PR")) == 0: - param = str2float(snapTypeStr[len(QadMsg.translate("Snap", "PR")):]) # fino alla fine della stringa - else: - param = str2float(snapTypeStr[len("_PR"):]) # fino alla fine della stringa - if param is not None: - params.append([QadSnapTypeEnum.PR, param]) - - return params - - -#=============================================================================== -# strip -#=============================================================================== -def strip(s, stripList): - """ - Rimuove dalla stringa tutte le stringhe nella lista che sono - all'inizio e anche alla fine della stringa - """ - for item in stripList: - s = s.strip(item) # rimuovo prima e dopo - return s - - -#=============================================================================== -# findFile -#=============================================================================== -def findFile(fileName): - """ - Cerca il file indicato usando i percorsi indicati dalla variabile "SUPPORTPATH" - più il percorso locale di QAD. Ritorna il percorso del file in caso di successo - oppure "" in caso di file non trovato - """ - path = QadVariables.get(QadMsg.translate("Environment variables", "SUPPORTPATH")) - if len(path) > 0: - path += ";" - path += QgsApplication.qgisSettingsDirPath() + "python/plugins/qad/" - # lista di directory separate da ";" - dirList = path.strip().split(";") - for _dir in dirList: - _dir = QDir.cleanPath(_dir) - if _dir != "": - if _dir.endswith("/") == False: - _dir = _dir + "/" - _dir = _dir + fileName - - if os.path.exists(_dir): - return _dir - - return "" - - -#=============================================================================== -# toRadians -#=============================================================================== -def toRadians(angle): - """ - Converte da gradi a radianti - """ - return math.radians(angle) - - -#=============================================================================== -# toDegrees -#=============================================================================== -def toDegrees(angle): - """ - Converte da radianti a gradi - """ - return math.degrees(angle) - - -#=============================================================================== -# normalizeAngle -#=============================================================================== -def normalizeAngle(angle, norm = math.pi * 2): - """ - Normalizza un angolo a da [0 - 2pi] o da [0 - pi]. - Così, ad esempio, se un angolo é più grande di 2pi viene ridotto all'angolo giusto - (il raffronto in gradi sarebbe da 380 a 20 gradi) o se é negativo diventa positivo - (il raffronto in gradi sarebbe da -90 a 270 gradi) - """ - if angle == 0: - return 0 - if angle > 0: - return angle % norm - else: - return norm - ((-angle) % norm) - - -#=============================================================================== -# getStrIntDecParts -#=============================================================================== -def getStrIntDecParts(n): - """ - Restituisce due stringhe rappresentanti la parte intera senza segno e la parte decimale di un numero - """ - if type(n) == int or type(n) == float: - nStr = str(n) - if "." in nStr: - parts = nStr.split(".") - return str(abs(int(parts[0]))), parts[1] - else: - return n, 0 - else: - return None - - - - -#=============================================================================== -# distMapToLayerCoordinates -#=============================================================================== -def distMapToLayerCoordinates(dist, canvas, layer): - # trovo il punto centrale dello schermo - boundBox = canvas.extent() - x = (boundBox.xMinimum() + boundBox.xMaximum()) / 2 - y = (boundBox.yMinimum() + boundBox.yMaximum()) / 2 - pt1 = QgsPoint(x, y) - pt2 = QgsPoint(x + dist, y) - transformedPt1 = canvas.mapRenderer().mapToLayerCoordinates(layer, pt1) - transformedPt2 = canvas.mapRenderer().mapToLayerCoordinates(layer, pt2) - return getDistance(transformedPt1, transformedPt2) - - -#=============================================================================== -# filterFeaturesByType -#=============================================================================== -def filterFeaturesByType(features, filterByGeomType): - """ - Riceve una lista di features e la tipologia di geometria che deve essere filtrata. - La funzine modifica la lista depurandola dalle geometrie di tipo diverso - da . - Restituisce 3 liste rispettivamente di punti, linee e poligoni. - La lista del tipo indicato dal parametro sarà vuota, le altre - due liste conterranno geometrie. - """ - resultPoint = [] - resultLine = [] - resultPolygon = [] - - for i in xrange(len(features) - 1, -1, -1): - f = features[i] - g = f.geometry() - geomType = g.type() - if geomType != filterByGeomType: - if geomType == QGis.Point: - resultPoint.append(QgsGeometry(g)) - elif geomType == QGis.Line: - resultLine.append(QgsGeometry(g)) - elif geomType == QGis.Polygon: - resultPolygon.append(QgsGeometry(g)) - del features[i] - - return resultPoint, resultLine, resultPolygon - - -#=============================================================================== -# filterGeomsByType -#=============================================================================== -def filterGeomsByType(geoms, filterByGeomType): - """ - Riceve una lista di geometrie e la tipologia di geometria che deve essere filtrata. - La funzine modifica la lista depurandola dalle geometrie di tipo diverso - da . - Restituisce 3 liste rispettivamente di punti, linee e poligoni. - La lista del tipo indicato dal parametro sarà vuota, le altre - due liste conterranno geometrie. - """ - resultPoint = [] - resultLine = [] - resultPolygon = [] - - for i in xrange(len(geoms) - 1, -1, -1): - g = geoms[i] - geomType = g.type() - if geomType != filterByGeomType: - if geomType == QGis.Point: - resultPoint.append(QgsGeometry(g)) - elif geomType == QGis.Line: - resultLine.append(QgsGeometry(g)) - elif geomType == QGis.Polygon: - resultPolygon.append(QgsGeometry(g)) - del geoms[i] - - return resultPoint, resultLine, resultPolygon - - -#=============================================================================== -# getEntSelCursor -#=============================================================================== -def getEntSelCursor(): - """ - Ritorna l'immagine del cursore per la selezione di un'entità - """ - - size = 1 + QadVariables.get(QadMsg.translate("Environment variables", "PICKBOX")) * 2 - # - row = str(size) + " " + str(size) + " 2 1" - xpm = [row] - # - xpm.append(" c None") - xpm.append("+ c " + QadVariables.get(QadMsg.translate("Environment variables", "PICKBOXCOLOR"))) - # - # es . "+++++", - # es . "+ +", - # es . "+ +", - # es . "+ +", - # es . "+++++", - xpm.append("+" * size) - if size > 1: - row = "+" + " " * (size - 2) + "+" - for i in range(size - 2): # da 0 - xpm.append(row) - xpm.append("+" * size) - - return QCursor(QPixmap(xpm)) - - -def getGetPointCursor(): - """ - Ritorna l'immagine del cursore per la selezione di un punto - """ - pickBox = QadVariables.get(QadMsg.translate("Environment variables", "CURSORSIZE")) - size = 1 + pickBox * 2 - # - row = str(size) + " " + str(size) + " 2 1" - xpm = [row] - # - xpm.append(" c None") - xpm.append("+ c " + QadVariables.get(QadMsg.translate("Environment variables", "PICKBOXCOLOR"))) - # - # es . " + ", - # es . " + ", - # es . "+++++", - # es . " + ", - # es . " + ", - row = (" " * pickBox) + "+" + (" " * pickBox) - xpm.append(row) - if size > 1: - for i in range(pickBox - 1): # da 0 - xpm.append(row) - xpm.append("+" * (size)) - for i in range(pickBox - 1): # da 0 - xpm.append(row) - - return QCursor(QPixmap(xpm)) - - -#=============================================================================== -# getFeatureRequest -#=============================================================================== -def getFeatureRequest(fetchAttributes = [], fetchGeometry = True, \ - rect = None, useIntersect = False): - # PER ORA NON VIENE USATO PERCHE' NON SO FARE IL CAST in QgsFeatureRequest.Flags - # restituisce un oggetto QgsFeatureRequest per interrogare un layer - # It can get 4 arguments, all of them are optional: - # fetchAttributes: List of attributes which should be fetched. - # None = disable fetching attributes, Empty list means that all attributes are used. - # default: empty list - # fetchGeometry: Whether geometry of the feature should be fetched. Default: True - # rect: Spatial filter by rectangle. - # None = nessuna ricerca spaziale, empty rect means (QgsRectangle()), all features are fetched. - # Default: none - # useIntersect: When using spatial filter, this argument says whether accurate test for intersection - # should be done or whether test on bounding box suffices. - # This is needed e.g. for feature identification or selection. Default: False - - request = QgsFeatureRequest() - - #flag = QgsFeatureRequest.NoFlags - -# if fetchGeometry == False: -# flag = flag | QgsFeatureRequest.NoGeometry - - if rect is not None: - r = QgsRectangle(rect) - - # Se il rettangolo é schiacciato in verticale o in orizzontale - # risulta una linea e la funzione fa casino, allora in questo caso lo allargo un pochino - if doubleNear(r.xMinimum(), r.xMaximum(), 1.e-6): - r.setXMaximum(r.xMaximum() + 1.e-6) - r.setXMinimum(r.xMinimum() - 1.e-6) - if doubleNear(r.yMinimum(), r.yMaximum(), 1.e-6): - r.setYMaximum(r.yMaximum() + 1.e-6) - r.setYMinimum(r.yMinimum() - 1.e-6) - - request.setFilterRect(r) - - if useIntersect == True: - request.setFlags(QgsFeatureRequest.ExactIntersect) - - if fetchAttributes is None: - request.setSubsetOfAttributes([]) - else: - if len(fetchAttributes) > 0: - request.setSubsetOfAttributes(fetchAttributes) - - return request - - -#=============================================================================== -# getEntSel -#=============================================================================== -def getEntSel(point, mQgsMapTool, \ - layersToCheck = None, checkPointLayer = True, checkLineLayer = True, checkPolygonLayer = True, - onlyBoundary = True, onlyEditableLayers = False): - """ - dato un punto (in screen coordinates) e un QgsMapTool, - la funzione cerca la prima entità dentro il quadrato - di dimensioni PICKBOX centrato sul punto - layersToCheck = opzionale, lista dei layer in cui cercare - checkPointLayer = opzionale, considera i layer di tipo punto - checkLineLayer = opzionale, considera i layer di tipo linea - checkPolygonLayer = opzionale, considera i layer di tipo poligono - onlyBoundary = serve per considerare solo il bordo dei poligoni o anche il loro interno - Restituisce una lista composta da una QgsFeature e il suo layer e il punto di selezione - in caso di successo altrimenti None - """ - - if checkPointLayer == False and checkLineLayer == False and checkPolygonLayer == False: - return None - - feature = QgsFeature() - Tolerance = QadVariables.get(QadMsg.translate("Environment variables", "PICKBOX")) # leggo la tolleranza - - #QApplication.setOverrideCursor(Qt.WaitCursor) - - if layersToCheck is None: - # Tutti i layer visibili visibili - _layers = mQgsMapTool.canvas.layers() - else: - # solo la lista passata come parametro - _layers = layersToCheck - - for layer in _layers: # ciclo sui layer - # considero solo i layer vettoriali che sono filtrati per tipo - if (layer.type() == QgsMapLayer.VectorLayer) and \ - ((layer.geometryType() == QGis.Point and checkPointLayer == True) or \ - (layer.geometryType() == QGis.Line and checkLineLayer == True) or \ - (layer.geometryType() == QGis.Polygon and checkPolygonLayer == True)) and \ - (onlyEditableLayers == False or layer.isEditable()): - layerCoords = mQgsMapTool.toLayerCoordinates(layer, point) - ToleranceInMapUnits = QgsTolerance.toleranceInMapUnits(Tolerance, layer, \ - mQgsMapTool.canvas.mapRenderer(), \ - QgsTolerance.Pixels) - - selectRect = QgsRectangle(layerCoords.x() - ToleranceInMapUnits, layerCoords.y() - ToleranceInMapUnits, \ - layerCoords.x() + ToleranceInMapUnits, layerCoords.y() + ToleranceInMapUnits) - - featureIterator = layer.getFeatures(getFeatureRequest([], True, selectRect, True)) - - # se é un layer contenente poligoni allora verifico se considerare solo i bordi - if onlyBoundary == False or layer.geometryType() != QGis.Polygon: - for feature in featureIterator: - return feature, layer, point - else: - # considero solo i bordi delle geometrie e non lo spazio interno dei poligoni - for feature in featureIterator: - # Riduco le geometrie in point o polyline - geoms = asPointOrPolyline(feature.geometry()) - for g in geoms: - if g.intersects(selectRect): - return feature, layer, point - -# # test per usare la cache (ancora più lento...) -# dummy, snappingResults = layer.snapWithContext(layerCoords, ToleranceInMapUnits, -# QgsSnapper.SnapToVertex if layer.geometryType() == QGis.Point else QgsSnapper.SnapToSegment) -# if len(snappingResults) > 0: -# featureId = snappingResults[0][1].snappedAtGeometry() -# feature = getFeatureById(layer, featureId) -# -# # se é un layer contenente poligoni allora verifico se considerare solo i bordi -# if onlyBoundary == False or layer.geometryType() != QGis.Polygon: -# return feature, layer, point -# else: -# geoms = asPointOrPolyline(feature.geometry()) -# for g in geoms: -# if g.intersects(selectRect): -# return feature, layer, point - - #QApplication.restoreOverrideCursor() - return None - - -#=============================================================================== -# getFeatureById -#=============================================================================== -def getFeatureById(layer, id): - """ - Ricava una feature dal suo id. - """ - feature = QgsFeature() - if layer.getFeatures(QgsFeatureRequest().setFilterFid(id)).nextFeature(feature): - return feature - else: - return None - - -#=============================================================================== -# isGeomInPickBox -#=============================================================================== -def isGeomInPickBox(point, mQgsMapTool, geom, crs = None, \ - checkPointLayer = True, checkLineLayer = True, checkPolygonLayer = True, - onlyBoundary = True): - """ - dato un punto (in screen coordinates) e un QgsMapTool, - la funzione verifica se la geometria é dentro il quadrato - di dimensioni PICKBOX centrato sul punto - geom = geometria da verificare - crs = sistema di coordinate della geometria (se = NON significa in map coordinates) - checkPointLayer = opzionale, considera la geometria di tipo punto - checkLineLayer = opzionale, considera la geometria di tipo linea - checkPolygonLayer = opzionale, considera la geometria di tipo poligono - onlyBoundary = serve per considerare solo il bordo dei poligoni o anche il loro interno - Restituisce True se la geometria é nel quadrato di PickBox altrimenti False - """ - if geom is None: - return False - if checkPointLayer == False and checkLineLayer == False and checkPolygonLayer == False: - return False - - Tolerance = QadVariables.get(QadMsg.translate("Environment variables", "PICKBOX")) # leggo la tolleranza - - # considero solo la geometria filtrata per tipo - if ((geom.type() == QGis.Point and checkPointLayer == True) or \ - (geom.type() == QGis.Line and checkLineLayer == True) or \ - (geom.type() == QGis.Polygon and checkPolygonLayer == True)): - mapPoint = mQgsMapTool.toMapCoordinates(point) - mapGeom = QgsGeometry(geom) - if crs is not None and mQgsMapTool.canvas.mapRenderer().destinationCrs() != crs: - # trasformo le coord della geometria in map coordinates - coordTransform = QgsCoordinateTransform(crs, mQgsMapTool.canvas.mapRenderer().destinationCrs()) - mapGeom.transform(coordTransform) - - ToleranceInMapUnits = Tolerance * mQgsMapTool.canvas.mapRenderer().mapUnitsPerPixel() - selectRect = QgsRectangle(mapPoint.x() - ToleranceInMapUnits, mapPoint.y() - ToleranceInMapUnits, \ - mapPoint.x() + ToleranceInMapUnits, mapPoint.y() + ToleranceInMapUnits) - - # se é una geometria poligono allora verifico se considerare solo i bordi - if onlyBoundary == False or geom.type() != QGis.Polygon: - if mapGeom.intersects(selectRect): - return True - else: - # considero solo i bordi della geometria e non lo spazio interno del poligono - # Riduco la geometria in point o polyline - geoms = asPointOrPolyline(mapGeom) - for g in geoms: - if g.intersects(selectRect): - return True - - return False - - -#=============================================================================== -# getGeomInPickBox -#=============================================================================== -def getGeomInPickBox(point, mQgsMapTool, geoms, crs = None, \ - checkPointLayer = True, checkLineLayer = True, checkPolygonLayer = True, - onlyBoundary = True): - """ - dato un punto (in screen coordinates) e un QgsMapTool, - la funzione cerca la prima geometria dentro il quadrato - di dimensioni PICKBOX centrato sul punto - geoms = lista di geometrie da verificare - crs = sistema di coordinate della geometria (se = NON significa in map coordinates) - checkPointLayer = opzionale, considera la geometria di tipo punto - checkLineLayer = opzionale, considera la geometria di tipo linea - checkPolygonLayer = opzionale, considera la geometria di tipo poligono - onlyBoundary = serve per considerare solo il bordo dei poligoni o anche il loro interno - Restituisce la geometria che é nel quadrato di PickBox altrimenti None - """ - if geoms is None: - return False - for geom in geoms: - if isGeomInPickBox(point, mQgsMapTool, geom, crs, checkPointLayer, checkLineLayer, checkPolygonLayer, onlyBoundary): - return geom - return None - - -#=============================================================================== -# getActualSingleSelection -#=============================================================================== -def getActualSingleSelection(layers): - """ - la funzione cerca se esiste una sola entità selezionata tra i layer - Restituisce un QgsFeature e il suo layer in caso di successo altrimenti None - """ - selFeature = [] - - for layer in layers: # ciclo sui layer - if (layer.type() == QgsMapLayer.VectorLayer): - selectedFeatureCount = layer.selectedFeaturCount() - if selectedFeatureCount == 1: - selFeature = layer.selectedFeatures() - selLayer = Layer - elif selectedFeatureCount > 1: - del selFeature[:] # svuoto la lista - break - - if len(selFeature) == 1: # se c'era solo una entità selezionata - return selFeature[0], selLayer - - return None - - -def deselectAll(layers): - """ - la funzione deseleziona tutte le entità selezionate nei layer - """ - selFeatureIds = [] - for layer in layers: # ciclo sui layer - if (layer.type() == QgsMapLayer.VectorLayer): - if layer.selectedFeaturesIds() > 0: - layer.setSelectedFeatures(selFeatureIds) - - -#=============================================================================== -# getSelSet -#=============================================================================== -def getSelSet(mode, mQgsMapTool, points = None, \ - layersToCheck = None, checkPointLayer = True, checkLineLayer = True, checkPolygonLayer = True, - onlyEditableLayers = False): - """ - dato un QgsMapTool, una modalità di selezione e una lista opzionale di punti (in map coordinates), - la funzione cerca le entità. - mode = "C" -> Crossing selection (inside and crossing) - "CP" -> Crossing polygon (inside and crossing) - "F" -> Fence selection (crossing) - "W" -> Window selection (inside) - "WP" -> Windows polygon (inside) - "X" -> all - layer = opzionale, lista dei layer in cui cercare - checkPointLayer = opzionale, considera i layer di tipo punto - checkLineLayer = opzionale, considera i layer di tipo linea - checkPolygonLayer = opzionale, considera i layer di tipo poligono - onlyEditableLayers = opzionale, considera i layer editabili - Restituisce un QadEntitySet in caso di successo altrimenti None - """ - - if checkPointLayer == False and checkLineLayer == False and checkPolygonLayer == False: - return None - - entity = QadEntity() - result = QadEntitySet() - feature = QgsFeature() - - #QApplication.setOverrideCursor(Qt.WaitCursor) - - if layersToCheck is None: - # Tutti i layer visibili visibili - _layers = mQgsMapTool.canvas.layers() - else: - # solo la lista passata come parametro - _layers = layersToCheck - - for layer in _layers: # ciclo sui layer - # considero solo i layer vettoriali che sono filtrati per tipo - if (layer.type() == QgsMapLayer.VectorLayer) and \ - ((layer.geometryType() == QGis.Point and checkPointLayer == True) or \ - (layer.geometryType() == QGis.Line and checkLineLayer == True) or \ - (layer.geometryType() == QGis.Polygon and checkPolygonLayer == True)) and \ - (onlyEditableLayers == False or layer.isEditable()): - provider = layer.dataProvider() - - if mode.upper() == "X": # take all features - # fetchAttributes, fetchGeometry, rectangle, useIntersect - for feature in layer.getFeatures(getFeatureRequest([], True, None, False)): - entity.set(layer, feature.id()) - result.addEntity(entity) - elif mode.upper() == "C": # crossing selection - p1 = mQgsMapTool.toLayerCoordinates(layer, points[0]) - p2 = mQgsMapTool.toLayerCoordinates(layer, points[1]) - selectRect = QgsRectangle(p1, p2) - # Select features in rectangle - # fetchAttributes, fetchGeometry, rectangle, useIntersect - for feature in layer.getFeatures(getFeatureRequest([], True, selectRect, True)): - entity.set(layer, feature.id()) - result.addEntity(entity) - elif mode.upper() == "W": # window selection - p1 = mQgsMapTool.toLayerCoordinates(layer, points[0]) - p2 = mQgsMapTool.toLayerCoordinates(layer, points[1]) - selectRect = QgsRectangle(p1, p2) - g = QgsGeometry.fromRect(selectRect) - # Select features in rectangle - # fetchAttributes, fetchGeometry, rectangle, useIntersect - for feature in layer.getFeatures(getFeatureRequest([], True, selectRect, True)): - # solo le feature completamente interne al rettangolo - if g.contains(feature.geometry()): - entity.set(layer, feature.id()) - result.addEntity(entity) - elif mode.upper() == "CP": # crossing polygon - polyline = [] - for point in points: - polyline.append(mQgsMapTool.toLayerCoordinates(layer, point)) - - g = QgsGeometry.fromPolygon([polyline]) - # Select features in the polygon bounding box - # fetchAttributes, fetchGeometry, rectangle, useIntersect - for feature in layer.getFeatures(getFeatureRequest([], True, g.boundingBox(), True)): - # solo le feature intersecanti il poligono - if g.intersects(feature.geometry()): - entity.set(layer, feature.id()) - result.addEntity(entity) - elif mode.upper() == "WP": # windows polygon - polyline = [] - for point in points: - polyline.append(mQgsMapTool.toLayerCoordinates(layer, point)) - - g = QgsGeometry.fromPolygon([polyline]) - # Select features in the polygon bounding box - # fetchAttributes, fetchGeometry, rectangle, useIntersect - for feature in layer.getFeatures(getFeatureRequest([], True, g.boundingBox(), True)): - # solo le feature completamente interne al poligono - if g.contains(feature.geometry()): - entity.set(layer, feature.id()) - result.addEntity(entity) - elif mode.upper() == "CO": # crossing object - # points é in questo caso un QgsGeometry - g = QgsGeometry(points) - if mQgsMapTool.canvas.mapRenderer().destinationCrs() != layer.crs(): - coordTransform = QgsCoordinateTransform(mQgsMapTool.canvas.mapRenderer().destinationCrs(), \ - layer.crs()) # trasformo la geometria - g.transform(coordTransform) - - # Select features in the object bounding box - wkbType = g.wkbType() - if wkbType == QGis.WKBPoint or wkbType == QGis.WKBPoint25D: - Tolerance = QadVariables.get(QadMsg.translate("Environment variables", "PICKBOX")) # leggo la tolleranza - ToleranceInMapUnits = QgsTolerance.toleranceInMapUnits(Tolerance, layer, \ - mQgsMapTool.canvas.mapRenderer(), \ - QgsTolerance.Pixels) - - pt = g.asPoint() - # QgsRectangle (double xmin=0, double ymin=0, double xmax=0, double ymax=0) - selectRect = QgsRectangle(pt.x() - ToleranceInMapUnits, pt.y() - ToleranceInMapUnits, \ - pt.x() + ToleranceInMapUnits, pt.y() + ToleranceInMapUnits) - # fetchAttributes, fetchGeometry, rectangle, useIntersect - request = getFeatureRequest([], True, selectRect, True) - else: - # fetchAttributes, fetchGeometry, rectangle, useIntersect - request = getFeatureRequest([], True, g.boundingBox(), True) - - # fetchAttributes, fetchGeometry, rectangle, useIntersect - for feature in layer.getFeatures(request): - # solo le feature intersecanti l'oggetto - if g.intersects(feature.geometry()): - entity.set(layer, feature.id()) - result.addEntity(entity) - elif mode.upper() == "WO": # windows object - # points é in questo caso un QgsGeometry - g = QgsGeometry(points) - if mQgsMapTool.canvas.mapRenderer().destinationCrs() != layer.crs(): - coordTransform = QgsCoordinateTransform(mQgsMapTool.canvas.mapRenderer().destinationCrs(), \ - layer.crs()) # trasformo la geometria - g.transform(coordTransform) - - # Select features in the object bounding box - wkbType = g.wkbType() - if wkbType == QGis.WKBPoint or wkbType == QGis.WKBPoint25D: - Tolerance = QadVariables.get(QadMsg.translate("Environment variables", "PICKBOX")) # leggo la tolleranza - ToleranceInMapUnits = QgsTolerance.toleranceInMapUnits(Tolerance, layer, \ - mQgsMapTool.canvas.mapRenderer(), \ - QgsTolerance.Pixels) - - pt = g.asPoint() - selectRect = QgsRectangle(pt.x() - ToleranceInMapUnits, pt.y() - ToleranceInMapUnits, \ - pt.x() + ToleranceInMapUnits, pt.y() + ToleranceInMapUnits) - # fetchAttributes, fetchGeometry, rectangle, useIntersect - request = getFeatureRequest([], True, selectRect, True) - else: - # fetchAttributes, fetchGeometry, rectangle, useIntersect - request = getFeatureRequest([], True, g.boundingBox(), True) - - # solo le feature completamente interne all'oggetto - for feature in layer.getFeatures(request): - if g.contains(feature.geometry()): - entity.set(layer, feature.id()) - result.addEntity(entity) - elif mode.upper() == "F": # fence - polyline = [] - for point in points: - polyline.append(mQgsMapTool.toLayerCoordinates(layer, point)) - - g = QgsGeometry() - g = QgsGeometry.fromPolyline(polyline) - # Select features in the polyline bounding box - # fetchAttributes, fetchGeometry, rectangle, useIntersect - for feature in layer.getFeatures(getFeatureRequest([], True, g.boundingBox(), True)): - # solo le feature che intersecano la polyline - if g.intersects(feature.geometry()): - entity.set(layer, feature.id()) - result.addEntity(entity) - - #QApplication.restoreOverrideCursor() - return result - - -#=============================================================================== -# appendUniquePointToList -#=============================================================================== -def appendUniquePointToList(pointList, point): - """ - Aggiunge un punto alla lista verificando che non sia già presente. - Resituisce True se l'inserimento é avvenuto False se il punto c'era già. - """ - for iPoint in pointList: - if ptNear(iPoint, point): - return False - - pointList.append(point) - return True - - -#=============================================================================== -# getIntersectionPoints -#=============================================================================== -def getIntersectionPoints(geom1, geom2, checkForCurves = False): - """ - la funzione ritorna una lista dei punti di intersezione tra le 2 geometrie. - Purtroppo non posso usare QgsGeometry.intersection perché non usa una tolleranza - (le geometrie spesso vengono convertite in un'altro crs - e poi riconvertite in quello originale perdendo precisione) - """ - result = [] - # Riduco le geometrie in point o polyline - geoms1 = asPointOrPolyline(geom1) - geoms2 = asPointOrPolyline(geom2) - - for g1 in geoms1: - wkbType1 = g1.wkbType() - if wkbType1 == QGis.WKBPoint: - pt1 = g1.asPoint() - for g2 in geoms2: - wkbType2 = g2.wkbType() - if wkbType2 == QGis.WKBPoint: - if ptNear(pt1, g2.asPoint()): - appendUniquePointToList(result, pt1) - elif wkbType2 == QGis.WKBLineString: - points2 = g2.asPolyline() - p2Start = points2[0] - for i in xrange(1, len(points2), 1): - p2End = points2[i] - if isPtOnSegment(p2Start, p2End, pt1): - appendUniquePointToList(result, pt1) - break - p2Start = p2End - elif wkbType1 == QGis.WKBLineString: - points1 = g1.asPolyline() - p1Start = points1[0] - for i in xrange(1, len(points1), 1): - p1End = points1[i] - for g2 in geoms2: - wkbType2 = g2.wkbType() - if wkbType2 == QGis.WKBPoint: - pt2 = g2.asPoint() - if isPtOnSegment(p1Start, p1End, pt2): - appendUniquePointToList(result, pt2) - elif wkbType2 == QGis.WKBLineString: - points2 = g2.asPolyline() - p2Start = points2[0] - for i in xrange(1, len(points2), 1): - p2End = points2[i] - intPt = getIntersectionPointOn2Segments(p1Start, p1End,p2Start, p2End) - if intPt is not None: - appendUniquePointToList(result, intPt) - p2Start = p2End - - p1Start = p1End - - return result - - -def getNextPolygonVertex(vertex, totalSegment): - return 0 if vertex == totalSegment - 1 else vertex + 1 -def getPrevPolygonVertex(vertex, totalSegment): - return totalSegment - 1 if vertex == 0 else vertex - 1 - -def getLinePart(geom, ptStart, ptEnd): - """ - la funzione ritorna una lista dei punti che rappresenta una linea che va dal - punto ptStart al punto ptEnd passando per il contorno di geom. - """ - if ptStart == ptEnd: - return None - - geomPtStart = QgsGeometry.fromPoint(ptStart) - geomPtEnd = QgsGeometry.fromPoint(ptEnd) - - isPolygon = True if geom.wkbType() == QGis.WKBPolygon else False - - # Riduco le geometrie in point o polyline - geoms = asPointOrPolyline(geom) - - for g in geoms: - if g.wkbType() == QGis.WKBPoint: - continue - points = g.asPolyline() - totalSegment = len(points) - 1 - - # cerco il segmento che contiene il punto iniziale - found = False - for segmentStart in xrange(0, totalSegment, 1): - geomSegment = QgsGeometry.fromPolyline([points[segmentStart], points[segmentStart + 1]]) - # se ci sono punti di intersezione - if len(getIntersectionPoints(geomSegment, geomPtStart)) > 0: - found = True - break - if found == False: - continue - - # cerco il segmento che contiene il punto finale - found = False - for segmentEnd in xrange(0, totalSegment, 1): - geomSegment = QgsGeometry.fromPolyline([points[segmentEnd], points[segmentEnd + 1]]) - # se ci sono punti di intersezione - if len(getIntersectionPoints(geomSegment, geomPtEnd)) > 0: - found = True - break - if found == False: - continue - - if isPolygon == False: - # trovata la polilinea che contiene il punto iniziale e finale - result = [ptStart] - if segmentStart < segmentEnd: - # se il punto ptStart é uguale al punto iniziale del segmento successivo - if ptStart == points[segmentStart + 1]: - segmentStart = segmentStart + 1 - - for i in xrange(segmentStart + 1, segmentEnd + 1, 1): - result.append(points[i]) - - elif segmentStart > segmentEnd: - # se il punto ptEnd é uguale al punto finale del segmento - if ptEnd == points[segmentEnd + 1]: - segmentEnd = segmentEnd + 1 - - for i in xrange(segmentStart, segmentEnd, -1): - result.append(points[i]) - - result.append(ptEnd) - else: - # do il senso di circolarità - if ptStart == points[0]: - segmentStart = totalSegment - 1 - - if segmentStart == segmentEnd: - return [ptStart, ptEnd] - # Se é un poligono devo verificare il percorso più corto da ptStart e ptEnd - - # seguo il senso dei vertici - result1 = [ptStart] - # se il punto ptStart é uguale al punto iniziale del segmento successivo - i = segmentStart - nextSegment = getNextPolygonVertex(segmentStart, totalSegment) - if ptStart == points[nextSegment]: - i = nextSegment - - i = getNextPolygonVertex(i, totalSegment) - nextSegment = getNextPolygonVertex(segmentEnd, totalSegment) - while i != nextSegment: - result1.append(points[i]) - i = getNextPolygonVertex(i, totalSegment) - - result1.append(ptEnd) - - # seguo il senso inverso dei vertici - result2 = [ptStart] - # se il punto ptEnd é uguale al punto finale del segmento - nextSegment = getNextPolygonVertex(segmentEnd, totalSegment) - if ptEnd == points[nextSegment]: - segmentEnd = nextSegment - - i = segmentStart - segmentPrevEnd = getNextPolygonVertex(segmentEnd, totalSegment) - while i != segmentEnd: - result2.append(points[i]) - i = getPrevPolygonVertex(i, totalSegment) - - result2.append(ptEnd) - - g1 = QgsGeometry.fromPolyline(result1) - g2 = QgsGeometry.fromPolyline(result2) - - result = result1 if g1.length() < g2.length() else result2 - - return result - - return None - - -#=============================================================================== -# getPerpendicularPointOnInfinityLine -#=============================================================================== -def getPerpendicularPointOnInfinityLine(p1, p2, pt): - """ - la funzione ritorna il punto di proiezione perpendicolare di pt - alla linea passante per p1-p2. - """ - - diffX = p2.x() - p1.x() - diffY = p2.y() - p1.y() - - if doubleNear(diffX, 0): # se la retta passante per p1 e p2 é verticale - return QgsPoint(p1.x(), pt.y()) - elif doubleNear(diffY, 0): # se la retta passante per p1 e p2 é orizzontale - return QgsPoint(pt.x(), p1.y()) - else: - coeff = diffY / diffX - x = (coeff * p1.x() - p1.y() + pt.x() / coeff + pt.y()) / (coeff + 1 / coeff) - y = coeff * (x - p1.x()) + p1.y() - - return QgsPoint(x, y) - - -#=============================================================================== -# getInfinityLinePerpOnMiddle -#=============================================================================== -def getInfinityLinePerpOnMiddle(pt1, pt2): - """ - dato un segmento pt1-pt2, la funzione trova una linea perpendicolare al segmento - che passa per il suo punto medio. La funzione restituisce 2 punti della linea. - """ - ptMiddle = getMiddlePoint(pt1, pt2) - dist = getDistance(pt1, ptMiddle) - if dist == 0: - return None - angle = getAngleBy2Pts(pt1, pt2) + math.pi / 2 - pt2Middle = getPolarPointByPtAngle(ptMiddle, angle, dist) - return ptMiddle, pt2Middle - - -#=============================================================================== -# getBisectorInfinityLine -#=============================================================================== -def getBisectorInfinityLine(pt1, pt2, pt3, acuteMode = True): - """ - dato un angolo definito da 3 punti il cui secondo punto é vertice dell'angolo, - la funzione restituisce la linea bisettrice dell'angolo attraverso 2 punti - della linea (il vertice dell'angolo e un altro punto calcolato distante quanto - la distanza di pt1 da pt2). - acuteMode = True considera l'angolo acuto, acuteMode = False l'angolo ottuso - """ - angle1 = getAngleBy2Pts(pt2, pt1) - angle2 = getAngleBy2Pts(pt2, pt3) - angle = (angle1 + angle2) / 2 # angolo medio -# return pt2, getPolarPointByPtAngle(pt2, angle, 10) - - dist = getDistance(pt1, pt2) - ptProj = getPolarPointByPtAngle(pt2, angle, dist) - ptInverseProj = getPolarPointByPtAngle(pt2, angle - math.pi, dist) - if getDistance(pt1, ptProj) < getDistance(pt1, ptInverseProj): - if acuteMode == True: - return pt2, ptProj - else: - return pt2, ptInverseProj - else: - if acuteMode == True: - return pt2, ptInverseProj - else: - return pt2, ptProj - - -#=============================================================================== -# getXOnInfinityLine -#=============================================================================== -def getXOnInfinityLine(p1, p2, y): - """ - data la coordinata Y di un punto la funzione ritorna la coordinata X dello stesso - sulla linea passante per p1-p2 - """ - - diffX = p2.x() - p1.x() - diffY = p2.y() - p1.y() - - if doubleNear(diffX, 0): # se la retta passante per p1 e p2 é verticale - return p1.x() - elif doubleNear(diffY, 0): # se la retta passante per p1 e p2 é orizzontale - return None # infiniti punti - else: - coeff = diffY / diffX - return p1.x() + (y - p1.y()) / coeff - - -#=============================================================================== -# getYOnInfinityLine -#=============================================================================== -def getYOnInfinityLine(p1, p2, x): - """ - data la coordinata Y di un punto la funzione ritorna la coordinata X dello stesso - sulla linea passante per p1-p2 - """ - - diffX = p2.x() - p1.x() - diffY = p2.y() - p1.y() - - if doubleNear(diffX, 0): # se la retta passante per p1 e p2 é verticale - return None # infiniti punti - elif doubleNear(diffY, 0): # se la retta passante per p1 e p2 é orizzontale - return p1.y() - else: - coeff = diffY / diffX - return p1.y() + (x - p1.x()) * coeff - - -#=============================================================================== -# getSqrDistance -#=============================================================================== -def getSqrDistance(p1, p2): - """ - la funzione ritorna la distanza al quadrato tra 2 punti (QgsPoint) - """ - dx = p2.x() - p1.x() - dy = p2.y() - p1.y() - - return dx * dx + dy * dy - - -#=============================================================================== -# getDistance -#=============================================================================== -def getDistance(p1, p2): - """ - la funzione ritorna la distanza tra 2 punti (QgsPoint) - """ - return math.sqrt(getSqrDistance(p1, p2)) - - -#=============================================================================== -# getMinDistancePtBetweenSegmentAndPt -#=============================================================================== -def getMinDistancePtBetweenSegmentAndPt(p1, p2, pt): - """ - la funzione ritorna il punto di distanza minima e la distanza minima tra un segmento ed un punto - () - """ - if isPtOnSegment(p1, p2, pt) == True: - return [pt, 0] - perpPt = getPerpendicularPointOnInfinityLine(p1, p2, pt) - if perpPt is not None: - if isPtOnSegment(p1, p2, perpPt) == True: - return [perpPt, getDistance(perpPt, pt)] - - distFromP1 = getDistance(p1, pt) - distFromP2 = getDistance(p2, pt) - if distFromP1 < distFromP2: - return [p1, distFromP1] - else: - return [p2, distFromP2] - - -#=============================================================================== -# getMinDistancePtBetweenArcAndPt -#=============================================================================== -def getMinDistancePtBetweenArcAndPt(arc, pt): - """ - la funzione ritorna il punto di distanza minima e la distanza minima tra un arco ed un punto - () - """ - angle = getAngleBy2Pts(arc.center, pt) - if isAngleBetweenAngles(arc.startAngle, arc.endAngle, angle) == True: - return [getPolarPointByPtAngle(arc.center, angle, arc.radius), \ - math.fabs(getDistance(arc.center, pt) - arc.radius)] - - ptStart = arc.getStartPt() - ptEnd = arc.getEndPt() - distFromStartPt = getDistance(ptStart, pt) - distFromEndPt = getDistance(ptEnd, pt) - if distFromStartPt < distFromEndPt: - return [ptStart, distFromStartPt] - else: - return [ptEnd, distFromEndPt] - - -#=============================================================================== -# getMinDistancePtsBetween2Segments -#=============================================================================== -def getMinDistancePtsBetween2Segments(line1P1, line1P2, line2P1, line2P2): - """ - la funzione ritorna i punti di distanza minima e la distanza minima tra due segmenti - () - """ - intPt = getIntersectionPointOn2Segments(line1P1, line1P2, line2P1, line2P2) - if intPt is not None: - return [intPt, intPt, 0] - - # ritorna una lista: () - bestResult = getMinDistancePtBetweenSegmentAndPt(line2P1, line2P2, line1P1) - bestResult.insert(0, line1P1) - resultLine1P2 = getMinDistancePtBetweenSegmentAndPt(line2P1, line2P2, line1P2) - resultLine1P2.insert(0, line1P2) - if bestResult[2] > resultLine1P2[2]: - bestResult = resultLine1P2 - resultLine2P1 = getMinDistancePtBetweenSegmentAndPt(line1P1, line1P2, line2P1) - resultLine2P1.insert(1, line2P1) - if bestResult[2] > resultLine2P1[2]: - bestResult = resultLine2P1 - resultLine2P2 = getMinDistancePtBetweenSegmentAndPt(line1P1, line1P2, line2P2) - resultLine2P2.insert(1, line2P2) - if bestResult[2] > resultLine2P2[2]: - bestResult = resultLine2P2 - return bestResult - - -#=============================================================================== -# getMinDistancePtsBetweenSegmentAndArc -#=============================================================================== -def getMinDistancePtsBetweenSegmentAndArc(p1, p2, arc): - """ - la funzione ritorna i punti di distanza minima e la distanza minima tra un segmento ed un arco - () - """ - intPtList = arc.getIntersectionPointsWithSegment(p1, p2) - if len(intPtList) > 0: - return [intPtList[0], intPtList[0], 0] - - # ritorna una lista: () - resultP1 = getMinDistancePtBetweenArcAndPt(arc, p1) - resultP2 = getMinDistancePtBetweenArcAndPt(arc, p2) - # se il segmento é interno al cerchio orginato dall'estensione dell'arco - if getDistance(p1, arc.center) < arc.radius and \ - getDistance(p2, arc.center) < arc.radius: - if resultP1[1] < resultP2[1]: - return [p1, resultP1[0], resultP1[1]] - else: - return [p2, resultP2[0], resultP2[1]] - # se il segmento é esterno al cerchio orginato dall'estensione dell'arco - else: - perpPt = getPerpendicularPointOnInfinityLine(p1, p2, arc.center) - angle = getAngleBy2Pts(arc.center, perpPt) - # il punto di perpendicolare alla linea infinita p1,p2 é sul segmento e sull'arco - if isPtOnSegment(p1, p2, perpPt) == True and \ - isAngleBetweenAngles(arc.startAngle, arc.endAngle, angle) == True: - ptOnArc = getPolarPointByPtAngle(arc.center, angle, arc.radius) - return [perpPt, ptOnArc, getDistance(perpPt, ptOnArc)] - - bestResult = resultP1 - bestResult.insert(0, p1) - resultP2.insert(0, p2) - if bestResult[2] > resultP2[2]: - bestResult = resultP2 - - ptStart = arc.getStartPt() - ptEnd = arc.getEndPt() - - # ritorna una lista: () - resultStartPt = getMinDistancePtBetweenSegmentAndPt(p1, p2, ptStart) - resultStartPt.insert(1, ptStart) - if bestResult[2] > resultStartPt[2]: - bestResult = resultStartPt - resultEndPt = getMinDistancePtBetweenSegmentAndPt(p1, p2, ptEnd) - resultEndPt.insert(1, ptEnd) - if bestResult[2] > resultEndPt[2]: - bestResult = resultEndPt - return bestResult - - -#=============================================================================== -# getMinDistancePtsBetween2Arcs -#=============================================================================== -def getMinDistancePtsBetween2Arcs(arc1, arc2): - """ - la funzione ritorna i punti di distanza minima e la distanza minima tra due archi - () - """ - intPtList = arc1.getIntersectionPointsWithArc(arc2) - if len(intPtList) > 0: - return [intPtList[0], intPtList[0], 0] - - StartPtArc1 = arc1.getStartPt() - EndPtArc1 = arc1.getEndPt() - StartPtArc2 = arc2.getStartPt() - EndPtArc2 = arc2.getEndPt() - - # calcolo la minima distanza tra gli estremi di un arco e l'altro arco e - # scelgo la migliore tra le quattro distanze - # ritorna una lista: () - bestResult = getMinDistancePtBetweenArcAndPt(arc2, StartPtArc1) - bestResult.insert(0, StartPtArc1) - - resultArc2_EndPtArc1 = getMinDistancePtBetweenArcAndPt(arc2, EndPtArc1) - resultArc2_EndPtArc1.insert(0, EndPtArc1) - if bestResult[2] > resultArc2_EndPtArc1[2]: - bestResult = resultArc2_EndPtArc1 - - resultArc1_StartPtArc2 = getMinDistancePtBetweenArcAndPt(arc1, StartPtArc2) - resultArc1_StartPtArc2.insert(0, EndPtArc2) - if bestResult[2] > resultArc1_StartPtArc2[2]: - bestResult = resultArc1_StartPtArc2 - - resultArc1_EndPtArc2 = getMinDistancePtBetweenArcAndPt(arc1, EndPtArc2) - resultArc1_EndPtArc2.insert(0, EndPtArc2) - if bestResult[2] > resultArc1_EndPtArc2[2]: - bestResult = resultArc1_EndPtArc2 - - # il cerchio1 e il cerchio 2 sono derivati rispettivamente dall'estensione dell'arco1 e arco2. - circle1 = QadCircle() - circle1.set(arc1.center, arc1.radius) - circle2 = QadCircle() - circle2.set(arc2.center, arc2.radius) - distanceBetweenCenters = getDistance(circle1.center, circle2.center) - - # considero i seguenti 2 casi: - # i cerchi sono esterni - if distanceBetweenCenters - circle1.radius - circle2.radius > 0: - # creo un segmento che unisce i due centri e lo interseco con l'arco 1 - intPtListArc1 = arc1.getIntersectionPointsWithSegment(arc1.center, arc2.center) - if len(intPtListArc1) > 0: - intPtArc1 = intPtListArc1[0] - - # creo un segmento che unisce i due centri e lo interseco con l'arco 2 - intPtListArc2 = arc2.getIntersectionPointsWithSegment(arc1.center, arc2.center) - if len(intPtListArc2) > 0: - intPtArc2 = intPtListArc2[0] - - distanceIntPts = getDistance(intPtArc1, intPtArc2) - if bestResult[2] > distanceIntPts: - bestResult = [intPtArc1, intPtArc2, distanceIntPts] - # il cerchio1 é interno al cerchio2 oppure - # il cerchio2 é interno al cerchio1 - elif distanceBetweenCenters + circle1.radius < circle2.radius or \ - distanceBetweenCenters + circle2.radius < circle1.radius: - # creo un segmento che unisce i due centri e lo interseco con l'arco 2 - intPtListArc2 = arc2.getIntersectionPointsWithInfinityLine(arc1.center, arc2.center) - if len(intPtListArc2) > 0: - # creo un segmento che unisce i due centri e lo interseco con l'arco 1 - intPtListArc1 = arc1.getIntersectionPointsWithInfinityLine(arc1.center, arc2.center) - - for intPtArc2 in intPtListArc2: - for intPtArc1 in intPtListArc1: - distanceIntPts = getDistance(intPtArc2, intPtArc1) - if bestResult[2] > distanceIntPts: - bestResult = [intPtArc1, intPtArc2, distanceIntPts] - - return bestResult - - -#=============================================================================== -# getMiddlePoint -#=============================================================================== -def getMiddlePoint(p1, p2): - """ - la funzione ritorna il punto medio tra 2 punti (QgsPoint) - """ - x = (p1.x() + p2.x()) / 2 - y = (p1.y() + p2.y()) / 2 - - return QgsPoint(x, y) - - -#=============================================================================== -# getAngleBy2Pts -#=============================================================================== -def getAngleBy2Pts(p1, p2): - """ - la funzione ritorna l'angolo in radianti della retta passante per p1 e p2 - """ - diffX = p2.x() - p1.x() - diffY = p2.y() - p1.y() - if doubleNear(diffX, 0): # se la retta passante per p1 e p2 é verticale - if p1.y() < p2.y(): - angle = math.pi / 2 - else : - angle = math.pi * 3 / 2 - elif doubleNear(diffY, 0): # se la retta passante per p1 e p2 é orizzontale - if p1.x() <= p2.x(): - angle = 0.0 - else: - angle = math.pi - else: - angle = math.atan(diffY / diffX) - if diffX < 0: - angle = math.pi + angle - else: - if diffY < 0: - angle = 2 * math.pi + angle - - return angle - - -#=============================================================================== -# getAngleBy3Pts -#=============================================================================== -def getAngleBy3Pts(p1, vertex, p2, clockWise): - """ - la funzione ritorna l'angolo in radianti dell'angolo che parte da - per arrivare a con vertice nella direzione (oraria o antioraria) - """ - angle1 = getAngleBy2Pts(p1, vertex) - angle2 = getAngleBy2Pts(p2, vertex) - if clockWise: # senso orario - if angle2 > angle1: - return (2 * math.pi) - (angle2 - angle1) - else: - return angle1 - angle2 - else: # senso anti-orario - if angle2 < angle1: - return (2 * math.pi) - (angle1 - angle2) - else: - return angle2 - angle1 - - -#=============================================================================== -# isAngleBetweenAngles -#=============================================================================== -def isAngleBetweenAngles(startAngle, endAngle, angle): - """ - la funzione ritorna True se l'angolo si trova entro l'angolo di partenza e quello finale - estremi compresi - """ - _angle = angle % (math.pi * 2) # modulo - if _angle < 0: - _angle = (math.pi * 2) - _angle - - if startAngle < endAngle: - if (_angle > startAngle or doubleNear(_angle, startAngle)) and \ - (_angle < endAngle or doubleNear(_angle, endAngle)): - return True - else: - if (_angle > 0 or doubleNear(_angle, 0)) and \ - (_angle < endAngle or doubleNear(_angle, endAngle)): - return True - - if (_angle < (math.pi * 2) or doubleNear(_angle, (math.pi * 2))) and \ - (_angle > startAngle or doubleNear(_angle, startAngle)): - return True - - return False - - -def getPolarPointBy2Pts(p1, p2, dist): - """ - la funzione ritorna il punto sulla retta passante per p1 e p2 che - dista da p1 verso p2 . - """ - angle = getAngleBy2Pts(p1, p2) - - return getPolarPointByPtAngle(p1, angle, dist) - - -#=============================================================================== -# isPtOnSegment -#=============================================================================== -def isPtOnSegment(p1, p2, point): - """ - la funzione ritorna true se il punto é sul segmento (estremi compresi). - p1, p2 e point sono QgsPoint. - """ - if p1.x() < p2.x(): - xMin = p1.x() - xMax = p2.x() - else: - xMax = p1.x() - xMin = p2.x() - - if p1.y() < p2.y(): - yMin = p1.y() - yMax = p2.y() - else: - yMax = p1.y() - yMin = p2.y() - - y = getYOnInfinityLine(p1, p2, point.x()) - if y is None: # il segmento p1-p2 é verticale - if (doubleNear(point.x(), xMin)) and \ - (point.y() < yMax or doubleNear(point.y(), yMax)) and \ - (point.y() > yMin or doubleNear(point.y(), yMin)): - return True - else: - # se il punto é sulla linea infinita che passa da p1-p2 - if doubleNear(point.y(), y): - # se la coordinata x é compresa nel segmento - if (point.x() > xMin or doubleNear(point.x(), xMin)) and \ - (point.x() < xMax or doubleNear(point.x(), xMax)): - return True - - return False - - -#=============================================================================== -# isPtOnInfinityLine -#=============================================================================== -def isPtOnInfinityLine(lineP1, lineP2, point): - """ - la funzione ritorna true se il punto é sul segmento (estremi compresi). - p1, p2 e point sono QgsPoint. - """ - y = getYOnInfinityLine(lineP1, lineP2, point.x()) - if y is None: # la linea infinita lineP1-lineP2 é verticale - if doubleNear(point.x(), lineP1.x()): - return True - else: - # se il punto é sulla linea infinita che passa da p1-p2 - if doubleNear(point.y(), y): - return True - - return False - - -#=============================================================================== -# getIntersectionPointOn2InfinityLines -#=============================================================================== -def getIntersectionPointOn2InfinityLines(line1P1, line1P2, line2P1, line2P2): - """ - la funzione ritorna il punto di intersezione tra la linea passante per line1P1-line1P2 e - la linea passante per line2P1-line2P2. - """ - line1DiffX = line1P2.x() - line1P1.x() - line1DiffY = line1P2.y() - line1P1.y() - - line2DiffX = line2P2.x() - line2P1.x() - line2DiffY = line2P2.y() - line2P1.y() - - if doubleNear(line1DiffX, 0) and doubleNear(line2DiffX, 0): # se la retta1 e la retta2 sono verticale - return None # sono parallele - elif doubleNear(line1DiffY, 0) and doubleNear(line2DiffY, 0): # se la retta1 e la retta2 sono orizzonatali - return None # sono parallele - - if doubleNear(line1DiffX, 0): # se la retta1 é verticale - return QgsPoint(line1P2.x(), getYOnInfinityLine(line2P1, line2P2, line1P2.x())) - if doubleNear(line1DiffY, 0): # se la retta1 é orizzontale - return QgsPoint(getXOnInfinityLine(line2P1, line2P2, line1P2.y()), line1P2.y()) - if doubleNear(line2DiffX, 0): # se la retta2 é verticale - return QgsPoint(line2P2.x(), getYOnInfinityLine(line1P1, line1P2, line2P2.x())) - if doubleNear(line2DiffY, 0): # se la retta2 é orizzontale - return QgsPoint(getXOnInfinityLine(line1P1, line1P2, line2P2.y()), line2P2.y()) - - line1Coeff = line1DiffY / line1DiffX - line2Coeff = line2DiffY / line2DiffX - - if line1Coeff == line2Coeff: # sono parallele - return None - - D = line1Coeff - line2Coeff - # se D é così vicino a zero - if doubleNear(D, 0.0): - return None - x = line1P1.x() * line1Coeff - line1P1.y() - line2P1.x() * line2Coeff + line2P1.y() - x = x / D - y = (x - line1P1.x()) * line1Coeff + line1P1.y() - - return QgsPoint(x, y) - - -#=============================================================================== -# getIntersectionPointOn2Segments -#=============================================================================== -def getIntersectionPointOn2Segments(line1P1, line1P2, line2P1, line2P2): - """ - la funzione ritorna il punto di intersezione tra il segmento1 avente come estremi line1P1-line1P2 e - il segmento2 avente come estremi line2P1-line2P2. - """ - ptInt = getIntersectionPointOn2InfinityLines(line1P1, line1P2, line2P1, line2P2) - if ptInt is not None: # se non sono parallele - # se il punto di intersezione é sui segmenti - if isPtOnSegment(line1P1, line1P2, ptInt) and isPtOnSegment(line2P1, line2P2, ptInt): - return QgsPoint(ptInt) - else: - # il segmento line2 si sovrappone a line1 - if isPtOnSegment(line1P1, line1P2, line2P1) == True and \ - isPtOnSegment(line1P1, line1P2, line2P2) == True: - return None - # il segmento line1 si sovrappone a line2 - if isPtOnSegment(line2P1, line2P2, line1P1) == True and \ - isPtOnSegment(line2P1, line2P2, line1P2) == True: - return None - # se il punto iniziale di line1 coincide con il punto iniziale o finale di line2 - if line1P1 == line2P1 or line1P1 == line2P2: - return QgsPoint(line1P1) - # se il punto finale di line1 coincide con il punto iniziale o finale di line2 - if line1P2 == line2P1 or line1P2 == line2P2: - return QgsPoint(line1P2) - - return None - - -#=============================================================================== -# getNearestPoints -#=============================================================================== -def getNearestPoints(point, points, tolerance = 0): - """ - Ritorna una lista di punti più vicino a point. - """ - result = [] - minDist = sys.float_info.max - - if tolerance == 0: # solo il punto più vicino - for pt in points: - dist = getDistance(point, pt) - if dist < minDist: - minDist = dist - nearestPoint = pt - - if minDist != sys.float_info.max: # trovato - result.append(nearestPoint) - else: - nearest = getNearestPoints(point, points) # punto più vicino - nearestPoint = nearest[0] - - for pt in points: - dist = getDistance(nearestPoint, pt) - if dist <= tolerance: - result.append(pt) - - return result - - -#=============================================================================== -# getPolarPointByPtAngle -#=============================================================================== -def getPolarPointByPtAngle(p1, angle, dist): - """ - la funzione ritorna il punto sulla retta passante per p1 con angolo che - dista da p1 . - """ - y = dist * math.sin(angle) - x = dist * math.cos(angle) - return QgsPoint(p1.x() + x, p1.y() + y) - - -#=============================================================================== -# asPointOrPolyline -#=============================================================================== -def asPointOrPolyline(geom): - """ - la funzione ritorna una lista di geometrie di punti e/o polilinee in cui viene ridotta la geometria. - """ - # Riduco le geometrie in point o polyline - result = [] - for g in geom.asGeometryCollection(): - wkbType = g.wkbType() - if wkbType == QGis.WKBPoint or wkbType == QGis.WKBLineString: - result.append(g) - elif wkbType == QGis.WKBMultiPoint: - pointList = g.asMultiPoint() # vettore di punti - for point in pointList: - _g = QgsGeometry.fromPoint(point) - result.append(_g) - elif wkbType == QGis.WKBMultiLineString: - lineList = g.asMultiPolyline() # vettore di linee - for line in lineList: - _g = QgsGeometry.fromPolyline(line) - result.append(_g) - elif wkbType == QGis.WKBPolygon: - lineList = g.asPolygon() # vettore di linee - for line in lineList: - _g = QgsGeometry.fromPolyline(line) - result.append(_g) - elif wkbType == QGis.WKBMultiPolygon: - polygonList = g.asMultiPolygon() # vettore di poligoni - for polygon in polygonList: - for line in polygon: - _g = QgsGeometry.fromPolyline(line) - result.append(_g) - - return result - - -#=============================================================================== -# leftOfLineCoords -#=============================================================================== -def leftOfLineCoords(x, y, x1, y1, x2, y2): - """ - la funzione ritorna una numero < 0 se il punto x,y é alla sinistra della linea x1,y1 -> x2,y2 - """ - f1 = x - x1 - f2 = y2 - y1 - f3 = y - y1 - f4 = x2 - x1 - return f1*f2 - f3*f4 - -def leftOfLine(pt, pt1, pt2): - return leftOfLineCoords(pt.x(), pt.y(), pt1.x(), pt1.y(), pt2.x(), pt2.y()) - - -#=============================================================================== -# ptNear -#=============================================================================== -def ptNear(pt1, pt2, tolerance = TOLERANCE): - """ - la funzione compara 2 punti (ma permette una tolleranza) - """ - return getDistance(pt1, pt2) <= tolerance - - -#=============================================================================== -# doubleNear -#=============================================================================== -def doubleNear(a, b, tolerance = TOLERANCE): - """ - la funzione compara 2 float (ma permette una tolleranza) - """ - diff = a - b - return diff > -tolerance and diff <= tolerance - - -#=============================================================================== -# doubleGreater -#=============================================================================== -def doubleGreater(a, b, tolerance = TOLERANCE): - """ - la funzione compara 2 float (ma permette una tolleranza) - """ - return a > b and not doubleNear(a, b, tolerance) - - -#=============================================================================== -# doubleSmaller -#=============================================================================== -def doubleSmaller(a, b, tolerance = TOLERANCE): - """ - la funzione compara 2 float (ma permette una tolleranza) - """ - return a < b and not doubleNear(a, b, tolerance) - - -#=============================================================================== -# TanDirectionNear -#=============================================================================== -def TanDirectionNear(a, b, tolerance = TOLERANCE): - """ - la funzione compara 2 direzioni di tangenti (ma permette una tolleranza) - """ - if doubleNear(a, b): - return True - arc = QadArc() - arc.set(QgsPoint(0,0), 1, a, b) - if arc.totalAngle() <= tolerance: - return True - else: - arc.set(QgsPoint(0,0), 1, b, a) - return arc.totalAngle() <= tolerance - - -#=============================================================================== -# numericListAvg -#=============================================================================== -def numericListAvg(dblList): - """ - la funzione calcola la media di una lista di numeri - """ - if (dblList is None) or len(dblList) == 0: - return None - sum = 0 - for num in dblList: - sum = sum + num - - return sum / len(dblList) - - -#=============================================================================== -# sqrDistToSegment -#=============================================================================== -def sqrDistToSegment(point, x1, y1, x2, y2, epsilon): - """ - la funzione ritorna una lista con - ( - ) - """ - minDistPoint = QgsPoint() - - if x1 == x2 and y1 == y2: - minDistPoint.setX(x1) - minDistPoint.setY(y1) - else: - nx = y2 - y1 - ny = -( x2 - x1 ) - - t = (point.x() * ny - point.y() * nx - x1 * ny + y1 * nx ) / (( x2 - x1 ) * ny - ( y2 - y1 ) * nx ) - - if t < 0.0: - minDistPoint.setX(x1) - minDistPoint.setY(y1) - elif t > 1.0: - minDistPoint.setX(x2) - minDistPoint.setY(y2) - else: - minDistPoint.setX( x1 + t *( x2 - x1 ) ) - minDistPoint.setY( y1 + t *( y2 - y1 ) ) - - dist = point.sqrDist(minDistPoint) - # prevent rounding errors if the point is directly on the segment - if doubleNear( dist, 0.0, epsilon ): - minDistPoint.setX( point.x() ) - minDistPoint.setY( point.y() ) - return (0.0, minDistPoint) - - return (dist, minDistPoint) - - -#=============================================================================== -# sqrDistToArc -#=============================================================================== -def sqrDistToArc(point, arc): - """ - la funzione ritorna una lista con - ( - ) - """ - minDistPoint = QgsPoint() - angle = getAngleBy2Pts(arc.center, point) - if isAngleBetweenAngles(arc.startAngle, arc.endAngle, angle): - distFromArc = getDistance(arc.center, point) - arc.radius - return (distFromArc * distFromArc, getPolarPointByPtAngle(arc.center, angle, arc.radius)) - else: - startPt = arc.getStartPt() - endPt = arc.getEndPt() - distFromStartPt = getSqrDistance(startPt, point) - distFromEndPt = getSqrDistance(endPt, point) - if distFromStartPt < distFromEndPt: - return (distFromStartPt, startPt) - else: - return (distFromEndPt, endPt) - - -#=============================================================================== -# closestSegmentWithContext -#=============================================================================== -def closestSegmentWithContext(point, geom, epsilon = 1.e-15): - """ - la funzione ritorna una lista con - ( - - - <"a sinistra di" se il punto é alla sinista del segmento (< 0 -> sinistra, > 0 -> destra) - """ - minDistPoint = QgsPoint() - closestSegmentIndex = 0 - wkbType = geom.wkbType() - sqrDist = sys.float_info.max - - if wkbType == QGis.WKBPoint: - minDistPoint = geom.asPoint() - point.sqrDist(minDistPoint) - return (point.sqrDist(minDistPoint), minDistPoint, None, None) - - if wkbType == QGis.WKBMultiPoint: - minDistPoint = getNearestPoints(point, geom.asMultiPoint())[0] # vettore di punti - return (point.sqrDist(minDistPoint), minDistPoint, None, None) - - if wkbType == QGis.WKBLineString: - points = geom.asPolyline() # vettore di punti - index = 0 - for pt in points: - if index > 0: - prevX = thisX - prevY = thisY - - thisX = pt.x() - thisY = pt.y() - - if index > 0: - result = sqrDistToSegment(point, prevX, prevY, thisX, thisY, epsilon) - testdist = result[0] - distPoint = result[1] - - if testdist < sqrDist: - closestSegmentIndex = index - sqrDist = testdist - minDistPoint = distPoint - - index = index + 1 - - leftOf = leftOfLine(point, geom.vertexAt(closestSegmentIndex - 1), geom.vertexAt(closestSegmentIndex)) - return (sqrDist, minDistPoint, closestSegmentIndex, leftOf) - - if wkbType == QGis.WKBMultiLineString: - lines = geom.asMultiPolyline() # lista di linee - pointindex = 0 - for line in lines: - prevX = 0 - prevY = 0 - - for pt in line: # lista di punti - thisX = pt.x() - thisY = pt.y() - - if prevX and prevY: - result = sqrDistToSegment(point, prevX, prevY, thisX, thisY, epsilon) - testdist = result[0] - distPoint = result[1] - - if testdist < sqrDist: - closestSegmentIndex = index - sqrDist = testdist - minDistPoint = distPoint - - prevX = thisX - prevY = thisY - pointindex = pointindex + 1 - - leftOf = leftOfLine(point, geom.vertexAt(closestSegmentIndex - 1), geom.vertexAt(closestSegmentIndex)) - return (sqrDist, minDistPoint, closestSegmentIndex, leftOf) - - if wkbType == QGis.WKBPolygon: - lines = geom.asPolygon() # lista di linee - index = 0 - for line in lines: - prevX = 0 - prevY = 0 - - for pt in line: # lista di punti - thisX = pt.x() - thisY = pt.y() - - if prevX and prevY: - result = sqrDistToSegment(point, prevX, prevY, thisX, thisY, epsilon) - testdist = result[0] - distPoint = result[1] - - if testdist < sqrDist: - closestSegmentIndex = index - sqrDist = testdist - minDistPoint = distPoint - - prevX = thisX - prevY = thisY - index = index + 1 - - leftOf = leftOfLine(point, geom.vertexAt(closestSegmentIndex - 1), geom.vertexAt(closestSegmentIndex)) - return (sqrDist, minDistPoint, closestSegmentIndex, leftOf) - - if wkbType == QGis.WKBMultiPolygon: - polygons = geom.asMultiPolygon() # vettore di poligoni - pointindex = 0 - for polygon in polygons: - for line in polygon: # lista di linee - prevX = 0 - prevY = 0 - - for pt in line: # lista di punti - thisX = pt.x() - thisY = pt.y() - - if prevX and prevY: - result = sqrDistToSegment(point, prevX, prevY, thisX, thisY, epsilon) - testdist = result[0] - distPoint = result[1] - - if testdist < sqrDist: - closestSegmentIndex = pointindex - sqrDist = testdist - minDistPoint = distPoint - - prevX = thisX - prevY = thisY - pointindex = pointindex + 1 - - leftOf = leftOfLine(point, geom.vertexAt(closestSegmentIndex - 1), geom.vertexAt(closestSegmentIndex)) - return (sqrDist, minDistPoint, closestSegmentIndex, leftOf) - - return (-1, None, None, None) - - - -#=============================================================================== -# closestVertexPtWithContext -#=============================================================================== -def closestVertexPtWithContext(point, geom, epsilon = 1.e-15): - """ - la funzione ritorna il punto del vertice più vicino a point - """ - wkbType = geom.wkbType() - if wkbType == QGis.WKBPoint: - return geom.asPoint() - if wkbType == QGis.WKBMultiPoint: - return getNearestPoints(point, geom.asMultiPoint())[0] # vettore di punti - - # ritorna una tupla (, - # - # - # ) - dummy = closestSegmentWithContext(point, geom, epsilon) - if dummy[2] is not None: - # ritorna la sotto-geometria al vertice e la sua posizione nella geometria (0-based) - subGeom, atSubGeom1 = qad_utils.getSubGeomAtVertex(geom, dummy[2]) - l = QadLinearObjectList() - l.fromPolyline(subGeom.asPolyline()) - vertexAt = l.closestVertexWithContext(point) - return l.getPointAtVertex(vertexAt) - - -#=============================================================================== -# getBoundingPtsOnOnInfinityLine -#=============================================================================== -def getBoundingPtsOnOnInfinityLine(linePt1, linePt2, pts): - """ - Data una linea infinita passante per e e una lista di punti non ordinati sulla linea, - la funzione ritorna i due punti estremi al fascio di punti (i due punti più lontani tra di loro). - """ - tot = len(pts) - if tot < 3: - return pts[:] # copio la lista - - result = [] - # elaboro i tratti intermedi - # calcolo la direzione dal primo punto al secondo punto - angle = getAngleBy2Pts(pts[0], pts[1]) - # ciclo su tutti i punti considerando solo quelli che hanno la stessa direzione con il punto precedente (boundingPt1) - i = 2 - boundingPt1 = pts[1] - while i < tot: - pt2 = pts[i] - if TanDirectionNear(angle, getAngleBy2Pts(boundingPt1, pt2)): - boundingPt1 = pt2 - i = i + 1 - - # calcolo la direzione dal secondo punto al primo punto - angle = getAngleBy2Pts(pts[1], pts[0]) - # ciclo su tutti i punti considerando solo quelli che hanno la stessa direzione con il punto precedente (boundingPt2) - i = 2 - boundingPt2 = pts[0] - while i < tot: - pt2 = pts[i] - if TanDirectionNear(angle, getAngleBy2Pts(boundingPt2, pt2)): - boundingPt2 = pt2 - i = i + 1 - - return [QgsPoint(boundingPt1), QgsPoint(boundingPt2)] - - -#=============================================================================== -# rotatePoint -#=============================================================================== -def rotatePoint(point, basePt, angle): - """ - la funzione ruota un punto QgsPoint secondo un punto base e un angolo in radianti - """ - return getPolarPointByPtAngle(basePt, getAngleBy2Pts(basePt, point) + angle, getDistance(basePt, point)) - - -#=============================================================================== -# rotateQgsGeometry -#=============================================================================== -def rotateQgsGeometry(geom, basePt, angle): - """ - la funzione ruota la geometria secondo un punto base e un angolo in radianti - """ - if geom is None: - return None - - wkbType = geom.wkbType() - - if wkbType == QGis.WKBPoint or wkbType == QGis.WKBPoint25D: - pt = geom.asPoint() # un punto - newPt = rotatePoint(pt, basePt, angle) - return QgsGeometry.fromPoint(newPt) - - if wkbType == QGis.WKBMultiPoint: - points = geom.asMultiPoint() # vettore di punti - for pt in points: - newPt = rotatePoint(pt, basePt, angle) - pt.set(newPt.x(), newPt.y()) - return QgsGeometry.fromMultiPoint(points) - - if wkbType == QGis.WKBLineString: - points = geom.asPolyline() # vettore di punti - for pt in points: - newPt = rotatePoint(pt, basePt, angle) - pt.set(newPt.x(), newPt.y()) - - return QgsGeometry.fromPolyline(points) - - if wkbType == QGis.WKBMultiLineString: - lines = geom.asMultiPolyline() # lista di linee - for line in lines: - for pt in line: # lista di punti - newPt = rotatePoint(pt, basePt, angle) - pt.set(newPt.x(), newPt.y()) - - return QgsGeometry.fromMultiPolyline(lines) - - if wkbType == QGis.WKBPolygon: - lines = geom.asPolygon() # lista di linee - for line in lines: - for pt in line: # lista di punti - newPt = rotatePoint(pt, basePt, angle) - pt.set(newPt.x(), newPt.y()) - - return QgsGeometry.fromPolygon(lines) - - if wkbType == QGis.WKBMultiPolygon: - polygons = geom.asMultiPolygon() # vettore di poligoni - for polygon in polygons: - for line in polygon: # lista di linee - for pt in line: # lista di punti - newPt = rotatePoint(pt, basePt, angle) - pt.set(newPt.x(), newPt.y()) - - return QgsGeometry.fromMultiPolygon(polygons) - - return None - - -#=============================================================================== -# scalePoint -#=============================================================================== -def scalePoint(point, basePt, scale): - """ - la funzione scala un punto QgsPoint secondo un punto base e un fattore di scala - """ - return getPolarPointByPtAngle(basePt, getAngleBy2Pts(basePt, point), getDistance(basePt, point) * scale) - - -#=============================================================================== -# scaleQgsGeometry -#=============================================================================== -def scaleQgsGeometry(geom, basePt, scale): - """ - la funzione scala la geometria secondo un punto base e un fattore di scala - """ - if geom is None: - return None - - wkbType = geom.wkbType() - - if wkbType == QGis.WKBPoint or wkbType == QGis.WKBPoint25D: - pt = geom.asPoint() # un punto - newPt = scalePoint(pt, basePt, scale) - return QgsGeometry.fromPoint(newPt) - - if wkbType == QGis.WKBMultiPoint: - points = geom.asMultiPoint() # vettore di punti - for pt in points: - newPt = scalePoint(pt, basePt, scale) - pt.set(newPt.x(), newPt.y()) - return QgsGeometry.fromMultiPoint(points) - - if wkbType == QGis.WKBLineString: - points = geom.asPolyline() # vettore di punti - for pt in points: - newPt = scalePoint(pt, basePt, scale) - pt.set(newPt.x(), newPt.y()) - - return ApproxCurvesOnGeom(QgsGeometry.fromPolyline(points)) - - if wkbType == QGis.WKBMultiLineString: - lines = geom.asMultiPolyline() # lista di linee - for line in lines: - for pt in line: # lista di punti - newPt = scalePoint(pt, basePt, scale) - pt.set(newPt.x(), newPt.y()) - - return ApproxCurvesOnGeom(QgsGeometry.fromMultiPolyline(lines)) - - if wkbType == QGis.WKBPolygon: - lines = geom.asPolygon() # lista di linee - for line in lines: - for pt in line: # lista di punti - newPt = scalePoint(pt, basePt, scale) - pt.set(newPt.x(), newPt.y()) - - return ApproxCurvesOnGeom(QgsGeometry.fromPolygon(lines)) - - if wkbType == QGis.WKBMultiPolygon: - polygons = geom.asMultiPolygon() # vettore di poligoni - for polygon in polygons: - for line in polygon: # lista di linee - for pt in line: # lista di punti - newPt = scalePoint(pt, basePt, scale) - pt.set(newPt.x(), newPt.y()) - - return ApproxCurvesOnGeom(QgsGeometry.fromMultiPolygon(polygons)) - - return None - - -#=============================================================================== -# movePoint -#=============================================================================== -def movePoint(point, offSetX, offSetY): - """ - la funzione sposta un punto QgsPoint secondo un offset X e uno Y - """ - return QgsPoint(point.x() + offSetX, point.y() + offSetY) - - -#=============================================================================== -# moveQgsGeometry -#=============================================================================== -def moveQgsGeometry(geom, offSetX, offSetY): - """ - la funzione sposta la geometria secondo un offset X uno Y - """ - if geom is None: - return None - - wkbType = geom.wkbType() - - if wkbType == QGis.WKBPoint or wkbType == QGis.WKBPoint25D: - pt = geom.asPoint() # un punto - newPt = movePoint(pt, offSetX, offSetY) - return QgsGeometry.fromPoint(newPt) - - if wkbType == QGis.WKBMultiPoint: - points = geom.asMultiPoint() # vettore di punti - for pt in points: - newPt = movePoint(pt, offSetX, offSetY) - pt.set(newPt.x(), newPt.y()) - return QgsGeometry.fromMultiPoint(points) - - if wkbType == QGis.WKBLineString: - points = geom.asPolyline() # vettore di punti - for pt in points: - newPt = movePoint(pt, offSetX, offSetY) - pt.set(newPt.x(), newPt.y()) - - return QgsGeometry.fromPolyline(points) - - if wkbType == QGis.WKBMultiLineString: - lines = geom.asMultiPolyline() # lista di linee - for line in lines: - for pt in line: # lista di punti - newPt = movePoint(pt, offSetX, offSetY) - pt.set(newPt.x(), newPt.y()) - - return QgsGeometry.fromMultiPolyline(lines) - - if wkbType == QGis.WKBPolygon: - lines = geom.asPolygon() # lista di linee - for line in lines: - for pt in line: # lista di punti - newPt = movePoint(pt, offSetX, offSetY) - pt.set(newPt.x(), newPt.y()) - - return QgsGeometry.fromPolygon(lines) - - if wkbType == QGis.WKBMultiPolygon: - polygons = geom.asMultiPolygon() # vettore di poligoni - for polygon in polygons: - for line in polygon: # lista di linee - for pt in line: # lista di punti - newPt = movePoint(pt, offSetX, offSetY) - pt.set(newPt.x(), newPt.y()) - - return QgsGeometry.fromMultiPolygon(polygons) - - return None - - -#=============================================================================== -# extendQgsGeometry -#=============================================================================== -def extendQgsGeometry(geom_crs, geom, pt, limitEntitySet, edgeMode, tolerance2ApproxCurve): - """ - la funzione estende la geometria (lineare) nella parte iniziale o finale fino ad - incontrare l'oggetto più vicino nel gruppo secondo la modalità . - = sistema di coordinate della geometria da estendere - = geometria da estendere - = punto che indica il sotto-oggetto grafico (se si tratta di WKBMultiLineString) - e la parte di quell'oggetto che deve essere estesa - = gruppo di entità che serve da limite di estensione - se = 0 si deve estendere la geometria fino ad incontrare l'oggetto più vicino - se = 1 si deve estendere la geometria fino ad incontrare l'oggetto più vicino o - anche il suo prolungamento - = tolleranza di approssimazione per le curve - """ - if geom is None: - return None - - wkbType = geom.wkbType() - - if wkbType != QGis.WKBLineString and wkbType != QGis.WKBMultiLineString: - return None - - # ritorna una tupla (, - # - # - # ) - dummy = closestSegmentWithContext(pt, geom) - if dummy[2] is None: - return None - # ritorna la sotto-geometria al vertice e la sua posizione nella geometria (0-based) - subGeom, atSubGeom = getSubGeomAtVertex(geom, dummy[2]) - - LinearObjectListToExtend = QadLinearObjectList() - LinearObjectListToExtend.fromPolyline(subGeom.asPolyline()) - - if LinearObjectListToExtend.isClosed(): # non si può fare con polilinea chiusa - return None - - # stabilisco se devo considerare l'inizio o la fine della polilinea - if subGeom.vertexAt(0) == pt: - distFromStart = 0 - else: - distFromStart = QgsGeometry.fromPolyline(getLinePart(subGeom, \ - subGeom.vertexAt(0), \ - pt)).length() - if distFromStart > (subGeom.length() / 2): - # parte finale - LinearObjectToExtend = LinearObjectListToExtend.getLinearObjectAt(-1) - else: - # parte iniziale - LinearObjectToExtend = QadLinearObject(LinearObjectListToExtend.getLinearObjectAt(0)) - LinearObjectToExtend.reverse() - - minDist = sys.float_info.max - newPt = None - ExtendedLinearObject = QadLinearObject() - gTransformed = QgsGeometry() - - # per ciascun layer - for limitLayerEntitySet in limitEntitySet.layerEntitySetList: - limitLayer = limitLayerEntitySet.layer - - if limitLayer.crs() != geom_crs: - coordTransform = QgsCoordinateTransform(limitLayer.crs(), geom_crs) - ExtendedLinearObject.set(LinearObjectToExtend) - - # per ciascuna entità del layer - for featureId in limitLayerEntitySet.featureIds: - f = getFeatureById(limitLayer, featureId) - if f is None: - continue - - # Trasformo la geometria limite nel sistema di coordinate del - gTransformed = f.geometry() - if limitLayer.crs() != geom_crs: - gTransformed.transform(coordTransform) - - intPt = getIntersectionPtExtendQgsGeometry(LinearObjectToExtend, gTransformed, edgeMode) - if intPt is not None: - # cerco il punto di intersezione più vicino al punto finale di linearObject - ExtendedLinearObject.setEndPt(intPt) - if ExtendedLinearObject.length() < minDist: - minDist = ExtendedLinearObject.length() - newPt = intPt - - if newPt is None: - return None - - if distFromStart > (subGeom.length() / 2): - # modifico la parte finale - LinearObjectListToExtend.getLinearObjectAt(-1).setEndPt(newPt) - else: - # modifico la parte iniziale - LinearObjectListToExtend.getLinearObjectAt(0).setStartPt(newPt) - - pts = LinearObjectListToExtend.asPolyline(tolerance2ApproxCurve) - - return setSubGeom(geom, QgsGeometry.fromPolyline(pts), atSubGeom) - - -#=============================================================================== -# getIntersectionPtExtendQgsGeometry -#=============================================================================== -def getIntersectionPtExtendQgsGeometry(linearObject, limitGeom, edgeMode): - """ - la funzione calcola il punto di intersezione tra il prolungamento della parte lineare - oltre il punto finale fino ad incontrare la geometria secondo la modalità . - Viene restituito il punto più vicino al punto finale di . - = parte lineare da estendere - = geometria da usare come limite di estensione - se = 0 si deve estendere la geometria fino ad incontrare l'oggetto più vicino - se = 1 si deve estendere la geometria fino ad incontrare l'oggetto più vicino o - anche il suo prolungamento - """ - intPts = [] - limitLinearObjectParts = QadLinearObjectList() - - # riduco in polilinee - limitGeoms = asPointOrPolyline(limitGeom) - for limitGeom in limitGeoms: - Found = False - wkbType = limitGeom.wkbType() - if wkbType == QGis.WKBPoint or wkbType == QGis.WKBPoint25D: - pt = limitGeom.asPoint() - if linearObject.isSegment(): - if isPtOnInfinityLine(linearObject.getStartPt(), linearObject.getEndPt(), pt): - intPts.append(pt) - else: # arco - circle = QadCircle() - circle.set(linearObject.getArc().center, linearObject.getArc().radius) - if circle.isPtOnCircle(pt): - intPts.append(pt) - else: # Linestring - limitLinearObjectParts.fromPolyline(limitGeom.asPolyline()) - - # primo tratto - LimitLinearObject = limitLinearObjectParts.getLinearObjectAt(0) - pts = linearObject.getIntersectionPtsOnExtensionWithLinearObject(LimitLinearObject) - if edgeMode == 0: # senza estendere - # considero solo i punti sulla parte - for i in xrange(len(pts) - 1, -1, -1): - pt = pts[i] - if LimitLinearObject.containsPt(pt) == False: - del pts[i] - else: - if LimitLinearObject.isSegment(): - # considero solo i punti sulla parte o oltre l'inizio - for i in xrange(len(pts) - 1, -1, -1): - pt = pts[i] - if LimitLinearObject.containsPt(pt) == False: - if getDistance(LimitLinearObject.getStartPt(), pt) > \ - getDistance(LimitLinearObject.getEndPt(), pt): - del pts[i] - intPts.extend(pts) - - # elaboro i tratti intermedi - i = 1 - while i < limitLinearObjectParts.qty() - 1: - LimitLinearObject = limitLinearObjectParts.getLinearObjectAt(i) - pts = linearObject.getIntersectionPtsOnExtensionWithLinearObject(LimitLinearObject) - # considero solo i punti sulla parte - for j in xrange(len(pts) - 1, -1, -1): - pt = pts[j] - if LimitLinearObject.containsPt(pt) == False: - del pts[j] - - intPts.extend(pts) - i = i + 1 - - # ultimo tratto - LimitLinearObject = limitLinearObjectParts.getLinearObjectAt(-1) - pts = linearObject.getIntersectionPtsOnExtensionWithLinearObject(LimitLinearObject) - if edgeMode == 0: # senza estendere - # considero solo i punti sulla parte - for i in xrange(len(pts) - 1, -1, -1): - pt = pts[i] - if LimitLinearObject.containsPt(pt) == False: - del pts[i] - else: - if LimitLinearObject.isSegment(): - # considero solo i punti sulla parte o oltre la fine - for i in xrange(len(pts) - 1, -1, -1): - pt = pts[i] - if LimitLinearObject.containsPt(pt) == False: - if getDistance(LimitLinearObject.getStartPt(), pt) < \ - getDistance(LimitLinearObject.getEndPt(), pt): - del pts[i] - intPts.extend(pts) - - # cancello i punti di intersezione che non sono oltre la fine di linearObject - for i in xrange(len(intPts) - 1, -1, -1): - if linearObject.containsPt(intPts[i]) == True: - del intPts[i] - else: - if linearObject.isSegment(): - if getDistance(linearObject.getStartPt(), intPts[i]) < \ - getDistance(linearObject.getEndPt(), intPts[i]): - del intPts[i] - - if len(intPts) == 0: - return None - - # cerco il punto di intersezione più vicino al punto finale di linearObject - minDist = sys.float_info.max - LimitLinearObject.set(linearObject) - for intPt in intPts: - LimitLinearObject.setEndPt(intPt) - if LimitLinearObject.length() < minDist: - minDist = LimitLinearObject.length() - pt = intPt - - return pt - - -#=============================================================================== -# trimQgsGeometry -#=============================================================================== -def trimQgsGeometry(geom_crs, geom, pt, limitEntitySet, edgeMode, tolerance2ApproxCurve): - """ - la funzione taglia la geometria (lineare) in una parte i cui limiti sono le intersezioni più - vicine a pt con gli oggetti del gruppo secondo la modalità . - = sistema di coordinate della geometria da tagliare - = geometria da tagliare - = punto che indica il sotto-oggetto grafico (se si tratta di WKBMultiLineString) - e la parte di quell'oggetto che deve essere tagliata - = gruppo di entità che serve da limite di taglio - se = 0 si deve estendere la geometria fino ad incontrare l'oggetto più vicino - se = 1 si deve estendere la geometria fino ad incontrare l'oggetto più vicino o - anche il suo prolungamento - = tolleranza di approssimazione per le curve - """ - if geom is None: - return None - - wkbType = geom.wkbType() - - if wkbType != QGis.WKBLineString and wkbType != QGis.WKBMultiLineString and \ - wkbType != QGis.WKBPolygon and wkbType != QGis.WKBMultiPolygon: - return None - - # ritorna una tupla (, - # - # - # ) - dummy = closestSegmentWithContext(pt, geom) - if dummy[2] is None: - return None - # ritorna la sotto-geometria al vertice e la sua posizione nella geometria (0-based) - subGeom, atSubGeom = getSubGeomAtVertex(geom, dummy[2]) - - LinearObjectListToCut = QadLinearObjectList() - LinearObjectListToCut.fromPolyline(subGeom.asPolyline()) - - # divido la polilinea in 2 - dummy = LinearObjectListToCut.breakOnPt(pt) - partList1ToTrim = dummy[0] - partList2ToTrim = dummy[1] - - trimmedLinearObject = QadLinearObject() - gTransformed = QgsGeometry() - - # cerco intersezione più vicina a pt nella prima parte - newPt1 = None - geom1 = None - if partList1ToTrim is not None: - partList1ToTrim.reverse() - newPt1, partNumberAtpartList1 = partList1ToTrim.getIntPtNearestToStartPt(geom_crs, limitEntitySet, edgeMode) - if newPt1 is None: # nessuna intersezione - if LinearObjectListToCut.isClosed(): # se é chiusa - if partList2ToTrim is None: - return None - partList2ToTrim.reverse() - newPt, partNumberAtpartList = partList2ToTrim.getIntPtNearestToStartPt(geom_crs, limitEntitySet, edgeMode) - if newPt is None: - return None - for i in xrange(0, partNumberAtpartList, 1): - partList2ToTrim.remove(0) - # modifico la parte iniziale della prima parte - partList2ToTrim.getLinearObjectAt(0).setStartPt(newPt) - partList2ToTrim.reverse() - else: - for i in xrange(0, partNumberAtpartList1, 1): - partList1ToTrim.remove(0) - # modifico la parte iniziale della prima parte - partList1ToTrim.getLinearObjectAt(0).setStartPt(newPt1) - geom1 = QgsGeometry.fromPolyline(partList1ToTrim.asPolyline(tolerance2ApproxCurve)) - - partList1ToTrim.reverse() - - # cerco intersezione più vicina a pt nella seconda parte - newPt2 = None - if partList2ToTrim is not None: - newPt2, partNumberAtpartList2 = partList2ToTrim.getIntPtNearestToStartPt(geom_crs, limitEntitySet, edgeMode) - if newPt2 is None: # nessuna intersezione - if LinearObjectListToCut.isClosed(): # se é chiusa - if partList1ToTrim is None: - return None - newPt, partNumberAtpartList = partList1ToTrim.getIntPtNearestToStartPt(geom_crs, limitEntitySet, edgeMode) - if newPt is None: - return None - for i in xrange(0, partNumberAtpartList, 1): - partList1ToTrim.remove(0) - # modifico la parte iniziale della prima parte - partList1ToTrim.getLinearObjectAt(0).setStartPt(newPt) - - if newPt1 is None and newPt2 is None: # non ci sono punti di intersezione - return None - if newPt1 is not None and newPt2 is not None: - if ptNear(newPt1, newPt2): # i due punti di intersezione coincidono - return None - - if newPt2 is not None: - for i in xrange(0, partNumberAtpartList2, 1): - partList2ToTrim.remove(0) - # modifico la parte iniziale della seconda parte - partList2ToTrim.getLinearObjectAt(0).setStartPt(newPt2) - geom2 = QgsGeometry.fromPolyline(partList2ToTrim.asPolyline(tolerance2ApproxCurve)) - if geom1 is None: - return [geom2, None, atSubGeom] - else: - geom2 = None - - return [geom1, geom2, atSubGeom] - - -#=============================================================================== -# getIntersectionPtTrimQgsGeometry -#=============================================================================== -def getIntersectionPtTrimQgsGeometry(linearObject, limitGeom, edgeMode): - """ - la funzione calcola il punto di intersezione tra la parte lineare - e la geometria secondo la modalità . - Viene restituito il punto più vicino al punto iniziale di . - = parte lineare da estendere - = geometria da usare come limite di estensione - se = 0 si deve estendere la geometria fino ad incontrare l'oggetto più vicino - se = 1 si deve estendere la geometria fino ad incontrare l'oggetto più vicino o - anche il suo prolungamento - """ - intPts = [] - limitLinearObjectParts = QadLinearObjectList() - - # riduco in polilinee - limitGeoms = asPointOrPolyline(limitGeom) - for limitGeom in limitGeoms: - Found = False - wkbType = limitGeom.wkbType() - if wkbType == QGis.WKBPoint or wkbType == QGis.WKBPoint25D: - pt = limitGeom.asPoint() - if linearObject.containsPt(pt): - intPts.append(pt) - else: # Linestring - limitLinearObjectParts.fromPolyline(limitGeom.asPolyline()) - - # primo tratto - LimitLinearObject = limitLinearObjectParts.getLinearObjectAt(0) - pts = linearObject.getIntersectionPtsOnExtensionWithLinearObject(LimitLinearObject) - if edgeMode == 0: # senza estendere - # considero solo i punti sulla parte - for i in xrange(len(pts) - 1, -1, -1): - pt = pts[i] - if LimitLinearObject.containsPt(pt) == False: - del pts[i] - else: - if LimitLinearObject.isSegment(): - # considero solo i punti sulla parte o oltre l'inizio - for i in xrange(len(pts) - 1, -1, -1): - pt = pts[i] - if LimitLinearObject.containsPt(pt) == False: - if getDistance(LimitLinearObject.getStartPt(), pt) > \ - getDistance(LimitLinearObject.getEndPt(), pt): - del pts[i] - - # considero solo i punti sulla parte - for i in xrange(len(pts) - 1, -1, -1): - pt = pts[i] - if linearObject.containsPt(pt) == False: - del pts[i] - - intPts.extend(pts) - - # elaboro i tratti intermedi - i = 1 - while i < limitLinearObjectParts.qty() - 1: - LimitLinearObject = limitLinearObjectParts.getLinearObjectAt(i) - pts = linearObject.getIntersectionPtsOnExtensionWithLinearObject(LimitLinearObject) - # considero solo i punti sulla parte e - for j in xrange(len(pts) - 1, -1, -1): - pt = pts[j] - if LimitLinearObject.containsPt(pt) == False or linearObject.containsPt(pt) == False: - del pts[j] - - intPts.extend(pts) - i = i + 1 - - # ultimo tratto - LimitLinearObject = limitLinearObjectParts.getLinearObjectAt(-1) - pts = linearObject.getIntersectionPtsOnExtensionWithLinearObject(LimitLinearObject) - if edgeMode == 0: # senza estendere - # considero solo i punti sulla parte - for i in xrange(len(pts) - 1, -1, -1): - pt = pts[i] - if LimitLinearObject.containsPt(pt) == False: - del pts[i] - else: - if LimitLinearObject.isSegment(): - # considero solo i punti sulla parte o oltre la fine - for i in xrange(len(pts) - 1, -1, -1): - pt = pts[i] - if LimitLinearObject.containsPt(pt) == False: - if getDistance(LimitLinearObject.getStartPt(), pt) < \ - getDistance(LimitLinearObject.getEndPt(), pt): - del pts[i] - - # considero solo i punti sulla parte - for i in xrange(len(pts) - 1, -1, -1): - pt = pts[i] - if linearObject.containsPt(pt) == False: - del pts[i] - - intPts.extend(pts) - - if len(intPts) == 0: - return None - - # cerco il punto di intersezione più vicino al punto iniziale di linearObject - minDist = sys.float_info.max - LimitLinearObject.set(linearObject) - for intPt in intPts: - LimitLinearObject.setEndPt(intPt) - if LimitLinearObject.length() < minDist: - minDist = LimitLinearObject.length() - pt = intPt - - return pt - - -#=============================================================================== -# breakQgsGeometry -#=============================================================================== -def breakQgsGeometry(geom, firstPt, secondPt, tolerance2ApproxCurve): - """ - la funzione spezza la geometria in un punto (se = None) o in due punti - come fa il trim. - = geometria da tagliare - = primo punto di divisione - = secondo punto di divisione - = tolleranza di approssimazione per le curve - """ - if geom is None: - return None - - wkbType = geom.wkbType() - - if wkbType != QGis.WKBLineString and wkbType != QGis.WKBMultiLineString and \ - wkbType != QGis.WKBPolygon and wkbType != QGis.WKBMultiPolygon: - return None - - # ritorna una tupla (, - # - # - # ) - dummy = closestSegmentWithContext(firstPt, geom) - myFirstPt = dummy[1] - if dummy[2] is None: - return None - # ritorna la sotto-geometria al vertice e la sua posizione nella geometria (0-based) - subGeom, atSubGeom = getSubGeomAtVertex(geom, dummy[2]) - - mySecondPt = None - if secondPt is not None: - dummy = closestSegmentWithContext(secondPt, geom) - mySecondPt = dummy[1] - # ritorna la sotto-geometria al vertice e la sua posizione nella geometria (0-based) - subGeomSeconPt, atSubGeomSecondPt = getSubGeomAtVertex(geom, dummy[2]) - # se le sottogeometrie sono diverse - if len(atSubGeom) != len(atSubGeomSecondPt): - return None - i = 0 - while i < len(atSubGeom): - if atSubGeom[i] != atSubGeomSecondPt[i]: - return None - i = i + 1 - - LinearObjectListToCut = QadLinearObjectList() - LinearObjectListToCut.fromPolyline(subGeom.asPolyline()) - - geom1 = None - geom2 = None - - if mySecondPt is None or myFirstPt == mySecondPt: - # divido la polilinea in 2 - dummy = LinearObjectListToCut.breakOnPt(myFirstPt) - partList1ToTrim = dummy[0] - partList2ToTrim = dummy[1] - if LinearObjectListToCut.isClosed(): # se é chiusa - return None - else: - if partList1ToTrim is not None: - geom1 = QgsGeometry.fromPolyline(partList1ToTrim.asPolyline(tolerance2ApproxCurve)) - if partList2ToTrim is not None: - geom2 = QgsGeometry.fromPolyline(partList2ToTrim.asPolyline(tolerance2ApproxCurve)) - - return [geom1, geom2, atSubGeom] - else: # c'é anche il secondo punto di divisione - dist1 = LinearObjectListToCut.getDistanceFromStart(myFirstPt) - dist2 = LinearObjectListToCut.getDistanceFromStart(mySecondPt) - if dist1 < dist2: - p1 = myFirstPt - p2 = mySecondPt - else: - p1 = mySecondPt - p2 = myFirstPt - - # divido la polilinea in 2 - dummy = LinearObjectListToCut.breakOnPt(p1) - partList1ToTrim = dummy[0] - partList2ToTrim = dummy[1] - if partList2ToTrim is not None: - # divido la polilinea in 2 - dummy = partList2ToTrim.breakOnPt(p2) - partList2ToTrim = dummy[1] - - if LinearObjectListToCut.isClosed(): # se é chiusa - if partList2ToTrim is None: - partList2ToTrim = QadLinearObjectList() - if partList1ToTrim is not None: - for linearObject in partList1ToTrim.defList: - partList2ToTrim.append(linearObject) - - if partList2ToTrim.qty() > 0: - circle = LinearObjectListToCut.getCircle() - if circle is not None: # se era una cerchio - arc = QadArc() - linearObject = partList2ToTrim.getLinearObjectAt(0) - arc.fromStartSecondEndPts(linearObject.getStartPt(), linearObject.getEndPt(), partList2ToTrim.getEndPt()) - geom1 = QgsGeometry.fromPolyline(arc.asPolyline(tolerance2ApproxCurve)) - else: - geom1 = QgsGeometry.fromPolyline(partList2ToTrim.asPolyline(tolerance2ApproxCurve)) - else: # se é aperta - if partList1ToTrim is not None: - geom1 = QgsGeometry.fromPolyline(partList1ToTrim.asPolyline(tolerance2ApproxCurve)) - if partList2ToTrim is not None: - geom2 = QgsGeometry.fromPolyline(partList2ToTrim.asPolyline(tolerance2ApproxCurve)) - if geom1 is None and geom2 is None: - return None - - return [geom1, geom2, atSubGeom] - - -#=============================================================================== -# mirrorPoint -#=============================================================================== -def mirrorPoint(point, mirrorPt, mirrorAngle): - """ - la funzione sposta un punto QgsPoint secondo una linea speculare passante per un - un punto ed avente angolo - """ - pointAngle = getAngleBy2Pts(mirrorPt, point) - dist = getDistance(mirrorPt, point) - - return getPolarPointByPtAngle(mirrorPt, mirrorAngle + (mirrorAngle - pointAngle), dist) - -#=============================================================================== -# mirrorQgsGeometry -#=============================================================================== -def mirrorQgsGeometry(geom, pt1, pt2): - """ - la funzione crea copia speculare della geometria secondo una linea - """ - if geom is None: - return None - - mirrorAngle = getAngleBy2Pts(pt1, pt2) - wkbType = geom.wkbType() - - if wkbType == QGis.WKBPoint or wkbType == QGis.WKBPoint25D: - pt = geom.asPoint() # un punto - newPt = mirrorPoint(pt, pt1, mirrorAngle) - return QgsGeometry.fromPoint(newPt) - - if wkbType == QGis.WKBMultiPoint: - points = geom.asMultiPoint() # vettore di punti - for pt in points: - newPt = mirrorPoint(pt, pt1, mirrorAngle) - pt.set(newPt.x(), newPt.y()) - return QgsGeometry.fromMultiPoint(points) - - if wkbType == QGis.WKBLineString: - points = geom.asPolyline() # vettore di punti - for pt in points: - newPt = mirrorPoint(pt, pt1, mirrorAngle) - pt.set(newPt.x(), newPt.y()) - - return QgsGeometry.fromPolyline(points) - - if wkbType == QGis.WKBMultiLineString: - lines = geom.asMultiPolyline() # lista di linee - for line in lines: - for pt in line: # lista di punti - newPt = mirrorPoint(pt, pt1, mirrorAngle) - pt.set(newPt.x(), newPt.y()) - - return QgsGeometry.fromMultiPolyline(lines) - - if wkbType == QGis.WKBPolygon: - lines = geom.asPolygon() # lista di linee - for line in lines: - for pt in line: # lista di punti - newPt = mirrorPoint(pt, pt1, mirrorAngle) - pt.set(newPt.x(), newPt.y()) - - return QgsGeometry.fromPolygon(lines) - - if wkbType == QGis.WKBMultiPolygon: - polygons = geom.asMultiPolygon() # vettore di poligoni - for polygon in polygons: - for line in polygon: # lista di linee - for pt in line: # lista di punti - newPt = mirrorPoint(pt, pt1, mirrorAngle) - pt.set(newPt.x(), newPt.y()) - - return QgsGeometry.fromMultiPolygon(polygons) - - return None - - -#=============================================================================== -# closeQgsGeometry -#=============================================================================== -def closeQgsGeometry(geom, toClose, tolerance2ApproxCurve): - """ - la funzione chiude o apre la geometria - """ - if geom is None: - return None - - wkbType = geom.wkbType() - - if wkbType == QGis.WKBLineString: - linearObjectList = QadLinearObjectList() - linearObjectList.fromPolyline(geom.asPolyline()) - linearObjectList.setClose(toClose) - return QgsGeometry.fromPolyline(linearObjectList.asPolyline(tolerance2ApproxCurve)) - - if wkbType == QGis.WKBMultiLineString: - newGeom = QgsGeometry(geom) - lines = geom.asMultiPolyline() # lista di linee - atSubGeom = 0 - for line in lines: - subGeom = closeQgsGeometry(QgsGeometry.fromPolyline(line), toClose, tolerance2ApproxCurve) - newGeom = setSubGeom(newGeom, subGeom, [atSubGeom]) - atSubGeom = atSubGeom + 1 - return newGeom - - return None - - -#=============================================================================== -# reverseQgsGeometry -#=============================================================================== -def reverseQgsGeometry(geom, tolerance2ApproxCurve): - """ - la funzione inverte l'ordine dei punti della geometria - """ - if geom is None: - return None - - wkbType = geom.wkbType() - - if wkbType == QGis.WKBLineString: - linearObjectList = QadLinearObjectList() - linearObjectList.fromPolyline(geom.asPolyline()) - linearObjectList.reverse() - return QgsGeometry.fromPolyline(linearObjectList.asPolyline(tolerance2ApproxCurve)) - - if wkbType == QGis.WKBMultiLineString: - newGeom = QgsGeometry(geom) - lines = geom.asMultiPolyline() # lista di linee - atSubGeom = 0 - for line in lines: - subGeom = reverseQgsGeometry(QgsGeometry.fromPolyline(line), tolerance2ApproxCurve) - newGeom = setSubGeom(newGeom, subGeom, [atSubGeom]) - atSubGeom = atSubGeom + 1 - return newGeom - - if wkbType == QGis.WKBPolygon: - newGeom = QgsGeometry(geom) - lines = geom.asPolygon() # lista di linee - atSubGeom = 0 - for line in lines: - subGeom = reverseQgsGeometry(QgsGeometry.fromPolyline(line), tolerance2ApproxCurve) - newGeom = setSubGeom(newGeom, subGeom, [atSubGeom]) - atSubGeom = atSubGeom + 1 - return newGeom - - if wkbType == QGis.WKBMultiPolygon: - newGeom = QgsGeometry(geom) - polygons = geom.asMultiPolygon() # vettore di poligoni - atSubGeom = 0 - for polygon in polygons: - subGeom = reverseQgsGeometry(QgsGeometry.fromPolygon(polygon), tolerance2ApproxCurve) - newGeom = setSubGeom(newGeom, subGeom, [atSubGeom]) - atSubGeom = atSubGeom + 1 - return newGeom - - return None - - -#=============================================================================== -# curveQgsGeometry -#=============================================================================== -def curveQgsGeometry(geom, toCurve, tolerance2ApproxCurve): - """ - se toCurve = True: - la funzione curva ogni segmento per adattarlo alla polilinea (lista di parti segmenti-archi). - se toCurve = False: - la funzione trasforma in segmento retto ogni arco della polilinea (lista di parti segmenti-archi). - """ - if geom is None: - return None - - wkbType = geom.wkbType() - - if wkbType == QGis.WKBLineString: - linearObjectList = QadLinearObjectList() - linearObjectList.fromPolyline(geom.asPolyline()) - linearObjectList.curve(toCurve) - - return QgsGeometry.fromPolyline(linearObjectList.asPolyline(tolerance2ApproxCurve)) - - if wkbType == QGis.WKBMultiLineString: - newGeom = QgsGeometry(geom) - lines = geom.asMultiPolyline() # lista di linee - atSubGeom = 0 - for line in lines: - subGeom = curveQgsGeometry(QgsGeometry.fromPolyline(line), toCurve, tolerance2ApproxCurve) - newGeom = setSubGeom(newGeom, subGeom, [atSubGeom]) - atSubGeom = atSubGeom + 1 - return newGeom - - if wkbType == QGis.WKBPolygon: - newGeom = QgsGeometry(geom) - lines = geom.asPolygon() # lista di linee - atSubGeom = 0 - for line in lines: - subGeom = curveQgsGeometry(QgsGeometry.fromPolyline(line), toCurve, tolerance2ApproxCurve) - newGeom = setSubGeom(newGeom, subGeom, [atSubGeom]) - atSubGeom = atSubGeom + 1 - return newGeom - - if wkbType == QGis.WKBMultiPolygon: - newGeom = QgsGeometry(geom) - polygons = geom.asMultiPolygon() # vettore di poligoni - atSubGeom = 0 - for polygon in polygons: - subGeom = curveQgsGeometry(QgsGeometry.fromPolygon(polygon), toCurve, tolerance2ApproxCurve) - newGeom = setSubGeom(newGeom, subGeom, [atSubGeom]) - atSubGeom = atSubGeom + 1 - return newGeom - - return None - - -#=============================================================================== -# funzioni di creazione rettangoli -# getRectByCorners -#=============================================================================== -def getRectByCorners(firstCorner, secondCorner, rot, gapType, \ - gapValue1 = None, gapValue2 = None, tolerance2ApproxCurve = None): - """ - ritorna una lista di punti che definisce il rettangolo costruito mediante - i due spigoli opposti firstCorner e secondCorner, la rotazione con punto base firstCorner e gapType - 0 = gli spigoli del rettangolo hanno angoli retti - 1 = raccorda gli spigoli del rettangolo con un raggio di curvatura gapValue1 - 2 = smussa gli spigoli del rettangolo con 2 distanze di cimatura gapValue1, gapValue2 - tolerance2ApproxCurve = errore minimo di tolleranza per rappresentare le curve - """ - # creo un rettangolo ruotato con con angoli retti - secondCornerProj = getPolarPointByPtAngle(firstCorner, rot, 10) - pt2 = getPerpendicularPointOnInfinityLine(firstCorner, secondCornerProj, secondCorner) - angle = getAngleBy2Pts(firstCorner, pt2) - pt4 = getPolarPointByPtAngle(secondCorner, angle + math.pi, \ - getDistance(firstCorner, pt2)) - - if gapType == 0: # gli spigoli del rettangolo hanno angoli retti - return [QgsPoint(firstCorner), pt2, QgsPoint(secondCorner), pt4, QgsPoint(firstCorner)] - else: - length = getDistance(firstCorner, pt2) - width = getDistance(pt2, secondCorner) - - if gapType == 1: # raccorda gli spigoli del rettangolo con un raggio di curvatura gapValue1 - if (gapValue1 * 2) > length or (gapValue1 * 2) > width: # il rettangolo é troppo piccolo - return [QgsPoint(firstCorner), pt2, QgsPoint(secondCorner), pt4, QgsPoint(firstCorner)] - - if tolerance2ApproxCurve is None: - tolerance = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) - else: - tolerance = tolerance2ApproxCurve - - diagonal = math.sqrt((gapValue1 * gapValue1) * 2) - diagonal = gapValue1 - (diagonal / 2) - LinearObjectList = QadLinearObjectList() - - # lato - p1 = getPolarPointByPtAngle(firstCorner, angle, gapValue1) - p2 = getPolarPointByPtAngle(pt2, angle + math.pi, gapValue1) - LinearObjectList.append([p1, p2]) - # arco - angle = getAngleBy2Pts(pt2, secondCorner) - p3 = getPolarPointByPtAngle(pt2, angle, gapValue1) - pMiddle = getMiddlePoint(p2, p3) - pMiddle = getPolarPointByPtAngle(pMiddle, getAngleBy2Pts(pMiddle, pt2), diagonal) - arc = QadArc() - arc.fromStartSecondEndPts(p2, pMiddle, p3) - Inverse = False if ptNear(arc.getStartPt(), p2) else True - LinearObjectList.append([arc, Inverse]) - # lato - p4 = getPolarPointByPtAngle(secondCorner, angle + math.pi, gapValue1) - LinearObjectList.append([p3, p4]) - # arco - angle = getAngleBy2Pts(secondCorner, pt4) - p5 = getPolarPointByPtAngle(secondCorner, angle, gapValue1) - pMiddle = getMiddlePoint(p4, p5) - pMiddle = getPolarPointByPtAngle(pMiddle, getAngleBy2Pts(pMiddle, secondCorner), diagonal) - arc = QadArc() - arc.fromStartSecondEndPts(p4, pMiddle, p5) - Inverse = False if ptNear(arc.getStartPt(), p4) else True - LinearObjectList.append([arc, Inverse]) - # lato - p6 = getPolarPointByPtAngle(pt4, angle + math.pi, gapValue1) - LinearObjectList.append([p5, p6]) - # arco - angle = getAngleBy2Pts(pt4, firstCorner) - p7 = getPolarPointByPtAngle(pt4, angle, gapValue1) - pMiddle = getMiddlePoint(p6, p7) - pMiddle = getPolarPointByPtAngle(pMiddle, getAngleBy2Pts(pMiddle, pt4), diagonal) - arc = QadArc() - arc.fromStartSecondEndPts(p6, pMiddle, p7) - Inverse = False if ptNear(arc.getStartPt(), p6) else True - LinearObjectList.append([arc, Inverse]) - # lato - p8 = getPolarPointByPtAngle(firstCorner, angle + math.pi, gapValue1) - LinearObjectList.append([p7, p8]) - # arco - pMiddle = getMiddlePoint(p8, p1) - pMiddle = getPolarPointByPtAngle(pMiddle, getAngleBy2Pts(pMiddle, firstCorner), diagonal) - arc = QadArc() - arc.fromStartSecondEndPts(p8, pMiddle, p1) - Inverse = False if ptNear(arc.getStartPt(), p8) else True - LinearObjectList.append([arc, Inverse]) - return LinearObjectList.asPolyline(tolerance) - elif gapType == 2: # smussa gli spigoli del rettangolo con 2 distanze di cimatura gapValue1, gapValue2 - if (gapValue1 + gapValue2) > length or (gapValue1 + gapValue2) > width: # il rettangolo é troppo piccolo - return [QgsPoint(firstCorner), pt2, QgsPoint(secondCorner), pt4, QgsPoint(firstCorner)] - - p1 = getPolarPointByPtAngle(firstCorner, angle, gapValue2) - p2 = getPolarPointByPtAngle(pt2, angle + math.pi, gapValue1) - angle = getAngleBy2Pts(pt2, secondCorner) - p3 = getPolarPointByPtAngle(pt2, angle, gapValue2) - p4 = getPolarPointByPtAngle(secondCorner, angle + math.pi, gapValue1) - angle = getAngleBy2Pts(secondCorner, pt4) - p5 = getPolarPointByPtAngle(secondCorner, angle, gapValue2) - p6 = getPolarPointByPtAngle(pt4, angle+ math.pi, gapValue1) - angle = getAngleBy2Pts(pt4, firstCorner) - p7 = getPolarPointByPtAngle(pt4, angle, gapValue2) - p8 = getPolarPointByPtAngle(firstCorner, angle + math.pi, gapValue1) - return [p1, p2, p3, p4, p5, p6, p7, p8, p1] - - return [] - -#=============================================================================== -# getRectByCornerAndDims -#=============================================================================== -def getRectByCornerAndDims(firstCorner, lengthDim, widthDim, rot, gapType, \ - gapValue1 = None, gapValue2 = None, tolerance2ApproxCurve = None): - """ - ritorna una lista di punti che definisce il rettangolo costruito mediante - uno spigolo , la lunghezza, la larghezza, la rotazione con punto base firstCorner e gapType - 0 = gli spigoli del rettangolo hanno angoli retti - 1 = raccorda gli spigoli del rettangolo con un raggio di curvatura gapValue1 - 2 = smussa gli spigoli del rettangolo con 2 distanze di cimatura gapValue1, gapValue2 - tolerance2ApproxCurve = errore minimo di tolleranza per rappresentare le curve - """ - pt2 = getPolarPointByPtAngle(firstCorner, rot, lengthDim) - secondCorner = getPolarPointByPtAngle(pt2, rot + (math.pi / 2), widthDim) - return getRectByCorners(firstCorner, secondCorner, rot, gapType, gapValue1, gapValue2) - -#=============================================================================== -# getRectByAreaAndLength -#=============================================================================== -def getRectByAreaAndLength(firstCorner, area, lengthDim, rot, gapType, \ - gapValue1 = None, gapValue2 = None, tolerance2ApproxCurve = None): - """ - ritorna una lista di punti che definisce il rettangolo costruito mediante - uno spigolo , l'area, la larghezza, la rotazione con punto base firstCorner e gapType - 0 = gli spigoli del rettangolo hanno angoli retti - 1 = raccorda gli spigoli del rettangolo con un raggio di curvatura gapValue1 - 2 = smussa gli spigoli del rettangolo con 2 distanze di cimatura gapValue1, gapValue2 - tolerance2ApproxCurve = errore minimo di tolleranza per rappresentare le curve - """ - if gapType == 0: # gli spigoli del rettangolo hanno angoli retti - widthDim = area / lengthDim - return getRectByCornerAndDims(firstCorner, lengthDim, widthDim, rot, gapType, \ - gapValue1, gapValue2, tolerance2ApproxCurve) - else: - if gapType == 1: # raccorda gli spigoli del rettangolo con un raggio di curvatura gapValue1 - angleArea = ((2 * gapValue1) * (2 * gapValue1)) - (math.pi * gapValue1 * gapValue1) - widthDim = (area + angleArea) / lengthDim - if (gapValue1 * 2) > lengthDim or (gapValue1 * 2) > widthDim: # il rettangolo é troppo piccolo - widthDim = area / lengthDim - return getRectByCornerAndDims(firstCorner, lengthDim, widthDim, rot, gapType, \ - gapValue1, gapValue2, tolerance2ApproxCurve) - elif gapType == 2: # smussa gli spigoli del rettangolo con 2 distanze di cimatura gapValue1, gapValue2 - angleArea = 2 * (gapValue1 * gapValue2) - widthDim = (area + angleArea) / lengthDim - if (gapValue1 + gapValue2) > lengthDim or (gapValue1 + gapValue2) > widthDim: # il rettangolo é troppo piccolo - widthDim = area / lengthDim - return getRectByCornerAndDims(firstCorner, lengthDim, widthDim, rot, gapType, \ - gapValue1, gapValue2, tolerance2ApproxCurve) - -#=============================================================================== -# getRectByAreaAndWidth -#=============================================================================== -def getRectByAreaAndWidth(firstCorner, area, widthDim, rot, gapType, \ - gapValue1 = None, gapValue2 = None, tolerance2ApproxCurve = None): - """ - ritorna una lista di punti che definisce il rettangolo costruito mediante - uno spigolo , l'area, la larghezza, la rotazione con punto base firstCorner e gapType - 0 = gli spigoli del rettangolo hanno angoli retti - 1 = raccorda gli spigoli del rettangolo con un raggio di curvatura gapValue1 - 2 = smussa gli spigoli del rettangolo con 2 distanze di cimatura gapValue1, gapValue2 - tolerance2ApproxCurve = errore minimo di tolleranza per rappresentare le curve - """ - if gapType == 0: # gli spigoli del rettangolo hanno angoli retti - lengthDim = area / widthDim - return getRectByCornerAndDims(firstCorner, lengthDim, widthDim, rot, gapType, \ - gapValue1, gapValue2, tolerance2ApproxCurve) - else: - if gapType == 1: # raccorda gli spigoli del rettangolo con un raggio di curvatura gapValue1 - angleArea = math.pi * gapValue1 * gapValue1 - lengthDim = (area + angleArea) / widthDim - if (gapValue1 * 2) > lengthDim or (gapValue1 * 2) > widthDim: # il rettangolo é troppo piccolo - lengthDim = area / widthDim - return getRectByCornerAndDims(firstCorner, lengthDim, widthDim, rot, gapType, \ - gapValue1, gapValue2, tolerance2ApproxCurve) - elif gapType == 2: # smussa gli spigoli del rettangolo con 2 distanze di cimatura gapValue1, gapValue2 - angleArea = 2 * (gapValue1 * gapValue2) - lengthDim = (area + angleArea) / widthDim - if (gapValue1 + gapValue2) > lengthDim or (gapValue1 + gapValue2) > widthDim: # il rettangolo é troppo piccolo - lengthDim = area / widthDim - return getRectByCornerAndDims(firstCorner, lengthDim, widthDim, rot, gapType, \ - gapValue1, gapValue2, tolerance2ApproxCurve) - - -#=============================================================================== -# funzioni di creazione poligoni -# getPolygonByNsidesCenterRadius -#=============================================================================== -def getPolygonByNsidesCenterRadius(sideNumber, centerPt, radius, Inscribed, ptStart = None): - """ - ritorna una lista di punti che definisce il poligono costruito mediante - sideNumber = numero di lati - centerPt = centro del poligono - radius = raggio del cerchio - Inscribed = se True significa poligono inscritto altrimento circoscritto - ptStart = punto da cui partire - """ - result = [] - angleIncrement = 2 * math.pi / sideNumber - # poligono circoscritto - if Inscribed == False: - # calcolo il nuovo raggio - myRadius = radius / math.cos(angleIncrement / 2) - - if ptStart is None: - myPtStart = getPolarPointByPtAngle(centerPt, math.pi / 2 * 3 + (angleIncrement / 2), myRadius) - angle = getAngleBy2Pts(centerPt, myPtStart) - else: - angle = getAngleBy2Pts(centerPt, ptStart) - myPtStart = getPolarPointByPtAngle(centerPt, angle + (angleIncrement / 2), myRadius) - angle = getAngleBy2Pts(centerPt, myPtStart) - else: # poligono inscritto - myRadius = radius - - if ptStart is None: - myPtStart = getPolarPointByPtAngle(centerPt, math.pi / 2 * 3 + (angleIncrement / 2), myRadius) - angle = getAngleBy2Pts(centerPt, myPtStart) - else: - myPtStart = ptStart - angle = getAngleBy2Pts(centerPt, ptStart) - - result.append(myPtStart) - for i in xrange(1, sideNumber, 1): - angle = angle + angleIncrement - result.append(getPolarPointByPtAngle(centerPt, angle, myRadius)) - result.append(myPtStart) - - return result - -#=============================================================================== -# getPolygonByNsidesEdgePts -#=============================================================================== -def getPolygonByNsidesEdgePts(sideNumber, firstEdgePt, secondEdgePt): - """ - ritorna una lista di punti che definisce il poligono costruito mediante - sideNumber = numero di lati - firstEdgePt = primo punto di un lato - secondEdgePt = secondo punto di un lato - """ - result = [] - angleIncrement = 2 * math.pi / sideNumber - angle = getAngleBy2Pts(firstEdgePt, secondEdgePt) - sideLength = getDistance(firstEdgePt, secondEdgePt) - - result.append(firstEdgePt) - result.append(secondEdgePt) - lastPoint = secondEdgePt - for i in xrange(1, sideNumber - 1, 1): - angle = angle + angleIncrement - lastPoint = getPolarPointByPtAngle(lastPoint, angle, sideLength) - result.append(lastPoint) - result.append(firstEdgePt) - - return result - -#=============================================================================== -# getPolygonByNsidesArea -#=============================================================================== -def getPolygonByNsidesArea(sideNumber, centerPt, area): - """ - ritorna una lista di punti che definisce il poligono costruito mediante - sideNumber = numero di lati - centerPt = centro del poligono - area = area del poligono - """ - angle = 2 * math.pi / sideNumber - triangleArea = area / sideNumber / 2 - # divido il poligono in sideNumber triangoli - # ogni trinagolo viene diviso in 2 generando 2 trinagoli rettangoli in cui - # "(base * altezza) / 2 = Area" che equivale a "base = 2 * Area / altezza" - # "tan(alfa) = base / altezza" che equivale a "tan(alfa) * altezza = base - # per sostituzione si ha - # "tan(alfa) * altezza = 2 * Area / altezza" quindi - # "altezza = sqrt(2 * Area / tan(alfa))" - h = math.sqrt(2 * triangleArea / math.tan(angle / 2)) - - return getPolygonByNsidesCenterRadius(sideNumber, centerPt, h, False) - - -#=============================================================================== -# getSubGeomAtVertex -#=============================================================================== -def getSubGeomAtVertex(geom, atVertex): - # ritorna la sotto-geometria al vertice e la sua posizione nella geometria (0-based) - # la posizione é espressa con una lista ( []) - wkbType = geom.wkbType() - - if wkbType == QGis.WKBPoint or wkbType == QGis.WKBPoint25D: - if atVertex != 0: - return None - else: - return QgsGeometry(geom), [0] - - if wkbType == QGis.WKBMultiPoint: - pts = geom.asMultiPoint() # lista di punti - if atVertex > len(pts) - 1: - return None, None - else: - return QgsGeometry.fromPoint(pts[atVertex]), [atVertex] - - if wkbType == QGis.WKBLineString: - pts = geom.asPolyline() # lista di punti - if atVertex > len(pts) - 1: - return None, None - else: - return QgsGeometry(geom), [0] - - if wkbType == QGis.WKBMultiLineString: - # cerco in quale linea é il vertice - i = 0 - iLine = 0 - lines = geom.asMultiPolyline() # lista di linee - for line in lines: - lineLen = len(line) - if atVertex >= i and atVertex < i + lineLen: - return QgsGeometry.fromPolyline(line), [iLine] - i = lineLen - iLine = iLine + 1 - return None, None - - if wkbType == QGis.WKBPolygon: - i = 0 - iLine = 0 - lines = geom.asPolygon() # lista di linee - for line in lines: - lineLen = len(line) - if atVertex >= i and atVertex < i + lineLen: - return QgsGeometry.fromPolyline(line), [iLine] - i = lineLen - iLine = iLine + 1 - return None, None - - if wkbType == QGis.WKBMultiPolygon: - i = 0 - iPolygon = 0 - polygons = geom.asMultiPolygon() # lista di poligoni - for polygon in polygons: - iLine = 0 - for line in lines: - lineLen = len(line) - if atVertex >= i and atVertex < i + lineLen: - return QgsGeometry.fromPolyline(line), [iPolygon, iLine] - i = lineLen - iLine = iLine + 1 - iPolygon = iPolygon + 1 - - return None - - -#=============================================================================== -# setSubGeomAt -#=============================================================================== -def getSubGeomAt(geom, atSubGeom): - # ritorna la sotto-geometria la cui posizione - # é espressa con una lista ( []) - wkbType = geom.wkbType() - - ndx = 0 - if wkbType == QGis.WKBPoint or wkbType == QGis.WKBPoint25D or wkbType == QGis.WKBLineString: - if atSubGeom[0] == 0: - return QgsGeometry(geom) - - if wkbType == QGis.WKBMultiPoint: - nPoint = atSubGeom[0] - return QgsGeometry(geom.vertexAt(nPoint)) - - if wkbType == QGis.WKBMultiLineString: - nLine = atSubGeom[0] - lines = geom.asMultiPolyline() # lista di linee - if nLine < len(lines) and nLine >= -len(lines): - return QgsGeometry.fromPolyline(lines[nLine]) - - if wkbType == QGis.WKBPolygon: - nLine = atSubGeom[0] - lines = geom.asPolygon() # lista di linee - if nLine < len(lines) and nLine >= -len(lines): - return QgsGeometry.fromPolyline(lines[nLine]) - - if wkbType == QGis.WKBMultiPolygon: - nPolygon = atSubGeom[0] - nLine = atSubGeom[1] - polygons = geom.asMultiPolygon() # lista di poligoni - if nPolygon < len(polygons) and nPolygon >= -len(polygons): - lines = polygons[nPolygon] - if nLine < len(lines) and nLine >= -len(lines): - return QgsGeometry.fromPolyline(lines[nLine]) - - return None - - -#=============================================================================== -# setSubGeom -#=============================================================================== -def setSubGeom(geom, SubGeom, atSubGeom): - # restituisce una geometria con la sotto-geometria alla posizione - # la posizione é espressa con una lista ( []) - wkbType = geom.wkbType() - subWkbType = SubGeom.wkbType() - - ndx = 0 - if wkbType == QGis.WKBPoint or wkbType == QGis.WKBPoint25D or wkbType == QGis.WKBLineString: - if atSubGeom[0] == 0 and \ - (subWkbType == QGis.WKBPoint or subWkbType == QGis.WKBPoint25D or subWkbType == QGis.WKBLineString): - return QgsGeometry(SubGeom) - - if wkbType == QGis.WKBMultiPoint: - nPoint = atSubGeom[0] - if subWkbType == QGis.WKBPoint or subWkbType == QGis.WKBPoint25D: - result = QgsGeometry(geom) - pt = SubGeom.asPoint() - if result.moveVertex(pt.x, pt.y(), nPoint) == True: - return result - - if wkbType == QGis.WKBMultiLineString: - if subWkbType == QGis.WKBLineString: - nLine = atSubGeom[0] - lines = geom.asMultiPolyline() # lista di linee - if nLine < len(lines) and nLine >= -len(lines): - del lines[nLine] - lines.insert(nLine, SubGeom.asPolyline()) - return QgsGeometry.fromMultiPolyline(lines) - - if wkbType == QGis.WKBPolygon: - if subWkbType == QGis.WKBLineString: - nLine = atSubGeom[0] - lines = geom.asPolygon() # lista di linee - if nLine < len(lines) and nLine >= -len(lines): - del lines[nLine] - lines.insert(nLine, SubGeom.asPolyline()) - # per problemi di approssimazione con LL il primo punto e l'ultimo non sono uguali quindi lo forzo - lines[0][-1].set(lines[0][0].x(), lines[0][0].y()) - return QgsGeometry.fromPolygon(lines) - - if wkbType == QGis.WKBMultiPolygon: - if subWkbType == QGis.WKBLineString: - nPolygon = atSubGeom[0] - nLine = atSubGeom[1] - polygons = geom.asMultiPolygon() # lista di poligoni - if nPolygon < len(polygons) and nPolygon >= -len(polygons): - lines = polygons[nPolygon] - if nLine < len(lines) and nLine >= -len(lines): - del lines[nLine] - lines.insert(nLine, SubGeom.asPolyline()) - # per problemi di approssimazione con LL il primo punto e l'ultimo non sono uguali quindi lo forzo - lines[0][-1].set(lines[0][0].x(), lines[0][0].y()) - return QgsGeometry.fromMultiPolygon(polygons) - elif subWkbType == QGis.WKBPolygon: - nPolygon = atSubGeom[0] - polygons = geom.asMultiPolygon() # lista di poligoni - if nPolygon < len(polygons) and nPolygon >= -len(polygons): - del polygons[nPolygon] - polygons.insert(nPolygon, SubGeom.asPolygon()) - return QgsGeometry.fromMultiPolygon(polygons) - - return None - - -#=============================================================================== -# delSubGeom -#=============================================================================== -def delSubGeom(geom, atSubGeom): - # restituisce una geometria con la sotto-geometria alla posizione cancellata - # la posizione é espressa con una lista ( []) - wkbType = geom.wkbType() - - ndx = 0 - if wkbType == QGis.WKBPoint or wkbType == QGis.WKBPoint25D or wkbType == QGis.WKBLineString: - return None - - if wkbType == QGis.WKBMultiPoint: - nPoint = atSubGeom[0] - result = QgsGeometry(geom) - pt = SubGeom.asPoint() - if result.deleteVertex(nPoint) == True: - return result - - if wkbType == QGis.WKBMultiLineString: - nLine = atSubGeom[0] - lines = geom.asMultiPolyline() # lista di linee - if nLine < len(lines) and nLine >= -len(lines): - del lines[nLine] - return QgsGeometry.fromMultiPolyline(lines) - - if wkbType == QGis.WKBPolygon: - nLine = atSubGeom[0] - lines = geom.asPolygon() # lista di linee - if nLine < len(lines) and nLine >= -len(lines): - del lines[nLine] - return QgsGeometry.fromPolygon(lines) - - if wkbType == QGis.WKBMultiPolygon: - nPolygon = atSubGeom[0] - nLine = atSubGeom[1] if len(atSubGeom) > 1 else None - polygons = geom.asMultiPolygon() # lista di poligoni - if nPolygon < len(polygons) and nPolygon >= -len(polygons): - if nLine is not None: - lines = polygons[nPolygon] - if nLine < len(lines) and nLine >= -len(lines): - del lines[nLine] - return QgsGeometry.fromMultiPolygon(polygons) - else: - del polygons[nPolygon] - return QgsGeometry.fromMultiPolygon(polygons) - - return None - - -#=============================================================================== -# getOffSetCircle -#=============================================================================== -def getOffSetCircle(circle, offSetDist, offSetSide): - """ - la funzione ritorna l'offset di un cerchio - secondo una distanza e un lato di offset ("internal" o "external") - """ - if offSetSide == "internal": - # offset verso l'interno del cerchio - radius = circle.radius - offSetDist - if radius <= 0: - return None - else: - # offset verso l'esterno del cerchio - radius = circle.radius + offSetDist - - result = QadCircle(circle) - result.radius = radius - - return result - - -#=============================================================================== -# getOffSetArc -#=============================================================================== -def getOffSetArc(arc, offSetDist, offSetSide): - """ - la funzione ritorna l'offset di un arco - secondo una distanza e un lato di offset ("internal" o "external") - """ - if offSetSide == "internal": - # offset verso l'interno del cerchio - radius = arc.radius - offSetDist - if radius <= 0: - return None - else: - # offset verso l'esterno del cerchio - radius = arc.radius + offSetDist - - result = QadArc(arc) - result.radius = radius - - return result - - -#=============================================================================== -# getOffSetLine -#=============================================================================== -def getOffSetLine(pt1, pt2, offSetDist, offSetSide): - """ - la funzione ritorna l'offset di una linea (lista di 2 punti) - secondo una distanza e un lato di offset ("right" o "left") - """ - if offSetSide == "right": - AngleProjected = getAngleBy2Pts(pt1, pt2) - (math.pi / 2) - else: - AngleProjected = getAngleBy2Pts(pt1, pt2) + (math.pi / 2) - # calcolo il punto proiettato - pt1Proj = getPolarPointByPtAngle(pt1, AngleProjected, offSetDist) - pt2Proj = getPolarPointByPtAngle(pt2, AngleProjected, offSetDist) - - return [pt1Proj, pt2Proj] - - -#=============================================================================== -# offsetBridgeTheGapBetweenLines -#=============================================================================== -def offsetBridgeTheGapBetweenLines(line1, line2, offset, gapType): - """ - la funzione colma il vuoto tra 2 segmenti retti (QadLinearObject) nel comando offset - secondo una distanza (che corrisponde alla distanza di offset s - chiamata da tale comando) ed un modo : - 0 = Estende i segmenti alle relative intersezioni proiettate - 1 = Raccorda i segmenti attraverso un arco di raccordo di raggio - 2 = Cima i segmenti di linea in corrispondenza delle intersezioni proiettate. - La distanza perpendicolare da ciascuna cima al rispettivo vertice - sull'oggetto originale é uguale alla distanza . - - Se - Ritorna una lista di 3 elementi (None in caso di errore): - una linea che sostituisce , se = None va rimossa - un arco, se = None non c'é arco di raccordo tra le due linee - una linea che sostituisce , se = None va rimossa - """ - # cerco il punto di intersezione tra le due linee - ptInt = getIntersectionPointOn2InfinityLines(line1.getStartPt(), line1.getEndPt(), \ - line2.getStartPt(), line2.getEndPt()) - if ptInt is None: # linee parallele - return None - distBetweenLine1Pt1AndPtInt = getDistance(line1.getStartPt(), ptInt) - distBetweenLine1Pt2AndPtInt = getDistance(line1.getEndPt(), ptInt) - distBetweenLine2Pt1AndPtInt = getDistance(line2.getStartPt(), ptInt) - distBetweenLine2Pt2AndPtInt = getDistance(line2.getEndPt(), ptInt) - - if gapType == 0: # Estende i segmenti - if distBetweenLine1Pt1AndPtInt > distBetweenLine1Pt2AndPtInt: - # secondo punto di line1 più vicino al punto di intersezione - newLine1 = QadLinearObject([line1.getStartPt(), ptInt]) - else: - # primo punto di line1 più vicino al punto di intersezione - newLine1 = QadLinearObject([ptInt, line1.getEndPt()]) - - if distBetweenLine2Pt1AndPtInt > distBetweenLine2Pt2AndPtInt: - # secondo punto di line2 più vicino al punto di intersezione - newLine2 = QadLinearObject([line2.getStartPt(), ptInt]) - else: - # primo punto di line2 più vicino al punto di intersezione - newLine2 = QadLinearObject([ptInt, line2.getEndPt()]) - - return [newLine1, None, newLine2] - elif gapType == 1: # Raccorda i segmenti - pt1Distant = line1.getStartPt() if distBetweenLine1Pt1AndPtInt > distBetweenLine1Pt2AndPtInt else line1.getEndPt() - angleLine1 = getAngleBy2Pts(ptInt, pt1Distant) - - pt2Distant = line2.getStartPt() if distBetweenLine2Pt1AndPtInt > distBetweenLine2Pt2AndPtInt else line2.getEndPt() - angleLine2 = getAngleBy2Pts(ptInt, pt2Distant) - - bisectorLine = getBisectorInfinityLine(pt1Distant, ptInt, pt2Distant, True) - # cerco il punto di intersezione tra la bisettrice e - # la retta che congiunge i punti più distanti delle due linee - pt = getIntersectionPointOn2InfinityLines(bisectorLine[0], bisectorLine[1], \ - pt1Distant, pt2Distant) - angleBisectorLine = getAngleBy2Pts(ptInt, pt) - #angleBisectorLine = getAngleBy2Pts(bisectorLine[0], bisectorLine[1]) - - # calcolo l'angolo (valore assoluto) tra un lato e la bisettrice - alfa = angleLine1 - angleBisectorLine - if alfa < 0: - alfa = angleBisectorLine - angleLine1 - if alfa > math.pi: - alfa = (2 * math.pi) - alfa - - # calcolo l'angolo del triangolo rettangolo sapendo che la somma degli angoli interni = 180 - # - alfa - 90 gradi (angolo retto) - distFromPtInt = math.tan(math.pi - alfa - (math.pi / 2)) * offset - pt1Proj = getPolarPointByPtAngle(ptInt, angleLine1, distFromPtInt) - pt2Proj = getPolarPointByPtAngle(ptInt, angleLine2, distFromPtInt) - # Pitagora - distFromPtInt = math.sqrt((distFromPtInt * distFromPtInt) + (offset * offset)) - secondPt = getPolarPointByPtAngle(ptInt, angleBisectorLine, distFromPtInt - offset) - arc = QadArc() - arc.fromStartSecondEndPts(pt1Proj, secondPt, pt2Proj) - - if distBetweenLine1Pt1AndPtInt > distBetweenLine1Pt2AndPtInt: - # secondo punto di line1 più vicino al punto di intersezione - newLine1 = QadLinearObject([pt1Distant, pt1Proj]) - else: - # primo punto di line1 più vicino al punto di intersezione - newLine1 = QadLinearObject([pt1Proj, pt1Distant]) - - if distBetweenLine2Pt1AndPtInt > distBetweenLine2Pt2AndPtInt: - # secondo punto di line2 più vicino al punto di intersezione - newLine2 = QadLinearObject([pt2Distant, pt2Proj]) - else: - # primo punto di line2 più vicino al punto di intersezione - newLine2 = QadLinearObject([pt2Proj, pt2Distant]) - - # se i punti sono così vicini da essere considerati uguali - inverse = False if ptNear(newLine1.getEndPt(), arc.getStartPt()) else True - return [newLine1, QadLinearObject([arc, inverse]), newLine2] - elif gapType == 2: # Cima i segmenti - bisectorLine = getBisectorInfinityLine(line1.getEndPt(), ptInt, line2.getEndPt(), True) - angleBisectorLine = getAngleBy2Pts(bisectorLine[0], bisectorLine[1]) - ptProj = getPolarPointByPtAngle(ptInt, angleBisectorLine, offset) - - pt1Proj = getPerpendicularPointOnInfinityLine(line1.getStartPt(), line1.getEndPt(), ptProj) - if distBetweenLine1Pt1AndPtInt > distBetweenLine1Pt2AndPtInt: - # secondo punto di line1 più vicino al punto di intersezione - newLine1 = QadLinearObject([line1.getStartPt(), pt1Proj]) - else: - # primo punto di line1 più vicino al punto di intersezione - newLine1 = QadLinearObject([pt1Proj, line1.getEndPt()]) - - pt2Proj = getPerpendicularPointOnInfinityLine(line2.getStartPt(), line2.getEndPt(), ptProj) - if distBetweenLine2Pt1AndPtInt > distBetweenLine2Pt2AndPtInt: - # secondo punto di line2 più vicino al punto di intersezione - newLine2 = QadLinearObject([line2.getStartPt(), pt2Proj]) - else: - # primo punto di line2 più vicino al punto di intersezione - newLine2 = QadLinearObject([pt2Proj, line2.getEndPt()]) - - return [newLine1, QadLinearObject([pt1Proj, pt2Proj]), newLine2] - - return None - - -#=============================================================================== -# bridgeTheGapBetweenLines -#=============================================================================== -def bridgeTheGapBetweenLines(line1, ptOnLine1, line2, ptOnLine2, radius, filletMode): - """ - la funzione raccorda 2 segmenti retti (QadLinearObject) attraverso - un arco di raccordo di raggio che più si avvicinza ai punti di selezione - sul segmento 1 e sul segmento 2 . - modalità di raccordo; 1=Taglia-estendi, 2=Non taglia-estendi - - Ritorna una lista di 3 elementi (None in caso di errore): - una linea che sostituisce , se = None va rimossa - un arco, se = None non c'é arco di raccordo tra le due linee - una linea che sostituisce , se = None va rimossa - """ - if radius == 0: # Estende i segmenti - # cerco il punto di intersezione tra le due linee - ptInt = getIntersectionPointOn2InfinityLines(line1.getStartPt(), line1.getEndPt(), \ - line2.getStartPt(), line2.getEndPt()) - if ptInt is None: # linee parallele - return None - - distBetweenLine1Pt1AndPtInt = getDistance(line1.getStartPt(), ptInt) - distBetweenLine1Pt2AndPtInt = getDistance(line1.getEndPt(), ptInt) - distBetweenLine2Pt1AndPtInt = getDistance(line2.getStartPt(), ptInt) - distBetweenLine2Pt2AndPtInt = getDistance(line2.getEndPt(), ptInt) - - if distBetweenLine1Pt1AndPtInt > distBetweenLine1Pt2AndPtInt: - # secondo punto di line1 più vicino al punto di intersezione - resLine1 = QadLinearObject([line1.getStartPt(), ptInt]) - else: - # primo punto di line1 più vicino al punto di intersezione - resLine1 = QadLinearObject([ptInt, line1.getEndPt()]) - - if distBetweenLine2Pt1AndPtInt > distBetweenLine2Pt2AndPtInt: - # secondo punto di line2 più vicino al punto di intersezione - resLine2 = QadLinearObject([line2.getStartPt(), ptInt]) - else: - # primo punto di line2 più vicino al punto di intersezione - resLine2 = QadLinearObject([ptInt, line2.getEndPt()]) - - return [resLine1, None, resLine2] - else: # Raccorda i segmenti - filletArcs = getFilletArcsBetweenLines(line1, line2, radius) - - # cerco l'arco valido più vicino a ptOnLine1 e ptOnLine2 - AvgList = [] - Avg = sys.float_info.max - - resLine1 = QadLinearObject() - resFilletArc = QadLinearObject() - resLine2 = QadLinearObject() - for filletArc in filletArcs: - # ricavo il nuovo segmento in modo che sia tangente con l'arco di raccordo - newLine1, distFromPtOnLine1 = getNewLineAccordingFilletArc(line1, filletArc, ptOnLine1) - if newLine1 is None: - continue - # ricavo il nuovo segmento in modo che sia tangente con l'arco di raccordo - newLine2, distFromPtOnLine2 = getNewLineAccordingFilletArc(line2, filletArc, ptOnLine2) - if newLine2 is None: - continue - - del AvgList[:] - AvgList.append(distFromPtOnLine1) - AvgList.append(distFromPtOnLine2) - - currAvg = numericListAvg(AvgList) - if currAvg < Avg: # mediamente più vicino - Avg = currAvg - resLine1.set(newLine1) - resFilletArc.setArc(filletArc, False) - resLine2.set(newLine2) - - if Avg == sys.float_info.max: - return None - - if filletMode == 1: # 1=Taglia-estendi - return [resLine1, resFilletArc, resLine2] - else: - return [None, resFilletArc, None] - - -#=============================================================================== -# getNewLineAccordingFilletArc -#=============================================================================== -def getNewLineAccordingFilletArc(line, filletArc, ptOnLine): - """ - dato un segmento retto (line di tipo ) e un arco che si - raccorda ad esso (), la funzione restituisce un nuovo segmento retto - modificando in modo che sia tangente all'arco di raccordo. - Inoltre, usando un punto indicato sul segmento restituisce - la distanza di quel punto dal punto di tangenza con l'arco di raccordo. - """ - newLine = QadLinearObject() - - # determino quale punto (iniziale o finale) dell'arco di raccordo - # si interseca sul prolugamento del segmento retto - if isPtOnInfinityLine(line.getStartPt(), line.getEndPt(), filletArc.getStartPt()): - filletPtOnLine = filletArc.getStartPt() - isStartFilletPtOnLine = True - else: - filletPtOnLine = filletArc.getEndPt() - isStartFilletPtOnLine = False - - if line.containsPt(filletPtOnLine) == True: # se il punto é all'interno del segmento - newLine.set([filletPtOnLine, line.getEndPt()]) - - if isStartFilletPtOnLine: # se il punto iniziale dell'arco di raccordo é sulla linea - # se il nuovo segmento non é un segmento valido - if ptNear(newLine.getStartPt(), newLine.getEndPt()): - # se l'arco di raccordo é tangente sul punto finale del nuovo segmento - if TanDirectionNear(line.getTanDirectionOnEndPt(), \ - normalizeAngle(filletArc.getTanDirectionOnStartPt())) == True: - newLine.set(line) # ripristino il segmento originale - else: - # se l'arco di raccordo non é tangente sul punto iniziale del nuovo segmento - if TanDirectionNear(newLine.getTanDirectionOnStartPt(), \ - normalizeAngle(filletArc.getTanDirectionOnStartPt() + math.pi)) == False: - newLine.set([line.getStartPt(), filletPtOnLine]) - - # se il nuovo segmento non é un segmento valido - if ptNear(newLine.getStartPt(), newLine.getEndPt()) or \ - newLine.containsPt(ptOnLine) == False: - return None, None - - # calcolo la distanza dal punto ptOnLine - distFromPtOnLine = getDistance(ptOnLine, filletPtOnLine) - else: # se il punto finale dell'arco di raccordo é sulla linea - # se il nuovo segmento non é un segmento valido - if ptNear(newLine.getStartPt(), newLine.getEndPt()): - # se l'arco di raccordo é tangente sul punto finale del nuovo segmento - if TanDirectionNear(line.getTanDirectionOnEndPt(), \ - normalizeAngle(filletArc.getTanDirectionOnEndPt() + math.pi)) == True: - newLine.set(line) # ripristino il segmento originale - else: - # se l'arco di raccordo non é tangente sul punto iniziale del nuovo segmento - if TanDirectionNear(newLine.getTanDirectionOnStartPt(), \ - filletArc.getTanDirectionOnEndPt()) == False: - newLine.set([line.getStartPt(), filletPtOnLine]) - - # se il nuovo segmento non é un segmento valido - if ptNear(newLine.getStartPt(), newLine.getEndPt()) or \ - newLine.containsPt(ptOnLine) == False: - return None, None - - # calcolo la distanza dal punto ptOnLine - distFromPtOnLine = getDistance(ptOnLine, filletPtOnLine) - - return newLine, distFromPtOnLine - else: # se il punto é all'esterno del segmento - if getDistance(line.getStartPt(), filletPtOnLine) < getDistance(line.getEndPt(), filletPtOnLine): - newLine.set([filletPtOnLine, line.getEndPt()]) - else: - newLine.set([line.getStartPt(), filletPtOnLine]) - - return getNewLineAccordingFilletArc(newLine, filletArc, ptOnLine) - - -#=============================================================================== -# auxFilletArcsBetweenLines -#=============================================================================== -def auxFilletArcsBetweenLines(ptLine1, ptLine2, intPt, radius, both = True): - """ - la funzione di ausilio a getFilletArcsBetweenLines - Ritorna una lista dei possibili archi di raccordo tra la - linea 1 che va da fino al punto di intersezione con la linea 2 - e - linea2 che va da fino al punto di intersezione con la linea 1 - """ - res = [] - - angleLine1 = getAngleBy2Pts(intPt, ptLine1) - angleLine2 = getAngleBy2Pts(intPt, ptLine2) - - bisectorLine = getBisectorInfinityLine(ptLine1, intPt, ptLine2, True) - # cerco il punto di intersezione tra la bisettrice e - # la retta che congiunge i punti più distanti delle due linee - pt = getIntersectionPointOn2InfinityLines(bisectorLine[0], bisectorLine[1], \ - ptLine1, ptLine2) - angleBisectorLine = getAngleBy2Pts(intPt, pt) - - # calcolo l'angolo (valore assoluto) tra un lato e la bisettrice - alfa = angleLine1 - angleBisectorLine - if alfa < 0: - alfa = angleBisectorLine - angleLine1 - if alfa > math.pi: - alfa = (2 * math.pi) - alfa - - # calcolo l'angolo del triangolo rettangolo sapendo che la somma degli angoli interni = 180 - # - alfa - 90 gradi (angolo retto) - distFromIntPt = math.tan(math.pi - alfa - (math.pi / 2)) * radius - pt1Proj = getPolarPointByPtAngle(intPt, angleLine1, distFromIntPt) - pt2Proj = getPolarPointByPtAngle(intPt, angleLine2, distFromIntPt) - # Pitagora - distFromIntPt = math.sqrt((distFromIntPt * distFromIntPt) + (radius * radius)) - secondPt = getPolarPointByPtAngle(intPt, angleBisectorLine, distFromIntPt - radius) - filletArc = QadArc() - if filletArc.fromStartSecondEndPts(pt1Proj, secondPt, pt2Proj) == True: - res.append(filletArc) - if both: - # stesso arco con il punto iniziale e finale invertiti - filletArc = QadArc(filletArc) - filletArc.inverse() - res.append(filletArc) - - return res - -#=============================================================================== -# getFilletArcsBetweenCircleLine -#=============================================================================== -def getFilletArcsBetweenLines(line1, line2, radius): - """ - la funzione raccorda due linee rette (QadLinearObject) attraverso - un arco di raccordo di raggio . - - Ritorna una lista dei possibili archi - """ - res = [] - - # cerco il punto di intersezione tra le due linee - intPt = getIntersectionPointOn2InfinityLines(line1.getStartPt(), line1.getEndPt(), \ - line2.getStartPt(), line2.getEndPt()) - if intPt is None: # linee parallele - # calcolo la proiezione perpendicolare del punto iniziale di su - ptPerp = getPerpendicularPointOnInfinityLine(line2.getStartPt(), line2.getEndPt(), line1.getStartPt()) - d = getDistance(line1.getStartPt(), ptPerp) - # d deve essere 2 volte - if doubleNear(radius * 2, d): - angle = getAngleBy2Pts(line1.getStartPt(), ptPerp) - ptCenter = getPolarPointByPtAngle(line1.getStartPt(), angle, radius) - filletArc = QadArc() - if filletArc.fromStartCenterEndPts(line1.getStartPt(), ptCenter, ptPerp) == True: - res.append(filletArc) - # stesso arco con il punto iniziale e finale invertiti - filletArc = QadArc() - if filletArc.fromStartCenterEndPts(ptPerp, ptCenter, line1.getStartPt()) == True: - res.append(filletArc) - - ptPerp = getPolarPointByPtAngle(line1.getEndPt(), angle, d) - ptCenter = getPolarPointByPtAngle(line1.getEndPt(), angle, radius) - filletArc = QadArc() - if filletArc.fromStartCenterEndPts(line1.getEndPt(), ptCenter, ptPerp) == True: - res.append(filletArc) - # stesso arco con il punto iniziale e finale invertiti - filletArc = QadArc() - if filletArc.fromStartCenterEndPts(ptPerp, ptCenter, line1.getEndPt()) == True: - res.append(filletArc) - else: # linee non parallele - angleLine1 = getAngleBy2Pts(line1.getStartPt(), line1.getEndPt()) - angleLine2 = getAngleBy2Pts(line2.getStartPt(), line2.getEndPt()) - - ptLine1 = getPolarPointByPtAngle(intPt, angleLine1, 1) - ptLine2 = getPolarPointByPtAngle(intPt, angleLine2, 1) - res.extend(auxFilletArcsBetweenLines(ptLine1, ptLine2, intPt, radius)) - - ptLine2 = getPolarPointByPtAngle(intPt, angleLine2, -1) - res.extend(auxFilletArcsBetweenLines(ptLine1, ptLine2, intPt, radius)) - - ptLine1 = getPolarPointByPtAngle(intPt, angleLine1, -1) - res.extend(auxFilletArcsBetweenLines(ptLine1, ptLine2, intPt, radius)) - - ptLine2 = getPolarPointByPtAngle(intPt, angleLine2, 1) - res.extend(auxFilletArcsBetweenLines(ptLine1, ptLine2, intPt, radius)) - - return res - - -#=============================================================================== -# bridgeTheGapBetweenCircleLine -#=============================================================================== -def bridgeTheGapBetweenCircleLine(circle, ptOnCircle, line, ptOnLine, radius, filletMode): - """ - la funzione raccorda un cerchio e un segmento retto (QadLinearObject) attraverso - un arco di raccordo di raggio che più si avvicinza ai punti di selezione - sul cerchio e sul segmento retto . - modalità di raccordo; 1=Taglia-estendi, 2=Non taglia-estendi - - Ritorna una lista di 3 elementi (None in caso di errore): - None - un arco, se = None non c'é arco di raccordo tra le due linee - una linea che sostituisce - """ - # ricavo i possibili archi di raccordo - _circle = circle.getCircle() - filletArcs = getFilletArcsBetweenCircleLine(_circle, line, radius) - - # cerco l'arco valido più vicino a ptOnArc e ptOnLine - AvgList = [] - Avg = sys.float_info.max - - resFilletArc = QadLinearObject() - resLine = QadLinearObject() - for filletArc in filletArcs: - # ricavo il nuovo segmento in modo che sia tangente con l'arco di raccordo - newLine, distFromPtOnLine = getNewLineAccordingFilletArc(line, filletArc, ptOnLine) - if newLine is None: - continue - - if _circle.isPtOnCircle(filletArc.getStartPt()): - distFromPtOnCircle = _circle.lengthBetween2Points(filletArc.getStartPt(), \ - ptOnCircle, \ - filletArc.getTanDirectionOnStartPt() + math.pi) - else: - distFromPtOnCircle = _circle.lengthBetween2Points(filletArc.getEndPt(), \ - ptOnCircle, \ - filletArc.getTanDirectionOnEndPt()) - - del AvgList[:] - AvgList.append(distFromPtOnLine) - AvgList.append(distFromPtOnCircle) - - currAvg = numericListAvg(AvgList) - if currAvg < Avg: # mediamente piùvicino - Avg = currAvg - resLine.set(newLine) - resFilletArc.setArc(filletArc, False) - - if Avg == sys.float_info.max: - return None - - if filletMode == 1: # 1=Taglia-estendi - return [None, resFilletArc, resLine] - else: - return [None, resFilletArc, None] - - -#=============================================================================== -# auxFilletArcsBetweenCircleLine -#=============================================================================== -def auxFilletArcsBetweenCircleLine(circle, line, origCircle, origLine, both = True): - """ - la funzione di ausilio a getFilletArcsBetweenArcLine - Ritorna una lista dei possibili archi di raccordo tra e - """ - res = [] - # calcolo le intersezioni tra la circonferenza del cerchio e la retta parallela a - # che daranno origine ai centri degli archi di raccordo - intPts = circle.getIntersectionPointsWithInfinityLine(line[0], line[1]) - if len(intPts) > 0: - # un punto di tangenza é dato dal punto a distanza radius dal centro di - # in direzione centro dell'arco di raccordo - angle = getAngleBy2Pts(origCircle.center, intPts[0]) - tanCirclePt = getPolarPointByPtAngle(origCircle.center, angle, origCircle.radius) - # un punto di tangenza é la proiezione perpendicolare del centro dell'arco di raccordo - # con - ptPerp = getPerpendicularPointOnInfinityLine(origLine.getStartPt(), origLine.getEndPt(), intPts[0]) - filletArc = QadArc() - if filletArc.fromStartCenterEndPts(tanCirclePt, \ - intPts[0], \ - ptPerp) == True: - res.append(filletArc) - if both: - # stesso arco con il punto iniziale e finale invertiti - filletArc = QadArc(filletArc) - filletArc.inverse() - res.append(filletArc) - - if len(intPts) > 1: # # due centri per i due archi di raccordo - # un punto di tangenza é dato dal punto a distanza arc.radius dal centro di - # in direzione centro dell'arco di raccordo - angle = getAngleBy2Pts(origCircle.center, intPts[1]) - tanCirclePt = getPolarPointByPtAngle(origCircle.center, angle, origCircle.radius) - # un punto di tangenza é la proiezione perpendicolare del centro dell'arco di raccordo - # con - ptPerp = getPerpendicularPointOnInfinityLine(origLine.getStartPt(), origLine.getEndPt(), intPts[1]) - filletArc = QadArc() - if filletArc.fromStartCenterEndPts(tanCirclePt, \ - intPts[1], \ - ptPerp) == True: - res.append(filletArc) - if both: - # stesso arco con il punto iniziale e finale invertiti - filletArc = QadArc(filletArc) - filletArc.inverse() - res.append(filletArc) - - return res - -#=============================================================================== -# getFilletArcsBetweenCircleLine -#=============================================================================== -def getFilletArcsBetweenCircleLine(circle, line, radius): - """ - la funzione raccorda un arco e una linea retta (QadLinearObject) attraverso - un arco di raccordo di raggio . - - Ritorna una lista dei possibili archi - """ - res = [] - - offsetCircle = QadCircle(circle) - - intPts = circle.getIntersectionPointsWithInfinityLine(line.getStartPt(), line.getEndPt()) - if len(intPts) == 0: - # se il cerchio e la retta generata dall'estensione di line - # non hanno punti in comune - leftOfLine = line.leftOf(circle.center) - # creo una retta parallela a ad una distanza verso il centro di - linePar = [] - angle = line.getTanDirectionOnStartPt() - if leftOfLine < 0: # a sinistra - linePar.append(getPolarPointByPtAngle(line.getStartPt(), angle + math.pi / 2, radius)) - linePar.append(getPolarPointByPtAngle(line.getEndPt(), angle + math.pi / 2, radius)) - else :# a destra - linePar.append(getPolarPointByPtAngle(line.getStartPt(), angle - math.pi / 2, radius)) - linePar.append(getPolarPointByPtAngle(line.getEndPt(), angle - math.pi / 2, radius)) - - # Calcolo la distanza dal centro di a - ptPerp = getPerpendicularPointOnInfinityLine(line.getStartPt(), line.getEndPt(), circle.center) - d = getDistance(circle.center, ptPerp) - # deve essere >= (d - raggio cerchio) / 2 - if radius >= (d - circle.radius) / 2: - - # caso 1: raccordo tra e formando un flesso con - - # creo un cerchio con raggio aumentato di - offsetCircle.radius = circle.radius + radius - res.extend(auxFilletArcsBetweenCircleLine(offsetCircle, linePar, circle, line)) - - # caso 2: raccordo tra e senza formare un flesso con - - # deve essere > raggio cerchio - if radius > circle.radius: - # creo un cerchio con raggio = - circle.radius - offsetCircle.radius = radius - circle.radius - res.extend(auxFilletArcsBetweenCircleLine(offsetCircle, linePar, circle, line)) - else: - # se il cerchio e la retta generata dall'estensione di line - # hanno punti in comune - # creo una retta parallela a ad una distanza verso sinistra - linePar = [] - angle = line.getTanDirectionOnStartPt() - linePar.append(getPolarPointByPtAngle(line.getStartPt(), angle + math.pi / 2, radius)) - linePar.append(getPolarPointByPtAngle(line.getEndPt(), angle + math.pi / 2, radius)) - - # creo un cerchio con raggio aumentato di - offsetCircle.radius = circle.radius + radius - res.extend(auxFilletArcsBetweenCircleLine(offsetCircle, linePar, circle, line)) - - if circle.radius > radius: - # creo un cerchio con raggio diminuito di - offsetCircle.radius = circle.radius - radius - res.extend(auxFilletArcsBetweenCircleLine(offsetCircle, linePar, circle, line)) - - # creo una retta parallela a ad una distanza verso destra - del linePar[:] # svuoto la lista - linePar.append(getPolarPointByPtAngle(line.getStartPt(), angle - math.pi / 2, radius)) - linePar.append(getPolarPointByPtAngle(line.getEndPt(), angle - math.pi / 2, radius)) - - # creo un cerchio con raggio aumentato di - offsetCircle.radius = circle.radius + radius - res.extend(auxFilletArcsBetweenCircleLine(offsetCircle, linePar, circle, line)) - # calcolo le intersezioni tra la circonferenza del cerchio e la retta parallela a - - if circle.radius > radius: - # creo un cerchio con raggio diminuito di - offsetCircle.radius = circle.radius - radius - res.extend(auxFilletArcsBetweenCircleLine(offsetCircle, linePar, circle, line)) - - return res - - -#=============================================================================== -# getNewArcAccordingFilletArc -#=============================================================================== -def getNewArcAccordingFilletArc(arc, filletArc, ptOnArc): - """ - dato un arco () e un altro arco che si raccorda ad esso (), - la funzione restituisce un nuovo arco modificando in modo che sia - tangente all'arco di raccordo. Inoltre, usando un punto indicato sull'arco - restituisce la distanza di quel punto dal punto di tangenza con l'arco - di raccordo usando la direzione della tangente dell'arco di raccordo. - """ - circle = QadCircle() - circle.set(arc.center, arc.radius) - - newArc = QadArc(arc) - - # determino quale punto (iniziale o finale) dell'arco di raccordo - # si interseca sul prolugamento dell'arco - if circle.isPtOnCircle(filletArc.getStartPt()): - filletPtOnArc = filletArc.getStartPt() - isStartFilletPtOnArc = True - else: - filletPtOnArc = filletArc.getEndPt() - isStartFilletPtOnArc = False - - # verifico che l'arco di raccordo sia tangente con l'arco - newArc.setStartAngleByPt(filletPtOnArc) - - if isStartFilletPtOnArc: # se il punto iniziale dell'arco di raccordo é sull'arco - # se il nuovo arco non é un arco valido - if doubleNear(newArc.startAngle, newArc.endAngle): - # se l'arco di raccordo é tangente sul punto finale dell'arco - if TanDirectionNear(arc.getTanDirectionOnEndPt(), \ - normalizeAngle(filletArc.getTanDirectionOnStartPt())) == True: - newArc.startAngle = arc.startAngle # ripristino l'arco originale - else: - # se l'arco di raccordo non é tangente sul punto iniziale del nuovo arco - if TanDirectionNear(newArc.getTanDirectionOnStartPt(), \ - normalizeAngle(filletArc.getTanDirectionOnStartPt() + math.pi)) == False: - newArc.startAngle = arc.startAngle # ripristino l'arco originale - newArc.setEndAngleByPt(filletPtOnArc) - - # se il nuovo arco non é un arco valido - if doubleNear(newArc.startAngle, newArc.endAngle): - return None, None - - # calcolo la distanza dal punto ptOnArc - distFromPtOnArc = circle.lengthBetween2Points(filletArc.getStartPt(), \ - ptOnArc, \ - filletArc.getTanDirectionOnStartPt() + math.pi) - else: # se il punto finale dell'arco di raccordo é sull'arco - # se il nuovo arco non é un arco valido - if doubleNear(newArc.startAngle, newArc.endAngle): - # se l'arco di raccordo é tangente sul punto finale dell'arco - if TanDirectionNear(arc.getTanDirectionOnEndPt(), \ - normalizeAngle(filletArc.getTanDirectionOnEndPt() + math.pi)) == True: - newArc.startAngle = arc.startAngle # ripristino l'arco originale - else: - # se l'arco di raccordo non é tangente sul punto iniziale del nuovo arco - if TanDirectionNear(newArc.getTanDirectionOnStartPt(), \ - filletArc.getTanDirectionOnEndPt()) == False: - newArc.startAngle = arc.startAngle # ripristino l'arco originale - newArc.setEndAngleByPt(filletPtOnArc) - - # se il nuovo arco non é un arco valido - if doubleNear(newArc.startAngle, newArc.endAngle): - return None, None - - # calcolo la distanza dal punto ptOnArc - distFromPtOnArc = circle.lengthBetween2Points(filletArc.getEndPt(), \ - ptOnArc, \ - filletArc.getTanDirectionOnEndPt()) - - return newArc, distFromPtOnArc - - -#=============================================================================== -# bridgeTheGapBetweenArcLine -#=============================================================================== -def bridgeTheGapBetweenArcLine(arc, ptOnArc, line, ptOnLine, radius, filletMode): - """ - la funzione raccorda un arco e un segmento retto (QadLinearObject) attraverso - un arco di raccordo di raggio che piùsi avvicinza ai punti di selezione - sull'arco e sul segmento retto . - modalità di raccordo; 1=Taglia-estendi, 2=Non taglia-estendi - - Ritorna una lista di 3 elementi (None in caso di errore): - una arco che sostituisce - un arco, se = None non c'é arco di raccordo tra le due linee - una linea che sostituisce - """ - # ricavo i possibili archi di raccordo - filletArcs = getFilletArcsBetweenArcLine(arc, line, radius) - - # cerco l'arco valido più vicino a ptOnArc e ptOnLine - AvgList = [] - Avg = sys.float_info.max - - resArc = QadLinearObject() - resFilletArc = QadLinearObject() - resLine = QadLinearObject() - for filletArc in filletArcs: - # ricavo il nuovo segmento in modo che sia tangente con l'arco di raccordo - newLine, distFromPtOnLine = getNewLineAccordingFilletArc(line, filletArc, ptOnLine) - if newLine is None: - continue - - # ricavo il nuovo arco in modo che sia tangente con l'arco di raccordo - newArc, distFromPtOnArc = getNewArcAccordingFilletArc(arc.getArc(), filletArc, ptOnArc) - if newArc is None: - continue - - del AvgList[:] - AvgList.append(distFromPtOnLine) - AvgList.append(distFromPtOnArc) - - currAvg = numericListAvg(AvgList) - if currAvg < Avg: # mediamente più vicino - Avg = currAvg - resLine.set(newLine) - resFilletArc.setArc(filletArc, False) - resArc.setArc(newArc, False) - - if Avg == sys.float_info.max: - return None - - if filletMode == 1: # 1=Taglia-estendi - return [resLine, resFilletArc, resArc] - else: - return [None, resFilletArc, None] - -#=============================================================================== -# getFilletArcsBetweenArcLine -#=============================================================================== -def getFilletArcsBetweenArcLine(arc, line, radius): - """ - la funzione raccorda un arco e una linea retta (QadLinearObject) attraverso - un arco di raccordo di raggio . - - Ritorna una lista dei possibili archi - """ - circle = QadCircle() - circle.set(arc.getArc().center, arc.getArc().radius) - - return getFilletArcsBetweenCircleLine(circle, line, radius) - - -#=============================================================================== -# bridgeTheGapBetweenCircles -#=============================================================================== -def bridgeTheGapBetweenCircles(circle1, ptOnCircle1, circle2, ptOnCircle2, radius): - """ - la funzione raccorda due cerchi (QadLinearObject) attraverso - un arco di raccordo di raggio che più si avvicinza ai punti di selezione - sui cerchi. - - Ritorna una lista di 3 elementi (None in caso di errore): - None - un arco, se = None non c'é arco di raccordo tra le due linee - None - """ - # ricavo i possibili archi di raccordo - _circle1 = circle1.getCircle() - _circle2 = circle2.getCircle() - filletArcs = getFilletArcsBetweenCircles(_circle1, _circle2, radius) - - # cerco l'arco valido più vicino a ptOnCircle1 e ptOnCircle2 - AvgList = [] - Avg = sys.float_info.max - - resFilletArc = QadLinearObject() - for filletArc in filletArcs: - if _circle1.isPtOnCircle(filletArc.getStartPt()): - distFromPtOnCircle1 = _circle1.lengthBetween2Points(filletArc.getStartPt(), \ - ptOnCircle1, \ - filletArc.getTanDirectionOnStartPt() + math.pi) - distFromPtOnCircle2 = _circle2.lengthBetween2Points(filletArc.getEndPt(), \ - ptOnCircle2, \ - filletArc.getTanDirectionOnEndPt()) - else: - distFromPtOnCircle1 = _circle1.lengthBetween2Points(filletArc.getEndPt(), \ - ptOnCircle1, \ - filletArc.getTanDirectionOnEndPt()) - distFromPtOnCircle2 = _circle2.lengthBetween2Points(filletArc.getStartPt(), \ - ptOnCircle2, \ - filletArc.getTanDirectionOnStartPt()+ math.pi) - - del AvgList[:] - AvgList.append(distFromPtOnCircle1) - AvgList.append(distFromPtOnCircle2) - - currAvg = numericListAvg(AvgList) - if currAvg < Avg: # mediamente più vicino - Avg = currAvg - resFilletArc.setArc(filletArc, False) - - if Avg == sys.float_info.max: - return None - - return [None, resFilletArc, None] - - -#=============================================================================== -# auxFilletArcsBetweenCircles -#=============================================================================== -def auxFilletArcsBetweenCircles(circle1, circle2, radius, both = True): - """ - la funzione di ausilio a getFilletArcsBetweenCircles - Ritorna una lista dei possibili archi di raccordo tra i cerchi e - """ - res = [] - # calcolo le intersezioni tra le due circonferenze - # che daranno origine ai centri degli archi di raccordo - intPts = circle1.getIntersectionPointsWithCircle(circle2) - - if len(intPts) > 0: - # un punto di tangenza é dato dal punto a distanza radius dal centro dell'arco di raccordo - # in direzione centro dell'arco - angle = getAngleBy2Pts(intPts[0], circle1.center) - tanC1Pt = getPolarPointByPtAngle(intPts[0], angle, radius) - # un punto di tangenza é dato dal punto a distanza radius dal centro dell'arco di raccordo - # in direzione centro dell'arco - angle = getAngleBy2Pts(intPts[0], circle2.center) - tanC2Pt = getPolarPointByPtAngle(intPts[0], angle, radius) - filletArc = QadArc() - if filletArc.fromStartCenterEndPts(tanC1Pt, intPts[0], tanC2Pt) == True: - res.append(filletArc) - if both: - # stesso arco con il punto iniziale e finale invertiti - filletArc = QadArc(filletArc) - filletArc.inverse() - res.append(filletArc) - - if len(intPts) > 1: - # un punto di tangenza é dato dal punto a distanza radius dal centro dell'arco di raccordo - # in direzione centro dell'arco - angle = getAngleBy2Pts(intPts[1], circle1.center) - tanC1Pt = getPolarPointByPtAngle(intPts[1], angle, radius) - # un punto di tangenza é dato dal punto a distanza radius dal centro dell'arco di raccordo - # in direzione centro dell'arco - angle = getAngleBy2Pts(intPts[1], circle2.center) - tanC2Pt = getPolarPointByPtAngle(intPts[1], angle, radius) - filletArc = QadArc() - if filletArc.fromStartCenterEndPts(tanC1Pt, intPts[1], tanC2Pt) == True: - res.append(filletArc) - if both: - # stesso arco con il punto iniziale e finale invertiti - filletArc = QadArc(filletArc) - filletArc.inverse() - res.append(filletArc) - - return res - -#=============================================================================== -# getFilletArcsBetweenCircles -#=============================================================================== -def getFilletArcsBetweenCircles(circle1, circle2, radius): - """ - la funzione raccorda due cerchi attraverso un arco di raccordo di raggio . - - Ritorna una lista dei possibili archi - """ - res = [] - - # caso 1: raccordo tra e formando un flesso con ciascuno dei cerchi - # creo un nuovo cerchio concentrico a circle1 con raggio aumentato di - newCircle1 = QadCircle(circle1) - newCircle1.radius = newCircle1.radius + radius - # creo un nuovo cerchio concentrico a circle2 con raggio aumentato di - newCircle2 = QadCircle(circle2) - newCircle2.radius = newCircle2.radius + radius - - res.extend(auxFilletArcsBetweenCircles(newCircle1, newCircle2, radius)) - - # caso 2: raccordo tra e senza formare un flesso con ciascuno dei cerchi - if radius - circle1.radius > 0 and radius - circle2.radius > 0: - # creo un nuovo cerchio concentrico a circle1 con raggio = - raggio di circle1 - newCircle1 = QadCircle(circle1) - newCircle1.radius = radius - newCircle1.radius - # creo un nuovo cerchio concentrico a circle2 con raggio = - raggio di circle2 - newCircle2 = QadCircle(circle2) - newCircle2.radius = radius - newCircle2.radius - - res.extend(auxFilletArcsBetweenCircles(newCircle1, newCircle2, radius)) - - # caso 3: raccordo tra e formando un flesso solo con circle1 - if radius - circle2.radius > 0: - # creo un nuovo cerchio concentrico a circle1 con raggio aumentato di - newCircle1 = QadCircle(circle1) - newCircle1.radius = newCircle1.radius + radius - # creo un nuovo cerchio concentrico a circle2 con raggio = - raggio di circle2 - newCircle2 = QadCircle(circle2) - newCircle2.radius = radius - newCircle2.radius - - res.extend(auxFilletArcsBetweenCircles(newCircle1, newCircle2, radius)) - - # caso 4: raccordo tra e formando un flesso solo con circle2 - if radius - circle1.radius > 0: - # creo un nuovo cerchio concentrico a circle1 con raggio aumentato di - newCircle1 = QadCircle(circle1) - newCircle1.radius = radius - newCircle1.radius - # creo un nuovo cerchio concentrico a circle2 con raggio = - raggio di circle2 - newCircle2 = QadCircle(circle2) - newCircle2.radius = newCircle2.radius + radius - - res.extend(auxFilletArcsBetweenCircles(newCircle1, newCircle2, radius)) - - # caso 5: raccordo tra e interno a formando un flesso solo con circle2 - if getDistance(circle1.center, circle2.center) + circle2.radius <= circle1.radius and \ - circle1.radius - radius > 0: - # creo un nuovo cerchio concentrico a circle1 con raggio diminuito di - newCircle1 = QadCircle(circle1) - newCircle1.radius = newCircle1.radius - radius - # creo un nuovo cerchio concentrico a circle2 con raggio aumentato di - newCircle2 = QadCircle(circle2) - newCircle2.radius = newCircle2.radius + radius - - res.extend(auxFilletArcsBetweenCircles(newCircle1, newCircle2, radius)) - - # caso 6: raccordo tra interno a e formando un flesso solo con circle1 - if getDistance(circle1.center, circle2.center) + circle1.radius <= circle2.radius and \ - circle2.radius - radius > 0: - # creo un nuovo cerchio concentrico a circle1 con raggio aumentato di - newCircle1 = QadCircle(circle1) - newCircle1.radius = newCircle1.radius + radius - # creo un nuovo cerchio concentrico a circle2 con raggio diminuito di - newCircle2 = QadCircle(circle2) - newCircle2.radius = newCircle2.radius - radius - - res.extend(auxFilletArcsBetweenCircles(newCircle1, newCircle2, radius)) - - # caso 7: raccordo tra e interno a senza formare alcun flesso - if getDistance(circle1.center, circle2.center) + circle2.radius <= circle1.radius and \ - circle1.radius - radius > 0 and radius - circle2.radius: - # creo un nuovo cerchio concentrico a circle1 con raggio diminuito di - newCircle1 = QadCircle(circle1) - newCircle1.radius = newCircle1.radius - radius - # creo un nuovo cerchio concentrico a circle2 con raggio = - raggio di circle2 - newCircle2 = QadCircle(circle2) - newCircle2.radius = radius - newCircle2.radius - - res.extend(auxFilletArcsBetweenCircles(newCircle1, newCircle2, radius)) - - return res - - -#=============================================================================== -# bridgeTheGapBetweenArcs -#=============================================================================== -def bridgeTheGapBetweenArcs(arc1, ptOnArc1, arc2, ptOnArc2, radius, filletMode): - """ - la funzione raccorda due archi (QadLinearObject) attraverso - un arco di raccordo di raggio che più si avvicinza ai punti di selezione - sull'arco1 e sull'arco2 . - modalità di raccordo; 1=Taglia-estendi, 2=Non taglia-estendi - - Ritorna una lista di 3 elementi (None in caso di errore): - una arco che sostituisce - un arco, se = None non c'é arco di raccordo tra le due linee - una arco che sostituisce - """ - # ricavo i possibili archi di raccordo - filletArcs = getFilletArcsBetweenArcs(arc1, arc2, radius) - - # cerco l'arco valido più vicino a ptOnArc1 e ptOnArc2 - AvgList = [] - Avg = sys.float_info.max - - resFilletArc = QadLinearObject() - resArc1 = QadLinearObject() - resArc2 = QadLinearObject() - for filletArc in filletArcs: - # ricavo il nuovo arco1 in modo che sia tangente con l'arco di raccordo - newArc1, distFromPtOnArc1 = getNewArcAccordingFilletArc(arc1.getArc(), filletArc, ptOnArc1) - if newArc1 is None: - continue - # ricavo il nuovo arco in modo che sia tangente con l'arco di raccordo - newArc2, distFromPtOnArc2 = getNewArcAccordingFilletArc(arc2.getArc(), filletArc, ptOnArc2) - if newArc2 is None: - continue - - del AvgList[:] - AvgList.append(distFromPtOnArc1) - AvgList.append(distFromPtOnArc2) - - currAvg = numericListAvg(AvgList) - if currAvg < Avg: # mediamente più vicino - Avg = currAvg - resArc1.setArc(newArc1, False) - resFilletArc.setArc(filletArc, False) - resArc2.setArc(newArc2, False) - - if Avg == sys.float_info.max: - return None - - if filletMode == 1: # 1=Taglia-estendi - return [resArc1, resFilletArc, resArc2] - else: - return [None, resFilletArc, None] - -#=============================================================================== -# getFilletArcsBetweenArcs -#=============================================================================== -def getFilletArcsBetweenArcs(arc1, arc2, radius): - """ - la funzione raccorda due archi (QadLinearObject) attraverso - un arco di raccordo di raggio . - - Ritorna una lista dei possibili archi - """ - circle1 = QadCircle() - circle1.set(arc1.getArc().center, arc1.getArc().radius) - circle2 = QadCircle() - circle2.set(arc2.getArc().center, arc2.getArc().radius) - - return getFilletArcsBetweenCircles(circle1, circle2, radius) - - -#=============================================================================== -# bridgeTheGapBetweenArcCircle -#=============================================================================== -def bridgeTheGapBetweenArcCircle(arc, ptOnArc, circle, ptOnCircle, radius, filletMode): - """ - la funzione raccorda un arco e un cerchio (QadLinearObject) attraverso - un arco di raccordo di raggio che più si avvicinza ai punti di selezione - sull'arco e sul cerchio . - modalità di raccordo; 1=Taglia-estendi, 2=Non taglia-estendi - - Ritorna una lista di 3 elementi (None in caso di errore): - una arco che sostituisce - un arco, se = None non c'é arco di raccordo tra le due linee - None - """ - # ricavo i possibili archi di raccordo - _circle = circle.getCircle() - filletArcs = getFilletArcsBetweenArcCircle(arc, _circle, radius) - - # cerco l'arco valido più vicino a ptOnArc e ptOnCircle - AvgList = [] - Avg = sys.float_info.max - - resFilletArc = QadLinearObject() - resArc = QadLinearObject() - for filletArc in filletArcs: - # ricavo il nuovo arco in modo che sia tangente con l'arco di raccordo - newArc, distFromPtOnArc = getNewArcAccordingFilletArc(arc.getArc(), filletArc, ptOnArc) - if newArc is None: - continue - - # calcolo la distanza dal punto ptOnCircle - if _circle.isPtOnCircle(filletArc.getStartPt()): # se il punto iniziale dell'arco di raccordo é sul cerchio - distFromPtOnCircle = _circle.lengthBetween2Points(filletArc.getStartPt(), \ - ptOnCircle, \ - filletArc.getTanDirectionOnStartPt() + math.pi) - else: # se il punto finale dell'arco di raccordo é sul cerchio - distFromPtOnCircle = _circle.lengthBetween2Points(filletArc.getEndPt(), \ - ptOnCircle, \ - filletArc.getTanDirectionOnEndPt()) - - del AvgList[:] - AvgList.append(distFromPtOnArc) - AvgList.append(distFromPtOnCircle) - - currAvg = numericListAvg(AvgList) - if currAvg < Avg: # mediamente più vicino - Avg = currAvg - resArc.setArc(newArc, False) - resFilletArc.setArc(filletArc, False) - - if Avg == sys.float_info.max: - return None - - if filletMode == 1: # 1=Taglia-estendi - return [resArc, resFilletArc, None] - else: - return [None, resFilletArc, None] - -#=============================================================================== -# getFilletArcsBetweenArcCircle -#=============================================================================== -def getFilletArcsBetweenArcCircle(arc, circle, radius): - """ - la funzione raccorda un arco (QadLinearObject) e un cerchio attraverso - un arco di raccordo di raggio . - - Ritorna una lista dei possibili archi - """ - circle1 = QadCircle() - circle1.set(arc.getArc().center, arc.getArc().radius) - - return getFilletArcsBetweenCircles(circle1, circle, radius) - - -#=============================================================================== -# pretreatment_offset -#=============================================================================== -def pretreatment_offset(partList): - """ - la funzione controlla le "local self intersection"> : - se il segmento (o arco) i-esimo e il successivo hanno 2 intersezioni allora si inserisce un vertice - nel segmento (o arco) i-esimo tra i 2 punti di intersezione. - La funzione riceve una lista di segmenti ed archi e ritorna una nuova lista di parti - """ - # verifico se polilinea chiusa - i = -1 if partList.isClosed() else 0 - - result = QadLinearObjectList() - while i < partList.qty() - 1: - if i == -1: # polilinea chiusa quindi prendo in esame l'ultimo segmento e il primo - part = partList.getLinearObjectAt(-1) - nextPart = partList.getLinearObjectAt(0) - else: - part = partList.getLinearObjectAt(i) - nextPart = partList.getLinearObjectAt(i + 1) - - ptIntList = part.getIntersectionPtsWithLinearObject(nextPart) - if len(ptIntList) == 2: # 2 punti di intersezione - # calcolo il punto medio tra i 2 punti di intersezione in part - if part.isSegment(): # segmento - ptMiddle = getMiddlePoint(ptIntList[0], ptIntList[1]) - result.append([part.getStartPt(), ptMiddle]) - result.append([ptMiddle, part.getEndPt()]) - else: # arco - arc1 = QadArc(part.getArc()) - arc2 = QadArc(part.getArc()) - # se i punti sono così vicini da essere considerati uguali - if ptNear(part.getArc().getEndPt(), ptIntList[0]): - ptInt = part.getArc().getEndPt() - else: - ptInt = part.getArc().getStartPt() - - angleInt = getAngleBy2Pts(part.getArc().center, ptInt) - arc1.endAngle = angleInt - arc2.startAngle = angleInt - result.append([arc1, part.isInverseArc()]) - result.append([arc2, part.isInverseArc()]) - else: # un solo punto di intersezione - result.append(part) - - i = i + 1 - - if partList.isClosed() == False: # se non é chiusa aggiungo l'ultima parte - if partList.qty() > 1: - result.append(nextPart) - else: - result.append(partList.getLinearObjectAt(0)) - - return result - - -#=============================================================================== -# getIntersectionPointInfo_offset -#=============================================================================== -def getIntersectionPointInfo_offset(part, nextPart): - """ - la funzione restituisce il punto di intersezione tra le 2 parti e - e il tipo di intersezione per e per . - Alle parti deve essere già stato fatto l'offset singolarmente: - - 1 = TIP (True Intersection Point) se il punto di intersezione ottenuto estendendo - le 2 parti si trova su - - 2 = FIP (False Intersection Point) se il punto di intersezione ottenuto estendendo - - le 2 parti non si trova su - 3 = PFIP (Positive FIP) se il punto di intersezione é nella stessa direzione di part - - 4 = NFIP (Negative FIP) se il punto di intersezione é nella direzione opposta di part - """ - - ptIntList = part.getIntersectionPtsOnExtensionWithLinearObject(nextPart) - - if len(ptIntList) == 0: - if part.getEndPt() == nextPart.getStartPt(): # inizia dove finisce - return [part.getEndPt(), 1, 1] # TIP-TIP - else: - return None - elif len(ptIntList) == 1: - if part.isSegment(): # segmento - if part.containsPt(ptIntList[0]): - intTypePart = 1 # TIP - else: # l'intersezione non é sul segmento (FIP) - # se la direzione é la stessa del segmento - if doubleNear(getAngleBy2Pts(part.getStartPt(), part.getEndPt()), \ - getAngleBy2Pts(part.getStartPt(), ptIntList[0])): - intTypePart = 3 # PFIP - else: - intTypePart = 4 # NFIP - else: # arco - if part.containsPt(ptIntList[0]): - intTypePart = 1 # TIP - else: - intTypePart = 2 # FIP - - if nextPart.isSegment(): # segmento - if nextPart.containsPt(ptIntList[0]): - intTypeNextPart = 1 # TIP - else: # l'intersezione non é sul segmento (FIP) - # se la direzione é la stessa del segmento - if doubleNear(getAngleBy2Pts(nextPart.getStartPt(), nextPart.getEndPt()), \ - getAngleBy2Pts(nextPart.getStartPt(), ptIntList[0])): - intTypeNextPart = 3 # PFIP - else: - intTypeNextPart = 4 # NFIP - else: # arco - if nextPart.containsPt(ptIntList[0]): - intTypeNextPart = 1 # TIP - else: - intTypeNextPart = 2 # FIP - - return [ptIntList[0], intTypePart, intTypeNextPart] - else: # 2 punti di intersezione - # scelgo il punto più vicino al punto finale di part - if part.isSegment(): # segmento - if getDistance(ptIntList[0], part.getEndPt()) < getDistance(ptIntList[1], part.getEndPt()): - ptInt = ptIntList[0] - else: - ptInt = ptIntList[1] - - if part.containsPt(ptInt): - intTypePart = 1 # TIP - else: # l'intersezione non é sul segmento (FIP) - # se la direzione é la stessa del segmento - if doubleNear(getAngleBy2Pts(part.getStartPt(), part.getEndPt()), \ - getAngleBy2Pts(part.getStartPt(), ptInt)): - intTypePart = 3 # PFIP - else: - intTypePart = 4 # NFIP - - # la seconda parte é sicuramente un'arco - if nextPart.containsPt(ptInt): - intTypeNextPart = 1 # TIP - else: # l'intersezione non é sull'arco (FIP) - intTypeNextPart = 2 # FIP - - return [ptInt, intTypePart, intTypeNextPart] - else: # arco - finalPt = part.getEndPt() - - if getDistance(ptIntList[0], finalPt) < getDistance(ptIntList[1], finalPt): - ptInt = ptIntList[0] - else: - ptInt = ptIntList[1] - - if part.containsPt(ptInt): - intTypePart = 1 # TIP - else: # l'intersezione non é sull'arco (FIP) - intTypePart = 2 # FIP - - if nextPart.isSegment(): # segmento - if nextPart.containsPt(ptInt): - intTypeNextPart = 1 # TIP - else: # l'intersezione non é sul segmento (FIP) - # se la direzione é la stessa del segmento - if doubleNear(getAngleBy2Pts(nextPart.getStartPt(), nextPart.getEndPt()), \ - getAngleBy2Pts(nextPart.getStartPt(), ptInt)): - intTypeNextPart = 3 # PFIP - else: - intTypeNextPart = 4 # NFIP - else : # arco - if nextPart.containsPt(ptInt): - intTypeNextPart = 1 # TIP - else: # l'intersezione non é sull'arco (FIP) - intTypeNextPart = 2 # FIP - - return [ptInt, intTypePart, intTypeNextPart] - - -#=============================================================================== -# fillet2Parts_offset -#=============================================================================== -def fillet2Parts_offset(part, nextPart, offSetSide, offSetDist): - """ - la funzione raccorda 2 parti nei seguenti casi: - 1) segmento-arco (PFIP-FIP, nessuna intersezione) - 2) arco-segmento (FIP-NFIP, nessuna intersezione) - 3) arco-arco (nessuna intersezione) - """ - # se la prima parte é un segmento e la seconda é un arco - if part.isSegment(): - newNextPart = QadLinearObject(part) - newNextPart.reverse() # rovescio la direzione - newPart = QadLinearObject(nextPart) - newPart.reverse() # rovescio la direzione - newOffSetSide = "left" if offSetSide == "right" else "right" - result = fillet2Parts_offset(newPart, newNextPart, newOffSetSide, offSetDist) - result.setInverseArc(not result.isInverseArc()) # cambio verso - return result - else: # se la prima parte é un arco - arc = part.getArc() - inverse = part.isInverseArc() - AngleProjected = getAngleBy2Pts(arc.center, part.getEndPt()) - if inverse == False: # l'arco gira verso sin - if offSetSide == "right": # l'offset era verso l'esterno - # calcolo il punto proiettato per ri-ottenere quello originale - center = getPolarPointByPtAngle(arc.center, AngleProjected, arc.radius - offSetDist) - else: # l'offset era verso l'interno - center = getPolarPointByPtAngle(arc.center, AngleProjected, arc.radius + offSetDist) - else: # l'arco gira verso destra - if offSetSide == "right": # l'offset era verso l'interno - center = getPolarPointByPtAngle(arc.center, AngleProjected, arc.radius + offSetDist) - else: # l'offset era verso l'esterno - center = getPolarPointByPtAngle(arc.center, AngleProjected, arc.radius - offSetDist) - - newArc = QadArc() - # se il centro dell'arco di raccordo é interno all'arco di offset - if getDistance(arc.center, center) < arc.radius: - newArcInverse = inverse - if inverse == False: - newArc.fromStartCenterEndPts(arc.getEndPt(), \ - center, \ - nextPart.getStartPt()) - else: - newArc.fromStartCenterEndPts(nextPart.getStartPt(), \ - center, \ - arc.getStartPt()) - else: # se il centro dell'arco di raccordo é esterno all'arco di offset - newArcInverse = not inverse - if inverse == False: - newArc.fromStartCenterEndPts(nextPart.getStartPt(), \ - center, \ - arc.getEndPt()) - else: - newArc.fromStartCenterEndPts(arc.getStartPt(), \ - center, \ - nextPart.getStartPt()) - - return QadLinearObject([newArc, newArcInverse]) - - -#=============================================================================== -# getUntrimmedOffSetPartList -#=============================================================================== -def getUntrimmedOffSetPartList(partList, offSetDist, offSetSide, gapType, tolerance2ApproxCurve = None): - """ - la funzione fa l'offset non pulito da eventuali tagli da apportare (vedi - getTrimmedOffSetPartList") di una polilinea (lista di parti é QadLinearObjectList) - secondo una distanza e un lato di offset ("right" o "left") - ed un modo : - 0 = Estende i segmenti di linea alle relative intersezioni proiettate - 1 = Raccorda i segmenti di linea in corrispondenza delle relative intersezioni proiettate. - Il raggio di ciascun segmento di arco é uguale alla distanza di offset - 2 = Cima i segmenti di linea in corrispondenza delle intersezioni proiettate. - La distanza perpendicolare da ciascuna cima al rispettivo vertice - sull'oggetto originale é uguale alla distanza di offset. - tolerance2ApproxCurve = errore minimo di tolleranza - - La funzione ritorna una lista di parti della polilinee (lista di segmenti o archi) - """ - # verifico se polilinea chiusa - isClosedPolyline = partList.isClosed() - - # creo una lista dei segmenti e archi che formano la polilinea - partList = pretreatment_offset(partList) - - # faccio l'offset di ogni parte della polilinea - newPartList = QadLinearObjectList() - for part in partList.defList: - if part.isSegment(): # segmento - newPart = QadLinearObject(getOffSetLine(part.getStartPt(), part.getEndPt(), offSetDist, offSetSide)) - newPartList.append(newPart) - else: # arco - if part.isInverseArc(): # l'arco gira verso destra - arcOffSetSide = "internal" if offSetSide == "right" else "external" - else: # l'arco gira verso sin - arcOffSetSide = "external" if offSetSide == "right" else "internal" - - newArc = getOffSetArc(part.getArc(), offSetDist, arcOffSetSide) - if newArc is not None: - newPart = QadLinearObject([newArc, part.isInverseArc()]) # e - newPartList.append(newPart) - - # calcolo i punti di intersezione tra parti adiacenti - # per ottenere una linea di offset non tagliata - if isClosedPolyline == True: - i = -1 - else: - i = 0 - - untrimmedOffsetPartList = QadLinearObjectList() - virtualPartPositionList = [] - while i < newPartList.qty() - 1: - if i == -1: # polylinea chiusa quindi prendo in esame l'ultimo segmento e il primo - part = newPartList.getLinearObjectAt(-1) # ultima parte - nextPart = newPartList.getLinearObjectAt(0) # prima parte - else: - part = newPartList.getLinearObjectAt(i) - nextPart = newPartList.getLinearObjectAt(i + 1) - - if untrimmedOffsetPartList.qty() == 0: - lastUntrimmedOffsetPt = part.getStartPt() - else: - lastUntrimmedOffsetPt = untrimmedOffsetPartList.getLinearObjectAt(-1).getEndPt() # ultima parte - - IntPointInfo = getIntersectionPointInfo_offset(part, nextPart) - if IntPointInfo is not None: # se c'é un'intersezione - IntPoint = IntPointInfo[0] - IntPointTypeForPart = IntPointInfo[1] - IntPointTypeForNextPart = IntPointInfo[2] - - if part.isSegment(): # segmento - if nextPart.isSegment(): # segmento-segmento - if IntPointInfo is not None: # se esiste un punto di intersezione - if IntPointTypeForPart == 1: # TIP - if IntPointTypeForNextPart == 1: # TIP - untrimmedOffsetPartList.append([lastUntrimmedOffsetPt, IntPoint]) - else: # FIP - untrimmedOffsetPartList.append([lastUntrimmedOffsetPt, part.getEndPt()]) - untrimmedOffsetPartList.append([part.getEndPt(), nextPart.getStartPt()]) - # aggiungo la posizione di questa parte virtuale - virtualPartPositionList.append(untrimmedOffsetPartList.qty() - 1) - else: # FIP - if IntPointTypeForPart == 3: # PFIP - if gapType != 0: - newLines = offsetBridgeTheGapBetweenLines(part, nextPart, offSetDist, gapType) - untrimmedOffsetPartList.append(newLines[0]) - untrimmedOffsetPartList.append(newLines[1]) # arco o linea di raccordo - else: - untrimmedOffsetPartList.append([lastUntrimmedOffsetPt, IntPoint]) - else: # NFIP - untrimmedOffsetPartList.append([lastUntrimmedOffsetPt, part.getEndPt()]) - untrimmedOffsetPartList.append([part.getEndPt(), nextPart.getStartPt()]) - # aggiungo la posizione di questa parte virtuale - virtualPartPositionList.append(untrimmedOffsetPartList.qty() - 1) - else: # segmento-arco - if IntPointInfo is not None: # se esiste un punto di intersezione - if IntPointTypeForPart == 1: # TIP - if IntPointTypeForNextPart == 1: # TIP - untrimmedOffsetPartList.append([lastUntrimmedOffsetPt, IntPoint]) - else: # FIP - untrimmedOffsetPartList.append([lastUntrimmedOffsetPt, part.getEndPt()]) - untrimmedOffsetPartList.append([part.getEndPt(), nextPart.getStartPt()]) - # aggiungo la posizione di questa parte virtuale - virtualPartPositionList.append(untrimmedOffsetPartList.qty() - 1) - else: # FIP - if IntPointTypeForPart == 3: # PFIP - if IntPointTypeForNextPart == 2: # FIP - untrimmedOffsetPartList.append([lastUntrimmedOffsetPt, part.getEndPt()]) - untrimmedOffsetPartList.append(fillet2Parts_offset(part, nextPart, offSetSide, offSetDist)) - else: # NFIP - if IntPointTypeForNextPart == 1: # TIP - untrimmedOffsetPartList.append([lastUntrimmedOffsetPt, part.getEndPt()]) - untrimmedOffsetPartList.append([part.getEndPt(), nextPart.getStartPt()]) - # aggiungo la posizione di questa parte virtuale - virtualPartPositionList.append(untrimmedOffsetPartList.qty() - 1) - else: # non esiste un punto di intersezione - untrimmedOffsetPartList.append([lastUntrimmedOffsetPt, part.getEndPt()]) - untrimmedOffsetPartList.append(fillet2Parts_offset(part, nextPart, offSetSide, offSetDist)) - else: # arco - if nextPart.isSegment(): # arco-segmento - if IntPointInfo is not None: # se esiste un punto di intersezione - if IntPointTypeForPart == 1: # TIP - if IntPointTypeForNextPart == 1: # TIP - newPart = QadLinearObject(part) - newPart.setStartEndPts(lastUntrimmedOffsetPt, IntPoint) # modifico l'arco - untrimmedOffsetPartList.append(newPart) - else: # FIP - if IntPointTypeForNextPart == 3: # PFIP - newPart = QadLinearObject(part) - newPart.setStartPt(lastUntrimmedOffsetPt) # modifico l'arco - untrimmedOffsetPartList.append(newPart) - untrimmedOffsetPartList.append([part.getEndPt(), nextPart.getStartPt()]) - # aggiungo la posizione di questa parte virtuale - virtualPartPositionList.append(untrimmedOffsetPartList.qty() - 1) - else: # FIP - if IntPointTypeForNextPart == 4: # NFIP - newPart = QadLinearObject(part) - newPart.setStartPt(lastUntrimmedOffsetPt) # modifico l'arco - untrimmedOffsetPartList.append(newPart) - untrimmedOffsetPartList.append(fillet2Parts_offset(part, nextPart, offSetSide, offSetDist)) - elif IntPointTypeForNextPart == 1: # TIP - newPart = QadLinearObject(part) - newPart.setStartPt(lastUntrimmedOffsetPt) # modifico l'arco - untrimmedOffsetPartList.append(newPart) - untrimmedOffsetPartList.append([part.getEndPt(), nextPart.getStartPt()]) - # aggiungo la posizione di questa parte virtuale - virtualPartPositionList.append(untrimmedOffsetPartList.qty() - 1) - else: # non esiste un punto di intersezione - newPart = QadLinearObject(part) - newPart.setStartPt(lastUntrimmedOffsetPt) # modifico l'arco - untrimmedOffsetPartList.append(newPart) - untrimmedOffsetPartList.append(fillet2Parts_offset(part, nextPart, offSetSide, offSetDist)) - else: # arco-arco - arc = part.getArc() - inverse = part.isInverseArc() - nextArc = nextPart.getArc() - nextInverse = nextPart.isInverseArc() - - if IntPointInfo is not None: # se esiste un punto di intersezione - if IntPointTypeForPart == 1: # TIP - if IntPointTypeForNextPart == 1: # TIP - newPart = QadLinearObject(part) - newPart.setStartEndPts(lastUntrimmedOffsetPt, IntPoint) # modifico l'arco - untrimmedOffsetPartList.append(newPart) - else : # FIP - newPart = QadLinearObject(part) - newPart.setStartPt(lastUntrimmedOffsetPt) # modifico l'arco - untrimmedOffsetPartList.append(newPart) - - if inverse == False: - center = getPolarPointByPtAngle(arc.center, arc.endAngle, arc.radius - offSetDist) - else: - center = getPolarPointByPtAngle(arc.center, arc.startAngle, arc.radius - offSetDist) - - secondPtNewArc = getPolarPointByPtAngle(center, \ - getAngleBy2Pts(center, IntPoint), \ - offSetDist) - newArc = QadArc() - newArc.fromStartSecondEndPts(part.getEndPt(), \ - secondPtNewArc, \ - nextPart.getStartPt()) - - if ptNear(newArc.getStartPt(), part.getEndPt()): - untrimmedOffsetPartList.append([newArc, False]) - else: - untrimmedOffsetPartList.append([newArc, True]) - # aggiungo la posizione di questa parte virtuale - virtualPartPositionList.append(untrimmedOffsetPartList.qty() - 1) - else: # FIP - if IntPointTypeForNextPart == 1: # TIP - newPart = QadLinearObject(part) - newPart.setStartPt(lastUntrimmedOffsetPt) # modifico l'arco - untrimmedOffsetPartList.append(newPart) - - if inverse == False: - center = getPolarPointByPtAngle(arc.center, arc.endAngle, arc.radius - offSetDist) - else: - center = getPolarPointByPtAngle(arc.center, arc.startAngle, arc.radius - offSetDist) - - secondPtNewArc = getPolarPointByPtAngle(center, \ - getAngleBy2Pts(center, IntPoint), \ - offSetDist) - newArc = QadArc() - newArc.fromStartSecondEndPts(part.getEndPt(), \ - secondPtNewArc, \ - nextPart.getStartPt()) - if ptNear(newArc.getStartPt(), part.getEndPt()): - untrimmedOffsetPartList.append([newArc, False]) - else: - untrimmedOffsetPartList.append([newArc, True]) - # aggiungo la posizione di questa parte virtuale - virtualPartPositionList.append(untrimmedOffsetPartList.qty() - 1) - else: # FIP - newPart = QadLinearObject(part) - newPart.setStartEndPts(lastUntrimmedOffsetPt, IntPoint) # modifico l'arco - untrimmedOffsetPartList.append(newPart) - else: # non esiste un punto di intersezione - newPart = QadLinearObject(part) - newPart.setStartPt(lastUntrimmedOffsetPt) # modifico l'arco - untrimmedOffsetPartList.append(newPart) - - # prima di raccordare verifico se l'arco si trova interamente dentro la zona di offset - # dell'arco e viceversa. - # Per replicare questa eccezione fare una polilinea composta da 2 archi: - # il primo con centro in ..., raggio..., angolo iniziale ... angolo finale ... - # il secondo con centro in ..., raggio..., angolo iniziale ... angolo finale ... - # offset a destra = 8 - arc = part.getArc() - nextArc = nextPart.getArc() - dist = getDistance(arc.center, nextArc.center) - minDistArc, maxDistArc = getOffsetDistancesFromCenterOnOffsetedArc(arc, inverse, offSetDist, offSetSide) - minDistNextArc, maxDistNextArc = getOffsetDistancesFromCenterOnOffsetedArc(nextArc, nextInverse, offSetDist, offSetSide) - - if (dist + nextArc.radius <= maxDistArc and dist - nextArc.radius >= minDistArc) or \ - (dist + arc.radius <= maxDistNextArc and dist - arc.radius >= minDistNextArc): - untrimmedOffsetPartList.append([newPart.getEndPt(), nextPart.getStartPt()]) - else: - untrimmedOffsetPartList.append(fillet2Parts_offset(part, nextPart, offSetSide, offSetDist)) - - i = i + 1 - - if newPartList.qty() > 0: - if isClosedPolyline == False: - if untrimmedOffsetPartList.qty() == 0: - # primo punto della prima parte di newPartList - lastUntrimmedOffsetPt = newPartList.getLinearObjectAt(0).getStartPt() - else: - # ultimo punto dell'ultima parte di untrimmedOffsetPartList - lastUntrimmedOffsetPt = untrimmedOffsetPartList.getLinearObjectAt(-1).getEndPt() - - newPart = QadLinearObject(newPartList.getLinearObjectAt(-1)) - newPart.setStartPt(lastUntrimmedOffsetPt) # modifico l'inizio - untrimmedOffsetPartList.append(newPart) - else: - # primo punto = ultimo punto - untrimmedOffsetPartList.getLinearObjectAt(0).setStartPt(untrimmedOffsetPartList.getLinearObjectAt(-1).getEndPt()) # modifico l'inizio - - # faccio un pre-clipping sulle parti virtuali - return virtualPartClipping(untrimmedOffsetPartList, virtualPartPositionList) - # test - #return untrimmedOffsetPartList - - -#=============================================================================== -# getOffsetDistancesFromCenterOnOffsetedArc -#=============================================================================== -def getOffsetDistancesFromCenterOnOffsetedArc(arc, isInverseArc, offSetDist, offSetSide): - """ - la funzione restituisce la distanza minima e massima dal centro dell'arco su cui è già stato fatto un offset. - Queste distanze generano un'area di offset intorno all'arco originale. - arco a cui è già stato fatto un offset - come gira l'arco, se true gira verso destra altrimento gira verso sinistra - distanza di offset - parte in cui si vuole l'offset "right" o "left" - """ - if isInverseArc: # l'arco gira verso destra - if offSetSide == "right": # offset sulla parte interna dell'arco - minDist = arc.radius - maxDist = arc.radius + 2 * offSetDist - else: # offset sulla parte esterna dell'arco - maxDist = arc.radius - minDist = arc.radius - 2 * offSetDist - else: # l'arco gira verso sin - if offSetSide == "right": # offset sulla parte esterna dell'arco - maxDist = arc.radius - minDist = arc.radius - 2 * offSetDist - else: # offset sulla parte interna dell'arco - minDist = arc.radius - maxDist = arc.radius + 2 * offSetDist - - if minDist < 0: minDist = 0 - - return minDist, maxDist - - -#=============================================================================== -# virtualPartClipping -#=============================================================================== -def virtualPartClipping(untrimmedOffsetPartList, virtualPartPositionList): - """ - la funzione restituisce una lista di parti in cui vengono tagliate le isole generate - da parti virtuali (che invertono il senso della linea). - Per ogni parte virtuale, si verifica se le parti che precedono e che seguono formano un'isola. - In caso affermativo, se possibile (vedi casi specifici), l'sola viene rimossa. - lista delle parti - lista delle posizioni delle parti virtuali (viene modificata) - """ - result = QadLinearObjectList(untrimmedOffsetPartList) - - # per prima cosa elimino tutte le isole con parti virtuali che hanno le parti - # direttamente adiacenti intersecanti - i = len(virtualPartPositionList) - 1 - while i >= 0: - virtualPartPosition = virtualPartPositionList[i] - # parte successiva a quella virtuale - nextPos = result.getNextPos(virtualPartPosition) - # parte precedente - prevPos = result.getPrevPos(virtualPartPosition) - - if (prevPos is not None) and (nextPos is not None): - nextPart = result.getLinearObjectAt(nextPos) - prevPart = result.getLinearObjectAt(prevPos) - # verifico se hanno un solo punto di intersezione - ptIntList = prevPart.getIntersectionPtsWithLinearObject(nextPart) - if len(ptIntList) == 1: - nextPart.setStartPt(ptIntList[0]) # modifico l'inizio - prevPart.setEndPt(ptIntList[0]) # modifico la fine - result.remove(virtualPartPosition) - del virtualPartPositionList[i] - - i = i - 1 - - prevPart_1 = QadLinearObject() - prevPart_2 = QadLinearObject() - nextPart_1 = QadLinearObject() - nextPart_2 = QadLinearObject() - - # elimino tutte le isole con parti virtuali che hanno le parti adiacenti intersecanti - # ma che non formino con il resto della linea altre isole. - # quando considero un lato adiacente alla parte virtuale da un lato devo considerare le intersezioni - # partendo dal lato successivo quello adicente nella parte opposta di quello virtuale - for i in xrange(len(virtualPartPositionList) - 1, -1, -1): - virtualPartPosition = virtualPartPositionList[i] - # finché non trovo l'intersezione - nPrevPartsToRemove = -1 - prevPos = virtualPartPosition - ptIntList = [] - while len(ptIntList) == 0: - virtualPart = result.getLinearObjectAt(virtualPartPosition) - # parte successiva a quella virtuale - nextPos = result.getNextPos(virtualPartPosition) - nNextPartsToRemove = 0 - # parte precedente - prevPos = result.getPrevPos(prevPos) - # se trovo una parte virtuale mi fermo - if virtualPartPositionList.count(prevPos) > 0: - break - - # l'ultima condizione é nel caso la polilinea sia chiusa - if (prevPos is None) or (nextPos is None) or prevPos == nextPos: - break - - nPrevPartsToRemove = nPrevPartsToRemove + 1 - prevPart = result.getLinearObjectAt(prevPos) - - # ciclo finche non ci sono più parti successive - while (nextPos is not None) and (prevPos != nextPos): - # se trovo una parte virtuale mi fermo - if virtualPartPositionList.count(nextPos) > 0: - break - nextPart = result.getLinearObjectAt(nextPos) - ptIntList = prevPart.getIntersectionPtsWithLinearObject(nextPart) - if len(ptIntList) > 0: - break - nextPos = result.getNextPos(nextPos) # parte successiva - nNextPartsToRemove = nNextPartsToRemove + 1 - - if len(ptIntList) == 1 and \ - not ptNear(ptIntList[0], virtualPart.getStartPt()) and \ - not ptNear(ptIntList[0], virtualPart.getEndPt()): - prevPart_1.set(prevPart) - # se il punto iniziale della parte non coincide con quella del punto di intersezione - if not ptNear(ptIntList[0], prevPart.getStartPt()): - prevPart_1.setEndPt(ptIntList[0]) # modifico la fine - prevPart_2.set(prevPart) - prevPart_2.setStartPt(ptIntList[0]) # modifico l'inizio - else: - prevPart_2.clear() - - nextPart_1.set(nextPart) - # se il punto finale della parte non coincide con quella del punto di intersezione - if not ptNear(ptIntList[0], nextPart.getEndPt()): - nextPart_1.setEndPt(ptIntList[0]) # modifico la fine - nextPart_2.set(nextPart) - nextPart_2.setStartPt(ptIntList[0]) # modifico l'inizio - else: - nextPart_2.clear() - - ######################################################## - # Creo una lista di parti che definisce l'isola - inizio - islandPartList = QadLinearObjectList() - - islandPart = QadLinearObject(prevPart_2 if prevPart_2.isInitialized() else prevPart_1) - islandPartList.append(islandPart) - - pos = virtualPartPosition - for j in xrange(nPrevPartsToRemove, 0, - 1): - pos = result.getPrevPos(pos) # parte precedente - islandPartList.append(QadLinearObject(result.getLinearObjectAt(pos))) - - islandPartList.append(virtualPart) - - pos = virtualPartPosition - for j in xrange(1, nNextPartsToRemove + 1, 1): - pos = result.getNextPos(pos) # parte successiva - islandPartList.append(QadLinearObject(result.getLinearObjectAt(pos))) - - islandPart = QadLinearObject(nextPart_1) - islandPartList.append(islandPart) - - # Creo una lista di parti che definisce l'isola - fine - ######################################################## - - # verifico se le parti seguenti formano con islandPartList delle aree (più di 2 intersezioni) - if nextPart_2.isInitialized(): - nIntersections = 1 - else: - nIntersections = 0 - - for j in xrange(nextPos + 1, result.qty(), 1): - dummy = islandPartList.getIntersectionPtsWithLinearObject(result.getLinearObjectAt(j)) - intPtList = dummy[0] - nIntersections = nIntersections + len(intPtList) - - # se é positivo e minore o uguale a 2 verifico anche dall'altra parte - if nIntersections > 0 and nIntersections <= 2: - # verifico se le parti precedenti formano con islandPartList delle aree (almeno 2 intersezioni) - if prevPart_2.isInitialized(): - nIntersections = 1 - else: - nIntersections = 0 - - for j in xrange(prevPos - 1, -1, -1): - dummy = islandPartList.getIntersectionPtsWithLinearObject(result.getLinearObjectAt(j)) - intPtList = dummy[0] - nIntersections = nIntersections + len(intPtList) - - # se é positivo e minore o uguale a 2 verifico anche dall'altra parte - if nIntersections > 0 and nIntersections <= 2: - # rimuovo island da result - if nextPart_2.isInitialized(): - nextPart.setStartPt(nextPart_2.getStartPt()) # modifico l'inizio - else: - result.remove(nextPos) - - # cancello le parti inutili - for j in xrange(0, nNextPartsToRemove, 1): - result.remove(virtualPartPosition + 1) - - # cancello la parte virtuale - result.remove(virtualPartPosition) - - # cancello le parti inutili - for j in xrange(0, nPrevPartsToRemove, 1): - result.remove(virtualPartPosition - nPrevPartsToRemove) - - if prevPart_2.isInitialized(): - prevPart.setEndPt(nextPart_2.getStartPt()) # modifico la fine - else: - result.remove(prevPos) - - del virtualPartPositionList[i] - - return result - - -#=============================================================================== -# getIntPtListBetweenPartAndPartList_offset -#=============================================================================== -def getIntPtListBetweenPartAndPartList_offset(part, partList): - """ - la funzione restituisce due liste: - la prima é una lista di punti di intersezione tra la parte - e una lista di parti in cui si trova quel punto. - : un segmento o arco - : lista delle parti di una polilinea - """ - startPtOfPart = part.getStartPt() - endPtOfPart = part.getEndPt() - intPtSortedList = [] # lista di ((punto, distanza dall'inizio della parte) ...) - partNumber = -1 - # per ogni parte di partList - for part2 in partList.defList: - partNumber = partNumber + 1 - partialIntPtList = part.getIntersectionPtsWithLinearObject(part2) - for partialIntPt in partialIntPtList: - # escludo i punti che sono all'inizio-fine di part - - # se i punti sono così vicini da essere considerati uguali - if ptNear(startPtOfPart, partialIntPt) == False and \ - ptNear(endPtOfPart, partialIntPt) == False: - # escludo i punti che sono già in intPtSortedList - found = False - for intPt in intPtSortedList: - if ptNear(intPt[0], partialIntPt): - found = True - break - - if found == False: - # inserisco il punto ordinato per distanza dal inizio di part - distFromStart = part.getDistanceFromStart(partialIntPt) - insertAt = 0 - for intPt in intPtSortedList: - if intPt[1] < distFromStart: - insertAt = insertAt + 1 - else: - break - intPtSortedList.insert(insertAt, [partialIntPt, distFromStart, partNumber]) - resultIntPt = [] - resultPartNumber = [] - for intPt in intPtSortedList: - resultIntPt.append(intPt[0]) - resultPartNumber.append(intPt[2]) - - return resultIntPt, resultPartNumber - - -#=============================================================================== -# dualClipping -#=============================================================================== -def dualClipping(partList, untrimmedOffsetPartList, untrimmedReversedOffsetPartList, offSetDist): - """ - la funzione effettua il dual clipping su untrimmedOffsetPartList. - : lista delle parti originali della polilinea - : lista delle parti non tagliate derivate dall'offset - : lista delle parti non tagliate derivate dall'offset in senso inverso - - La funzione ritorna una lista di parti risultato del dual clipping - """ - - # inizio Dual Clipping - dualClippedPartList = QadLinearObjectList() - - # linea spezzata sui self intersection points e - # sui punti di intersezione con untrimmedReversedOffsetPartList - - # per ogni parte di untrimmedOffsetPartList - for part in untrimmedOffsetPartList.defList: - # calcola i punti di intersezione di part con untrimmedOffsetPartList ordinati x distanza - # (self intersection points) - dummy = getIntPtListBetweenPartAndPartList_offset(part, untrimmedOffsetPartList) - intPtList = dummy[0] - - if len(intPtList) > 0: - # inserisco dividendo part - intPt = intPtList[0] - newPart = QadLinearObject(part) - newPart.setEndPt(intPt) - dualClippedPartList.append(newPart) - i = 1 - while i < len(intPtList): - newPart = QadLinearObject(part) - newPart.setStartPt(intPt) - intPt = intPtList[i] - newPart.setEndPt(intPt) - dualClippedPartList.append(newPart) - i = i + 1 - newPart = QadLinearObject(part) - newPart.setStartPt(intPt) - dualClippedPartList.append(newPart) - else: # inserisco part intera - dualClippedPartList.append(part) - - # ciclo per spezzare dualClippedPartList - # sui punti di intersezione con untrimmedReversedOffsetPartList - i = 0 - while i < dualClippedPartList.qty(): - part = dualClippedPartList.getLinearObjectAt(i) - # calcola i punti di intersezione di part con untrimmedReversedOffsetPartList ordinati x distanza - dummy = getIntPtListBetweenPartAndPartList_offset(part, untrimmedReversedOffsetPartList) - intPtList = dummy[0] - - for intPt in intPtList: - newPart = QadLinearObject(part) - newPart.setEndPt(intPt) - dualClippedPartList.insert(i + 1, newPart) - newPart = QadLinearObject(part) - newPart.setStartPt(intPt) - dualClippedPartList.insert(i + 2, newPart) - dualClippedPartList.remove(i) - i = i + 1 - - i = i + 1 - - isClosedPolyline = dualClippedPartList.isClosed() # verifico se polilinea chiusa - splittedParts = QadLinearObjectList() - circle = QadCircle() - i = 0 - # per ogni parte - while i < dualClippedPartList.qty(): - part = dualClippedPartList.getLinearObjectAt(i) - # calcola i punti di intersezione con partList - dummy = getIntPtListBetweenPartAndPartList_offset(part, partList) - intPtList = dummy[0] - partNumberList = dummy[1] - - if len(intPtList) > 0: - if isClosedPolyline: - firstOrLastPart = False - else: - # verifico se tutti i punti di intersezione sono sul primo o sull'ultimo segmento di partList - firstOrLastPart = True - for partNumber in partNumberList: - if partNumber != 0 and partNumber != partList.qty() -1: - firstOrLastPart = False - break - - # se tutti i punti di intersezione sono sul primo o sull'ultimo segmento di partList - if firstOrLastPart: - splittedParts.removeAll() # pulisco la lista - splittedParts.append(QadLinearObject(part)) - for intPt in intPtList: - j = 0 - while j < splittedParts.qty(): - splittedPart = splittedParts.getLinearObjectAt(j) - # creo un cerchio nel punto di intersezione - circle.set(intPt, offSetDist) - # ottengo le parti esterne al cerchio - externalPartsOfIntPt = splittedPart.getPartsExternalToCircle(circle) - if externalPartsOfIntPt.qty() > 0: - for externalPartOfIntPt in externalPartsOfIntPt.defList: - splittedParts.insert(j, externalPartOfIntPt) - j = j + 1 - splittedParts.remove(j) - - # le sostituisco a part - for splittedPart in splittedParts.defList: - dualClippedPartList.insert(i, splittedPart) - i = i + 1 - dualClippedPartList.remove(i) - else: # se tutti i punti di intersezione non sono sul primo o sull'ultimo segmento di partList - dualClippedPartList.remove(i) - else: - i = i + 1 - - return dualClippedPartList - - -#=============================================================================== -# generalClosedPointPairClipping -#=============================================================================== -def generalClosedPointPairClipping(partList, dualClippedPartList, offSetDist): - """ - la funzione effettua il general closed point pair clipping su dualClippedPartList. - : lista delle parti originali della polilinea - : lista delle parti risultato del dual clipping - distanza di offset - - La funzione ritorna una lista di parti risultato del dual clipping - """ - # inizio di General Closed Point Pair clipping - GCPPCList = QadLinearObjectList(dualClippedPartList) # duplico la lista di parti - circle = QadCircle() - - # per ogni parte di partList - for part in partList.defList: - # per ogni parte di GCPPCList - i = 0 - while i < GCPPCList.qty(): - # ripeto finché viene fatto lo split - splitted = True - while splitted: - splitted = False - GCPPCPart = GCPPCList.getLinearObjectAt(i) - # verifico quale é il punto di part più vicino a GCPPCPart - # () - MinDistancePts = part.getMinDistancePtsWithLinearObject(GCPPCPart) - # se la distanza é inferiore a offSetDist (e non così vicina da essere considerata uguale) - if MinDistancePts[2] < offSetDist and not doubleNear(MinDistancePts[2], offSetDist): - # creo un cerchio nel punto di part più vicino a GCPPCPart - circle.set(MinDistancePts[0], offSetDist) - # ottengo le parti di GCPPCPart esterne al cerchio - splittedParts = GCPPCPart.getPartsExternalToCircle(circle) - # se la splittedParts è composta da una sola parte che è uguale a GCPPCPart - # ad es. se GCPPCPart è tangente al cerchio allora non faccio niente - if splittedParts.qty() == 0 or \ - splittedParts.qty() == 1 and splittedParts.getLinearObjectAt(0) == GCPPCPart: - i = i + 1 - else: - # le sostituisco a GCPPCPart - for splittedPart in splittedParts.defList: - GCPPCList.insert(i, splittedPart) - i = i + 1 - GCPPCList.remove(i) - if splittedParts.qty() > 0: - splitted = True - i = i - splittedParts.qty() # torno alla prima parte risultato dello split - else: - i = i + 1 - - return GCPPCList - - -#=============================================================================== -# getTrimmedOffSetPartList -#=============================================================================== -def getTrimmedOffSetPartList(partList, epsg, untrimmedOffsetPartList, untrimmedReversedOffsetPartList, \ - offSetDist): - """ - la funzione taglia la polilinea dove necessario. - : lista delle parti originali della polilinea - = the authority identifier for this srs - : lista delle parti non tagliate derivate dall'offset - : lista delle partinon tagliate derivate dall'offset in senso inverso - distanza di offset - - La funzione ritorna una lista di parti della polilinee (lista di segmenti o archi) - """ - - # faccio il dual clipping - dualClippedPartList = dualClipping(partList, untrimmedOffsetPartList, untrimmedReversedOffsetPartList, offSetDist) - # test - #GCPPCList = untrimmedOffsetPartList - #GCPPCList = dualClipping(partList, untrimmedOffsetPartList, untrimmedReversedOffsetPartList, offSetDist) - - # faccio il general closed point pair clipping - # test - GCPPCList = generalClosedPointPairClipping(partList, dualClippedPartList, offSetDist) - - # faccio il join tra le parti - return GCPPCList.selfJoin(epsg) - -#=============================================================================== -# offSetPolyline -#=============================================================================== -def offSetPolyline(points, epsg, offSetDist, offSetSide, gapType, tolerance2ApproxCurve = None): - """ - la funzione fa l'offset di una polilinea (lista di punti = ) - secondo una distanza e un lato di offset ("right" o "left") - ed un modo : - 0 = Estende i segmenti di linea alle relative intersezioni proiettate - 1 = Raccorda i segmenti di linea in corrispondenza delle relative intersezioni proiettate. - Il raggio di ciascun segmento di arco é uguale alla distanza di offset - 2 = Cima i segmenti di linea in corrispondenza delle intersezioni proiettate. - La distanza perpendicolare da ciascuna cima al rispettivo vertice - sull'oggetto originale é uguale alla distanza di offset. - = the authority identifier for this srs - = errore minimo di tolleranza - - La funzione ritorna una lista di polilinee (lista di liste di punti) - """ - if tolerance2ApproxCurve is None: - tolerance = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) - else: - tolerance = tolerance2ApproxCurve - - result = [] - - pointsLen = len(points) - - # verifico se é un cerchio - circle = QadCircle() - startEndVertices = circle.fromPolyline(points, 0) - if startEndVertices is not None and \ - startEndVertices[0] == 0 and startEndVertices[1] == (pointsLen - 1): - # siccome i punti del cerchio sono disegnati in senso antiorario - # se offSetSide = "right" significa verso l'esterno del cerchio - # se offSetSide = "left" significa verso l'interno del cerchio - if offSetSide == "left": - # offset verso l'interno del cerchio - offSetCircle = getOffSetCircle(circle, offSetDist, "internal") - else: - # offset verso l'esterno del cerchio - offSetCircle = getOffSetCircle(circle, offSetDist, "external") - - if offSetCircle is not None: - result.append(offSetCircle.asPolyline(tolerance)) - - return result - - # creo una lista dei segmenti e archi che formano la polilinea - partList = QadLinearObjectList() - partList.fromPolyline(points) - # ottengo la polilinea di offset non tagliata - untrimmedOffsetPartList = getUntrimmedOffSetPartList(partList, offSetDist, offSetSide, gapType, tolerance) - # inverto il senso dei punti x ottenere la polilinea di offset non tagliata invertita - reversedPoints = list(points) # duplico la lista - reversedPoints.reverse() - reversedPartList = QadLinearObjectList() - reversedPartList.fromPolyline(reversedPoints) - - untrimmedReversedOffsetPartList = getUntrimmedOffSetPartList(reversedPartList, offSetDist, offSetSide, gapType, tolerance) - # taglio la polilinea dove necessario - trimmedOffsetPartList = getTrimmedOffSetPartList(partList, epsg, \ - untrimmedOffsetPartList, \ - untrimmedReversedOffsetPartList, \ - offSetDist) - - # ottengo una lista di punti delle nuove polilinee - result = [] - for trimmedOffsetPart in trimmedOffsetPartList: - result.append(trimmedOffsetPart.asPolyline()) - - return result - -#=============================================================================== -# getAdjustedRubberBandVertex -#=============================================================================== -def getAdjustedRubberBandVertex(vertexBefore, vertex): - adjustedVertex = QgsPoint(vertex) - - # per un baco non ancora capito in QGIS: se la linea ha solo 2 vertici e - # hanno la stessa x o y (linea orizzontale o verticale) - # la linea non viene disegnata perciò sposto un pochino la x o la y - # del secondo vertice - if vertexBefore.x() == vertex.x(): - adjustedVertex.setX(vertex.x() + 1.e-9) - if vertexBefore.y() == vertex.y(): - adjustedVertex.setY(vertex.y() + 1.e-9) - - return adjustedVertex - - -#=============================================================================== -# ApproxCurvesOnGeom -#=============================================================================== -def ApproxCurvesOnGeom(geom, atLeastNSegmentForArc = None, atLeastNSegmentForCircle = None, - tolerance2ApproxCurve = None): - """ - ritorna una geometria le cui curve sono approssimate secondo una tolleranza di errore - atLeastNSegment = numero minimo di segmenti per riconoscere una curva - tolerance2ApproxCurve = errore minimo di tolleranza - """ - if tolerance2ApproxCurve is None: - tolerance = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE")) - else: - tolerance = tolerance2ApproxCurve - - if atLeastNSegmentForArc is None: - _atLeastNSegmentForArc = QadVariables.get(QadMsg.translate("Environment variables", "ARCMINSEGMENTQTY"), 12) - else: - _atLeastNSegmentForArc = atLeastNSegmentForArc - - if atLeastNSegmentForCircle is None: - _atLeastNSegmentForCircle = QadVariables.get(QadMsg.translate("Environment variables", "CIRCLEMINSEGMENTQTY"), 12) - else: - _atLeastNSegmentForCircle = atLeastNSegmentForCircle - - g = QgsGeometry(geom) # copio la geometria - - # verifico se ci sono archi - arcList = QadArcList() - arcList.fromGeom(g, _atLeastNSegmentForArc) - - # verifico se ci sono cerchi - circleList = QadCircleList() - circleList.fromGeom(g, _atLeastNSegmentForCircle) - - beforeVertex = 1 - - # dall'ultimo arco al primo - for i in xrange(len(arcList.arcList) - 1, -1, -1): - arc = arcList.arcList[i] - startVertex = arcList.startEndVerticesList[i][0] - endVertex = arcList.startEndVerticesList[i][1] - points = arc.asPolyline(tolerance) - # inserisco i nuovi vertici saltando il primo e l'ultimo - if arc.getStartPt() == g.vertexAt(startVertex): - for i in xrange(len(points) - 2, 0, -1): - if g.insertVertex(points[i].x(), points[i].y(), endVertex) == False: - return None - else: - for i in xrange(1, len(points), 1): - if g.insertVertex(points[i].x(), points[i].y(), endVertex) == False: - return None - # cancello i vecchi vertici - for i in range(0, endVertex - startVertex - 1): - if g.deleteVertex(startVertex + 1) == False: - return None - - # dall'ultimo cerchio al primo - for i in xrange(len(circleList.circleList) - 1, -1, -1): - circle = circleList.circleList[i] - startVertex = circleList.startEndVerticesList[i][0] - endVertex = circleList.startEndVerticesList[i][1] - points = circle.asPolyline(tolerance) - # inserisco i nuovi vertici saltando il primo e l'ultimo - for i in xrange(len(points) - 2, 0, -1): - if g.insertVertex(points[i].x(), points[i].y(), endVertex) == False: - return None - # cancello i vecchi vertici - for i in range(0, endVertex - startVertex - 1): - if g.deleteVertex(startVertex + 1) == False: - return None - - return g - -#=============================================================================== -# whatGeomIs -#=============================================================================== -def whatGeomIs(pt, geom): - if type(pt) == int: # pt é il numero del vertice - afterVertex = pt - else: - # ritorna una tupla (, - # - # ) - dummy = closestSegmentWithContext(pt, geom) - afterVertex = dummy[2] - if afterVertex is None: - return None - - arcList = QadArcList() - circleList = QadCircleList() - - # verifico se pt si riferisce ad un arco - if arcList.fromGeom(geom) > 0: - info = arcList.arcAt(afterVertex) - if info is not None: - return info[0] - - # verifico se pt si riferisce ad un cerchio - if circleList.fromGeom(geom) > 0: - info = circleList.circleAt(afterVertex) - if info is not None: - return info[0] - - # se non é un cerchio é una linea - pt1 = geom.vertexAt(afterVertex - 1) - pt2 = geom.vertexAt(afterVertex) - return pt1, pt2 - - -#=============================================================================== -# solveApollonius -#=============================================================================== -def solveApollonius(c1, c2, c3, s1, s2, s3): - ''' - >>> solveApollonius((0, 0, 1), (4, 0, 1), (2, 4, 2), 1,1,1) - Circle(x=2.0, y=2.1, r=3.9) - >>> solveApollonius((0, 0, 1), (4, 0, 1), (2, 4, 2), -1,-1,-1) - Circle(x=2.0, y=0.8333333333333333, r=1.1666666666666667) - Trova il cerchio tangente a tre cerchi (sarebbero 8 cerchi che si trovano con le - 8 combinazioni di s1, s2, s3 che assumo valore -1 o 1) - ''' - x1 = c1.center.x() - y1 = c1.center.y() - r1 = c1.radius - x2 = c2.center.x() - y2 = c2.center.y() - r2 = c2.radius - x3 = c3.center.x() - y3 = c3.center.y() - r3 = c3.radius - - v11 = 2*x2 - 2*x1 - v12 = 2*y2 - 2*y1 - v13 = x1*x1 - x2*x2 + y1*y1 - y2*y2 - r1*r1 + r2*r2 - v14 = 2*s2*r2 - 2*s1*r1 - - v21 = 2*x3 - 2*x2 - v22 = 2*y3 - 2*y2 - v23 = x2*x2 - x3*x3 + y2*y2 - y3*y3 - r2*r2 + r3*r3 - v24 = 2*s3*r3 - 2*s2*r2 - - if v11 == 0: - return None - - w12 = v12/v11 - w13 = v13/v11 - w14 = v14/v11 - - if v21-w12 == 0 or v21-w13 == 0 or v21-w14 == 0: - return None - - w22 = v22/v21-w12 - w23 = v23/v21-w13 - w24 = v24/v21-w14 - - if w22 == 0: - return None - - P = -w23/w22 - Q = w24/w22 - M = -w12*P-w13 - N = w14 - w12*Q - - a = N*N + Q*Q - 1 - b = 2*M*N - 2*N*x1 + 2*P*Q - 2*Q*y1 + 2*s1*r1 - c = x1*x1 + M*M - 2*M*x1 + P*P + y1*y1 - 2*P*y1 - r1*r1 - - # Find a root of a quadratic equation. This requires the circle centers not to be e.g. colinear - if a == 0: - return None - D = (b * b) - (4 * a * c) - - # se D é così vicino a zero - if doubleNear(D, 0.0): - D = 0 - elif D < 0: # non si può fare la radice quadrata di un numero negativo - return None - - rs = (-b-math.sqrt(D))/(2*a) - - xs = M+N*rs - ys = P+Q*rs - - center = QgsPoint(xs, ys) - circle = QadCircle() - circle.set(center, rs) - return circle - - -#=============================================================================== -# solveCircleTangentTo2LinesAndCircle -#=============================================================================== -def solveCircleTangentTo2LinesAndCircle(line1, line2, circle, s1, s2): - ''' - Trova i due cerchi tangenti a due rette e un cerchio (sarebbero 8 cerchi che si trovano con le - 4 combinazioni di s1, s2 che assumo valore -1 o 1) - e restituisce quello più vicino a pt - ''' - circleList = [] - # http://www.batmath.it/matematica/a_apollonio/rrc.htm - - # Questa costruzione utilizza una particolare trasformazione geometrica, che alcuni chiamano dilatazione parallela: - # si immagina che il raggio r del cerchio dato c si riduca a zero (il cerchio é ridotto al suo centro), - # mentre le rette rimangono parallele con distanze dal centro del cerchio che si é ridotto a zero aumentate o - # diminuite di r. Si é così ricondotti al caso di un punto e due rette e si può applicare una delle tecniche viste - # in quel caso. - - line1Par = [] - angle = getAngleBy2Pts(line1[0], line1[1]) - line1Par.append(getPolarPointByPtAngle(line1[0], angle + math.pi / 2, circle.radius * s1)) - line1Par.append(getPolarPointByPtAngle(line1[1], angle + math.pi / 2, circle.radius * s1)) - - line2Par = [] - angle = getAngleBy2Pts(line2[0], line2[1]) - line2Par.append(getPolarPointByPtAngle(line2[0], angle + math.pi / 2, circle.radius * s2)) - line2Par.append(getPolarPointByPtAngle(line2[1], angle + math.pi / 2, circle.radius * s2)) - - circleTan = QadCircle() - circleList = circleTan.from1IntPtLineLineTanPts(circle.center, line1Par, None, line2Par, None, True) - - for circleTan in circleList: - ptPerp = getPerpendicularPointOnInfinityLine(line1[0], line1[1], circleTan.center) - circleTan.radius = getDistance(ptPerp, circleTan.center) - - return circleList - - -#=============================================================================== -# solveCircleTangentToLineAnd2Circles -#=============================================================================== -def solveCircleTangentToLineAnd2Circles(line, circle1, circle2, s1, s2): - ''' - Trova i due cerchi tangenti a una retta e due cerchi (sarebbero 8 cerchi che si trovano con le - 4 combinazioni di s1, s2 che assumo valore -1 o 1) - e restituisce quello più vicino a pt - ''' - # http://www.batmath.it/matematica/a_apollonio/rcc.htm - - # Il modo più semplice per risolvere questo problema é quello di utilizzare una particolare - # trasformazione geometrica, che alcuni chiamano dilatazione parallela: si immagina che il raggio r - # del più piccolo dei cerchi in questione si riduca a zero (il cerchio é ridotto al suo centro), - # mentre le rette (risp. gli altri cerchi) rimangono parallele (risp. concentrici) con distanze - # dal centro del cerchio che si é ridotto a zero (rispettivamente con raggi dei cerchi) aumentati o - # diminuiti di r. - # Se applichiamo questa trasformazione al nostro caso, riducendo a zero il raggio del cerchio più piccolo - # (o di uno dei due se hanno lo stesso raggio) ci ritroveremo con un punto, un cerchio e una retta: - # trovate le circonferenze passanti per il punto e tangenti alla retta e al cerchio (nel modo già noto) - # potremo applicare la trasformazione inversa della dilatazione parallela precedente per determinare - # le circonferenze richieste. - if circle1.radius <= circle2.radius: - smallerCircle = circle1 - greaterCircle = circle2 - else: - smallerCircle = circle2 - greaterCircle = circle1 - - linePar = [] - angle = getAngleBy2Pts(line[0], line[1]) - linePar.append(getPolarPointByPtAngle(line[0], angle + math.pi / 2, smallerCircle.radius * s1)) - linePar.append(getPolarPointByPtAngle(line[1], angle + math.pi / 2, smallerCircle.radius * s1)) - - circlePar = QadCircle(greaterCircle) - circlePar.radius = circlePar.radius + smallerCircle.radius * s1 - - circleTan = QadCircle() - circleList = circleTan.from1IntPtLineCircleTanPts(smallerCircle.center, linePar, None, circlePar, None, True) - - for circleTan in circleList: - ptPerp = getPerpendicularPointOnInfinityLine(line[0], line[1], circleTan.center) - circleTan.radius = getDistance(ptPerp, circleTan.center) - - return circleList - - -def getCircularInversionOfpoint(circleRef, pt): - """ - la funzione ritorna l'inversione circolare di un punto - """ - dist = getDistance(circleRef.center, pt) - angle = getAngleBy2Pts(circleRef.center, pt) - circInvDist = circleRef.radius * circleRef.radius / dist - return getPolarPointByPtAngle(circleRef.center, angle, circInvDist) - - -def getCircularInversionOfLine(circleRef, line): - """ - la funzione ritorna l'inversione circolare di una linea (che é un cerchio) - """ - angleLine = getAngleBy2Pts(line[0], line[1]) - ptNearestLine = getPerpendicularPointOnInfinityLine(line[0], line[1], circleRef.center) - dist = getDistance(circleRef.center, ptNearestLine) - - pt1 = getCircularInversionOfpoint(circleRef, ptNearestLine) - - pt = getPolarPointByPtAngle(ptNearestLine, angleLine, dist) - pt2 = getCircularInversionOfpoint(circleRef, pt) - - pt = getPolarPointByPtAngle(ptNearestLine, angleLine + math.pi, dist) - pt3 = getCircularInversionOfpoint(circleRef, pt) - - result = QadCircle() - if result.from3Pts(pt1, pt2, pt3) == False: - return None - - return result - - -def getCircularInversionOfCircle(circleRef, circle): - """ - la funzione ritorna l'inversione circolare di un cerchio (che é un cerchio) - """ - - angleLine = getAngleBy2Pts(circle.center, circleRef.center) - ptNearestLine = getPolarPointByPtAngle(circle.center, angleLine, circle.radius) - dist = getDistance(circleRef.center, circle.center) - - pt1 = getCircularInversionOfpoint(circleRef, ptNearestLine) - - pt = getPolarPointByPtAngle(circle.center, angleLine + math.pi / 2, circle.radius) - pt2 = getCircularInversionOfpoint(circleRef, pt) - - pt = getPolarPointByPtAngle(circle.center, angleLine - math.pi / 2, circle.radius) - pt3 = getCircularInversionOfpoint(circleRef, pt) - - result = QadCircle() - if result.from3Pts(pt1, pt2, pt3) == False: - return None - - return result - - -#=============================================================================== -# lineFrom2TanPts -#=============================================================================== -def lineFrom2TanPts(geom1, pt1, geom2, pt2): - ''' - Trova la linea tangente a 2 oggetti - geometria 1 di tangenza (arco o cerchio) - punto di selezione geometria 1 - geometria 2 di tangenza (arco o cerchio) - punto di selezione geometria 2 - ''' - obj1 = whatGeomIs(pt1, geom1) - obj2 = whatGeomIs(pt2, geom2) - - if (type(obj1) == list or type(obj1) == tuple): # se linea esco - return None - obj1Type = obj1.whatIs() - if obj1Type == "ARC": # se é arco lo trasformo in cerchio - circle1 = QadCircle() - circle1.set(obj1.center, obj1.radius) - else: - circle1 = QadCircle(obj1) - - if (type(obj2) == list or type(obj2) == tuple): # se linea esco - return None - obj2Type = obj2.whatIs() - if obj2Type == "ARC": # se é arco lo trasformo in cerchio - circle2 = QadCircle() - circle2.set(obj2.center, obj2.radius) - else: - circle2 = QadCircle(obj2) - - tangents = circle1.getTangentsWithCircle(circle2) - - if obj1Type == "ARC" or obj2Type == "ARC": - # cancello le linee di tangenza che non abbiano un punto di tangenza nell'arco - for i in xrange(len(tangents) - 1, -1, -1): - toDelete1 = False - toDelete2 = False - if obj1Type == "ARC": - toDelete1 = True - for point in tangents[i]: - if obj1.isPtOnArc(point) == True: - toDelete1 = False - if obj2Type == "ARC": - toDelete2 = True - for point in tangents[i]: - if obj2.isPtOnArc(point) == True: - toDelete2 = False - - if toDelete1 == True or toDelete2 == True: - del tangents[i] - - if len(tangents) == 0: - return None - - AvgList = [] - Avg = sys.float_info.max - for tangent in tangents: - del AvgList[:] # svuoto la lista - - ptInt = getPerpendicularPointOnInfinityLine(tangent[0], tangent[1], obj1.center) - AvgList.append(getDistance(ptInt, pt1)) - - ptInt = getPerpendicularPointOnInfinityLine(tangent[0], tangent[1], obj2.center) - AvgList.append(getDistance(ptInt, pt2)) - - currAvg = numericListAvg(AvgList) - if currAvg < Avg: # mediamente più vicino - Avg = currAvg - result = tangent - - return result - - -#=============================================================================== -# lineFromTanPerPts -#=============================================================================== -def lineFromTanPerPts(tanGeom1, tanPt1, perGeom2, perPt2): - ''' - Trova la linea tangente a 1 oggetto e perpendicolare ad un altro - geometria di tangenza (arco o cerchio) - punto di selezione geometria di tangenza - geometria di perpendicolarità (linea, arco o cerchio) - punto di selezione geometria di perpendicolarità - ''' - obj1 = whatGeomIs(tanPt1, tanGeom1) - obj2 = whatGeomIs(perPt2, perGeom2) - - if (type(obj1) == list or type(obj1) == tuple): # se linea esco - return None - obj1Type = obj1.whatIs() - if obj1Type == "ARC": # se é arco lo trasformo in cerchio - circle1 = QadCircle() - circle1.set(obj1.center, obj1.radius) - else: - circle1 = QadCircle(obj1) - - if (type(obj2) == list or type(obj2) == tuple): - obj2Type = "LINE" - else: - obj2Type = obj2.whatIs() - if obj2Type == "ARC": # se é arco lo trasformo in cerchio - circle2 = QadCircle() - circle2.set(obj2.center, obj2.radius) - else: - circle2 = QadCircle(obj2) - - lines = [] - if obj2Type == "LINE": - # linee tangenti ad un cerchio e perpendicolari ad una linea - angle = getAngleBy2Pts(obj2[0], obj2[1]) - pt1 = getPolarPointByPtAngle(circle1.center, angle, circle1.radius) - pt2 = getPerpendicularPointOnInfinityLine(obj2[0], obj2[1], pt1) - if pt1 != pt2: # se la linea non passa per il centro del cerchio - lines.append([pt1, pt2]) # primo punto tangente e secondo punto perpendicolare - pt1 = getPolarPointByPtAngle(circle1.center, angle, -1 * circle1.radius) - pt2 = getPerpendicularPointOnInfinityLine(obj2[0], obj2[1], pt1) - lines.append([pt1, pt2]) # primo punto tangente e secondo punto perpendicolare - elif obj2Type == "CIRCLE" or obj2Type == "ARC": - # linee tangenti ad un cerchio e perpendicolari ad un cerchio - points = circle1.getTanPoints(circle2.center) - for point in points: - angle = getAngleBy2Pts(circle2.center, point) - pt1 = getPolarPointByPtAngle(circle2.center, angle, circle2.radius) - lines.append([point, pt1]) # primo punto tangente e secondo punto perpendicolare - pt1 = getPolarPointByPtAngle(circle2.center, angle, -1 * circle2.radius) - lines.append([point, pt1]) # primo punto tangente e secondo punto perpendicolare - - if obj1Type == "ARC" or obj2Type == "ARC": - # cancello le linee che non abbiano un punto nell'arco - for i in xrange(len(lines) - 1, -1, -1): - toDelete1 = False - toDelete2 = False - if obj1Type == "ARC": - toDelete1 = True - for point in lines[i]: - if obj1.isPtOnArc(point) == True: - toDelete1 = False - if obj2Type == "ARC": - toDelete2 = True - for point in lines[i]: - if obj2.isPtOnArc(point) == True: - toDelete2 = False - - if toDelete1 == True or toDelete2 == True: - del lines[i] - - if obj2Type == "LINE": - # cancello le linee che non abbiano un punto nel segmento - for i in xrange(len(lines) - 1, -1, -1): - line = lines[i] - # primo punto tangente e secondo punto perpendicolare - if isPtOnSegment(obj2[0], obj2[1], line[1]) == False: - del lines[i] - - if len(lines) == 0: - return None - - AvgList = [] - Avg = sys.float_info.max - for line in lines: - del AvgList[:] # svuoto la lista - # primo punto tangente e secondo punto perpendicolare - # tangente - AvgList.append(getDistance(line[0], tanPt1)) - # perpendicolare - AvgList.append(getDistance(line[1], perPt2)) - - currAvg = numericListAvg(AvgList) - if currAvg < Avg: # mediamente più vicino - Avg = currAvg - result = line - - return result - - -#=============================================================================== -# lineFrom2PerPts -#=============================================================================== -def lineFrom2PerPts(geom1, pt1, geom2, pt2): - ''' - Trova la linea perpendicolare a 2 oggetti: - geometria di perpendicolarità (linea, arco o cerchio) - punto di selezione geometria di perpendicolarità - geometria di perpendicolarità (linea, arco o cerchio) - punto di selezione geometria di perpendicolarità - ''' - obj1 = whatGeomIs(pt1, geom1) - obj2 = whatGeomIs(pt2, geom2) - - if (type(obj1) == list or type(obj1) == tuple): - obj1Type = "LINE" - else: - obj1Type = obj1.whatIs() - if obj1Type == "ARC": # se é arco lo trasformo in cerchio - circle1 = QadCircle() - circle1.set(obj1.center, obj1.radius) - else: - circle1 = QadCircle(obj1) - - if (type(obj2) == list or type(obj2) == tuple): - obj2Type = "LINE" - else: - obj2Type = obj2.whatIs() - if obj2Type == "ARC": # se é arco lo trasformo in cerchio - circle2 = QadCircle() - circle2.set(obj2.center, obj2.radius) - else: - circle2 = QadCircle(obj2) - - lines = [] - if obj1Type == "LINE": - if obj2Type == "LINE": - # linea perpendicolare a due linee - return None - else: - # linea perpendicolare ad una linea e ad un cerchio - ptPer1 = getPerpendicularPointOnInfinityLine(obj1[0], obj1[1], circle2.center) - angle = getAngleBy2Pts(circle2.center, ptPer1) - ptPer2 = getPolarPointByPtAngle(circle2.center, angle, circle2.radius) - if ptPer1 != ptPer2: # se la linea non é tangente nel punto ptPer2 - lines.append([ptPer1, ptPer2]) - ptPer2 = getPolarPointByPtAngle(circle2.center, angle, -1 * circle2.radius) - if ptPer1 != ptPer2: # se la linea non é tangente nel punto ptPer2 - lines.append([ptPer1, ptPer2]) - else: - if obj2Type == "LINE": - # linea perpendicolare ad un cerchio e ad una linea - ptPer2 = getPerpendicularPointOnInfinityLine(obj2[0], obj2[1], circle1.center) - angle = getAngleBy2Pts(circle1.center, ptPer2) - ptPer1 = getPolarPointByPtAngle(circle1.center, angle, circle1.radius) - if ptPer1 != ptPer2: # se la linea non é tangente nel punto ptPer1 - lines.append([ptPer1, ptPer2]) - ptPer1 = getPolarPointByPtAngle(circle1.center, angle, -1 * circle1.radius) - if ptPer1 != ptPer2: # se la linea non é tangente nel punto ptPer1 - lines.append([ptPer1, ptPer2]) - else: - perPoints1 = circle1.getIntersectionPointsWithInfinityLine(circle1.center, circle2.center) - perPoints2 = circle2.getIntersectionPointsWithInfinityLine(circle1.center, circle2.center) - for ptPer1 in perPoints1: - for ptPer2 in perPoints2: - if ptPer1 != ptPer2: - lines.append([ptPer1, ptPer2]) - - if obj1Type == "ARC" or obj2Type == "ARC": - # cancello le linee che non abbiano un punto nell'arco - for i in xrange(len(lines) - 1, -1, -1): - toDelete1 = False - toDelete2 = False - if obj1Type == "ARC": - toDelete1 = True - for point in lines[i]: - if obj1.isPtOnArc(point) == True: - toDelete1 = False - if obj2Type == "ARC": - toDelete2 = True - for point in lines[i]: - if obj2.isPtOnArc(point) == True: - toDelete2 = False - - if toDelete1 == True or toDelete2 == True: - del lines[i] - - if obj1Type == "LINE" or obj2Type == "LINE": - # cancello le linee che non abbiano un punto nell'arco - for i in xrange(len(lines) - 1, -1, -1): - toDelete1 = False - toDelete2 = False - if obj1Type == "LINE": - toDelete1 = True - for point in lines[i]: - if isPtOnSegment(obj1[0], obj1[1], point) == True: - toDelete1 = False - if obj2Type == "LINE": - toDelete2 = True - for point in lines[i]: - if isPtOnSegment(obj2[0], obj2[1], point) == True: - toDelete2 = False - - if toDelete1 == True or toDelete2 == True: - del lines[i] - - if len(lines) == 0: - return None - - AvgList = [] - Avg = sys.float_info.max - for line in lines: - del AvgList[:] # svuoto la lista - AvgList.append(getDistance(line[0], pt1)) - AvgList.append(getDistance(line[1], pt2)) - - currAvg = numericListAvg(AvgList) - if currAvg < Avg: # mediamente più vicino - Avg = currAvg - result = line - - return result - - -#=============================================================================== -# QadLinearObject class -# Classe che definisce un oggetto lineare che può essere un segmento o un arco -#=============================================================================== -class QadLinearObject(): - - - def __init__(self, linearObject = None): - # deflist = ( ) se si tratta di segmento - # ( ) se si tratta di arco - # dove = True significa che il punto iniziale dell'arco deve essere - # considerato finale nel senso del verso dell'arco - self.defList = None - if linearObject is not None: - self.set(linearObject) - - - #============================================================================ - # isInitialized - #============================================================================ - def isInitialized(self): - """ - la funzione ritorna True se l'oggetto é inizializzato. - """ - return False if self.defList is None else True - - - #============================================================================ - # __eq__ - #============================================================================ - def __eq__(self, other): - """ - la funzione ritorna True se l'oggetto é uguale a other. - """ - if self.isInitialized() == False and other.isInitialized() == False: - return True - if self.isInitialized() and other.isInitialized(): - if self.isSegment() and other.isSegment(): - return self.getStartPt() == other.getStartPt() and self.getEndPt() == other.getEndPt() - elif self.isArc() and other.isArc(): - return self.getArc() == other.getArc() - else: - return False - else: - return False - - - #============================================================================ - # clear - #============================================================================ - def clear(self): - """ - la funzione pulisce l'oggetto. - """ - if self.defList is not None: - del self.defList[:] - self.defList = None - - - #============================================================================ - # isSegment - #============================================================================ - def isSegment(self): - """ - la funzione ritorna True se l'oggetto é un segmento. - """ - if self.isInitialized() == False: - return False - return True if type(self.defList[0]) == QgsPoint else False - - - #============================================================================ - # isArc - #============================================================================ - def isArc(self): - """ - la funzione ritorna True se l'oggetto é un arco. - """ - if self.isInitialized() == False: - return False - return False if type(self.defList[0]) == QgsPoint else True - - - #============================================================================ - # whatIs - #============================================================================ - def whatIs(self): - return "LINE" if self.isSegment() else "ARC" - - - #============================================================================ - # isInverseArc - #============================================================================ - def isInverseArc(self): - """ - la funzione ritorna True se il punto iniziale dell'arco é da considerare come finale - nel verso impostato all'arco. - """ - if self.isArc() == False: - return False - return self.defList[1] - - - #============================================================================ - # setInverseArc - #============================================================================ - def setInverseArc(self, inverse): - """ - la funzione imposta il verso dell'arco. - """ - if self.isArc() == False: - return False - self.defList[1] = inverse - - - #============================================================================ - # getArc - #============================================================================ - def getArc(self): - """ - la funzione ritorna l'oggetto QadArc. - """ - if self.isArc() == False: - return None - return self.defList[0] - - - #============================================================================ - # setArc - #============================================================================ - def setArc(self, arc, inverse): - """ - la funzione ritorna l'oggetto arco. - """ - if self.isInitialized(): - del self.defList[:] # svuoto la lista - self.defList = [QadArc(arc), inverse] - - - #============================================================================ - # setSegment - #============================================================================ - def setSegment(self, p1, p2): - """ - la funzione imposta il segmento. - """ - if self.isInitialized(): - del self.defList[:] # svuoto la lista - self.defList = [QgsPoint(p1), QgsPoint(p2)] - - - #============================================================================ - # set - #============================================================================ - def set(self, linearObject): - """ - la funzione imposta l'oggetto come . - """ - if self.isInitialized(): - del self.defList[:] # svuoto la lista - - if type(linearObject) == list or type(linearObject) == tuple: # é una lista - if type(linearObject[0]) == QgsPoint: # é un segmento - self.defList = [QgsPoint(linearObject[0]), QgsPoint(linearObject[1])] - else: # é un arco - self.defList = [QadArc(linearObject[0]), linearObject[1]] - else: # é un oggetto QadLinearObject - if linearObject.isSegment(): - self.defList = [QgsPoint(linearObject.defList[0]), QgsPoint(linearObject.defList[1])] - else: - self.defList = [QadArc(linearObject.defList[0]), linearObject.defList[1]] - - - #============================================================================ - # setByGeom - #============================================================================ - def setByClosestSegmentOfGeom(self, pt, geom): - """ - la funzione imposta l'oggetto attraverso una geometria di cui si conosce un punto - nelle vicinanze. - """ - if self.isInitialized(): - del self.defList[:] # svuoto la lista - - # ritorna una tupla (, - # - # - # ) - dummy = closestSegmentWithContext(pt, geom) - if dummy is None or dummy[2] is None: - return False - - afterVertex = dummy[2] - # ritorna la sotto-geometria al vertice e la sua posizione nella geometria (0-based) - subGeom = getSubGeomAtVertex(geom, afterVertex)[0] - dummy = closestSegmentWithContext(pt, subGeom) - afterVertex = dummy[2] - points = subGeom.asPolyline() - - arcList = QadArcList() - arcList.fromPoints(points) - - # verifico se il punto afterVertex fa parte di un arco - arcInfo = arcList.arcAt(afterVertex) - if arcInfo is not None: - arc = arcInfo[0] - startEndVertices = arcInfo[1] - # verifico il verso - if points[startEndVertices[0]] == arc.getStartPt(): - inverse = False - else: - inverse = True - self.setArc(arc, inverse) - else: - pt1 = points[afterVertex - 1 ] - pt2 = points[afterVertex] - if pt1 != pt2: # solo se il punto iniziale é diverso da quello finale - self.setSegment(pt1, pt2) - - return True - - - #============================================================================ - # getStartPt - #============================================================================ - def getStartPt(self): - """ - la funzione ritorna il punto iniziale dell'oggetto (ne alloca uno nuovo). - """ - if self.isInitialized() == False: - return None - if self.isSegment(): # segmento - return QgsPoint(self.defList[0]) - else: # arco - if self.isInverseArc(): - return self.defList[0].getEndPt() - else: - return self.defList[0].getStartPt() - - - #============================================================================ - # setStartPt - #============================================================================ - def setStartPt(self, pt): - """ - la funzione imposta il punto iniziale dell'oggetto. - """ - if self.isInitialized() == False: - return None - if self.isSegment(): # segmento - return self.defList[0].set(pt.x(), pt.y()) - else: # arco - if self.isInverseArc(): - return self.defList[0].setEndAngleByPt(pt) - else: - return self.defList[0].setStartAngleByPt(pt) - - - #============================================================================ - # getEndPt - #============================================================================ - def getEndPt(self): - """ - la funzione ritorna il punto finale dell'oggetto. - """ - if self.isInitialized() == False: - return None - if self.isSegment(): # segmento - return self.defList[1] - else: # arco - if self.isInverseArc(): - return self.defList[0].getStartPt() - else: - return self.defList[0].getEndPt() - - - #============================================================================ - # setEndPt - #============================================================================ - def setEndPt(self, pt): - """ - la funzione imposta il punto finale dell'oggetto. - """ - if self.isInitialized() == False: - return None - if self.isSegment(): # segmento - return self.defList[1].set(pt.x(), pt.y()) - else: # arco - if self.isInverseArc(): - return self.defList[0].setStartAngleByPt(pt) - else: - return self.defList[0].setEndAngleByPt(pt) - - - #============================================================================ - # getStartEndPts - #============================================================================ - def getStartEndPts(self): - """ - la funzione ritorna il punto iniziale e finale dell'oggetto. - """ - return [self.getStartPt(), self.getEndPt()] - - - #============================================================================ - # setStartEndPts - #============================================================================ - def setStartEndPts(self, startPt, endPt): - """ - la funzione imposta il punto iniziale e finale dell'oggetto. - """ - self.setStartPt(startPt) - self.setEndPt(endPt) - - - #============================================================================ - # getTanDirectionOnStartPt - #============================================================================ - def getTanDirectionOnStartPt(self): - """ - la funzione ritorna la direzione della tangente al punto iniziale dell'oggetto. - """ - if self.isSegment(): # segmento - return getAngleBy2Pts(self.getStartPt(), self.getEndPt()) - else: # se é un arco - arc = QadArc() - if self.isInverseArc(): - return self.getArc().getTanDirectionOnEndPt() + math.pi - else: - return self.getArc().getTanDirectionOnStartPt() - - - #============================================================================ - # getTanDirectionOnEndPt - #============================================================================ - def getTanDirectionOnEndPt(self): - """ - la funzione ritorna la direzione della tangente al punto finale dell'oggetto. - """ - if self.isSegment(): # segmento - return getAngleBy2Pts(self.getStartPt(), self.getEndPt()) - else: # se é un arco - arc = QadArc() - if self.isInverseArc(): - return self.getArc().getTanDirectionOnStartPt() + math.pi - else: - return self.getArc().getTanDirectionOnEndPt() - - - #============================================================================ - # getMiddlePt - #============================================================================ - def getMiddlePt(self): - """ - la funzione restituisce il punto medio della parte. - """ - if self.isInitialized() == False: - return None - if self.isSegment(): # segmento - return getMiddlePoint(self.getStartPt(), self.getEndPt()) - else: # arco - return self.getArc().getMiddlePt() - - - #============================================================================ - # getTanDirectionOnMiddlePt - #============================================================================ - def getTanDirectionOnMiddlePt(self): - """ - la funzione ritorna la direzione della tangente al punto medio dell'oggetto. - """ - if self.isSegment(): # segmento - return getAngleBy2Pts(self.getStartPt(), self.getEndPt()) - else: # se é un arco - arc = QadArc() - middlePt = self.getMiddlePt() - if self.isInverseArc(): - return self.getArc().getTanDirectionOnPt(middlePt) + math.pi - else: - return self.getArc().getTanDirectionOnPt(middlePt) - - - #============================================================================ - # length - #============================================================================ - def length(self): - """ - la funzione restituisce la lunghezza della parte. - """ - if self.isInitialized() == False: - return None - if self.isSegment(): # segmento - return getDistance(self.getStartPt(), self.getEndPt()) - else: # arco - return self.getArc().length() - - - #============================================================================ - # move - #============================================================================ - def move(self, offSetX, offSetY): - """ - la funzione sposta le parti secondo un offset X e uno Y - """ - if self.isInitialized(): - if self.isSegment(): # segmento - self.defList[0].set(self.defList[0].x() + offSetX, self.defList[0].y() + offSetY) - self.defList[1].set(self.defList[1].x() + offSetX, self.defList[1].y() + offSetY) - else: # arco - self.getArc().center.set(self.getArc().center.x() + offSetX, self.getArc().center.y() + offSetY) - - - #============================================================================ - # lengthen_delta - #============================================================================ - def lengthen_delta(self, move_startPt, delta): - """ - la funzione sposta il punto iniziale (se move_startPt = True) o finale (se move_startPt = False) - di una distanza delta estendendo la parte lineare - """ - if self.isInitialized() == False: - return False - - length = self.length() - # lunghezza della parte + delta non può essere <= 0 - if length + delta <= 0: - return False - - if self.isSegment(): # segmento - if move_startPt == True: - angle = getAngleBy2Pts(self.getEndPt(), self.getStartPt()) - self.setStartPt(getPolarPointByPtAngle(self.getStartPt(), angle, delta)) - else: - angle = getAngleBy2Pts(self.getStartPt(), self.getEndPt()) - self.setEndPt(getPolarPointByPtAngle(self.getEndPt(), angle, delta)) - return True - else: # arco - # lunghezza arco + delta non può essere >= alla circonferenza del cerchio - if length + delta >= 2 * math.pi * self.getArc().radius: - return False - # (2*pi) : (2*pi*r) = angle : delta - angle = delta / self.getArc().radius - - if move_startPt == True: - if self.isInverseArc(): - self.getArc().endAngle = self.getArc().endAngle + angle - else: - self.getArc().startAngle = self.getArc().startAngle - angle - else: - if self.isInverseArc(): - self.getArc().startAngle = self.getArc().startAngle - angle - else: - self.getArc().endAngle = self.getArc().endAngle + angle - return True - - - #============================================================================ - # lengthen_deltaAngle - #============================================================================ - def lengthen_deltaAngle(self, move_startPt, delta): - """ - la funzione sposta il punto iniziale (se move_startPt = True) o finale (se move_startPt = False) - dell'arco di un certo numero di gradi delta estendendo la parte lineare - """ - if self.isInitialized() == False: - return False - - if self.isArc() == False: # se non è arco - return False - - totalAngle = self.getArc().totalAngle() - # angolo dell'arco + delta non può essere >= 2 * pi - if totalAngle + delta >= 2 * math.pi: - return False - # angolo dell'arco + delta non può essere <= 0 - if totalAngle + delta <= 0: - return False - - if move_startPt == True: - if self.isInverseArc(): - self.getArc().endAngle = self.getArc().endAngle + delta - else: - self.getArc().startAngle = self.getArc().startAngle - delta - else: - if self.isInverseArc(): - self.getArc().startAngle = self.getArc().startAngle - delta - else: - self.getArc().endAngle = self.getArc().endAngle + delta - return True - - - #============================================================================ - # getIntersectionPtsWithLinearObject - #============================================================================ - def getIntersectionPtsWithLinearObject(self, linearObject): - """ - la funzione calcola i punti di intersezione tra 2 oggetti lineari. - Ritorna una lista di punti di intersezione - """ - if self.isInitialized() == False: - return None - if self.isSegment(): # segmento - if linearObject.isSegment(): # segmento - ptInt = getIntersectionPointOn2Segments(self.getStartPt(), self.getEndPt(), \ - linearObject.getStartPt(), linearObject.getEndPt()) - if ptInt is not None: # se non sono parallele - return [ptInt] - else: - return [] - else: # arco - return linearObject.getArc().getIntersectionPointsWithSegment(self.getStartPt(), self.getEndPt()) - else: # arco - if linearObject.isSegment(): # segmento - return self.getArc().getIntersectionPointsWithSegment(linearObject.getStartPt(), linearObject.getEndPt()) - else: # arco - return self.getArc().getIntersectionPointsWithArc(linearObject.getArc()) - - return [] - - - #============================================================================ - # getIntersectionPtsOnExtensionWithLinearObject - #============================================================================ - def getIntersectionPtsOnExtensionWithLinearObject(self, linearObject): - """ - la funzione calcola i punti di intersezione tra le estensioni di 2 oggetti lineari. - Un arco diventa un cerchio e un segmento diventa una linea infinita. - Ritorna una lista di punti di intersezione - """ - if self.isInitialized() == False: - return None - if self.isSegment(): # segmento - if linearObject.isSegment(): # segmento - ptInt = getIntersectionPointOn2InfinityLines(self.getStartPt(), self.getEndPt(), \ - linearObject.getStartPt(), linearObject.getEndPt()) - if ptInt is not None: # se non sono parallele - return [ptInt] - else: - return [] - else: # arco - circle = QadCircle() - circle.set(linearObject.getArc().center, linearObject.getArc().radius) - return circle.getIntersectionPointsWithInfinityLine(self.getStartPt(), self.getEndPt()) - else: # arco - if linearObject.isSegment(): # segmento - circle = QadCircle() - circle.set(self.getArc().center, self.getArc().radius) - return circle.getIntersectionPointsWithInfinityLine(linearObject.getStartPt(), linearObject.getEndPt()) - else: # arco - circle1 = QadCircle() - circle1.set(self.getArc().center, self.getArc().radius) - circle2 = QadCircle() - circle2.set(linearObject.getArc().center, linearObject.getArc().radius) - return circle1.getIntersectionPointsWithCircle(circle2) - - return [] - - - #============================================================================ - # getMinDistancePtsWithLinearObject - #============================================================================ - def getMinDistancePtsWithLinearObject(self, linearObject): - """ - la funzione ritorna i punti di distanza minima e la distanza minima tra due parti - () - """ - if self.isInitialized() == False: - return None - if self.isSegment(): # segmento - if linearObject.isSegment(): # segmento-segmento - return getMinDistancePtsBetween2Segments(self.getStartPt(), self.getEndPt(), \ - linearObject.getStartPt(), linearObject.getEndPt()) - else: # segmento-arco - return getMinDistancePtsBetweenSegmentAndArc(self.getStartPt(), self.getEndPt(), \ - linearObject.getArc()) - else: # arco - if linearObject.isSegment(): # arco-segmento - return getMinDistancePtsBetweenSegmentAndArc(linearObject.getStartPt(), linearObject.getEndPt(), \ - self.getArc()) - else: # arco-arco - return getMinDistancePtsBetween2Arcs(self.getArc(), linearObject.getArc()) - - - #============================================================================ - # getDistanceFromStart - #============================================================================ - def getDistanceFromStart(self, pt): - """ - la funzione restituisce la distanza di (che deve essere sull'oggetto o sua estensione) - dal punto iniziale. - """ - if self.isInitialized() == False: - return None - - dummy = QadLinearObject(self) - dummy.setEndPt(pt) - - # se il punto é sull'estensione dalla parte del punto iniziale - if self.containsPt(pt) == False and \ - getDistance(self.getStartPt(), pt) < getDistance(self.getEndPt(), pt): - return -dummy.length() - - return dummy.length() - - - #============================================================================ - # getPointFromStart - #============================================================================ - def getPointFromStart(self, distance): - """ - la funzione restituisce un punto della parte alla distanza (che deve essere sull'oggetto) dal punto iniziale. - """ - if distance < 0: - return None - l = self.length() - if distance > l: - return None - - if self.isSegment(): # segmento - angle = getAngleBy2Pts(self.getStartPt(), self.getEndPt()) - return getPolarPointByPtAngle(self.getStartPt(), angle, distance) - else: # arco - # (2*pi) : (2*pi*r) = angle : delta - angle = delta / self.getArc().radius - - if self.isInverseArc(): - angle = self.getArc().endAngle - angle - else: - angle = self.getArc().startAngle + angle - - return getPolarPointByPtAngle(self.getArc().center, angle, self.getArc().radius) - - return None - - - #============================================================================ - # getPartsExternalToCircle - #============================================================================ - def getPartsExternalToCircle(self, circle): - """ - la funzione usa un cerchio per dividere l'oggetto lineare. - Le parti esterne al cerchio vengono restituite - nell'ordine dal punto iniziale a quello finale dell'oggetto linear. - """ - if self.isInitialized() == False: - return None - result = QadLinearObjectList() - - startPt = self.getStartPt() - endPt = self.getEndPt() - - if self.isSegment(): # segmento - intPtList = circle.getIntersectionPointsWithSegment(startPt, endPt) - else: # arco - intPtList = self.getArc().getIntersectionPointsWithCircle(circle) - - intPtSortedList = [] - for pt in intPtList: - # inserisco il punto ordinato per distanza dall'inizio di part - distFromStart = self.getDistanceFromStart(pt) - insertAt = 0 - for intPt in intPtSortedList: - if intPt[1] < distFromStart: - insertAt = insertAt + 1 - else: - break - intPtSortedList.insert(insertAt, [pt, distFromStart]) - - del intPtList[:] # svuoto la lista - for intPt in intPtSortedList: - intPtList.append(intPt[0]) - - startPtFromCenter = getDistance(circle.center, startPt) - endPtFromCenter = getDistance(circle.center, endPt) - intPtListLen = len(intPtList) - if intPtListLen == 0: # se non ci sono punti di intersezione - # se entrambi i punti terminali della parte sono esterni al cerchio - if startPtFromCenter >= circle.radius and endPtFromCenter >= circle.radius: - result.append(QadLinearObject(self)) - elif intPtListLen == 1: # se c'é un solo punto di intersezione - # se entrambi i punti terminali della parte sono esterni al cerchio - if startPtFromCenter >= circle.radius and endPtFromCenter >= circle.radius: - result.append(QadLinearObject(self)) - # se il primo punto della parte é interno e il secondo esterno al cerchio - elif startPtFromCenter < circle.radius and endPtFromCenter > circle.radius: - newLinearobj = QadLinearObject(self) - newLinearobj.setStartPt(intPtList[0]) - result.append(newLinearobj) - # se il primo punto della parte é esterno e il secondo interno al cerchio - elif startPtFromCenter > circle.radius and endPtFromCenter < circle.radius: - newLinearobj = QadLinearObject(self) - newLinearobj.setEndPt(intPtList[0]) - result.append(newLinearobj) - else : # se ci sono due punti di intersezione - # se il primo punto della parte é esterno al cerchio - if startPtFromCenter > circle.radius: - newLinearobj = QadLinearObject(self) - newLinearobj.setEndPt(intPtList[0]) - result.append(newLinearobj) - # se il secondo punto della parte é esterno al cerchio - if endPtFromCenter > circle.radius: - newLinearobj = QadLinearObject(self) - newLinearobj.setStartPt(intPtList[1]) - result.append(newLinearobj) - - return result - - - #============================================================================ - # reverse - #============================================================================ - def reverse(self): - """ - la funzione rovescia il verso dell'oggetto lineare. - """ - if self.isInitialized() == False: - return self - if self.isSegment(): # segmento - ptStart = QgsPoint(self.getStartPt()) - ptEnd = QgsPoint(self.getEndPt()) - self.setStartEndPts(ptEnd, ptStart) - else: # arco - self.setInverseArc(not self.isInverseArc()) - return self - - - #============================================================================ - # containsPt - #============================================================================ - def containsPt(self, pt): - """ - la funzione ritorna True se il punto si trova sull'oggetto lineare. - """ - if self.isInitialized() == False: - return False - if self.isSegment(): # segmento - return isPtOnSegment(self.getStartPt(), self.getEndPt(), pt) - else: # arco - return self.getArc().isPtOnArc(pt) - - - #============================================================================ - # join - #============================================================================ - def join(self, linearObject): - """ - la funzione restituisce una lista QadLinearObjectList che contiene la polilinea - generata dall'unione di questo oggetto lineare con se possibile - altrimenti None. - """ - if self.getEndPt() == linearObject.getStartPt(): - result = QadLinearObjectList() - result.append(QadLinearObject(self)) - result.append(QadLinearObject(linearObject)) - return result - elif self.getStartPt() == linearObject.getEndPt(): - result = QadLinearObjectList() - result.append(QadLinearObject(linearObject)) - result.append(QadLinearObject(self)) - return result - else: - return None - - - #=============================================================================== - # asPolyline - #=============================================================================== - def asPolyline(self, tolerance2ApproxCurve = None): - """ - la funzione ritorna una lista di punti che compone l'oggetto lineare. - """ - if self.isSegment(): # segmento - result = [self.getStartPt(), self.getEndPt()] - else: # arco - result = self.getArc().asPolyline(tolerance2ApproxCurve) - if self.isInverseArc(): # l'arco é in senso inverso - result.reverse() - - return result - - - #=============================================================================== - # transform - #=============================================================================== - def transform(self, coordTransform): - """ - la funzione trasforma le coordinate dei punti che compone l'oggetto lineare. - """ - result = QadLinearObject(self) - if result.isSegment(): # segmento - result.setStartPt(coordTransform.transform(result.getStartPt())) - result.setEndPt(coordTransform.transform(result.getEndPt())) - else: # arco - result.getArc().transform(coordTransform) - - return result - - - #=============================================================================== - # transformFromCRSToCRS - #=============================================================================== - def transformFromCRSToCRS(self, sourceCRS, destCRS): - """ - la funzione trasforma le coordinate dei punti che compone l'oggetto lineare. - """ - return transform(QgsCoordinateTransform(sourceCRS, destCRS)) - - - #============================================================================ - # leftOf - #============================================================================ - def leftOf(self, point): - """ - la funzione ritorna una numero < 0 se il punto é alla sinistra della parte lineare - """ - if self.isSegment(): # segmento - return leftOfLine(point, self.getStartPt(), self.getEndPt()) - else: - if getDistance(self.getArc().center, point) - self.getArc().radius > 0: - # esterno all'arco - if self.isInverseArc(): # l'arco é in senso inverso - return -1 # a sinistra - else: - return 1 # a destra - else: - # interno all'arco - if self.isInverseArc(): # l'arco é in senso inverso - return 1 # a destra - else: - return -1 # a sinistra - - - #=============================================================================== - # closestPtWithContext - #=============================================================================== - def closestPtWithContext(self, point, epsilon = 1.e-15): - """ - la funzione ritorna una lista con - ( - - <"a sinistra di" se il punto é alla sinista della parte (< 0 -> sinistra, > 0 -> destra) - """ - if self.isSegment(): # segmento - startPt = self.getStartPt() - endPt = self.getEndPt() - result = sqrDistToSegment(point, startPt.x(), startPt.y(), endPt.x(), endPt.y(), epsilon) - else: # arco - result = sqrDistToArc(point, self.getArc()) - - return result[0], result[1], self.leftOf(point) - - - #=============================================================================== - # breakOnPt - #=============================================================================== - def breakOnPt(self, point): - """ - la funzione spezza in due la parte nel punto . - Ritorna una lista di due parti: la prima parte (che può essere - nulla se conicide con il punto iniziale) e la seconda parte (che può essere - nulla se conicide con il punto finale) - """ - dummy = self.closestPtWithContext(point) - nearestPt = dummy[1] - if nearestPt is None: - return [None, None] - - if ptNear(nearestPt, self.getStartPt()): - part1 = None - else: - part1 = QadLinearObject(self) - part1.setEndPt(nearestPt) - - if ptNear(nearestPt, self.getEndPt()): - part2 = None - else: - part2 = QadLinearObject(self) - part2.setStartPt(nearestPt) - - return [part1, part2] - - -#=============================================================================== -# QadLinearObjectList class -# Classe che definisce una lista di oggetti lineari che può essere una polilinea -#=============================================================================== -class QadLinearObjectList(): - - - def __init__(self, linearObjectList = None): - self.defList = [] - # deflist = (...) - if linearObjectList is not None: - self.set(linearObjectList) - - - #============================================================================ - # whatIs - #============================================================================ - def whatIs(self): - return "LINEAROBJS" - - - #============================================================================ - # set - #============================================================================ - def set(self, linearObjectList): - self.removeAll() - for linearObject in linearObjectList.defList: - self.append(linearObject) - - - #============================================================================ - # append - #============================================================================ - def append(self, linearObject): - """ - la funzione aggiunge un oggetto lineare in fondo alla lista. - """ - return self.defList.append(QadLinearObject(linearObject)) - - - #============================================================================ - # appendList - #============================================================================ - def appendList(self, linearObjectList, start = None, qty = None): - """ - la funzione aggiunge una lista di oggetti lineari in fondo alla lista. - Se start diverso da None significa numero della parte di da cui iniziare. - Se diverso da None significa numero delle parti di da aggiungere, - se = None significa fino alla fine di . - """ - if start is None: - for linearObject in linearObjectList.defList: - self.append(linearObject) - else: - i = start - if qty is None: - tot = linearObjectList.qty() - else: - tot = linearObjectList.qty() if qty > linearObjectList.qty() else qty - - while i < tot: - self.append(linearObjectList.defList[i]) - i = i + 1 - - - #============================================================================ - # insert - #============================================================================ - def insert(self, partAt, linearObject): - """ - la funzione aggiunge un oggetto lineare nella posizione i-esima della lista. - """ - if partAt >= self.qty(): - return self.append(linearObject) - else: - return self.defList.insert(partAt, QadLinearObject(linearObject)) - - - #============================================================================ - # insertList - #============================================================================ - def insertList(self, i, linearObjectList): - """ - la funzione aggiunge una lista di oggetti lineari nella posizione i-esima della lista. - """ - ndx = i - for linearObject in linearObjectList.defList: - self.insert(ndx, linearObject) - ndx = ndx + 1 - - - #============================================================================ - # insertPoint - #============================================================================ - def insertPoint(self, partAt, pt): - """ - la funzione aggiunge un punto tra il punto iniziale e finale della parte i-esima della lista. - se i < 0 aggiunge il punto all'inizio della polilinea - se i >= qty() aggiunge il punto alla fine della polilinea - """ - if partAt < 0: # inserisco parte all'inizio - self.insert(0, [pt, self.getStartPt()]) - elif partAt >= self.qty(): # inserisco parte in fondo - self.append([self.getEndPt(), pt]) - else: - linearObject = self.getLinearObjectAt(partAt) - - if linearObject.isArc(): - arc1 = QadArc() - arc2 = QadArc() - totalAngle = linearObject.getArc().totalAngle() - inverseArc = linearObject.isInverseArc() - if inverseArc: - if arc1.fromStartEndPtsAngle(pt, linearObject.getArc().getEndPt(), totalAngle) == False: - return - if arc2.fromStartEndPtsAngle(linearObject.getArc().getStartPt(), pt, totalAngle) == False: - return - else: - if arc1.fromStartEndPtsAngle(linearObject.getArc().getStartPt(), pt, totalAngle) == False: - return - if arc2.fromStartEndPtsAngle(pt, linearObject.getArc().getEndPt(), totalAngle) == False: - return - - self.insert(partAt, [arc1, inverseArc]) - linearObject = self.getLinearObjectAt(partAt + 1) - linearObject.setArc(arc2, inverseArc) - else: - self.insert(partAt, [linearObject.getStartPt(), pt]) - linearObject = self.getLinearObjectAt(partAt + 1) - linearObject.set([pt, linearObject.getEndPt()]) - - - #============================================================================ - # movePoint - #============================================================================ - def movePoint(self, vertexAt, pt): - """ - la funzione sposta un punto tra il punto iniziale e finale della parte i-esima della lista. - se i < 0 aggiunge il punto all'inizio della polilinea - se i >= qty() aggiunge il punto alla fine della polilinea - """ - prevLinearObject, nextLinearObject = self.getPrevNextLinearObjectsAtVertex(vertexAt) - - if prevLinearObject is not None: - if prevLinearObject.isArc(): - if ptNear(prevLinearObject.getArc().getStartPt(), prevLinearObject.getEndPt()): - # sposto il punto iniziale dell'arco - if prevLinearObject.getArc().fromStartEndPtsAngle(pt, \ - prevLinearObject.getArc().getEndPt(), \ - prevLinearObject.getArc().totalAngle()) == False: - return - else: - # sposto il punto finale dell'arco - if prevLinearObject.getArc().fromStartEndPtsAngle(prevLinearObject.getArc().getStartPt(), \ - pt, \ - prevLinearObject.getArc().totalAngle()) == False: - return - else: - prevLinearObject.setEndPt(pt) - - if nextLinearObject is not None: - if nextLinearObject.isArc(): - if ptNear(nextLinearObject.getArc().getStartPt(), nextLinearObject.getStartPt()): - # sposto il punto iniziale dell'arco - if nextLinearObject.getArc().fromStartEndPtsAngle(pt, \ - nextLinearObject.getArc().getEndPt(), \ - nextLinearObject.getArc().totalAngle()) == False: - return - else: - # sposto il punto finale dell'arco - if nextLinearObject.getArc().fromStartEndPtsAngle(nextLinearObject.getArc().getStartPt(), \ - pt, \ - nextLinearObject.getArc().totalAngle()) == False: - return - else: - nextLinearObject.setStartPt(pt) - - - #============================================================================ - # remove - #============================================================================ - def remove(self, i): - """ - la funzione cancella un oggetto lineare nella posizione i-esima della lista. - """ - del self.defList[i] - - - #============================================================================ - # removeAll - #============================================================================ - def removeAll(self): - """ - la funzione cancella gli oggetti della lista. - """ - del self.defList[:] - - - #============================================================================ - # getLinearObjectAt - #============================================================================ - def getLinearObjectAt(self, i): - """ - la funzione restituisce l'oggetto lineare alla posizione i-esima - con numeri negativi parte dal fondo (es. -1 = ultima posizione) - """ - if self.qty() == 0 or i > self.qty() - 1: - return None - return self.defList[i] - - - #============================================================================ - # getVertexPosAtPt - #============================================================================ - def getVertexPosAtPt(self, pt): - """ - la funzione restituisce la posizione del vertice con coordinate (0-based), - None se non trovato. - """ - vertexAt = 0 - for linearObject in self.defList: - if ptNear(linearObject.getStartPt(), pt): - return vertexAt - vertexAt = vertexAt + 1 - if self.isClosed() == False: # se non é chiusa verifico ultimo vertice dell'ultima parte - if ptNear(self.defList[-1].getEndPt(), pt): - return vertexAt - - return None - - - #============================================================================ - # getPrevNextLinearObjectsAtVertex - #============================================================================ - def getPrevNextLinearObjectsAtVertex(self, vertexAt): - """ - la funzione restituisce l'oggetto lineare precedente e successivo al vertice vertexAt-esimo - """ - prevLinearObject = None - nextLinearObject = None - - if vertexAt == 0: # primo vertice - nextLinearObject = self.getLinearObjectAt(0) - if self.isClosed(): - prevLinearObject = self.getLinearObjectAt(-1) - elif vertexAt == self.qty(): # ultimo vertice - prevLinearObject = self.getLinearObjectAt(-1) - if self.isClosed(): - nextLinearObject = self.getLinearObjectAt(0) - else: - nextLinearObject = self.getLinearObjectAt(vertexAt) - prevLinearObject = self.getLinearObjectAt(vertexAt - 1) - - return prevLinearObject, nextLinearObject - - - #============================================================================ - # getPointAtVertex - #============================================================================ - def getPointAtVertex(self, vertexAt): - """ - la funzione restituisce il punto del vertice vertexAt-esimo che compone la polilinea. - """ - if vertexAt == self.qty(): # ultimo vertice - return self.getLinearObjectAt(-1).getEndPt() - else: - return self.getLinearObjectAt(vertexAt).getStartPt() - - - #============================================================================ - # getNextPos - #============================================================================ - def getNextPos(self, i): - """ - la funzione restituisce la posizione della parte successiva all' i-esima (0-based) - """ - if i == self.qty() - 1 or i == -1: # sono alla fine - if self.isClosed(): # se é chiusa torno all'inizio - return 0 - else: - return None - else: - return i + 1 - - - #============================================================================ - # getPrevPos - #============================================================================ - def getPrevPos(self, i): - """ - la funzione restituisce la posizione della parte precedente all' i-esima (0-based) - """ - if i == 0: # sono all'inizio - if self.isClosed(): # se é chiusa torno alla fine - return self.qty() - 1 - else: - return None - else: - return i - 1 - - - #============================================================================ - # fromPolyline - #============================================================================ - def fromPolyline(self, points): - """ - la funzione inizializza una lista di segmenti e archi (QadLinearObject) - che compone la polilinea. - Se una parte ha punto iniziale e finale coincidenti (es. 2 vertici consecutivi - che si sovrappongono o arco con angolo totale = 0 oppure = 360) - la parte viene rimossa dalla lista. - """ - pointsLen = len(points) - arcList = QadArcList() - arcList.fromPoints(points) - - # creo una lista dei segmenti e archi che formano la polilinea - del self.defList[:] # svuoto la lista - - i = 0 - while i < pointsLen - 1: - # verifico il punto i + 1 fa parte di un arco - arcInfo = arcList.arcAt(i + 1) - if arcInfo is not None: - arc = arcInfo[0] - if arc.getStartPt() != arc.getEndPt(): - # se i punti sono così vicini da essere considerati uguali - inverse = False if ptNear(points[i], arc.getStartPt()) else True - self.append([arc, inverse]) - startEndVertices = arcInfo[1] - endVertex = startEndVertices[1] - i = endVertex - else: - pt1 = points[i] - pt2 = points[i + 1] - self.append([pt1, pt2]) - i = i + 1 - - return - - - #=============================================================================== - # asPolyline - #=============================================================================== - def asPolyline(self, tolerance2ApproxCurve = None): - """ - la funzione ritorna una lista di punti che compone la polilinea formata da una lista di - parti di segmenti e archi (QadLinearObject) consecutive. - """ - result = [] - firstPt = True - for linearObject in self.defList: - pts = linearObject.asPolyline(tolerance2ApproxCurve) - ptsLen = len(pts) - if firstPt: - i = 0 - firstPt = False - else: - i = 1 - while i < ptsLen: - result.append(pts[i]) - i = i + 1 - - return result - - - #=============================================================================== - # reverse - #=============================================================================== - def reverse(self): - """ - la funzione rovescia il verso di una lista di - parti di segmenti e archi (QadLinearObject) consecutive. - """ - self.defList.reverse() - for linearObject in self.defList: - linearObject.reverse() - return - - - #============================================================================ - # length - #============================================================================ - def length(self): - """ - la funzione restituisce la somma delle lunghezze della parti. - """ - tot = 0 - for linearObject in self.defList: - tot = tot + linearObject.length() - return tot - - - #============================================================================ - # move - #============================================================================ - def move(self, offSetX, offSetY): - """ - la funzione sposta le parti secondo un offset X e uno Y - """ - for linearObject in self.defList: - linearObject.move(offSetX, offSetY) - - - #============================================================================ - # qty - #============================================================================ - def qty(self): - """ - la funzione restituisce la quantità di parti nella lista. - """ - return len(self.defList) - - - #============================================================================ - # getStartPt - #============================================================================ - def getStartPt(self): - """ - la funzione restituisce il punto iniziale della polilinea. - """ - linearObject = self.getLinearObjectAt(0) # primo oggetto lineare - return None if linearObject is None else linearObject.getStartPt() - - - #============================================================================ - # getEndPt - #============================================================================ - def getEndPt(self): - """ - la funzione restituisce il punto finale della polilinea. - """ - linearObject = self.getLinearObjectAt(-1) # ultimo oggetto lineare - return None if linearObject is None else linearObject.getEndPt() - - - #============================================================================ - # getCentroid - #============================================================================ - def getCentroid(self, tolerance2ApproxCurve = None): - """ - la funzione restituisce il punto centroide di una polilinea chiusa. - """ - if self.isClosed(): # verifico se polilinea chiusa - ptList = self.asPolyline(tolerance2ApproxCurve) - g = QgsGeometry.fromPolygon([ptList]) - if g is not None: - return g.centroid().asPoint() - - return None - - - #============================================================================ - # isClosed - #============================================================================ - def isClosed(self): - """ - la funzione restituisce True se la polilinea (lista di parti segmenti-archi) é chiusa. - """ - if len(self.defList) == 0: - return False - else: - return True if ptNear(self.getStartPt(), self.getEndPt()) else False - - - #============================================================================ - # setClose - #============================================================================ - def setClose(self, toClose = True): - """ - la funzione chiude o apre la polilinea (lista di parti segmenti-archi). - """ - if toClose: # da chiudere - if self.isClosed() == False: - linearObject = self.getLinearObjectAt(-1) - if linearObject.isArc(): # se é un arco - arc = QadArc() - if linearObject.isInverseArc(): - if arc.fromStartEndPtsTan(linearObject.getArc().getStartPt(), \ - self.getStartPt(), \ - linearObject.getArc().getTanDirectionOnStartPt() + math.pi) == False: - return - else: - if arc.fromStartEndPtsTan(linearObject.getArc().getEndPt(), \ - self.getStartPt(), \ - linearObject.getArc().getTanDirectionOnEndPt()) == False: - return - - newLinearObject = QadLinearObject() - newLinearObject.setArc(arc, linearObject.isInverseArc()) - self.append(newLinearObject) - else: # non é un arco - if self.qty() > 1: - self.append([self.getEndPt(), self.getStartPt()]) - else: # da aprire - if self.isClosed() == True: - if self.qty() > 1: - self.remove(-1) - - - #============================================================================ - # curve - #============================================================================ - def curve(self, toCurve = True): - """ - se toCurve = True: - la funzione curva ogni segmento per adattarlo alla polilinea (lista di parti segmenti-archi) - facendo passare la nuova polilinea per i vertici. - se toCurve = False: - la funzione trasforma in segmento retto ogni arco della polilinea (lista di parti segmenti-archi). - """ - if toCurve == False: - if self.getCircle() is not None: # se é un cerchio - return - - for linearObject in self.defList: - if linearObject.isArc(): - linearObject.set([linearObject.getStartPt(), linearObject.getEndPt()]) - return - - tot = self.qty() - if tot < 2: - return - isClosed = self.isClosed() - if isClosed: - if self.getCircle() is not None: # se é un cerchio - return - - newLinearObjectList = QadLinearObjectList() - - # primo oggetto lineare - current = self.getLinearObjectAt(0) - prev = None - tanDirectionOnStartPt = None - if isClosed: - prev = self.getLinearObjectAt(-1) - arc = QadArc() - if arc.fromStartSecondEndPts(prev.getStartPt(), current.getStartPt(), current.getEndPt()): - if ptNear(prev.getStartPt(), arc.getStartPt()): # arco non é inverso - arc.setStartAngleByPt(current.getStartPt()) - tanDirectionOnStartPt = arc.getTanDirectionOnStartPt() - else: # arco é inverso - arc.setEndAngleByPt(current.getStartPt()) - tanDirectionOnStartPt = arc.getTanDirectionOnEndPt() + math.pi - - next = self.getLinearObjectAt(1) - newLinearObjectList.defList.extend(getCurveLinearObjects(tanDirectionOnStartPt, prev, current, next)) - - i = 1 - while i < tot - 1: - tanDirectionOnStartPt = newLinearObjectList.getLinearObjectAt(-1).getTanDirectionOnEndPt() - prev = current - current = next - next = self.getLinearObjectAt(i + 1) - newLinearObjectList.defList.extend(getCurveLinearObjects(tanDirectionOnStartPt, prev, current, next)) - i = i + 1 - - # ultimo oggetto lineare - tanDirectionOnStartPt = newLinearObjectList.getLinearObjectAt(-1).getTanDirectionOnEndPt() - prev = current - current = next - next = self.getLinearObjectAt(0) if isClosed else None - newLinearObjectList.defList.extend(getCurveLinearObjects(tanDirectionOnStartPt, prev, current, next)) - - self.set(newLinearObjectList) - - - #============================================================================ - # fillet - #============================================================================ - def fillet(self, radius): - """ - la funzione raccorda ogni segmento al successivo con un raggio di curvatura noto, - la nuova polilinea avrà i vertici cambiati. - """ - if radius <= 0: - return - newLinearObjectList = QadLinearObjectList() - - part = self.getLinearObjectAt(0) - i = 1 - tot = self.qty() - while i <= tot - 1: - nextPart = self.getLinearObjectAt(i) - if part.isSegment() and nextPart.isSegment(): - # Ritorna una lista di 3 elementi (None in caso di errore): - # - una linea che sostituisce , se = None va rimossa - # - un arco, se = None non c'é arco di raccordo tra le due linee - # - una linea che sostituisce , se = None va rimossa - res = offsetBridgeTheGapBetweenLines(part, nextPart, radius, 1) - if res is None: - return - if res[0] is not None: - part = res[0] - newLinearObjectList.append(part) - if res[1] is not None: - part = res[1] - newLinearObjectList.append(part) - if res[2] is not None: - part = res[2] - else: - # offSetSide = "left" or "right" - res = fillet2Parts_offset(part, nextPart, offSetSide, radius) - i = i + 1 - - if self.isClosed(): - nextPart = newLinearObjectList.getLinearObjectAt(0) - if part.isSegment() and nextPart.isSegment(): - # Ritorna una lista di 3 elementi (None in caso di errore): - # - una linea che sostituisce , se = None va rimossa - # - un arco, se = None non c'é arco di raccordo tra le due linee - # - una linea che sostituisce , se = None va rimossa - res = offsetBridgeTheGapBetweenLines(part, nextPart, radius, 1) - if res is None: - return - if res[0] is not None: - part = res[0] - newLinearObjectList.append(part) - if res[1] is not None: - part = res[1] - newLinearObjectList.append(part) - if res[2] is not None: - part = res[2] - self.remove(0) - else: - newLinearObjectList.append(part) - - self.set(newLinearObjectList) - - - #============================================================================ - # getCircle - #============================================================================ - def getCircle(self): - """ - la funzione ritorna l'oggetto cerchio. - """ - points = self.asPolyline() # vettore di punti - circle = QadCircle() - return circle if circle.fromPolyline(points, 0) is not None else None - - - #============================================================================ - # getDistanceFromStart - #============================================================================ - def getDistanceFromStart(self, pt): - """ - la funzione restituisce la distanza di (che deve essere sull'oggetto) dal punto iniziale. - Da usarsi solo se le parti rappresentano una polilinea. - """ - tot = 0 - for linearObject in self.defList: - if linearObject.containsPt(pt) == True: - return tot + linearObject.getDistanceFromStart(pt) - else: - tot = tot + linearObject.length() - - return -1 - - - #============================================================================ - # getPointFromStart - #============================================================================ - def getPointFromStart(self, distance): - """ - la funzione restituisce un punto della polilinea alla distanza (che deve essere sull'oggetto) dal punto iniziale. - Da usarsi solo se le parti rappresentano una polilinea. - """ - if distance < 0: - return None - d = distance - for linearObject in self.defList: - l = linearObject.length() - if d > l: - d = d - l - else: - return linearObject.getPointFromStart(d) - - return None - - - #============================================================================ - # getLinearObjectNdxFromStart - #============================================================================ - def getLinearObjectNdxFromStart(self, distance): - """ - la funzione restituisce il numero della parte lineare (0-index) in cui termina la distanza dal punto iniziale della polilinea. - Da usarsi solo se le parti rappresentano una polilinea. - """ - if distance < 0: - return None - d = distance - n = 0 - for linearObject in self.defList: - l = linearObject.length() - if d > l: - return n - else: - n = n +1 - - return None - - - #============================================================================ - # lengthen_delta - #============================================================================ - def lengthen_delta(self, move_startPt, delta): - """ - la funzione sposta il punto iniziale (se move_startPt = True) o finale (se move_startPt = False) - di una distanza delta allungando (se delta > 0) o accorciando (se delta < 0) la polilinea - """ - length = self.length() - # lunghezza polilinea + delta non può essere <= 0 - if length + delta <= 0: - return False - - if move_startPt == False: - # dal punto finale - if delta >= 0: # allungo la polilinea - # ultima parte - return self.getLinearObjectAt(-1).lengthen_delta(False, delta) - else: # accorcio la polilinea - # cerco la parte in cui finirebbe la polilinea accorciata - nPart = 0 - d = length + delta - for linearObject in self.defList: - l = linearObject.length() - if d > l: - d = d - l - nPart = nPart + 1 - else: - if linearObject.lengthen_delta(False, -(l - d)) == False: - return False - # se non è l'ultima parte - if nPart+1 < len(self.defList): - # cancello le parti successive a nPart - del self.defList[nPart+1 :] - break - else: # dal punto iniziale - self.reverse() - res = self.lengthen_delta(False, delta) - self.reverse() - return res - - - #============================================================================ - # lengthen_deltaAngle - #============================================================================ - def lengthen_deltaAngle(self, move_startPt, delta): - """ - la funzione sposta il punto iniziale del primo arco (se move_startPt = True) o - punto finale dell'ultimo arco (se move_startPt = False) - di un certo numero di gradi delta allungando (se delta > 0) o accorciando (se delta < 0) la polilinea - """ - if move_startPt == False: - # dal punto finale - return self.getLinearObjectAt(-1).lengthen_deltaAngle(False, delta) - else: - # dal punto iniziale - return self.getLinearObjectAt(0).lengthen_deltaAngle(True, delta) - - - #============================================================================ - # asQgsFeatureList - #============================================================================ - def asQgsFeatureList(self, polylineMode): - """ - la funzione restituisce una lista di feature. - Se polylineMode = True allora la lista degli oggetti lineari sarà considerata un'unica polilinea - """ - fList = [] - if polylineMode == False: - for linearObject in self.defList: - f = QgsFeature() - f.setGeometry(QgsGeometry.fromPolyline(linearObject.asPolyline())) - fList.append(f) - else: - f = QgsFeature() - f.setGeometry(QgsGeometry.fromPolyline(self.asPolyline())) - fList.append(f) - - return fList - - - #============================================================================ - # appendToTempQgsVectorLayer - #============================================================================ - def appendToTempQgsVectorLayer(self, vectorLayer, polylineMode, updateExtents = True): - """ - la funzione inserisce gli oggetti lineari in lista in un QgsVectorLayer temporaneo già creato. - Se polylineMode = True allora la lista degli oggetti lineari sarà considerata un'unica polilinea - Ritorna la lista dei corrispettivi id di feature oppure None in caso di errore - """ - fList = self.asQgsFeatureList(polylineMode) - - idList = [] - result = True - if vectorLayer.startEditing() == False: - return None - - vectorLayer.beginEditCommand("Feature added") - - for f in fList: - if vectorLayer.addFeature(f): - idList.append(f.id()) - else: - result = False - break - - if result == True: - vectorLayer.endEditCommand(); - if updateExtents: - vectorLayer.updateExtents() - return idList - else: - vectorLayer.destroyEditCommand() - return None - - - #=============================================================================== - # getIntersectionPtsWithLinearObject - #=============================================================================== - def getIntersectionPtsWithLinearObject(self, part, orderByStartPtOfPart = False): - """ - la funzione restituisce diverse liste: - - la prima é una lista di punti di intersezione tra la parte e - la lista di parti ordinata per distanza dal punto iniziale di se - = True altrimenti ordinata per distanza dal punto iniziale - della lista di parti. - - la seconda é una lista che contiene, rispettivamente per ogni punto di intersezione, - il numero della parte (0-based) della lista di parti in cui si trova quel punto. - - la terza é una lista che contiene, rispettivamente per ogni punto di intersezione, - la distanza dal punto iniziale di o dal punto iniziale della lista di parti - (vedi ) - : un segmento o arco - """ - intPtSortedList = [] # lista di ((punto, distanza dall'inizio della parte) ...) - partNumber = -1 - if orderByStartPtOfPart == False: - distFromStartPrevParts = 0 - - # per ogni parte della lista - for part2 in self.defList: - partNumber = partNumber + 1 - partialIntPtList = part.getIntersectionPtsWithLinearObject(part2) - for partialIntPt in partialIntPtList: - # escludo i punti che sono già in intPtSortedList - found = False - for intPt in intPtSortedList: - if ptNear(intPt[0], partialIntPt): - found = True - break - - if found == False: - if orderByStartPtOfPart: - # inserisco il punto ordinato per distanza dall'inizio di part - distFromStart = part.getDistanceFromStart(partialIntPt) - else: - distFromStart = distFromStartPrevParts + part2.getDistanceFromStart(partialIntPt) - - insertAt = 0 - for intPt in intPtSortedList: - if intPt[1] < distFromStart: - insertAt = insertAt + 1 - else: - break - intPtSortedList.insert(insertAt, [partialIntPt, distFromStart, partNumber]) - - if orderByStartPtOfPart == False: - distFromStartPrevParts = distFromStartPrevParts + part2.length() - - resultIntPt = [] - resultPartNumber = [] - resultDistanceFromStart = [] - for intPt in intPtSortedList: - resultIntPt.append(intPt[0]) - resultPartNumber.append(intPt[2]) - resultDistanceFromStart.append(intPt[1]) - - return resultIntPt, resultPartNumber, resultDistanceFromStart - - - #=============================================================================== - # getIntersectionPtsWithLinearObjectlist - #=============================================================================== - def getIntersectionPtsWithLinearObjectList(self, partList): - """ - la funzione restituisce diverse liste: - - la prima é una lista di punti di intersezione tra le 2 liste di parti - ordinata per distanza dal punto iniziale della lista. - - la seconda é una lista che contiene, rispettivamente per ogni punto di intersezione, - il numero della parte (0-based) della lista di parti in cui si trova quel punto. - - la terza é una lista che contiene, rispettivamente per ogni punto di intersezione, - la distanza dal punto iniziale della lista. - : lista di parti - """ - resultIntPt = [] - resultPartNumber = [] - resultDistanceFromStart = [] - - # per ogni parte della lista - for part in self.defList: - # lista di punti di intersezione ordinata per distanza dal punto iniziale di - partialResult = partList.getIntersectionPtsWithLinearObject(part, True) - resultIntPt.extend(partialResult[0]) - resultPartNumber.extend(partialResult[2]) - resultDistanceFromStart.extend(partialResult[1]) - - return resultIntPt, resultPartNumber, resultDistanceFromStart - - - #============================================================================ - # join - #============================================================================ - def join(self, linearObjectListToJoinTo, toleranceDist = TOLERANCE, mode = 1): - """ - la funzione unisce la polilinea con un'altra polilinea secondo la modalità . - In caso di successo ritorna True altrimenti False. - = polilinea con cui unirsi - = distanza di tolleranza perché 2 punti siano considerati coincidenti - = Imposta il metodo di unione (usato se toleranceDist > 0): - 1 -> Estendi; Consente di unire polilinee selezionate estendendo o tagliando - i segmenti nei punti finali più vicini. - 2 -> Aggiungi; Consente di unire polilinee selezionate aggiungendo un segmento - retto tra i punti finali più vicini. - 3 -> Entrambi;Consente di unire polilinee selezionate estendendo o tagliando, se possibile. - In caso contrario, consente di unire polilinee selezionate aggiungendo - un segmento retto tra i punti finali più vicini. - """ - myToleranceDist = TOLERANCE if toleranceDist == 0 else toleranceDist - # cerco il punto più vicino al punto iniziale della polilinea - ptToJoin = self.getStartPt() - isStartPt = True - minDist = sys.float_info.max - # considero il punto iniziale della polilinea a cui unirsi - if linearObjectListToJoinTo.getStartPt() is None: # test - fermati = True - dist = getDistance(ptToJoin, linearObjectListToJoinTo.getStartPt()) - if dist < minDist: - isStartPtToJoinTo = True - minDist = dist - # considero il punto finale della polilinea a cui unirsi - dist = getDistance(ptToJoin, linearObjectListToJoinTo.getEndPt()) - if dist < minDist: - isStartPtToJoinTo = False - minDist = dist - - # cerco il punto più vicino al punto finale della polilinea - ptToJoin = self.getEndPt() - # considero il punto iniziale della polilinea a cui unirsi - dist = getDistance(ptToJoin, linearObjectListToJoinTo.getStartPt()) - if dist < minDist: - isStartPt = False - isStartPtToJoinTo = True - minDist = dist - # considero il punto finale della polilinea a cui unirsi - dist = getDistance(ptToJoin, linearObjectListToJoinTo.getEndPt()) - if dist < minDist: - isStartPt = False - isStartPtToJoinTo = False - minDist = dist - - if minDist <= myToleranceDist: # trovato un punto - # se il punto iniziale della polilinea da unire é uguale a quello iniziale della polilinea a cui unirsi - if isStartPt == True and isStartPtToJoinTo == True: - part1 = qad_utils.QadLinearObject(self.getLinearObjectAt(0)) - part1.reverse() - part2 = qad_utils.QadLinearObject(linearObjectListToJoinTo.getLinearObjectAt(0)) - part2.reverse() - - res = joinEndPtsLinearParts(part1, part2, mode) - if res is not None: - # elimino la prima parte - self.remove(0) - res.reverse() - self.insertList(0, res) - - # aggiungo le parti di tranne la prima - i = 1 - tot = linearObjectListToJoinTo.qty() - while i < tot: - self.insert(0, linearObjectListToJoinTo.getLinearObjectAt(i).reverse()) - i = i + 1 - return True - - # se il punto iniziale della polilinea da unire é uguale a quello finale della polilinea a cui unirsi - elif isStartPt == True and isStartPtToJoinTo == False: - part1 = qad_utils.QadLinearObject(self.getLinearObjectAt(0)) - part1.reverse() - part2 = linearObjectListToJoinTo.getLinearObjectAt(-1) - - res = joinEndPtsLinearParts(part1, part2, mode) - if res is not None: - # elimino la prima parte - self.remove(0) - res.reverse() - self.insertList(0, res) - - # aggiungo le parti di tranne l'ultima - i = linearObjectListToJoinTo.qty() - 2 - while i >= 0: - self.insert(0, linearObjectListToJoinTo.getLinearObjectAt(i)) - i = i - 1 - return True - - # se il punto finale della polilinea da unire é uguale a quello iniziale della polilinea a cui unirsi - elif isStartPt == False and isStartPtToJoinTo == True: - part1 = self.getLinearObjectAt(-1) - part2 = qad_utils.QadLinearObject(linearObjectListToJoinTo.getLinearObjectAt(0)) - part2.reverse() - - res = joinEndPtsLinearParts(part1, part2, mode) - if res is not None: - # elimino l'ultima parte - self.remove(-1) - self.appendList(res) - - # aggiungo le parti di tranne la prima - i = 1 - tot = linearObjectListToJoinTo.qty() - while i < tot: - self.append(linearObjectListToJoinTo.getLinearObjectAt(i)) - i = i + 1 - return True - - # se il punto finale della polilinea da unire é uguale a quello finale della polilinea a cui unirsi - elif isStartPt == False and isStartPtToJoinTo == False: - part1 = self.getLinearObjectAt(-1) - part2 = linearObjectListToJoinTo.getLinearObjectAt(-1) - - res = joinEndPtsLinearParts(part1, part2, mode) - if res is not None: - # elimino l'ultima parte - self.remove(-1) - self.appendList(res) - - # aggiungo le parti di tranne l'ultima - i = linearObjectListToJoinTo.qty() - 2 - while i >= 0: - self.append(linearObjectListToJoinTo.getLinearObjectAt(i).reverse()) - i = i - 1 - return True - - return False - - - #============================================================================ - # selfJoin - #============================================================================ - def selfJoin(self, epsg): - """ - la funzione restituisce una lista QadLinearObjectList che contiene le polilinee - generate dall'unione degli oggetti lineari (lista di parti segmenti-archi). - = the authority identifier for this srs - """ - # creo un layer temporaneo in memoria - vectorLayer = QgsVectorLayer("LineString?crs=%s&index=yes" % epsg, "QAD_SelfJoinLines", "memory") - provider = vectorLayer.dataProvider() - - # unisco le parti nella lista di self - # inserisco nel layer i vari oggetti lineari - idList = self.appendToTempQgsVectorLayer(vectorLayer, False) - if idList is None: - return [] - if provider.capabilities() & QgsVectorDataProvider.CreateSpatialIndex: - provider.createSpatialIndex() - - vectorLayer.beginEditCommand("selfJoin") - - for featureIdToJoin in idList: - # featureIdToJoin, vectorLayer, tolerance2ApproxCurve, tomyToleranceDist - joinFeatureInVectorLayer(featureIdToJoin, vectorLayer, QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE"))) - - vectorLayer.endEditCommand() - vectorLayer.commitChanges() - - result = [] - feature = QgsFeature() - - # fetchAttributes, fetchGeometry, rectangle, useIntersect - for feature in vectorLayer.getFeatures(getFeatureRequest([], True, None, False)): - linearObjectList = QadLinearObjectList() - linearObjectList.fromPolyline(feature.geometry().asPolyline()) - result.append(linearObjectList) - - return result - - #=============================================================================== - # transform - #=============================================================================== - def transform(self, coordTransform): - """ - la funzione restituisce una nuova lista di parti con le coordinate trasformate. - """ - result = QadLinearObjectList() - for linearObject in self.defList: - result.append(linearObject.transform(coordTransform)) - return result - - - #=============================================================================== - # transformFromCRSToCRS - #=============================================================================== - def transformFromCRSToCRS(self, sourceCRS, destCRS): - """ - la funzione trasforma le coordinate dei punti che compone l'oggetto lineare. - """ - return self.transform(QgsCoordinateTransform(sourceCRS, destCRS)) - - - #============================================================================ - # containsPt - #============================================================================ - def containsPt(self, pt, startAt = 0): - """ - la funzione ritorna la posizione della parte che contiene il punto oppure -1. - Il controllo inizia dalla parte (0-based) - """ - tot = len(self.defList) - if startAt < 0 or startAt >= tot: - return -1 - i = startAt - while i < tot: - linearObject = self.defList[i] - if linearObject.containsPt(pt): - return i - i = i + 1 - return -1 - - - #=============================================================================== - # closestPartWithContext - #=============================================================================== - def closestPartWithContext(self, pt, epsilon = 1.e-15): - """ - la funzione ritorna una lista con - ( - - - <"a sinistra di" se il punto é alla sinista della parte (< 0 -> sinistra, > 0 -> destra) - """ - minDistPoint = QgsPoint() - closestPartIndex = 0 - sqrDist = sys.float_info.max - leftOf = None - index = 0 - for linearObject in self.defList: - result = linearObject.closestPtWithContext(pt) - testdist = result[0] - if testdist < sqrDist: - closestPartIndex = index - sqrDist = testdist - minDistPoint = result[1] - leftOf = result[2] - - index = index + 1 - - return (sqrDist, minDistPoint, closestPartIndex, leftOf) - - - #=============================================================================== - # closestVertexWithContext - #=============================================================================== - def closestVertexWithContext(self, pt, epsilon = 1.e-15): - """ - la funzione ritorna il vertice più vicino a pt - """ - # la funzione ritorna una lista con (, - # - # - # <"a sinistra di">) - dummy = self.closestPartWithContext(pt, epsilon) - partAt = dummy[2] - linearObject = self.getLinearObjectAt(partAt) - # punto iniziale della parte - if qad_utils.getDistance(linearObject.getStartPt(), pt) < \ - qad_utils.getDistance(linearObject.getEndPt(), pt): - return partAt - else: # punto finale della parte - if partAt == self.qty() - 1: # se ultima parte - return 0 if self.isClosed() else partAt + 1 - else: - return partAt + 1 - - - #=============================================================================== - # breakOnPt - #=============================================================================== - def breakOnPt(self, point): - """ - la funzione spezza in due la lista di parti nel punto . - Ritorna una lista di due parti: la prima parte (che può essere - nulla se coincide con il punto iniziale) e la seconda parte (che può essere - nulla se coincide con il punto finale) - """ - dummy = self.closestPartWithContext(point) - nearestPt = dummy[1] - partAt = dummy[2] - if nearestPt is None or partAt is None: - return [None, None] - - partToCut = self.getLinearObjectAt(partAt) - cuttedParts = partToCut.breakOnPt(point) - - if ptNear(nearestPt, self.getStartPt()): - partList1 = None - partList2 = QadLinearObjectList(self) - return [partList1, partList2] - else: - partList1 = QadLinearObjectList() - for i in xrange(0, partAt, 1): - partList1.append(QadLinearObject(self.getLinearObjectAt(i))) - - if cuttedParts[0] is not None: - partList1.append(cuttedParts[0]) - - if ptNear(nearestPt, self.getEndPt()): - partList1 = QadLinearObjectList(self) - partList2 = None - return [partList1, partList2] - else: - partList2 = QadLinearObjectList() - - if cuttedParts[1] is not None: - partList2.append(cuttedParts[1]) - - for i in xrange(partAt + 1, self.qty(), 1): - partList2.append(QadLinearObject(self.getLinearObjectAt(i))) - - return [partList1, partList2] - - - #============================================================================ - # getIntPtNearestToStartPt - #============================================================================ - def getIntPtNearestToStartPt(self, crs, entitySet, edgeMode): - """ - La funzione cerca il punto di intersezione tra la polilinea e un gruppo di entità - che é più vicino al punto iniziale della polilinea. - La funzione riceve: - sistema di coordinate in cui é espressa la polilinea - gruppo di entità - La funzione restituisce: - punto di intersezione, numero della parte - """ - newPt = None - partNumber = -1 - distFromStart = 0 - trimmedLinearObject = QadLinearObject() - gTransformed = QgsGeometry() - - # scorro i segmenti - for i in xrange(0, self.qty(), 1): - minDist = sys.float_info.max - LinearObject = self.getLinearObjectAt(i) - - # per ciascun layer - for layerEntitySet in entitySet.layerEntitySetList: - layer = layerEntitySet.layer - - if layer.crs() != crs: - coordTransform = QgsCoordinateTransform(layer.crs(), crs) - trimmedLinearObject.set(LinearObject) - - # per ciascuna entità del layer - for featureId in layerEntitySet.featureIds: - f = getFeatureById(layer, featureId) - if f is None: - continue - # Trasformo la geometria nel sistema di coordinate del - gTransformed = f.geometry() - if layer.crs() != crs: - gTransformed.transform(coordTransform) - - intPt = getIntersectionPtTrimQgsGeometry(LinearObject, gTransformed, edgeMode) - if intPt is not None: - # cerco il punto di intersezione più vicino al punto iniziale - trimmedLinearObject.setEndPt(intPt) - if trimmedLinearObject.length() < minDist: - minDist = trimmedLinearObject.length() - newPt = intPt - partNumber = i - - if newPt is not None: - break - - distFromStart = distFromStart + LinearObject.length() - - if newPt is None: - return None, -1 - else: - return newPt, partNumber - - -#=============================================================================== -# FINE - QadLinearObjectList class -#=============================================================================== - - -#============================================================================ -# joinFeatureInVectorLayer -#============================================================================ -def joinFeatureInVectorLayer(featureIdToJoin, vectorLayer, tolerance2ApproxCurve, toleranceDist = TOLERANCE, \ - mode = 2): - """ - la funzione effettua il join (unione) di una polilinea con un gruppo di altre polilinee. - Non sono ammesse geometrie multiLineString. - Il layer deve essere in modifica (startEditing) e in una transazione (beginEditCommand) - La funzione riceve: - = un ID della feature da unire - = un QgsVectorLayer che deve contenere le feature da unire - (si usano gli indici spaziali del vettore x essere più veloci). - = distanza di tolleranza perché 2 punti siano considerati coincidenti - = tolleranza di approssimazione per le curve (usato se toleranceDist > 0) - = Imposta il metodo di unione (usato se toleranceDist > 0): - 1 -> Estendi; Consente di unire polilinee selezionate estendendo o tagliando - i segmenti nei punti finali più vicini. - 2 -> Aggiungi; Consente di unire polilinee selezionate aggiungendo un segmento - retto tra i punti finali più vicini. - 3 -> Entrambi;Consente di unire polilinee selezionate estendendo o tagliando, se possibile. - In caso contrario, consente di unire polilinee selezionate aggiungendo - un segmento retto tra i punti finali più vicini. - La funzione modifica il modificando la feature da unire e cancellando - quelle unite a featureIdToJoin . Ritorna la lista di features cancellate. - """ - featureToJoin = getFeatureById(vectorLayer, featureIdToJoin) - if featureToJoin is None: - return [] - - g = QgsGeometry(featureToJoin.geometry()) - linearObjectList = qad_utils.QadLinearObjectList() - linearObjectList.fromPolyline(g.asPolyline()) - - linearObjectListToJoinTo = qad_utils.QadLinearObjectList() - - deleteFeatures = [] - feature = QgsFeature() - - # Unisco usando il punto iniziale finché trovo feature da unire - ptToJoin = linearObjectList.getStartPt() - found = True - while found == True: - found = False - if ptToJoin is None: # test - fermati = True - # cerco le features nel punto iniziale usando un micro rettangolo secondo - selectRect = QgsRectangle(ptToJoin.x() - toleranceDist, ptToJoin.y() - toleranceDist, \ - ptToJoin.x() + toleranceDist, ptToJoin.y() + toleranceDist) - # cerco il punto più vicino al punto iniziale della polilinea - minDist = sys.float_info.max - # fetchAttributes, fetchGeometry, rectangle, useIntersect - for feature in vectorLayer.getFeatures(getFeatureRequest([], True, selectRect, True)): - if feature.id() != featureIdToJoin: # salto la feature da unire - linearObjectListToJoinTo.fromPolyline(feature.geometry().asPolyline()) - - if linearObjectList.join(linearObjectListToJoinTo, toleranceDist, mode) == True: - found = True - - deleteFeatures.append(QgsFeature(feature)) - if vectorLayer.deleteFeature(feature.id()) == False: - return [] - - ptToJoin = linearObjectList.getStartPt() - pts = linearObjectList.asPolyline(tolerance2ApproxCurve) - featureToJoin.setGeometry(QgsGeometry.fromPolyline(pts)) - if vectorLayer.updateFeature(featureToJoin) == False: - return [] - break - - # Unisco usando il punto finale finché trovo feature da unire - ptToJoin = linearObjectList.getEndPt() - found = True - while found == True: - found = False - # cerco le features nel punto finale usando un micro rettangolo secondo - selectRect = QgsRectangle(ptToJoin.x() - toleranceDist, ptToJoin.y() - toleranceDist, \ - ptToJoin.x() + toleranceDist, ptToJoin.y() + toleranceDist) - # fetchAttributes, fetchGeometry, rectangle, useIntersect - for feature in vectorLayer.getFeatures(getFeatureRequest([], True, selectRect, True)): - if feature.id() != featureIdToJoin: # salto la feature da unire - linearObjectListToJoinTo.fromPolyline(feature.geometry().asPolyline()) - - if linearObjectList.join(linearObjectListToJoinTo, toleranceDist, mode) == True: - found = True - - deleteFeatures.append(QgsFeature(feature)) - if vectorLayer.deleteFeature(feature.id()) == False: - return [] - - ptToJoin = linearObjectList.getEndPt() - pts = linearObjectList.asPolyline(tolerance2ApproxCurve) - featureToJoin.setGeometry(QgsGeometry.fromPolyline(pts)) - if vectorLayer.updateFeature(featureToJoin) == False: - return [] - break - - return deleteFeatures - - -#=============================================================================== -# joinEndPtsLinearParts -#=============================================================================== -def joinEndPtsLinearParts(part1, part2, mode): - """ - la funzione effettua il join (unione) tra 2 parti lineari considerando il punto finale di part1 - e il punto iniziale di part2. - La funzione riceve: - = prima parte lineare - = seconda parte parte lineare - = Imposta il metodo di unione: - 1 -> Estendi; Consente di unire polilinee selezionate estendendo o tagliando - i segmenti nei punti finali più vicini. - 2 -> Aggiungi; Consente di unire polilinee selezionate aggiungendo un segmento - retto tra i punti finali più vicini. - 3 -> Entrambi; Consente di unire polilinee selezionate estendendo o tagliando, se possibile. - In caso contrario, consente di unire polilinee selezionate aggiungendo - un segmento retto tra i punti finali più vicini. - La funzione restituisce una QadLinearObjectList che comprende: - part1 (eventualmente modificata nel punto finale) + - eventuale segmento + - part2 (eventualmente modificata nel punto finale) - oppure restituisce None se non é possibile l'unione delle parti - """ - linearObjectList = qad_utils.QadLinearObjectList() - endPt1 = part1.getEndPt() - endPt2 = part2.getEndPt() - - if ptNear(endPt1, endPt2): # le 2 parti sono già unite - linearObjectList.append(QadLinearObject(part1)) - linearObjectList.append(QadLinearObject(part2).reverse()) - return linearObjectList - - if mode == 1: # Estendi/Taglia - IntPtList = part1.getIntersectionPtsWithLinearObject(part2) - if len(IntPtList) > 0: # Taglia - linearObjectList.append(QadLinearObject(part1)) - linearObjectList.getLinearObjectAt(-1).setEndPt(IntPtList[0]) - linearObjectList.append(QadLinearObject(part2).reverse()) - linearObjectList.getLinearObjectAt(-1).setStartPt(IntPtList[0]) - return linearObjectList - else: # estendi - IntPtList = part1.getIntersectionPtsOnExtensionWithLinearObject(part2) - # considero solo i punti oltre l'inizio delle parti - for i in xrange(len(IntPtList) - 1, -1, -1): - if part1.getDistanceFromStart(IntPtList[i]) < 0 or \ - part2.getDistanceFromStart(IntPtList[i]) < 0: - del IntPtList[i] - - if len(IntPtList) > 0: - IntPt = IntPtList[0] - linearObjectList.append(QadLinearObject(part1)) - linearObjectList.getLinearObjectAt(-1).setEndPt(IntPtList[0]) - linearObjectList.append(QadLinearObject(part2.reverse())) - linearObjectList.getLinearObjectAt(-1).setStartPt(IntPtList[0]) - return linearObjectList - - if mode == 2 or mode == 3: # Aggiungi - linearObjectList.append(QadLinearObject(part1)) - linearObjectList.append([endPt1, endPt2]) - linearObjectList.append(QadLinearObject(part2).reverse()) - return linearObjectList - - return None - - -#=============================================================================== -# getCurveLinearObjects -#=============================================================================== -def getCurveLinearObjects(tanDirectionOnStartPt, prev, current, next): - """ - Data la direzione della tangente nel punto iniziale della parte corrente e - una successione di 3 parti lineari, - la funzione ritorna una lista di parti lineari - da sostituire alla parte per curvare la polilinea - """ - if current.isArc(): - return [QadLinearObject(current)] - - # se non ci sono né la parte precedente né la parte successiva - if prev is None and next is None: - return QadLinearObject(current) - - arc = QadArc() - if prev is None: # non c'é una parte precedente - if arc.fromStartSecondEndPts(current.getStartPt(), current.getEndPt(), next.getEndPt()) == False: - return [QadLinearObject(current)] - if ptNear(current.getStartPt(), arc.getStartPt()): # arco non é inverso - arc.setEndAngleByPt(current.getEndPt()) - return [QadLinearObject([arc, False])] - else: # arco é inverso - arc.setStartAngleByPt(current.getEndPt()) - return [QadLinearObject([arc, True])] - else: - t = prev.getTanDirectionOnEndPt() if tanDirectionOnStartPt is None else tanDirectionOnStartPt - - if next is None: # non c'é una parte successiva - if arc.fromStartEndPtsTan(current.getStartPt(), current.getEndPt(), t) == False: - return [QadLinearObject(current)] - if ptNear(current.getStartPt(), arc.getStartPt()): # arco non é inverso - return [QadLinearObject([arc, False])] - else: # arco é inverso - return [QadLinearObject([arc, True])] - else: # c'é una parte precedente e successiva - # calcolo il punto medio tra i 2 archi di raccordo -# if arc.fromStartEndPtsTan(current.getStartPt(), current.getEndPt(), \ -# prev.getTanDirectionOnEndPt()) == False: -# return [QadLinearObject(current)] -# tanDirectionOnEndPt = next.getTanDirectionOnStartPt() + math.pi -# arc2 = QadArc() -# if arc2.fromStartEndPtsTan(current.getEndPt(), current.getStartPt(), \ -# tanDirectionOnEndPt) == False: -# return [QadLinearObject(current)] - - if arc.fromStartSecondEndPts(prev.getStartPt(), current.getStartPt(), current.getEndPt()) == False: - return [QadLinearObject(current)] - if ptNear(prev.getStartPt(), arc.getStartPt()): # arco non é inverso - arc.setStartAngleByPt(current.getStartPt()) - else: # arco é inverso - arc.setEndAngleByPt(current.getStartPt()) - arc2 = QadArc() - if arc2.fromStartSecondEndPts(current.getStartPt(), current.getEndPt(), next.getEndPt()) == False: - return [QadLinearObject(current)] - if ptNear(current.getStartPt(), arc2.getStartPt()): # arco non é inverso - arc2.setEndAngleByPt(current.getEndPt()) - else: # arco é inverso - arc2.setStartAngleByPt(current.getEndPt()) - - midPt = getMiddlePoint(arc.getMiddlePt(), arc2.getMiddlePt()) - - if arc.fromStartEndPtsTan(current.getStartPt(), midPt, t) == False: - return [QadLinearObject(current)] - if ptNear(current.getStartPt(), arc.getStartPt()): # arco non é inverso - linearObject1 = QadLinearObject([arc, False]) - else: # arco é inverso - linearObject1 = QadLinearObject([arc, True]) - - if arc2.fromStartEndPtsTan(linearObject1.getEndPt(), current.getEndPt(), \ - linearObject1.getTanDirectionOnEndPt()) == False: - return [QadLinearObject(current)] - if ptNear(current.getEndPt(), arc2.getEndPt()): # arco non é inverso - linearObject2 = QadLinearObject([arc2, False]) - else: # arco é inverso - linearObject2 = QadLinearObject([arc2, True]) - - return [linearObject1, linearObject2] - - -#============================================================================ -# TrimExtend -#============================================================================ -def getFilletLinearObjectList(poly1, partAt1, pointAt1, poly2, partAt2, pointAt2, filletMode, radius, epsg): - """ - Date due polilinee, la parte e il punto in cui bisogna fare il raccordo tra le due - polilinee, la funzione ritorna una polilinea risultato del raccordo e due flag che - danno indicazioni su ciò che deve essere fatto alle polilinee originali: - (0=niente, 1=modificare, 2=cancellare) - modalità di raccordo; 1=Taglia-estendi, 2=Non taglia-estendi - raggio di raccordo - """ - circle1 = poly1.getCircle() - circle2 = poly2.getCircle() - - if circle1 is None: # se poly1 non era un cerchio - part = QadLinearObject(poly1.getLinearObjectAt(partAt1)) - if circle2 is None: # se poly2 non era un cerchio - nextPart = QadLinearObject(poly2.getLinearObjectAt(partAt2)) - if part.isSegment(): - if nextPart.isSegment(): # part e nextPart sono segmenti retti - if radius == 0: - res = bridgeTheGapBetweenLines(part, pointAt1, nextPart, pointAt2, radius, 0) - else: - res = bridgeTheGapBetweenLines(part, pointAt1, nextPart, pointAt2, radius, 1) - else: # part é un segmento retto, nextPart é un arco - res = bridgeTheGapBetweenArcLine(nextPart, pointAt2, part, pointAt1, radius, filletMode) - if res is not None: - dummy = res[0] # inverto il primo e il terzo elemento - res[0] = res[2] - res[2] = dummy - else: - if nextPart.isSegment(): # part é un arco, nextPart é un segmento retto - res = bridgeTheGapBetweenArcLine(part, pointAt1, nextPart, pointAt2, radius, filletMode) - else: # part é un arco, nextPart é un arco - res = bridgeTheGapBetweenArcs(part, pointAt1, nextPart, pointAt2, radius, filletMode) - else: - if part.isSegment(): # part é un segmento retto, poly2 é un cerchio - res = bridgeTheGapBetweenCircleLine(poly2, pointAt2, part, pointAt1, radius, filletMode) - if res is not None: - dummy = res[0] # inverto il primo e il terzo elemento - res[0] = res[2] - res[2] = dummy - else: # part é un arco, poly2 é un cerchio - res = bridgeTheGapBetweenArcCircle(part, pointAt1, poly2, pointAt2, radius, filletMode) - else: - if circle2 is None: # se poly2 non era un cerchio - nextPart = QadLinearObject(poly2.getLinearObjectAt(partAt2)) - if nextPart.isSegment(): # poly1 é un cerchio, nextPart é un segmento retto - res = bridgeTheGapBetweenCircleLine(poly1, pointAt1, nextPart, pointAt2, radius, filletMode) - else: # poly1 é un cerchio, nextPart é un arco - res = bridgeTheGapBetweenArcCircle(nextPart, pointAt2, poly1, pointAt1, radius, filletMode) - if res is not None: - dummy = res[0] # inverto il primo e il terzo elemento - res[0] = res[2] - res[2] = dummy - else: # poly1 e poly2 sono cerchi - res = bridgeTheGapBetweenCircles(poly1, pointAt1, poly2, pointAt2, radius) - - if res is None: # raccordo non possibile - return None - - filletLinearObjectList = QadLinearObjectList() - whatToDoPoly1 = 0 # 0=niente, 1=modificare, 2=cancellare - whatToDoPoly2 = 0 # 0=niente, 1=modificare, 2=cancellare - - if filletMode == 1 or radius == 0: # modalità di raccordo "Taglia-estendi" - if circle1 is None: # se poly1 non era un cerchio - if res[0] is not None: - part.set(res[0]) # modifico part - if circle2 is None: # se poly2 non era un cerchio - if res[2] is not None: - nextPart.set(res[2]) # modifico nextPart - - filletArc = res[1] # arco di raccordo - - if filletArc is None: - if circle1 is None and circle2 is None: - # se il punto iniziale di part tocca nextPart - if ptNear(part.getStartPt(), nextPart.getStartPt()) or \ - ptNear(part.getStartPt(), nextPart.getEndPt()): - whatToDoPoly1 = 1 # 1=modificare - # aggiungo part e le parti successive di part - filletLinearObjectList.append(part) - filletLinearObjectList.appendList(poly1, partAt1 + 1) - # se il punto finale di part tocca nextPart - elif ptNear(part.getEndPt(), nextPart.getStartPt()) or \ - ptNear(part.getEndPt(), nextPart.getEndPt()): - whatToDoPoly1 = 1 # 1=modificare - # aggiungo part e le parti precedenti di part - filletLinearObjectList.append(part) - filletLinearObjectList.appendList(poly1, 0, partAt1) - - # se il punto iniziale di nextPart tocca part - if ptNear(nextPart.getStartPt(), part.getStartPt()) or \ - ptNear(nextPart.getStartPt(), part.getEndPt()): - if whatToDoPoly1 == 1: # se la poly1 era da modificare (1=modificare) - whatToDoPoly2 = 2 # 2=cancellare - else: - whatToDoPoly2 = 1 # 1=modificare - # aggiungo nextPart e le parti successive di nextPart - filletLinearObjectList.append(nextPart) - filletLinearObjectList.appendList(poly2, partAt2 + 1) - # se il punto finale di nextPart tocca part - elif ptNear(nextPart.getEndPt(), part.getStartPt()) or \ - ptNear(nextPart.getEndPt(), part.getEndPt()): - if whatToDoPoly1 == 1: # se la poly1 era da modificare (1=modificare) - whatToDoPoly2 = 2 # 2=cancellare - else: - whatToDoPoly2 = 1 # 1=modificare - # aggiungo nextPart e le parti precedenti di nextPart - filletLinearObjectList.append(nextPart) - filletLinearObjectList.appendList(poly2, 0, partAt2) - else: # esiste un arco di raccordo - filletLinearObjectList.append(filletArc) - if circle1 is None: - # se l'arco di raccordo tocca il punto iniziale di part - if ptNear(filletArc.getStartPt(), part.getStartPt()) or \ - ptNear(filletArc.getEndPt(), part.getStartPt()): - whatToDoPoly1 = 1 # 1=modificare - # aggiungo part e le parti successive di part - filletLinearObjectList.append(part) - filletLinearObjectList.appendList(poly1, partAt1 + 1) - # se l'arco di raccordo tocca il punto finale di part - elif ptNear(filletArc.getStartPt(), part.getEndPt()) or \ - ptNear(filletArc.getEndPt(), part.getEndPt()): - whatToDoPoly1 = 1 # 1=modificare - # aggiungo part e le parti precedenti di part - filletLinearObjectList.append(part) - filletLinearObjectList.appendList(poly1, 0, partAt1) - - if circle2 is None: - # se l'arco di raccordo tocca il punto iniziale di nextPart - if ptNear(filletArc.getStartPt(), nextPart.getStartPt()) or \ - ptNear(filletArc.getEndPt(), nextPart.getStartPt()): - if whatToDoPoly1 == 1: # se la poly1 era da modificare (1=modificare) - whatToDoPoly2 = 2 # 2=cancellare - else: - whatToDoPoly2 = 1 # 1=modificare - # aggiungo nextPart e le parti successive di nextPart - filletLinearObjectList.append(nextPart) - filletLinearObjectList.appendList(poly2, partAt2 + 1) - # se l'arco di raccordo tocca il punto finale di nextPart - elif ptNear(filletArc.getStartPt(), nextPart.getEndPt()) or \ - ptNear(filletArc.getEndPt(), nextPart.getEndPt()): - if whatToDoPoly1 == 1: # se la poly1 era da modificare (1=modificare) - whatToDoPoly2 = 2 # 2=cancellare - else: - whatToDoPoly2 = 1 # 1=modificare - # aggiungo nextPart e le parti precedenti di nextPart - filletLinearObjectList.append(nextPart) - filletLinearObjectList.appendList(poly2, 0, partAt2) - - res = filletLinearObjectList.selfJoin(epsg) - if len(res) != 1: - return None - - return res[0], whatToDoPoly1, whatToDoPoly2 - - -#============================================================================ -# QadRawConfigParser class suppporting unicode -#============================================================================ -class QadRawConfigParser(ConfigParser.RawConfigParser): - - def __init__(self, defaults=None, dict_type=ConfigParser._default_dict, - allow_no_value=False): - ConfigParser.RawConfigParser.__init__(self, defaults, dict_type, allow_no_value) - - def get(self, section, option, default = None): - try: - return ConfigParser.RawConfigParser.get(self, section, option) - except: - return default - - def getint(self, section, option, default = None): - try: - return ConfigParser.RawConfigParser.getint(self, section, option) - except: - return default - - def getfloat(self, section, option, default = None): - try: - return ConfigParser.RawConfigParser.getfloat(self, section, option) - except: - return default - - def getboolean(self, section, option, default = None): - try: - return ConfigParser.RawConfigParser.getboolean(self, section, option) - except: - return default - - def write(self, fp): - """Fixed for Unicode output""" - if self._defaults: - fp.write("[%s]\n" % DEFAULTSECT) - for (key, value) in self._defaults.items(): - fp.write("%s = %s\n" % (key, unicode(value).replace('\n', '\n\t'))) - fp.write("\n") - for section in self._sections: - fp.write("[%s]\n" % section) - for (key, value) in self._sections[section].items(): - if key != "__name__": - fp.write("%s = %s\n" % (key, unicode(value).replace('\n','\n\t'))) - fp.write("\n") - - -#=============================================================================== -# Timer class for profiling -#=============================================================================== -class Timer(object): - # da usare: - # with qad_utils.Timer() as t: - # ... - # elasped = t.secs - def __init__(self, verbose=False): - self.verbose = verbose - - def __enter__(self): - self.start = time.time() - return self - - def __exit__(self, *args): - self.end = time.time() - self.secs = self.end - self.start - self.msecs = self.secs * 1000 # millisecs - if self.verbose: - print 'elapsed time: %f ms' % self.msecs +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + funzioni varie di utilità + + ------------------- + begin : 2013-05-22 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + +from qgis.PyQt.QtCore import QVariant, QDir, QMetaType +from qgis.PyQt.QtGui import QCursor, QPixmap, QColor, QFont, QPalette +from qgis.PyQt.QtWidgets import QToolTip, QMessageBox, QApplication +from qgis.core import * +import qgis.utils + + +import os +import math +import sys +from gettext import find +import configparser +import time +import uuid, re + +from .qad_variables import QadVariables +from .qad_msg import QadMsg + + +# Modulo che gestisce varie funzionalità di QAD + + +def getMacAddress(): + return ':'.join(re.findall('..', '%012x' % uuid.getnode())).upper() + + +def criptPlainText(strValue): + mytable = str.maketrans('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz -./0123456789:;<=>?@"', \ + 'NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm9:;<=>?@" -./012345678') + return strValue.translate(mytable) + + +def decriptPlainText(strValue): + mytable = str.maketrans('NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm9:;<=>?@" -./012345678', \ + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz -./0123456789:;<=>?@"') + return strValue.translate(mytable) + + +# =============================================================================== +# FUNZIONI GENERICHE PER LE OPZIONI DEI COMANDI - INIZIO +# =============================================================================== + + +def extractUpperCaseSubstr(str): + # estraggo la parte maiuscola della stringa + upperPart = "" + for letter in str: + if letter.isupper(): + upperPart = upperPart + letter + elif len(upperPart) > 0: + break + return upperPart + + +def evaluateCmdKeyWords(cmd, keyWordList): + # Riceve un comando e la lista delle parole chiave delle opzioni di un comando + # La funzione ritorna la keyword del comando seguito da un eventuale messaggio di errore se la keyword = None + + # The required portion of the keyword is specified in uppercase characters, + # and the remainder of the keyword is specified in lowercase characters. + # The uppercase abbreviation can be anywhere in the keyword + if cmd == "": # se cmd = "" la funzione find ritorna 0 (no comment) + return None, None + upperCmd = cmd.upper() + selectedKeyWords = [] + for keyWord in keyWordList: + # estraggo la parte maiuscola della parola chiave + upperPart = extractUpperCaseSubstr(keyWord) + + if upperPart.find(upperCmd) == 0: # se la parte maiuscola della parola chiave inizia per upperCmd + if upperPart == upperCmd: # Se uguale + return keyWord, None + else: + selectedKeyWords.append(keyWord) + elif keyWord.upper().find(upperCmd) == 0: # se la parola chiave inizia per cmd (insensitive) + if keyWord.upper() == upperCmd: # Se uguale + return keyWord, None + else: + selectedKeyWords.append(keyWord) + + selectedKeyWordsLen = len(selectedKeyWords) + if selectedKeyWordsLen == 0: + return None, None + elif selectedKeyWordsLen == 1: + return selectedKeyWords[0], None + else: + Msg = QadMsg.translate("QAD", "\nAmbiguous answer: specify with more clarity...") + ambiguousMsg = "" + for keyWord in selectedKeyWords: + if ambiguousMsg == "": + ambiguousMsg = keyWord + else: + ambiguousMsg = ambiguousMsg + QadMsg.translate("QAD", " or ") + keyWord + + Msg = Msg + "\n" + ambiguousMsg + QadMsg.translate("QAD", " ?\n") + + return None, Msg + + +# =============================================================================== +# FUNZIONI GENERICHE PER LE OPZIONI DEI COMANDI - FINE +# FUNZIONI GENERICHE PER I WIDGET - INIZIO +# =============================================================================== + + +# =============================================================================== +# setMapCanvasToolTip +# =============================================================================== +# visualizza il testo della tooltip del mapCanvas con l'aspetto determinato da DYNTOOLTIPS +def setMapCanvasToolTip(msg): + canvas = qgis.utils.iface.mapCanvas() + pt = canvas.mapToGlobal(canvas.mouseLastXY()) + + if QadVariables.get(QadMsg.translate("Environment variables", "DYNTOOLTIPS")) == 1: + font_size = 8 + QadVariables.get(QadMsg.translate("Environment variables", "TOOLTIPSIZE")) + + #opacity = 100 - QadVariables.get(QadMsg.translate("Environment variables", "TOOLTIPTRANSPARENCY")) + #fc.setAlphaF(opacity/100.0) # non va + + fColor = QColor(QadVariables.get(QadMsg.translate("Environment variables", "DYNEDITFORECOLOR"))) + bColor = QColor(QadVariables.get(QadMsg.translate("Environment variables", "DYNEDITBACKCOLOR"))) + else: + font_size = QFont().pointSize() + p = QPalette() + fColor = p.color(QPalette.Inactive, QPalette.ToolTipText) + bColor = p.color(QPalette.Inactive, QPalette.ToolTipBase) + + toolTipFont = QToolTip.font() + if toolTipFont.pointSize() != font_size: + toolTipFont.setPointSize(font_size) + QToolTip.setFont(toolTipFont) + + toolTipPalette = QToolTip.palette() + if toolTipPalette.color(QPalette.Inactive, QPalette.ToolTipText) != fColor or \ + toolTipPalette.color(QPalette.Inactive, QPalette.ToolTipBase) != bColor: + toolTipPalette.setColor(QPalette.Inactive, QPalette.ToolTipText, fColor) + toolTipPalette.setColor(QPalette.Inactive, QPalette.ToolTipBase, bColor) + QToolTip.setPalette(toolTipPalette) + + QToolTip.showText(pt, msg) + + return + + +# =============================================================================== +# floatLineEditWidgetValidation +# =============================================================================== +# controlla che il valore di un widget di tipo line edit soddisfi l'intervallo ammesso per la variabile di ambiente +def intLineEditWidgetValidation(widget, var, msg): + err = False + string = widget.text() + if str2int(string) is None: + err = True + else: + if var.minNum is not None: + if str2int(string) < var.minNum: + err = True + if var.maxNum is not None: + if str2int(string) > var.maxNum: + err = True + + if err: + msg = msg + QadMsg.translate("QAD", ": enter a number") + if var.minNum is not None: + msg = msg + QadMsg.translate("QAD", " >= {0}").format(str(var.minNum)) + if var.maxNum is not None: + if var.minNum is not None: + msg = msg + QadMsg.translate("QAD", " and") + msg = msg + QadMsg.translate("QAD", " <= {0}").format(str(var.maxNum)) + msg = msg + "." + QMessageBox.critical(None, QadMsg.getQADTitle(), msg) + widget.setFocus() + widget.selectAll() + return False + return True + + +# =============================================================================== +# floatLineEditWidgetValidation +# =============================================================================== +# controlla che il valore di un widget di tipo line edit soddisfi l'intervallo ammesso per la variabile di ambiente +def floatLineEditWidgetValidation(widget, var, msg): + err = False + string = widget.text() + if str2float(string) is None: + err = True + else: + if var.minNum is not None: + if str2float(string) < var.minNum: + err = True + if var.maxNum is not None: + if str2float(string) > var.maxNum: + err = True + + if err: + msg = msg + QadMsg.translate("QAD", ": enter a number") + if var.minNum is not None: + minValMsg = msg + QadMsg.translate("QAD", " > {0}").format(str(var.minNum)) + else: + minValMsg = "" + if var.maxNum is not None: + if len(minValMsg) > 0: + msg = msg + QadMsg.translate("QAD", " and") + msg = msg + QadMsg.translate("QAD", " < {0}").format(str(var.maxNum)) + msg = msg + "." + QMessageBox.critical(None, QadMsg.getQADTitle(), msg) + widget.setFocus() + widget.selectAll() + return False + return True + + +# =============================================================================== +# FUNZIONI GENERICHE PER I WIDGET - FINE +# =============================================================================== + + +# =============================================================================== +# isNumericField +# =============================================================================== +def isNumericField(field): + """ + La funzione verifica che il campo di tipo QgsField sia numerico + """ + fldType = field.type() + if fldType == QMetaType.Double or fldType == QMetaType.LongLong or fldType == QMetaType.Int or \ + fldType == QMetaType.ULongLong or fldType == QMetaType.UInt: + return True + else: + return False + + +# =============================================================================== +# checkUniqueNewName +# =============================================================================== +def checkUniqueNewName(newName, nameList, prefix = None, suffix = None, caseSensitive = True): + """ + La funzione verifica che il nuovo nome non esistà già nella lista . + Se nella lista dovesse già esistere allora aggiunge un prefisso (se <> None) o un suffisso (se <> None) + finchè il nome non è più presnete nella lista + """ + ok = False + result = newName + while ok == False: + ok = True + for name in nameList: + if caseSensitive == True: + if name == result: + ok = False + break + else: + if name.upper() == result.upper(): + ok = False + break + + if ok == True: + return result + if prefix is not None: + result = prefix + result + else: + if suffix is not None: + result = result + suffix + + return None + +# =============================================================================== +# wildCard2regularExpr +# =============================================================================== +def wildCard2regularExpr(wildCard, ignoreCase = True): + """ + Ritorna la conversione di una stringa con wildcards (es. "gas*") + in forma di regular expression (es. "[g][a][s].*") + """ + # ? -> . + # * -> .* + # altri caratteri -> [carattere] + regularExpr = "" + for ch in wildCard: + if ch == "?": + regularExpr = regularExpr + "." + elif ch == "*": + regularExpr = regularExpr + ".*" + else: + if ignoreCase: + regularExpr = regularExpr + "[" + ch.upper() + ch.lower() + "]" + else: + regularExpr = regularExpr + "[" + ch + "]" + + return regularExpr + + +# =============================================================================== +# str2float +# =============================================================================== +def str2float(s): + """ + Ritorna la conversione di una stringa in numero reale + """ + try: + n = float(s) + return n + except ValueError: + return None + + +# =============================================================================== +# str2long +# =============================================================================== +def str2long(s): + """ + Ritorna la conversione di una stringa in numero lungo + """ + try: + n = long(s) + return n + except ValueError: + return None + + +# =============================================================================== +# str2int +# =============================================================================== +def str2int(s): + """ + Ritorna la conversione di una stringa in numero intero + """ + try: + n = int(s) + return n + except ValueError: + return None + + +# =============================================================================== +# str2bool +# =============================================================================== +def str2bool(s): + """ + Ritorna la conversione di una stringa in bool + """ + try: + upperS = s.upper() + # 16 = "N", 17 = "NO" + # "F" "FALSO" + if upperS == "0" or \ + upperS == QadMsg.translate("QAD", "N") or \ + upperS == QadMsg.translate("QAD", "NO") or \ + upperS == QadMsg.translate("QAD", "F") or \ + upperS == QadMsg.translate("QAD", "FALSE") or \ + upperS == "FALSE": + return False + else: + return True + except ValueError: + return None + + +# =============================================================================== +# str2QgsPoint +# =============================================================================== +def str2QgsPoint(s, lastPoint = None, currenPoint = None, oneNumberAllowed = True): + """ + Ritorna la conversione di una stringa in punto QgsPointXY + se = False significa che s non può essere un solo numero + che rappresenterebbe la distanza dall'ultimo punto con angolo in base al punto corrente + (questo viene vietato quando si vuole accettare un numero o un punto) + lastPoint viene usato solo per le espressioni tipo @10<45 (dall'ultimo punto, lunghezza 10, angolo 45 gradi) + o @ (dall'ultimo punto) + o @10,20 (dall'ultimo punto, + 10 per la X e + 20 per la Y) + o 100 (dall'ultimo punto, distanza 100, angolo in base al punto corrente) + """ + expression = s.strip() # senza spazi iniziali e finali + if len(expression) == 0: + return None + + if expression[0] == "@": # coordinate relative a lastpoint + if lastPoint is None: + return None + + if len(expression) == 1: + return lastPoint + + expression = expression[1:] # scarto il primo carattere "@" + coords = expression.split(",") + if len(coords) == 2: + OffSetX = str2float(coords[0].strip()) + OffSetY = str2float(coords[1].strip()) + if (OffSetX is None) or (OffSetY is None): + return None + return QgsPointXY(lastPoint.x() + OffSetX, lastPoint.y() + OffSetY) + else: + if len(coords) != 1: + return None + # verifico se si sta usando la coordinata polare + expression = coords[0].strip() + values = expression.split("<") + if len(values) != 2: + return None + dist = str2float(values[0].strip()) + angle = str2float(values[1].strip()) + if (dist is None) or (angle is None): + return None + coords = getPolarPointByPtAngle(lastPoint, math.radians(angle), dist) + return QgsPointXY(coords[0], coords[1]) + else: + # verifico se è specificato un CRS + CRS, newExpr = strFindCRS(expression) + if CRS is not None: + if CRS.isGeographic(): + pt = strLatLon2QgsPoint(newExpr) + else: + coords = newExpr.split(",") + if len(coords) != 2: + return None + x = str2float(coords[0].strip()) + y = str2float(coords[1].strip()) + if (x is None) or (y is None): + return None + pt = QgsPointXY(x, y) + + if pt is not None: + destCRS = qgis.utils.iface.mapCanvas().mapSettings().destinationCrs() # CRS corrente + return QgsCoordinateTransform(CRS, destCRS, QgsProject.instance()).transform(pt) # trasformo le coord + + + coords = expression.split(",") + if len(coords) == 2: # coordinate assolute + x = str2float(coords[0].strip()) + y = str2float(coords[1].strip()) + if (x is None) or (y is None): + return None + return QgsPointXY(x, y) + else: + if oneNumberAllowed == False: # vietato che la stringa sia un solo numero + return None + + dist = str2float(expression) + + if (dist is None) or (lastPoint is None) or (currenPoint is None): + return None + + angle = getAngleBy2Pts(lastPoint, currenPoint) + coords = getPolarPointByPtAngle(lastPoint, angle, dist) + return QgsPointXY(coords[0], coords[1]) + + +# =============================================================================== +# pointToStringFmt +# =============================================================================== +def pointToStringFmt(pt): + """ + Ritorna la conversione di un punto QgsPointXY in stringa formattata + """ + return numToStringFmt(pt.x()) + "," + numToStringFmt(pt.y()) + + +# =============================================================================== +# numToStringFmt +# =============================================================================== +def numToStringFmt(n, textDecimals = 4, textDecimalSep = '.', \ + textSuppressLeadingZeros = False, textDecimalZerosSuppression = True, + textPrefix = "", textSuffix = ""): + """ + Restituisce la conversione di un numero (int o float) in stringa formattata + """ + strIntPart, strDecPart = getStrIntDecParts(round(n, textDecimals)) # numero di decimali + + if strIntPart == "0" and textSuppressLeadingZeros == True: # per sopprimere o meno gli zero all'inizio del testo + strIntPart = "" + + for i in range(0, textDecimals - len(strDecPart), 1): # aggiunge "0" per arrivare al numero di decimali + strDecPart = strDecPart + "0" + + if textDecimalZerosSuppression == True: # per sopprimere gli zero finali nei decimali + strDecPart = strDecPart.rstrip("0") + + formattedText = "-" if n < 0 else "" # segno + formattedText = formattedText + strIntPart # parte intera + if len(strDecPart) > 0: # parte decimale + formattedText = formattedText + textDecimalSep + strDecPart # Separatore dei decimali + # aggiungo prefisso e suffisso per il testo della quota + return textPrefix + formattedText + textSuffix + + +# =============================================================================== +# strLatLon2QgsPoint +# =============================================================================== +def strFindCRS(s): + """ + Cerca il sistema di coordinate in una stringa indicante un punto (usa authid). + Il sistema di coordinate va espresso in qualsiasi punto della stringa e deve essere + racchiuso tra parentesi tonde (es "111,222 (EPSG:3003)") + Ritorna il SR e la stringa depurata del SR (es "111,222") + """ + initial = s.find("(") + if initial == -1: + return None, s + final = s.find(")") + if initial > final: + return None, s + authId = s[initial+1:final] + authId = authId.strip() # senza spazi iniziali e finali + return QgsCoordinateReferenceSystem(authId), s.replace(s[initial:final+1], "") + + +# =============================================================================== +# strLatLon2QgsPoint +# =============================================================================== +def strLatLon2QgsPoint(s): + """ + Ritorna la conversione di una stringa contenente una coordinata in latitudine longitudine + in punto QgsPointXY. + + Sono supportati i seguenti formati: + DDD gradi decimali (49.11675S o S49.11675 o 49.11675 S o S 49.11675 o -49.1167) + DMS gradi minuti secondi (49 7 20.06) + DMM gradi minuti con secondi decimali (49 7.0055) + + Sintassi latitudine longitudine: + Il separatore può essere uno spazio, puoi anche usare ' per i minuti e " per i secondi (47 7'20.06") + La notazione di direzione è N, S, E, W maiuscolo o minuscolo prima o dopo la coordinata + ("N 37 24 23.3" o "N37 24 23.3" o "37 24 23.3 N" o "37 24 23.3N") + Puoi usare anche le coordinate negative per l'ovest e il sud. + + La prima coordinata viene interpretata come latitudine a meno che specifichi una lettera di direzione (E o W) + ("122 05 08.40 W 37 25 19.07 N") + Puoi usare uno spazio, una virgola o una barra per delimitare le coppie di valori + ("37.7 N 122.2 W" o "37.7 N,122.2 W" o "37.7 N/122.2 W") + """ + expression = s.strip() # senza spazi iniziali e finali + if len(expression) == 0: + return None + + numbers = [] + directions = [] + word = "" + for ch in s: + if ch.isnumeric() or ch == "." or ch == "-": + word += ch + else: + if len(word) > 0: + n = str2float(word) + if n is None: + return None + numbers.append(n) + word = "" + if ch == "N" or ch == "n" or ch == "S" or ch == "s" or ch == "E" or ch == "e" or ch == "W" or ch == "w": + directions.append(ch.upper()) + word = "" + + directions_len = len(directions) + if directions_len != 0 and directions_len != 2: + return None + + numbers_len = len(numbers) + if numbers_len == 2: # DDD + lat = numbers[0] + lon = numbers[1] + elif numbers_len == 4: # DMM + degrees = numbers[0] + minutes = numbers[1] + lat = degrees + minutes / 60 + degrees = numbers[2] + minutes = numbers[3] + lon = degrees + minutes / 60 + elif numbers_len == 6: # DMS + degrees = numbers[0] + minutes = numbers[1] + seconds = numbers[2] + lat = degrees + minutes / 60 + seconds / 3600 + degrees = numbers[3] + minutes = numbers[4] + seconds = numbers[5] + lon = degrees + minutes / 60 + seconds / 3600 + else: + return None + + if directions_len == 2: + if lat < 0 or lon < 0: + return None + if directions[0] == "N" or directions[0] == "S": # latitude first + if directions[0] == "S": + lat = -lat + elif directions[0] == "E" or directions[0] == "W": # longitude first + dummy = lat + lat = lon if directions[0] == "E" else -lon + lon = dummy if directions[1] == "S" else -value2 + else: + return None + + return QgsPointXY(lon, lat) + else: # latitude first + return QgsPointXY(lon, lat) + + +# =============================================================================== +# strip +# =============================================================================== +def strip(s, stripList): + """ + Rimuove dalla stringa tutte le stringhe nella lista che sono + all'inizio e anche alla fine della stringa + """ + for item in stripList: + s = s.strip(item) # rimuovo prima e dopo + return s + + +# =============================================================================== +# findFile +# =============================================================================== +def findFile(fileName): + """ + Cerca il file indicato usando i percorsi indicati dalla variabile "SUPPORTPATH" + più il percorso locale di QAD. Ritorna il percorso del file in caso di successo + oppure "" in caso di file non trovato + """ + path = QadVariables.get(QadMsg.translate("Environment variables", "SUPPORTPATH")) + if len(path) > 0: + path += ";" + path += QgsApplication.qgisSettingsDirPath() + "python/plugins/qad/" + # lista di directory separate da ";" + dirList = path.strip().split(";") + for _dir in dirList: + _dir = QDir.cleanPath(_dir) + if _dir != "": + if _dir.endswith("/") == False: + _dir = _dir + "/" + _dir = _dir + fileName + + if os.path.exists(_dir): + return _dir + + return "" + + return s + + +# =============================================================================== +# getQADPath +# =============================================================================== +def getQADPath(): + """ + Restituisce la path di installazione di QAD + """ + return os.path.dirname(os.path.realpath(__file__)) + + +# =============================================================================== +# toRadians +# =============================================================================== +def toRadians(angle): + """ + Converte da gradi a radianti + """ + return math.radians(angle) + + +# =============================================================================== +# toDegrees +# =============================================================================== +def toDegrees(angle): + """ + Converte da radianti a gradi + """ + return math.degrees(angle) + + +# =============================================================================== +# normalizeAngle +# =============================================================================== +def normalizeAngle(angle, norm = math.pi * 2): + """ + Normalizza un angolo a da [0 - 2pi] o da [0 - pi]. + Così, ad esempio, se un angolo é più grande di 2pi viene ridotto all'angolo giusto + (il raffronto in gradi sarebbe da 380 a 20 gradi) o se é negativo diventa positivo + (il raffronto in gradi sarebbe da -90 a 270 gradi) + """ + if angle == 0: + return 0 + if angle > 0: + return angle % norm + else: + return norm - ((-angle) % norm) + + +# =============================================================================== +# getStrIntDecParts +# =============================================================================== +def getStrIntDecParts(n): + """ + Restituisce due stringhe rappresentanti la parte intera senza segno e la parte decimale di un numero + """ + if type(n) == int or type(n) == long or type(n) == float: + nStr = str(n) + if "." in nStr: + parts = nStr.split(".") + return str(abs(int(parts[0]))), parts[1] + else: + return nStr, "" + else: + return None + + +# =============================================================================== +# distMapToLayerCoordinates +# =============================================================================== +def distMapToLayerCoordinates(dist, canvas, layer): + # trovo il punto centrale dello schermo + boundBox = canvas.extent() + x = (boundBox.xMinimum() + boundBox.xMaximum()) / 2 + y = (boundBox.yMinimum() + boundBox.yMaximum()) / 2 + pt1 = QgsPointXY(x, y) + pt2 = QgsPointXY(x + dist, y) + transformedPt1 = canvas.mapSettings().mapToLayerCoordinates(layer, pt1) + transformedPt2 = canvas.mapSettings().mapToLayerCoordinates(layer, pt2) + return getDistance(transformedPt1, transformedPt2) + + +# =============================================================================== +# filterFeaturesByType +# =============================================================================== +def filterFeaturesByType(features, filterByGeomType): + """ + Riceve una lista di features e la tipologia di geometria che deve essere filtrata. + La funzione modifica la lista depurandola dalle geometrie di tipo diverso + da . + Restituisce 3 liste rispettivamente di punti, linee e poligoni. + La lista del tipo indicato dal parametro sarà vuota, le altre + due liste conterranno geometrie. + """ + resultPoint = [] + resultLine = [] + resultPolygon = [] + + for i in range(len(features) - 1, -1, -1): + f = features[i] + g = f.geometry() + geomType = g.type() + if geomType != filterByGeomType: + if geomType == QgsWkbTypes.PointGeometry: + resultPoint.append(QgsGeometry(g)) + + elif geomType == QgsWkbTypes.LineGeometry: + resultLine.append(QgsGeometry(g)) + + elif geomType == QgsWkbTypes.PolygonGeometry: + resultPolygon.append(QgsGeometry(g)) + + del features[i] + + return resultPoint, resultLine, resultPolygon + + +# =============================================================================== +# filterGeomsByType +# =============================================================================== +def filterGeomsByType(geoms, filterByGeomType): + """ + Riceve una lista di geometrie e la tipologia di geometria che deve essere filtrata. + La funzine modifica la lista depurandola dalle geometrie di tipo diverso + da . + Restituisce 3 liste rispettivamente di punti, linee e poligoni. + La lista del tipo indicato dal parametro sarà vuota, le altre + due liste conterranno geometrie. + """ + resultPoint = [] + resultLine = [] + resultPolygon = [] + + for i in range(len(geoms) - 1, -1, -1): + g = geoms[i] + geomType = g.type() + if geomType != filterByGeomType: + if geomType == QgsWkbTypes.PointGeometry: + resultPoint.append(QgsGeometry(g)) + + elif geomType == QgsWkbTypes.LineGeometry: + resultLine.append(QgsGeometry(g)) + + elif geomType == QgsWkbTypes.PolygonGeometry: + resultPolygon.append(QgsGeometry(g)) + + del geoms[i] + + return resultPoint, resultLine, resultPolygon + + +# =============================================================================== +# getEntSelCursor +# =============================================================================== +def getEntSelCursor(): + """ + Ritorna l'immagine del cursore per la selezione di un'entità + """ + + size = 1 + QadVariables.get(QadMsg.translate("Environment variables", "PICKBOX")) * 2 + # + row = str(size) + " " + str(size) + " 2 1" + xpm = [row] + # + xpm.append(" c None") + xpm.append("+ c " + QadVariables.get(QadMsg.translate("Environment variables", "PICKBOXCOLOR"))) + # + # es . "+++++", + # es . "+ +", + # es . "+ +", + # es . "+ +", + # es . "+++++", + xpm.append("+" * size) + if size > 1: + row = "+" + " " * (size - 2) + "+" + for i in range(size - 2): # da 0 + xpm.append(row) + xpm.append("+" * size) + + return QCursor(QPixmap(xpm)) + + +def getGetPointCursor(): + """ + Ritorna l'immagine del cursore per la selezione di un punto + """ + pickBox = QadVariables.get(QadMsg.translate("Environment variables", "CURSORSIZE")) + size = 1 + pickBox * 2 + # + row = str(size) + " " + str(size) + " 2 1" + xpm = [row] + # + xpm.append(" c None") + xpm.append("+ c " + QadVariables.get(QadMsg.translate("Environment variables", "PICKBOXCOLOR"))) + # + # es . " + ", + # es . " + ", + # es . "+++++", + # es . " + ", + # es . " + ", + row = (" " * pickBox) + "+" + (" " * pickBox) + xpm.append(row) + if size > 1: + for i in range(pickBox - 1): # da 0 + xpm.append(row) + xpm.append("+" * (size)) + for i in range(pickBox - 1): # da 0 + xpm.append(row) + + return QCursor(QPixmap(xpm)) + + +# =============================================================================== +# getFeatureRequest +# =============================================================================== +def getFeatureRequest(fetchAttributes = [], fetchGeometry = True, \ + rect = None, useIntersect = False): + # PER ORA NON VIENE USATO PERCHE' NON SO FARE IL CAST in QgsFeatureRequest.Flags + # restituisce un oggetto QgsFeatureRequest per interrogare un layer + # It can get 4 arguments, all of them are optional: + # fetchAttributes: List of attributes which should be fetched. + # None = disable fetching attributes, Empty list means that all attributes are used. + # default: empty list + # fetchGeometry: Whether geometry of the feature should be fetched. Default: True + # rect: Spatial filter by rectangle. + # None = nessuna ricerca spaziale, empty rect means (QgsRectangle()), all features are fetched. + # Default: none + # useIntersect: When using spatial filter, this argument says whether accurate test for intersection + # should be done or whether test on bounding box suffices. + # This is needed e.g. for feature identification or selection. Default: False + + request = QgsFeatureRequest() + + #flag = QgsFeatureRequest.NoFlags + +# if fetchGeometry == False: +# flag = flag | QgsFeatureRequest.NoGeometry + + if rect is not None: + r = QgsRectangle(rect) + + # non serve più +# # Se il rettangolo é schiacciato in verticale o in orizzontale +# # risulta una linea e la funzione fa casino, allora in questo caso lo allargo un pochino +# if doubleNear(r.xMinimum(), r.xMaximum(), 1.e-6): +# r.setXMaximum(r.xMaximum() + 1.e-6) +# r.setXMinimum(r.xMinimum() - 1.e-6) +# if doubleNear(r.yMinimum(), r.yMaximum(), 1.e-6): +# r.setYMaximum(r.yMaximum() + 1.e-6) +# r.setYMinimum(r.yMinimum() - 1.e-6) + + request.setFilterRect(r) + + if useIntersect == True: + request.setFlags(QgsFeatureRequest.ExactIntersect) + + if fetchAttributes is None: + request.setSubsetOfAttributes([]) + else: + if len(fetchAttributes) > 0: + request.setSubsetOfAttributes(fetchAttributes) + + return request + + +# =============================================================================== +# getVisibleVectorLayers +# =============================================================================== +def getVisibleVectorLayers(canvas): + # Tutti i layer vettoriali visibili + layers = canvas.layers() + for i in range(len(layers) - 1, -1, -1): + # se il layer non è vettoriale o non è visibile a questa scala + if layers[i].type() != QgsMapLayer.VectorLayer or \ + layers[i].hasScaleBasedVisibility() and \ + (canvas.mapSettings().scale() > layers[i].minimumScale() or canvas.mapSettings().scale() < layers[i].maximumScale()): + del layers[i] + return layers + + +# =============================================================================== +# getSnappableVectorLayers +# =============================================================================== +def getSnappableVectorLayers(canvas): + # make QAD honor QGIS's snap settings (ALL LAYERS, ACTIVE LAYER, ADVANCED). + # proposed by Oliver Dalang + enabled = canvas.snappingUtils().config().enabled() + mode = canvas.snappingUtils().config().mode() + + if enabled and mode == QgsSnappingConfig.ActiveLayer: + if qgis.utils.iface.activeLayer() is None: + layers = [] + else: + layers = [qgis.utils.iface.activeLayer()] + elif enabled and mode == QgsSnappingConfig.AdvancedConfiguration: + layers = list(cfg.layer for cfg in canvas.snappingUtils().layers()) + else: # mode == QgsSnappingConfig.AllLayers: + layers = canvas.layers() + + # Solo i layer vettoriali visibili + for i in range(len(layers) - 1, -1, -1): + # se il layer non è vettoriale o non è visibile a questa scala + if layers[i].type() != QgsMapLayer.VectorLayer or \ + layers[i].hasScaleBasedVisibility() and \ + (canvas.mapSettings().scale() > layers[i].minimumScale() or canvas.mapSettings().scale() < layers[i].maximumScale()): + del layers[i] + return layers + + +# =============================================================================== +# getEntSel +# =============================================================================== +def getEntSel(point, mQgsMapTool, boxSize, \ + layersToCheck = None, checkPointLayer = True, checkLineLayer = True, checkPolygonLayer = True, + onlyBoundary = True, onlyEditableLayers = False, \ + firstLayerToCheck = None, layerCacheGeomsDict = None, returnFeatureCached = False): + """ + dato un punto (in screen coordinates) e un QgsMapTool, + la funzione cerca la prima entità dentro il quadrato + di dimensioni (in pixel) centrato sul punto + layersToCheck = opzionale, lista dei layer in cui cercare + checkPointLayer = opzionale, considera i layer di tipo punto + checkLineLayer = opzionale, considera i layer di tipo linea + checkPolygonLayer = opzionale, considera i layer di tipo poligono + onlyBoundary = serve per considerare solo il bordo dei poligoni o anche il loro interno + onlyEditableLayers = per cercare solo nei layer modificabili + firstLayerToCheck = per ottimizzare la ricerca, primo layer da controllare + layerCacheGeomsDict = per ottimizzare la ricerca, è una chache delle geometrie dei layer + returnFeatureCached = per ottimizzare, ritorna la featura letta dalla cache (quando interessa solo la geometria) + + Restituisce una lista composta da una QgsFeature e il suo layer e il punto di selezione + in caso di successo altrimenti None + """ + + if checkPointLayer == False and checkLineLayer == False and checkPolygonLayer == False: + return None + + #QApplication.setOverrideCursor(Qt.WaitCursor) + + if layersToCheck is None: + # Tutti i layer vettoriali visibili + _layers = getVisibleVectorLayers(mQgsMapTool.canvas) # Tutti i layer vettoriali visibili + else: + # solo la lista passata come parametro + _layers = layersToCheck + + # se il processo può essere ottimizzato con il primo layer in cui cercare + if firstLayerToCheck is not None: + # considero solo se layer vettoriale visibile che è filtrato per tipo + if firstLayerToCheck.type() == QgsMapLayer.VectorLayer and \ + (onlyEditableLayers == False or firstLayerToCheck.isEditable()) and \ + (firstLayerToCheck.hasScaleBasedVisibility() == False or \ + (mQgsMapTool.canvas.mapSettings().scale() <= firstLayerToCheck.minimumScale() and mQgsMapTool.canvas.mapSettings().scale() >= firstLayerToCheck.maximumScale())) and \ + ((firstLayerToCheck.geometryType() == QgsWkbTypes.PointGeometry and checkPointLayer == True) or \ + (firstLayerToCheck.geometryType() == QgsWkbTypes.LineGeometry and checkLineLayer == True) or \ + (firstLayerToCheck.geometryType() == QgsWkbTypes.PolygonGeometry and checkPolygonLayer == True)): + # restituisce feature, point + res = getEntSelOnLayer(point, mQgsMapTool, boxSize, firstLayerToCheck, onlyBoundary, layerCacheGeomsDict, returnFeatureCached) + if res is not None: + return res[0], firstLayerToCheck, res[1] + + for layer in _layers: # ciclo sui layer + # se il processo può essere ottimizzato con il primo layer in cui cercare lo salto in questo ciclo + if (firstLayerToCheck is not None) and firstLayerToCheck.id() == layer.id(): + continue; + + # considero solo i layer vettoriali che sono filtrati per tipo + if layer.type() == QgsMapLayer.VectorLayer and \ + (onlyEditableLayers == False or layer.isEditable()) and \ + ((layer.geometryType() == QgsWkbTypes.PointGeometry and checkPointLayer == True) or \ + (layer.geometryType() == QgsWkbTypes.LineGeometry and checkLineLayer == True) or \ + (layer.geometryType() == QgsWkbTypes.PolygonGeometry and checkPolygonLayer == True)): + # restituisce feature, point + res = getEntSelOnLayer(point, mQgsMapTool, boxSize, layer, onlyBoundary, layerCacheGeomsDict, returnFeatureCached) + if res is not None: + return res[0], layer, res[1] + + #QApplication.restoreOverrideCursor() + return None + + +# =============================================================================== +# getEntSelOnLayer +# =============================================================================== +def getEntSelOnLayer(point, mQgsMapTool, boxSize, layer, onlyBoundary = True, \ + layerCacheGeomsDict = None, returnFeatureCached = False): + """ + dato un punto (in screen coordinates) e un QgsMapTool, + la funzione cerca la prima entità del layer dentro il quadrato + di dimensioni (in pixel) centrato sul punto + onlyBoundary = serve per considerare solo il bordo dei poligoni o anche il loro interno + layerCacheGeomsDict = per ottimizzare la ricerca, è una chache delle geometrie dei layer + + Restituisce una lista composta da una QgsFeature e il punto di selezione + in caso di successo altrimenti None + """ + layerCoords = mQgsMapTool.toLayerCoordinates(layer, point) + ToleranceInMapUnits = QgsTolerance.toleranceInMapUnits(boxSize, layer, \ + mQgsMapTool.canvas.mapSettings(), \ + QgsTolerance.Pixels) / 2 + + selectRect = QgsRectangle(layerCoords.x() - ToleranceInMapUnits, layerCoords.y() - ToleranceInMapUnits, \ + layerCoords.x() + ToleranceInMapUnits, layerCoords.y() + ToleranceInMapUnits) + + # se il processo può essere ottimizzato con la cache + if layerCacheGeomsDict is not None: + cachedFeatures = layerCacheGeomsDict.getFeatures(layer, selectRect) + + featureRequest = QgsFeatureRequest() + featureRequest.setSubsetOfAttributes([]) + + for cachedFeature in cachedFeatures: + # se é un layer contenente poligoni allora verifico se considerare solo i bordi + if onlyBoundary == False or layer.geometryType() != QgsWkbTypes.PolygonGeometry: + if cachedFeature.geometry().intersects(selectRect): + if returnFeatureCached: # ritorna la feature della cache + return cachedFeature, point + # ottengo la feature del layer + featureRequest.setFilterFid(cachedFeature.attribute("index")) + featureIterator = layer.getFeatures(featureRequest) + for feature in featureIterator: + return feature, point + else: + # considero solo i bordi delle geometrie e non lo spazio interno dei poligoni + # Riduco le geometrie in point o polyline + geoms = asPointOrPolyline(cachedFeature.geometry()) + for g in geoms: + #start = time.time() # test + #for i in range(1, 10): + if g.intersects(selectRect): + if returnFeatureCached: # ritorna la feature della cache + return cachedFeature, point + + # ottengo la feature del layer + featureRequest.setFilterFid(cachedFeature.attribute("index")) + featureIterator = layer.getFeatures(featureRequest) + for feature in featureIterator: + return feature, point + #tempo = ((time.time() - start) * 1000) # test + #tempo += 0 # test + else: + featureIterator = layer.getFeatures(getFeatureRequest([], True, selectRect, True)) + feature = QgsFeature() + + # se é un layer contenente poligoni allora verifico se considerare solo i bordi + if onlyBoundary == False or layer.geometryType() != QgsWkbTypes.PolygonGeometry: + for feature in featureIterator: + return feature, point + else: + # considero solo i bordi delle geometrie e non lo spazio interno dei poligoni + for feature in featureIterator: + # Riduco le geometrie in point o polyline + geoms = asPointOrPolyline(feature.geometry()) + for g in geoms: + if g.intersects(selectRect): + return feature, point + + return None + + +# =============================================================================== +# getFeatureById +# =============================================================================== +def getFeatureById(layer, id): + """ + Ricava una feature dal suo id. + """ + feature = QgsFeature() + if layer.getFeatures(QgsFeatureRequest().setFilterFid(id)).nextFeature(feature): + return feature + else: + return None + + +# =============================================================================== +# isGeomInBox +# =============================================================================== +def isGeomInBox(point, mQgsMapTool, geom, boxSize, crs = None, \ + checkPointLayer = True, checkLineLayer = True, checkPolygonLayer = True, + onlyBoundary = True): + """ + dato un punto (in screen coordinates) e un QgsMapTool, + la funzione verifica se la geometria é dentro il quadrato + di dimensioni boxSize (in pixel) centrato sul punto + geom = geometria da verificare + crs = sistema di coordinate della geometria (se = NON significa in map coordinates) + checkPointLayer = opzionale, considera la geometria di tipo punto + checkLineLayer = opzionale, considera la geometria di tipo linea + checkPolygonLayer = opzionale, considera la geometria di tipo poligono + onlyBoundary = serve per considerare solo il bordo dei poligoni o anche il loro interno + Restituisce True se la geometria é nel quadrato di dimensione boxSize in (pixel) altrimenti False + """ + if geom is None: + return False + if checkPointLayer == False and checkLineLayer == False and checkPolygonLayer == False: + return False + + # considero solo la geometria filtrata per tipo + if ((geom.type() == QgsWkbTypes.PointGeometry and checkPointLayer == True) or \ + (geom.type() == QgsWkbTypes.LineGeometry and checkLineLayer == True) or \ + (geom.type() == QgsWkbTypes.PolygonGeometry and checkPolygonLayer == True)): + mapPoint = mQgsMapTool.toMapCoordinates(point) + mapGeom = QgsGeometry(geom) + if crs is not None and mQgsMapTool.canvas.mapSettings().destinationCrs() != crs: + # trasformo le coord della geometria in map coordinates + coordTransform = QgsCoordinateTransform(crs, mQgsMapTool.canvas.mapSettings().destinationCrs(), QgsProject.instance()) + mapGeom.transform(coordTransform) + + ToleranceInMapUnits = boxSize * mQgsMapTool.canvas.mapSettings().mapUnitsPerPixel() + selectRect = QgsRectangle(mapPoint.x() - ToleranceInMapUnits, mapPoint.y() - ToleranceInMapUnits, \ + mapPoint.x() + ToleranceInMapUnits, mapPoint.y() + ToleranceInMapUnits) + + # se é una geometria poligono allora verifico se considerare solo i bordi + if onlyBoundary == False or geom.type() != QgsWkbTypes.PolygonGeometry: + if mapGeom.intersects(selectRect): + return True + else: + # considero solo i bordi della geometria e non lo spazio interno del poligono + # Riduco la geometria in point o polyline + geoms = asPointOrPolyline(mapGeom) + for g in geoms: + if g.intersects(selectRect): + return True + + return False + + +# =============================================================================== +# getGeomInBox +# =============================================================================== +def getGeomInBox(point, mQgsMapTool, geoms, boxSize, crs = None, \ + checkPointLayer = True, checkLineLayer = True, checkPolygonLayer = True, + onlyBoundary = True): + """ + dato un punto (in screen coordinates) e un QgsMapTool, + la funzione cerca la prima geometria dentro il quadrato + di dimensioni boxSize (in pixel) centrato sul punto + geoms = lista di geometrie da verificare + crs = sistema di coordinate della geometria (se = NON significa in map coordinates) + checkPointLayer = opzionale, considera la geometria di tipo punto + checkLineLayer = opzionale, considera la geometria di tipo linea + checkPolygonLayer = opzionale, considera la geometria di tipo poligono + onlyBoundary = serve per considerare solo il bordo dei poligoni o anche il loro interno + Restituisce la geometria che é nel quadrato di dimensini boxSize altrimenti None + """ + if geoms is None: + return False + for geom in geoms: + if isGeomInBox(point, mQgsMapTool, geom, boxSize, crs, checkPointLayer, checkLineLayer, checkPolygonLayer, onlyBoundary): + return geom + return None + + +# =============================================================================== +# getActualSingleSelection +# =============================================================================== +def getActualSingleSelection(layers): + """ + la funzione cerca se esiste una sola entità selezionata tra i layer + Restituisce un QgsFeature e il suo layer in caso di successo altrimenti None + """ + selFeature = [] + + for layer in layers: # ciclo sui layer + if (layer.type() == QgsMapLayer.VectorLayer): + selectedFeatureCount = layer.selectedFeaturCount() + if selectedFeatureCount == 1: + selFeature = layer.selectedFeatures() + selLayer = Layer + elif selectedFeatureCount > 1: + del selFeature[:] # svuoto la lista + break + + if len(selFeature) == 1: # se c'era solo una entità selezionata + return selFeature[0], selLayer + + return None + + +def deselectAll(layers): + """ + la funzione deseleziona tutte le entità selezionate nei layer + """ + for layer in layers: # ciclo sui layer + if (layer.type() == QgsMapLayer.VectorLayer): + layer.removeSelection() + + +# =============================================================================== +# appendUniquePointToList +# =============================================================================== +def appendUniquePointToList(pointList, point): + """ + Aggiunge un punto alla lista verificando che non sia già presente. + Resituisce True se l'inserimento é avvenuto False se il punto c'era già. + """ + for iPoint in pointList: + if ptNear(iPoint, point): + return False + + pointList.append(point) + return True + + +# =============================================================================== +# getPerpendicularPointOnInfinityLine +# =============================================================================== +def getPerpendicularPointOnInfinityLine(p1, p2, pt): + """ + la funzione ritorna il punto di proiezione perpendicolare di pt + alla linea passante per p1-p2. + """ + + diffX = p2.x() - p1.x() + diffY = p2.y() - p1.y() + + if doubleNear(diffX, 0): # se la retta passante per p1 e p2 é verticale + return QgsPointXY(p1.x(), pt.y()) + elif doubleNear(diffY, 0): # se la retta passante per p1 e p2 é orizzontale + return QgsPointXY(pt.x(), p1.y()) + else: + coeff = diffY / diffX + x = (coeff * p1.x() - p1.y() + pt.x() / coeff + pt.y()) / (coeff + 1 / coeff) + y = coeff * (x - p1.x()) + p1.y() + + return QgsPointXY(x, y) + + +# =============================================================================== +# getInfinityLinePerpOnMiddle +# =============================================================================== +def getInfinityLinePerpOnMiddle(pt1, pt2): + """ + dato un segmento pt1-pt2, la funzione trova una linea perpendicolare al segmento + che passa per il suo punto medio. La funzione restituisce 2 punti della linea. + """ + ptMiddle = getMiddlePoint(pt1, pt2) + dist = getDistance(pt1, ptMiddle) + if dist == 0: + return None + angle = getAngleBy2Pts(pt1, pt2) + math.pi / 2 + pt2Middle = getPolarPointByPtAngle(ptMiddle, angle, dist) + return ptMiddle, pt2Middle + + +# =============================================================================== +# getMiddleAngle +# =============================================================================== +def getMiddleAngle(angle1, angle2): + """ + dati 2 angoli, la funzione restituisce l'angolo medio. + """ + a1 = normalizeAngle(angle1) + a2 = normalizeAngle(angle2) + if a2 < a1: a2 = (math.pi * 2) + a2 + return normalizeAngle((a2 + a1) / 2) + + +# =============================================================================== +# getBisectorInfinityLine +# =============================================================================== +def getBisectorInfinityLine(pt1, pt2, pt3, acuteMode = True): + """ + dato un angolo definito da 3 punti il cui secondo punto é vertice dell'angolo, + la funzione restituisce la linea bisettrice dell'angolo attraverso 2 punti + della linea (il vertice dell'angolo e un altro punto calcolato distante quanto + la distanza di pt1 da pt2). + acuteMode = True considera l'angolo acuto, acuteMode = False l'angolo ottuso + """ + angle1 = getAngleBy2Pts(pt2, pt1) + angle2 = getAngleBy2Pts(pt2, pt3) + angle = (angle1 + angle2) / 2 # angolo medio +# return pt2, getPolarPointByPtAngle(pt2, angle, 10) + + dist = getDistance(pt1, pt2) + ptProj = getPolarPointByPtAngle(pt2, angle, dist) + ptInverseProj = getPolarPointByPtAngle(pt2, angle - math.pi, dist) + if getDistance(pt1, ptProj) < getDistance(pt1, ptInverseProj): + if acuteMode == True: + return pt2, ptProj + else: + return pt2, ptInverseProj + else: + if acuteMode == True: + return pt2, ptInverseProj + else: + return pt2, ptProj + + +# =============================================================================== +# getXOnInfinityLine +# =============================================================================== +def getXOnInfinityLine(p1, p2, y): + """ + data la coordinata Y di un punto la funzione ritorna la coordinata X dello stesso + sulla linea passante per p1-p2 + """ + + diffX = p2.x() - p1.x() + diffY = p2.y() - p1.y() + + if doubleNear(diffX, 0): # se la retta passante per p1 e p2 é verticale + return p1.x() + elif doubleNear(diffY, 0): # se la retta passante per p1 e p2 é orizzontale + return None # infiniti punti + else: + coeff = diffY / diffX + return p1.x() + (y - p1.y()) / coeff + + +# =============================================================================== +# getYOnInfinityLine +# =============================================================================== +def getYOnInfinityLine(p1, p2, x): + """ + data la coordinata Y di un punto la funzione ritorna la coordinata X dello stesso + sulla linea passante per p1-p2 + """ + + diffX = p2.x() - p1.x() + diffY = p2.y() - p1.y() + + if doubleNear(diffX, 0): # se la retta passante per p1 e p2 é verticale + return None # infiniti punti + elif doubleNear(diffY, 0): # se la retta passante per p1 e p2 é orizzontale + return p1.y() + else: + coeff = diffY / diffX + return p1.y() + (x - p1.x()) * coeff + + +# =============================================================================== +# getSqrDistance +# =============================================================================== +def getSqrDistance(p1, p2): + """ + la funzione ritorna la distanza al quadrato tra 2 punti (QgsPointXY) + """ + dx = p2.x() - p1.x() + dy = p2.y() - p1.y() + + return dx * dx + dy * dy + + +# =============================================================================== +# getDistance +# =============================================================================== +def getDistance(p1, p2): + """ + la funzione ritorna la distanza tra 2 punti (QgsPointXY) + """ + return math.sqrt(getSqrDistance(p1, p2)) + + +# =============================================================================== +# getMinDistancePtBetweenSegmentAndPt +# =============================================================================== +def getMinDistancePtBetweenSegmentAndPt(p1, p2, pt): + """ + la funzione ritorna il punto di distanza minima e la distanza minima tra un segmento ed un punto + () + """ + if isPtOnSegment(p1, p2, pt) == True: + return [pt, 0] + perpPt = getPerpendicularPointOnInfinityLine(p1, p2, pt) + if perpPt is not None: + if isPtOnSegment(p1, p2, perpPt) == True: + return [perpPt, getDistance(perpPt, pt)] + + distFromP1 = getDistance(p1, pt) + distFromP2 = getDistance(p2, pt) + if distFromP1 < distFromP2: + return [p1, distFromP1] + else: + return [p2, distFromP2] + + +# =============================================================================== +# getMiddlePoint +# =============================================================================== +def getMiddlePoint(p1, p2): + """ + la funzione ritorna il punto medio tra 2 punti (QgsPointXY) + """ + x = (p1.x() + p2.x()) / 2 + y = (p1.y() + p2.y()) / 2 + + return QgsPointXY(x, y) + + +# =============================================================================== +# getAngleBy2Pts +# =============================================================================== +def getAngleBy2Pts(p1, p2, tolerance = None): + """ + la funzione ritorna l'angolo in radianti della retta passante per p1 e p2 + """ + diffX = p2.x() - p1.x() + diffY = p2.y() - p1.y() + if doubleNear(diffX, 0, tolerance): # se la retta passante per p1 e p2 é verticale + if p1.y() < p2.y(): + angle = math.pi / 2 + else : + angle = math.pi * 3 / 2 + elif doubleNear(diffY, 0, tolerance): # se la retta passante per p1 e p2 é orizzontale + if p1.x() <= p2.x(): + angle = 0.0 + else: + angle = math.pi + else: + angle = math.atan(diffY / diffX) + if diffX < 0: + angle = math.pi + angle + else: + if diffY < 0: + angle = 2 * math.pi + angle + + return angle + + +# =============================================================================== +# getAngleBy3Pts +# =============================================================================== +def getAngleBy3Pts(p1, vertex, p2, clockWise): + """ + la funzione ritorna l'angolo in radianti dell'angolo che parte da + per arrivare a con vertice nella direzione (oraria o antioraria) + """ + angle1 = getAngleBy2Pts(p1, vertex) + angle2 = getAngleBy2Pts(p2, vertex) + if clockWise: # senso orario + if angle2 > angle1: + return (2 * math.pi) - (angle2 - angle1) + else: + return angle1 - angle2 + else: # senso anti-orario + if angle2 < angle1: + return (2 * math.pi) - (angle1 - angle2) + else: + return angle2 - angle1 + + +# =============================================================================== +# isAngleBetweenAngles +# =============================================================================== +def isAngleBetweenAngles(startAngle, endAngle, angle): + """ + la funzione ritorna True se l'angolo si trova entro l'angolo di partenza e quello finale + estremi compresi + """ + _angle = angle % (math.pi * 2) # modulo + + if startAngle < endAngle: + if (_angle > startAngle or doubleNear(_angle, startAngle)) and \ + (_angle < endAngle or doubleNear(_angle, endAngle)): + return True + else: + if (_angle > 0 or doubleNear(_angle, 0)) and \ + (_angle < endAngle or doubleNear(_angle, endAngle)): + return True + + if (_angle < (math.pi * 2) or doubleNear(_angle, (math.pi * 2))) and \ + (_angle > startAngle or doubleNear(_angle, startAngle)): + return True + + return False + + +def getPolarPointBy2Pts(p1, p2, dist): + """ + la funzione ritorna il punto sulla retta passante per p1 e p2 che + dista da p1 verso p2 . + """ + angle = getAngleBy2Pts(p1, p2) + + return getPolarPointByPtAngle(p1, angle, dist) + + +# =============================================================================== +# isPtOnSegment +# =============================================================================== +def isPtOnSegment(p1, p2, point): + """ + la funzione ritorna true se il punto é sul segmento (estremi compresi). + p1, p2 e point sono QgsPointXY. + """ + if p1.x() < p2.x(): + xMin = p1.x() + xMax = p2.x() + else: + xMax = p1.x() + xMin = p2.x() + + # verifico se il punto può essere sul segmento 22/07/2017 + if doubleSmaller(point.x(), xMin) or doubleGreater(point.x(), xMax): return False + + if p1.y() < p2.y(): + yMin = p1.y() + yMax = p2.y() + else: + yMax = p1.y() + yMin = p2.y() + + # verifico se il punto può essere sul segmento 22/07/2017 + if doubleSmaller(point.y(), yMin) or doubleGreater(point.y(), yMax): return False + + y = getYOnInfinityLine(p1, p2, point.x()) + if y is None: # il segmento p1-p2 é verticale + return True + else: + # se il punto é sulla linea infinita che passa da p1-p2 + if doubleNear(point.y(), y): + return True + + return False + + +# =============================================================================== +# getIntersectionPointOn2InfinityLines +# =============================================================================== +def getIntersectionPointOn2InfinityLines(line1P1, line1P2, line2P1, line2P2): + """ + la funzione ritorna il punto di intersezione tra la linea passante per line1P1-line1P2 e + la linea passante per line2P1-line2P2. + """ + line1DiffX = line1P2.x() - line1P1.x() + line1DiffY = line1P2.y() - line1P1.y() + + line2DiffX = line2P2.x() - line2P1.x() + line2DiffY = line2P2.y() - line2P1.y() + + if doubleNear(line1DiffX, 0) and doubleNear(line2DiffX, 0): # se la retta1 e la retta2 sono verticale + return None # sono parallele + elif doubleNear(line1DiffY, 0) and doubleNear(line2DiffY, 0): # se la retta1 e la retta2 sono orizzontali + return None # sono parallele + + if doubleNear(line1DiffX, 0): # se la retta1 é verticale + return QgsPointXY(line1P2.x(), getYOnInfinityLine(line2P1, line2P2, line1P2.x())) + if doubleNear(line1DiffY, 0): # se la retta1 é orizzontale + return QgsPointXY(getXOnInfinityLine(line2P1, line2P2, line1P2.y()), line1P2.y()) + if doubleNear(line2DiffX, 0): # se la retta2 é verticale + return QgsPointXY(line2P2.x(), getYOnInfinityLine(line1P1, line1P2, line2P2.x())) + if doubleNear(line2DiffY, 0): # se la retta2 é orizzontale + return QgsPointXY(getXOnInfinityLine(line1P1, line1P2, line2P2.y()), line2P2.y()) + + line1Coeff = line1DiffY / line1DiffX + line2Coeff = line2DiffY / line2DiffX + + if line1Coeff == line2Coeff: # sono parallele + return None + + D = line1Coeff - line2Coeff + # se D é così vicino a zero + if doubleNear(D, 0.0): + return None + x = line1P1.x() * line1Coeff - line1P1.y() - line2P1.x() * line2Coeff + line2P1.y() + x = x / D + y = (x - line1P1.x()) * line1Coeff + line1P1.y() + + return QgsPointXY(x, y) + + +# =============================================================================== +# getNearestPoints +# =============================================================================== +def getNearestPoints(point, points, tolerance = 0): + """ + Ritorna una lista di punti più vicino a point. + """ + result = [] + minDist = sys.float_info.max + + if tolerance == 0: # solo il punto più vicino + for pt in points: + dist = getDistance(point, pt) + if dist < minDist: + minDist = dist + nearestPoint = pt + + if minDist != sys.float_info.max: # trovato + result.append(nearestPoint) + else: + nearest = getNearestPoints(point, points) # punto più vicino + nearestPoint = nearest[0] + + for pt in points: + dist = getDistance(nearestPoint, pt) + if dist <= tolerance: + result.append(pt) + + return result + + +# =============================================================================== +# getPolarPointByPtAngle +# =============================================================================== +def getPolarPointByPtAngle(p1, angle, dist): + """ + la funzione ritorna il punto sulla retta passante per p1 con angolo che + dista da p1 . + """ + y = dist * math.sin(angle) + x = dist * math.cos(angle) + return QgsPointXY(p1.x() + x, p1.y() + y) + + +# =============================================================================== +# asPointOrPolyline +# =============================================================================== +def asPointOrPolyline(geom): + """ + la funzione ritorna una lista di geometrie di punti e/o polilinee in cui viene trasformata la geometria. + """ + # Trasformo le geometrie in point o polyline + result = [] + for g in geom.asGeometryCollection(): + gType = g.type() + if g.isMultipart() == False: + if gType == QgsWkbTypes.PointGeometry or gType == QgsWkbTypes.LineGeometry: + result.append(g) + + elif gType == QgsWkbTypes.PolygonGeometry: + lineList = g.asPolygon() # vettore di linee + for line in lineList: + _g = QgsGeometry.fromPolylineXY(line) + result.append(_g) + + else: # multi + if gType == QgsWkbTypes.PointGeometry: + pointList = g.asMultiPoint() # vettore di punti + for point in pointList: + _g = QgsGeometry.fromPointXY(point) + result.append(_g) + + elif gType == QgsWkbTypes.LineGeometry: + lineList = g.asMultiPolyline() # vettore di linee + for line in lineList: + _g = QgsGeometry.fromPolylineXY(line) + result.append(_g) + + elif gType == QgsWkbTypes.PolygonGeometry: + polygonList = g.asMultiPolygon() # vettore di poligoni + for polygon in polygonList: + for line in polygon: + _g = QgsGeometry.fromPolylineXY(line) + result.append(_g) + + return result + + +# =============================================================================== +# leftOfLineCoords +# =============================================================================== +# usare qad_line +def leftOfLineCoords(x, y, x1, y1, x2, y2): + """ + la funzione ritorna una numero < 0 se il punto x,y é alla sinistra della linea x1,y1 -> x2,y2 + """ + f1 = x - x1 + f2 = y2 - y1 + f3 = y - y1 + f4 = x2 - x1 + return f1*f2 - f3*f4 + +# usare qad_line +def leftOfLine(pt, pt1, pt2): + return leftOfLineCoords(pt.x(), pt.y(), pt1.x(), pt1.y(), pt2.x(), pt2.y()) + + +# =============================================================================== +# get a and b for line equation (y = ax + b) +# =============================================================================== +# usare qad_line +def get_A_B_LineEquation(x1, y1, x2, y2): + # dati 2 punti vengono calcolati a e b dell'equazione della retta passante per i due punti (y = ax + b) + a = (y2 - y1) / (x2 - x1) + # y = ax + b -> b = y - ax + b = y1 - (a * x1) + + return a, b + + +# =============================================================================== +# radice cubica +# =============================================================================== +def cbrt(x): + # https://stackoverflow.com/questions/28014241/how-to-find-cube-root-using-python + if x>0: + return x**(1.0 / 3.0) + else: + return -((-x)**(1.0 / 3.0)) + + +# =============================================================================== +# ptNear +# =============================================================================== +def ptNear(pt1, pt2, tolerance = None): + """ + la funzione compara 2 punti (ma permette una tolleranza) + """ + if tolerance is None: + myTolerance = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2COINCIDENT")) + else: + myTolerance = tolerance + + return getDistance(pt1, pt2) <= myTolerance + + +# =============================================================================== +# doubleNear +# =============================================================================== +def doubleNear(a, b, tolerance = None): + """ + la funzione compara 2 float (ma permette una tolleranza) + """ + if tolerance is None: + myTolerance = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2COINCIDENT")) + else: + myTolerance = tolerance + + diff = a - b + return diff >= -myTolerance and diff <= myTolerance + + +# =============================================================================== +# doubleGreater +# =============================================================================== +def doubleGreater(a, b, tolerance = None): + """ + la funzione compara 2 float (ma permette una tolleranza) + """ + if tolerance is None: + myTolerance = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2COINCIDENT")) + else: + myTolerance = tolerance + + return a > b and not doubleNear(a, b, myTolerance) + + +# =============================================================================== +# doubleGreaterOrEquals +# =============================================================================== +def doubleGreaterOrEquals(a, b, tolerance = None): + """ + la funzione compara 2 float (ma permette una tolleranza) + """ + if tolerance is None: + myTolerance = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2COINCIDENT")) + else: + myTolerance = tolerance + + return a > b or doubleNear(a, b, myTolerance) + + +# =============================================================================== +# doubleSmaller +# =============================================================================== +def doubleSmaller(a, b, tolerance = None): + """ + la funzione compara 2 float (ma permette una tolleranza) + """ + if tolerance is None: + myTolerance = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2COINCIDENT")) + else: + myTolerance = tolerance + + return a < b and not doubleNear(a, b, myTolerance) + + +# =============================================================================== +# doubleSmallerOrEquals +# =============================================================================== +def doubleSmallerOrEquals(a, b, tolerance = None): + """ + la funzione compara 2 float (ma permette una tolleranza) + """ + if tolerance is None: + myTolerance = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2COINCIDENT")) + else: + myTolerance = tolerance + + return a < b or doubleNear(a, b, myTolerance) + + +# =============================================================================== +# TanDirectionNear +# =============================================================================== +def TanDirectionNear(a, b, tolerance = None): + """ + la funzione compara 2 direzioni di tangenti (ma permette una tolleranza) + """ + if tolerance is None: + myTolerance = QadVariables.get(QadMsg.translate("Environment variables", "TOLERANCE2COINCIDENT")) + else: + myTolerance = tolerance + + a1 = normalizeAngle(a) + b1 = normalizeAngle(b) + if a1 > b1: + diff1 = a1 - b1 + diff2 = (2 * math.pi + b1) - a1 + else: + diff1 = b1 - a1 + diff2 = (2 * math.pi + a1) - b1 + + return diff1 <= myTolerance or diff2 <= myTolerance + + +# =============================================================================== +# numericListAvg +# =============================================================================== +def numericListAvg(dblList): + """ + la funzione calcola la media di una lista di numeri + """ + if (dblList is None) or len(dblList) == 0: + return None + sum = 0 + for num in dblList: + sum = sum + num + + return sum / len(dblList) + + +# =============================================================================== +# sqrDistToSegment +# =============================================================================== +# usare qad_line fino qui +def sqrDistToSegment(point, x1, y1, x2, y2, epsilon): + """ + la funzione ritorna una lista con + ( + ) + """ + minDistPoint = QgsPointXY() + + if x1 == x2 and y1 == y2: + minDistPoint.setX(x1) + minDistPoint.setY(y1) + else: + nx = y2 - y1 + ny = -( x2 - x1 ) + + t = (point.x() * ny - point.y() * nx - x1 * ny + y1 * nx ) / (( x2 - x1 ) * ny - ( y2 - y1 ) * nx ) + + if t < 0.0: + minDistPoint.setX(x1) + minDistPoint.setY(y1) + elif t > 1.0: + minDistPoint.setX(x2) + minDistPoint.setY(y2) + else: + minDistPoint.setX( x1 + t *( x2 - x1 ) ) + minDistPoint.setY( y1 + t *( y2 - y1 ) ) + + dist = point.sqrDist(minDistPoint) + # prevent rounding errors if the point is directly on the segment + if doubleNear( dist, 0.0, epsilon ): + minDistPoint.setX( point.x() ) + minDistPoint.setY( point.y() ) + return (0.0, minDistPoint) + + return (dist, minDistPoint) + + +# =============================================================================== +# closestSegmentWithContext +# =============================================================================== +def closestSegmentWithContext(point, geom, epsilon = 1.e-15): + """ + la funzione ritorna una lista con + ( + + + <"a sinistra di" se il punto é alla sinista del segmento (< 0 -> sinistra, > 0 -> destra) + """ + minDistPoint = QgsPointXY() + closestSegmentIndex = 0 + sqrDist = sys.float_info.max + + gType = geom.type() + if geom.isMultipart() == False: + if gType == QgsWkbTypes.PointGeometry: + minDistPoint = geom.asPoint() + point.sqrDist(minDistPoint) + return (point.sqrDist(minDistPoint), minDistPoint, None, None) + + elif gType == QgsWkbTypes.LineGeometry: + points = geom.asPolyline() # vettore di punti + index = 0 + for pt in points: + if index > 0: + prevX = thisX + prevY = thisY + + thisX = pt.x() + thisY = pt.y() + + if index > 0: + result = sqrDistToSegment(point, prevX, prevY, thisX, thisY, epsilon) + testdist = result[0] + distPoint = result[1] + + if testdist < sqrDist: + closestSegmentIndex = index + sqrDist = testdist + minDistPoint = distPoint + + index = index + 1 + + leftOf = leftOfLine(point, geom.vertexAt(closestSegmentIndex - 1), geom.vertexAt(closestSegmentIndex)) + return (sqrDist, minDistPoint, closestSegmentIndex, leftOf) + + elif gType == QgsWkbTypes.PolygonGeometry: + lines = geom.asPolygon() # lista di linee + index = 0 + for line in lines: + prevX = 0 + prevY = 0 + + for pt in line: # lista di punti + thisX = pt.x() + thisY = pt.y() + + if prevX and prevY: + result = sqrDistToSegment(point, prevX, prevY, thisX, thisY, epsilon) + testdist = result[0] + distPoint = result[1] + + if testdist < sqrDist: + closestSegmentIndex = index + sqrDist = testdist + minDistPoint = distPoint + + prevX = thisX + prevY = thisY + index = index + 1 + + leftOf = leftOfLine(point, geom.vertexAt(closestSegmentIndex - 1), geom.vertexAt(closestSegmentIndex)) + return (sqrDist, minDistPoint, closestSegmentIndex, leftOf) + + else: # multi + if gType == QgsWkbTypes.PointGeometry: + minDistPoint = getNearestPoints(point, geom.asMultiPoint())[0] # vettore di punti + return (point.sqrDist(minDistPoint), minDistPoint, None, None) + + elif gType == QgsWkbTypes.LineGeometry: + lines = geom.asMultiPolyline() # lista di linee + pointindex = 0 + for line in lines: + prevX = 0 + prevY = 0 + + for pt in line: # lista di punti + thisX = pt.x() + thisY = pt.y() + + if prevX and prevY: + result = sqrDistToSegment(point, prevX, prevY, thisX, thisY, epsilon) + testdist = result[0] + distPoint = result[1] + + if testdist < sqrDist: + closestSegmentIndex = pointindex + sqrDist = testdist + minDistPoint = distPoint + + prevX = thisX + prevY = thisY + pointindex = pointindex + 1 + + leftOf = leftOfLine(point, geom.vertexAt(closestSegmentIndex - 1), geom.vertexAt(closestSegmentIndex)) + return (sqrDist, minDistPoint, closestSegmentIndex, leftOf) + + elif gType == QgsWkbTypes.PolygonGeometry: + polygons = geom.asMultiPolygon() # vettore di poligoni + pointindex = 0 + for polygon in polygons: + for line in polygon: # lista di linee + prevX = 0 + prevY = 0 + + for pt in line: # lista di punti + thisX = pt.x() + thisY = pt.y() + + if prevX and prevY: + result = sqrDistToSegment(point, prevX, prevY, thisX, thisY, epsilon) + testdist = result[0] + distPoint = result[1] + + if testdist < sqrDist: + closestSegmentIndex = pointindex + sqrDist = testdist + minDistPoint = distPoint + + prevX = thisX + prevY = thisY + pointindex = pointindex + 1 + + leftOf = leftOfLine(point, geom.vertexAt(closestSegmentIndex - 1), geom.vertexAt(closestSegmentIndex)) + return (sqrDist, minDistPoint, closestSegmentIndex, leftOf) + + return (-1, None, None, None) + + +# =============================================================================== +# rotatePoint +# =============================================================================== +def rotatePoint(point, basePt, angle): + """ + la funzione ruota un punto QgsPointXY secondo un punto base e un angolo in radianti + """ + return getPolarPointByPtAngle(basePt, getAngleBy2Pts(basePt, point) + angle, getDistance(basePt, point)) + + +# =============================================================================== +# scalePoint +# =============================================================================== +def scalePoint(point, basePt, scale): + """ + la funzione scala un punto QgsPointXY secondo un punto base e un fattore di scala + """ + return getPolarPointByPtAngle(basePt, getAngleBy2Pts(basePt, point), getDistance(basePt, point) * scale) + + +# =============================================================================== +# movePoint +# =============================================================================== +def movePoint(point, offsetX, offsetY): + """ + la funzione sposta un punto QgsPointXY secondo un offset X e uno Y + """ + return QgsPointXY(point.x() + offsetX, point.y() + offsetY) + + +# =============================================================================== +# mirrorPoint +# =============================================================================== +def mirrorPoint(point, mirrorPt, mirrorAngle): + """ + la funzione sposta un punto QgsPointXY secondo una linea speculare passante per un + un punto ed avente angolo + """ + pointAngle = getAngleBy2Pts(mirrorPt, point) + dist = getDistance(mirrorPt, point) + + return getPolarPointByPtAngle(mirrorPt, mirrorAngle + (mirrorAngle - pointAngle), dist) + + +# =============================================================================== +# getSubGeomAtVertex +# =============================================================================== +def getSubGeomAtVertex(geom, atVertex): + # ritorna la sotto-geometria al vertice e la sua posizione nella geometria (0-based) + # la posizione é espressa con una lista ( []) + gType = geom.type() + if geom.isMultipart() == False: + if gType == QgsWkbTypes.PointGeometry: + if atVertex == 0: + return QgsGeometry(geom), [0] + + elif gType == QgsWkbTypes.LineGeometry: + pts = geom.asPolyline() # lista di punti + if atVertex > len(pts) - 1: + return None, None + else: + return QgsGeometry(geom), [0] + + elif gType == QgsWkbTypes.PolygonGeometry: + lines = geom.asPolygon() # lista di linee + if len(lines) > 0: + i = 0 + iRing = -1 + for line in lines: + lineLen = len(line) + if atVertex >= i and atVertex < i + lineLen: # il numero di vertice ricade in questa linea + if iRing == -1: # si tratta della parte più esterna + return QgsGeometry.fromPolylineXY(line), [0] # parte <0>, ring <0> + else: + return QgsGeometry.fromPolylineXY(line), [0, iRing] # parte <0>, ring + i = i + lineLen + iRing = iRing + 1 + return None, None + + else: # multi + if gType == QgsWkbTypes.PointGeometry: + pts = geom.asMultiPoint() # lista di punti + if atVertex > len(pts) - 1: + return None, None + else: + return QgsGeometry.fromPointXY(pts[atVertex]), [atVertex] + + elif gType == QgsWkbTypes.LineGeometry: + # cerco in quale linea é il vertice + i = 0 + iLine = 0 + lines = geom.asMultiPolyline() # lista di linee + for line in lines: + lineLen = len(line) + if atVertex >= i and atVertex < i + lineLen: + return QgsGeometry.fromPolylineXY(line), [iLine] + i = i + lineLen + iLine = iLine + 1 + return None, None + + elif gType == QgsWkbTypes.PolygonGeometry: + i = 0 + iPolygon = 0 + polygons = geom.asMultiPolygon() # lista di poligoni + for polygon in polygons: + iRing = -1 + for line in polygon: + lineLen = len(line) + if atVertex >= i and atVertex < i + lineLen: # il numero di vertice ricade in questa linea + if iRing == -1: # si tratta della parte più esterna + return QgsGeometry.fromPolylineXY(line), [iPolygon] # parte + else: + return QgsGeometry.fromPolylineXY(line), [iPolygon, iRing] # parte , ring + + i = i + lineLen + iRing = iRing + 1 + iPolygon = iPolygon + 1 + + return None, None + + +# =============================================================================== +# getSubGeomAt +# =============================================================================== +def getSubGeomAt(geom, atSubGeom): + # ritorna la sotto-geometria la cui posizione + # é espressa con una lista ( []) + gType = geom.type() + if geom.isMultipart() == False: + if gType == QgsWkbTypes.PointGeometry or gType == QgsWkbTypes.LineGeometry: + if atSubGeom[0] == 0: + return QgsGeometry(geom) + + elif gType == QgsWkbTypes.PolygonGeometry: + if atSubGeom[0] == 0: + lines = geom.asPolygon() # lista di linee + if len(atSubGeom) == 1: # si tratta della parte più esterna + return QgsGeometry.fromPolylineXY(lines[0]) + else: + iRing = atSubGeom[1] + if iRing + 1 < len(lines): + return QgsGeometry.fromPolylineXY(lines[iRing + 1]) + + else: # multi + if gType == QgsWkbTypes.PointGeometry: + nPoint = atSubGeom[0] + return QgsGeometry(geom.vertexAt(nPoint)) + + elif gType == QgsWkbTypes.LineGeometry: + nLine = atSubGeom[0] + lines = geom.asMultiPolyline() # lista di linee + if nLine < len(lines): + return QgsGeometry.fromPolylineXY(lines[nLine]) + + elif gType == QgsWkbTypes.PolygonGeometry: + nPolygon = atSubGeom[0] + polygons = geom.asMultiPolygon() # lista di poligoni + if nPolygon < len(polygons): + lines = polygons[nPolygon] + if len(atSubGeom) == 1: # si tratta della parte più esterna + return QgsGeometry.fromPolylineXY(lines[0]) + else: + iRing = atSubGeom[1] + if iRing + 1 < len(lines): + return QgsGeometry.fromPolylineXY(lines[iRing + 1]) + + return None + + +# =============================================================================== +# setSubGeom +# =============================================================================== +def setSubGeom(geom, subGeom, atSubGeom): + # restituisce una geometria con la sotto-geometria alla posizione + # sostituita da + gType = geom.type() + subGType = subGeom.type() + ndx = 0 + + if geom.isMultipart() == False: + if gType == QgsWkbTypes.PointGeometry or gType == QgsWkbTypes.LineGeometry: + if atSubGeom[0] == 0: + if subGeom.isMultipart() == False and (subGType == QgsWkbTypes.PointGeometry or subGType == QgsWkbTypes.LineGeometry): + return QgsGeometry(SubGeom) + + elif gType == QgsWkbTypes.PolygonGeometry: + if subGeom.isMultipart() == False and subGType == QgsWkbTypes.LineGeometry: + if atSubGeom[0] == 0: + lines = geom.asPolygon() # lista di linee + if len(atSubGeom) == 1: # si tratta della parte più esterna + del lines[0] + lines.insert(0, SubGeom.asPolyline()) + # per problemi di approssimazione con LL il primo punto e l'ultimo non sono uguali quindi lo forzo + lines[0][-1].set(lines[0][0].x(), lines[0][0].y()) + return QgsGeometry.fromPolygonXY(lines) + else: + iRing = atSubGeom[1] + if iRing + 1 < len(lines): + del lines[iRing + 1] + lines.insert(iRing + 1, SubGeom.asPolyline()) + # per problemi di approssimazione con LL il primo punto e l'ultimo non sono uguali quindi lo forzo + lines[iRing + 1][-1].set(lines[iRing + 1][0].x(), lines[iRing + 1][0].y()) + return QgsGeometry.fromPolygonXY(lines) + + else: # multi + if gType == QgsWkbTypes.PointGeometry: + nPoint = atSubGeom[0] + if subGeom.isMultipart() == False and subGType == QgsWkbTypes.PointGeometry: + result = QgsGeometry(geom) + pt = SubGeom.asPoint() + if result.moveVertex(pt.x, pt.y(), nPoint) == True: + return result + + elif gType == QgsWkbTypes.LineGeometry: + if subGeom.isMultipart() == False and subGType == QgsWkbTypes.LineGeometry: + nLine = atSubGeom[0] + lines = geom.asMultiPolyline() # lista di linee + if nLine < len(lines) and nLine >= -len(lines): + del lines[nLine] + lines.insert(nLine, SubGeom.asPolyline()) + return QgsGeometry.fromMultiPolylineXY(lines) + + elif gType == QgsWkbTypes.PolygonGeometry: + if subGeom.isMultipart() == False and subGType == QgsWkbTypes.LineGeometry: + nPolygon = atSubGeom[0] + polygons = geom.asMultiPolygon() # lista di poligoni + if nPolygon < len(polygons): + lines = polygons[nPolygon] + if len(atSubGeom) == 1: # si tratta della parte più esterna + del lines[0] + lines.insert(0, SubGeom.asPolyline()) + # per problemi di approssimazione con LL il primo punto e l'ultimo non sono uguali quindi lo forzo + lines[0][-1].set(lines[0][0].x(), lines[0][0].y()) + return QgsGeometry.fromMultiPolygonXY(polygons) + else: + iRing = atSubGeom[1] + if iRing + 1 < len(lines): + del lines[iRing + 1] + lines.insert(iRing + 1, SubGeom.asPolyline()) + # per problemi di approssimazione con LL il primo punto e l'ultimo non sono uguali quindi lo forzo + lines[iRing + 1][-1].set(lines[iRing + 1][0].x(), lines[iRing + 1][0].y()) + return QgsGeometry.fromMultiPolygonXY(polygons) + elif subGeom.isMultipart() == False and subGType == QgsWkbTypes.PolygonGeometry: + nPolygon = atSubGeom[0] + polygons = geom.asMultiPolygon() # lista di poligoni + if nPolygon < len(polygons): + del polygons[nPolygon] + polygons.insert(nPolygon, SubGeom.asPolygon()) + return QgsGeometry.fromMultiPolygonXY(polygons) + + return None + + +# =============================================================================== +# delSubGeom +# =============================================================================== +def delSubGeom(geom, atSubGeom): + # Cancella la sotto-geometria alla posizione dalla geometria + gType = geom.type() + if geom.isMultipart() == False: + if gType == QgsWkbTypes.PointGeometry or gType == QgsWkbTypes.LineGeometry: + return None + + elif gType == QgsWkbTypes.PolygonGeometry: + if atSubGeom[0] == 0: + lines = geom.asPolygon() # lista di linee + if len(atSubGeom) == 1: # si tratta della parte più esterna + del lines[0] + return QgsGeometry() # geometria vuota perchè il poligono è stato cancellato + else: + iRing = atSubGeom[1] + if iRing + 1 < len(lines): + del lines[iRing + 1] + return QgsGeometry.fromPolygonXY(lines) + + else: # multi + if gType == QgsWkbTypes.PointGeometry: + nPoint = atSubGeom[0] + result = QgsGeometry(geom) + pt = SubGeom.asPoint() + if result.deleteVertex(nPoint) == True: + return result + + elif gType == QgsWkbTypes.LineGeometry: + nLine = atSubGeom[0] + lines = geom.asMultiPolyline() # lista di linee + if nLine < len(lines) and nLine >= -len(lines): + del lines[nLine] + return QgsGeometry.fromMultiPolylineXY(lines) + + elif gType == QgsWkbTypes.PolygonGeometry: + nPolygon = atSubGeom[0] + polygons = geom.asMultiPolygon() # lista di poligoni + if nPolygon < len(polygons): + lines = polygons[nPolygon] + if len(atSubGeom) == 1: # si tratta della parte più esterna + del polygons[nPolygon] + return QgsGeometry.fromMultiPolygonXY(polygons) + else: + iRing = atSubGeom[1] + if iRing + 1 < len(lines): + del lines[iRing + 1] + return QgsGeometry.fromMultiPolygonXY(polygons) + + return None + + +# =============================================================================== +# getAdjustedRubberBandVertex +# =============================================================================== +def getAdjustedRubberBandVertex(vertexBefore, vertex): + adjustedVertex = QgsPointXY(vertex) + + # per un baco non ancora capito in QGIS: se la linea ha solo 2 vertici e + # hanno la stessa x o y (linea orizzontale o verticale) + # la linea non viene disegnata perciò sposto un pochino la x o la y + # del secondo vertice + # 1.e-7 è derivato dal fatto che l'operatore == di QgsPointXY ha una tolleranza di 1E-8 + if vertexBefore.x() == vertex.x(): + adjustedVertex.setX(vertex.x() + 1.e-7) + if vertexBefore.y() == vertex.y(): + adjustedVertex.setY(vertex.y() + 1.e-7) + + return adjustedVertex + + +# ============================================================================ +# QadRawConfigParser class suppporting unicode +# ============================================================================ +class QadRawConfigParser(configparser.RawConfigParser): + + def __init__(self, defaults=None, dict_type=configparser._default_dict, + allow_no_value=False): + configparser.RawConfigParser.__init__(self, defaults, dict_type, allow_no_value) + + def write(self, fp): + """Fixed for Unicode output""" + if self._defaults: + fp.write("[%s]\n" % DEFAULTSECT) + for (key, value) in self._defaults.items(): + fp.write("%s = %s\n" % (key, unicode(value).replace('\n', '\n\t'))) + fp.write("\n") + for section in self._sections: + fp.write("[%s]\n" % section) + for (key, value) in self._sections[section].items(): + if key != "__name__": + fp.write("%s = %s\n" % (key, unicode(value).replace('\n','\n\t'))) + fp.write("\n") + + +# =============================================================================== +# Timer class for profiling +# =============================================================================== +class Timer(object): + # da usare: + # with Timer() as t: + # ... + # elasped = t.secs + def __init__(self, verbose=False): + self.verbose = verbose + + def __enter__(self): + self.start = time.time() + return self + + def __exit__(self, *args): + self.end = time.time() + self.secs = self.end - self.start + self.msecs = self.secs * 1000 # millisecs + if self.verbose: + print ('elapsed time: %f ms' % self.msecs) diff --git a/qad_variables.py b/qad_variables.py index 836903d6..208a1946 100644 --- a/qad_variables.py +++ b/qad_variables.py @@ -1,675 +1,1368 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - classe per gestire le variabili di ambiente - - ------------------- - begin : 2013-05-22 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -from PyQt4.QtCore import * -import os.path -from qgis.core import * - - -import qad_utils -from qad_msg import QadMsg - - -#=============================================================================== -# Qad variable class. -#=============================================================================== -class QadVariableTypeEnum(): - UNKNOWN = 0 # sconosciuto (non gestito da QAD) - STRING = 1 # caratteri - COLOR = 2 # colore espresso in caratteri (es. rosso = "#FF0000") - INT = 3 # numero intero - FLOAT = 4 # nmer con decimali - BOOL = 5 # booleano (True o False) - - -#=============================================================================== -# Qad variable class. -#=============================================================================== -class QadVariable(): - """ - Classe che gestisce le variabili di ambiente di Qad - """ - - def __init__(self, name, value, typeValue, minNum = None, maxNum = None, descr = ""): - self.name = name - self.value = value - self.typeValue = typeValue - self.default = value - self.minNum = minNum - self.maxNum = maxNum - self.descr = descr - - -#=============================================================================== -# Qad variables class. -#=============================================================================== -class QadVariablesClass(): - """ - Classe che gestisce le variabuili di ambiente di Qad - """ - - def __init__(self): - """ - Inizializza un dizionario con le variabili e i loro valori di default - """ - self.__VariableValuesDict = dict() # variabile privata - - - # ARCMINSEGMENTQTY (int): numero minimo di segmenti perché venga riconosciuto un arco - VariableName = QadMsg.translate("Environment variables", "ARCMINSEGMENTQTY") # x lupdate - VariableDescr = QadMsg.translate("Environment variables", "Minimum number of segments to approximate an arc.") # x lupdate - VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type.") - self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(12), \ - QadVariableTypeEnum.INT, \ - 4, 999, \ - VariableDescr) - - # AUTOSNAP (int): attiva il puntamento polare (somma di bit): - # 8 = Attiva il puntamento polare - VariableName = QadMsg.translate("Environment variables", "AUTOSNAP") - VariableDescr = QadMsg.translate("Environment variables", "Controls the display of the AutoSnap marker, tooltip, and magnet." + \ - "\nAlso turns on polar and object snap tracking, and controls the display of polar tracking, object snap tracking, and Ortho mode tooltips." + \ - "\nThe setting is stored as a bitcode using the sum of the following values:" + \ - "\n0 = Turns off the AutoSnap marker, tooltips, and magnet. Also turns off polar tracking, object snap tracking, and tooltips for polar tracking, object snap tracking, and Ortho mode." + \ - "\n1 = Turns on the AutoSnap mark." + \ - "\n2 = Turns on the AutoSnap tooltips." + \ - "\n4 = Turns on the AutoSnap magnet." + \ - "\n8 = Turns on polar tracking." + \ - "\n16 = Turns on object snap tracking." + \ - "\n32 = Turns on tooltips for polar tracking, object snap tracking, and Ortho mode.") # x lupdate - VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type.") - self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(63), \ - QadVariableTypeEnum.INT, \ - 0, 64, \ - VariableDescr) - - # CIRCLEMINSEGMENTQTY (int): numero minimo di segmenti perché venga riconosciuto un cerchio - VariableName = QadMsg.translate("Environment variables", "CIRCLEMINSEGMENTQTY") # x lupdate - VariableDescr = QadMsg.translate("Environment variables", "Minimum number of segments to approximate a circle.") # x lupdate - VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type.") - self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(12), \ - QadVariableTypeEnum.INT, \ - 6, 999, \ - VariableDescr) - - # CMDINPUTHISTORYMAX (int): Imposta il numero massimo di comandi nella lista di storicizzazione - VariableName = QadMsg.translate("Environment variables", "CMDINPUTHISTORYMAX") # x lupdate - VariableDescr = QadMsg.translate("Environment variables", "Sets the maximum number of previous input values that are stored for a prompt in a command.") # x lupdate - VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type.") - self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(20), \ - QadVariableTypeEnum.INT, \ - 1, 999, \ - VariableDescr) - - # COPYMODE (int): - # 0 = Imposta il comando COPIA in modo che venga ripetuto automaticamente - # 1 = Imposta il comando COPIA in modo da creare una singola copia - VariableName = QadMsg.translate("Environment variables", "COPYMODE") # x lupdate - VariableDescr = QadMsg.translate("Environment variables", "Controls whether the COPY command repeats automatically:" + \ - "\n0 = Sets the COPY command to repeat automatically." + \ - "\n1 = Sets the COPY command to create a single copy.") # x lupdate - VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type.") - self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(0), \ - QadVariableTypeEnum.INT, \ - 0, 1, \ - VariableDescr) - - # CROSSINGAREACOLOR (str): Imposta il colore (RGB) dell'area di selezione degli oggetti nel modo intersezione - VariableName = QadMsg.translate("Environment variables", "CROSSINGAREACOLOR") # x lupdate - VariableDescr = QadMsg.translate("Environment variables", "Controls the color of the transparent selection area during crossing selection (RGB, #33A02C = green)." + \ - "\nThe SELECTIONAREA system variable must be on.") # x lupdate - VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Character type.") - self.__VariableValuesDict[VariableName] = QadVariable(VariableName, unicode("#33A02C"), \ - QadVariableTypeEnum.COLOR, \ - None, None, \ - VariableDescr) # green - - # CURSORCOLOR (str): Imposta il colore (RGB) del cursore (la croce) - VariableName = QadMsg.translate("Environment variables", "CURSORCOLOR") # x lupdate - VariableDescr = QadMsg.translate("Environment variables", "Cross pointer color (RGB, #FF0000 = red).") # x lupdate - VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Character type.") - self.__VariableValuesDict[VariableName] = QadVariable(VariableName, unicode("#FF0000"), \ - QadVariableTypeEnum.COLOR, \ - None, None, \ - VariableDescr) # red - - # CURSORSIZE (int): Imposta la dimensione in pixel del cursore (la croce) - VariableName = QadMsg.translate("Environment variables", "CURSORSIZE") # x lupdate - VariableDescr = QadMsg.translate("Environment variables", "Determines the size of the crosshairs as a percentage of the screen size.") # x lupdate - VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type.") - self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(5), \ - QadVariableTypeEnum.INT, \ - 1, 100, \ - VariableDescr) - - # DIMSTYLE (str): Imposta il nome dello stile di quotatura corrente - VariableName = QadMsg.translate("Environment variables", "DIMSTYLE") # x lupdate - VariableDescr = QadMsg.translate("Environment variables", "Stores the name of the current dimension style.") # x lupdate - VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Character type.") - self.__VariableValuesDict[VariableName] = QadVariable(VariableName, unicode(""), \ - QadVariableTypeEnum.STRING, \ - None, None, \ - VariableDescr) - - # EDGEMODE (int): Controlla i comandi ESTENDI e TAGLIA. - # O = Vengono usate le dimensioni reali degli oggetti di riferimento - # 1 = Vengono usate le estensioni degli oggetti di riferimento (es. un arco viene considerato cerchio) - VariableName = QadMsg.translate("Environment variables", "EDGEMODE") # x lupdate - VariableDescr = QadMsg.translate("Environment variables", "Controls how the TRIM and EXTEND commands determine cutting and boundary edges:" + \ - "\n0 = Uses the selected edge without an extensions." + \ - "\n1 = Extends or trims the selected object to an imaginary extension of the cutting or boundary edge.") # x lupdate - VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type.") - self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(0), \ - QadVariableTypeEnum.INT, \ - 0, 1, \ - VariableDescr) - - # FILLETRAD (float): raggio applicato per raccordare (gradi) - VariableName = QadMsg.translate("Environment variables", "FILLETRAD") # x lupdate - VariableDescr = QadMsg.translate("Environment variables", "Stores the current fillet radius.") # x lupdate - VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Real type.") - self.__VariableValuesDict[VariableName] = QadVariable(VariableName, float(0.0), \ - QadVariableTypeEnum.FLOAT, \ - 0.000001, None, \ - VariableDescr) - - # GRIPCOLOR (str): Imposta il colore (RGB) dei grip non selezionati - VariableName = QadMsg.translate("Environment variables", "GRIPCOLOR") # x lupdate - VariableDescr = QadMsg.translate("Environment variables", "Controls the color of unselected grips (RGB, #100DD6 = blue).") # x lupdate - VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Character type.") - self.__VariableValuesDict[VariableName] = QadVariable(VariableName, unicode("#100DD6"), \ - QadVariableTypeEnum.COLOR, \ - None, None, \ - VariableDescr) # blu - - # GRIPCONTOUR (str): Imposta il colore (RGB) del bordo dei grip - VariableName = QadMsg.translate("Environment variables", "GRIPCONTOUR") # x lupdate - VariableDescr = QadMsg.translate("Environment variables", "Controls the color of the grip contour (RGB, #939393 = gray).") # x lupdate - VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Character type.") - self.__VariableValuesDict[VariableName] = QadVariable(VariableName, unicode("#939393"), \ - QadVariableTypeEnum.COLOR, \ - None, None, \ - VariableDescr) # gray - - # GRIPHOT(str): Imposta il colore (RGB) dei grip selezionati - VariableName = QadMsg.translate("Environment variables", "GRIPHOT") # x lupdate - VariableDescr = QadMsg.translate("Environment variables", "Controls the color of selected grips (RGB, #FF0000 = red).") # x lupdate - VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Character type.") - self.__VariableValuesDict[VariableName] = QadVariable(VariableName, unicode("#FF0000"), \ - QadVariableTypeEnum.COLOR, \ - None, None, \ - VariableDescr) # red - - # GRIPHOVER(str): Imposta il colore (RGB) dei grip non selezionati quando il cursore si ferma su di essi - VariableName = QadMsg.translate("Environment variables", "GRIPHOVER") # x lupdate - VariableDescr = QadMsg.translate("Environment variables", "Controls the fill color of an unselected grip when the cursor pauses over it (RGB, #FF7F7F = orange).") # x lupdate - VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Character type.") - self.__VariableValuesDict[VariableName] = QadVariable(VariableName, unicode("#FF7F7F"), \ - QadVariableTypeEnum.COLOR, \ - None, None, \ - VariableDescr) # orange - - # GRIPS (int): Controlla la visualizzazione dei grip sugli oggetti selezionati - VariableName = QadMsg.translate("Environment variables", "GRIPS") # x lupdate - VariableDescr = QadMsg.translate("Environment variables", "Controls the use of selection set grips for the Stretch, Move, Rotate, Scale, and Mirror Grip modes." + \ - "\n0 = Hides grips." + \ - "\n1 = Displays grips." + \ - "\n2 = Displays additional midpoint grips on polyline segments.") # x lupdate - VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type.") - self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(2), \ - QadVariableTypeEnum.INT, \ - 0, 2, \ - VariableDescr) - - # GRIPSIZE (int): Imposta la dimensione in pixel dei simboli di grip - VariableName = QadMsg.translate("Environment variables", "GRIPSIZE") # x lupdate - VariableDescr = QadMsg.translate("Environment variables", "Grip symbol size in pixel.") # x lupdate - VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type.") - self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(10), \ - QadVariableTypeEnum.INT, \ - 1, 999, \ - VariableDescr) - - # INPUTSEARCHDELAY (int): Controlla il tempo trascorso prima che le funzionalità di tastiera vengano visualizzate sulla riga di comando - VariableName = QadMsg.translate("Environment variables", "INPUTSEARCHDELAY") # x lupdate - VariableDescr = QadMsg.translate("Environment variables", "Controls the amount of time that elapses before automated keyboard features display at the Command prompt." + \ - "\nValid values are real numbers from 100 to 10,000, which represent milliseconds." + \ - "\nThe time delay setting in the INPUTSEARCHOPTIONS system variable must be turned on for INPUTSEARCHDELAY to have an effect.") # x lupdate - VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type.") - self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(300), \ - QadVariableTypeEnum.INT, \ - 100, 10000, \ - VariableDescr) - - # INPUTSEARCHOPTIONS (int): Controlla i tipi di funzioni automatiche della tastiera disponibili dalla riga di comando - VariableName = QadMsg.translate("Environment variables", "INPUTSEARCHOPTIONS") # x lupdate - VariableDescr = QadMsg.translate("Environment variables", "Controls what types of automated keyboard features are available at the Command prompt." + \ - "\nThe setting is stored as a bitcode using the sum of the following values:" + \ - "\n 0 = Turns off all automated keyboard features when typing at the Command prompt." + \ - "\n 1 = Turns on any automated keyboard features when typing at the Command prompt." + \ - "\n 2 = Automatically appends suggestions as each keystroke is entered after the second keystroke." + \ - "\n 4 = Displays a list of suggestions as keystrokes are entered." + \ - "\n 8 = Displays the icon of the command or system variable, if available." + \ - "\n16 = Excludes the display of system variables.") # x lupdate - VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type.") - self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(15), \ - QadVariableTypeEnum.INT, \ - 0, 31, \ - VariableDescr) - - # OFFSETDIST(float): Setta la distanza di default per l'offset - # < 0 offset di un oggetto attraverso un punto - # >= 0 offset di un oggetto attraverso la distanza - VariableName = QadMsg.translate("Environment variables", "OFFSETDIST") # x lupdate - VariableDescr = QadMsg.translate("Environment variables", "Sets the default offset distance:" + \ - "\n<0 = Offsets an object through a specified point." + \ - "\n>=0 = Sets the default offset distance.") # x lupdate - VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Real type.") - self.__VariableValuesDict[VariableName] = QadVariable(VariableName, float(-1.0), \ - QadVariableTypeEnum.FLOAT, \ - None, None, \ - VariableDescr) - - # OFFSETGAPTYPE (int): - # 0 = Estende i segmenti di linea alle relative intersezioni proiettate - # 1 = Raccorda i segmenti di linea in corrispondenza delle relative intersezioni proiettate. - # Il raggio di ciascun segmento di arco é uguale alla distanza di offset - # 2 = Cima i segmenti di linea in corrispondenza delle intersezioni proiettate. - # La distanza perpendicolare da ciascuna cima al rispettivo vertice - # sull'oggetto originale é uguale alla distanza di offset. - VariableName = QadMsg.translate("Environment variables", "OFFSETGAPTYPE") # x lupdate - VariableDescr = QadMsg.translate("Environment variables", "Controls how potential gaps between segments are treated when polylines are offset:" + \ - "\n0 = Extends line segments to their projected intersections." + \ - "\n1 = Fillets line segments at their projected intersections. The radius of each arc segment is equal to the offset distance." + \ - "\n2 = Chamfers line segments at their projected intersections. The perpendicular distance from each chamfer to its corresponding vertex on the original object is equal to the offset distance.") # x lupdate - VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type.") - self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(0), \ - QadVariableTypeEnum.INT, \ - 0, 2, \ - VariableDescr) - - # ORTHOMODE (int): - # 0 = modalità di movimento ortogonale cursore disabilitata - # 1 = modalità di movimento ortogonale cursore abilitata - VariableName = QadMsg.translate("Environment variables", "ORTHOMODE") # x lupdate - VariableDescr = QadMsg.translate("Environment variables", "Constrains cursor movement to the perpendicular." + \ - "\nWhen ORTHOMODE is turned on, the cursor can move only horizontally or vertically:" + \ - "\n0 = Turns off Ortho mode." + \ - "\n1 = Turns on Ortho mode.") # x lupdate - VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type.") - self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(0), \ - QadVariableTypeEnum.INT, \ - 0, 1, \ - VariableDescr) - - # OSCOLOR (str): Imposta il colore (RGB) dei simboli di osnap - VariableName = QadMsg.translate("Environment variables", "OSCOLOR") # x lupdate - VariableDescr = QadMsg.translate("Environment variables", "Osnap symbols color (RGB, #FF0000 = red).") # x lupdate - VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Character type.") - self.__VariableValuesDict[VariableName] = QadVariable(VariableName, unicode("#FF0000"), \ - QadVariableTypeEnum.COLOR, \ - None, None, \ - VariableDescr) # red - - # OSMODE (int): Imposta lo snap ad oggetto (somma di bit): - # 0 = (NON) nessuno - # 1 = (FIN) punti finali di ogni segmento - # 2 = (MED) punto medio - # 4 = (CEN) centroide di un poligono - # 8 = (NOD) ad oggetto punto - # 16 = (QUA) punto quadrante di un poligono - # 32 = (INT) intersezione di un oggetto (anche i vertici intermedi di una linestring o polygon) - # 64 = (INS) punto di inserimento di oggetti (come 8) - # 128 = (PER) punto perpendicolare a un oggetto - # 256 = (TAN) tangente di un arco, di un cerchio, di un'ellisse, di un arco ellittico o di una spline - # 512 = (NEA) punto più vicino di un oggetto - # 1024 = (C) Cancella tutti gli snap ad oggetto - # 2048 = (APP) intersezione apparente di due oggetti che non si intersecano nello spazio 3D - # ma che possono apparire intersecanti nella vista corrente - # 4096 = (EST) Estensione : Visualizza una linea o un arco di estensione temporaneo quando si sposta il cursore sul punto finale degli oggetti, - # in modo che sia possibile specificare punti sull'estensione - # 8192 = (PAR) Parallelo: Vincola un segmento di linea, un segmento di polilinea, un raggio o una xlinea ad essere parallela ad un altro oggetto lineare - # 16384 = osnap off - # 65536 = (PR) Distanza progressiva - # 131072 = intersezione sull'estensione - # 262144 = perpendicolare differita - # 524288 = tangente differita - # 1048576 = puntamento polare - # 2097152 = punti finali dell'intera polilinea - VariableName = QadMsg.translate("Environment variables", "OSMODE") # x lupdate - VariableDescr = QadMsg.translate("Environment variables", "Sets running object snaps." + \ - "\nThe setting is stored as a bitcode using the sum of the following values:" + \ - "\n0 = NONe." + \ - "\n1 = ENDpoint." + \ - "\n2 = MIDpoint." + \ - "\n4 = CENter." + \ - "\n8 = NODe." + \ - "\n16 = QUAdrant." + \ - "\n32 = INTersection." + \ - "\n64 = INSertion." + \ - "\n128 = PERpendicular." + \ - "\n256 = TANgent." + \ - "\n512 = NEArest." + \ - "\n1024 = QUIck." + \ - "\n2048 = APParent Intersection." + \ - "\n4096 = EXTension." + \ - "\n8192 = PARallel." + \ - "\n65536 = PRogressive distance (PR[dist])." + \ - "\n131072 = Intersection on extension (EXT_INT)." + \ - "\n2097152 = Final points on polyline (FIN_PL).") # x lupdate - VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type.") - self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(0), \ - QadVariableTypeEnum.INT, \ - 0, None, \ - VariableDescr) - - # OSPROGRDISTANCE (float): Distanza progressiva per snap PR - VariableName = QadMsg.translate("Environment variables", "OSPROGRDISTANCE") # x lupdate - VariableDescr = QadMsg.translate("Environment variables", "Progressive distance for snap mode.") # x lupdate - VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Real type.") - self.__VariableValuesDict[VariableName] = QadVariable(VariableName, float(0.0), \ - QadVariableTypeEnum.FLOAT, \ - None, None, \ - VariableDescr) - - # OSSIZE (int): Imposta la dimensione in pixel dei simboli di osnap - VariableName = QadMsg.translate("Environment variables", "OSSIZE") # x lupdate - VariableDescr = QadMsg.translate("Environment variables", "Osnap symbol size in pixel.") # x lupdate - VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type.") - self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(13), \ - QadVariableTypeEnum.INT, \ - 1, 999, \ - VariableDescr) - - # PICKADD (int): Controlla se le selezioni successive sostituiscono il gruppo di selezione corrente o vengono aggiunte ad esso. - VariableName = QadMsg.translate("Environment variables", "PICKADD") # x lupdate - VariableDescr = QadMsg.translate("Environment variables", "Controls whether subsequent selections replace the current selection set or add to it." + \ - "\n0 = Turns off PICKADD. The objects most recently selected become the selection set. Previously selected objects are removed from the selection set. Add more objects to the selection set by pressing SHIFT while selecting." + \ - "\n1 = Turns on PICKADD. Each object selected, either individually or by windowing, is added to the current selection set. To remove objects from the set, press SHIFT while selecting." + \ - "\n2 = Turns on PICKADD. Each object selected, either individually or by windowing, is added to the current selection set. To remove objects from the set, press SHIFT while selecting. Keeps objects selected after the SELECT command ends. ") # x lupdate - VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type.") - self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(0), \ - QadVariableTypeEnum.INT, \ - 0, 2, \ - VariableDescr) - - # PICKBOX (int): Imposta la dimensione in pixel della distanza di selezione degli oggetti - # dalla posizione corrente del puntatore - VariableName = QadMsg.translate("Environment variables", "PICKBOX") # x lupdate - VariableDescr = QadMsg.translate("Environment variables", "Sets the object selection target height, in pixels.") # x lupdate - VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type.") - self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(5), \ - QadVariableTypeEnum.INT, \ - 1, 999, \ - VariableDescr) - - # PICKBOXCOLOR (str): Imposta il colore (RGB) del quadratino di selezione degli oggetti - VariableName = QadMsg.translate("Environment variables", "PICKBOXCOLOR") # x lupdate - VariableDescr = QadMsg.translate("Environment variables", "Sets the object selection target color (RGB, #FF0000 = red).") # x lupdate - VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Character type.") - self.__VariableValuesDict[VariableName] = QadVariable(VariableName, unicode("#FF0000"), \ - QadVariableTypeEnum.COLOR, \ - None, None, \ - VariableDescr) # red - - # PICKFIRST (int): Controlla se è possibile selezionare gli oggetti prima di eseguire un comando. - VariableName = QadMsg.translate("Environment variables", "PICKFIRST") # x lupdate - VariableDescr = QadMsg.translate("Environment variables", "Controls whether you can select objects before you start a command." + \ - "\n0 = Off. You can select objects only after you start a command." + \ - "\n1 = On. You can also select objects before you start a command") # x lupdate - VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type.") - self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(1), \ - QadVariableTypeEnum.INT, \ - 0, 1, \ - VariableDescr) - - # POLARANG (float): incremento dell'angolo polare per il puntamento polare (gradi) - VariableName = QadMsg.translate("Environment variables", "POLARANG") # x lupdate - VariableDescr = QadMsg.translate("Environment variables", "Sets the polar angle increment (degree).") # x lupdate - VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Real type.") - self.__VariableValuesDict[VariableName] = QadVariable(VariableName, float(90.0), \ - QadVariableTypeEnum.INT, \ - 0.000001, 359.999999, \ - VariableDescr) - - # SELECTIONAREA (int): Controlla gli effetti della visualizzazione della selezione di aree. - # dalla posizione corrente del puntatore - VariableName = QadMsg.translate("Environment variables", "SELECTIONAREA") # x lupdate - VariableDescr = QadMsg.translate("Environment variables", "Controls the display of effects for selection areas." + \ - "\nSelection areas are created by the Window, Crossing, WPolygon, CPolygon, WCircle, CCircle, WObjects, CObjects, WBuffer and CBuffer options of SELECT." + \ - "\n0 = Off" + \ - "\n1 = On") # x lupdate - VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type.") - self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(1), \ - QadVariableTypeEnum.INT, \ - 0, 1, \ - VariableDescr) - - # SELECTIONAREAOPACITY (int): Controlla gli effetti della visualizzazione della selezione di aree. - # dalla posizione corrente del puntatore - VariableName = QadMsg.translate("Environment variables", "SELECTIONAREAOPACITY") # x lupdate - VariableDescr = QadMsg.translate("Environment variables", "Controls the transparency of the selection area during window and crossing selection." + \ - "\nThe valid range is 0 to 100. The lower the setting, the more transparent the area. A value of 100 makes the area opaque. The SELECTIONAREA system variable must be on.") # x lupdate - VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type.") - self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(25), \ - QadVariableTypeEnum.INT, \ - 0, 100, \ - VariableDescr) - - # SUPPORTPATH (str): Path di ricerca per i files di supporto - default = os.path.abspath(os.path.dirname(__file__) + "\\support") - VariableName = QadMsg.translate("Environment variables", "SUPPORTPATH") # x lupdate - VariableDescr = QadMsg.translate("Environment variables", "Searching path for support files.") # x lupdate - VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Character type.") - self.__VariableValuesDict[VariableName] = QadVariable(VariableName, default, \ - QadVariableTypeEnum.STRING, \ - None, None, \ - VariableDescr) - - # SHOWTEXTWINDOW (bool): Visualizza la finestra di testo all'avvio - VariableName = QadMsg.translate("Environment variables", "SHOWTEXTWINDOW") # x lupdate - VariableDescr = QadMsg.translate("Environment variables", "Show the text window at startup.") # x lupdate - VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Boolean type.") - self.__VariableValuesDict[VariableName] = QadVariable(VariableName, True, \ - QadVariableTypeEnum.BOOL, \ - None, None, \ - VariableDescr) - - # TOLERANCE2APPROXCURVE (float): - # massimo errore tollerato tra una vera curva e quella approssimata dai segmenti retti - # (nel sistema map-coordinate) - VariableName = QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE") # x lupdate - VariableDescr = QadMsg.translate("Environment variables", "Maximum error approximating a curve to segments.") # x lupdate - VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Real type.") - self.__VariableValuesDict[VariableName] = QadVariable(VariableName, float(0.1), \ - QadVariableTypeEnum.FLOAT, \ - 0.000001, None, \ - VariableDescr) - - # WINDOWAREACOLOR (str): Imposta il colore (RGB) dell'area di selezione degli oggetti nel modo finestra - VariableName = QadMsg.translate("Environment variables", "WINDOWAREACOLOR") # x lupdate - VariableDescr = QadMsg.translate("Environment variables", "Controls the color of the transparent selection area during window selection (RGB, #1F78B4 = blu)." + \ - "\nThe SELECTIONAREA system variable must be on.") # x lupdate - VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Character type.") - self.__VariableValuesDict[VariableName] = QadVariable(VariableName, unicode("#1F78B4"), \ - QadVariableTypeEnum.COLOR, \ - None, None, \ - VariableDescr) # blue - - - def getVarNames(self): - """ - Ritorna la lista dei nomi delle variabili - """ - return self.__VariableValuesDict.keys() - - def set(self, VarName, VarValue): - """ - Modifica il valore di una variabile - """ - UpperVarName = VarName.upper() - variable = self.getVariable(UpperVarName) - - if variable is None: # se non c'è la variablie - self.__VariableValuesDict[UpperVarName] = QadVariable(UpperVarName, VarValue, \ - QadVariableTypeEnum.UNKNOWN, \ - None, None, \ - "") - else: - if type(variable.value) != type(VarValue): - if not((type(variable.value) == unicode or type(variable.value) == str) and - (type(VarValue) == unicode or type(VarValue) == str)): - return False - if variable.typeValue == QadVariableTypeEnum.COLOR: - if len(VarValue) == 7: # es. "#FF0000" - if VarValue[0] != "#": - return False - else: - return False - elif variable.typeValue == QadVariableTypeEnum.FLOAT or \ - variable.typeValue == QadVariableTypeEnum.INT: - if variable.minNum is not None: - if VarValue < variable.minNum: - return False - if variable.maxNum is not None: - if VarValue > variable.maxNum: - return False - - self.__VariableValuesDict[UpperVarName].value = VarValue - - return True - - def get(self, VarName, defaultValue = None): - """ - Restituisce il valore di una variabile - """ - variable = self.getVariable(VarName) - if variable is None: - result = defaultValue - else: - result = variable.value - - return result - - def getVariable(self, VarName): - UpperVarName = VarName - return self.__VariableValuesDict.get(UpperVarName.upper()) - - - def getDefaultQadIniFilePath(self): - # ottiene il percorso automatico incluso il nome del file dove salvare il file qad.ini - # se esiste un progetto caricato il percorso è quello del progetto - prjFileInfo = QFileInfo(QgsProject.instance().fileName()) - path = prjFileInfo.absolutePath() - if len(path) == 0: - # se non esiste un progetto caricato uso il percorso di installazione di qad - path = QDir.cleanPath(QgsApplication.qgisSettingsDirPath() + "python/plugins/qad") - return path + "/" + "qad.ini" - - - def save(self, Path=""): - """ - Salva il dizionario delle variabili su file - """ - if Path == "": - # Se la path non é indicata uso il file "qad.ini" del progetto o dell'installazione di qad - Path = self.getDefaultQadIniFilePath() - - dir = QFileInfo(Path).absoluteDir() - if not dir.exists(): - os.makedirs(dir.absolutePath()) - - file = open(Path, "w") # apre il file in scrittura - for VarName in self.__VariableValuesDict.keys(): - VarValue = self.get(VarName) - # scrivo il valore + il tipo (es var = 5 ) - VarValue = str(VarValue) + " " + str(type(VarValue)) - Item = "%s = %s\n" % (VarName, VarValue) - file.write(Item) - - file.close() - - def load(self, Path=""): - """ - Carica il dizionario delle variabili da file - Ritorna True in caso di successo, false in caso di errore - """ - # svuoto il dizionario e lo reimposto con i valori di default - self.__VariableValuesDict.clear() - self.__init__() - if Path == "": - # Se la path non é indicata uso il file "qad.ini" del progetto o dell'installazione di qad - Path = self.getDefaultQadIniFilePath() - - if not os.path.exists(Path): - return False - - file = open(Path, "r") # apre il file in lettura - for line in file: - # leggo il valore + il tipo (es var = 5 ) - sep = line.rfind(" = ") - VarName = line[0:sep] - VarName = VarName.strip(" ") # rimuovo gli spazi prima e dopo - VarValue = line[sep+3:] - sep = VarValue.rfind(" ") - VarType = VarValue[sep+8:sep2] - VarValue = VarValue[:sep] - if VarType == "int": - VarValue = qad_utils.str2int(VarValue) - if VarValue is None: - self.set(VarName, int(0)) - else: - self.set(VarName, VarValue) - elif VarType == "long": - VarValue = qad_utils.str2long(VarValue) - if VarValue is None: - self.set(VarName, long(0)) - else: - self.set(VarName, VarValue) - elif VarType == "float": - VarValue = qad_utils.str2float(VarValue) - if VarValue is None: - self.set(VarName, float(0)) - else: - self.set(VarName, VarValue) - elif VarType == "bool": - VarValue = qad_utils.str2bool(VarValue) - if VarValue is None: - self.set(VarName, False) - else: - self.set(VarName, VarValue) - else: - self.set(VarName, str(VarValue)) - - file.close() - - return True - - -#=============================================================================== -# = variabile globale -#=============================================================================== - -QadVariables = QadVariablesClass() +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + classe per gestire le variabili di ambiente + + ------------------- + begin : 2013-05-22 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from qgis.PyQt.QtCore import * +import os.path +from qgis.core import * +import math + + +from . import qad_utils +from .qad_msg import QadMsg + + +# =============================================================================== +# Qad variable type class. +# =============================================================================== +class QadVariableTypeEnum(): + UNKNOWN = 0 # sconosciuto (non gestito da QAD) + STRING = 1 # caratteri + COLOR = 2 # colore espresso in caratteri (es. rosso = "#FF0000") + INT = 3 # numero intero + FLOAT = 4 # numero con decimali + BOOL = 5 # booleano (True o False) + + +# =============================================================================== +# Qad AUTOSNAP class. +# =============================================================================== +class QadAUTOSNAPEnum(): + DISPLAY_MARK = 1 # Turns on the AutoSnap mark + DISPLAY_TOOLTIPS = 2 # Turns on the AutoSnap tooltips + MAGNET = 4 # Turns on the AutoSnap magnet + POLAR_TRACKING = 8 # Turns on polar tracking + OBJ_SNAP_TRACKING = 16 # Turns on object snap tracking + DISPLAY_TOOLTIPS_POLAR_OSNAP_TRACKING_ORTHO = 32 # Turns on tooltips for polar tracking, object snap tracking, and Ortho mode + + +# =============================================================================== +# Qad INPUTSEARCHOPTIONS class. +# =============================================================================== +class QadINPUTSEARCHOPTIONSEnum(): + ON = 1 # Turns off all automated keyboard features when typing at the Command prompt + AUTOCOMPLETE = 2 # Automatically appends suggestions as each keystroke is entered after the second keystroke + DISPLAY_LIST = 4 # Displays a list of suggestions as keystrokes are entered + DISPLAY_ICON = 8 # Displays the icon of the command or system variable, if available + EXCLUDE_SYS_VAR = 16 # Excludes the display of system variables + + +# =============================================================================== +# Qad POLARMODE class. +# =============================================================================== +class QadPOLARMODEnum(): + MEASURE_RELATIVE_ANGLE = 1 # if setted: Measure polar angles from selected objects (relative) + # if not setted: Measure polar angles based on current UCS (absolute) + POLAR_TRACKING = 2 # if setted: Use polar tracking settings in object snap tracking + # if not setted: Track orthogonally only + ADDITIONAL_ANGLES = 4 # if setted: Use additional polar tracking angles + SHIFT_TO_ACQUIRE = 8 # if setted: Press Shift to acquire object snap tracking points, + # if not setted: Acquire automatically object snap tracking points + + +# =============================================================================== +# Qad DELOBJ class. +# =============================================================================== +class QadDELOBJEnum(): + ALL_RETAINED = 0 # All defining geometry is retained + DELETE_ALL = 1 # Delete all defining geometry + ASK_FOR_DELETE_ALL = -1 # Ask the user for delete all defining geometry + + +# =============================================================================== +# Qad GRIPMULTIFUNCTIONAL class. +# =============================================================================== +class QadGRIPMULTIFUNCTIONALEnum(): + ON_CTRL_CYCLE_AND_HOT_GRIPT = 1 # Access multi-functional grips with Ctrl-cycling and the Hot Grip shortcut menu + ON_DYNAMIC_MENU_AND_HOT_GRIPT = 2 # Access multi-functional grips with the dynamic menu and the Hot Grip shortcut menu + + +# =============================================================================== +# Qad global or project class. +# =============================================================================== +class QadVariableLevelEnum(): + GLOBAL = 0 # variabile globale di QAD + PROJECT = 2 # variabile del progetto corrente (che sovrascrive quella globale) + + +# =============================================================================== +# Qad variable class. +# =============================================================================== +class QadVariable(): + """ + Classe che gestisce le variabili di ambiente di QAD + """ + + def __init__(self, name, value, typeValue, minNum = None, maxNum = None, descr = "", level = QadVariableLevelEnum.GLOBAL): + self.name = name + self.value = value + self.typeValue = typeValue + self.default = value + self.minNum = minNum + self.maxNum = maxNum + self.descr = descr + self.level = level # di tipo QadVariableLevelEnum + + +# =============================================================================== +# Qad variables class. +# =============================================================================== +class QadVariablesClass(): + """ + Classe che gestisce le variabili di ambiente di Qad + """ + + def __init__(self): + """ + Inizializza un dizionario con le variabili e i loro valori di default + """ + self.__VariableValuesDict = dict() # variabile privata - + + # APBOX (int): visualizza casella di apertura AutoSnap. Variabile globale. + VariableName = QadMsg.translate("Environment variables", "APBOX") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Turns the display of the AutoSnap aperture box on or off." + \ + "\nThe aperture box is displayed in the center of the crosshairs when you snap to an object.") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(0), \ + QadVariableTypeEnum.INT, \ + 0, 1, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # APERTURE (int): Determina la dimensione della casella di selezione dell'oggetto. Variabile globale. + VariableName = QadMsg.translate("Environment variables", "APERTURE") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Controls the size of the object target box, in pixels.") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(10), \ + QadVariableTypeEnum.INT, \ + 1, 50, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # ARCMINSEGMENTQTY (int): numero minimo di segmenti perché venga riconosciuto un arco. Variabile del progetto. + VariableName = QadMsg.translate("Environment variables", "ARCMINSEGMENTQTY") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Minimum number of segments to approximate an arc.") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "project variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(12), \ + QadVariableTypeEnum.INT, \ + 4, 999, \ + VariableDescr, \ + QadVariableLevelEnum.PROJECT) + + # AUTOSNAP (int): controlla la visualizzazione dei marcatori di autosnap (somma di bit). Variabile globale. + VariableName = QadMsg.translate("Environment variables", "AUTOSNAP") + VariableDescr = QadMsg.translate("Environment variables", "Controls the display of the AutoSnap marker, tooltip, and magnet." + \ + "\nAlso turns on polar and object snap tracking, and controls the display of polar tracking, object snap tracking, and Ortho mode tooltips." + \ + "\nThe setting is stored as a bitcode using the sum of the following values:" + \ + "\n0 = Turns off the AutoSnap marker, tooltips, and magnet. Also turns off polar tracking, object snap tracking, and tooltips for polar tracking, object snap tracking, and Ortho mode." + \ + "\n1 = Turns on the AutoSnap mark." + \ + "\n2 = Turns on the AutoSnap tooltips." + \ + "\n4 = Turns on the AutoSnap magnet." + \ + "\n8 = Turns on polar tracking." + \ + "\n16 = Turns on object snap tracking." + \ + "\n32 = Turns on tooltips for polar tracking, object snap tracking, and Ortho mode.") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(63), \ + QadVariableTypeEnum.INT, \ + 0, 64, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # AUTOSNAPCOLOR (str): Imposta il colore (RGB) dei marcatori autosnap. Variabile globale. + VariableName = QadMsg.translate("Environment variables", "AUTOSNAPCOLOR") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Controls the color of the AutoSnap marker (RGB, #33A02C = green).") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Character type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, unicode("#33A02C"), \ + QadVariableTypeEnum.COLOR, \ + None, None, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # AUTOSNAPSIZE (int): dimensione dei simboli di autosnap in pixel. Variabile globale. + VariableName = QadMsg.translate("Environment variables", "AUTOSNAPSIZE") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "AutoSnap marker size in pixel.") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(5), \ + QadVariableTypeEnum.INT, \ + 1, 20, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # AUTOTRECKINGVECTORCOLOR (str): Imposta il colore (RGB) del vettore autotrack (linee polari, linee di estensione). Variabile globale. + VariableName = QadMsg.translate("Environment variables", "AUTOTRECKINGVECTORCOLOR") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Autotreck vector color (RGB, #33A02C = green).") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Character type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, unicode("#33A02C"), \ + QadVariableTypeEnum.COLOR, \ + None, None, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # CIRCLEMINSEGMENTQTY (int): numero minimo di segmenti perché venga riconosciuto un cerchio. Variabile del progetto. + VariableName = QadMsg.translate("Environment variables", "CIRCLEMINSEGMENTQTY") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Minimum number of segments to approximate a circle.") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "project variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(12), \ + QadVariableTypeEnum.INT, \ + 6, 999, \ + VariableDescr, \ + QadVariableLevelEnum.PROJECT) + + # CMDHISTORYBACKCOLOR (str): Imposta il colore (RGB) di sfondo della finestra di cronologia dei comandi. Variabile globale. + VariableName = QadMsg.translate("Environment variables", "CMDHISTORYBACKCOLOR") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Command history background color (RGB, #C8C8C8 = grey).") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Character type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, unicode("#C8C8C8"), \ + QadVariableTypeEnum.COLOR, \ + None, None, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # CMDHISTORYFORECOLOR (str): Imposta il colore (RGB) del testo della finestra di cronologia dei comandi. Variabile globale. + VariableName = QadMsg.translate("Environment variables", "CMDHISTORYFORECOLOR") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Command history text color (RGB, #000000 = black).") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Character type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, unicode("#000000"), \ + QadVariableTypeEnum.COLOR, \ + None, None, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # CMDINPUTHISTORYMAX (int): Imposta il numero massimo di comandi nella lista di storicizzazione. Variabile globale. + VariableName = QadMsg.translate("Environment variables", "CMDINPUTHISTORYMAX") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Sets the maximum number of previous input values that are stored for a prompt in a command.") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(20), \ + QadVariableTypeEnum.INT, \ + 1, 999, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # CMDLINEBACKCOLOR (str): Imposta il colore (RGB) di sfondo della finestra dei comandi. Variabile globale. + VariableName = QadMsg.translate("Environment variables", "CMDLINEBACKCOLOR") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Active prompt background color (RGB, #FFFFFF = white).") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Character type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, unicode("#FFFFFF"), \ + QadVariableTypeEnum.COLOR, \ + None, None, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # CMDLINEFORECOLOR (str): Imposta il colore (RGB) del testo della finestra di cronologia dei comandi. Variabile globale. + VariableName = QadMsg.translate("Environment variables", "CMDLINEFORECOLOR") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Active prompt color (RGB, #000000 = black).") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Character type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, unicode("#000000"), \ + QadVariableTypeEnum.COLOR, \ + None, None, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # CMDLINEOPTBACKCOLOR (str): Imposta il colore (RGB) di sfondo della parola chiave opzione di comando. Variabile globale. + VariableName = QadMsg.translate("Environment variables", "CMDLINEOPTBACKCOLOR") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Command option keyword background color (RGB, #D2D2D2 = grey).") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Character type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, unicode("#D2D2D2"), \ + QadVariableTypeEnum.COLOR, \ + None, None, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # CMDLINEOPTCOLOR (str): Imposta il colore (RGB) della parola chiave opzione di comando. Variabile globale. + VariableName = QadMsg.translate("Environment variables", "CMDLINEOPTCOLOR") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Command option keyword color (RGB, #0000FF = blue).") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Character type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, unicode("#0000FF"), \ + QadVariableTypeEnum.COLOR, \ + None, None, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # CMDLINEOPTHIGHLIGHTEDCOLOR (str): Imposta il colore (RGB) della opzione di comando evidenziata. Variabile globale. + VariableName = QadMsg.translate("Environment variables", "CMDLINEOPTHIGHLIGHTEDCOLOR") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Command option highlighted color (RGB, #B3B3B3 = grey).") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Character type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, unicode("#B3B3B3"), \ + QadVariableTypeEnum.COLOR, \ + None, None, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # COPYMODE (int). Variabile globale. + # 0 = Imposta il comando COPIA in modo che venga ripetuto automaticamente + # 1 = Imposta il comando COPIA in modo da creare una singola copia + VariableName = QadMsg.translate("Environment variables", "COPYMODE") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Controls whether the COPY command repeats automatically:" + \ + "\n0 = Sets the COPY command to repeat automatically." + \ + "\n1 = Sets the COPY command to create a single copy.") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(0), \ + QadVariableTypeEnum.INT, \ + 0, 1, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # CROSSINGAREACOLOR (str): Imposta il colore (RGB) dell'area di selezione degli oggetti nel modo intersezione. Variabile globale. + VariableName = QadMsg.translate("Environment variables", "CROSSINGAREACOLOR") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Controls the color of the transparent selection area during crossing selection (RGB, #00FF3F = green)." + \ + "\nThe SELECTIONAREA system variable must be on.") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Character type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, unicode("#00FF3F"), \ + QadVariableTypeEnum.COLOR, \ + None, None, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) # green + + # CURSORCOLOR (str): Imposta il colore (RGB) del cursore (la croce). Variabile globale. + VariableName = QadMsg.translate("Environment variables", "CURSORCOLOR") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Cross pointer color (RGB, #FF0000 = red).") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Character type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, unicode("#FF0000"), \ + QadVariableTypeEnum.COLOR, \ + None, None, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # CURSORSIZE (int): Imposta la dimensione in pixel del cursore (la croce). Variabile globale. + VariableName = QadMsg.translate("Environment variables", "CURSORSIZE") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Determines the size of the crosshairs as a percentage of the screen size.") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(5), \ + QadVariableTypeEnum.INT, \ + 1, 100, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # DELOBJ (int): Controlla se la geometria utilizzata per creare altri oggetti viene mantenuta o eliminata. Variabile globale. + VariableName = QadMsg.translate("Environment variables", "DELOBJ") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Controls whether geometry used to create other objects is retained or deleted:" + \ + "\n0 = All defining geometry is retained." + \ + "\n1 = Deletes all defining geometry." + \ + "\n-1 = Ask the user for delete all defining geometry.") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(1), \ + QadVariableTypeEnum.INT, \ + -1, 1, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # DIMSTYLE (str): Imposta il nome dello stile di quotatura corrente. Variabile del progetto. + VariableName = QadMsg.translate("Environment variables", "DIMSTYLE") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Stores the name of the current dimension style.") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Character type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "project variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, unicode(""), \ + QadVariableTypeEnum.STRING, \ + None, None, \ + VariableDescr, \ + QadVariableLevelEnum.PROJECT) + + # DYNDIGRIP (int): Controlla la visualizzazione delle quote dinamiche durante la modifica dello stiramento dei grip. Variabile globale. + # La variabile di sistema DYNDIVIS deve essere impostata su 2 per visualizzare tutte le quote dinamiche. + # L'impostazione è memorizzata come codice binario che utilizza la somma dei valori + VariableName = QadMsg.translate("Environment variables", "DYNDIGRIP") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Turns Dynamic Input features on and off." + \ + "\n0 = None." + \ + "\n1 = Resulting dimension." + \ + "\n2 = Length change dimension." + \ + "\n4 = Absolute angle dimension." + \ + "\n8 = Angle change dimension.") # x lupdate + #"\n16 = Arc radius dimension.") # non supportato per ora + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(31), \ + QadVariableTypeEnum.INT, \ + 0, 31, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # DYNDIVIS (int): Controlla il numero di quote dinamiche visualizzate durante la modifica dello stiramento dei grip. Variabile globale. + VariableName = QadMsg.translate("Environment variables", "DYNDIVIS") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Controls how many dynamic dimensions are displayed during grip stretch editing." + \ + "\n0 = Only the first dynamic dimension in the cycle order." + \ + "\n1 = Only the first two dynamic dimensions in the cycle order." + \ + "\n2 = All dynamic dimensions, as controlled by the DYNDIGRIP system variable.") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(1), \ + QadVariableTypeEnum.INT, \ + 0, 2, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # DYNEDITFORECOLOR (str): Imposta il colore (RGB) del testo della finestra di input dinamico. Variabile globale. + VariableName = QadMsg.translate("Environment variables", "DYNEDITFORECOLOR") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Dynamic input text color (RGB, #000000 = black).") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Character type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, unicode("#000000"), \ + QadVariableTypeEnum.COLOR, \ + None, None, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # DYNEDITBACKCOLOR (str): Imposta il colore (RGB) dello sfondo della finestra di input dinamico. Variabile globale. + VariableName = QadMsg.translate("Environment variables", "DYNEDITBACKCOLOR") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Dynamic input background text color (RGB, #939393 = gray).") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Character type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, unicode("#939393"), \ + QadVariableTypeEnum.COLOR, \ + None, None, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # DYNEDITBORDERCOLOR (str): Imposta il colore (RGB) del bordo della finestra di input dinamico. Variabile globale. + VariableName = QadMsg.translate("Environment variables", "DYNEDITBORDERCOLOR") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Dynamic input border color (RGB, #000000 = black).") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Character type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, unicode("#000000"), \ + QadVariableTypeEnum.COLOR, \ + None, None, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # DYNMODE (int): Attiva e disattiva le funzioni di input dinamico. Variabile globale. + VariableName = QadMsg.translate("Environment variables", "DYNMODE") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Turns Dynamic Input features on and off." + \ + "\n0 = All Dynamic Input features, including dynamic prompts, off." + \ + "\n1 = Pointer input on." + \ + "\n2 = Dimensional input on." + \ + "\n3 = Both pointer input and dimensional input on.") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(3), \ + QadVariableTypeEnum.INT, \ + -3, 3, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # DYNPICOORDS (int): Determina se l'input del puntatore utilizza un formato relativo o assoluto per le coordinate. Variabile globale. + VariableName = QadMsg.translate("Environment variables", "DYNPICOORDS") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Controls whether pointer input uses relative or absolute format for coordinates." + \ + "\n0 = Relative." + \ + "\n1 = Absolute.") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(0), \ + QadVariableTypeEnum.INT, \ + 0, 1, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # DYNPIFORMAT (int): Determina se l'input del puntatore utilizza un formato polare o cartesiano per le coordinate. Variabile globale. + VariableName = QadMsg.translate("Environment variables", "DYNPIFORMAT") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Controls whether pointer input uses polar or cartesian format for coordinates." + \ + "\n0 = Polar." + \ + "\n1 = Cartesian.") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(0), \ + QadVariableTypeEnum.INT, \ + 0, 1, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # DYNPIVIS (int): Controlla quando è visualizzato l'input puntatore. Variabile globale. + VariableName = QadMsg.translate("Environment variables", "DYNPIVIS") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Controls when pointer input is displayed." + \ + "\n1 = Automatically at a prompt for a point." + \ + "\n2 = Always.") # x lupdate + #"\n0 = Only when you type at a prompt for a point." + \ non supportato, per ora + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(1), \ + QadVariableTypeEnum.INT, \ + 0, 2, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # DYNPROMPT (int): Controlla la visualizzazione dei messaggi di richiesta nelle descrizioni di input dinamico. Variabile globale. + VariableName = QadMsg.translate("Environment variables", "DYNPROMPT") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Controls display of prompts in Dynamic Input tooltips." + \ + "\n0 = Off." + \ + "\n1 = On.") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(1), \ + QadVariableTypeEnum.INT, \ + 0, 1, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # DYNTOOLTIPS (int): Determina su quali tooltip hanno effetto le impostazioni dell'aspetto delle descrizioni. Variabile globale. + VariableName = QadMsg.translate("Environment variables", "DYNTOOLTIPS") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Controls which tooltips are affected by tooltip appearance settings." + \ + "\n0 = Only Dynamic Input value fields ." + \ + "\n1 = All drafting tooltips.") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(1), \ + QadVariableTypeEnum.INT, \ + 0, 1, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # DYNTRECKINGVECTORCOLOR (str): Imposta il colore (RGB) del vettore track per l'imput dinamico (linee di estensione). Variabile globale. + VariableName = QadMsg.translate("Environment variables", "DYNTRECKINGVECTORCOLOR") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Autotreck vector color (RGB, #969C9A = gray).") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Character type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, unicode("#969C9A"), \ + QadVariableTypeEnum.COLOR, \ + None, None, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # EDGEMODE (int): Controlla i comandi ESTENDI e TAGLIA. Variabile globale. + # O = Vengono usate le dimensioni reali degli oggetti di riferimento + # 1 = Vengono usate le estensioni degli oggetti di riferimento (es. un arco viene considerato cerchio) + VariableName = QadMsg.translate("Environment variables", "EDGEMODE") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Controls how the TRIM and EXTEND commands determine cutting and boundary edges:" + \ + "\n0 = Uses the selected edge without an extensions." + \ + "\n1 = Extends or trims the selected object to an imaginary extension of the cutting or boundary edge.") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(0), \ + QadVariableTypeEnum.INT, \ + 0, 1, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # ELLIPSEMINSEGMENTQTY (int): numero minimo di segmenti perché venga riconosciuta una ellisse. Variabile del progetto. + VariableName = QadMsg.translate("Environment variables", "ELLIPSEMINSEGMENTQTY") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Minimum number of segments to approximate an ellipse.") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "project variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(12), \ + QadVariableTypeEnum.INT, \ + 8, 999, \ + VariableDescr, \ + QadVariableLevelEnum.PROJECT) + + # ELLIPSEARCMINSEGMENTQTY (int): numero minimo di segmenti perché venga riconosciuto un arco di ellisse. Variabile del progetto. + VariableName = QadMsg.translate("Environment variables", "ELLIPSEARCMINSEGMENTQTY") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Minimum number of segments to approximate an arc of ellipse.") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "project variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(12), \ + QadVariableTypeEnum.INT, \ + 8, 999, \ + VariableDescr, \ + QadVariableLevelEnum.PROJECT) + + # FILLETRAD (float): raggio applicato per raccordare (gradi). Variabile del progetto. + VariableName = QadMsg.translate("Environment variables", "FILLETRAD") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Stores the current fillet radius.") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Real type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "project variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, float(0.0), \ + QadVariableTypeEnum.FLOAT, \ + 0.000001, None, \ + VariableDescr, \ + QadVariableLevelEnum.PROJECT) + + # GRIPCOLOR (str): Imposta il colore (RGB) dei grip non selezionati. Variabile globale. + VariableName = QadMsg.translate("Environment variables", "GRIPCOLOR") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Controls the color of unselected grips (RGB, #100DD6 = blue).") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Character type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, unicode("#100DD6"), \ + QadVariableTypeEnum.COLOR, \ + None, None, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) # blu + + # GRIPCONTOUR (str): Imposta il colore (RGB) del bordo dei grip. Variabile globale. + VariableName = QadMsg.translate("Environment variables", "GRIPCONTOUR") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Controls the color of the grip contour (RGB, #939393 = gray).") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Character type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, unicode("#939393"), \ + QadVariableTypeEnum.COLOR, \ + None, None, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) # gray + + # GRIPHOT(str): Imposta il colore (RGB) dei grip selezionati. Variabile globale. + VariableName = QadMsg.translate("Environment variables", "GRIPHOT") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Controls the color of selected grips (RGB, #FF0000 = red).") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Character type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, unicode("#FF0000"), \ + QadVariableTypeEnum.COLOR, \ + None, None, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) # red + + # GRIPHOVER(str): Imposta il colore (RGB) dei grip non selezionati quando il cursore si ferma su di essi. Variabile globale. + VariableName = QadMsg.translate("Environment variables", "GRIPHOVER") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Controls the fill color of an unselected grip when the cursor pauses over it (RGB, #FF7F7F = orange).") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Character type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, unicode("#FF7F7F"), \ + QadVariableTypeEnum.COLOR, \ + None, None, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) # orange + + # GRIPMULTIFUNCTIONAL (int): Specifica i metodi di accesso per le opzioni dei grip multifunzionali. Variabile globale. + VariableName = QadMsg.translate("Environment variables", "GRIPMULTIFUNCTIONAL") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Specifies the access methods to multi-functional grips." + \ + "\n0 = Access to multi-functional grips is disabled." + \ + "\n2 = Access multi-functional grips with the dynamic menu and the Hot Grip shortcut menu.") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(3), \ + QadVariableTypeEnum.INT, \ + 0, 3, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # GRIPOBJLIMIT (int): Controlla la visualizzazione dei grip in base al numero di oggetti selezionati. Variabile globale. + VariableName = QadMsg.translate("Environment variables", "GRIPOBJLIMIT") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Suppresses the display of grips when the selection set includes more than the specified number of objects." + \ + "\nThe valid range is 0 to 32,767. For example, when set to 1, grips are suppressed when more than one object is selected." + \ + "\nWhen set to 0, grips are always displayed.") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(100), \ + QadVariableTypeEnum.INT, \ + 0, 32767, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # GRIPS (int): Controlla la visualizzazione dei grip sugli oggetti selezionati. Variabile globale. + VariableName = QadMsg.translate("Environment variables", "GRIPS") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Controls the use of selection set grips for the Stretch, Move, Rotate, Scale, and Mirror Grip modes." + \ + "\n0 = Hides grips." + \ + "\n1 = Displays grips." + \ + "\n2 = Displays additional midpoint grips on polyline segments.") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(2), \ + QadVariableTypeEnum.INT, \ + 0, 2, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # GRIPSIZE (int): Imposta la dimensione in pixel dei simboli di grip. Variabile globale. + VariableName = QadMsg.translate("Environment variables", "GRIPSIZE") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Grip symbol size in pixel.") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(5), \ + QadVariableTypeEnum.INT, \ + 1, 999, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # INPUTSEARCHDELAY (int): Controlla il tempo trascorso prima che le funzionalità di tastiera vengano visualizzate sulla riga di comando. + # Variabile globale. + VariableName = QadMsg.translate("Environment variables", "INPUTSEARCHDELAY") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Controls the amount of time that elapses before automated keyboard features display at the Command prompt." + \ + "\nValid values are real numbers from 100 to 10,000, which represent milliseconds." + \ + "\nThe time delay setting in the INPUTSEARCHOPTIONS system variable must be turned on for INPUTSEARCHDELAY to have an effect.") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(500), \ + QadVariableTypeEnum.INT, \ + 100, 10000, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # INPUTSEARCHOPTIONS (int): Controlla i tipi di funzioni automatiche della tastiera disponibili dalla riga di comando. Variabile globale. + VariableName = QadMsg.translate("Environment variables", "INPUTSEARCHOPTIONS") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Controls what types of automated keyboard features are available at the Command prompt." + \ + "\nThe setting is stored as a bitcode using the sum of the following values:" + \ + "\n 0 = Turns off all automated keyboard features when typing at the Command prompt." + \ + "\n 1 = Turns on any automated keyboard features when typing at the Command prompt." + \ + "\n 2 = Automatically appends suggestions as each keystroke is entered after the second keystroke." + \ + "\n 4 = Displays a list of suggestions as keystrokes are entered." + \ + "\n 8 = Displays the icon of the command or system variable, if available." + \ + "\n16 = Excludes the display of system variables.") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(15), \ + QadVariableTypeEnum.INT, \ + 0, 31, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # MAXARRAY (int): limita la dimensione degli array nel comando ARRAY. Variabile globale. + VariableName = QadMsg.translate("Environment variables", "MAXARRAY") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Limit the Size of arrays in ARRAY command.") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(100000), \ + QadVariableTypeEnum.INT, \ + 100, 10000000, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # OFFSETDIST (float): Setta la distanza di default per l'offset. Variabile del progetto. + # < 0 offset di un oggetto attraverso un punto + # >= 0 offset di un oggetto attraverso la distanza + VariableName = QadMsg.translate("Environment variables", "OFFSETDIST") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Sets the default offset distance:" + \ + "\n<0 = Offsets an object through a specified point." + \ + "\n>=0 = Sets the default offset distance.") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Real type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "project variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, float(-1.0), \ + QadVariableTypeEnum.FLOAT, \ + None, None, \ + VariableDescr, \ + QadVariableLevelEnum.PROJECT) + + # OFFSETGAPTYPE (int). Variabile globale. + # 0 = Estende i segmenti di linea alle relative intersezioni proiettate + # 1 = Raccorda i segmenti di linea in corrispondenza delle relative intersezioni proiettate. + # Il raggio di ciascun segmento di arco é uguale alla distanza di offset + # 2 = Cima i segmenti di linea in corrispondenza delle intersezioni proiettate. + # La distanza perpendicolare da ciascuna cima al rispettivo vertice + # sull'oggetto originale é uguale alla distanza di offset. + VariableName = QadMsg.translate("Environment variables", "OFFSETGAPTYPE") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Controls how potential gaps between segments are treated when polylines are offset:" + \ + "\n0 = Extends line segments to their projected intersections." + \ + "\n1 = Fillets line segments at their projected intersections. The radius of each arc segment is equal to the offset distance." + \ + "\n2 = Chamfers line segments at their projected intersections. The perpendicular distance from each chamfer to its corresponding vertex on the original object is equal to the offset distance.") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(0), \ + QadVariableTypeEnum.INT, \ + 0, 2, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # ORTHOMODE (int). Variabile del progetto. + # 0 = modalità di movimento ortogonale cursore disabilitata + # 1 = modalità di movimento ortogonale cursore abilitata + VariableName = QadMsg.translate("Environment variables", "ORTHOMODE") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Constrains cursor movement to the perpendicular." + \ + "\nWhen ORTHOMODE is turned on, the cursor can move only horizontally or vertically:" + \ + "\n0 = Turns off Ortho mode." + \ + "\n1 = Turns on Ortho mode.") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "project variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(0), \ + QadVariableTypeEnum.INT, \ + 0, 1, \ + VariableDescr, \ + QadVariableLevelEnum.PROJECT) + + # OSMODE (int): Imposta lo snap ad oggetto (somma di bit). Variabile globale. + # 0 = (NON) nessuno + # 1 = (FIN) punti finali di ogni segmento + # 2 = (MED) punto medio + # 4 = (CEN) centroide di un poligono + # 8 = (NOD) ad oggetto punto + # 16 = (QUA) punto quadrante di un poligono + # 32 = (INT) intersezione di un oggetto (anche i vertici intermedi di una linestring o polygon) + # 64 = (INS) punto di inserimento di oggetti (come 8) + # 128 = (PER) punto perpendicolare a un oggetto + # 256 = (TAN) tangente di un arco, di un cerchio, di un'ellisse, di un arco ellittico o di una spline + # 512 = (NEA) punto più vicino di un oggetto + # 1024 = (C) Cancella tutti gli snap ad oggetto + # 2048 = (APP) intersezione apparente di due oggetti che non si intersecano nello spazio 3D + # ma che possono apparire intersecanti nella vista corrente + # 4096 = (EST) Estensione : Visualizza una linea o un arco di estensione temporaneo quando si sposta il cursore sul punto finale degli oggetti, + # in modo che sia possibile specificare punti sull'estensione + # 8192 = (PAR) Parallelo: Vincola un segmento di linea, un segmento di polilinea, un raggio o una xlinea ad essere parallela ad un altro oggetto lineare + # 16384 = osnap off + # 65536 = (PR) Distanza progressiva + # 131072 = intersezione sull'estensione + # 262144 = perpendicolare differita + # 524288 = tangente differita + # 1048576 = puntamento polare + # 2097152 = punti finali dell'intera polilinea + VariableName = QadMsg.translate("Environment variables", "OSMODE") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Sets running object snaps." + \ + "\nThe setting is stored as a bitcode using the sum of the following values:" + \ + "\n0 = NONe." + \ + "\n1 = ENDpoint." + \ + "\n2 = MIDpoint." + \ + "\n4 = CENter." + \ + "\n8 = NODe." + \ + "\n16 = QUAdrant." + \ + "\n32 = INTersection." + \ + "\n64 = INSertion." + \ + "\n128 = PERpendicular." + \ + "\n256 = TANgent." + \ + "\n512 = NEArest." + \ + "\n1024 = QUIck." + \ + "\n2048 = APParent Intersection." + \ + "\n4096 = EXTension." + \ + "\n8192 = PARallel." + \ + "\n65536 = PRogressive distance (PR[dist])." + \ + "\n131072 = Intersection on extension (EXT_INT)." + \ + "\n2097152 = Final points on polyline (FIN_PL).") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(0), \ + QadVariableTypeEnum.INT, \ + 0, None, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # OSPROGRDISTANCE (float): Distanza progressiva per snap PR. Variabile del progetto. + VariableName = QadMsg.translate("Environment variables", "OSPROGRDISTANCE") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Progressive distance for snap mode.") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Real type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "project variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, float(0.0), \ + QadVariableTypeEnum.FLOAT, \ + None, None, \ + VariableDescr, \ + QadVariableLevelEnum.PROJECT) + + # PICKADD (int): Controlla se le selezioni successive sostituiscono il gruppo di selezione corrente o vengono aggiunte ad esso. + # Variabile globale. + VariableName = QadMsg.translate("Environment variables", "PICKADD") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Controls whether subsequent selections replace the current selection set or add to it." + \ + "\n0 = Turns off PICKADD. The objects most recently selected become the selection set. Previously selected objects are removed from the selection set. Add more objects to the selection set by pressing SHIFT while selecting." + \ + "\n1 = Turns on PICKADD. Each object selected, either individually or by windowing, is added to the current selection set. To remove objects from the set, press SHIFT while selecting." + \ + "\n2 = Turns on PICKADD. Each object selected, either individually or by windowing, is added to the current selection set. To remove objects from the set, press SHIFT while selecting. Keeps objects selected after the SELECT command ends.") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(0), \ + QadVariableTypeEnum.INT, \ + 0, 2, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # PICKBOX (int): Imposta la dimensione in pixel della distanza di selezione degli oggetti + # dalla posizione corrente del puntatore. Variabile globale. + VariableName = QadMsg.translate("Environment variables", "PICKBOX") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Sets the object selection target height, in pixels.") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(5), \ + QadVariableTypeEnum.INT, \ + 1, 999, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # PICKBOXCOLOR (str): Imposta il colore (RGB) del quadratino di selezione degli oggetti. Variabile globale. + VariableName = QadMsg.translate("Environment variables", "PICKBOXCOLOR") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Sets the object selection target color (RGB, #FF0000 = red).") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Character type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, unicode("#FF0000"), \ + QadVariableTypeEnum.COLOR, \ + None, None, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) # red + + # PICKFIRST (int): Controlla se è possibile selezionare gli oggetti prima di eseguire un comando. Variabile globale. + VariableName = QadMsg.translate("Environment variables", "PICKFIRST") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Controls whether you can select objects before you start a command." + \ + "\n0 = Off. You can select objects only after you start a command." + \ + "\n1 = On. You can also select objects before you start a command.") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(1), \ + QadVariableTypeEnum.INT, \ + 0, 1, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # POLARADDANG (string): Memorizza ulteriori angoli per il puntamento polare e lo snap polare. Variabile globale. + VariableName = QadMsg.translate("Environment variables", "POLARADDANG") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Stores additional angles for polar tracking and polar snap.") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Character type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, unicode(""), \ + QadVariableTypeEnum.STRING, \ + None, None, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) # red + + # POLARANG (float): incremento dell'angolo polare per il puntamento polare (gradi). Variabile globale. + VariableName = QadMsg.translate("Environment variables", "POLARANG") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Sets the polar angle increment (degree).") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Real type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, float(90.0), \ + QadVariableTypeEnum.FLOAT, \ + 0.000001, 359.999999, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # POLARMODE (int): Controlla le impostazioni per il puntamento di tipo polare e con snap ad oggetto. Variabile globale. + VariableName = QadMsg.translate("Environment variables", "POLARMODE") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Controls settings for polar and object snap tracking." + \ + "\nThe setting is stored as a bitcode using the sum of the following values:" + \ + "\nPolar angle measurements" + \ + "\n0 = Measure polar angles based on current UCS (absolute)" + \ + "\n1 = Measure polar angles from selected objects (relative)" + \ + "\nObject snap tracking" + \ + "\n0 = Track orthogonally only" + \ + "\n2 = Use polar tracking settings in object snap tracking" + \ + "\nUse additional polar tracking angles" + \ + "\n0 = No" + \ + "\n4 = Yes" + \ + "\nAcquire object snap tracking points" + \ + "\n0 = Acquire automatically" + \ + "\n8 = Press Shift to acquire") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(0), \ + QadVariableTypeEnum.INT, \ + 0, 15, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # SELECTIONAREA (int): Controlla gli effetti della visualizzazione della selezione di aree. Variabile globale. + # dalla posizione corrente del puntatore + VariableName = QadMsg.translate("Environment variables", "SELECTIONAREA") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Controls the display of effects for selection areas." + \ + "\nSelection areas are created by the Window, Crossing, WPolygon, CPolygon, WCircle, CCircle, WObjects, CObjects, WBuffer and CBuffer options of SELECT." + \ + "\n0 = Off" + \ + "\n1 = On") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(1), \ + QadVariableTypeEnum.INT, \ + 0, 1, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # SELECTIONAREAOPACITY (int): Controlla gli effetti della visualizzazione della selezione di aree + # dalla posizione corrente del puntatore. Variabile globale. + VariableName = QadMsg.translate("Environment variables", "SELECTIONAREAOPACITY") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Controls the transparency of the selection area during window and crossing selection." + \ + "\nThe valid range is 0 to 100. The lower the setting, the more transparent the area. A value of 100 makes the area opaque. The SELECTIONAREA system variable must be on.") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(25), \ + QadVariableTypeEnum.INT, \ + 0, 100, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # SHORTCUTMENU (int): Controlla se i menu di scelta rapida delle modalità Default, Modifica e Comando sono disponibili + # nella'rea di disegno. Variabile globale. + VariableName = QadMsg.translate("Environment variables", "SHORTCUTMENU") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Controls whether Default, Edit, and Command mode shortcut menus are available in the drawing area." + \ + "\nThe setting is stored as a bitcode using the sum of the following values:" + \ + "\n0 = Disables all Default, Edit, and Command mode shortcut menus" + \ + "\n1 = Enables Default mode shortcut menus" + \ + "\n2 = Enables Edit mode shortcut menus" + \ + "\n4 = Enables Command mode shortcut menus whenever a command is active" + \ + "\n8 = Enables Command mode shortcut menus only when command options are currently available at the Command prompt" + \ + "\n16 = Enables the display of a shortcut menu when the right button on the pointing device is held down long enough") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(11), \ + QadVariableTypeEnum.INT, \ + 0, 64, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + + # SHORTCUTMENUDURATION (int): Controlla se i menu di scelta rapida delle modalità Default, Modifica e Comando sono disponibili + # nella'rea di disegno. Variabile globale. + VariableName = QadMsg.translate("Environment variables", "SHORTCUTMENUDURATION") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Specifies how long the right button on a pointing device must be pressed to display a shortcut menu in the drawing area." + \ + "\nThe value is expressed in milliseconds, and the valid range is 100 to 10000.") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(250), \ + QadVariableTypeEnum.INT, \ + 100, 10000, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # SUPPORTPATH (str): Path di ricerca per i files di supporto. Variabile globale. + default = os.path.abspath(os.path.dirname(__file__) + "\\support") + VariableName = QadMsg.translate("Environment variables", "SUPPORTPATH") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Searching path for support files.") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Character type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, default, \ + QadVariableTypeEnum.STRING, \ + None, None, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # SHOWTEXTWINDOW (bool): Visualizza la finestra di testo all'avvio. Variabile globale. + VariableName = QadMsg.translate("Environment variables", "SHOWTEXTWINDOW") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Show the text window at startup.") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Boolean type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, True, \ + QadVariableTypeEnum.BOOL, \ + None, None, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # TOLERANCE2APPROXCURVE (float): + # massimo errore tollerato tra una vera curva e quella approssimata dai segmenti retti + # (nel sistema map-coordinate). Variabile del progetto + VariableName = QadMsg.translate("Environment variables", "TOLERANCE2APPROXCURVE") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Maximum error approximating a curve to segments.") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Real type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "project variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, float(0.1), \ + QadVariableTypeEnum.FLOAT, \ + 0.000001, None, \ + VariableDescr, \ + QadVariableLevelEnum.PROJECT) + + # TOLERANCE2COINCIDENT (float): + # massimo errore tollerato per stabilire se due punti sono coincidenti + # (nel sistema map-coordinate). Variabile del progetto + VariableName = QadMsg.translate("Environment variables", "TOLERANCE2COINCIDENT") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Maximum error approximating two coincident points.") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Real type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "project variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, float(0.001), \ + QadVariableTypeEnum.FLOAT, \ + 0, 9999, \ + VariableDescr, \ + QadVariableLevelEnum.PROJECT) + + # TOOLTIPTRANSPARENCY (int): Imposta la trasparenza della finestra di input dinamico. Variabile globale. + VariableName = QadMsg.translate("Environment variables", "TOOLTIPTRANSPARENCY") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Sets the transparency for drafting tooltips." + \ + "\nThe valid range is 0 to 100. When a value of 0 is used, the drafting tooltip is opaque." + \ + "\nGreater values increase the transparency of the drafting tooltip.") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(0), \ + QadVariableTypeEnum.INT, \ + 0, 100, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # TOOLTIPSIZE (int): . Variabile globale. + VariableName = QadMsg.translate("Environment variables", "TOOLTIPSIZE") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Sets the display size for drafting tooltips, and dynamic input text." + \ + "\nValid range is -3 to 6. Greater values result in larger drafting tooltips, and larger automatic completion text at the Command prompt." + \ + "\nNegative values represent smaller sizes than the default.") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Integer type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, int(0), \ + QadVariableTypeEnum.INT, \ + -3, 6, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) + + # WINDOWAREACOLOR (str): Imposta il colore (RGB) dell'area di selezione degli oggetti nel modo finestra. Variabile globale. + VariableName = QadMsg.translate("Environment variables", "WINDOWAREACOLOR") # x lupdate + VariableDescr = QadMsg.translate("Environment variables", "Controls the color of the transparent selection area during window selection (RGB, #007EFF = blu)." + \ + "\nThe SELECTIONAREA system variable must be on.") # x lupdate + VariableDescr = VariableDescr + "\n" + QadMsg.translate("Environment variables", "Character type") + VariableDescr = VariableDescr + ", " + QadMsg.translate("Environment variables", "global variable") + "." + self.__VariableValuesDict[VariableName] = QadVariable(VariableName, unicode("#007EFF"), \ + QadVariableTypeEnum.COLOR, \ + None, None, \ + VariableDescr, \ + QadVariableLevelEnum.GLOBAL) # blue + + + def getVarNames(self): + """ + Ritorna la lista dei nomi delle variabili + """ + return list(self.__VariableValuesDict.keys()) + + + def set(self, VarName, varValue): + """ + Modifica il valore di una variabile + """ + UpperVarName = VarName.upper() + variable = self.getVariable(UpperVarName) + + if variable is None: # se non c'è la variablie + self.__VariableValuesDict[UpperVarName] = QadVariable(UpperVarName, varValue, \ + QadVariableTypeEnum.UNKNOWN, \ + None, None, \ + "") + else: + if type(variable.value) != type(varValue): + if not((type(variable.value) == unicode or type(variable.value) == str) and + (type(varValue) == unicode or type(varValue) == str)): + return False + if variable.typeValue == QadVariableTypeEnum.COLOR: + if len(varValue) == 7: # es. "#FF0000" + if varValue[0] != "#": + return False + else: + return False + elif variable.typeValue == QadVariableTypeEnum.FLOAT or \ + variable.typeValue == QadVariableTypeEnum.INT: + if variable.minNum is not None: + if varValue < variable.minNum: + return False + if variable.maxNum is not None: + if varValue > variable.maxNum: + return False + + self.__VariableValuesDict[UpperVarName].value = varValue + + return True + + def get(self, VarName, defaultValue = None): + """ + Restituisce il valore di una variabile + """ + variable = self.getVariable(VarName) + if variable is None: + result = defaultValue + else: + result = variable.value + + return result + + + def getVariable(self, VarName): + UpperVarName = VarName + return self.__VariableValuesDict.get(UpperVarName.upper()) + + + def getDefaultQadIniFilePath(self): + # ottiene il percorso automatico incluso il nome del file dove salvare il file qad.ini + # se esiste un progetto caricato il percorso è quello del progetto + prjFileInfo = QFileInfo(QgsProject.instance().fileName()) + path = prjFileInfo.absolutePath() + if len(path) == 0: + # se non esiste un progetto caricato uso il percorso di installazione di qad + path = QDir.cleanPath(QgsApplication.qgisSettingsDirPath() + "python/plugins/qad") + return path + "/" + "qad.ini" + else: + return path + "/" + prjFileInfo.baseName() + "_qad.ini" + + + def getDefaultQadIniFilePath(self): + # ottiene il percorso incluso il nome del file dove salvare il file qad.ini di default + # uso il percorso di installazione di qad + path = QDir.cleanPath(QgsApplication.qgisSettingsDirPath() + "python/plugins/qad") + return path + "/" + "qad.ini" + + + def getProjectQadIniFilePath(self): + # ottiene il percorso incluso il nome del file dove salvare il file qad.ini del progetto + # se esiste un progetto caricato il percorso è quello del progetto + prjFileInfo = QFileInfo(QgsProject.instance().fileName()) + path = prjFileInfo.absolutePath() + if len(path) == 0: # se non esiste un progetto caricato + return "" + else: + return path + "/" + prjFileInfo.baseName() + "_qad.ini" + + + def __saveVariables(self, Path, filterOnLevel = None): + """ + Salva il dizionario delle variabili su file eventualmente salvando solo quelle del livello indicati (globali, progetto) + """ + dir = QFileInfo(Path).absoluteDir() + if not dir.exists(): + os.makedirs(dir.absolutePath()) + + file = open(Path, "w") # apre il file in scrittura + for VarName in self.__VariableValuesDict.keys(): + variable = self.getVariable(VarName) + # se c'è un filtro che non viene soddisfatto allora salto la variabile + if (filterOnLevel is not None) and (variable is not None) and (filterOnLevel != variable.level): + continue + varValue = variable.value + # scrivo il valore + il tipo (es var = 5 ) + if type(varValue) == int: + varValue = str(varValue) + " " + elif type(varValue) == long: + varValue = str(varValue) + " " + elif type(varValue) == float: + varValue = str(varValue) + " " + elif type(varValue) == bool: + varValue = str(varValue) + " " + else: # stringa + varValue = str(varValue) + " " + + Item = "%s = %s\n" % (VarName, varValue) + file.write(Item) + + file.close() + + def save(self, Path=""): + """ + Salva il dizionario delle variabili su file + """ + if Path != "": # Se la path é indicata salvo tutte le variabili nel file + self.__saveVariables(Path) + return + + # Se la path non é indicata + projectPath = self.getProjectQadIniFilePath() + defaultPath = self.getDefaultQadIniFilePath() + + if len(projectPath) == 0: # se non c'è nessun progetto corrente + self.__saveVariables(defaultPath) # salvo tutte le variabili nel file "qad.ini" dell'installazione di qad + else: + # salvo tutte le variabili globali nel file "qad.ini" dell'installazione di qad + self.__saveVariables(defaultPath, QadVariableLevelEnum.GLOBAL) + # salvo tutte le variabili del progetto nel file "qad.ini" del progetto corrente + self.__saveVariables(projectPath, QadVariableLevelEnum.PROJECT) + + + def __loadVariables(self, Path, filterOnLevel = None): + """ + Carica il dizionario delle variabili da file eventualmente caricando solo quelle del livello indicati (globali, progetto) + Ritorna True in caso di successo, false in caso di errore + """ + if not os.path.exists(Path): + return False + + file = open(Path, "r") # apre il file in lettura + for line in file: + # leggo il valore + il tipo (es var = 5 ) + sep = line.rfind(" = ") + VarName = line[0:sep] + VarName = VarName.strip(" ") # rimuovo gli spazi prima e dopo + variable = self.getVariable(VarName) + # se c'è un filtro che non viene soddisfatto allora salto la variabile + if (filterOnLevel is not None) and (variable is not None) and (filterOnLevel != variable.level): + continue + + varValue = line[sep+3:] + sep = varValue.rfind(" ") + VarType = varValue[sep+8:sep2] + varValue = varValue[:sep] + if VarType == "int": + varValue = qad_utils.str2int(varValue) + if varValue is None: + self.set(VarName, int(0)) + else: + self.set(VarName, varValue) + elif VarType == "long": + varValue = qad_utils.str2long(varValue) + if varValue is None: + self.set(VarName, long(0)) + else: + self.set(VarName, varValue) + elif VarType == "float": + varValue = qad_utils.str2float(varValue) + if varValue is None: + self.set(VarName, float(0)) + else: + self.set(VarName, varValue) + elif VarType == "bool": + varValue = qad_utils.str2bool(varValue) + if varValue is None: + self.set(VarName, False) + else: + self.set(VarName, varValue) + else: # stringa + self.set(VarName, str(varValue)) + + file.close() + + return True + + + def load(self, Path=""): + """ + Carica il dizionario delle variabili da file + Ritorna True in caso di successo, false in caso di errore + """ + # svuoto il dizionario e lo reimposto con i valori di default + self.__VariableValuesDict.clear() + self.__init__() + + + if Path != "": # Se la path é indicata carico tutte le variabili nel file + self.__loadVariables(Path) + return + + # Se la path non é indicata + projectPath = self.getProjectQadIniFilePath() + defaultPath = self.getDefaultQadIniFilePath() + + if len(projectPath) == 0: # se non c'è nessun progetto corrente + self.__loadVariables(defaultPath) # carico tutte le variabili nel file "qad.ini" dell'installazione di qad + else: + # carico tutte le variabili globali dal file "qad.ini" dell'installazione di qad + self.__loadVariables(defaultPath, QadVariableLevelEnum.GLOBAL) + # carico tutte le variabili del progetto nel file "qad.ini" del progetto corrente + self.__loadVariables(projectPath, QadVariableLevelEnum.PROJECT) + + return True + + + # ============================================================================ + # copyTo + # ============================================================================ + def copyTo(self, dest): + """ + Copia il dizionario con le variabili in dest + """ + for VarName in self.__VariableValuesDict.keys(): + dest.set(VarName, self.get(VarName)) + + +# =============================================================================== +# = variabile globale +# =============================================================================== + + +def POLARADDANG_to_list(PolarAddAngles, convertToRadians = False): + strValueList = PolarAddAngles.split(";") + floatValueList = [] + for strValue in strValueList: + floatValue = qad_utils.str2float(strValue) + if floatValue is not None: + if convertToRadians == True: + floatValue = math.radians(floatValue) + floatValueList.append(floatValue) + + floatValueList.sort() + + return floatValueList + +QadVariables = QadVariablesClass() diff --git a/qad_vertexmarker.py b/qad_vertexmarker.py index cd35059f..66a44ee3 100644 --- a/qad_vertexmarker.py +++ b/qad_vertexmarker.py @@ -1,286 +1,290 @@ -# -*- coding: utf-8 -*- -""" -/*************************************************************************** - QAD Quantum Aided Design plugin - - classe per gestire i simboli marcatori - - ------------------- - begin : 2013-05-22 - copyright : iiiii - email : hhhhh - developers : bbbbb aaaaa ggggg - ***************************************************************************/ - -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -""" - - -from PyQt4.QtCore import * -from PyQt4.QtGui import * -from qgis.core import * -from qgis.gui import * - - -#=============================================================================== -# QadVertexmarkerIconTypeEnum class. -#=============================================================================== -class QadVertexmarkerIconTypeEnum(): - NONE = 0 # nessuno - CROSS = 1 # croce - X = 2 # una X - BOX = 3 # un quadrato - TRIANGLE = 4 # triangolo equilatero con punta in su - CIRCLE = 5 # cerchio - CIRCLE_X = 6 # cerchio con al centro una x - RHOMBUS = 7 # rombo - INFINITY_LINE = 8 # linea infinita (------ . .) - DOUBLE_BOX = 9 # due quadrati sfalsati - PERP = 10 # simbolo di "perpendicolare" - TANGENT = 11 # un cerchio con una retta tangente sopra - DOUBLE_TRIANGLE = 12 # due triangoli uno sull'altro con vertice al centro (clessidra) - BOX_X = 13 # quadrato con al centro una x - PARALLEL = 14 # due righe parallele a 45 gradi - PROGRESS = 15 # linea con X e i puntini (----X-- . .) - X_INFINITY_LINE = 16 # X e i puntini (X-- . .) - PERP_DEFERRED = 17 # come perpendicolare con i puntini - TANGENT_DEFERRED = 18 # come tangente con i puntini - - -class QadVertexMarker(QgsMapCanvasItem): - """ - Classe che gestisce i marcatori dei vertici - """ - - - #============================================================================ - # __init__ - #============================================================================ - def __init__(self, mapCanvas): - QgsMapCanvasItem.__init__(self, mapCanvas) - self.__canvas = mapCanvas - self.__iconType = QadVertexmarkerIconTypeEnum.X # icon to be shown - self.__iconSize = 13 # size - self.__center = QgsPoint(0, 0) # coordinates of the point in the center - self.__color = QColor(255, 0, 0) # color of the marker - self.__penWidth = 2 # pen width - - - def __del__(self): - self.removeItem() - - - def removeItem(self): - self.__canvas.scene().removeItem(self) - - - def setCenter(self, point): - self.__center = point - pt = self.toCanvasCoordinates(self.__center) - self.setPos(pt) - - - def setIconType(self, iconType): - self.__iconType = iconType - - - def setIconSize(self, iconSize): - self.__iconSize = iconSize - - - def setColor(self, color): - self.__color = color - - - def setPenWidth(self, width): - self.__penWidth = width - - - def paint(self, painter, option, widget): - """ - p é un QPainter - """ - - s = (self.__iconSize - 1) / 2 - - pen = QPen(self.__color) - pen.setWidth(self.__penWidth) - painter.setPen(pen) - - if self.__iconType == QadVertexmarkerIconTypeEnum.NONE: - pass - elif self.__iconType == QadVertexmarkerIconTypeEnum.CROSS: - # croce - painter.drawLine(QLineF(-s, 0, s, 0)) - painter.drawLine(QLineF( 0, -s, 0, s)) - elif self.__iconType == QadVertexmarkerIconTypeEnum.X: - # una X - painter.drawLine(QLineF(-s, -s, s, s)) - painter.drawLine(QLineF(-s, s, s, -s)) - elif self.__iconType == QadVertexmarkerIconTypeEnum.BOX: - # un quadrato - painter.drawLine(QLineF(-s, -s, s, -s)) - painter.drawLine(QLineF( s, -s, s, s)) - painter.drawLine(QLineF( s, s, -s, s)) - painter.drawLine(QLineF(-s, s, -s, -s)) - elif self.__iconType == QadVertexmarkerIconTypeEnum.TRIANGLE: - # triangolo equilatero con punta in su - painter.drawLine(QLineF(-s, s, s, s)) - painter.drawLine(QLineF( s, s, 0, -s)) - painter.drawLine(QLineF( 0, -s, -s, s)) - elif self.__iconType == QadVertexmarkerIconTypeEnum.CIRCLE: - # cerchio - # la linea é più sottile - pen.setWidth(self.__penWidth / 2) - painter.setPen(pen) - painter.drawEllipse(QPointF(0, 0), s, s) - pen.setWidth(self.__penWidth) - painter.setPen(pen) - elif self.__iconType == QadVertexmarkerIconTypeEnum.CIRCLE_X: - # cerchio con al centro una x - # la linea é più sottile - pen.setWidth(self.__penWidth / 2) - painter.setPen(pen) - painter.drawEllipse(QPointF(0, 0), s, s) - painter.drawLine(QLineF(-s, -s, s, s)) - painter.drawLine(QLineF(-s, s, s, -s)) - pen.setWidth(self.__penWidth) - painter.setPen(pen) - elif self.__iconType == QadVertexmarkerIconTypeEnum.RHOMBUS: - # rombo - painter.drawLine(QLineF( 0, -s, -s, 0)) - painter.drawLine(QLineF(-s, 0, 0, s)) - painter.drawLine(QLineF( 0, s, s, 0)) - painter.drawLine(QLineF( s, 0, 0, -s)) - elif self.__iconType == QadVertexmarkerIconTypeEnum.INFINITY_LINE: - # linea infinita (------ . .) - l = self.__penWidth - painter.drawLine(QLineF(-s, 0, 0, 0)) - painter.drawLine(QLineF(2 * l, 0, 2 * l, 0)) - painter.drawLine(QLineF(4 * l, 0, 4 * l, 0)) - elif self.__iconType == QadVertexmarkerIconTypeEnum.DOUBLE_BOX: - # due quadrati sfalsati - l = (s / 4) - painter.drawLine(QLineF(-s, -s, -s, l)) - painter.drawLine(QLineF(-s, l, -l, l)) - painter.drawLine(QLineF(-l, l, -l, s)) - painter.drawLine(QLineF(-l, s, s, s)) - painter.drawLine(QLineF( s, s, s, -l)) - painter.drawLine(QLineF( s, -l, l, -l)) - painter.drawLine(QLineF( l, -l, l, -s)) - painter.drawLine(QLineF( l, -s, -s, -s)) - elif self.__iconType == QadVertexmarkerIconTypeEnum.PERP: - # simbolo di "perpendicolare" - painter.drawLine(QLineF(-s, -s, -s, s)) - painter.drawLine(QLineF(-s, s, s, s)) - painter.drawLine(QLineF(-s, 0, 0, 0)) - painter.drawLine(QLineF( 0, 0, 0, s)) - elif self.__iconType == QadVertexmarkerIconTypeEnum.TANGENT: - # un cerchio con una retta tangente sopra - # la linea é più sottile - l = s - self.__penWidth - pen.setWidth(self.__penWidth / 2) - painter.setPen(pen) - painter.drawEllipse(QPointF(0, 0), l + 1, l + 1) - pen.setWidth(self.__penWidth) - painter.setPen(pen) - painter.drawLine(QLineF(-s, -s, s, -s)) - elif self.__iconType == QadVertexmarkerIconTypeEnum.DOUBLE_TRIANGLE: - # due triangoli uno sull'altro con vertice al centro (clessidra) - # le linee oblique sono più sottili - pen.setWidth(self.__penWidth / 2) - painter.setPen(pen) - painter.drawLine(QLineF(-s, -s, s, s)) - painter.drawLine(QLineF( s, -s, -s, s)) - pen.setWidth(self.__penWidth) - painter.setPen(pen) - painter.drawLine(QLineF(-s, -s, s, -s)) - painter.drawLine(QLineF(-s, s, s, s)) - elif self.__iconType == QadVertexmarkerIconTypeEnum.BOX_X: - # quadrato con al centro una x - painter.drawLine(QLineF(-s, -s, s, -s)) - painter.drawLine(QLineF( s, -s, s, s)) - painter.drawLine(QLineF( s, s, -s, s)) - painter.drawLine(QLineF(-s, s, -s, -s)) - # le linee oblique della x sono più sottili - pen.setWidth(self.__penWidth / 2) - painter.setPen(pen) - painter.drawLine(QLineF(-s, -s, s, s)) - painter.drawLine(QLineF(-s, s, s, -s)) - pen.setWidth(self.__penWidth) - painter.setPen(pen) - elif self.__iconType == QadVertexmarkerIconTypeEnum.PARALLEL: - # due righe parallele a 45 gradi - painter.drawLine(QLineF(-s, 0, 0, -s)) - painter.drawLine(QLineF( 0, s, s, 0)) - elif self.__iconType == QadVertexmarkerIconTypeEnum.PROGRESS: - # linea con X e i puntini (----X-- . .) - l = self.__penWidth - painter.drawLine(QLineF(-s, 0, 0, 0)) - painter.drawLine(QLineF(2 * l, 0, 2 * l, 0)) - painter.drawLine(QLineF(4 * l, 0, 4 * l, 0)) - # le linee oblique della x sono più sottili - pen.setWidth(self.__penWidth / 2) - l = s / 2 - painter.setPen(pen) - painter.drawLine(QLineF(-l, -l, l, l)) - painter.drawLine(QLineF(-l, l, l, -l)) - pen.setWidth(self.__penWidth) - painter.setPen(pen) - elif self.__iconType == QadVertexmarkerIconTypeEnum.X_INFINITY_LINE: - # linea con X e i puntini (X-- . .) - l = self.__penWidth - painter.drawLine(QLineF(2 * l, 0, 2 * l, 0)) - painter.drawLine(QLineF(4 * l, 0, 4 * l, 0)) - # le linee oblique della x sono più sottili - pen.setWidth(self.__penWidth / 2) - l = s / 2 - painter.setPen(pen) - painter.drawLine(QLineF(-l, -l, l, l)) - painter.drawLine(QLineF(-l, l, l, -l)) - pen.setWidth(self.__penWidth) - painter.setPen(pen) - elif self.__iconType == QadVertexmarkerIconTypeEnum.PERP_DEFERRED: - painter.drawLine(QLineF(-s, -s, -s, s)) - painter.drawLine(QLineF(-s, s, s, s)) - painter.drawLine(QLineF(-s, 0, 0, 0)) - painter.drawLine(QLineF( 0, 0, 0, s)) - # simbolo di "perpendicolare" con i puntini - l = s - self.__penWidth - l = l + (self.__penWidth * 2) - painter.drawLine(QLineF(l, 0, l, 0)) - l = l + (self.__penWidth * 2) - painter.drawLine(QLineF(l, 0, l, 0)) - elif self.__iconType == QadVertexmarkerIconTypeEnum.TANGENT_DEFERRED: - # un cerchio con una retta tangente sopra - # la linea é più sottile - l = s - self.__penWidth - pen.setWidth(self.__penWidth / 2) - painter.setPen(pen) - painter.drawEllipse(QPointF(0, 0), l + 1, l + 1) - pen.setWidth(self.__penWidth) - painter.setPen(pen) - painter.drawLine(QLineF(-s, -s, s, -s)) - # come tangente con i puntini - l = l + (self.__penWidth * 2) - painter.drawLine(QLineF(l, 0, l, 0)) - l = l + (self.__penWidth * 2) - painter.drawLine(QLineF(l, 0, l, 0)) - - - def boundingRect(self): - a = self.__iconSize / 2.0 + 1 - width = 2 * a + self.__penWidth * 2 - height = 2 * a - return QRectF(-a, -a, width, height) - - - def updatePosition(self): - self.setCenter(self.__center) +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + classe per gestire i simboli marcatori + + ------------------- + begin : 2013-05-22 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +from qgis.PyQt.QtCore import * +from qgis.PyQt.QtGui import * +from qgis.core import * +from qgis.gui import * + + +from .qad_msg import QadMsg +from .qad_variables import QadVariables + + +# =============================================================================== +# QadVertexmarkerIconTypeEnum class. +# =============================================================================== +class QadVertexmarkerIconTypeEnum(): + NONE = 0 # nessuno + CROSS = 1 # croce + X = 2 # una X + BOX = 3 # un quadrato + TRIANGLE = 4 # triangolo equilatero con punta in su + CIRCLE = 5 # cerchio + CIRCLE_X = 6 # cerchio con al centro una x + RHOMBUS = 7 # rombo + INFINITY_LINE = 8 # linea infinita (------ . .) + DOUBLE_BOX = 9 # due quadrati sfalsati + PERP = 10 # simbolo di "perpendicolare" + TANGENT = 11 # un cerchio con una retta tangente sopra + DOUBLE_TRIANGLE = 12 # due triangoli uno sull'altro con vertice al centro (clessidra) + BOX_X = 13 # quadrato con al centro una x + PARALLEL = 14 # due righe parallele a 45 gradi + PROGRESS = 15 # linea con X e i puntini (----X-- . .) + X_INFINITY_LINE = 16 # X e i puntini (X-- . .) + PERP_DEFERRED = 17 # come perpendicolare con i puntini + TANGENT_DEFERRED = 18 # come tangente con i puntini + + +class QadVertexMarker(QgsMapCanvasItem): + """ + Classe che gestisce i marcatori dei vertici + """ + + + # ============================================================================ + # __init__ + # ============================================================================ + def __init__(self, mapCanvas): + QgsMapCanvasItem.__init__(self, mapCanvas) + self.__canvas = mapCanvas + self.__iconType = QadVertexmarkerIconTypeEnum.X # icon to be shown + self.__iconSize = QadVariables.get(QadMsg.translate("Environment variables", "AUTOSNAPSIZE")) + self.__center = QgsPointXY(0, 0) # coordinates of the point in the center + self.__color = QColor(255, 0, 0) # color of the marker + self.__penWidth = 2 # pen width + + + def __del__(self): + self.removeItem() + + + def removeItem(self): + self.__canvas.scene().removeItem(self) + + + def setCenter(self, point): + self.__center = point + pt = self.toCanvasCoordinates(self.__center) + self.setPos(pt) + + + def setIconType(self, iconType): + self.__iconType = iconType + + + def setIconSize(self, iconSize): + self.__iconSize = iconSize + + + def setColor(self, color): + self.__color = color + + + def setPenWidth(self, width): + self.__penWidth = width + + + def paint(self, painter, option, widget): + """ + p é un QPainter + """ + + s = self.__iconSize + + pen = QPen(self.__color) + pen.setWidth(self.__penWidth) + painter.setPen(pen) + + if self.__iconType == QadVertexmarkerIconTypeEnum.NONE: + pass + elif self.__iconType == QadVertexmarkerIconTypeEnum.CROSS: + # croce + painter.drawLine(QLineF(-s, 0, s, 0)) + painter.drawLine(QLineF( 0, -s, 0, s)) + elif self.__iconType == QadVertexmarkerIconTypeEnum.X: + # una X + painter.drawLine(QLineF(-s, -s, s, s)) + painter.drawLine(QLineF(-s, s, s, -s)) + elif self.__iconType == QadVertexmarkerIconTypeEnum.BOX: + # un quadrato + painter.drawLine(QLineF(-s, -s, s, -s)) + painter.drawLine(QLineF( s, -s, s, s)) + painter.drawLine(QLineF( s, s, -s, s)) + painter.drawLine(QLineF(-s, s, -s, -s)) + elif self.__iconType == QadVertexmarkerIconTypeEnum.TRIANGLE: + # triangolo equilatero con punta in su + painter.drawLine(QLineF(-s, s, s, s)) + painter.drawLine(QLineF( s, s, 0, -s)) + painter.drawLine(QLineF( 0, -s, -s, s)) + elif self.__iconType == QadVertexmarkerIconTypeEnum.CIRCLE: + # cerchio + # la linea é più sottile + pen.setWidth(int(self.__penWidth / 2)) + painter.setPen(pen) + painter.drawEllipse(QPointF(0, 0), s, s) + pen.setWidth(self.__penWidth) + painter.setPen(pen) + elif self.__iconType == QadVertexmarkerIconTypeEnum.CIRCLE_X: + # cerchio con al centro una x + # la linea é più sottile + pen.setWidth(int(self.__penWidth / 2)) + painter.setPen(pen) + painter.drawEllipse(QPointF(0, 0), s, s) + painter.drawLine(QLineF(-s, -s, s, s)) + painter.drawLine(QLineF(-s, s, s, -s)) + pen.setWidth(self.__penWidth) + painter.setPen(pen) + elif self.__iconType == QadVertexmarkerIconTypeEnum.RHOMBUS: + # rombo + painter.drawLine(QLineF( 0, -s, -s, 0)) + painter.drawLine(QLineF(-s, 0, 0, s)) + painter.drawLine(QLineF( 0, s, s, 0)) + painter.drawLine(QLineF( s, 0, 0, -s)) + elif self.__iconType == QadVertexmarkerIconTypeEnum.INFINITY_LINE: + # linea infinita (------ . .) + l = self.__penWidth + painter.drawLine(QLineF(-s, 0, 0, 0)) + painter.drawLine(QLineF(2 * l, 0, 2 * l, 0)) + painter.drawLine(QLineF(4 * l, 0, 4 * l, 0)) + elif self.__iconType == QadVertexmarkerIconTypeEnum.DOUBLE_BOX: + # due quadrati sfalsati + l = (s / 4) + painter.drawLine(QLineF(-s, -s, -s, l)) + painter.drawLine(QLineF(-s, l, -l, l)) + painter.drawLine(QLineF(-l, l, -l, s)) + painter.drawLine(QLineF(-l, s, s, s)) + painter.drawLine(QLineF( s, s, s, -l)) + painter.drawLine(QLineF( s, -l, l, -l)) + painter.drawLine(QLineF( l, -l, l, -s)) + painter.drawLine(QLineF( l, -s, -s, -s)) + elif self.__iconType == QadVertexmarkerIconTypeEnum.PERP: + # simbolo di "perpendicolare" + painter.drawLine(QLineF(-s, -s, -s, s)) + painter.drawLine(QLineF(-s, s, s, s)) + painter.drawLine(QLineF(-s, 0, 0, 0)) + painter.drawLine(QLineF( 0, 0, 0, s)) + elif self.__iconType == QadVertexmarkerIconTypeEnum.TANGENT: + # un cerchio con una retta tangente sopra + # la linea é più sottile + l = s - self.__penWidth + pen.setWidth(int(self.__penWidth / 2)) + painter.setPen(pen) + painter.drawEllipse(QPointF(0, 0), l + 1, l + 1) + pen.setWidth(self.__penWidth) + painter.setPen(pen) + painter.drawLine(QLineF(-s, -s, s, -s)) + elif self.__iconType == QadVertexmarkerIconTypeEnum.DOUBLE_TRIANGLE: + # due triangoli uno sull'altro con vertice al centro (clessidra) + # le linee oblique sono più sottili + pen.setWidth(int(self.__penWidth / 2)) + painter.setPen(pen) + painter.drawLine(QLineF(-s, -s, s, s)) + painter.drawLine(QLineF( s, -s, -s, s)) + pen.setWidth(self.__penWidth) + painter.setPen(pen) + painter.drawLine(QLineF(-s, -s, s, -s)) + painter.drawLine(QLineF(-s, s, s, s)) + elif self.__iconType == QadVertexmarkerIconTypeEnum.BOX_X: + # quadrato con al centro una x + painter.drawLine(QLineF(-s, -s, s, -s)) + painter.drawLine(QLineF( s, -s, s, s)) + painter.drawLine(QLineF( s, s, -s, s)) + painter.drawLine(QLineF(-s, s, -s, -s)) + # le linee oblique della x sono più sottili + pen.setWidth(int(self.__penWidth / 2)) + painter.setPen(pen) + painter.drawLine(QLineF(-s, -s, s, s)) + painter.drawLine(QLineF(-s, s, s, -s)) + pen.setWidth(self.__penWidth) + painter.setPen(pen) + elif self.__iconType == QadVertexmarkerIconTypeEnum.PARALLEL: + # due righe parallele a 45 gradi + painter.drawLine(QLineF(-s, 0, 0, -s)) + painter.drawLine(QLineF( 0, s, s, 0)) + elif self.__iconType == QadVertexmarkerIconTypeEnum.PROGRESS: + # linea con X e i puntini (----X-- . .) + l = self.__penWidth + painter.drawLine(QLineF(-s, 0, 0, 0)) + painter.drawLine(QLineF(2 * l, 0, 2 * l, 0)) + painter.drawLine(QLineF(4 * l, 0, 4 * l, 0)) + # le linee oblique della x sono più sottili + pen.setWidth(int(self.__penWidth / 2)) + l = s / 2 + painter.setPen(pen) + painter.drawLine(QLineF(-l, -l, l, l)) + painter.drawLine(QLineF(-l, l, l, -l)) + pen.setWidth(self.__penWidth) + painter.setPen(pen) + elif self.__iconType == QadVertexmarkerIconTypeEnum.X_INFINITY_LINE: + # linea con X e i puntini (X-- . .) + l = self.__penWidth + painter.drawLine(QLineF(2 * l, 0, 2 * l, 0)) + painter.drawLine(QLineF(4 * l, 0, 4 * l, 0)) + # le linee oblique della x sono più sottili + pen.setWidth(int(self.__penWidth / 2)) + l = s / 2 + painter.setPen(pen) + painter.drawLine(QLineF(-l, -l, l, l)) + painter.drawLine(QLineF(-l, l, l, -l)) + pen.setWidth(self.__penWidth) + painter.setPen(pen) + elif self.__iconType == QadVertexmarkerIconTypeEnum.PERP_DEFERRED: + painter.drawLine(QLineF(-s, -s, -s, s)) + painter.drawLine(QLineF(-s, s, s, s)) + painter.drawLine(QLineF(-s, 0, 0, 0)) + painter.drawLine(QLineF( 0, 0, 0, s)) + # simbolo di "perpendicolare" con i puntini + l = s - self.__penWidth + l = l + (self.__penWidth * 2) + painter.drawLine(QLineF(l, 0, l, 0)) + l = l + (self.__penWidth * 2) + painter.drawLine(QLineF(l, 0, l, 0)) + elif self.__iconType == QadVertexmarkerIconTypeEnum.TANGENT_DEFERRED: + # un cerchio con una retta tangente sopra + # la linea é più sottile + l = s - self.__penWidth + pen.setWidth(int(self.__penWidth / 2)) + painter.setPen(pen) + painter.drawEllipse(QPointF(0, 0), l + 1, l + 1) + pen.setWidth(self.__penWidth) + painter.setPen(pen) + painter.drawLine(QLineF(-s, -s, s, -s)) + # come tangente con i puntini + l = l + (self.__penWidth * 2) + painter.drawLine(QLineF(l, 0, l, 0)) + l = l + (self.__penWidth * 2) + painter.drawLine(QLineF(l, 0, l, 0)) + + + def boundingRect(self): + a = self.__iconSize / 2.0 + 1 + width = 2 * a + self.__penWidth * 2 + height = 2 * a + return QRectF(-a, -a, width, height) + + + def updatePosition(self): + self.setCenter(self.__center) diff --git a/qad_windowcolor.ui b/qad_windowcolor.ui new file mode 100644 index 00000000..301c04db --- /dev/null +++ b/qad_windowcolor.ui @@ -0,0 +1,309 @@ + + + WindowColor_Dialog + + + + 0 + 0 + 592 + 424 + + + + + 592 + 424 + + + + + 592 + 424 + + + + Drawing window Colors + + + + + 430 + 390 + 75 + 23 + + + + Cancel + + + + + + 510 + 390 + 75 + 23 + + + + Help + + + + + + 304 + 390 + 121 + 23 + + + + Apply && Close + + + + + + 10 + 10 + 161 + 16 + + + + Context: + + + + + + 10 + 30 + 161 + 81 + + + + + + + 180 + 30 + 231 + 171 + + + + + + + 180 + 10 + 161 + 16 + + + + Interface element: + + + + + + 420 + 30 + 161 + 23 + + + + + + + + + + 420 + 120 + 161 + 23 + + + + Restore current element + + + + + + 420 + 150 + 161 + 23 + + + + Restore current context + + + + + + 420 + 180 + 161 + 23 + + + + Restore all context + + + + + + 10 + 210 + 401 + 171 + + + + + + + 10 + 190 + 161 + 16 + + + + Preview: + + + + + + 420 + 10 + 161 + 16 + + + + Color: + + + + + + + Button_Cancel + clicked() + WindowColor_Dialog + Button_Cancel_Pressed() + + + 397 + 403 + + + 72 + 405 + + + + + Button_Help + clicked() + WindowColor_Dialog + ButtonHELP_Pressed() + + + 464 + 404 + + + 131 + 409 + + + + + Button_RestoreCurrElement + clicked() + WindowColor_Dialog + Button_RestoreCurrElement_clicked() + + + 482 + 135 + + + 550 + 238 + + + + + Button_RestoreCurrContext + clicked() + WindowColor_Dialog + Button_RestoreCurrContext_clicked() + + + 465 + 162 + + + 549 + 274 + + + + + Button_RestoreAllContext + clicked() + WindowColor_Dialog + Button_RestoreAllContext_clicked() + + + 447 + 185 + + + 547 + 309 + + + + + Button_ApplyClose + clicked() + WindowColor_Dialog + Button_ApplyClose_Pressed() + + + 314 + 403 + + + 21 + 398 + + + + + + Button_ApplyClose_Pressed() + Button_Cancel_Pressed() + ButtonHELP_Pressed() + Button_RestoreCurrElement_clicked() + Button_RestoreCurrContext_clicked() + Button_RestoreAllContext_clicked() + + diff --git a/qad_windowcolor_dlg.py b/qad_windowcolor_dlg.py new file mode 100644 index 00000000..3700011f --- /dev/null +++ b/qad_windowcolor_dlg.py @@ -0,0 +1,523 @@ +# -*- coding: utf-8 -*- +""" +/*************************************************************************** + QAD Quantum Aided Design plugin + + Gestione dei colori di QAD + + ------------------- + begin : 2016-17-02 + copyright : iiiii + email : hhhhh + developers : bbbbb aaaaa ggggg + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +""" + + +# Import the PyQt and QGIS libraries +from qgis.PyQt.QtCore import Qt, QObject, QItemSelectionModel, QRectF +from qgis.PyQt.QtGui import * +from qgis.PyQt.QtWidgets import * +from qgis.gui import QgsColorButton +from qgis.core import QgsApplication + + +from . import qad_windowcolor_ui + + +from .qad_variables import QadVariable, QadVariableTypeEnum, QadVariables, QadVariablesClass +from .qad_msg import QadMsg, qadShowPluginPDFHelp +from . import qad_utils + + +# =============================================================================== +# QadColorContextEnum class. +# =============================================================================== +class QadColorContextEnum(): + NONE = 0 + MODEL_SPACE_2D = 1 # finestra grafica + COMMAND_LINE = 2 # finestra di comando + + +# =============================================================================== +# QadColorElementEnum class. +# =============================================================================== +class QadColorElementEnum(): + NONE = 0 + CROSSHAIRS = 1 # Puntatori a croce + PICKBOX = 2 # Quadratino di selezione degli oggetti + AUTOTRECK_VECTOR = 3 # Vettore autotrack + AUTOSNAP_MARKER = 4 # Contrassegno di autosnap + COMMAND_HISTORY_BACKGROUND = 5 # Sfondo cronologia comandi + COMMAND_HISTORY_TEXT = 6 # Testo cronologia comandi + PROMPT_BACKGROUND = 7 # Sfondo prompt attivo + PROMPT_TEXT = 8 # Testo prompt attivo + COMMAND_OPTION_KEYWORD = 9 # Parola chiave opzione di comando + COMMAND_OPTION_BACKGROUND = 10 # Sfondo opzione di comando + COMMAND_OPTION_HIGHLIGHTED = 11 # Opzione di comando evidenziata + DI_AUTOTRECK_VECTOR = 12 # Dynamic input - Linne di quota dinamiche + DI_COMMAND_DESCR = 13 # Dynamic input - Descrizione comando + DI_COMMAND_DESCR_BACKGROUND = 14 # Dynamic input - Sfondo descrizione comando + DI_COMMAND_DESCR_BORDER = 15 # Dynamic input - Bordo descrizione comando + + +####################################################################################### +# Classe che gestisce l'interfaccia grafica per i colori di QAD +class QadWindowColorDialog(QDialog, QObject, qad_windowcolor_ui.Ui_WindowColor_Dialog): + def __init__(self, plugIn, parent, contextEnum = QadColorContextEnum.NONE, elementEnum = QadColorElementEnum.NONE): + self.plugIn = plugIn + self.iface = self.plugIn.iface.mainWindow() + + QDialog.__init__(self, parent) + + self.tempQadVariables = QadVariablesClass() + QadVariables.copyTo(self.tempQadVariables) + self.currentVarName = "" + self.currentContext = contextEnum + self.currentElement = elementEnum + + self.setupUi(self) + self.setWindowTitle(QadMsg.getQADTitle() + " - " + self.windowTitle()) + + # Inizializzazione dei colori + self.init_colors() + + if contextEnum != QadColorContextEnum.NONE: + # contesti + index = self.listView_Context.model().index(0,0) # seleziono il primo elemento della lista + context = self.contextList[contextEnum] # context = (, (, )) + contextDescr = context[0] + items = self.listView_Context.model().findItems(contextDescr) + if len(items) > 0: + item = items[0] + if item is not None: + index = self.listView_Context.model().indexFromItem(item) + self.listView_Context.selectionModel().setCurrentIndex(index, QItemSelectionModel.SelectCurrent) + + if elementEnum != QadColorElementEnum.NONE: + # elementi + elementDict = context[1] + element = elementDict[elementEnum] # element = (, (, )) + elementDescr = element[0] + items = self.listView_Element.model().findItems(elementDescr) + if len(items) > 0: + item = items[0] + if item is not None: + index = self.listView_Element.model().indexFromItem(item) + self.listView_Element.selectionModel().setCurrentIndex(index, QItemSelectionModel.SelectCurrent) + + + # ============================================================================ + # setupUi + # ============================================================================ + def setupUi(self, Dialog): + qad_windowcolor_ui.Ui_WindowColor_Dialog.setupUi(self, self) + # aggiungo il bottone di qgis QgsColorButton chiamato buttonColor + # che eredita la posizione di Button_ColorDummy (che viene nascosto) + self.Button_ColorDummy.setHidden(True) + self.buttonColor = QgsColorButton(self.Button_ColorDummy.parent()) + self.buttonColor.setGeometry(self.Button_ColorDummy.geometry()) + self.buttonColor.setObjectName("buttonColor") + self.buttonColor.colorChanged.connect(self.colorChanged) + + # aggiungo il QWidget chiamato QadPreview + # che eredita la posizione di widget_Preview (che viene nascosto) + self.widget_Preview.setHidden(True) + self.preview = QadPreview(self.plugIn, self.widget_Preview.parent(), self.tempQadVariables, self.currentContext) + self.preview.setGeometry(self.widget_Preview.geometry()) + self.preview.setObjectName("preview") + + + # ============================================================================ + # init_context_list + # ============================================================================ + def init_context_list(self): + self.contextList = dict() + + # description, element dictionary + contextDescr = QadMsg.translate("WindowColor_Dialog", "Model Space") # x lupdate + self.contextList[QadColorContextEnum.MODEL_SPACE_2D] = [contextDescr, self.get_MODEL_SPACE_2D_element_dict()] + contextDescr = QadMsg.translate("WindowColor_Dialog", "Command line") # x lupdate + self.contextList[QadColorContextEnum.COMMAND_LINE] = [contextDescr, self.get_COMMAND_LINE_element_dict()] + + + # ============================================================================ + # get_MODEL_SPACE_2D_element_dict + # ============================================================================ + def get_MODEL_SPACE_2D_element_dict(self): + elementList = dict() + + # description, system variable + elementDescr = QadMsg.translate("WindowColor_Dialog", "Crosshairs") # x lupdate + elementVarName = QadMsg.translate("Environment variables", "CURSORCOLOR") # x lupdate + elementList[QadColorElementEnum.CROSSHAIRS] = [elementDescr, elementVarName] + + # description, system variable + elementDescr = QadMsg.translate("WindowColor_Dialog", "Pickbox") # x lupdate + elementVarName = QadMsg.translate("Environment variables", "PICKBOXCOLOR") # x lupdate + elementList[QadColorElementEnum.PICKBOX] = [elementDescr, elementVarName] + + # description, system variable + elementDescr = QadMsg.translate("WindowColor_Dialog", "Autotreck vector") # x lupdate + elementVarName = QadMsg.translate("Environment variables", "AUTOTRECKINGVECTORCOLOR") # x lupdate + elementList[QadColorElementEnum.AUTOTRECK_VECTOR] = [elementDescr, elementVarName] + + # description, system variable + elementDescr = QadMsg.translate("WindowColor_Dialog", "Autosnap marker") # x lupdate + elementVarName = QadMsg.translate("Environment variables", "AUTOSNAPCOLOR") # x lupdate + elementList[QadColorElementEnum.AUTOSNAP_MARKER] = [elementDescr, elementVarName] + + # description, system variable + elementDescr = QadMsg.translate("WindowColor_Dialog", "Dynamic dimension lines") # x lupdate + elementVarName = QadMsg.translate("Environment variables", "DYNTRECKINGVECTORCOLOR") # x lupdate + elementList[QadColorElementEnum.DI_AUTOTRECK_VECTOR] = [elementDescr, elementVarName] + + # description, system variable + elementDescr = QadMsg.translate("WindowColor_Dialog", "Drafting tool tip") # x lupdate + elementVarName = QadMsg.translate("Environment variables", "DYNEDITFORECOLOR") # x lupdate + elementList[QadColorElementEnum.DI_COMMAND_DESCR] = [elementDescr, elementVarName] + + # description, system variable + elementDescr = QadMsg.translate("WindowColor_Dialog", "Drafting tool tip contour") # x lupdate + elementVarName = QadMsg.translate("Environment variables", "DYNEDITBORDERCOLOR") # x lupdate + elementList[QadColorElementEnum.DI_COMMAND_DESCR_BACKGROUND] = [elementDescr, elementVarName] + + # description, system variable + elementDescr = QadMsg.translate("WindowColor_Dialog", "Drafting tool tip background") # x lupdate + elementVarName = QadMsg.translate("Environment variables", "DYNEDITBACKCOLOR") # x lupdate + elementList[QadColorElementEnum.DI_COMMAND_DESCR_BORDER] = [elementDescr, elementVarName] + + + return elementList + + + # ============================================================================ + # get_COMMAND_LINE_element_dict + # ============================================================================ + def get_COMMAND_LINE_element_dict(self): + elementList = dict() + + # description, system variable + elementDescr = QadMsg.translate("WindowColor_Dialog", "Command history background") # x lupdate + elementVarName = QadMsg.translate("Environment variables", "CMDHISTORYBACKCOLOR") # x lupdate + elementList[QadColorElementEnum.COMMAND_HISTORY_BACKGROUND] = [elementDescr, elementVarName] + + # description, system variable + elementDescr = QadMsg.translate("WindowColor_Dialog", "Command history text") # x lupdate + elementVarName = QadMsg.translate("Environment variables", "CMDHISTORYFORECOLOR") # x lupdate + elementList[QadColorElementEnum.COMMAND_HISTORY_TEXT] = [elementDescr, elementVarName] + + # description, system variable + elementDescr = QadMsg.translate("WindowColor_Dialog", "Active prompt background") # x lupdate + elementVarName = QadMsg.translate("Environment variables", "CMDLINEBACKCOLOR") # x lupdate + elementList[QadColorElementEnum.PROMPT_BACKGROUND] = [elementDescr, elementVarName] + + # description, system variable + elementDescr = QadMsg.translate("WindowColor_Dialog", "Active prompt text") # x lupdate + elementVarName = QadMsg.translate("Environment variables", "CMDLINEFORECOLOR") # x lupdate + elementList[QadColorElementEnum.PROMPT_TEXT] = [elementDescr, elementVarName] + + # description, system variable + elementDescr = QadMsg.translate("WindowColor_Dialog", "Command option keyword") # x lupdate + elementVarName = QadMsg.translate("Environment variables", "CMDLINEOPTCOLOR") # x lupdate + elementList[QadColorElementEnum.COMMAND_OPTION_KEYWORD] = [elementDescr, elementVarName] + + # description, system variable + elementDescr = QadMsg.translate("WindowColor_Dialog", "Command option keyword background") # x lupdate + elementVarName = QadMsg.translate("Environment variables", "CMDLINEOPTBACKCOLOR") # x lupdate + elementList[QadColorElementEnum.COMMAND_OPTION_BACKGROUND] = [elementDescr, elementVarName] + + # description, system variable + elementDescr = QadMsg.translate("WindowColor_Dialog", "Command option highlighted") # x lupdate + elementVarName = QadMsg.translate("Environment variables", "CMDLINEOPTHIGHLIGHTEDCOLOR") # x lupdate + elementList[QadColorElementEnum.COMMAND_OPTION_HIGHLIGHTED] = [elementDescr, elementVarName] + + return elementList + + + # ============================================================================ + # init_colors + # ============================================================================ + def init_colors(self): + self.init_context_list() + + # Inizializzazione della lista dei contesti + model = QStandardItemModel(self.listView_Context) + contexts = self.contextList.items() # lista dei contesti + for context in contexts: + # context = (, (, )) + contextDescr = context[1][0] + # Create an item with a caption + item = QStandardItem(contextDescr) + item.setData(context) + model.appendRow(item) + + self.listView_Context.setModel(model) + + # collego l'evento "cambio di selezione" alla funzione self.contextChanged + self.listView_Context.selectionModel().selectionChanged.connect(self.contextChanged) + + + # ============================================================================ + # contextChanged + # ============================================================================ + def contextChanged(self, current, previous): + # leggo ciò che selezionato + index = current.indexes()[0] + item = self.listView_Context.model().itemFromIndex(index) + context = item.data() # context = (, (, )) + self.currentContext = context[0] + elementDict = context[1][1] + self.preview.refreshColors(self.currentContext, self.tempQadVariables) # forzo il disegno del preview + self.currentVarName = "" + + # Inizializzazione della lista dei contesti + model = QStandardItemModel(self.listView_Element) + elements = elementDict.items() # lista degli elementi + for element in elements: + # element = (, (, )) + elementDescr = element[1][0] + # Create an item with a caption + item = QStandardItem(elementDescr) + item.setData(element) + model.appendRow(item) + + self.listView_Element.setModel(model) + # collego l'evento "cambio di selezione" alla funzione self.elementChanged + self.listView_Element.selectionModel().selectionChanged.connect(self.elementChanged) + + + # ============================================================================ + # elementChanged + # ============================================================================ + def elementChanged(self, current, previous): + # leggo ciò che selezionato + index = current.indexes()[0] + item = self.listView_Element.model().itemFromIndex(index) + element = item.data() # element = (, (, )) + self.currentElement = element[0] + self.currentVarName = element[1][1] + self.buttonColor.setColor(QColor(self.tempQadVariables.get(self.currentVarName))) + + + # ============================================================================ + # colorChanged + # ============================================================================ + def colorChanged(self, value): + self.tempQadVariables.set(self.currentVarName, self.buttonColor.color().name()) + self.preview.refreshColors(self.currentContext, self.tempQadVariables) # forzo il disegno del preview + + + # ============================================================================ + # restoreVarValueElement + # ============================================================================ + def restoreVarValueElement(self, varName): + variable = QadVariables.getVariable(varName) + if variable is None: + return False + self.tempQadVariables.set(varName, variable.default) + return True + + + # ============================================================================ + # restoreContext + # ============================================================================ + def restoreContext(self, context): + context = self.contextList[context] # context = (, (, )) + elementDict = context[1] + elements = elementDict.items() # lista degli elementi + for element in elements: + # element = (, (, )) + varName = element[1][1] + self.restoreVarValueElement(varName) + + + # ============================================================================ + # Button_RestoreCurrElement_clicked + # ============================================================================ + def Button_RestoreCurrElement_clicked(self): + if self.restoreVarValueElement(self.currentVarName): + self.preview.refreshColors(self.currentContext, self.tempQadVariables) # forzo il disegno del preview + self.buttonColor.setColor(QColor(self.tempQadVariables.get(self.currentVarName))) + + + # ============================================================================ + # Button_RestoreCurrContext_clicked + # ============================================================================ + def Button_RestoreCurrContext_clicked(self): + self.restoreContext(self.currentContext) + self.preview.refreshColors(self.currentContext, self.tempQadVariables) # forzo il disegno del preview + if self.currentVarName != "": + self.buttonColor.setColor(QColor(self.tempQadVariables.get(self.currentVarName))) + + + # ============================================================================ + # Button_RestoreAllContext_clicked + # ============================================================================ + def Button_RestoreAllContext_clicked(self): + contexts = self.contextList.keys() # lista dei contesti + for context in contexts: + self.restoreContext(context) + + self.preview.refreshColors(self.currentContext, self.tempQadVariables) # forzo il disegno del preview + if self.currentVarName != "": + self.buttonColor.setColor(QColor(self.tempQadVariables.get(self.currentVarName))) + + + + # ============================================================================ + # getSysVariableList + # ============================================================================ + def getSysVariableList(self): + # ritorna una lista di variabili di sistema dei colori gestiti da questa finestra + variables = [] + contexts = self.contextList.items() # lista dei contesti + for context in contexts: + # context = (, (, )) + elementDict = context[1][1] + elements = elementDict.items() # lista degli elementi + for element in elements: + # element = (, (, )) + varName = element[1][1] + varValue = self.tempQadVariables.get(varName) + variables.append(QadVariable(varName, varValue, QadVariableTypeEnum.COLOR)) + return variables + + + def Button_ApplyClose_Pressed(self): + # copio i valori dei colori in QadVariables e li salvo + variables = self.getSysVariableList() + for variable in variables: + QadVariables.set(variable.name, variable.value) + QadVariables.save() + self.plugIn.TextWindow.refreshColors() + + QDialog.accept(self) + + + def Button_Cancel_Pressed(self): + QDialog.reject(self) + + + def ButtonHELP_Pressed(self): + qadShowPluginPDFHelp(QadMsg.translate("Help", "")) + + +# =============================================================================== +# QadPreview class. +# =============================================================================== +class QadPreview(QWidget): + def __init__(self, plugIn, parent, tempQadVariables, context, windowFlags = Qt.Widget): + self.plugIn = plugIn + self.context = context + self.tempQadVariables = tempQadVariables + QWidget.__init__(self, parent, windowFlags) + + def refreshColors(self, context, tempQadVariables): + self.context = context + self.tempQadVariables = tempQadVariables + self.update() # forzo il disegno del preview + + def paintEvent(self, event): + if self.context == QadColorContextEnum.MODEL_SPACE_2D: + self.paint_MODEL_SPACE_2D() + elif self.context == QadColorContextEnum.COMMAND_LINE: + self.paint_COMMAND_LINE() + + def paint_MODEL_SPACE_2D(self): + rect = self.rect() + painter = QPainter(self) + painter.fillRect(rect, self.plugIn.canvas.canvasColor()) + painter.setRenderHint(QPainter.Antialiasing) + + # PICKBOX + x1 = (int) (rect.width() / 3) + y1 = (int) (rect.height() - rect.height() / 3) + color = QColor(self.tempQadVariables.get(QadMsg.translate("Environment variables", "PICKBOXCOLOR"))) + pickSize = 5 + painter.setPen(QPen(color, 1, Qt.SolidLine)) + painter.drawRect(x1 - pickSize, y1 - pickSize, 2 * pickSize, 2 * pickSize) + + # CROSSHAIRS + color = QColor(self.tempQadVariables.get(QadMsg.translate("Environment variables", "CURSORCOLOR"))) + cursorSize = 20 + painter.setPen(QPen(color, 1, Qt.SolidLine)) + painter.drawLine(x1 - pickSize, y1, x1 - pickSize - cursorSize, y1) + painter.drawLine(x1 + pickSize, y1, x1 + pickSize + cursorSize, y1) + painter.drawLine(x1, y1 - pickSize, x1, y1 - pickSize - cursorSize) + painter.drawLine(x1, y1 + pickSize, x1, y1 + pickSize + cursorSize) + + # AUTOTRECK_VECTOR + x1 = (int) (rect.width() / 3) + color = QColor(self.tempQadVariables.get(QadMsg.translate("Environment variables", "AUTOTRECKINGVECTORCOLOR"))) + painter.setPen(QPen(color, 1, Qt.DashLine)) + painter.drawLine(x1, 0, x1, rect.height()) + painter.drawLine((int) (x1 + rect.height() * 2 / 3), 0, (int) (x1 - rect.height() / 3), rect.height()) + + # AUTOSNAP + x1 = (int) (rect.width() / 3) + y1 = (int) (rect.height() / 3) + color = QColor(self.tempQadVariables.get(QadMsg.translate("Environment variables", "AUTOSNAPCOLOR"))) + pickSize = 5 + painter.setPen(QPen(color, 2, Qt.SolidLine)) + painter.drawRect(x1 - pickSize, y1 - pickSize, 2 * pickSize, 2 * pickSize) + + # DYNAMIC INPUT + x1 = (int) (rect.width() / 3) + y1 = (int) (rect.height() - rect.height() / 3) + cursorSize = 20 + fMetrics = painter.fontMetrics() + msg1 = "12.3456" + sz1 = fMetrics.size(Qt.TextSingleLine, msg1 + "__") + dynInputRect1 = QRectF(x1 + cursorSize, y1 + cursorSize, sz1.width(), sz1.height() + 2) + msg2 = "78.9012" + sz2 = fMetrics.size(Qt.TextSingleLine, msg2 + "__") + dynInputRect2 = QRectF(dynInputRect1.right() + sz1.height() / 3, dynInputRect1.top(), sz2.width(), sz2.height() + 2) + # DYNAMIC INPUT COMMAND DESCR BACKGROUND + color = QColor(self.tempQadVariables.get(QadMsg.translate("Environment variables", "DYNEDITBACKCOLOR"))) + painter.fillRect(dynInputRect1, color) + painter.fillRect(dynInputRect2, color) + # DYNAMIC INPUT COMMAND DESCR BORDER + color = QColor(self.tempQadVariables.get(QadMsg.translate("Environment variables", "DYNEDITBORDERCOLOR"))) + painter.setPen(QPen(color, 1, Qt.SolidLine)) + painter.drawRect(dynInputRect1) + painter.drawRect(dynInputRect2) + # DYNAMIC INPUT COMMAND DESCR FOREGROUND + color = QColor(self.tempQadVariables.get(QadMsg.translate("Environment variables", "DYNEDITFORECOLOR"))) + painter.setPen(QPen(color, 1, Qt.SolidLine)) + painter.drawText(dynInputRect1, msg1) + painter.drawText(dynInputRect2, msg2) + + + def paint_COMMAND_LINE(self): + rect = self.rect() + sep = (int) (rect.height() * 2 / 3) + painter = QPainter(self) + + # CMDHISTORYBACKCOLOR + color = QColor(self.tempQadVariables.get(QadMsg.translate("Environment variables", "CMDHISTORYBACKCOLOR"))) + painter.fillRect(0, 0, rect.width(), sep, color) + + # CMDHISTORYFORECOLOR + color = QColor(self.tempQadVariables.get(QadMsg.translate("Environment variables", "CMDHISTORYFORECOLOR"))) + painter.setPen(QPen(color)) + painter.drawText(QRectF(0, 0, rect.width(), sep), Qt.AlignCenter, QadMsg.translate("QAD", "Command: ")) + + # CMDLINEBACKCOLOR + color = QColor(self.tempQadVariables.get(QadMsg.translate("Environment variables", "CMDLINEBACKCOLOR"))) + painter.fillRect(0, sep, rect.width(), rect.height(), color) + + # CMDLINEFORECOLOR + color = QColor(self.tempQadVariables.get(QadMsg.translate("Environment variables", "CMDLINEFORECOLOR"))) + painter.setPen(QPen(color)) + painter.drawText(QRectF(0, sep, rect.width(), rect.height() - sep), Qt.AlignCenter, QadMsg.translate("QAD", "Command: ")) + diff --git a/qad_windowcolor_ui.py b/qad_windowcolor_ui.py new file mode 100644 index 00000000..f64620c7 --- /dev/null +++ b/qad_windowcolor_ui.py @@ -0,0 +1,85 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file '.\qad_windowcolor.ui' +# +# Created by: PyQt5 UI code generator 5.15.4 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_WindowColor_Dialog(object): + def setupUi(self, WindowColor_Dialog): + WindowColor_Dialog.setObjectName("WindowColor_Dialog") + WindowColor_Dialog.resize(592, 424) + WindowColor_Dialog.setMinimumSize(QtCore.QSize(592, 424)) + WindowColor_Dialog.setMaximumSize(QtCore.QSize(592, 424)) + self.Button_Cancel = QtWidgets.QPushButton(WindowColor_Dialog) + self.Button_Cancel.setGeometry(QtCore.QRect(430, 390, 75, 23)) + self.Button_Cancel.setObjectName("Button_Cancel") + self.Button_Help = QtWidgets.QPushButton(WindowColor_Dialog) + self.Button_Help.setGeometry(QtCore.QRect(510, 390, 75, 23)) + self.Button_Help.setObjectName("Button_Help") + self.Button_ApplyClose = QtWidgets.QPushButton(WindowColor_Dialog) + self.Button_ApplyClose.setGeometry(QtCore.QRect(304, 390, 121, 23)) + self.Button_ApplyClose.setObjectName("Button_ApplyClose") + self.label = QtWidgets.QLabel(WindowColor_Dialog) + self.label.setGeometry(QtCore.QRect(10, 10, 161, 16)) + self.label.setObjectName("label") + self.listView_Context = QtWidgets.QListView(WindowColor_Dialog) + self.listView_Context.setGeometry(QtCore.QRect(10, 30, 161, 81)) + self.listView_Context.setObjectName("listView_Context") + self.listView_Element = QtWidgets.QListView(WindowColor_Dialog) + self.listView_Element.setGeometry(QtCore.QRect(180, 30, 231, 171)) + self.listView_Element.setObjectName("listView_Element") + self.label_2 = QtWidgets.QLabel(WindowColor_Dialog) + self.label_2.setGeometry(QtCore.QRect(180, 10, 161, 16)) + self.label_2.setObjectName("label_2") + self.Button_ColorDummy = QtWidgets.QPushButton(WindowColor_Dialog) + self.Button_ColorDummy.setGeometry(QtCore.QRect(420, 30, 161, 23)) + self.Button_ColorDummy.setText("") + self.Button_ColorDummy.setObjectName("Button_ColorDummy") + self.Button_RestoreCurrElement = QtWidgets.QPushButton(WindowColor_Dialog) + self.Button_RestoreCurrElement.setGeometry(QtCore.QRect(420, 120, 161, 23)) + self.Button_RestoreCurrElement.setObjectName("Button_RestoreCurrElement") + self.Button_RestoreCurrContext = QtWidgets.QPushButton(WindowColor_Dialog) + self.Button_RestoreCurrContext.setGeometry(QtCore.QRect(420, 150, 161, 23)) + self.Button_RestoreCurrContext.setObjectName("Button_RestoreCurrContext") + self.Button_RestoreAllContext = QtWidgets.QPushButton(WindowColor_Dialog) + self.Button_RestoreAllContext.setGeometry(QtCore.QRect(420, 180, 161, 23)) + self.Button_RestoreAllContext.setObjectName("Button_RestoreAllContext") + self.widget_Preview = QtWidgets.QWidget(WindowColor_Dialog) + self.widget_Preview.setGeometry(QtCore.QRect(10, 210, 401, 171)) + self.widget_Preview.setObjectName("widget_Preview") + self.label_3 = QtWidgets.QLabel(WindowColor_Dialog) + self.label_3.setGeometry(QtCore.QRect(10, 190, 161, 16)) + self.label_3.setObjectName("label_3") + self.label_4 = QtWidgets.QLabel(WindowColor_Dialog) + self.label_4.setGeometry(QtCore.QRect(420, 10, 161, 16)) + self.label_4.setObjectName("label_4") + + self.retranslateUi(WindowColor_Dialog) + self.Button_Cancel.clicked.connect(WindowColor_Dialog.Button_Cancel_Pressed) + self.Button_Help.clicked.connect(WindowColor_Dialog.ButtonHELP_Pressed) + self.Button_RestoreCurrElement.clicked.connect(WindowColor_Dialog.Button_RestoreCurrElement_clicked) + self.Button_RestoreCurrContext.clicked.connect(WindowColor_Dialog.Button_RestoreCurrContext_clicked) + self.Button_RestoreAllContext.clicked.connect(WindowColor_Dialog.Button_RestoreAllContext_clicked) + self.Button_ApplyClose.clicked.connect(WindowColor_Dialog.Button_ApplyClose_Pressed) + QtCore.QMetaObject.connectSlotsByName(WindowColor_Dialog) + + def retranslateUi(self, WindowColor_Dialog): + _translate = QtCore.QCoreApplication.translate + WindowColor_Dialog.setWindowTitle(_translate("WindowColor_Dialog", "Drawing window Colors")) + self.Button_Cancel.setText(_translate("WindowColor_Dialog", "Cancel")) + self.Button_Help.setText(_translate("WindowColor_Dialog", "Help")) + self.Button_ApplyClose.setText(_translate("WindowColor_Dialog", "Apply && Close")) + self.label.setText(_translate("WindowColor_Dialog", "Context:")) + self.label_2.setText(_translate("WindowColor_Dialog", "Interface element:")) + self.Button_RestoreCurrElement.setText(_translate("WindowColor_Dialog", "Restore current element")) + self.Button_RestoreCurrContext.setText(_translate("WindowColor_Dialog", "Restore current context")) + self.Button_RestoreAllContext.setText(_translate("WindowColor_Dialog", "Restore all context")) + self.label_3.setText(_translate("WindowColor_Dialog", "Preview:")) + self.label_4.setText(_translate("WindowColor_Dialog", "Color:")) diff --git a/sample_data/dim_line.dbf b/sample_data/dim_line.dbf index 9d460def..f24de406 100644 Binary files a/sample_data/dim_line.dbf and b/sample_data/dim_line.dbf differ diff --git a/sample_data/dim_line.shp b/sample_data/dim_line.shp index eb48c49a..b3346b73 100644 Binary files a/sample_data/dim_line.shp and b/sample_data/dim_line.shp differ diff --git a/sample_data/dim_line.shx b/sample_data/dim_line.shx index a11f22b3..b3346b73 100644 Binary files a/sample_data/dim_line.shx and b/sample_data/dim_line.shx differ diff --git a/sample_data/dim_symbol.dbf b/sample_data/dim_symbol.dbf index 204b5ebe..0b3add77 100644 Binary files a/sample_data/dim_symbol.dbf and b/sample_data/dim_symbol.dbf differ diff --git a/sample_data/dim_symbol.shp b/sample_data/dim_symbol.shp index 4ab53546..29de63a1 100644 Binary files a/sample_data/dim_symbol.shp and b/sample_data/dim_symbol.shp differ diff --git a/sample_data/dim_symbol.shx b/sample_data/dim_symbol.shx index 62b42a27..29de63a1 100644 Binary files a/sample_data/dim_symbol.shx and b/sample_data/dim_symbol.shx differ diff --git a/sample_data/dim_text.dbf b/sample_data/dim_text.dbf index d546c53c..18d3bc5e 100644 Binary files a/sample_data/dim_text.dbf and b/sample_data/dim_text.dbf differ diff --git a/sample_data/dim_text.qix b/sample_data/dim_text.qix new file mode 100644 index 00000000..bfa1f8e9 Binary files /dev/null and b/sample_data/dim_text.qix differ diff --git a/sample_data/dim_text.shp b/sample_data/dim_text.shp index 342a337b..29de63a1 100644 Binary files a/sample_data/dim_text.shp and b/sample_data/dim_text.shp differ diff --git a/sample_data/dim_text.shx b/sample_data/dim_text.shx index 47987bee..29de63a1 100644 Binary files a/sample_data/dim_text.shx and b/sample_data/dim_text.shx differ diff --git a/sample_data/dimension.qgs b/sample_data/dimension.qgs index a60c01cb..676cdae0 100644 --- a/sample_data/dimension.qgs +++ b/sample_data/dimension.qgs @@ -1,991 +1,2272 @@ - + + - - - - + + + + + PROJCRS["Monte Mario / Italy zone 1",BASEGEOGCRS["Monte Mario",DATUM["Monte Mario",ELLIPSOID["International 1924",6378388,297,LENGTHUNIT["metre",1]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],ID["EPSG",4265]],CONVERSION["Italy zone 1",METHOD["Transverse Mercator",ID["EPSG",9807]],PARAMETER["Latitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8801]],PARAMETER["Longitude of natural origin",9,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8802]],PARAMETER["Scale factor at natural origin",0.9996,SCALEUNIT["unity",1],ID["EPSG",8805]],PARAMETER["False easting",1500000,LENGTHUNIT["metre",1],ID["EPSG",8806]],PARAMETER["False northing",0,LENGTHUNIT["metre",1],ID["EPSG",8807]]],CS[Cartesian,2],AXIS["easting (X)",east,ORDER[1],LENGTHUNIT["metre",1]],AXIS["northing (Y)",north,ORDER[2],LENGTHUNIT["metre",1]],USAGE[SCOPE["Engineering survey, topographic mapping."],AREA["Italy - onshore and offshore - west of 12°E."],BBOX[36.53,5.93,47.04,12]],ID["EPSG",3003]] + +proj=tmerc +lat_0=0 +lon_0=9 +k=0.9996 +x_0=1500000 +y_0=0 +ellps=intl +units=m +no_defs + 968 + 3003 + EPSG:3003 + Monte Mario / Italy zone 1 + tmerc + EPSG:7022 + false + + + + + + + + + - - + + + - - + + + + + dim_line20150923094228051 + dim_symbol20150923094228084 + dim_text20150923094228115 + + + + + + + + - + + meters - 14.92412695654826038 - -10.90823725970652269 - 26.9048592481432074 - 28.49928824285221651 + 64.5495810717279852 + 11.10583124124840992 + 88.51104565491790765 + 89.92088224636583504 0 - 1 - - +proj=tmerc +lat_0=0 +lon_0=9 +k=0.9996 +x_0=1500000 +y_0=0 +ellps=intl +towgs84=-104.1,-49.1,-9.9,0.971,-2.917,0.714,-11.68 +units=m +no_defs + + PROJCRS["Monte Mario / Italy zone 1",BASEGEOGCRS["Monte Mario",DATUM["Monte Mario",ELLIPSOID["International 1924",6378388,297,LENGTHUNIT["metre",1]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],ID["EPSG",4265]],CONVERSION["Italy zone 1",METHOD["Transverse Mercator",ID["EPSG",9807]],PARAMETER["Latitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8801]],PARAMETER["Longitude of natural origin",9,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8802]],PARAMETER["Scale factor at natural origin",0.9996,SCALEUNIT["unity",1],ID["EPSG",8805]],PARAMETER["False easting",1500000,LENGTHUNIT["metre",1],ID["EPSG",8806]],PARAMETER["False northing",0,LENGTHUNIT["metre",1],ID["EPSG",8807]]],CS[Cartesian,2],AXIS["easting (X)",east,ORDER[1],LENGTHUNIT["metre",1]],AXIS["northing (Y)",north,ORDER[2],LENGTHUNIT["metre",1]],USAGE[SCOPE["Engineering survey, topographic mapping."],AREA["Italy - onshore and offshore - west of 12°E."],BBOX[36.53,5.93,47.04,12]],ID["EPSG",3003]] + +proj=tmerc +lat_0=0 +lon_0=9 +k=0.9996 +x_0=1500000 +y_0=0 +ellps=intl +units=m +no_defs 968 3003 EPSG:3003 Monte Mario / Italy zone 1 tmerc - intl + EPSG:7022 false - - - - - + 0 + - - - - dim_line20150923094228051 - dim_symbol20150923094228084 - dim_text20150923094228115 - - + - - - - - dim_line20150923094228051 - ./dim_line.shp + + + Annotations_ee0fce21_4624_43cd_a4fa_a77603d895e6 + + + + + + + + + + 0 + 0 + + + + + false + + + + + + + + + + + + + + + + 0 + 0 + + + + + false + + + + + + + 1 + 1 + 1 + 0 + + + + 1 + 0 + + + + + + -12.83366278749123168 + 17.67927167849559211 + 136.11138173638576632 + 90.40770482759378979 + + + -4.35653443668093221 + 0.00085882780804157 + -4.35523237972381239 + 0.00149890222579916 + + dim_line20150923094228051 + ./dim_line.shp dim_line - - +proj=tmerc +lat_0=0 +lon_0=9 +k=0.9996 +x_0=1500000 +y_0=0 +ellps=intl +towgs84=-104.1,-49.1,-9.9,0.971,-2.917,0.714,-11.68 +units=m +no_defs + + PROJCRS["Monte Mario / Italy zone 1",BASEGEOGCRS["Monte Mario",DATUM["Monte Mario",ELLIPSOID["International 1924",6378388,297,LENGTHUNIT["metre",1]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],ID["EPSG",4265]],CONVERSION["Italy zone 1",METHOD["Transverse Mercator",ID["EPSG",9807]],PARAMETER["Latitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8801]],PARAMETER["Longitude of natural origin",9,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8802]],PARAMETER["Scale factor at natural origin",0.9996,SCALEUNIT["unity",1],ID["EPSG",8805]],PARAMETER["False easting",1500000,LENGTHUNIT["metre",1],ID["EPSG",8806]],PARAMETER["False northing",0,LENGTHUNIT["metre",1],ID["EPSG",8807]]],CS[Cartesian,2],AXIS["easting (X)",east,ORDER[1],LENGTHUNIT["metre",1]],AXIS["northing (Y)",north,ORDER[2],LENGTHUNIT["metre",1]],USAGE[SCOPE["Engineering survey, topographic mapping."],AREA["Italy - onshore and offshore - west of 12°E."],BBOX[36.53,5.93,47.04,12]],ID["EPSG",3003]] + +proj=tmerc +lat_0=0 +lon_0=9 +k=0.9996 +x_0=1500000 +y_0=0 +ellps=intl +units=m +no_defs 968 3003 EPSG:3003 Monte Mario / Italy zone 1 tmerc - intl + EPSG:7022 false - ogr - COALESCE( "id", '<NULL>' ) + + + + + + + + + + + + + + + + + + + + + + + + +proj=tmerc +lat_0=0 +lon_0=9 +k=0.9996 +x_0=1500000 +y_0=0 +ellps=intl +units=m +no_defs + 0 + 0 + + + + + false + + + + + + + + + + + + + ogr + + - - + + - - - - - - - - - - - - - - - - - - + + + + 1 + 1 + 1 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + - + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + 0 0 - 0 - id - - - - . + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + . + 0 + + 0 - . generatedlayout - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + COALESCE( "id", '<NULL>' ) + id - + + + -12.83366278749123168 + 17.67927167849559211 + 136.11138173638576632 + 90.40770482759378979 + + + -4.35653443668093221 + 0.00085882780804157 + -4.35523237972381239 + 0.00149890222579916 + dim_symbol20150923094228084 ./dim_symbol.shp - - dim_symbol - - +proj=tmerc +lat_0=0 +lon_0=9 +k=0.9996 +x_0=1500000 +y_0=0 +ellps=intl +towgs84=-104.1,-49.1,-9.9,0.971,-2.917,0.714,-11.68 +units=m +no_defs + + PROJCRS["Monte Mario / Italy zone 1",BASEGEOGCRS["Monte Mario",DATUM["Monte Mario",ELLIPSOID["International 1924",6378388,297,LENGTHUNIT["metre",1]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],ID["EPSG",4265]],CONVERSION["Italy zone 1",METHOD["Transverse Mercator",ID["EPSG",9807]],PARAMETER["Latitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8801]],PARAMETER["Longitude of natural origin",9,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8802]],PARAMETER["Scale factor at natural origin",0.9996,SCALEUNIT["unity",1],ID["EPSG",8805]],PARAMETER["False easting",1500000,LENGTHUNIT["metre",1],ID["EPSG",8806]],PARAMETER["False northing",0,LENGTHUNIT["metre",1],ID["EPSG",8807]]],CS[Cartesian,2],AXIS["easting (X)",east,ORDER[1],LENGTHUNIT["metre",1]],AXIS["northing (Y)",north,ORDER[2],LENGTHUNIT["metre",1]],USAGE[SCOPE["Engineering survey, topographic mapping."],AREA["Italy - onshore and offshore - west of 12°E."],BBOX[36.53,5.93,47.04,12]],ID["EPSG",3003]] + +proj=tmerc +lat_0=0 +lon_0=9 +k=0.9996 +x_0=1500000 +y_0=0 +ellps=intl +units=m +no_defs 968 3003 EPSG:3003 Monte Mario / Italy zone 1 tmerc - intl + EPSG:7022 false - ogr - COALESCE( "id", '<NULL>' ) + + + + + + + + + + + + + + + + + + + + + + + + +proj=tmerc +lat_0=0 +lon_0=9 +k=0.9996 +x_0=1500000 +y_0=0 +ellps=intl +units=m +no_defs + 0 + 0 + + + + + false + + + + + + + + + + + + + ogr + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - + + + + 1 + 1 + 1 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + 0 0 - 0 - id - - - - . + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + . + 0 + + 0 - . generatedlayout - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + COALESCE( "id", '<NULL>' ) + id - + + + 2.44972368866019341 + 43.78494621453982205 + 121.92800820794229821 + 82.31928283704711191 + + + -4.3564008316324303 + 0.00108857751534781 + -4.35535637021980904 + 0.00142771681173785 + dim_text20150923094228115 ./dim_text.shp - - dim_text - - +proj=tmerc +lat_0=0 +lon_0=9 +k=0.9996 +x_0=1500000 +y_0=0 +ellps=intl +towgs84=-104.1,-49.1,-9.9,0.971,-2.917,0.714,-11.68 +units=m +no_defs + + PROJCRS["Monte Mario / Italy zone 1",BASEGEOGCRS["Monte Mario",DATUM["Monte Mario",ELLIPSOID["International 1924",6378388,297,LENGTHUNIT["metre",1]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],ID["EPSG",4265]],CONVERSION["Italy zone 1",METHOD["Transverse Mercator",ID["EPSG",9807]],PARAMETER["Latitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8801]],PARAMETER["Longitude of natural origin",9,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8802]],PARAMETER["Scale factor at natural origin",0.9996,SCALEUNIT["unity",1],ID["EPSG",8805]],PARAMETER["False easting",1500000,LENGTHUNIT["metre",1],ID["EPSG",8806]],PARAMETER["False northing",0,LENGTHUNIT["metre",1],ID["EPSG",8807]]],CS[Cartesian,2],AXIS["easting (X)",east,ORDER[1],LENGTHUNIT["metre",1]],AXIS["northing (Y)",north,ORDER[2],LENGTHUNIT["metre",1]],USAGE[SCOPE["Engineering survey, topographic mapping."],AREA["Italy - onshore and offshore - west of 12°E."],BBOX[36.53,5.93,47.04,12]],ID["EPSG",3003]] + +proj=tmerc +lat_0=0 +lon_0=9 +k=0.9996 +x_0=1500000 +y_0=0 +ellps=intl +units=m +no_defs 968 3003 EPSG:3003 Monte Mario / Italy zone 1 tmerc - intl + EPSG:7022 false - ogr - COALESCE( "id", '<NULL>' ) + + + + + + + + + + + + + + + + + + + + + + + + +proj=tmerc +lat_0=0 +lon_0=9 +k=0.9996 +x_0=1500000 +y_0=0 +ellps=intl +units=m +no_defs + 0 + 0 + + + + + false + + + + + + + + + + + + + ogr + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + 1 + 1 + 1 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + 0 0 - 0 - id - - - - . + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + . + 0 + + 0 - . generatedlayout - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + COALESCE( "id", '<NULL>' ) + id + + + + + - - - false - - - - - - - false - - - - - - - - 2 - true - D - - - - false - - false - - intl - - 8 - - - - - + + + + 2 + 10 + 2 + to vertex and segment + + to_vertex_and_segment + to_vertex_and_segment + to_vertex_and_segment + + + enabled + enabled + enabled + + + dim_line20150923094228051 + dim_symbol20150923094228084 + dim_text20150923094228115 + + + 10.000000 + 10.000000 + 10.000000 + + + 2 + 2 + 2 + + current_layer + - 0 + 0 0 0 - 255 255 + 0 255 - 0 + 255 - - 2 - current_layer - to vertex and segment - 0 - - - - - - false + + + + + intl + + + m2 + meters + + + 50 + 5 + 16 + 30 + 2.5 + false + false + false + 0 + 0 + false + false + true + 0 + 255,0,0,255 + + + false + + + true + 2 + D + + 968 +proj=tmerc +lat_0=0 +lon_0=9 +k=0.9996 +x_0=1500000 +y_0=0 +ellps=intl +towgs84=-104.1,-49.1,-9.9,0.971,-2.917,0.714,-11.68 +units=m +no_defs EPSG:3003 - 968 1 - - - - - true - 255 - - + + + + + + + + + + + false + + + + 90 + + + + + 8 + + + + false + + false + + + + + + + + + + + + + + + + + + + + + + + + + + + PROJCRS["Monte Mario / Italy zone 1",BASEGEOGCRS["Monte Mario",DATUM["Monte Mario",ELLIPSOID["International 1924",6378388,297,LENGTHUNIT["metre",1]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],ID["EPSG",4265]],CONVERSION["Italy zone 1",METHOD["Transverse Mercator",ID["EPSG",9807]],PARAMETER["Latitude of natural origin",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8801]],PARAMETER["Longitude of natural origin",9,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8802]],PARAMETER["Scale factor at natural origin",0.9996,SCALEUNIT["unity",1],ID["EPSG",8805]],PARAMETER["False easting",1500000,LENGTHUNIT["metre",1],ID["EPSG",8806]],PARAMETER["False northing",0,LENGTHUNIT["metre",1],ID["EPSG",8807]]],CS[Cartesian,2],AXIS["easting (X)",east,ORDER[1],LENGTHUNIT["metre",1]],AXIS["northing (Y)",north,ORDER[2],LENGTHUNIT["metre",1]],USAGE[SCOPE["Engineering survey, topographic mapping."],AREA["Italy - onshore and offshore - west of 12°E."],BBOX[36.53,5.93,47.04,12]],ID["EPSG",3003]] + +proj=tmerc +lat_0=0 +lon_0=9 +k=0.9996 +x_0=1500000 +y_0=0 +ellps=intl +units=m +no_defs + 968 + 3003 + EPSG:3003 + Monte Mario / Italy zone 1 + tmerc + EPSG:7022 + false + + + + + + + + + + + + + + + + + + + + + + GEOGCRS["WGS 84",ENSEMBLE["World Geodetic System 1984 ensemble",MEMBER["World Geodetic System 1984 (Transit)"],MEMBER["World Geodetic System 1984 (G730)"],MEMBER["World Geodetic System 1984 (G873)"],MEMBER["World Geodetic System 1984 (G1150)"],MEMBER["World Geodetic System 1984 (G1674)"],MEMBER["World Geodetic System 1984 (G1762)"],MEMBER["World Geodetic System 1984 (G2139)"],ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]],ENSEMBLEACCURACY[2.0]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["Horizontal component of 3D system."],AREA["World."],BBOX[-90,-180,90,180]],ID["EPSG",4326]] + +proj=longlat +datum=WGS84 +no_defs + 3452 + 4326 + EPSG:4326 + WGS 84 + longlat + EPSG:7030 + true + + + + + + diff --git a/sample_data/dimension_qad.ini b/sample_data/dimension_qad.ini new file mode 100644 index 00000000..6e4385be --- /dev/null +++ b/sample_data/dimension_qad.ini @@ -0,0 +1,11 @@ +ARCMINSEGMENTQTY = 12 +CIRCLEMINSEGMENTQTY = 12 +DIMSTYLE = standard +ELLIPSEMINSEGMENTQTY = 12 +ELLIPSEARCMINSEGMENTQTY = 12 +FILLETRAD = 0.0 +OFFSETDIST = -1.0 +ORTHOMODE = 0 +OSPROGRDISTANCE = 0.0 +TOLERANCE2COINCIDENT = 1e-07 +TOLERANCE2APPROXCURVE = 0.1 diff --git a/sample_data/standard.dim b/sample_data/standard.dim index f036fcf7..446d9080 100644 --- a/sample_data/standard.dim +++ b/sample_data/standard.dim @@ -1,13 +1,13 @@ [dimension_options] name = standard description = -dimtype = AL +dimtype = AR textprefix = textsuffix = textsuppressleadingzeros = False textdecimalzerossuppression = True -textheight = 2.0 -textverticalpos = 0 +textheight = 1.0 +textverticalpos = 1 texthorizontalpos = 0 textoffsetdist = 0.5 textrotmode = 1 @@ -21,22 +21,23 @@ arcsymbpos = 0 dimline1show = True dimline2show = True dimlinelinetype = continuous -dimlinecolor = #f4ff13 +dimlinecolor = #1aec1a dimlinespaceoffset = 3.75 +dimlineoffsetextline = 0.0 block1name = triangle2 block2name = triangle2 blockleadername = triangle2 -blockwidth = 1.0 +blockwidth = 0.5 blockscale = 1.0 blocksuppressionfornospace = False -centermarksize = 0.0 +centermarksize = 0 textblockadjust = 3 extline1show = True extline2show = True extline1linetype = continuous extline2linetype = continuous -extlinecolor = #f4ff13 -extlineoffsetdimline = 1.0 +extlinecolor = #1aec1a +extlineoffsetdimline = 0.0 extlineoffsetorigpoints = 0.0 extlineisfixedlen = False extlinefixedlen = 1.0 @@ -45,12 +46,12 @@ linearlayername = dim_line symbollayername = dim_symbol componentfieldname = type symbolfieldname = block -linetypefieldname = +linetypefieldname = linetype colorfieldname = color idfieldname = id -idparentfieldname = id_parent -dimstylefieldname = stile -dimtypefieldname = tipo +idparentfieldname = parent_id +dimstylefieldname = style +dimtypefieldname = type scalefieldname = scale rotfieldname = rot diff --git a/standard.dim b/standard.dim deleted file mode 100644 index f036fcf7..00000000 --- a/standard.dim +++ /dev/null @@ -1,56 +0,0 @@ -[dimension_options] -name = standard -description = -dimtype = AL -textprefix = -textsuffix = -textsuppressleadingzeros = False -textdecimalzerossuppression = True -textheight = 2.0 -textverticalpos = 0 -texthorizontalpos = 0 -textoffsetdist = 0.5 -textrotmode = 1 -textforcedrot = 0.0 -textdecimals = 2 -textdecimalsep = . -textfont = Arial -textcolor = #1aec1a -textdirection = 0 -arcsymbpos = 0 -dimline1show = True -dimline2show = True -dimlinelinetype = continuous -dimlinecolor = #f4ff13 -dimlinespaceoffset = 3.75 -block1name = triangle2 -block2name = triangle2 -blockleadername = triangle2 -blockwidth = 1.0 -blockscale = 1.0 -blocksuppressionfornospace = False -centermarksize = 0.0 -textblockadjust = 3 -extline1show = True -extline2show = True -extline1linetype = continuous -extline2linetype = continuous -extlinecolor = #f4ff13 -extlineoffsetdimline = 1.0 -extlineoffsetorigpoints = 0.0 -extlineisfixedlen = False -extlinefixedlen = 1.0 -textuallayername = dim_text -linearlayername = dim_line -symbollayername = dim_symbol -componentfieldname = type -symbolfieldname = block -linetypefieldname = -colorfieldname = color -idfieldname = id -idparentfieldname = id_parent -dimstylefieldname = stile -dimtypefieldname = tipo -scalefieldname = scale -rotfieldname = rot - diff --git a/support/qad_en.pgp b/support/qad_en.pgp index 178766dd..907ce71a 100644 --- a/support/qad_en.pgp +++ b/support/qad_en.pgp @@ -1,133 +1,133 @@ -; -- Command Aliases -- - -; Command alias format: -; ,* - -; The following are guidelines for creating new command aliases. -; 1. An alias should reduce a command by at least two characters. -; 2. Try the first character of the command, then try the first two, -; then the first three. - -; -- Sample aliases for AutoCAD commands -- -; These examples include most frequently used commands. NOTE: It is recommended -; that you not make any changes to this section of the PGP file to ensure the -; proper migration of your customizations when you upgrade to the next version of -; QAD. - -BH, *HATCH -BR, *BREAK -C, *CIRCLE -CH, *PROPERTIES --CH, *CHANGE -CHA, *CHAMFER -CO, *COPY -CP, *COPY -D, *DIMSTYLE -DAL, *DIMALIGNED -DAN, *DIMANGULAR -DAR, *DIMARC -JOG, *DIMJOGGED -DBA, *DIMBASELINE -DCE, *DIMCENTER -DCO, *DIMCONTINUE -DDA, *DIMDISASSOCIATE -DDI, *DIMDIAMETER -DED, *DIMEDIT -DI, *DIST -DIV, *DIVIDE -DLI, *DIMLINEAR -DO, *DONUT -DOR, *DIMORDINATE -DOV, *DIMOVERRIDE -DRA, *DIMRADIUS -DRE, *DIMREASSOCIATE -DS, *DSETTINGS -DST, *DIMSTYLE -DT, *TEXT -E, *ERASE -EL, *ELLIPSE -EX, *EXTEND -EXT, *EXTRUDE -F, *FILLET -H, *HATCH --H, *-HATCH -HE, *HATCHEDIT -I, *INSERT -IMP, *IMPORT -IN, *INTERSECT -J, *JOIN -L, *LINE -LE, *QLEADER -LEN, *LENGTHEN -LI, *LIST -M, *MOVE -ME, *MEASURE -MI, *MIRROR -MO, *PROPERTIES -O, *OFFSET -OP, *OPTIONS -OS, *OSNAP -PE, *PEDIT -PL, *PLINE -PO, *POINT -POL, *POLYGON -PR, *PROPERTIES -PRCLOSE, *PROPERTIESCLOSE -PROPS, *PROPERTIES -PSOLID, *POLYSOLID -PYR, *PYRAMID -QC, *QUICKCALC -REC, *RECTANG -REG, *REGION -REV, *REVOLVE -RO, *ROTATE -S, *STRETCH -SC, *SCALE -SE, *DSETTINGS -SET, *SETVAR -SL, *SLICE -SN, *SNAP -SO, *SOLID -SPL, *SPLINE -SPE, *SPLINEDIT -ST, *STYLE -SU, *SUBTRACT -TB, *TABLE -TOL, *TOLERANCE -TOR, *TORUS -TR, *TRIM -UNI, *UNION -V, *VIEW - -CP, *COPY -DIMALI, *DIMALIGNED -DIMANG, *DIMANGULAR -DIMBASE, *DIMBASELINE -DIMCONT, *DIMCONTINUE -DIMDIA, *DIMDIAMETER -DIMED, *DIMEDIT -DIMTED, *DIMTEDIT -DIMLIN, *DIMLINEAR -DIMORD, *DIMORDINATE -DIMRAD, *DIMRADIUS -DIMSTY, *DIMSTYLE -DIMOVER, *DIMOVERRIDE -LEAD, *LEADER - -DDCHPROP, *PROPERTIES -DDOSNAP, *OSNAP - -BPOLY, *BOUNDARY -DDATTE, *ATTEDIT -DDIM, *DIMSTYLE -DDINSERT, *INSERT -DDRMODES, *DSETTINGS -DDSTYLE, *STYLE -DIMHORIZONTAL, *DIMLINEAR -DIMROTATED, *DIMLINEAR -DIMVERTICAL, *DIMLINEAR -DOUGHNUT, *DONUT -DTEXT, *TEXT -PAINTER, *MATCHPROP -PREFERENCES, *OPTIONS +; -- Command Aliases -- + +; Command alias format: +; ,* + +; The following are guidelines for creating new command aliases. +; 1. An alias should reduce a command by at least two characters. +; 2. Try the first character of the command, then try the first two, +; then the first three. + +; -- Sample aliases for AutoCAD commands -- +; These examples include most frequently used commands. NOTE: It is recommended +; that you not make any changes to this section of the PGP file to ensure the +; proper migration of your customizations when you upgrade to the next version of +; QAD. + +BH, *HATCH +BR, *BREAK +C, *CIRCLE +CH, *PROPERTIES +-CH, *CHANGE +CHA, *CHAMFER +CO, *COPY +CP, *COPY +D, *DIMSTYLE +DAL, *DIMALIGNED +DAN, *DIMANGULAR +DAR, *DIMARC +JOG, *DIMJOGGED +DBA, *DIMBASELINE +DCE, *DIMCENTER +DCO, *DIMCONTINUE +DDA, *DIMDISASSOCIATE +DDI, *DIMDIAMETER +DED, *DIMEDIT +DI, *DIST +DIV, *DIVIDE +DLI, *DIMLINEAR +DO, *DONUT +DOR, *DIMORDINATE +DOV, *DIMOVERRIDE +DRA, *DIMRADIUS +DRE, *DIMREASSOCIATE +DS, *DSETTINGS +DST, *DIMSTYLE +DT, *TEXT +E, *ERASE +EL, *ELLIPSE +EX, *EXTEND +EXT, *EXTRUDE +F, *FILLET +H, *HATCH +-H, *-HATCH +HE, *HATCHEDIT +I, *INSERT +IMP, *IMPORT +IN, *INTERSECT +J, *JOIN +L, *LINE +LE, *QLEADER +LEN, *LENGTHEN +LI, *LIST +M, *MOVE +ME, *MEASURE +MI, *MIRROR +MO, *PROPERTIES +O, *OFFSET +OP, *OPTIONS +OS, *OSNAP +PE, *PEDIT +PL, *PLINE +PO, *POINT +POL, *POLYGON +PR, *PROPERTIES +PRCLOSE, *PROPERTIESCLOSE +PROPS, *PROPERTIES +PSOLID, *POLYSOLID +PYR, *PYRAMID +QC, *QUICKCALC +REC, *RECTANG +REG, *REGION +REV, *REVOLVE +RO, *ROTATE +S, *STRETCH +SC, *SCALE +SE, *DSETTINGS +SET, *SETVAR +SL, *SLICE +SN, *SNAP +SO, *SOLID +SPL, *SPLINE +SPE, *SPLINEDIT +ST, *STYLE +SU, *SUBTRACT +TB, *TABLE +TOL, *TOLERANCE +TOR, *TORUS +TR, *TRIM +UNI, *UNION +V, *VIEW + +CP, *COPY +DIMALI, *DIMALIGNED +DIMANG, *DIMANGULAR +DIMBASE, *DIMBASELINE +DIMCONT, *DIMCONTINUE +DIMDIA, *DIMDIAMETER +DIMED, *DIMEDIT +DIMTED, *DIMTEDIT +DIMLIN, *DIMLINEAR +DIMORD, *DIMORDINATE +DIMRAD, *DIMRADIUS +DIMSTY, *DIMSTYLE +DIMOVER, *DIMOVERRIDE +LEAD, *LEADER + +DDCHPROP, *PROPERTIES +DDOSNAP, *OSNAP + +BPOLY, *BOUNDARY +DDATTE, *ATTEDIT +DDIM, *DIMSTYLE +DDINSERT, *INSERT +DDRMODES, *DSETTINGS +DDSTYLE, *STYLE +DIMHORIZONTAL, *DIMLINEAR +DIMROTATED, *DIMLINEAR +DIMVERTICAL, *DIMLINEAR +DOUGHNUT, *DONUT +DTEXT, *TEXT +PAINTER, *MATCHPROP +PREFERENCES, *OPTIONS RECTANGLE, *RECTANG \ No newline at end of file diff --git a/support/qad_it.pgp b/support/qad_it.pgp index 3f389f44..68eb2b0f 100644 --- a/support/qad_it.pgp +++ b/support/qad_it.pgp @@ -1,156 +1,156 @@ -; -- Alias dei comandi -- - -; Formato degli alias dei comandi: -; ,* - -; Le seguenti sono istruzioni necessarie per la creazione di nuovi -; alias dei comandi. -; 1. Un alias dovrebbe abbreviare un comando di almeno due caratteri. -; 2. Provare con il primo carattere del comando, poi con i primi due, -; quindi con i primi tre. - -; -- Alias di esempio per i comandi di QAD -- -; Questi esempi comprendono i comandi di uso più frequente. NOTA: si consiglia -; di non modificare la presente sezione del file PGP per assicurare che la migrazione -; delle modifiche durante l'aggiornamento a versioni future di QAD venga effettuata -; correttamente. - -ALL, *ALLUNGA -ALLI, *ALLINEA -AR, *ARCO -ARQ, *ARCOQUOTA -B, *BLOCCO -BS, *BAR_STRU -C, *CERCHIO -CA, *CANCELLA -CB, *CAMBIA -CH, *PROPRIETA -CM, *CIMA -CON, *CONTORNI -CP, *COPIA -CR, *CALCRAPIDA -CR, *SCRIPT -D, *DIMSTILE -DAL, *DIMALLINEATA -DAN, *DIMANGOLO -DBA, *DIMLBASE -DCE, *DIMCENTRO -DCO, *DIMCONTINUA -DD, *DIVIDI -DDA, *DIMDISASSOCIATE -DDI, *DIMDIAMETRO -DED, *DIMEDITA -DI, *DIST -DLI, *DIMLINEARE -DML, *DIMMODILOCALE -DOR, *DIMCOORDINATA -DRA, *DIMRAGGIO -DRAP, *DIRRAPID -DRE, *DIMREASSOCIATE -DST, *DIMSTILE -EL, *ELLISSE -EN, *ELIMINA -EP, *EDITPL -ER, *EDITATRATT -ES, *ESTENDI -ESI, *EDITSPLINE -ESP, *ESPORTA -ESSU, *ESTENDISUPERF -GR, *DDGRIPS -IDS, *IMPOSTADIS -IN, *INSER -IP, *IMPORTA -ISOB, *ISOLATEOBJECTS -IT, *INTERSEZIONE -IZ, *INTERFERENZA -L, *LINEA -MDV, *MODIVAR -MIS, *MISURA -MLD, *MULTIDIR -MMD, *MODMULTIDIR -MOD, *MODTESTO -OF, *OFFSET -OFSU, *OFFSETSUPERF -OG, *ORTOGRAF -OP, *OPZIONI -OS, *OSNAP -PG, *POLIGONO -PL, *PLINEA -PLG, *POLIGONO -PO, *PUNTO -PR, *PROPRIETA -PRO, *PROPRIETA -PROP, *PROPRIETA -PSOLID, *POLISOLIDO -PTR, *TRATTEGGIO -QRI, *QUOTARIDOTTA -QRL, *QUOTARIDLIN -RA, *RACCORDO -RASU, *RACCORDASUPERF -RDT, *RIDIST -RE, *TRATTEGGIO -REG, *REGIONE -RF, *RIFESTERNI -RGZ, *REGAZIO -RICR, *RICREAVC -RIDUCI, *QUOTARIDOTTA -RIT, *RITAGLIAIMM -RIVI, *RIMUOVIVC -RMD, *RACCMULTIDIR -RT, *RETTANGOLO -RU, *RUOTA -RVIS, *RIPRVISTA -S, *SPOSTA -SL, *IMPOSTADIS -SN, *SNAP -SOT, *SOTTRAI -SP, *SPECCHIO -SPL, *SPLINE -SR, *SERIE -SS, *SCALA -ST, *STILE -STI, *STIRA -SUCH, *SUPERFCHIUSURA -SUDI, *SUPERFRACCDIFF -SUSE, *SUPERFSEZDIV -SZ, *SPEZZA -T, *TESTO -TA, *TAGLIA -TC, *TRANCIA -TD, *TESTO -TOL, *TOLLERANZA -TOR, *TORO -TZ, *TAVOLOZZESTRUMENTI -U, *UNISCI -UIR, *IUPRAPIDA -UNHIDE, *UNISOLATEOBJECTS -UNI, *UNIONE -UNISOLATE, *UNISOLATEOBJECTS -QP, *QUICKPROPERTIES -DELETE, *CANCELLA -DDEDIT, *MODTESTO -MT, *MODTESTO -AT, *ALLINEATESTO -PROPRIETARAP, *QUICKPROPERTIES - -DDCHPROP, *PROPRIETA -DDMODIFY, *PROPRIETA -DDOSNAP, *OSNAP - -ANELLO, *ANELLO -COPIAPROP, *CORRISPROP -DDATTE, *EDITATT -DDIM, *DIMSTILE -DDIMODI, *IMPOSTADIS -DDINSERT, *INSERT -DDSTILE, *STILE -DIMORIZZONTALE, *DIMLINEARE -DIMRUOTATO, *DIMLINEARE -DIMVERTICALE, *DIMLINEARE -EDITARETINO, *EDITATRATT -PPOLI, *CONTORNO -PREFERENZE, *OPZIONI -RETINO, *TRATTEGGIO -TESTODIN, *TESTO - -RETTANG, *RETTANGOLO +; -- Alias dei comandi -- + +; Formato degli alias dei comandi: +; ,* + +; Le seguenti sono istruzioni necessarie per la creazione di nuovi +; alias dei comandi. +; 1. Un alias dovrebbe abbreviare un comando di almeno due caratteri. +; 2. Provare con il primo carattere del comando, poi con i primi due, +; quindi con i primi tre. + +; -- Alias di esempio per i comandi di QAD -- +; Questi esempi comprendono i comandi di uso più frequente. NOTA: si consiglia +; di non modificare la presente sezione del file PGP per assicurare che la migrazione +; delle modifiche durante l'aggiornamento a versioni future di QAD venga effettuata +; correttamente. + +ALL, *ALLUNGA +ALLI, *ALLINEA +AR, *ARCO +ARQ, *ARCOQUOTA +B, *BLOCCO +BS, *BAR_STRU +C, *CERCHIO +CA, *CANCELLA +CB, *CAMBIA +CH, *PROPRIETA +CM, *CIMA +CON, *CONTORNI +CP, *COPIA +CR, *CALCRAPIDA +CR, *SCRIPT +D, *DIMSTILE +DAL, *DIMALLINEATA +DAN, *DIMANGOLO +DBA, *DIMLBASE +DCE, *DIMCENTRO +DCO, *DIMCONTINUA +DD, *DIVIDI +DDA, *DIMDISASSOCIATE +DDI, *DIMDIAMETRO +DED, *DIMEDITA +DI, *DIST +DLI, *DIMLINEARE +DML, *DIMMODILOCALE +DOR, *DIMCOORDINATA +DRA, *DIMRAGGIO +DRAP, *DIRRAPID +DRE, *DIMREASSOCIATE +DST, *DIMSTILE +EL, *ELLISSE +EN, *ELIMINA +EP, *EDITPL +ER, *EDITATRATT +ES, *ESTENDI +ESI, *EDITSPLINE +ESP, *ESPORTA +ESSU, *ESTENDISUPERF +GR, *DDGRIPS +IDS, *IMPOSTADIS +IN, *INSER +IP, *IMPORTA +ISOB, *ISOLATEOBJECTS +IT, *INTERSEZIONE +IZ, *INTERFERENZA +L, *LINEA +MDV, *MODIVAR +MIS, *MISURA +MLD, *MULTIDIR +MMD, *MODMULTIDIR +MOD, *MODTESTO +OF, *OFFSET +OFSU, *OFFSETSUPERF +OG, *ORTOGRAF +OP, *OPZIONI +OS, *OSNAP +PG, *POLIGONO +PL, *PLINEA +PLG, *POLIGONO +PO, *PUNTO +PR, *PROPRIETA +PRO, *PROPRIETA +PROP, *PROPRIETA +PSOLID, *POLISOLIDO +PTR, *TRATTEGGIO +QRI, *QUOTARIDOTTA +QRL, *QUOTARIDLIN +RA, *RACCORDO +RASU, *RACCORDASUPERF +RDT, *RIDIST +RE, *TRATTEGGIO +REG, *REGIONE +RF, *RIFESTERNI +RGZ, *REGAZIO +RICR, *RICREAVC +RIDUCI, *QUOTARIDOTTA +RIT, *RITAGLIAIMM +RIVI, *RIMUOVIVC +RMD, *RACCMULTIDIR +RT, *RETTANGOLO +RU, *RUOTA +RVIS, *RIPRVISTA +S, *SPOSTA +SL, *IMPOSTADIS +SN, *SNAP +SOT, *SOTTRAI +SP, *SPECCHIO +SPL, *SPLINE +SR, *SERIE +SS, *SCALA +ST, *STILE +STI, *STIRA +SUCH, *SUPERFCHIUSURA +SUDI, *SUPERFRACCDIFF +SUSE, *SUPERFSEZDIV +SZ, *SPEZZA +T, *TESTO +TA, *TAGLIA +TC, *TRANCIA +TD, *TESTO +TOL, *TOLLERANZA +TOR, *TORO +TZ, *TAVOLOZZESTRUMENTI +U, *UNISCI +UIR, *IUPRAPIDA +UNHIDE, *UNISOLATEOBJECTS +UNI, *UNIONE +UNISOLATE, *UNISOLATEOBJECTS +QP, *QUICKPROPERTIES +DELETE, *CANCELLA +DDEDIT, *MODTESTO +MT, *MODTESTO +AT, *ALLINEATESTO +PROPRIETARAP, *QUICKPROPERTIES + +DDCHPROP, *PROPRIETA +DDMODIFY, *PROPRIETA +DDOSNAP, *OSNAP + +ANELLO, *ANELLO +COPIAPROP, *CORRISPROP +DDATTE, *EDITATT +DDIM, *DIMSTILE +DDIMODI, *IMPOSTADIS +DDINSERT, *INSERT +DDSTILE, *STILE +DIMORIZZONTALE, *DIMLINEARE +DIMRUOTATO, *DIMLINEARE +DIMVERTICALE, *DIMLINEARE +EDITARETINO, *EDITATRATT +PPOLI, *CONTORNO +PREFERENZE, *OPZIONI +RETINO, *TRATTEGGIO +TESTODIN, *TESTO + +RETTANG, *RETTANGOLO